Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnovm)!: store refactor #2655

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 3 additions & 2 deletions contribs/gnodev/pkg/dev/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState)

// Setup node config
nodeConfig := newNodeConfig(n.config.TMConfig, n.config.ChainID, genesis)
nodeConfig.GenesisTxHandler = n.genesisTxHandler
nodeConfig.GenesisTxResultHandler = n.genesisTxResultHandler
nodeConfig.CacheStdlibLoad = true
nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock

// recoverFromError handles panics and converts them to errors.
Expand Down Expand Up @@ -511,7 +512,7 @@ func (n *Node) rebuildNode(ctx context.Context, genesis gnoland.GnoGenesisState)
return nil
}

func (n *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {
func (n *Node) genesisTxResultHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {
if !res.IsErr() {
return
}
Expand Down
1 change: 0 additions & 1 deletion gno.land/cmd/gnoland/testdata/issue_2283_cacheTypes.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,3 @@ import (
func Call(s string) {
base64.StdEncoding.DecodeString("hey")
}

198 changes: 122 additions & 76 deletions gno.land/pkg/gnoland/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log/slog"
"path/filepath"
"strconv"
"time"

"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
Expand All @@ -29,28 +30,22 @@ import (
)

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
GenesisTxHandler GenesisTxHandler
Logger *slog.Logger
EventSwitch events.EventSwitch
MaxCycles int64
// Whether to cache the result of loading the standard libraries.
// This is useful if you have to start many nodes, like in testing.
// This disables loading existing packages; so it should only be used
// on a fresh database.
CacheStdlibLoad bool
DB dbm.DB
Logger *slog.Logger
EventSwitch events.EventSwitch
MaxCycles int64
InitChainerConfig
}

func NewAppOptions() *AppOptions {
return &AppOptions{
GenesisTxHandler: PanicOnFailingTxHandler,
Logger: log.NewNoopLogger(),
DB: memdb.NewMemDB(),
GnoRootDir: gnoenv.RootDir(),
EventSwitch: events.NilEventSwitch(),
Logger: log.NewNoopLogger(),
DB: memdb.NewMemDB(),
EventSwitch: events.NilEventSwitch(),
InitChainerConfig: InitChainerConfig{
GenesisTxResultHandler: PanicOnFailingTxResultHandler,
StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"),
},
}
}

Expand Down Expand Up @@ -88,13 +83,13 @@ 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")
vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles)
vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, cfg.MaxCycles)

// Set InitChainer
baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler))
icc := cfg.InitChainerConfig
icc.baseApp = baseApp
icc.acctKpr, icc.bankKpr, icc.vmKpr = acctKpr, bankKpr, vmk
baseApp.SetInitChainer(icc.InitChainer)

// Set AnteHandler
authOptions := auth.AnteOptions{
Expand All @@ -108,13 +103,22 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
newCtx sdk.Context, res sdk.Result, abort bool,
) {
// Override auth params.
ctx = ctx.WithValue(
auth.AuthParamsContextKey{}, auth.DefaultParams())
ctx = ctx.
WithValue(auth.AuthParamsContextKey{}, auth.DefaultParams())
// Continue on with default auth ante handler.
newCtx, res, abort = authAnteHandler(ctx, tx, simulate)
return
},
)
baseApp.SetBeginTxHook(func(ctx sdk.Context) sdk.Context {
// Create Gno transaction store.
return vmk.MakeGnoTransactionStore(ctx)
})
baseApp.SetEndTxHook(func(ctx sdk.Context, result sdk.Result) {
if result.IsOK() {
vmk.CommitGnoTransactionStore(ctx)
}
})

// Set up the event collector
c := newCollector[validatorUpdate](
Expand Down Expand Up @@ -143,7 +147,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {

// Initialize the VMKeeper.
ms := baseApp.GetCacheMultiStore()
vmk.Initialize(cfg.Logger, ms, cfg.CacheStdlibLoad)
vmk.Initialize(cfg.Logger, ms)
ms.MultiWrite() // XXX why was't this needed?

return baseApp, nil
Expand All @@ -160,7 +164,7 @@ func NewApp(

cfg := NewAppOptions()
if skipFailingGenesisTxs {
cfg.GenesisTxHandler = NoopGenesisTxHandler
cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler
}

// Get main DB.
Expand All @@ -175,68 +179,110 @@ func NewApp(
return NewAppWithOptions(cfg)
}

type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result)
// GenesisTxResultHandler is called in the InitChainer after a genesis
// transaction is executed.
type GenesisTxResultHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result)

func NoopGenesisTxHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {}
// NoopGenesisTxResultHandler is a no-op GenesisTxResultHandler.
func NoopGenesisTxResultHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {}

func PanicOnFailingTxHandler(_ sdk.Context, _ std.Tx, res sdk.Result) {
// PanicOnFailingTxResultHandler handles genesis transactions by panicking if
// res.IsErr() returns true.
func PanicOnFailingTxResultHandler(_ sdk.Context, _ 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,
resHandler GenesisTxHandler,
) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
txResponses := []abci.ResponseDeliverTx{}

if req.AppState != nil {
// Get genesis state
genState := req.AppState.(GnoGenesisState)

// Parse and set genesis state balances
for _, bal := range genState.Balances {
acc := acctKpr.NewAccountWithAddress(ctx, bal.Address)
acctKpr.SetAccount(ctx, acc)
err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount)
if err != nil {
panic(err)
}
}
// InitChainerConfig keeps the configuration for the InitChainer.
type InitChainerConfig struct {
// Handles the results of each genesis transaction.
GenesisTxResultHandler

// Standard library directory.
StdlibDir string
// Whether to keep a record of the DB operations to load standard libraries,
// so they can be quickly replicated on additional genesis executions.
// This should be used for integration testing, where InitChainer will be
// called several times.
CacheStdlibLoad bool

// These fields are passed directly by NewAppWithOptions, and should not be
// configurable by end-users.
baseApp *sdk.BaseApp
vmKpr vm.VMKeeperI
acctKpr auth.AccountKeeperI
bankKpr bank.BankKeeperI
}

// InitChainer is the function that can be used as a [sdk.InitChainer].
func (cfg InitChainerConfig) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
start := time.Now()
ctx.Logger().Debug("InitChainer: started")

{
// load standard libraries
// need to write to the MultiStore directly - so that the standard
// libraries are available when we process genesis txs
stdlibCtx := cfg.vmKpr.MakeGnoTransactionStore(ctx)
if cfg.CacheStdlibLoad {
cfg.vmKpr.LoadStdlibCached(stdlibCtx, cfg.StdlibDir)
} else {
cfg.vmKpr.LoadStdlib(stdlibCtx, cfg.StdlibDir)
}
cfg.vmKpr.CommitGnoTransactionStore(stdlibCtx)
stdlibCtx.MultiStore().MultiWrite()
}

ctx.Logger().Debug("InitChainer: standard libraries loaded",
"elapsed", time.Since(start))

txResponses := []abci.ResponseDeliverTx{}

// Run genesis txs
for _, tx := range genState.Txs {
res := baseApp.Deliver(tx)
if res.IsErr() {
ctx.Logger().Error(
"Unable to deliver genesis tx",
"log", res.Log,
"error", res.Error,
"gas-used", res.GasUsed,
)
}

txResponses = append(txResponses, abci.ResponseDeliverTx{
ResponseBase: res.ResponseBase,
GasWanted: res.GasWanted,
GasUsed: res.GasUsed,
})

resHandler(ctx, tx, res)
if req.AppState != nil {
// Get genesis state
genState := req.AppState.(GnoGenesisState)

// Parse and set genesis state balances
for _, bal := range genState.Balances {
acc := cfg.acctKpr.NewAccountWithAddress(ctx, bal.Address)
cfg.acctKpr.SetAccount(ctx, acc)
err := cfg.bankKpr.SetCoins(ctx, bal.Address, bal.Amount)
if err != nil {
panic(err)
}
}

// Done!
return abci.ResponseInitChain{
Validators: req.Validators,
TxResponses: txResponses,
// Run genesis txs
for _, tx := range genState.Txs {
res := cfg.baseApp.Deliver(tx)
if res.IsErr() {
ctx.Logger().Error(
"Unable to deliver genesis tx",
"log", res.Log,
"error", res.Error,
"gas-used", res.GasUsed,
)
}

txResponses = append(txResponses, abci.ResponseDeliverTx{
ResponseBase: res.ResponseBase,
GasWanted: res.GasWanted,
GasUsed: res.GasUsed,
})

cfg.GenesisTxResultHandler(ctx, tx, res)
}
}

ctx.Logger().Debug("InitChainer: genesis transactions loaded",
"elapsed", time.Since(start))

// Done!
return abci.ResponseInitChain{
Validators: req.Validators,
TxResponses: txResponses,
}
}

// endBlockerApp is the app abstraction required by any EndBlocker
Expand Down
1 change: 1 addition & 0 deletions gno.land/pkg/gnoland/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type (
)

type mockVMKeeper struct {
vm.VMKeeperI
addPackageFn addPackageDelegate
callFn callDelegate
queryFn queryEvalDelegate
Expand Down
23 changes: 14 additions & 9 deletions gno.land/pkg/gnoland/node_inmemory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gnoland
import (
"fmt"
"log/slog"
"path/filepath"
"time"

abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
Expand All @@ -22,8 +23,10 @@ type InMemoryNodeConfig struct {
PrivValidator bft.PrivValidator // identity of the validator
Genesis *bft.GenesisDoc
TMConfig *tmcfg.Config
GenesisTxHandler GenesisTxHandler
GenesisMaxVMCycles int64

// If StdlibDir not set, then it's filepath.Join(TMConfig.RootDir, "gnovm", "stdlibs")
InitChainerConfig
}

// NewMockedPrivValidator generate a new key
Expand Down Expand Up @@ -70,7 +73,7 @@ func (cfg *InMemoryNodeConfig) validate() error {
return fmt.Errorf("`TMConfig.RootDir` is required to locate `stdlibs` directory")
}

if cfg.GenesisTxHandler == nil {
if cfg.GenesisTxResultHandler == nil {
return fmt.Errorf("`GenesisTxHandler` is required but not provided")
}

Expand All @@ -87,15 +90,17 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node,

evsw := events.NewEventSwitch()

if cfg.StdlibDir == "" {
cfg.StdlibDir = filepath.Join(cfg.TMConfig.RootDir, "gnovm", "stdlibs")
}

// Initialize the application with the provided options
gnoApp, err := NewAppWithOptions(&AppOptions{
Logger: logger,
GnoRootDir: cfg.TMConfig.RootDir,
GenesisTxHandler: cfg.GenesisTxHandler,
MaxCycles: cfg.GenesisMaxVMCycles,
DB: memdb.NewMemDB(),
EventSwitch: evsw,
CacheStdlibLoad: true,
Logger: logger,
MaxCycles: cfg.GenesisMaxVMCycles,
DB: memdb.NewMemDB(),
EventSwitch: evsw,
InitChainerConfig: cfg.InitChainerConfig,
})
if err != nil {
return nil, fmt.Errorf("error initializing new app: %w", err)
Expand Down
12 changes: 8 additions & 4 deletions gno.land/pkg/integration/testing_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNode
genesis := DefaultTestingGenesisConfig(t, gnoroot, pv.GetPubKey(), tmconfig)

return &gnoland.InMemoryNodeConfig{
PrivValidator: pv,
Genesis: genesis,
TMConfig: tmconfig,
GenesisTxHandler: gnoland.PanicOnFailingTxHandler,
PrivValidator: pv,
Genesis: genesis,
TMConfig: tmconfig,
InitChainerConfig: gnoland.InitChainerConfig{
GenesisTxResultHandler: gnoland.PanicOnFailingTxResultHandler,
CacheStdlibLoad: true,
// StdlibDir automatically set
},
}
}

Expand Down
Loading
Loading