diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 23ac66a5f9a..d026f89832e 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -17,18 +17,18 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" tm2events "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" // backup "github.com/gnolang/tx-archive/backup/client" // restore "github.com/gnolang/tx-archive/restore/client" ) type NodeConfig struct { - PackagesPathList []string - TMConfig *tmcfg.Config - SkipFailingGenesisTxs bool - NoReplay bool - MaxGasPerBlock int64 - ChainID string + PackagesPathList []string + TMConfig *tmcfg.Config + NoReplay bool + MaxGasPerBlock int64 + ChainID string } func DefaultNodeConfig(rootdir string) *NodeConfig { @@ -36,11 +36,10 @@ func DefaultNodeConfig(rootdir string) *NodeConfig { tmc.Consensus.SkipTimeoutCommit = false // avoid time drifting, see issue #1507 return &NodeConfig{ - ChainID: tmc.ChainID(), - PackagesPathList: []string{}, - TMConfig: tmc, - SkipFailingGenesisTxs: true, - MaxGasPerBlock: 10_000_000_000, + ChainID: tmc.ChainID(), + PackagesPathList: []string{}, + TMConfig: tmc, + MaxGasPerBlock: 10_000_000_000, } } @@ -230,6 +229,36 @@ func (d *Node) Reload(ctx context.Context) error { return nil } +func (d *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { + if res.IsErr() { + // XXX: for now, this is only way to catch the error + before, after, found := strings.Cut(res.Log, "\n") + if !found { + d.logger.Error("unable to send tx", "err", res.Error, "log", res.Log) + return + } + + var attrs []slog.Attr + + // Add error + attrs = append(attrs, slog.Any("err", res.Error)) + + // Fetch first line as error message + msg := strings.TrimFunc(before, func(r rune) bool { + return unicode.IsSpace(r) || r == ':' + }) + attrs = append(attrs, slog.String("err", msg)) + + // If debug is enable, also append stack + if d.logger.Enabled(context.Background(), slog.LevelDebug) { + attrs = append(attrs, slog.String("stack", after)) + + } + + d.logger.LogAttrs(context.Background(), slog.LevelError, "unable to deliver tx", attrs...) + } +} + // GetBlockTransactions returns the transactions contained // within the specified block, if any func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { @@ -325,7 +354,7 @@ func (n *Node) stopIfRunning() error { func (n *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) error { // Setup node config nodeConfig := newNodeConfig(n.config.TMConfig, n.config.ChainID, genesis) - nodeConfig.SkipFailingGenesisTxs = n.config.SkipFailingGenesisTxs + nodeConfig.GenesisTxHandler = n.genesisTxHandler nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock var recoverErr error diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cc15f74134e..f67b86fd735 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -28,17 +28,18 @@ type AppOptions struct { DB dbm.DB // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. - GnoRootDir string - SkipFailingGenesisTxs bool - Logger *slog.Logger - MaxCycles int64 + GnoRootDir string + GenesisTxHandler GenesisTxHandler + Logger *slog.Logger + MaxCycles int64 } func NewAppOptions() *AppOptions { return &AppOptions{ - Logger: log.NewNoopLogger(), - DB: memdb.NewMemDB(), - GnoRootDir: gnoenv.RootDir(), + GenesisTxHandler: PanicOnFailingTxHandler, + Logger: log.NewNoopLogger(), + DB: memdb.NewMemDB(), + GnoRootDir: gnoenv.RootDir(), } } @@ -81,7 +82,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer - baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.SkipFailingGenesisTxs)) + baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler)) // Set AnteHandler authOptions := auth.AnteOptions{ @@ -127,7 +128,9 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, var err error cfg := NewAppOptions() - cfg.SkipFailingGenesisTxs = skipFailingGenesisTxs + if skipFailingGenesisTxs { + cfg.GenesisTxHandler = NoopGenesisTxHandler + } // Get main DB. cfg.DB, err = dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(dataRootDir, "data")) @@ -140,8 +143,18 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, return NewAppWithOptions(cfg) } +type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) + +func NoopGenesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {} + +func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { + if res.IsErr() { + panic(res.Log) + } +} + // InitChainer returns a function that can initialize the chain with genesis. -func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, skipFailingGenesisTxs bool) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { +func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, resHandler GenesisTxHandler) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { // Get genesis state. genState := req.AppState.(GnoGenesisState) @@ -154,21 +167,20 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank panic(err) } } + // Run genesis txs. for i, tx := range genState.Txs { res := baseApp.Deliver(tx) if res.IsErr() { ctx.Logger().Error("LOG", "log", res.Log) ctx.Logger().Error(fmt.Sprintf("#%d", i), "value", string(amino.MustMarshalJSON(tx))) - - // NOTE: comment out to ignore. - if !skipFailingGenesisTxs { - panic(res.Log) - } } else { ctx.Logger().Info("SUCCESS:", "value", string(amino.MustMarshalJSON(tx))) } + + resHandler(ctx, tx, res) } + // Done! return abci.ResponseInitChain{ Validators: req.Validators, diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 89f222738d0..f6277508668 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -21,11 +21,11 @@ import ( ) type InMemoryNodeConfig struct { - PrivValidator bft.PrivValidator // identity of the validator - Genesis *bft.GenesisDoc - TMConfig *tmcfg.Config - SkipFailingGenesisTxs bool - GenesisMaxVMCycles int64 + PrivValidator bft.PrivValidator // identity of the validator + Genesis *bft.GenesisDoc + TMConfig *tmcfg.Config + GenesisTxHandler GenesisTxHandler + GenesisMaxVMCycles int64 } // NewMockedPrivValidator generate a new key @@ -82,6 +82,7 @@ func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig { PrivValidator: pv, TMConfig: tm, Genesis: genesis, + GenesisTxHandler: PanicOnFailingTxHandler, GenesisMaxVMCycles: 10_000_000, } } @@ -110,14 +111,16 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, return nil, fmt.Errorf("validate config error: %w", err) } + // Setup options + opts := NewAppOptions() + opts.Logger = logger + opts.GnoRootDir = cfg.TMConfig.RootDir + opts.MaxCycles = cfg.GenesisMaxVMCycles + opts.GenesisTxHandler = cfg.GenesisTxHandler + opts.DB = memdb.NewMemDB() + // Initialize the application with the provided options - gnoApp, err := NewAppWithOptions(&AppOptions{ - Logger: logger, - GnoRootDir: cfg.TMConfig.RootDir, - SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs, - MaxCycles: cfg.GenesisMaxVMCycles, - DB: memdb.NewMemDB(), - }) + gnoApp, err := NewAppWithOptions(opts) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) }