From 5865cd38921a549cb08bf59b6a534162981f4de9 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:30:17 +0200 Subject: [PATCH] feat(gnodev): support MetadataTX Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnodev/cmd/gnodev/setup_node.go | 9 ++- contribs/gnodev/cmd/gnodev/txs.go | 52 ++++++++++++++- contribs/gnodev/pkg/dev/node.go | 64 +++++++++++------- contribs/gnodev/pkg/dev/node_state.go | 85 ++++++++++++++++++++++-- contribs/gnodev/pkg/dev/packages.go | 19 ++++-- 5 files changed, 185 insertions(+), 44 deletions(-) diff --git a/contribs/gnodev/cmd/gnodev/setup_node.go b/contribs/gnodev/cmd/gnodev/setup_node.go index df6f1d712ec..e93567ca03e 100644 --- a/contribs/gnodev/cmd/gnodev/setup_node.go +++ b/contribs/gnodev/cmd/gnodev/setup_node.go @@ -11,7 +11,6 @@ import ( "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/std" ) // setupDevNode initializes and returns a new DevNode. @@ -24,7 +23,7 @@ func setupDevNode( if devCfg.txsFile != "" { // Load txs files var err error - nodeConfig.InitialTxs, err = parseTxs(devCfg.txsFile) + nodeConfig.InitialTxs, err = parseTxs(ctx, logger, devCfg.txsFile) if err != nil { return nil, fmt.Errorf("unable to load transactions: %w", err) } @@ -38,13 +37,13 @@ func setupDevNode( nodeConfig.BalancesList = state.GenesisBalances() stateTxs := state.GenesisTxs() - nodeConfig.InitialTxs = make([]std.Tx, len(stateTxs)) + nodeConfig.InitialTxs = make([]gnoland.GenesisTx, len(stateTxs)) for index, nodeTx := range stateTxs { - nodeConfig.InitialTxs[index] = nodeTx.Tx() + nodeConfig.InitialTxs[index] = nodeTx } - logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(nodeConfig.InitialTxs)) + logger.Info("genesis file loaded", "path", devCfg.genesisFile, "txs", len(stateTxs)) } return gnodev.NewDevNode(ctx, nodeConfig) diff --git a/contribs/gnodev/cmd/gnodev/txs.go b/contribs/gnodev/cmd/gnodev/txs.go index 0be33b68702..a7b73ebacac 100644 --- a/contribs/gnodev/cmd/gnodev/txs.go +++ b/contribs/gnodev/cmd/gnodev/txs.go @@ -1,14 +1,20 @@ package main import ( + "bufio" "context" + "errors" "fmt" + "log/slog" "os" + "github.com/gnolang/gno/contribs/gnodev/pkg/dev" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/std" ) -func parseTxs(txFile string) ([]std.Tx, error) { +func parseTxs(ctx context.Context, logger *slog.Logger, txFile string) ([]gnoland.GenesisTx, error) { if txFile == "" { return nil, nil } @@ -19,5 +25,47 @@ func parseTxs(txFile string) ([]std.Tx, error) { } defer file.Close() - return std.ParseTxs(context.Background(), file) + var txs []gnoland.GenesisTx + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + select { + case <-ctx.Done(): + return nil, std.ErrTxsLoadingAborted + default: + } + + // XXX this can be expensive if all th txs are std.Tx + // find a better way + line := scanner.Bytes() + + var metatx gnoland.MetadataTx + unmarshalMetaTxErr := amino.Unmarshal(line, &metatx) + if unmarshalMetaTxErr == nil { + logger.Debug("load metatx", "tx", metatx.GenesisTx, "meta", metatx.TxMetadata) + txs = append(txs, &metatx) + continue + } + + // fallback on std tx + var tx std.Tx + unmarshalTxErr := amino.Unmarshal(line, &tx) + if unmarshalTxErr == nil { + logger.Debug("load tx", "tx", metatx.GenesisTx, "meta", metatx.TxMetadata) + txs = append(txs, dev.GnoGenesisTx{StdTx: tx}) + continue + } + + return nil, fmt.Errorf("unable to unmarshal tx: %w", errors.Join(unmarshalMetaTxErr, unmarshalTxErr)) + } + + // Check for scanning errors + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf( + "error encountered while reading file, %w", + err, + ) + } + + return txs, nil } diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 4008892d4e0..705eef44155 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "sync" + "time" "unicode" "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" @@ -35,7 +36,7 @@ type NodeConfig struct { BalancesList []gnoland.Balance PackagesPathList []PackagePath Emitter emitter.Emitter - InitialTxs []std.Tx + InitialTxs []gnoland.GenesisTx TMConfig *tmcfg.Config SkipFailingGenesisTxs bool NoReplay bool @@ -83,8 +84,11 @@ type Node struct { // keep track of number of loaded package to be able to skip them on restore loadedPackages int + // track starting time for genesis + startTime time.Time + // state - initialState, state []std.Tx + initialState, state []gnoland.GenesisTx currentStateIndex int } @@ -96,7 +100,8 @@ func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { return nil, fmt.Errorf("unable map pkgs list: %w", err) } - pkgsTxs, err := mpkgs.Load(DefaultFee) + startTime := time.Now() + pkgsTxs, err := mpkgs.Load(DefaultFee, startTime) if err != nil { return nil, fmt.Errorf("unable to load genesis packages: %w", err) } @@ -109,18 +114,19 @@ func NewDevNode(ctx context.Context, cfg *NodeConfig) (*Node, error) { pkgs: mpkgs, logger: cfg.Logger, loadedPackages: len(pkgsTxs), + startTime: startTime, state: cfg.InitialTxs, initialState: cfg.InitialTxs, currentStateIndex: len(cfg.InitialTxs), } // generate genesis state - genesis := gnoland.GnoGenesisState{ + genesis := NodeGenesisState{ Balances: cfg.BalancesList, Txs: append(pkgsTxs, cfg.InitialTxs...), } - if err := devnode.rebuildNode(ctx, genesis); err != nil { + if err := devnode.rebuildNode(ctx, &genesis); err != nil { return nil, fmt.Errorf("unable to initialize the node: %w", err) } @@ -154,7 +160,7 @@ func (n *Node) GetRemoteAddress() string { // GetBlockTransactions returns the transactions contained // within the specified block, if any -func (n *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { +func (n *Node) GetBlockTransactions(blockNum uint64) ([]gnoland.GenesisTx, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -163,21 +169,27 @@ func (n *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { // GetBlockTransactions returns the transactions contained // within the specified block, if any -func (n *Node) getBlockTransactions(blockNum uint64) ([]std.Tx, error) { +func (n *Node) getBlockTransactions(blockNum uint64) ([]gnoland.GenesisTx, error) { int64BlockNum := int64(blockNum) b, err := n.client.Block(&int64BlockNum) if err != nil { - return []std.Tx{}, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here + return nil, fmt.Errorf("unable to load block at height %d: %w", blockNum, err) // nothing to see here } - txs := make([]std.Tx, len(b.Block.Data.Txs)) + txs := make([]gnoland.GenesisTx, len(b.Block.Data.Txs)) for i, encodedTx := range b.Block.Data.Txs { + // fallback on std tx var tx std.Tx if unmarshalErr := amino.Unmarshal(encodedTx, &tx); unmarshalErr != nil { - return nil, fmt.Errorf("unable to unmarshal amino tx, %w", unmarshalErr) + return nil, fmt.Errorf("unable to unmarshal tx: %w", err) } - txs[i] = tx + txs[i] = &gnoland.MetadataTx{ + GenesisTx: tx, + TxMetadata: gnoland.GenesisTxMetadata{ + Timestamp: b.Block.Time.Unix(), + }, + } } return txs, nil @@ -263,26 +275,28 @@ func (n *Node) Reset(ctx context.Context) error { } // Generate a new genesis state based on the current packages - pkgsTxs, err := n.pkgs.Load(DefaultFee) + startTime := time.Now() + pkgsTxs, err := n.pkgs.Load(DefaultFee, startTime) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } // Append initialTxs txs := append(pkgsTxs, n.initialState...) - genesis := gnoland.GnoGenesisState{ + genesis := NodeGenesisState{ Balances: n.config.BalancesList, Txs: txs, } // Reset the node with the new genesis state. - err = n.rebuildNode(ctx, genesis) + err = n.rebuildNode(ctx, &genesis) if err != nil { return fmt.Errorf("unable to initialize a new node: %w", err) } n.loadedPackages = len(pkgsTxs) n.currentStateIndex = len(n.initialState) + n.startTime = startTime n.emitter.Emit(&events.Reset{}) return nil } @@ -347,15 +361,15 @@ func (n *Node) SendTransaction(tx *std.Tx) error { return nil } -func (n *Node) getBlockStoreState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) getBlockStoreState(ctx context.Context) ([]gnoland.GenesisTx, error) { // get current genesis state genesis := n.GenesisDoc().AppState.(gnoland.GnoGenesis) initialTxs := genesis.GenesisTxs()[n.loadedPackages:] // ignore previously loaded packages - state := make([]std.Tx, 0, len(initialTxs)) + state := make([]gnoland.GenesisTx, 0, len(initialTxs)) for _, tx := range initialTxs { - state = append(state, tx.Tx()) + state = append(state, tx) } lastBlock := n.getLatestBlockNumber() @@ -394,12 +408,12 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error { // If NoReplay is true, simply reset the node to its initial state n.logger.Warn("replay disabled") - txs, err := n.pkgs.Load(DefaultFee) + txs, err := n.pkgs.Load(DefaultFee, n.startTime) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } - return n.rebuildNode(ctx, gnoland.GnoGenesisState{ + return n.rebuildNode(ctx, &NodeGenesisState{ Balances: n.config.BalancesList, Txs: txs, }) } @@ -410,19 +424,19 @@ func (n *Node) rebuildNodeFromState(ctx context.Context) error { } // Load genesis packages - pkgsTxs, err := n.pkgs.Load(DefaultFee) + pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } // Create genesis with loaded pkgs + previous state - genesis := gnoland.GnoGenesisState{ + genesis := NodeGenesisState{ Balances: n.config.BalancesList, Txs: append(pkgsTxs, state...), } // Reset the node with the new genesis state. - err = n.rebuildNode(ctx, genesis) + err = n.rebuildNode(ctx, &genesis) n.logger.Info("reload done", "pkgs", len(pkgsTxs), "state applied", len(state)) // Update node infos @@ -465,7 +479,7 @@ func (n *Node) handleEventTX(evt tm2events.Event) { } } -func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesis) (err error) { +func (n *Node) rebuildNode(ctx context.Context, genesis *NodeGenesisState) (err error) { noopLogger := log.NewNoopLogger() // Stop the node if it's currently running. @@ -553,11 +567,11 @@ func (n *Node) genesisTxResultHandler(ctx sdk.Context, tx std.Tx, res sdk.Result return } -func newNodeConfig(tmc *tmcfg.Config, chainid string, appstate gnoland.GnoGenesis) *gnoland.InMemoryNodeConfig { +func newNodeConfig(tmc *tmcfg.Config, chainid string, appstate *NodeGenesisState) *gnoland.InMemoryNodeConfig { // Create Mocked Identity pv := gnoland.NewMockedPrivValidator() genesis := gnoland.NewDefaultGenesisConfig(chainid) - genesis.AppState = appstate + genesis.AppState = appstate.MetadataGenesisState() // Add self as validator self := pv.GetPubKey() diff --git a/contribs/gnodev/pkg/dev/node_state.go b/contribs/gnodev/pkg/dev/node_state.go index 846c4857784..3854b3e1f1a 100644 --- a/contribs/gnodev/pkg/dev/node_state.go +++ b/contribs/gnodev/pkg/dev/node_state.go @@ -11,6 +11,64 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) +type NodeGenesisState struct { + Balances []gnoland.Balance + Txs []gnoland.GenesisTx +} + +func (s *NodeGenesisState) Len() int { return len(s.Txs) } + +func (s *NodeGenesisState) GenesisTxs() []gnoland.GenesisTx { return s.Txs } + +func (s *NodeGenesisState) GenesisBalances() []gnoland.Balance { + return s.Balances +} + +func (s *NodeGenesisState) MetadataGenesisState() *gnoland.MetadataGenesisState { + metaState := gnoland.MetadataGenesisState{ + Balances: s.GenesisBalances(), + } + + // Export metadata as well + txs := s.GenesisTxs() + metaState.Txs = make([]gnoland.MetadataTx, len(txs)) + for i, tx := range txs { + metaState.Txs[i] = gnoland.MetadataTx{ + GenesisTx: tx.Tx(), + } + + if meta := tx.Metadata(); meta != nil { + metaState.Txs[i].TxMetadata = *meta + } + } + + return &metaState +} + +func (s *NodeGenesisState) AppendTxs(txs ...std.Tx) { + genesisTxs := make([]gnoland.GenesisTx, len(txs)) + for i, tx := range txs { + genesisTxs[i] = &GnoGenesisTx{tx} + } + s.Txs = append(s.Txs, genesisTxs...) +} + +func (s *NodeGenesisState) AppendMetadataTxs(txs ...gnoland.MetadataTx) { + genesisTxs := make([]gnoland.GenesisTx, len(txs)) + for i, tx := range txs { + genesisTxs[i] = tx + } + + s.Txs = append(s.Txs, genesisTxs...) +} + +type GnoGenesisTx struct { + StdTx std.Tx +} + +func (g GnoGenesisTx) Tx() std.Tx { return g.StdTx } +func (g GnoGenesisTx) Metadata() *gnoland.GenesisTxMetadata { return nil } + var ErrEmptyState = errors.New("empty state") // Save the current state as initialState @@ -29,7 +87,7 @@ func (n *Node) SaveCurrentState(ctx context.Context) error { } // Export the current state as list of txs -func (n *Node) ExportCurrentState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) ExportCurrentState(ctx context.Context) ([]gnoland.GenesisTx, error) { n.muNode.RLock() defer n.muNode.RUnlock() @@ -42,7 +100,7 @@ func (n *Node) ExportCurrentState(ctx context.Context) ([]std.Tx, error) { return state[:n.currentStateIndex], nil } -func (n *Node) getState(ctx context.Context) ([]std.Tx, error) { +func (n *Node) getState(ctx context.Context) ([]gnoland.GenesisTx, error) { if n.state == nil { var err error n.state, err = n.getBlockStoreState(ctx) @@ -85,7 +143,7 @@ func (n *Node) MoveBy(ctx context.Context, x int) error { } // Load genesis packages - pkgsTxs, err := n.pkgs.Load(DefaultFee) + pkgsTxs, err := n.pkgs.Load(DefaultFee, n.startTime) if err != nil { return fmt.Errorf("unable to load pkgs: %w", err) } @@ -93,13 +151,13 @@ func (n *Node) MoveBy(ctx context.Context, x int) error { newState := n.state[:newIndex] // Create genesis with loaded pkgs + previous state - genesis := gnoland.GnoGenesisState{ + genesis := NodeGenesisState{ Balances: n.config.BalancesList, Txs: append(pkgsTxs, newState...), } // Reset the node with the new genesis state. - if err = n.rebuildNode(ctx, genesis); err != nil { + if err = n.rebuildNode(ctx, &genesis); err != nil { return fmt.Errorf("uanble to rebuild node: %w", err) } @@ -133,10 +191,23 @@ func (n *Node) ExportStateAsGenesis(ctx context.Context) (*bft.GenesisDoc, error // Get current blockstore state doc := *n.Node.GenesisDoc() // copy doc - doc.AppState = gnoland.GnoGenesisState{ + + metaState := gnoland.MetadataGenesisState{ Balances: n.config.BalancesList, - Txs: state, } + // Export metadata as well + metaState.Txs = make([]gnoland.MetadataTx, len(state)) + for i, tx := range state { + metaState.Txs[i] = gnoland.MetadataTx{ + GenesisTx: tx.Tx(), + } + + if meta := tx.Metadata(); meta != nil { + metaState.Txs[i].TxMetadata = *meta + } + } + + doc.AppState = metaState return &doc, nil } diff --git a/contribs/gnodev/pkg/dev/packages.go b/contribs/gnodev/pkg/dev/packages.go index 7b560c21e09..aa3c239889b 100644 --- a/contribs/gnodev/pkg/dev/packages.go +++ b/contribs/gnodev/pkg/dev/packages.go @@ -5,8 +5,10 @@ import ( "fmt" "net/url" "path/filepath" + "time" "github.com/gnolang/gno/contribs/gnodev/pkg/address" + "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" @@ -118,7 +120,7 @@ func (pm PackagesMap) toList() gnomod.PkgList { return list } -func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { +func (pm PackagesMap) Load(fee std.Fee, start time.Time) ([]gnoland.GenesisTx, error) { pkgs := pm.toList() sorted, err := pkgs.Sort() @@ -127,7 +129,7 @@ func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { } nonDraft := sorted.GetNonDraftPkgs() - txs := []std.Tx{} + metatxs := []gnoland.GenesisTx{} for _, modPkg := range nonDraft { pkg := pm[modPkg.Dir] if pkg.Creator.IsZero() { @@ -151,10 +153,17 @@ func (pm PackagesMap) Load(fee std.Fee) ([]std.Tx, error) { }, }, } - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) + + metatx := gnoland.MetadataTx{ + GenesisTx: tx, + TxMetadata: gnoland.GenesisTxMetadata{ + Timestamp: start.Unix(), + }, + } + + metatxs = append(metatxs, metatx) } - return txs, nil + return metatxs, nil }