From bcb44c80266ce093d9547ca7a5ffdcd978f3787a Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:06:17 +0100 Subject: [PATCH] wip: poc fork node Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/node_inmemory.go | 4 +- gno.land/pkg/integration/fork_config.go | 10 ++-- gno.land/pkg/integration/fork_test.go | 6 +-- gno.land/pkg/integration/forknode/main.go | 51 +++++++++++++++---- .../integration/testdata/adduserfrom.txtar | 4 +- .../testdata/loadpkg_example.txtar | 4 +- .../pkg/integration/testdata/restart.txtar | 5 +- .../pkg/integration/testscript_gnoland.go | 50 +++++++++++++----- tm2/pkg/bft/config/config.go | 4 ++ 9 files changed, 101 insertions(+), 37 deletions(-) diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index f42166411c8..a89f39b0b4a 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -23,8 +23,8 @@ type InMemoryNodeConfig struct { PrivValidator bft.PrivValidator // identity of the validator Genesis *bft.GenesisDoc TMConfig *tmcfg.Config - DB *memdb.MemDB // will be initialized if nil - VMOutput io.Writer // optional + DB db.DB // will be initialized if nil + VMOutput io.Writer // optional // If StdlibDir not set, then it's filepath.Join(TMConfig.RootDir, "gnovm", "stdlibs") InitChainerConfig diff --git a/gno.land/pkg/integration/fork_config.go b/gno.land/pkg/integration/fork_config.go index d3d94a1d0c3..067e615067b 100644 --- a/gno.land/pkg/integration/fork_config.go +++ b/gno.land/pkg/integration/fork_config.go @@ -12,6 +12,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" ) type MarshalableGenesisDoc bft.GenesisDoc @@ -42,9 +43,11 @@ func (m *MarshalableGenesisDoc) ToGenesisDoc() *bft.GenesisDoc { } type ForkConfig struct { - RootDir string `json:"rootdir"` - Genesis *MarshalableGenesisDoc `json:"genesis"` - TMConfig *tmcfg.Config `json:"tm"` + PrivValidator ed25519.PrivKeyEd25519 `json:"priv"` + DBDir string `json:"dbdir"` + RootDir string `json:"rootdir"` + Genesis *MarshalableGenesisDoc `json:"genesis"` + TMConfig *tmcfg.Config `json:"tm"` } // ExecuteForkBinary runs the binary at the given path with the provided configuration. @@ -107,6 +110,7 @@ func ExecuteForkBinary(ctx context.Context, binaryPath string, cfg *ForkConfig) select { case err := <-readyChan: if err != nil { + fmt.Println("ERR", err) cmd.Process.Kill() return "", cmd, err } diff --git a/gno.land/pkg/integration/fork_test.go b/gno.land/pkg/integration/fork_test.go index 2e61aa8746f..533c85cc520 100644 --- a/gno.land/pkg/integration/fork_test.go +++ b/gno.land/pkg/integration/fork_test.go @@ -2,13 +2,13 @@ package integration import ( "context" - "fmt" "path/filepath" "testing" "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -29,6 +29,7 @@ func TestForkGnoland(t *testing.T) { gnoenv.RootDir() remoteAddr, cmd, err := ExecuteForkBinary(ctx, gnolandBin, &ForkConfig{ + // PrivValidator: ed25519.GenPrivKey(), RootDir: gnoRootDir, TMConfig: cfg.TMConfig, Genesis: NewMarshalableGenesisDoc(cfg.Genesis), @@ -42,6 +43,5 @@ func TestForkGnoland(t *testing.T) { info, err := cli.ABCIInfo() require.NoError(t, err) - - fmt.Println(info) + assert.NotEmpty(t, info.Response.Data) } diff --git a/gno.land/pkg/integration/forknode/main.go b/gno.land/pkg/integration/forknode/main.go index 39f80621b4b..13ed48016ed 100644 --- a/gno.land/pkg/integration/forknode/main.go +++ b/gno.land/pkg/integration/forknode/main.go @@ -12,23 +12,56 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/integration" bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/db/goleveldb" + "github.com/gnolang/gno/tm2/pkg/db/memdb" ) +// isAllZero checks if all elements in the [64]byte array are zero. +func isAllZero(arr [64]byte) bool { + for _, v := range arr { + if v != 0 { + return false + } + } + return true +} + func ForkableNode(cfg *integration.ForkConfig) error { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + // logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + + var data db.DB + var err error + if cfg.DBDir == "" { + data = memdb.NewMemDB() + } else { + data, err = goleveldb.NewGoLevelDB("testdb", cfg.DBDir) + if err != nil { + return fmt.Errorf("unable to init database in %q: %w", cfg.DBDir, err) + } + } nodecfg := integration.TestingMinimalNodeConfig(cfg.RootDir) - pv := nodecfg.PrivValidator.GetPubKey() + + if len(cfg.PrivValidator) > 0 && !isAllZero(cfg.PrivValidator) { + nodecfg.PrivValidator = bft.NewMockPVWithParams(cfg.PrivValidator, false, false) + pv := nodecfg.PrivValidator.GetPubKey() + nodecfg.Genesis.Validators = []bft.GenesisValidator{ + { + Address: pv.Address(), + PubKey: pv, + Power: 10, + Name: "self", + }, + } + + } + + nodecfg.DB = data + nodecfg.TMConfig.DBPath = cfg.DBDir nodecfg.TMConfig = cfg.TMConfig nodecfg.Genesis = cfg.Genesis.ToGenesisDoc() - nodecfg.Genesis.Validators = []bft.GenesisValidator{ - { - Address: pv.Address(), - PubKey: pv, - Power: 10, - Name: "self", - }, - } node, err := gnoland.NewInMemoryNode(logger, nodecfg) if err != nil { diff --git a/gno.land/pkg/integration/testdata/adduserfrom.txtar b/gno.land/pkg/integration/testdata/adduserfrom.txtar index a23849aa604..47ec70b00e6 100644 --- a/gno.land/pkg/integration/testdata/adduserfrom.txtar +++ b/gno.land/pkg/integration/testdata/adduserfrom.txtar @@ -27,8 +27,8 @@ stdout ' "BaseAccount": {' stdout ' "address": "g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp",' stdout ' "coins": "10000000ugnot",' stdout ' "public_key": null,' -stdout ' "account_number": "58",' +stdout ' "account_number": "59",' stdout ' "sequence": "0"' stdout ' }' stdout '}' -! stderr '.+' # empty \ No newline at end of file +! stderr '.+' # empty diff --git a/gno.land/pkg/integration/testdata/loadpkg_example.txtar b/gno.land/pkg/integration/testdata/loadpkg_example.txtar index 9dccd72c8a6..f7be500f3b6 100644 --- a/gno.land/pkg/integration/testdata/loadpkg_example.txtar +++ b/gno.land/pkg/integration/testdata/loadpkg_example.txtar @@ -4,11 +4,11 @@ loadpkg gno.land/p/demo/ufmt ## start a new node gnoland start -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/importtest -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/importtest -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 stdout OK! ## execute Render -gnokey maketx call -pkgpath gno.land/r/importtest -func Render -gas-fee 1000000ugnot -gas-wanted 4000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/importtest -func Render -gas-fee 1000000ugnot -gas-wanted 10000000 -args '' -broadcast -chainid=tendermint_test test1 stdout '("92054" string)' stdout OK! diff --git a/gno.land/pkg/integration/testdata/restart.txtar b/gno.land/pkg/integration/testdata/restart.txtar index 8d50dd15814..5571aa9fa66 100644 --- a/gno.land/pkg/integration/testdata/restart.txtar +++ b/gno.land/pkg/integration/testdata/restart.txtar @@ -4,12 +4,12 @@ loadpkg gno.land/r/demo/counter $WORK gnoland start -gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 100000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test test1 stdout '\(1 int\)' gnoland restart -gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 100000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/counter -func Incr -gas-fee 1000000ugnot -gas-wanted 200000 -broadcast -chainid tendermint_test test1 stdout '\(2 int\)' -- counter.gno -- @@ -21,4 +21,3 @@ func Incr() int { counter++ return counter } - diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index 585ca818dca..a308ad4416d 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "syscall" "testing" "time" @@ -25,6 +26,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/bip39" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" tm2Log "github.com/gnolang/gno/tm2/pkg/log" @@ -114,9 +116,14 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { } } - tmpdir := t.TempDir() + // XXX: rework this + tmpdir, dbdir := t.TempDir(), t.TempDir() gnoHomeDir := filepath.Join(tmpdir, "gno") + env.Values["PK"] = ed25519.GenPrivKey() + + env.Setenv("GNO_DBDIR", dbdir) + // Get `TESTWORK` environement variable from setup persistWorkDir, _ := strconv.ParseBool(env.Getenv("TESTWORK")) @@ -264,21 +271,25 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnolandBin, gnoRootDir ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() + dbdir := ts.Getenv("GNO_DBDIR") + priv := ts.Value("PK").(ed25519.PrivKeyEd25519) remoteAddr, cmd, err := ExecuteForkBinary(ctx, gnolandBin, &ForkConfig{ - RootDir: gnoRootDir, - TMConfig: cfg.TMConfig, - Genesis: NewMarshalableGenesisDoc(cfg.Genesis), + PrivValidator: priv, + DBDir: dbdir, + RootDir: gnoRootDir, + TMConfig: cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(cfg.Genesis), }) if err != nil { ts.Fatalf("unable to start the node: %s", err) } - cfg.TMConfig.RPC.ListenAddress = remoteAddr nodesManager.Set(sid, &testNode{Cmd: cmd, remoteAddr: remoteAddr, cfg: cfg}) ts.Setenv("RPC_ADDR", remoteAddr) fmt.Fprintln(ts.Stdout(), "node started successfully") + case "restart": node, exists := nodesManager.Get(sid) if !exists { @@ -286,24 +297,37 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnolandBin, gnoRootDir break } - if stopErr := node.Cmd.Process.Kill(); stopErr != nil { - err = fmt.Errorf("error stopping node: %w", stopErr) + fmt.Println("STOPING") + + // Send SIGTERM to the process + if err := node.Cmd.Process.Signal(syscall.SIGTERM); err != nil { + err = fmt.Errorf("Error sending SIGTERM: %w\n", err) break } - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + // Optionally wait for the process to exit + if _, err := node.Cmd.Process.Wait(); err != nil { + err = fmt.Errorf("Process exited with error: %w", err) + break + } + + fmt.Println("STOP DONE") + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() + priv := ts.Value("PK").(ed25519.PrivKeyEd25519) + dbdir := ts.Getenv("GNO_DBDIR") newRemoteAddr, cmd, err := ExecuteForkBinary(ctx, gnolandBin, &ForkConfig{ - RootDir: gnoRootDir, - TMConfig: node.cfg.TMConfig, - Genesis: NewMarshalableGenesisDoc(node.cfg.Genesis), + PrivValidator: priv, + DBDir: dbdir, + RootDir: gnoRootDir, + TMConfig: node.cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(node.cfg.Genesis), }) if err != nil { ts.Fatalf("unable to start the node: %s", err) } - node.cfg.TMConfig.RPC.ListenAddress = newRemoteAddr nodesManager.Set(sid, &testNode{Cmd: cmd, remoteAddr: newRemoteAddr, cfg: node.cfg}) fmt.Fprintln(ts.Stdout(), "node restarted successfully") @@ -355,7 +379,7 @@ func gnokeyCmd(nodes *NodesManager) func(ts *testscript.TestScript, neg bool, ar } if n, ok := nodes.Get(sid); ok { - if raddr := n.cfg.TMConfig.RPC.ListenAddress; raddr != "" { + if raddr := n.remoteAddr; raddr != "" { defaultArgs = append(defaultArgs, "-remote", raddr) } diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index f9e9a0cd899..d290dba6b26 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -372,6 +372,10 @@ func (cfg BaseConfig) NodeKeyFile() string { // DBDir returns the full path to the database directory func (cfg BaseConfig) DBDir() string { + if filepath.IsAbs(cfg.DBPath) { + return cfg.DBPath + } + return filepath.Join(cfg.RootDir, cfg.DBPath) }