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

fix(gno.land): make gno store cache respect rollbacks #2319

Merged
merged 52 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
14cea4f
wip: issue 2283 txtar
thehowl Jun 10, 2024
dcffca3
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Jul 3, 2024
a132452
current status dump
thehowl Jul 3, 2024
bf9a692
txtar for test4 issue
thehowl Jul 18, 2024
33c580c
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Jul 25, 2024
57f6f47
chain init and restart now works!
thehowl Jul 26, 2024
ed84bf7
testing fixes, etc.
thehowl Jul 26, 2024
4b318a0
fixup gnodev
thehowl Jul 26, 2024
7be7e37
testing
thehowl Jul 26, 2024
48593e7
fixup
thehowl Jul 26, 2024
74df3ea
fix gnodev tests
thehowl Jul 26, 2024
a7db07a
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Aug 2, 2024
dfab57a
fix vm tests
thehowl Aug 2, 2024
4f91956
add back clearobjectcache
thehowl Aug 2, 2024
0cab316
Merge branch 'dev/morgan/cache-types-test4-mess' into dev/morgan/issu…
thehowl Aug 5, 2024
a45d9c5
fixup gnoland restart
thehowl Aug 5, 2024
818558f
some reorg, add comments
thehowl Aug 20, 2024
e360017
refactors; add tests for pkg/gnoland
thehowl Aug 20, 2024
63641a1
add test for `gnoland start`, and docs
thehowl Aug 20, 2024
738ff0b
move restart.txtar to pkg/integration tests
thehowl Aug 20, 2024
7baf0de
unit tests for option setters
thehowl Aug 20, 2024
a38ff80
fix ci
thehowl Aug 20, 2024
b0a67ae
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Aug 20, 2024
3e6bb61
further gnoland app tests
thehowl Aug 20, 2024
1347c5f
benchmark and test out "stackable" maps
thehowl Aug 21, 2024
84ad015
add txLogMap as the default solution
thehowl Aug 21, 2024
2235cb6
clone test for txLogMap
thehowl Aug 21, 2024
9a1ba12
oops
thehowl Aug 21, 2024
40fae48
move panics into methods of transactionStore
thehowl Aug 21, 2024
6e2591a
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Aug 23, 2024
bcf0e0e
codereview changes
thehowl Aug 23, 2024
b859b07
add newGnoTransactionStore
thehowl Aug 23, 2024
78552f8
codereview changes
thehowl Aug 23, 2024
73af9c4
add tests for iterators
thehowl Aug 23, 2024
cba4c0f
refactor: move txLogMap into own internal package
thehowl Aug 23, 2024
cf76081
some consistency
thehowl Aug 23, 2024
00297d1
remove hashMap data type
thehowl Aug 23, 2024
bd3bf21
more tests
thehowl Aug 23, 2024
0909738
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Aug 26, 2024
5b9d8d5
more comments / examples on txlog
thehowl Aug 26, 2024
8eb0ce4
add gnoland start -non-validator; and failing test.
thehowl Aug 26, 2024
7109b8a
fix bug reported by milos
thehowl Aug 26, 2024
b00508f
add test for CopyFromCachedStore
thehowl Aug 26, 2024
d16d560
add a test for NewApp
thehowl Aug 26, 2024
df9d708
split up
thehowl Aug 27, 2024
2b2c7da
correctly use gas stores
thehowl Aug 28, 2024
26afca5
fix more tests
thehowl Aug 28, 2024
c027067
return error in the case of nil AppStates
thehowl Aug 28, 2024
5799ce1
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Sep 2, 2024
1869855
remove calls from mock
thehowl Sep 2, 2024
4ca129c
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/issue…
thehowl Sep 5, 2024
c27c30c
add TestAppOptions
thehowl Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions contribs/gnodev/pkg/dev/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,9 @@ 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
// Speed up stdlib loading after first start (saves about 2-3 seconds on each reload).
thehowl marked this conversation as resolved.
Show resolved Hide resolved
nodeConfig.CacheStdlibLoad = true
nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock

// recoverFromError handles panics and converts them to errors.
Expand Down Expand Up @@ -512,7 +514,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")
}

202 changes: 202 additions & 0 deletions gno.land/cmd/gnoland/testdata/restart_missing_type.txtar

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions gno.land/cmd/gnoland/testdata/restart_nonval.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This txtar tests for starting up a non-validator node; then also restarting it.
loadpkg gno.land/p/demo/avl

gnoland start -non-validator
gnoland restart
258 changes: 166 additions & 92 deletions gno.land/pkg/gnoland/app.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Package gnoland contains the bootstrapping code to launch a gno.land node.
package gnoland

import (
"fmt"
"log/slog"
"path/filepath"
"strconv"
"time"

"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
Expand All @@ -25,52 +27,41 @@
// Only goleveldb is supported for now.
_ "github.com/gnolang/gno/tm2/pkg/db/_tags"
_ "github.com/gnolang/gno/tm2/pkg/db/goleveldb"
"github.com/gnolang/gno/tm2/pkg/db/memdb"
)

// AppOptions contains the options to create the gno.land ABCI application.
thehowl marked this conversation as resolved.
Show resolved Hide resolved
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 // required
Logger *slog.Logger // defaults to log.NewNoopLogger()
EventSwitch events.EventSwitch // defaults to events.NewEventSwitch()
MaxCycles int64 // defaults to 0 (unlimited)
InitChainerConfig // options related to InitChainer
}

func NewAppOptions() *AppOptions {
return &AppOptions{
GenesisTxHandler: PanicOnFailingTxHandler,
Logger: log.NewNoopLogger(),
DB: memdb.NewMemDB(),
GnoRootDir: gnoenv.RootDir(),
EventSwitch: events.NilEventSwitch(),
func (c *AppOptions) validate() error {
// Required fields
if c.DB == nil {
return fmt.Errorf("no db provided")
}
return nil
}

func (c *AppOptions) validate() error {
func (c *AppOptions) setDefaults() {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
// Set defaults
if c.Logger == nil {
return fmt.Errorf("no logger provided")
c.Logger = log.NewNoopLogger()
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}

if c.DB == nil {
return fmt.Errorf("no db provided")
if c.EventSwitch == nil {
c.EventSwitch = events.NewEventSwitch()
}

return nil
}

// NewAppWithOptions creates the GnoLand application with specified options
// NewAppWithOptions creates the gno.land application with specified options.
func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) {
if err := cfg.validate(); err != nil {
return nil, err
}
cfg.setDefaults()

// Capabilities keys.
mainKey := store.NewStoreKey("main")
Expand All @@ -88,13 +79,13 @@
// 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,14 +99,28 @@
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
},
)

// Set begin and end transaction hooks.
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
// These are used to create gno transaction stores and commit them when finishing
// the tx - in other words, data from a failing transaction won't be persisted
// to the gno store caches.
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this should be named 'commit.' Here, it only writes to the store, while the actual commit happens in Commit(), not in EndBlock().

}
})
ajnavarro marked this conversation as resolved.
Show resolved Hide resolved

// Set up the event collector
c := newCollector[validatorUpdate](
cfg.EventSwitch, // global event switch filled by the node
Expand Down Expand Up @@ -143,13 +148,13 @@

// 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
}

// NewApp creates the GnoLand application.
// NewApp creates the gno.land application.
func NewApp(
dataRootDir string,
skipFailingGenesisTxs bool,
Expand All @@ -158,9 +163,16 @@
) (abci.Application, error) {
var err error

cfg := NewAppOptions()
cfg := &AppOptions{
Logger: logger,
EventSwitch: evsw,
InitChainerConfig: InitChainerConfig{
GenesisTxResultHandler: PanicOnFailingTxResultHandler,
StdlibDir: filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs"),
},
}
if skipFailingGenesisTxs {
cfg.GenesisTxHandler = NoopGenesisTxHandler
cfg.GenesisTxResultHandler = NoopGenesisTxResultHandler
}

// Get main DB.
Expand All @@ -169,74 +181,136 @@
return nil, fmt.Errorf("error initializing database %q using path %q: %w", dbm.GoLevelDBBackend, dataRootDir, err)
}

cfg.Logger = logger
cfg.EventSwitch = evsw

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) {}

Check warning on line 192 in gno.land/pkg/gnoland/app.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoland/app.go#L192

Added line #L192 was not covered by tests

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.
// [NewAppWithOptions] will set [InitChainerConfig.InitChainer] as its InitChainer
// function.
type InitChainerConfig struct {
// Handles the results of each genesis transaction.
GenesisTxResultHandler
deelawn marked this conversation as resolved.
Show resolved Hide resolved

// Standard library directory.
StdlibDir string
// Whether to keep a record of the DB operations to load standard libraries,
thehowl marked this conversation as resolved.
Show resolved Hide resolved
// 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
}

// 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)
// 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; immediately committed to store so that they are
// available for use when processing the genesis transactions below.
cfg.loadStdlibs(ctx)
ctx.Logger().Debug("InitChainer: standard libraries loaded",
"elapsed", time.Since(start))

// load app state. AppState may be nil mostly in some minimal testing setups;
// so log a warning when that happens.
var txResponses []abci.ResponseDeliverTx
if req.AppState != nil {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
genState, ok := req.AppState.(GnoGenesisState)
if !ok {
return abci.ResponseInitChain{
ResponseBase: abci.ResponseBase{
Error: abci.StringError(fmt.Sprintf("invalid AppState of type %T", req.AppState)),
},

Check warning on line 245 in gno.land/pkg/gnoland/app.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoland/app.go#L242-L245

Added lines #L242 - L245 were not covered by tests
}
}
txResponses = cfg.loadAppState(ctx, genState)
} else {
ctx.Logger().Warn("InitChainer: initializing chain without AppState (no genesis transactions or balances have been loaded)")
txResponses = make([]abci.ResponseDeliverTx, 0)
}
ctx.Logger().Debug("InitChainer: genesis transactions loaded",
"elapsed", time.Since(start))

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

func (cfg InitChainerConfig) loadStdlibs(ctx sdk.Context) {
// cache-wrapping is necessary for non-validator nodes; in the tm2 BaseApp,
// this is done using BaseApp.cacheTxContext; so we replicate it here.
ms := ctx.MultiStore()
msCache := ms.MultiCacheWrap()

stdlibCtx := cfg.vmKpr.MakeGnoTransactionStore(ctx)
stdlibCtx = stdlibCtx.WithMultiStore(msCache)
if cfg.CacheStdlibLoad {
cfg.vmKpr.LoadStdlibCached(stdlibCtx, cfg.StdlibDir)
} else {
cfg.vmKpr.LoadStdlib(stdlibCtx, cfg.StdlibDir)
}
cfg.vmKpr.CommitGnoTransactionStore(stdlibCtx)

// Done!
return abci.ResponseInitChain{
Validators: req.Validators,
TxResponses: txResponses,
msCache.MultiWrite()
}

func (cfg InitChainerConfig) loadAppState(ctx sdk.Context, state GnoGenesisState) []abci.ResponseDeliverTx {
// Parse and set genesis state balances
for _, bal := range state.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)

Check warning on line 288 in gno.land/pkg/gnoland/app.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoland/app.go#L288

Added line #L288 was not covered by tests
}
}

txResponses := make([]abci.ResponseDeliverTx, 0, len(state.Txs))
// Run genesis txs
for _, tx := range state.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,
)

Check warning on line 302 in gno.land/pkg/gnoland/app.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoland/app.go#L297-L302

Added lines #L297 - L302 were not covered by tests
}

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

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

// endBlockerApp is the app abstraction required by any EndBlocker
Expand Down
Loading
Loading