diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml
index 95cb5fa8ce0..d305bed2dcd 100644
--- a/.github/workflows/gnoland.yml
+++ b/.github/workflows/gnoland.yml
@@ -60,9 +60,7 @@ jobs:
- _test.gnoland
- _test.gnokey
- _test.pkgs
- # XXX: test broken, should be rewritten to run an inmemory localnode
- # Re-add to makefile when fixed. Tracked here: https://github.com/gnolang/gno/issues/1222
- #- _test.gnoweb
+ - _test.gnoweb
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
@@ -78,7 +76,7 @@ jobs:
export LOG_DIR="${{ runner.temp }}/logs/test-${{ matrix.goversion }}-gnoland"
make ${{ matrix.args }}
- name: Upload Test Log
- if: always()
+ if: always()
uses: actions/upload-artifact@v3
with:
name: logs-test-gnoland-go${{ matrix.goversion }}
@@ -101,7 +99,7 @@ jobs:
uses: codecov/codecov-action@v3
with:
directory: ${{ runner.temp }}/coverage
- token: ${{ secrets.CODECOV_TOKEN }}
+ token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: ${{ github.repository == 'gnolang/gno' }}
docker-integration:
diff --git a/gno.land/Makefile b/gno.land/Makefile
index 22b9ec24650..29c192e9987 100644
--- a/gno.land/Makefile
+++ b/gno.land/Makefile
@@ -48,9 +48,7 @@ fmt:
########################################
# Test suite
.PHONY: test
-test: _test.gnoland _test.gnokey _test.pkgs
-# XXX: _test.gnoweb is currently disabled. If fixed, re-enable here and in CI.
-# https://github.com/gnolang/gno/issues/1222
+test: _test.gnoland _test.gnoweb _test.gnokey _test.pkgs
GOTEST_FLAGS ?= -v -p 1 -timeout=30m
diff --git a/gno.land/cmd/genesis/balances_add.go b/gno.land/cmd/genesis/balances_add.go
index 276e48690a8..8df193c770c 100644
--- a/gno.land/cmd/genesis/balances_add.go
+++ b/gno.land/cmd/genesis/balances_add.go
@@ -8,32 +8,22 @@ import (
"fmt"
"io"
"os"
- "regexp"
- "strconv"
"strings"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/commands"
- "github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/sdk/bank"
"github.com/gnolang/gno/tm2/pkg/std"
_ "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
)
-var (
- balanceRegex = regexp.MustCompile(`^(\w+)=(\d+)ugnot$`)
- amountRegex = regexp.MustCompile(`^(\d+)ugnot$`)
-)
-
var (
errNoBalanceSource = errors.New("at least one balance source must be set")
errBalanceParsingAborted = errors.New("balance parsing aborted")
- errInvalidBalanceFormat = errors.New("invalid balance format encountered")
errInvalidAddress = errors.New("invalid address encountered")
- errInvalidAmount = errors.New("invalid amount encountered")
)
type balancesAddCfg struct {
@@ -152,7 +142,7 @@ func execBalancesAdd(ctx context.Context, cfg *balancesAddCfg, io *commands.IO)
// Construct the initial genesis balance sheet
state := genesis.AppState.(gnoland.GnoGenesisState)
- genesisBalances, err := extractGenesisBalances(state)
+ genesisBalances, err := mapGenesisBalancesFromState(state)
if err != nil {
return err
}
@@ -190,12 +180,11 @@ func getBalancesFromEntries(entries []string) (accountBalances, error) {
balances := make(accountBalances)
for _, entry := range entries {
- accountBalance, err := getBalanceFromEntry(entry)
- if err != nil {
- return nil, fmt.Errorf("unable to extract balance data, %w", err)
+ var balance gnoland.Balance
+ if err := balance.Parse(entry); err != nil {
+ return nil, fmt.Errorf("unable to parse balance entry: %w", err)
}
-
- balances[accountBalance.address] = accountBalance.amount
+ balances[balance.Address] = balance
}
return balances, nil
@@ -220,12 +209,12 @@ func getBalancesFromSheet(sheet io.Reader) (accountBalances, error) {
continue
}
- accountBalance, err := getBalanceFromEntry(entry)
- if err != nil {
+ var balance gnoland.Balance
+ if err := balance.Parse(entry); err != nil {
return nil, fmt.Errorf("unable to extract balance data, %w", err)
}
- balances[accountBalance.address] = accountBalance.amount
+ balances[balance.Address] = balance
}
if err := scanner.Err(); err != nil {
@@ -262,21 +251,19 @@ func getBalancesFromTransactions(
if err := amino.UnmarshalJSON(line, &tx); err != nil {
io.ErrPrintfln(
- "invalid amino JSON encountered: %s",
+ "invalid amino JSON encountered: %q",
string(line),
)
continue
}
- feeAmount, err := getAmountFromEntry(tx.Fee.GasFee.String())
- if err != nil {
+ feeAmount := std.NewCoins(tx.Fee.GasFee)
+ if feeAmount.AmountOf("ugnot") <= 0 {
io.ErrPrintfln(
- "invalid gas fee amount encountered: %s",
+ "invalid gas fee amount encountered: %q",
tx.Fee.GasFee.String(),
)
-
- continue
}
for _, msg := range tx.Msgs {
@@ -286,13 +273,12 @@ func getBalancesFromTransactions(
msgSend := msg.(bank.MsgSend)
- sendAmount, err := getAmountFromEntry(msgSend.Amount.String())
- if err != nil {
+ sendAmount := msgSend.Amount
+ if sendAmount.AmountOf("ugnot") <= 0 {
io.ErrPrintfln(
"invalid send amount encountered: %s",
msgSend.Amount.String(),
)
-
continue
}
@@ -304,27 +290,35 @@ func getBalancesFromTransactions(
// causes an accounts balance to go < 0. In these cases,
// we initialize the account (it is present in the balance sheet), but
// with the balance of 0
- from := balances[msgSend.FromAddress]
- to := balances[msgSend.ToAddress]
- to += sendAmount
+ from := balances[msgSend.FromAddress].Amount
+ to := balances[msgSend.ToAddress].Amount
+
+ to = to.Add(sendAmount)
- if from < sendAmount || from < feeAmount {
+ if from.IsAllLT(sendAmount) || from.IsAllLT(feeAmount) {
// Account cannot cover send amount / fee
// (see message above)
- from = 0
+ from = std.NewCoins(std.NewCoin("ugnot", 0))
}
- if from > sendAmount {
- from -= sendAmount
+ if from.IsAllGT(sendAmount) {
+ from = from.Sub(sendAmount)
}
- if from > feeAmount {
- from -= feeAmount
+ if from.IsAllGT(feeAmount) {
+ from = from.Sub(feeAmount)
}
- balances[msgSend.FromAddress] = from
- balances[msgSend.ToAddress] = to
+ // Set new balance
+ balances[msgSend.FromAddress] = gnoland.Balance{
+ Address: msgSend.FromAddress,
+ Amount: from,
+ }
+ balances[msgSend.ToAddress] = gnoland.Balance{
+ Address: msgSend.ToAddress,
+ Amount: to,
+ }
}
}
}
@@ -340,65 +334,14 @@ func getBalancesFromTransactions(
return balances, nil
}
-// getAmountFromEntry
-func getAmountFromEntry(entry string) (int64, error) {
- matches := amountRegex.FindStringSubmatch(entry)
-
- // Check if there is a match
- if len(matches) != 2 {
- return 0, fmt.Errorf(
- "invalid amount, %s",
- entry,
- )
- }
-
- amount, err := strconv.ParseInt(matches[1], 10, 64)
- if err != nil {
- return 0, fmt.Errorf("invalid amount, %s", matches[1])
- }
-
- return amount, nil
-}
-
-// getBalanceFromEntry extracts the account balance information
-// from a single line in the form of:
=ugnot
-func getBalanceFromEntry(entry string) (*accountBalance, error) {
- matches := balanceRegex.FindStringSubmatch(entry)
- if len(matches) != 3 {
- return nil, fmt.Errorf("%w, %s", errInvalidBalanceFormat, entry)
- }
-
- // Validate the address
- address, err := crypto.AddressFromString(matches[1])
- if err != nil {
- return nil, fmt.Errorf("%w, %w", errInvalidAddress, err)
- }
-
- // Validate the amount
- amount, err := strconv.ParseInt(matches[2], 10, 64)
- if err != nil {
- return nil, fmt.Errorf("%w, %w", errInvalidAmount, err)
- }
-
- return &accountBalance{
- address: address,
- amount: amount,
- }, nil
-}
-
-// extractGenesisBalances extracts the initial account balances from the
+// mapGenesisBalancesFromState extracts the initial account balances from the
// genesis app state
-func extractGenesisBalances(state gnoland.GnoGenesisState) (accountBalances, error) {
+func mapGenesisBalancesFromState(state gnoland.GnoGenesisState) (accountBalances, error) {
// Construct the initial genesis balance sheet
genesisBalances := make(accountBalances)
- for _, entry := range state.Balances {
- accountBalance, err := getBalanceFromEntry(entry)
- if err != nil {
- return nil, fmt.Errorf("invalid genesis balance entry, %w", err)
- }
-
- genesisBalances[accountBalance.address] = accountBalance.amount
+ for _, balance := range state.Balances {
+ genesisBalances[balance.Address] = balance
}
return genesisBalances, nil
diff --git a/gno.land/cmd/genesis/balances_add_test.go b/gno.land/cmd/genesis/balances_add_test.go
index f986ee85274..73e2fe148a2 100644
--- a/gno.land/cmd/genesis/balances_add_test.go
+++ b/gno.land/cmd/genesis/balances_add_test.go
@@ -98,7 +98,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
tempGenesis.Name(),
}
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
for _, dummyKey := range dummyKeys {
args = append(args, "--single")
@@ -107,7 +107,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
fmt.Sprintf(
"%s=%dugnot",
dummyKey.Address().String(),
- amount,
+ amount.AmountOf("ugnot"),
),
)
}
@@ -127,16 +127,13 @@ func TestGenesis_Balances_Add(t *testing.T) {
require.Equal(t, len(dummyKeys), len(state.Balances))
- for _, entry := range state.Balances {
- accountBalance, err := getBalanceFromEntry(entry)
- require.NoError(t, err)
-
+ for _, balance := range state.Balances {
// Find the appropriate key
// (the genesis is saved with randomized balance order)
found := false
for _, dummyKey := range dummyKeys {
- if dummyKey.Address().String() == accountBalance.address.String() {
- assert.Equal(t, amount, accountBalance.amount)
+ if dummyKey.Address().String() == balance.Address.String() {
+ assert.Equal(t, amount, balance.Amount)
found = true
break
@@ -144,7 +141,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
}
if !found {
- t.Fatalf("unexpected entry with address %s found", accountBalance.address.String())
+ t.Fatalf("unexpected entry with address %s found", balance.Address.String())
}
}
})
@@ -159,7 +156,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
require.NoError(t, genesis.SaveAs(tempGenesis.Name()))
dummyKeys := getDummyKeys(t, 10)
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
balances := make([]string, len(dummyKeys))
@@ -170,7 +167,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
balances[index] = fmt.Sprintf(
"%s=%dugnot",
key.Address().String(),
- amount,
+ amount.AmountOf("ugnot"),
)
}
@@ -207,16 +204,13 @@ func TestGenesis_Balances_Add(t *testing.T) {
require.Equal(t, len(dummyKeys), len(state.Balances))
- for _, entry := range state.Balances {
- accountBalance, err := getBalanceFromEntry(entry)
- require.NoError(t, err)
-
+ for _, balance := range state.Balances {
// Find the appropriate key
// (the genesis is saved with randomized balance order)
found := false
for _, dummyKey := range dummyKeys {
- if dummyKey.Address().String() == accountBalance.address.String() {
- assert.Equal(t, amount, accountBalance.amount)
+ if dummyKey.Address().String() == balance.Address.String() {
+ assert.Equal(t, amount, balance.Amount)
found = true
break
@@ -224,7 +218,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
}
if !found {
- t.Fatalf("unexpected entry with address %s found", accountBalance.address.String())
+ t.Fatalf("unexpected entry with address %s found", balance.Address.String())
}
}
})
@@ -240,8 +234,8 @@ func TestGenesis_Balances_Add(t *testing.T) {
var (
dummyKeys = getDummyKeys(t, 10)
- amount = int64(10)
- amountCoins = std.NewCoins(std.NewCoin("ugnot", amount))
+ amount = std.NewCoins(std.NewCoin("ugnot", 10))
+ amountCoins = std.NewCoins(std.NewCoin("ugnot", 10))
gasFee = std.NewCoin("ugnot", 1000000)
txs = make([]std.Tx, 0)
)
@@ -309,10 +303,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
require.Equal(t, len(dummyKeys), len(state.Balances))
- for _, entry := range state.Balances {
- accountBalance, err := getBalanceFromEntry(entry)
- require.NoError(t, err)
-
+ for _, balance := range state.Balances {
// Find the appropriate key
// (the genesis is saved with randomized balance order)
found := false
@@ -321,11 +312,11 @@ func TestGenesis_Balances_Add(t *testing.T) {
if index == 0 {
// the first address should
// have a balance of 0
- checkAmount = 0
+ checkAmount = std.NewCoins(std.NewCoin("ugnot", 0))
}
- if dummyKey.Address().String() == accountBalance.address.String() {
- assert.Equal(t, checkAmount, accountBalance.amount)
+ if dummyKey.Address().String() == balance.Address.String() {
+ assert.True(t, balance.Amount.IsEqual(checkAmount))
found = true
break
@@ -333,7 +324,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
}
if !found {
- t.Fatalf("unexpected entry with address %s found", accountBalance.address.String())
+ t.Fatalf("unexpected entry with address %s found", balance.Address.String())
}
}
})
@@ -349,12 +340,11 @@ func TestGenesis_Balances_Add(t *testing.T) {
genesis := getDefaultGenesis()
state := gnoland.GnoGenesisState{
// Set an initial balance value
- Balances: []string{
- fmt.Sprintf(
- "%s=%dugnot",
- dummyKeys[0].Address().String(),
- 100,
- ),
+ Balances: []gnoland.Balance{
+ {
+ Address: dummyKeys[0].Address(),
+ Amount: std.NewCoins(std.NewCoin("ugnot", 100)),
+ },
},
}
genesis.AppState = state
@@ -369,7 +359,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
tempGenesis.Name(),
}
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
for _, dummyKey := range dummyKeys {
args = append(args, "--single")
@@ -378,7 +368,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
fmt.Sprintf(
"%s=%dugnot",
dummyKey.Address().String(),
- amount,
+ amount.AmountOf("ugnot"),
),
)
}
@@ -398,16 +388,13 @@ func TestGenesis_Balances_Add(t *testing.T) {
require.Equal(t, len(dummyKeys), len(state.Balances))
- for _, entry := range state.Balances {
- accountBalance, err := getBalanceFromEntry(entry)
- require.NoError(t, err)
-
+ for _, balance := range state.Balances {
// Find the appropriate key
// (the genesis is saved with randomized balance order)
found := false
for _, dummyKey := range dummyKeys {
- if dummyKey.Address().String() == accountBalance.address.String() {
- assert.Equal(t, amount, accountBalance.amount)
+ if dummyKey.Address().String() == balance.Address.String() {
+ assert.Equal(t, amount, balance.Amount)
found = true
break
@@ -415,7 +402,7 @@ func TestGenesis_Balances_Add(t *testing.T) {
}
if !found {
- t.Fatalf("unexpected entry with address %s found", accountBalance.address.String())
+ t.Fatalf("unexpected entry with address %s found", balance.Address.String())
}
}
})
@@ -429,7 +416,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
// Generate dummy keys
dummyKeys := getDummyKeys(t, 2)
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
balances := make([]string, len(dummyKeys))
@@ -437,7 +424,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
balances[index] = fmt.Sprintf(
"%s=%dugnot",
key.Address().String(),
- amount,
+ amount.AmountOf("ugnot"),
)
}
@@ -447,7 +434,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
// Validate the balance map
assert.Len(t, balanceMap, len(dummyKeys))
for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()])
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
}
})
@@ -461,7 +448,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
balanceMap, err := getBalancesFromEntries(balances)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, errInvalidBalanceFormat.Error())
+ assert.ErrorContains(t, err, "malformed entry")
})
t.Run("malformed balance, invalid address", func(t *testing.T) {
@@ -474,7 +461,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
balanceMap, err := getBalancesFromEntries(balances)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, errInvalidAddress.Error())
+ assert.ErrorContains(t, err, "invalid address")
})
t.Run("malformed balance, invalid amount", func(t *testing.T) {
@@ -493,7 +480,7 @@ func TestBalances_GetBalancesFromEntries(t *testing.T) {
balanceMap, err := getBalancesFromEntries(balances)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, errInvalidAmount.Error())
+ assert.ErrorContains(t, err, "invalid amount")
})
}
@@ -505,7 +492,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
// Generate dummy keys
dummyKeys := getDummyKeys(t, 2)
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
balances := make([]string, len(dummyKeys))
@@ -513,7 +500,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
balances[index] = fmt.Sprintf(
"%s=%dugnot",
key.Address().String(),
- amount,
+ amount.AmountOf("ugnot"),
)
}
@@ -524,7 +511,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
// Validate the balance map
assert.Len(t, balanceMap, len(dummyKeys))
for _, key := range dummyKeys {
- assert.Equal(t, amount, balanceMap[key.Address()])
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
}
})
@@ -546,7 +533,7 @@ func TestBalances_GetBalancesFromSheet(t *testing.T) {
balanceMap, err := getBalancesFromSheet(reader)
assert.Nil(t, balanceMap)
- assert.ErrorContains(t, err, errInvalidAmount.Error())
+ assert.ErrorContains(t, err, "invalid amount")
})
}
@@ -558,8 +545,8 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
var (
dummyKeys = getDummyKeys(t, 10)
- amount = int64(10)
- amountCoins = std.NewCoins(std.NewCoin("ugnot", amount))
+ amount = std.NewCoins(std.NewCoin("ugnot", 10))
+ amountCoins = std.NewCoins(std.NewCoin("ugnot", 10))
gasFee = std.NewCoin("ugnot", 1000000)
txs = make([]std.Tx, 0)
)
@@ -605,10 +592,10 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
// Validate the balance map
assert.Len(t, balanceMap, len(dummyKeys))
for _, key := range dummyKeys[1:] {
- assert.Equal(t, amount, balanceMap[key.Address()])
+ assert.Equal(t, amount, balanceMap[key.Address()].Amount)
}
- assert.Equal(t, int64(0), balanceMap[sender.Address()])
+ assert.Equal(t, std.Coins{}, balanceMap[sender.Address()].Amount)
})
t.Run("malformed transaction, invalid fee amount", func(t *testing.T) {
@@ -616,8 +603,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
var (
dummyKeys = getDummyKeys(t, 10)
- amount = int64(10)
- amountCoins = std.NewCoins(std.NewCoin("ugnot", amount))
+ amountCoins = std.NewCoins(std.NewCoin("ugnot", 10))
gasFee = std.NewCoin("gnos", 1) // invalid fee
txs = make([]std.Tx, 0)
)
@@ -669,8 +655,7 @@ func TestBalances_GetBalancesFromTransactions(t *testing.T) {
var (
dummyKeys = getDummyKeys(t, 10)
- amount = int64(10)
- amountCoins = std.NewCoins(std.NewCoin("gnogno", amount)) // invalid send amount
+ amountCoins = std.NewCoins(std.NewCoin("gnogno", 10)) // invalid send amount
gasFee = std.NewCoin("ugnot", 1)
txs = make([]std.Tx, 0)
)
diff --git a/gno.land/cmd/genesis/balances_export_test.go b/gno.land/cmd/genesis/balances_export_test.go
index 33e4f7bc800..d7441fd438f 100644
--- a/gno.land/cmd/genesis/balances_export_test.go
+++ b/gno.land/cmd/genesis/balances_export_test.go
@@ -3,31 +3,30 @@ package main
import (
"bufio"
"context"
- "fmt"
"testing"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/gnolang/gno/tm2/pkg/std"
"github.com/gnolang/gno/tm2/pkg/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-// getDummyBalanceLines generates dummy balance lines
-func getDummyBalanceLines(t *testing.T, count int) []string {
+// getDummyBalances generates dummy balance lines
+func getDummyBalances(t *testing.T, count int) []gnoland.Balance {
t.Helper()
dummyKeys := getDummyKeys(t, count)
- amount := int64(10)
+ amount := std.NewCoins(std.NewCoin("ugnot", 10))
- balances := make([]string, len(dummyKeys))
+ balances := make([]gnoland.Balance, len(dummyKeys))
for index, key := range dummyKeys {
- balances[index] = fmt.Sprintf(
- "%s=%dugnot",
- key.Address().String(),
- amount,
- )
+ balances[index] = gnoland.Balance{
+ Address: key.Address(),
+ Amount: amount,
+ }
}
return balances
@@ -85,7 +84,7 @@ func TestGenesis_Balances_Export(t *testing.T) {
genesis := getDefaultGenesis()
genesis.AppState = gnoland.GnoGenesisState{
- Balances: getDummyBalanceLines(t, 1),
+ Balances: getDummyBalances(t, 1),
}
require.NoError(t, genesis.SaveAs(tempGenesis.Name()))
@@ -107,7 +106,7 @@ func TestGenesis_Balances_Export(t *testing.T) {
t.Parallel()
// Generate dummy balances
- balances := getDummyBalanceLines(t, 10)
+ balances := getDummyBalances(t, 10)
tempGenesis, cleanup := testutils.NewTestFile(t)
t.Cleanup(cleanup)
@@ -139,9 +138,13 @@ func TestGenesis_Balances_Export(t *testing.T) {
// Validate the transactions were written down
scanner := bufio.NewScanner(outputFile)
- outputBalances := make([]string, 0)
+ outputBalances := make([]gnoland.Balance, 0)
for scanner.Scan() {
- outputBalances = append(outputBalances, scanner.Text())
+ var balance gnoland.Balance
+ err := balance.Parse(scanner.Text())
+ require.NoError(t, err)
+
+ outputBalances = append(outputBalances, balance)
}
require.NoError(t, scanner.Err())
diff --git a/gno.land/cmd/genesis/balances_remove.go b/gno.land/cmd/genesis/balances_remove.go
index f7e9092dc3b..f4286d95ad2 100644
--- a/gno.land/cmd/genesis/balances_remove.go
+++ b/gno.land/cmd/genesis/balances_remove.go
@@ -71,7 +71,7 @@ func execBalancesRemove(cfg *balancesRemoveCfg, io *commands.IO) error {
// Construct the initial genesis balance sheet
state := genesis.AppState.(gnoland.GnoGenesisState)
- genesisBalances, err := extractGenesisBalances(state)
+ genesisBalances, err := mapGenesisBalancesFromState(state)
if err != nil {
return err
}
diff --git a/gno.land/cmd/genesis/balances_remove_test.go b/gno.land/cmd/genesis/balances_remove_test.go
index 29179c43604..b9d10d0db08 100644
--- a/gno.land/cmd/genesis/balances_remove_test.go
+++ b/gno.land/cmd/genesis/balances_remove_test.go
@@ -2,12 +2,12 @@ package main
import (
"context"
- "fmt"
"testing"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/commands"
+ "github.com/gnolang/gno/tm2/pkg/std"
"github.com/gnolang/gno/tm2/pkg/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -70,12 +70,11 @@ func TestGenesis_Balances_Remove(t *testing.T) {
genesis := getDefaultGenesis()
state := gnoland.GnoGenesisState{
// Set an initial balance value
- Balances: []string{
- fmt.Sprintf(
- "%s=%dugnot",
- dummyKey.Address().String(),
- 100,
- ),
+ Balances: []gnoland.Balance{
+ {
+ Address: dummyKey.Address(),
+ Amount: std.NewCoins(std.NewCoin("ugnot", 100)),
+ },
},
}
genesis.AppState = state
@@ -118,7 +117,7 @@ func TestGenesis_Balances_Remove(t *testing.T) {
genesis := getDefaultGenesis()
state := gnoland.GnoGenesisState{
- Balances: []string{}, // Empty initial balance
+ Balances: []gnoland.Balance{}, // Empty initial balance
}
genesis.AppState = state
require.NoError(t, genesis.SaveAs(tempGenesis.Name()))
diff --git a/gno.land/cmd/genesis/types.go b/gno.land/cmd/genesis/types.go
index 208eaddb6da..dba39ea8ec1 100644
--- a/gno.land/cmd/genesis/types.go
+++ b/gno.land/cmd/genesis/types.go
@@ -1,8 +1,7 @@
package main
import (
- "fmt"
-
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/std"
)
@@ -39,23 +38,14 @@ func (i *txStore) leftMerge(b txStore) error {
return nil
}
-type (
- accountBalances map[types.Address]int64 // address -> balance (ugnot)
- accountBalance struct {
- address types.Address
- amount int64
- }
-)
+type accountBalances map[types.Address]gnoland.Balance // address -> balance (ugnot)
// toList linearizes the account balances map
-func (a accountBalances) toList() []string {
- balances := make([]string, 0, len(a))
+func (a accountBalances) toList() []gnoland.Balance {
+ balances := make([]gnoland.Balance, 0, len(a))
- for address, balance := range a {
- balances = append(
- balances,
- fmt.Sprintf("%s=%dugnot", address, balance),
- )
+ for _, balance := range a {
+ balances = append(balances, balance)
}
return balances
diff --git a/gno.land/cmd/genesis/verify.go b/gno.land/cmd/genesis/verify.go
index ba51f5801f6..6c877ca51ec 100644
--- a/gno.land/cmd/genesis/verify.go
+++ b/gno.land/cmd/genesis/verify.go
@@ -9,7 +9,6 @@ import (
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/commands"
- "github.com/gnolang/gno/tm2/pkg/std"
)
var errInvalidGenesisState = errors.New("invalid genesis state type")
@@ -68,8 +67,8 @@ func execVerify(cfg *verifyCfg, io *commands.IO) error {
// Validate the initial balances
for _, balance := range state.Balances {
- if _, parseErr := std.ParseCoins(balance); parseErr != nil {
- return fmt.Errorf("invalid balance %s, %w", balance, parseErr)
+ if err := balance.Verify(); err != nil {
+ return fmt.Errorf("invalid balance: %w", err)
}
}
}
diff --git a/gno.land/cmd/genesis/verify_test.go b/gno.land/cmd/genesis/verify_test.go
index fcc5305b9d0..8388949898b 100644
--- a/gno.land/cmd/genesis/verify_test.go
+++ b/gno.land/cmd/genesis/verify_test.go
@@ -44,7 +44,7 @@ func TestGenesis_Verify(t *testing.T) {
g := getValidTestGenesis()
g.AppState = gnoland.GnoGenesisState{
- Balances: []string{},
+ Balances: []gnoland.Balance{},
Txs: []std.Tx{
{},
},
@@ -74,8 +74,8 @@ func TestGenesis_Verify(t *testing.T) {
g := getValidTestGenesis()
g.AppState = gnoland.GnoGenesisState{
- Balances: []string{
- "dummybalance",
+ Balances: []gnoland.Balance{
+ {},
},
Txs: []std.Tx{},
}
@@ -103,7 +103,7 @@ func TestGenesis_Verify(t *testing.T) {
g := getValidTestGenesis()
g.AppState = gnoland.GnoGenesisState{
- Balances: []string{},
+ Balances: []gnoland.Balance{},
Txs: []std.Tx{},
}
diff --git a/gno.land/cmd/gnoland/root.go b/gno.land/cmd/gnoland/root.go
index cf2a6252478..5b2cbe0e4fe 100644
--- a/gno.land/cmd/gnoland/root.go
+++ b/gno.land/cmd/gnoland/root.go
@@ -11,8 +11,7 @@ import (
)
func main() {
- io := commands.NewDefaultIO()
- cmd := newRootCmd(io)
+ cmd := newRootCmd(commands.NewDefaultIO())
if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go
index a42e1df1bf0..618f4f87a09 100644
--- a/gno.land/cmd/gnoland/start.go
+++ b/gno.land/cmd/gnoland/start.go
@@ -1,21 +1,15 @@
package main
import (
- "bufio"
"context"
"errors"
"flag"
"fmt"
- "os"
"path/filepath"
"strings"
"time"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
- vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
- gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
- "github.com/gnolang/gno/gnovm/pkg/gnomod"
- "github.com/gnolang/gno/tm2/pkg/amino"
abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
"github.com/gnolang/gno/tm2/pkg/bft/config"
"github.com/gnolang/gno/tm2/pkg/bft/node"
@@ -32,13 +26,14 @@ import (
)
type startCfg struct {
+ gnoRootDir string
skipFailingGenesisTxs bool
skipStart bool
genesisBalancesFile string
genesisTxsFile string
chainID string
genesisRemote string
- rootDir string
+ dataDir string
genesisMaxVMCycles int64
config string
@@ -64,6 +59,10 @@ func newStartCmd(io *commands.IO) *commands.Command {
}
func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
+ gnoroot := gnoland.MustGuessGnoRootDir()
+ defaultGenesisBalancesFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt")
+ defaultGenesisTxsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.txt")
+
fs.BoolVar(
&c.skipFailingGenesisTxs,
"skip-failing-genesis-txs",
@@ -81,14 +80,14 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.genesisBalancesFile,
"genesis-balances-file",
- "./genesis/genesis_balances.txt",
+ defaultGenesisBalancesFile,
"initial distribution file",
)
fs.StringVar(
&c.genesisTxsFile,
"genesis-txs-file",
- "./genesis/genesis_txs.txt",
+ defaultGenesisTxsFile,
"initial txs to replay",
)
@@ -100,8 +99,16 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
)
fs.StringVar(
- &c.rootDir,
- "root-dir",
+ &c.gnoRootDir,
+ "gnoroot-dir",
+ gnoroot,
+ "the root directory of the gno repository",
+ )
+
+ // XXX: Use home directory for this
+ fs.StringVar(
+ &c.dataDir,
+ "data-dir",
"testdir",
"directory for config and data",
)
@@ -156,11 +163,19 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
"",
fmt.Sprintf("path for the file tx event store (required if event store is '%s')", file.EventStoreType),
)
+
+ // XXX(deprecated): use data-dir instead
+ fs.StringVar(
+ &c.dataDir,
+ "root-dir",
+ "testdir",
+ "deprecated: use data-dir instead - directory for config and data",
+ )
}
func execStart(c *startCfg, io *commands.IO) error {
logger := log.NewTMLogger(log.NewSyncWriter(io.Out))
- rootDir := c.rootDir
+ dataDir := c.dataDir
var (
cfg *config.Config
@@ -174,39 +189,28 @@ func execStart(c *startCfg, io *commands.IO) error {
cfg, loadCfgErr = config.LoadConfigFile(c.nodeConfigPath)
} else {
// Load the default node configuration
- cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(rootDir, nil)
+ cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(dataDir, nil)
}
if loadCfgErr != nil {
return fmt.Errorf("unable to load node configuration, %w", loadCfgErr)
}
- // create priv validator first.
- // need it to generate genesis.json
- newPrivValKey := cfg.PrivValidatorKeyFile()
- newPrivValState := cfg.PrivValidatorStateFile()
- priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState)
-
- // write genesis file if missing.
- genesisFilePath := filepath.Join(rootDir, cfg.Genesis)
-
- genesisTxs, genesisTxsErr := loadGenesisTxs(c.genesisTxsFile, c.chainID, c.genesisRemote)
- if genesisTxsErr != nil {
- return fmt.Errorf("unable to load genesis txs, %w", genesisTxsErr)
- }
+ // Write genesis file if missing.
+ genesisFilePath := filepath.Join(dataDir, cfg.Genesis)
if !osm.FileExists(genesisFilePath) {
- genDoc, err := makeGenesisDoc(
- priv.GetPubKey(),
- c.chainID,
- c.genesisBalancesFile,
- genesisTxs,
- )
- if err != nil {
- return fmt.Errorf("unable to generate genesis.json, %w", err)
+ // Create priv validator first.
+ // Need it to generate genesis.json
+ newPrivValKey := cfg.PrivValidatorKeyFile()
+ newPrivValState := cfg.PrivValidatorStateFile()
+ priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState)
+ pk := priv.GetPubKey()
+
+ // Generate genesis.json file
+ if err := generateGenesisFile(genesisFilePath, pk, c); err != nil {
+ return fmt.Errorf("unable to generate genesis file: %w", err)
}
-
- writeGenesisFile(genDoc, genesisFilePath)
}
// Initialize the indexer config
@@ -214,15 +218,13 @@ func execStart(c *startCfg, io *commands.IO) error {
if err != nil {
return fmt.Errorf("unable to parse indexer config, %w", err)
}
-
cfg.TxEventStore = txEventStoreCfg
- // create application and node.
- gnoApp, err := gnoland.NewApp(rootDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles)
+ // Create application and node.
+ gnoApp, err := gnoland.NewApp(dataDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles)
if err != nil {
return fmt.Errorf("error in creating new app: %w", err)
}
-
cfg.LocalApp = gnoApp
gnoNode, err := node.DefaultNewNode(cfg, logger)
@@ -233,8 +235,7 @@ func execStart(c *startCfg, io *commands.IO) error {
fmt.Fprintln(io.Err, "Node created.")
if c.skipStart {
- fmt.Fprintln(io.Err, "'--skip-start' is set. Exiting.")
-
+ io.ErrPrintln("'--skip-start' is set. Exiting.")
return nil
}
@@ -242,215 +243,96 @@ func execStart(c *startCfg, io *commands.IO) error {
return fmt.Errorf("error in start node: %w", err)
}
- // run forever
osm.TrapSignal(func() {
if gnoNode.IsRunning() {
_ = gnoNode.Stop()
}
})
- select {} // run forever
+ // Run forever
+ select {}
}
-// getTxEventStoreConfig constructs an event store config from provided user options
-func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) {
- var cfg *eventstorecfg.Config
-
- switch c.txEventStoreType {
- case file.EventStoreType:
- if c.txEventStorePath == "" {
- return nil, errors.New("unspecified file transaction indexer path")
- }
-
- // Fill out the configuration
- cfg = &eventstorecfg.Config{
- EventStoreType: file.EventStoreType,
- Params: map[string]any{
- file.Path: c.txEventStorePath,
- },
- }
- default:
- cfg = eventstorecfg.DefaultEventStoreConfig()
- }
-
- return cfg, nil
-}
-
-// Makes a local test genesis doc with local privValidator.
-func makeGenesisDoc(
- pvPub crypto.PubKey,
- chainID string,
- genesisBalancesFile string,
- genesisTxs []std.Tx,
-) (*bft.GenesisDoc, error) {
+func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error {
gen := &bft.GenesisDoc{}
-
gen.GenesisTime = time.Now()
- gen.ChainID = chainID
+ gen.ChainID = c.chainID
gen.ConsensusParams = abci.ConsensusParams{
Block: &abci.BlockParams{
// TODO: update limits.
- MaxTxBytes: 1000000, // 1MB,
- MaxDataBytes: 2000000, // 2MB,
- MaxGas: 10000000, // 10M gas
- TimeIotaMS: 100, // 100ms
+ MaxTxBytes: 1_000_000, // 1MB,
+ MaxDataBytes: 2_000_000, // 2MB,
+ MaxGas: 10_0000_00, // 10M gas
+ TimeIotaMS: 100, // 100ms
},
}
+
gen.Validators = []bft.GenesisValidator{
{
- Address: pvPub.Address(),
- PubKey: pvPub,
+ Address: pk.Address(),
+ PubKey: pk,
Power: 10,
Name: "testvalidator",
},
}
- // Load distribution.
- balances, err := loadGenesisBalances(genesisBalancesFile)
+ // Load balances files
+ balances, err := gnoland.LoadGenesisBalancesFile(c.genesisBalancesFile)
if err != nil {
- return nil, fmt.Errorf("unable to load genesis balances, %w", err)
+ return fmt.Errorf("unable to load genesis balances file %q: %w", c.genesisBalancesFile, err)
}
- // Load initial packages from examples.
+ // Load examples folder
+ examplesDir := filepath.Join(c.gnoRootDir, "examples")
test1 := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
- txs := []std.Tx{}
-
- // List initial packages to load from examples.
- pkgs, err := gnomod.ListPkgs(filepath.Join("..", "examples"))
+ defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
+ pkgsTxs, err := gnoland.LoadPackagesFromDir(examplesDir, test1, defaultFee, nil)
if err != nil {
- panic(fmt.Errorf("listing gno packages: %w", err))
+ return fmt.Errorf("unable to load examples folder: %w", err)
}
- // Sort packages by dependencies.
- sortedPkgs, err := pkgs.Sort()
+ // Load Genesis TXs
+ genesisTxs, err := gnoland.LoadGenesisTxsFile(c.genesisTxsFile, c.chainID, c.genesisRemote)
if err != nil {
- panic(fmt.Errorf("sorting packages: %w", err))
+ return fmt.Errorf("unable to load genesis txs file: %w", err)
}
- // Filter out draft packages.
- nonDraftPkgs := sortedPkgs.GetNonDraftPkgs()
-
- for _, pkg := range nonDraftPkgs {
- // open files in directory as MemPackage.
- memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
-
- var tx std.Tx
- tx.Msgs = []std.Msg{
- vmm.MsgAddPackage{
- Creator: test1,
- Package: memPkg,
- Deposit: nil,
- },
- }
- tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
- tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
- txs = append(txs, tx)
- }
+ genesisTxs = append(pkgsTxs, genesisTxs...)
- // load genesis txs from file.
- txs = append(txs, genesisTxs...)
-
- // construct genesis AppState.
+ // Construct genesis AppState.
gen.AppState = gnoland.GnoGenesisState{
Balances: balances,
- Txs: txs,
- }
- return gen, nil
-}
-
-func writeGenesisFile(gen *bft.GenesisDoc, filePath string) {
- err := gen.SaveAs(filePath)
- if err != nil {
- panic(err)
- }
-}
-
-func loadGenesisTxs(
- path string,
- chainID string,
- genesisRemote string,
-) ([]std.Tx, error) {
- txs := make([]std.Tx, 0)
-
- if !osm.FileExists(path) {
- // No initial transactions
- return txs, nil
- }
-
- txsFile, openErr := os.Open(path)
- if openErr != nil {
- return nil, fmt.Errorf("unable to open genesis txs file, %w", openErr)
- }
-
- scanner := bufio.NewScanner(txsFile)
-
- for scanner.Scan() {
- txLine := scanner.Text()
-
- if txLine == "" {
- continue // skip empty line
- }
-
- // patch the TX
- txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID)
- txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote)
-
- var tx std.Tx
-
- if unmarshalErr := amino.UnmarshalJSON([]byte(txLine), &tx); unmarshalErr != nil {
- return nil, fmt.Errorf("unable to amino unmarshal tx, %w", unmarshalErr)
- }
-
- txs = append(txs, tx)
+ Txs: genesisTxs,
}
- if scanErr := scanner.Err(); scanErr != nil {
- return nil, fmt.Errorf("error encountered while scanning, %w", scanErr)
+ // Write genesis state
+ if err := gen.SaveAs(genesisFile); err != nil {
+ return fmt.Errorf("unable to write genesis file %q: %w", genesisFile, err)
}
- return txs, nil
+ return nil
}
-func loadGenesisBalances(path string) ([]string, error) {
- // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot
- balances := make([]string, 0)
-
- if !osm.FileExists(path) {
- // No initial balances
- return balances, nil
- }
-
- balancesFile, openErr := os.Open(path)
- if openErr != nil {
- return nil, fmt.Errorf("unable to open genesis balances file, %w", openErr)
- }
-
- scanner := bufio.NewScanner(balancesFile)
-
- for scanner.Scan() {
- line := scanner.Text()
-
- line = strings.TrimSpace(line)
-
- // remove comments.
- line = strings.Split(line, "#")[0]
- line = strings.TrimSpace(line)
+// getTxEventStoreConfig constructs an event store config from provided user options
+func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) {
+ var cfg *eventstorecfg.Config
- // skip empty lines.
- if line == "" {
- continue
+ switch c.txEventStoreType {
+ case file.EventStoreType:
+ if c.txEventStorePath == "" {
+ return nil, errors.New("unspecified file transaction indexer path")
}
- if len(strings.Split(line, "=")) != 2 {
- return nil, fmt.Errorf("invalid genesis_balance line: %s", line)
+ // Fill out the configuration
+ cfg = &eventstorecfg.Config{
+ EventStoreType: file.EventStoreType,
+ Params: map[string]any{
+ file.Path: c.txEventStorePath,
+ },
}
-
- balances = append(balances, line)
- }
-
- if scanErr := scanner.Err(); scanErr != nil {
- return nil, fmt.Errorf("error encountered while scanning, %w", scanErr)
+ default:
+ cfg = eventstorecfg.DefaultEventStoreConfig()
}
- return balances, nil
+ return cfg, nil
}
diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar
index 5e871b058ac..5f1ee0caf49 100644
--- a/gno.land/cmd/gnoland/testdata/addpkg.txtar
+++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar
@@ -10,7 +10,11 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000
gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1
## compare render
-cmp stdout stdout.golden
+stdout '("hello from foo" string)'
+stdout 'OK!'
+stdout 'GAS WANTED: 2000000'
+stdout 'GAS USED: [0-9]+'
+
-- bar.gno --
package bar
@@ -19,8 +23,3 @@ func Render(path string) string {
return "hello from foo"
}
--- stdout.golden --
-("hello from foo" string)
-OK!
-GAS WANTED: 2000000
-GAS USED: 69163
\ No newline at end of file
diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go
index 0d9398cb8e2..b080e0b403d 100644
--- a/gno.land/cmd/gnoweb/main.go
+++ b/gno.land/cmd/gnoweb/main.go
@@ -486,11 +486,11 @@ func writeError(w http.ResponseWriter, err error) {
// XXX: writeError should return an error page template.
w.WriteHeader(500)
- details := errors.Unwrap(err).Error()
- main := err.Error()
+ fmt.Println("main", err.Error())
- fmt.Println("main", main)
- fmt.Println("details", details)
+ if details := errors.Unwrap(err); details != nil {
+ fmt.Println("details", details.Error())
+ }
w.Write([]byte(err.Error()))
}
diff --git a/gno.land/cmd/gnoweb/main_test.go b/gno.land/cmd/gnoweb/main_test.go
index 974d3f987b7..61650563405 100644
--- a/gno.land/cmd/gnoweb/main_test.go
+++ b/gno.land/cmd/gnoweb/main_test.go
@@ -4,10 +4,12 @@ import (
"fmt"
"net/http"
"net/http/httptest"
- "os"
"strings"
"testing"
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ "github.com/gnolang/gno/gno.land/pkg/integration"
+ "github.com/gnolang/gno/tm2/pkg/log"
"github.com/gotuna/gotuna/test/assert"
)
@@ -41,20 +43,17 @@ func TestRoutes(t *testing.T) {
{"/blog", found, "/r/gnoland/blog"},
{"/404-not-found", notFound, "/404-not-found"},
}
- if wd, err := os.Getwd(); err == nil {
- if strings.HasSuffix(wd, "cmd/gnoweb") {
- os.Chdir("../..")
- }
- } else {
- panic("os.Getwd() -> err: " + err.Error())
- }
- // configure default values
- flags.RemoteAddr = "127.0.0.1:26657"
- flags.HelpRemote = "127.0.0.1:26657"
+ config, _ := integration.TestingNodeConfig(t, gnoland.MustGuessGnoRootDir())
+ node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config)
+ defer node.Stop()
+
+ // set the `remoteAddr` of the client to the listening address of the
+ // node, which is randomly assigned.
+ flags.RemoteAddr = remoteAddr
flags.HelpChainID = "dev"
flags.CaptchaSite = ""
- flags.ViewsDir = "./cmd/gnoweb/views"
+ flags.ViewsDir = "../../cmd/gnoweb/views"
flags.WithAnalytics = false
app := makeApp()
@@ -93,27 +92,34 @@ func TestAnalytics(t *testing.T) {
"/404-not-found",
}
+ config, _ := integration.TestingNodeConfig(t, gnoland.MustGuessGnoRootDir())
+ node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config)
+ defer node.Stop()
+
+ flags.ViewsDir = "../../cmd/gnoweb/views"
t.Run("with", func(t *testing.T) {
for _, route := range routes {
t.Run(route, func(t *testing.T) {
+ flags.RemoteAddr = remoteAddr
flags.WithAnalytics = true
app := makeApp()
request := httptest.NewRequest(http.MethodGet, route, nil)
response := httptest.NewRecorder()
app.Router.ServeHTTP(response, request)
- assert.Contains(t, response.Body.String(), "simpleanalytics")
+ assert.Contains(t, response.Body.String(), "sa.gno.services")
})
}
})
t.Run("without", func(t *testing.T) {
for _, route := range routes {
t.Run(route, func(t *testing.T) {
+ flags.RemoteAddr = remoteAddr
flags.WithAnalytics = false
app := makeApp()
request := httptest.NewRequest(http.MethodGet, route, nil)
response := httptest.NewRecorder()
app.Router.ServeHTTP(response, request)
- assert.Equal(t, strings.Contains(response.Body.String(), "simpleanalytics"), false)
+ assert.Equal(t, strings.Contains(response.Body.String(), "sa.gno.services"), false)
})
}
})
diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go
index 3585f99d7de..a8a2736c8d1 100644
--- a/gno.land/pkg/gnoland/app.go
+++ b/gno.land/pkg/gnoland/app.go
@@ -1,10 +1,12 @@
package gnoland
import (
+ "errors"
"fmt"
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strings"
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
@@ -36,7 +38,7 @@ func NewAppOptions() *AppOptions {
return &AppOptions{
Logger: log.NewNopLogger(),
DB: dbm.NewMemDB(),
- GnoRootDir: GuessGnoRootDir(),
+ GnoRootDir: MustGuessGnoRootDir(),
}
}
@@ -73,6 +75,8 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
// Construct keepers.
acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount)
bankKpr := bank.NewBankKeeper(acctKpr)
+
+ // XXX: Embed this ?
stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs")
vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles)
@@ -142,10 +146,9 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank
genState := req.AppState.(GnoGenesisState)
// Parse and set genesis state balances.
for _, bal := range genState.Balances {
- addr, coins := parseBalance(bal)
- acc := acctKpr.NewAccountWithAddress(ctx, addr)
+ acc := acctKpr.NewAccountWithAddress(ctx, bal.Address)
acctKpr.SetAccount(ctx, acc)
- err := bankKpr.SetCoins(ctx, addr, coins)
+ err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount)
if err != nil {
panic(err)
}
@@ -195,24 +198,44 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock
}
}
-func GuessGnoRootDir() string {
- var rootdir string
+// XXX: all the method bellow should be removed in favor of
+// https://github.com/gnolang/gno/pull/1233
+func MustGuessGnoRootDir() string {
+ root, err := GuessGnoRootDir()
+ if err != nil {
+ panic(err)
+ }
+
+ return root
+}
+func GuessGnoRootDir() (string, error) {
// First try to get the root directory from the GNOROOT environment variable.
- if rootdir = os.Getenv("GNOROOT"); rootdir != "" {
- return filepath.Clean(rootdir)
+ if rootdir := os.Getenv("GNOROOT"); rootdir != "" {
+ return filepath.Clean(rootdir), nil
}
+ // Try to guess GNOROOT using the nearest go.mod.
if gobin, err := exec.LookPath("go"); err == nil {
// If GNOROOT is not set, try to guess the root directory using the `go list` command.
cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno")
out, err := cmd.CombinedOutput()
- if err != nil {
- panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err))
+ if err == nil {
+ return strings.TrimSpace(string(out)), nil
}
+ }
- return strings.TrimSpace(string(out))
+ // Try to guess GNOROOT using caller stack.
+ if _, filename, _, ok := runtime.Caller(1); ok && filepath.IsAbs(filename) {
+ if currentDir := filepath.Dir(filename); currentDir != "" {
+ // Gno root directory relative from `app.go` path:
+ // gno/ .. /gno.land/ .. /pkg/ .. /gnoland/app.go
+ rootdir, err := filepath.Abs(filepath.Join(currentDir, "..", "..", ".."))
+ if err == nil {
+ return rootdir, nil
+ }
+ }
}
- panic("no go binary available, unable to determine gno root-dir path")
+ return "", errors.New("unable to guess gno's root-directory")
}
diff --git a/gno.land/pkg/gnoland/genesis.go b/gno.land/pkg/gnoland/genesis.go
new file mode 100644
index 00000000000..e809103469d
--- /dev/null
+++ b/gno.land/pkg/gnoland/genesis.go
@@ -0,0 +1,126 @@
+package gnoland
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
+ gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
+ "github.com/gnolang/gno/gnovm/pkg/gnomod"
+ "github.com/gnolang/gno/tm2/pkg/amino"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ osm "github.com/gnolang/gno/tm2/pkg/os"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+// LoadGenesisBalancesFile loads genesis balances from the provided file path.
+func LoadGenesisBalancesFile(path string) ([]Balance, error) {
+ // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot
+ content := osm.MustReadFile(path)
+ lines := strings.Split(string(content), "\n")
+
+ balances := make([]Balance, 0, len(lines))
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+
+ // remove comments.
+ line = strings.Split(line, "#")[0]
+ line = strings.TrimSpace(line)
+
+ // skip empty lines.
+ if line == "" {
+ continue
+ }
+
+ parts := strings.Split(line, "=") // =
+ if len(parts) != 2 {
+ return nil, errors.New("invalid genesis_balance line: " + line)
+ }
+
+ addr, err := crypto.AddressFromBech32(parts[0])
+ if err != nil {
+ return nil, fmt.Errorf("invalid balance addr %s: %w", parts[0], err)
+ }
+
+ coins, err := std.ParseCoins(parts[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid balance coins %s: %w", parts[1], err)
+ }
+
+ balances = append(balances, Balance{
+ Address: addr,
+ Amount: coins,
+ })
+ }
+
+ return balances, nil
+}
+
+// LoadGenesisTxsFile loads genesis transactions from the provided file path.
+// XXX: Improve the way we generate and load this file
+func LoadGenesisTxsFile(path string, chainID string, genesisRemote string) ([]std.Tx, error) {
+ txs := []std.Tx{}
+ txsBz := osm.MustReadFile(path)
+ txsLines := strings.Split(string(txsBz), "\n")
+ for _, txLine := range txsLines {
+ if txLine == "" {
+ continue // Skip empty line.
+ }
+
+ // Patch the TX.
+ txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID)
+ txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote)
+
+ var tx std.Tx
+ if err := amino.UnmarshalJSON([]byte(txLine), &tx); err != nil {
+ return nil, fmt.Errorf("unable to Unmarshall txs file: %w", err)
+ }
+
+ txs = append(txs, tx)
+ }
+
+ return txs, nil
+}
+
+// LoadPackagesFromDir loads gno packages from a directory.
+// It creates and returns a list of transactions based on these packages.
+func LoadPackagesFromDir(dir string, creator bft.Address, fee std.Fee, deposit std.Coins) ([]std.Tx, error) {
+ // list all packages from target path
+ pkgs, err := gnomod.ListPkgs(dir)
+ if err != nil {
+ return nil, fmt.Errorf("listing gno packages: %w", err)
+ }
+
+ // Sort packages by dependencies.
+ sortedPkgs, err := pkgs.Sort()
+ if err != nil {
+ return nil, fmt.Errorf("sorting packages: %w", err)
+ }
+
+ // Filter out draft packages.
+ nonDraftPkgs := sortedPkgs.GetNonDraftPkgs()
+ txs := []std.Tx{}
+ for _, pkg := range nonDraftPkgs {
+ // Open files in directory as MemPackage.
+ memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
+
+ // Create transaction
+ tx := std.Tx{
+ Fee: fee,
+ Msgs: []std.Msg{
+ vmm.MsgAddPackage{
+ Creator: creator,
+ Package: memPkg,
+ Deposit: deposit,
+ },
+ },
+ }
+
+ tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
+ txs = append(txs, tx)
+ }
+
+ return txs, nil
+}
diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go
new file mode 100644
index 00000000000..a0ab6a51e82
--- /dev/null
+++ b/gno.land/pkg/gnoland/node_inmemory.go
@@ -0,0 +1,147 @@
+package gnoland
+
+import (
+ "fmt"
+ "time"
+
+ abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
+ tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config"
+ "github.com/gnolang/gno/tm2/pkg/bft/node"
+ "github.com/gnolang/gno/tm2/pkg/bft/proxy"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/ed25519"
+ "github.com/gnolang/gno/tm2/pkg/db"
+ "github.com/gnolang/gno/tm2/pkg/log"
+ "github.com/gnolang/gno/tm2/pkg/p2p"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+type InMemoryNodeConfig struct {
+ PrivValidator bft.PrivValidator // identity of the validator
+ Genesis *bft.GenesisDoc
+ TMConfig *tmcfg.Config
+ SkipFailingGenesisTxs bool
+ GenesisMaxVMCycles int64
+}
+
+// NewMockedPrivValidator generate a new key
+func NewMockedPrivValidator() bft.PrivValidator {
+ return bft.NewMockPVWithParams(ed25519.GenPrivKey(), false, false)
+}
+
+// NewInMemoryNodeConfig creates a default configuration for an in-memory node.
+func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc {
+ return &bft.GenesisDoc{
+ GenesisTime: time.Now(),
+ ChainID: chainid,
+ ConsensusParams: abci.ConsensusParams{
+ Block: &abci.BlockParams{
+ MaxTxBytes: 1_000_000, // 1MB,
+ MaxDataBytes: 2_000_000, // 2MB,
+ MaxGas: 10_0000_000, // 10M gas
+ TimeIotaMS: 100, // 100ms
+ },
+ },
+ AppState: &GnoGenesisState{
+ Balances: []Balance{},
+ Txs: []std.Tx{},
+ },
+ }
+}
+
+func NewDefaultTMConfig(rootdir string) *tmcfg.Config {
+ return tmcfg.DefaultConfig().SetRootDir(rootdir)
+}
+
+// NewInMemoryNodeConfig creates a default configuration for an in-memory node.
+func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig {
+ tm := NewDefaultTMConfig(rootdir)
+
+ // Create Mocked Identity
+ pv := NewMockedPrivValidator()
+ genesis := NewDefaultGenesisConfig(pv.GetPubKey(), tm.ChainID())
+
+ // Add self as validator
+ self := pv.GetPubKey()
+ genesis.Validators = []bft.GenesisValidator{
+ {
+ Address: self.Address(),
+ PubKey: self,
+ Power: 10,
+ Name: "self",
+ },
+ }
+
+ return &InMemoryNodeConfig{
+ PrivValidator: pv,
+ TMConfig: tm,
+ Genesis: genesis,
+ GenesisMaxVMCycles: 10_000_000,
+ }
+}
+
+func (cfg *InMemoryNodeConfig) validate() error {
+ if cfg.PrivValidator == nil {
+ return fmt.Errorf("`PrivValidator` is required but not provided")
+ }
+
+ if cfg.TMConfig == nil {
+ return fmt.Errorf("`TMConfig` is required but not provided")
+ }
+
+ if cfg.TMConfig.RootDir == "" {
+ return fmt.Errorf("`TMConfig.RootDir` is required to locate `stdlibs` directory")
+ }
+
+ return nil
+}
+
+// NewInMemoryNode creates an in-memory gnoland node. In this mode, the node does not
+// persist any data and uses an in-memory database. The `InMemoryNodeConfig.TMConfig.RootDir`
+// should point to the correct gno repository to load the stdlibs.
+func NewInMemoryNode(logger log.Logger, cfg *InMemoryNodeConfig) (*node.Node, error) {
+ if err := cfg.validate(); err != nil {
+ return nil, fmt.Errorf("validate config error: %w", err)
+ }
+
+ // Initialize the application with the provided options
+ gnoApp, err := NewAppWithOptions(&AppOptions{
+ Logger: logger,
+ GnoRootDir: cfg.TMConfig.RootDir,
+ SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs,
+ MaxCycles: cfg.GenesisMaxVMCycles,
+ DB: db.NewMemDB(),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("error initializing new app: %w", err)
+ }
+
+ cfg.TMConfig.LocalApp = gnoApp
+
+ // Setup app client creator
+ appClientCreator := proxy.DefaultClientCreator(
+ cfg.TMConfig.LocalApp,
+ cfg.TMConfig.ProxyApp,
+ cfg.TMConfig.ABCI,
+ cfg.TMConfig.DBDir(),
+ )
+
+ // Create genesis factory
+ genProvider := func() (*bft.GenesisDoc, error) {
+ return cfg.Genesis, nil
+ }
+
+ // generate p2p node identity
+ // XXX: do we need to configur
+ nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()}
+
+ // Create and return the in-memory node instance
+ return node.NewNode(cfg.TMConfig,
+ cfg.PrivValidator, nodekey,
+ appClientCreator,
+ genProvider,
+ node.DefaultDBProvider,
+ logger,
+ )
+}
diff --git a/gno.land/pkg/gnoland/types.go b/gno.land/pkg/gnoland/types.go
index 1c762366ae9..5d68064c9c5 100644
--- a/gno.land/pkg/gnoland/types.go
+++ b/gno.land/pkg/gnoland/types.go
@@ -1,9 +1,20 @@
package gnoland
import (
+ "errors"
+ "fmt"
+ "strings"
+
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
)
+var (
+ ErrBalanceEmptyAddress = errors.New("balance address is empty")
+ ErrBalanceEmptyAmount = errors.New("balance amount is empty")
+)
+
type GnoAccount struct {
std.BaseAccount
}
@@ -13,6 +24,56 @@ func ProtoGnoAccount() std.Account {
}
type GnoGenesisState struct {
- Balances []string `json:"balances"`
- Txs []std.Tx `json:"txs"`
+ Balances []Balance `json:"balances"`
+ Txs []std.Tx `json:"txs"`
+}
+
+type Balance struct {
+ Address bft.Address
+ Amount std.Coins
+}
+
+func (b *Balance) Verify() error {
+ if b.Address.IsZero() {
+ return ErrBalanceEmptyAddress
+ }
+
+ if b.Amount.Len() == 0 {
+ return ErrBalanceEmptyAmount
+ }
+
+ return nil
+}
+
+func (b *Balance) Parse(entry string) error {
+ parts := strings.Split(strings.TrimSpace(entry), "=") // =
+ if len(parts) != 2 {
+ return fmt.Errorf("malformed entry: %q", entry)
+ }
+
+ var err error
+
+ b.Address, err = crypto.AddressFromBech32(parts[0])
+ if err != nil {
+ return fmt.Errorf("invalid address %q: %w", parts[0], err)
+ }
+
+ b.Amount, err = std.ParseCoins(parts[1])
+ if err != nil {
+ return fmt.Errorf("invalid amount %q: %w", parts[1], err)
+ }
+
+ return nil
+}
+
+func (b *Balance) UnmarshalAmino(rep string) error {
+ return b.Parse(rep)
+}
+
+func (b Balance) MarshalAmino() (string, error) {
+ return b.String(), nil
+}
+
+func (b Balance) String() string {
+ return fmt.Sprintf("%s=%s", b.Address.String(), b.Amount.String())
}
diff --git a/gno.land/pkg/gnoland/types_test.go b/gno.land/pkg/gnoland/types_test.go
new file mode 100644
index 00000000000..97222d0cdfd
--- /dev/null
+++ b/gno.land/pkg/gnoland/types_test.go
@@ -0,0 +1,98 @@
+package gnoland
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/gnolang/gno/tm2/pkg/amino"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/jaekwon/testify/assert"
+ "github.com/jaekwon/testify/require"
+)
+
+func TestBalance_Verify(t *testing.T) {
+ validAddress := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+ emptyAmount := std.Coins{}
+ nonEmptyAmount := std.NewCoins(std.NewCoin("test", 100))
+
+ tests := []struct {
+ name string
+ balance Balance
+ expectErr bool
+ }{
+ {"empty amount", Balance{Address: validAddress, Amount: emptyAmount}, true},
+ {"empty address", Balance{Address: bft.Address{}, Amount: nonEmptyAmount}, true},
+ {"valid balance", Balance{Address: validAddress, Amount: nonEmptyAmount}, false},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ err := tc.balance.Verify()
+ if tc.expectErr {
+ assert.Error(t, err, fmt.Sprintf("TestVerifyBalance: %s", tc.name))
+ } else {
+ assert.NoError(t, err, fmt.Sprintf("TestVerifyBalance: %s", tc.name))
+ }
+ })
+ }
+}
+
+func TestBalance_Parse(t *testing.T) {
+ validAddress := crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+ validBalance := Balance{Address: validAddress, Amount: std.NewCoins(std.NewCoin("test", 100))}
+
+ tests := []struct {
+ name string
+ entry string
+ expected Balance
+ expectErr bool
+ }{
+ {"valid entry", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5=100test", validBalance, false},
+ {"invalid address", "invalid=100test", Balance{}, true},
+ {"incomplete entry", "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Balance{}, true},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ balance := Balance{}
+ err := balance.Parse(tc.entry)
+ if tc.expectErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, tc.expected, balance)
+ }
+ })
+ }
+}
+
+func TestBalance_AminoUnmarshalJSON(t *testing.T) {
+ expected := Balance{
+ Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
+ Amount: std.MustParseCoins("100ugnot"),
+ }
+ value := fmt.Sprintf("[%q]", expected.String())
+
+ var balances []Balance
+ err := amino.UnmarshalJSON([]byte(value), &balances)
+ require.NoError(t, err)
+ require.Len(t, balances, 1, "there should be one balance after unmarshaling")
+
+ balance := balances[0]
+ require.Equal(t, expected.Address, balance.Address)
+ require.True(t, expected.Amount.IsEqual(balance.Amount))
+}
+
+func TestBalance_AminoMarshalJSON(t *testing.T) {
+ expected := Balance{
+ Address: crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
+ Amount: std.MustParseCoins("100ugnot"),
+ }
+ expectedJSON := fmt.Sprintf("[%q]", expected.String())
+
+ balancesJSON, err := amino.MarshalJSON([]Balance{expected})
+ require.NoError(t, err)
+ require.JSONEq(t, expectedJSON, string(balancesJSON))
+}
diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go
deleted file mode 100644
index 318d76eea86..00000000000
--- a/gno.land/pkg/integration/gnoland.go
+++ /dev/null
@@ -1,334 +0,0 @@
-package integration
-
-import (
- "flag"
- "fmt"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "github.com/gnolang/gno/gno.land/pkg/gnoland"
- vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm"
- gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
- "github.com/gnolang/gno/gnovm/pkg/gnomod"
- "github.com/gnolang/gno/tm2/pkg/amino"
- abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
- "github.com/gnolang/gno/tm2/pkg/bft/config"
- "github.com/gnolang/gno/tm2/pkg/bft/node"
- "github.com/gnolang/gno/tm2/pkg/bft/privval"
- bft "github.com/gnolang/gno/tm2/pkg/bft/types"
- "github.com/gnolang/gno/tm2/pkg/crypto"
- "github.com/gnolang/gno/tm2/pkg/db"
- "github.com/gnolang/gno/tm2/pkg/log"
- osm "github.com/gnolang/gno/tm2/pkg/os"
- "github.com/gnolang/gno/tm2/pkg/std"
- "github.com/rogpeppe/go-internal/testscript"
-)
-
-type IntegrationConfig struct {
- SkipFailingGenesisTxs bool
- SkipStart bool
- GenesisBalancesFile string
- GenesisTxsFile string
- ChainID string
- GenesisRemote string
- RootDir string
- GenesisMaxVMCycles int64
- Config string
-}
-
-// NOTE: This is a copy of gnoland actual flags.
-// XXX: A lot this make no sense for integration.
-func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) {
- fs.BoolVar(
- &c.SkipFailingGenesisTxs,
- "skip-failing-genesis-txs",
- false,
- "don't panic when replaying invalid genesis txs",
- )
- fs.BoolVar(
- &c.SkipStart,
- "skip-start",
- false,
- "quit after initialization, don't start the node",
- )
-
- fs.StringVar(
- &c.GenesisBalancesFile,
- "genesis-balances-file",
- "./genesis/genesis_balances.txt",
- "initial distribution file",
- )
-
- fs.StringVar(
- &c.GenesisTxsFile,
- "genesis-txs-file",
- "./genesis/genesis_txs.txt",
- "initial txs to replay",
- )
-
- fs.StringVar(
- &c.ChainID,
- "chainid",
- "dev",
- "the ID of the chain",
- )
-
- fs.StringVar(
- &c.RootDir,
- "root-dir",
- "testdir",
- "directory for config and data",
- )
-
- fs.StringVar(
- &c.GenesisRemote,
- "genesis-remote",
- "localhost:26657",
- "replacement for '%%REMOTE%%' in genesis",
- )
-
- fs.Int64Var(
- &c.GenesisMaxVMCycles,
- "genesis-max-vm-cycles",
- 10_000_000,
- "set maximum allowed vm cycles per operation. Zero means no limit.",
- )
-}
-
-func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) {
- t.Helper()
-
- // Setup start config.
- icfg := &IntegrationConfig{}
- {
- fs := flag.NewFlagSet("start", flag.ExitOnError)
- icfg.RegisterFlags(fs)
-
- // Override default value for flags.
- fs.VisitAll(func(f *flag.Flag) {
- switch f.Name {
- case "root-dir":
- f.DefValue = gnoDataDir
- case "chainid":
- f.DefValue = "tendermint_test"
- case "genesis-balances-file":
- f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt")
- case "genesis-txs-file":
- f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt")
- default:
- return
- }
-
- f.Value.Set(f.DefValue)
- })
-
- if err := fs.Parse(args); err != nil {
- return nil, fmt.Errorf("unable to parse flags: %w", err)
- }
- }
-
- // Setup testing config.
- cfg := config.TestConfig().SetRootDir(gnoDataDir)
- {
- cfg.EnsureDirs()
- cfg.RPC.ListenAddress = "tcp://127.0.0.1:0"
- cfg.P2P.ListenAddress = "tcp://127.0.0.1:0"
- }
-
- // Prepare genesis.
- if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil {
- return nil, err
- }
-
- // Create application and node.
- return createAppAndNode(cfg, logger, gnoRootDir, icfg)
-}
-
-func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error {
- newPrivValKey := cfg.PrivValidatorKeyFile()
- newPrivValState := cfg.PrivValidatorStateFile()
- priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState)
-
- genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis)
- genesisDirPath := filepath.Dir(genesisFilePath)
- if err := osm.EnsureDir(genesisDirPath, 0o700); err != nil {
- return fmt.Errorf("unable to ensure directory %q: %w", genesisDirPath, err)
- }
-
- genesisTxs := loadGenesisTxs(icfg.GenesisTxsFile, icfg.ChainID, icfg.GenesisRemote)
- pvPub := priv.GetPubKey()
-
- gen := &bft.GenesisDoc{
- GenesisTime: time.Now(),
- ChainID: icfg.ChainID,
- ConsensusParams: abci.ConsensusParams{
- Block: &abci.BlockParams{
- // TODO: update limits.
- MaxTxBytes: 1000000, // 1MB,
- MaxDataBytes: 2000000, // 2MB,
- MaxGas: 10000000, // 10M gas
- TimeIotaMS: 100, // 100ms
- },
- },
- Validators: []bft.GenesisValidator{
- {
- Address: pvPub.Address(),
- PubKey: pvPub,
- Power: 10,
- Name: "testvalidator",
- },
- },
- }
-
- // Load distribution.
- balances := loadGenesisBalances(icfg.GenesisBalancesFile)
-
- // Load initial packages from examples.
- // XXX: We should be able to config this.
- test1 := crypto.MustAddressFromString(test1Addr)
- txs := []std.Tx{}
-
- // List initial packages to load from examples.
- // println(filepath.Join(gnoRootDir, "examples"))
- pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples"))
- if err != nil {
- return fmt.Errorf("listing gno packages: %w", err)
- }
-
- // Sort packages by dependencies.
- sortedPkgs, err := pkgs.Sort()
- if err != nil {
- return fmt.Errorf("sorting packages: %w", err)
- }
-
- // Filter out draft packages.
- nonDraftPkgs := sortedPkgs.GetNonDraftPkgs()
-
- for _, pkg := range nonDraftPkgs {
- // Open files in directory as MemPackage.
- memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name)
-
- var tx std.Tx
- tx.Msgs = []std.Msg{
- vmm.MsgAddPackage{
- Creator: test1,
- Package: memPkg,
- Deposit: nil,
- },
- }
-
- // XXX: Add fee flag ?
- // Or maybe reduce fee to the minimum ?
- tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
- tx.Signatures = make([]std.Signature, len(tx.GetSigners()))
- txs = append(txs, tx)
- }
-
- // Load genesis txs from file.
- txs = append(txs, genesisTxs...)
-
- // Construct genesis AppState.
- gen.AppState = gnoland.GnoGenesisState{
- Balances: balances,
- Txs: txs,
- }
-
- writeGenesisFile(gen, genesisFilePath)
-
- return nil
-}
-
-func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) {
- gnoApp, err := gnoland.NewAppWithOptions(&gnoland.AppOptions{
- Logger: logger,
- GnoRootDir: gnoRootDir,
- SkipFailingGenesisTxs: icfg.SkipFailingGenesisTxs,
- MaxCycles: icfg.GenesisMaxVMCycles,
- DB: db.NewMemDB(),
- })
- if err != nil {
- return nil, fmt.Errorf("error in creating new app: %w", err)
- }
-
- cfg.LocalApp = gnoApp
- node, err := node.DefaultNewNode(cfg, logger)
- if err != nil {
- return nil, fmt.Errorf("error in creating node: %w", err)
- }
-
- return node, node.Start()
-}
-
-func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) {
- if err != nil {
- fmt.Fprintf(ts.Stderr(), "%q error: %v\n", cmd, err)
- if !neg {
- ts.Fatalf("unexpected %q command failure: %s", cmd, err)
- }
- } else {
- if neg {
- ts.Fatalf("unexpected %s command success", cmd)
- }
- }
-}
-
-func loadGenesisTxs(
- path string,
- chainID string,
- genesisRemote string,
-) []std.Tx {
- txs := []std.Tx{}
- txsBz := osm.MustReadFile(path)
- txsLines := strings.Split(string(txsBz), "\n")
- for _, txLine := range txsLines {
- if txLine == "" {
- continue // Skip empty line.
- }
-
- // Patch the TX.
- txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID)
- txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote)
-
- var tx std.Tx
- amino.MustUnmarshalJSON([]byte(txLine), &tx)
- txs = append(txs, tx)
- }
-
- return txs
-}
-
-func loadGenesisBalances(path string) []string {
- // Each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot.
- balances := []string{}
- content := osm.MustReadFile(path)
- lines := strings.Split(string(content), "\n")
- for _, line := range lines {
- line = strings.TrimSpace(line)
-
- // Remove comments.
- line = strings.Split(line, "#")[0]
- line = strings.TrimSpace(line)
-
- // Skip empty lines.
- if line == "" {
- continue
- }
-
- parts := strings.Split(line, "=")
- if len(parts) != 2 {
- panic("invalid genesis_balance line: " + line)
- }
-
- balances = append(balances, line)
- }
- return balances
-}
-
-func writeGenesisFile(gen *bft.GenesisDoc, filePath string) {
- err := gen.SaveAs(filePath)
- if err != nil {
- panic(err)
- }
-}
diff --git a/gno.land/pkg/integration/testing.go b/gno.land/pkg/integration/testing.go
new file mode 100644
index 00000000000..7803e213da1
--- /dev/null
+++ b/gno.land/pkg/integration/testing.go
@@ -0,0 +1,39 @@
+package integration
+
+import (
+ "errors"
+
+ "github.com/jaekwon/testify/assert"
+ "github.com/jaekwon/testify/require"
+ "github.com/rogpeppe/go-internal/testscript"
+)
+
+// This error is from testscript.Fatalf and is needed to correctly
+// handle the FailNow method.
+// see: https://github.com/rogpeppe/go-internal/blob/32ae33786eccde1672d4ba373c80e1bc282bfbf6/testscript/testscript.go#L799-L812
+var errFailNow = errors.New("fail now!") //nolint:stylecheck
+
+var (
+ _ require.TestingT = (*testingTS)(nil)
+ _ assert.TestingT = (*testingTS)(nil)
+)
+
+type TestingTS = require.TestingT
+
+type testingTS struct {
+ *testscript.TestScript
+}
+
+func TSTestingT(ts *testscript.TestScript) TestingTS {
+ return &testingTS{ts}
+}
+
+func (t *testingTS) Errorf(format string, args ...interface{}) {
+ defer recover() // we can ignore recover result, we just want to catch it up
+ t.Fatalf(format, args...)
+}
+
+func (t *testingTS) FailNow() {
+ // unfortunately we can't access underlying `t.t.FailNow` method
+ panic(errFailNow)
+}
diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go
index f0a696ddd85..b773317513f 100644
--- a/gno.land/pkg/integration/testing_integration.go
+++ b/gno.land/pkg/integration/testing_integration.go
@@ -10,28 +10,18 @@ import (
"strings"
"sync"
"testing"
- "time"
"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/bft/node"
- "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto/keys"
"github.com/gnolang/gno/tm2/pkg/crypto/keys/client"
- "github.com/gnolang/gno/tm2/pkg/events"
"github.com/gnolang/gno/tm2/pkg/log"
"github.com/rogpeppe/go-internal/testscript"
)
-// XXX: This should be centralize somewhere.
-const (
- test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"
- test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast"
-)
-
type testNode struct {
*node.Node
- logger log.Logger
nGnoKeyExec uint // Counter for execution of gnokey.
}
@@ -51,15 +41,11 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
// `gnoRootDir` should point to the local location of the gno repository.
// It serves as the gno equivalent of GOROOT.
- gnoRootDir := gnoland.GuessGnoRootDir()
+ gnoRootDir := gnoland.MustGuessGnoRootDir()
// `gnoHomeDir` should be the local directory where gnokey stores keys.
gnoHomeDir := filepath.Join(tmpdir, "gno")
- // `gnoDataDir` should refer to the local location where the gnoland node
- // stores its configuration and data.
- gnoDataDir := filepath.Join(tmpdir, "data")
-
// Testscripts run concurrently by default, so we need to be prepared for that.
var muNodes sync.Mutex
nodes := map[string]*testNode{}
@@ -76,10 +62,35 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
return err
}
- // XXX: Add a command to add custom account.
- kb.CreateAccount("test1", test1Seed, "", "", 0, 0)
- env.Setenv("USER_SEED_test1", test1Seed)
- env.Setenv("USER_ADDR_test1", test1Addr)
+ // create sessions ID
+ var sid string
+ {
+ works := env.Getenv("WORK")
+ sum := crc32.ChecksumIEEE([]byte(works))
+ sid = strconv.FormatUint(uint64(sum), 16)
+ env.Setenv("SID", sid)
+ }
+
+ // setup logger
+ var logger log.Logger
+ {
+ logger = log.NewNopLogger()
+ if persistWorkDir || os.Getenv("LOG_DIR") != "" {
+ logname := fmt.Sprintf("gnoland-%s.log", sid)
+ logger, err = getTestingLogger(env, logname)
+ if err != nil {
+ return fmt.Errorf("unable to setup logger: %w", err)
+ }
+ }
+
+ env.Values["_logger"] = logger
+ }
+
+ // Setup "test1" default account
+ kb.CreateAccount(DefaultAccount_Name, DefaultAccount_Seed, "", "", 0, 0)
+
+ env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed)
+ env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address)
env.Setenv("GNOROOT", gnoRootDir)
env.Setenv("GNOHOME", gnoHomeDir)
@@ -96,7 +107,8 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
return
}
- sid := getSessionID(ts)
+ logger := ts.Value("_logger").(log.Logger) // grab logger
+ sid := ts.Getenv("SID") // grab session id
var cmd string
cmd, args = args[0], args[1:]
@@ -109,63 +121,20 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
break
}
- logger := log.NewNopLogger()
- if persistWorkDir || os.Getenv("LOG_DIR") != "" {
- logname := fmt.Sprintf("gnoland-%s.log", sid)
- logger = getTestingLogger(ts, logname)
- }
+ // Warp up `ts` so we can pass it to other testing method
+ t := TSTestingT(ts)
- dataDir := filepath.Join(gnoDataDir, sid)
- var node *node.Node
- if node, err = execTestingGnoland(t, logger, dataDir, gnoRootDir, args); err == nil {
- nodes[sid] = &testNode{
- Node: node,
- logger: logger,
- }
- ts.Defer(func() {
- muNodes.Lock()
- defer muNodes.Unlock()
-
- if n := nodes[sid]; n != nil {
- if err := n.Stop(); err != nil {
- panic(fmt.Errorf("node %q was unable to stop: %w", sid, err))
- }
- }
- })
-
- // Get listen address environment.
- // It should have been updated with the right port on start.
- laddr := node.Config().RPC.ListenAddress
-
- // Add default environements.
- ts.Setenv("RPC_ADDR", laddr)
- ts.Setenv("GNODATA", gnoDataDir)
-
- const listenerID = "testing_listener"
-
- // Wait for first block by waiting for `EventNewBlock` event.
- nb := make(chan struct{}, 1)
- node.EventSwitch().AddListener(listenerID, func(ev events.Event) {
- if _, ok := ev.(types.EventNewBlock); ok {
- select {
- case nb <- struct{}{}:
- default:
- }
- }
- })
-
- if node.BlockStore().Height() == 0 {
- select {
- case <-nb: // ok
- case <-time.After(time.Second * 6):
- ts.Fatalf("timeout while waiting for the node to start")
- }
- }
-
- node.EventSwitch().RemoveListener(listenerID)
-
- fmt.Fprintln(ts.Stdout(), "node started successfully")
- }
+ // Generate config and node
+ cfg := TestingMinimalNodeConfig(t, gnoRootDir)
+ n, remoteAddr := TestingInMemoryNode(t, logger, cfg)
+
+ // Register cleanup
+ nodes[sid] = &testNode{Node: n}
+
+ // Add default environements
+ ts.Setenv("RPC_ADDR", remoteAddr)
+
+ fmt.Fprintln(ts.Stdout(), "node started successfully")
case "stop":
n, ok := nodes[sid]
if !ok {
@@ -176,9 +145,8 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
if err = n.Stop(); err == nil {
delete(nodes, sid)
- // Unset gnoland environements.
+ // Unset gnoland environements
ts.Setenv("RPC_ADDR", "")
- ts.Setenv("GNODATA", "")
fmt.Fprintln(ts.Stdout(), "node stopped successfully")
}
default:
@@ -191,9 +159,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
muNodes.Lock()
defer muNodes.Unlock()
- sid := getSessionID(ts)
+ logger := ts.Value("_logger").(log.Logger) // grab logger
+ sid := ts.Getenv("SID") // grab session id
- // Setup IO command.
+ // Setup IO command
io := commands.NewTestIO()
io.SetOut(commands.WriteNopCloser(ts.Stdout()))
io.SetErr(commands.WriteNopCloser(ts.Stderr()))
@@ -212,9 +181,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
n.nGnoKeyExec++
headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.nGnoKeyExec)
+
// Log the command inside gnoland logger, so we can better scope errors.
- n.logger.Info(headerlog, strings.Join(args, " "))
- defer n.logger.Info(headerlog, "END")
+ logger.Info(headerlog, strings.Join(args, " "))
+ defer logger.Info(headerlog, "END")
}
// Inject default argument, if duplicate
@@ -230,35 +200,30 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
}
}
-func getSessionID(ts *testscript.TestScript) string {
- works := ts.Getenv("WORK")
- sum := crc32.ChecksumIEEE([]byte(works))
- return strconv.FormatUint(uint64(sum), 16)
-}
-
-func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger {
+func getTestingLogger(env *testscript.Env, logname string) (log.Logger, error) {
var path string
+
if logdir := os.Getenv("LOG_DIR"); logdir != "" {
if err := os.MkdirAll(logdir, 0o755); err != nil {
- ts.Fatalf("unable to make log directory %q", logdir)
+ return nil, fmt.Errorf("unable to make log directory %q", logdir)
}
var err error
if path, err = filepath.Abs(filepath.Join(logdir, logname)); err != nil {
- ts.Fatalf("uanble to get absolute path of logdir %q", logdir)
+ return nil, fmt.Errorf("uanble to get absolute path of logdir %q", logdir)
}
- } else if workdir := ts.Getenv("WORK"); workdir != "" {
+ } else if workdir := env.Getenv("WORK"); workdir != "" {
path = filepath.Join(workdir, logname)
} else {
- return log.NewNopLogger()
+ return log.NewNopLogger(), nil
}
f, err := os.Create(path)
if err != nil {
- ts.Fatalf("unable to create log file %q: %s", path, err.Error())
+ return nil, fmt.Errorf("unable to create log file %q: %w", path, err)
}
- ts.Defer(func() {
+ env.Defer(func() {
if err := f.Close(); err != nil {
panic(fmt.Errorf("unable to close log file %q: %w", path, err))
}
@@ -274,9 +239,22 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger {
logger.SetLevel(log.LevelInfo)
case "":
default:
- ts.Fatalf("invalid log level %q", level)
+ return nil, fmt.Errorf("invalid log level %q", level)
}
- ts.Logf("starting logger: %q", path)
- return logger
+ env.T().Log("starting logger: %q", path)
+ return logger, nil
+}
+
+func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) {
+ if err != nil {
+ fmt.Fprintf(ts.Stderr(), "%q error: %v\n", cmd, err)
+ if !neg {
+ ts.Fatalf("unexpected %q command failure: %s", cmd, err)
+ }
+ } else {
+ if neg {
+ ts.Fatalf("unexpected %q command success", cmd)
+ }
+ }
}
diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go
new file mode 100644
index 00000000000..1ca7e11eb63
--- /dev/null
+++ b/gno.land/pkg/integration/testing_node.go
@@ -0,0 +1,184 @@
+package integration
+
+import (
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
+ abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
+ tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config"
+ "github.com/gnolang/gno/tm2/pkg/bft/node"
+ bft "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/events"
+ "github.com/gnolang/gno/tm2/pkg/log"
+ "github.com/gnolang/gno/tm2/pkg/std"
+ "github.com/jaekwon/testify/require"
+)
+
+const (
+ DefaultAccount_Name = "test1"
+ DefaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"
+ DefaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast"
+)
+
+// TestingInMemoryNode initializes and starts an in-memory node for testing.
+// It returns the node instance and its RPC remote address.
+func TestingInMemoryNode(t TestingTS, logger log.Logger, config *gnoland.InMemoryNodeConfig) (*node.Node, string) {
+ node, err := gnoland.NewInMemoryNode(logger, config)
+ require.NoError(t, err)
+
+ err = node.Start()
+ require.NoError(t, err)
+
+ select {
+ case <-waitForNodeReadiness(node):
+ case <-time.After(time.Second * 6):
+ require.FailNow(t, "timeout while waiting for the node to start")
+ }
+
+ return node, node.Config().RPC.ListenAddress
+}
+
+// TestingNodeConfig constructs an in-memory node configuration
+// with default packages and genesis transactions already loaded.
+// It will return the default creator address of the loaded packages.
+func TestingNodeConfig(t TestingTS, gnoroot string) (*gnoland.InMemoryNodeConfig, bft.Address) {
+ cfg := TestingMinimalNodeConfig(t, gnoroot)
+
+ creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1
+
+ balances := LoadDefaultGenesisBalanceFile(t, gnoroot)
+ txs := []std.Tx{}
+ txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...)
+ txs = append(txs, LoadDefaultGenesisTXsFile(t, cfg.Genesis.ChainID, gnoroot)...)
+
+ cfg.Genesis.AppState = gnoland.GnoGenesisState{
+ Balances: balances,
+ Txs: txs,
+ }
+
+ return cfg, creator
+}
+
+// TestingMinimalNodeConfig constructs the default minimal in-memory node configuration for testing.
+func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNodeConfig {
+ tmconfig := DefaultTestingTMConfig(gnoroot)
+
+ // Create Mocked Identity
+ pv := gnoland.NewMockedPrivValidator()
+
+ // Generate genesis config
+ genesis := DefaultTestingGenesisConfig(t, gnoroot, pv.GetPubKey(), tmconfig)
+
+ return &gnoland.InMemoryNodeConfig{
+ PrivValidator: pv,
+ Genesis: genesis,
+ TMConfig: tmconfig,
+ }
+}
+
+func DefaultTestingGenesisConfig(t TestingTS, gnoroot string, self crypto.PubKey, tmconfig *tmcfg.Config) *bft.GenesisDoc {
+ return &bft.GenesisDoc{
+ GenesisTime: time.Now(),
+ ChainID: tmconfig.ChainID(),
+ ConsensusParams: abci.ConsensusParams{
+ Block: &abci.BlockParams{
+ MaxTxBytes: 1_000_000, // 1MB,
+ MaxDataBytes: 2_000_000, // 2MB,
+ MaxGas: 10_0000_000, // 10M gas
+ TimeIotaMS: 100, // 100ms
+ },
+ },
+ Validators: []bft.GenesisValidator{
+ {
+ Address: self.Address(),
+ PubKey: self,
+ Power: 10,
+ Name: "self",
+ },
+ },
+ AppState: gnoland.GnoGenesisState{
+ Balances: []gnoland.Balance{
+ {
+ Address: crypto.MustAddressFromString(DefaultAccount_Address),
+ Amount: std.MustParseCoins("10000000000000ugnot"),
+ },
+ },
+ Txs: []std.Tx{},
+ },
+ }
+}
+
+// LoadDefaultPackages loads the default packages for testing using a given creator address and gnoroot directory.
+func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []std.Tx {
+ examplesDir := filepath.Join(gnoroot, "examples")
+
+ defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
+ defaultCreator := crypto.MustAddressFromString(DefaultAccount_Address) // test1
+ txs, err := gnoland.LoadPackagesFromDir(examplesDir, defaultCreator, defaultFee, nil)
+ require.NoError(t, err)
+
+ return txs
+}
+
+// LoadDefaultGenesisBalanceFile loads the default genesis balance file for testing.
+func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balance {
+ balanceFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt")
+
+ genesisBalances, err := gnoland.LoadGenesisBalancesFile(balanceFile)
+ require.NoError(t, err)
+
+ return genesisBalances
+}
+
+// LoadDefaultGenesisTXsFile loads the default genesis transactions file for testing.
+func LoadDefaultGenesisTXsFile(t TestingTS, chainid string, gnoroot string) []std.Tx {
+ txsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.txt")
+
+ // NOTE: We dont care about giving a correct address here, as it's only for display
+ // XXX: Do we care loading this TXs for testing ?
+ genesisTXs, err := gnoland.LoadGenesisTxsFile(txsFile, chainid, "https://127.0.0.1:26657")
+ require.NoError(t, err)
+
+ return genesisTXs
+}
+
+// DefaultTestingTMConfig constructs the default Tendermint configuration for testing.
+func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config {
+ const defaultListner = "tcp://127.0.0.1:0"
+
+ tmconfig := tmcfg.TestConfig().SetRootDir(gnoroot)
+ tmconfig.Consensus.CreateEmptyBlocks = true
+ tmconfig.Consensus.CreateEmptyBlocksInterval = time.Duration(0)
+ tmconfig.RPC.ListenAddress = defaultListner
+ tmconfig.P2P.ListenAddress = defaultListner
+ return tmconfig
+}
+
+// waitForNodeReadiness waits until the node is ready, signaling via the EventNewBlock event.
+// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216
+func waitForNodeReadiness(n *node.Node) <-chan struct{} {
+ const listenerID = "first_block_listener"
+
+ var once sync.Once
+
+ nb := make(chan struct{})
+ ready := func() {
+ close(nb)
+ n.EventSwitch().RemoveListener(listenerID)
+ }
+
+ n.EventSwitch().AddListener(listenerID, func(ev events.Event) {
+ if _, ok := ev.(bft.EventNewBlock); ok {
+ once.Do(ready)
+ }
+ })
+
+ if n.BlockStore().Height() > 0 {
+ once.Do(ready)
+ }
+
+ return nb
+}