From 10d96efe1b143d54c64bd51adea00b783b8a0ec2 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 03:38:41 -0400 Subject: [PATCH 01/62] Fraudproof WIP --- baseapp/baseapp_test.go | 60 +++++++++++++++++++++++++++++++++++++++++ baseapp/fraudproof.go | 8 ++++++ 2 files changed, 68 insertions(+) create mode 100644 baseapp/fraudproof.go diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 474e84e5046..12866b1570e 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2153,3 +2153,63 @@ func TestBaseApp_Init(t *testing.T) { require.Equal(t, tc.expectedSnapshot.KeepRecent, tc.bapp.snapshotManager.GetKeepRecent()) } } + +func TestGenerateFraudProof(t *testing.T) { + + /* + 1. Create a fresh baseapp + 2. Create a 'block' and put transactions that set certain key/value pairs in it + 3. We should be able to `check block` IF block.hash not the same, trigger fraud: + - go through all transactions, run them, and keep track of which substores are being used + - export the SMTs inside those substores into a fraud proof data structure + + + // Question: What is a block? Right now abstract, need to make it more concrete + + */ + + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + + routerOpt := func(bapp *BaseApp) { + bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + kv := msg.(*msgKeyValue) + bapp.cms.GetKVStore(capKey2).Set(kv.Key, kv.Value) + return &sdk.Result{}, nil + })) + } + + app := setupBaseApp(t, + AppOptionFunc(routerOpt)) + + app.InitChain(abci.RequestInitChain{}) + + blocks := 1 + txsPerBlock := 2 + + r := rand.New(rand.NewSource(3920758213583)) + keyCounter := 0 + for height := int64(1); height <= int64(blocks); height++ { + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) + for txNum := 0; txNum < txsPerBlock; txNum++ { + tx := txTest{Msgs: []sdk.Msg{}} + for msgNum := 0; msgNum < 3; msgNum++ { + key := []byte(fmt.Sprintf("%v", keyCounter)) + value := make([]byte, 10000) + _, err := r.Read(value) + require.NoError(t, err) + tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) + keyCounter++ + } + txBytes, err := codec.Marshal(tx) + require.NoError(t, err) + resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + require.True(t, resp.IsOK(), "%v", resp.String()) + } + app.EndBlock(abci.RequestEndBlock{Height: height}) + commitResponse := app.Commit() + _ = commitResponse.GetData() + } + + return +} diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go new file mode 100644 index 00000000000..32d78874a9c --- /dev/null +++ b/baseapp/fraudproof.go @@ -0,0 +1,8 @@ +package baseapp + +// FraudProof is a fraud proof. +type FraudProof struct { + blockHeight uint64 + + // TODO: stateWitness StateWitness +} From c0c80aeb5d591f1921224b2dbdf48e4284b461da Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 14:05:47 -0400 Subject: [PATCH 02/62] Add more notes on future steps --- baseapp/baseapp_test.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 12866b1570e..73c19c5f83f 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2157,14 +2157,27 @@ func TestBaseApp_Init(t *testing.T) { func TestGenerateFraudProof(t *testing.T) { /* - 1. Create a fresh baseapp + 1. Create a fresh baseapp with a tracekv store 2. Create a 'block' and put transactions that set certain key/value pairs in it - 3. We should be able to `check block` IF block.hash not the same, trigger fraud: + 3. We should be able to `generate fraud proof` IF block.hash not the same, trigger fraud: - go through all transactions, run them, and keep track of which substores are being used - - export the SMTs inside those substores into a fraud proof data structure + - export the SMTs inside those substores into a fraud proof data structure along with block height - // Question: What is a block? Right now abstract, need to make it more concrete + Tests to write: + + 1. Block with bad txs: Txs that exceed gas limits, validateBasic fails, unregistered messages (see TestRunInvalidTransaction) + 2. Block with invalid appHash at the end + 3. Corrupted Fraud Proof: bad SMT format, insufficient key-value pairs inside SMT needed to verify fraud + 4. Bad block, fraud proof needed, fraud proof works, chain halts + + TODO: + Figure out how the tracekv interacts with the SMT multistore + Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx + + + Question: What is a block? Right now abstract, need to make it more concrete + Candidate Answer: For now, Block is just a list of a transactions and a header with an app hash */ @@ -2191,6 +2204,8 @@ func TestGenerateFraudProof(t *testing.T) { keyCounter := 0 for height := int64(1); height <= int64(blocks); height++ { app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) + + txs := make([]txTest, txsPerBlock) for txNum := 0; txNum < txsPerBlock; txNum++ { tx := txTest{Msgs: []sdk.Msg{}} for msgNum := 0; msgNum < 3; msgNum++ { @@ -2201,6 +2216,7 @@ func TestGenerateFraudProof(t *testing.T) { tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) keyCounter++ } + txs = append(txs, tx) txBytes, err := codec.Marshal(tx) require.NoError(t, err) resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) @@ -2209,6 +2225,11 @@ func TestGenerateFraudProof(t *testing.T) { app.EndBlock(abci.RequestEndBlock{Height: height}) commitResponse := app.Commit() _ = commitResponse.GetData() + + // Check Block here + + _ = txs + } return From 63161f55e0e375fff7aa44987820a78c08a9ffdf Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 21:43:04 -0400 Subject: [PATCH 03/62] Add tracer to baseapp test --- baseapp/baseapp_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 73c19c5f83f..e8c59c4957f 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -30,6 +30,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -2184,6 +2185,13 @@ func TestGenerateFraudProof(t *testing.T) { codec := codec.NewLegacyAmino() registerTestCodec(codec) + traceOpt := func(bapp *BaseApp) { + b := &bytes.Buffer{} + tc := types.TraceContext(map[string]interface{}{"blockHeight": 64}) + bapp.cms.SetTracer(b) + bapp.cms.SetTracingContext(tc) + } + routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { kv := msg.(*msgKeyValue) @@ -2193,6 +2201,7 @@ func TestGenerateFraudProof(t *testing.T) { } app := setupBaseApp(t, + AppOptionFunc(traceOpt), AppOptionFunc(routerOpt)) app.InitChain(abci.RequestInitChain{}) From 62b00500e1544a899524d9b9b83c8dc5d4cc8c8e Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 22:00:00 -0400 Subject: [PATCH 04/62] Try setting trace again --- baseapp/baseapp_test.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index e8c59c4957f..a378f1c46b1 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -30,7 +30,6 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" "github.com/cosmos/cosmos-sdk/testutil/testdata" - "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -2185,12 +2184,8 @@ func TestGenerateFraudProof(t *testing.T) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - traceOpt := func(bapp *BaseApp) { - b := &bytes.Buffer{} - tc := types.TraceContext(map[string]interface{}{"blockHeight": 64}) - bapp.cms.SetTracer(b) - bapp.cms.SetTracingContext(tc) - } + // traceWriter := &bytes.Buffer{} + // traceContext := types.TraceContext(map[string]interface{}{"blockHeight": 64}) routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { @@ -2201,8 +2196,9 @@ func TestGenerateFraudProof(t *testing.T) { } app := setupBaseApp(t, - AppOptionFunc(traceOpt), - AppOptionFunc(routerOpt)) + AppOptionFunc(routerOpt), + SetTrace(true), + ) app.InitChain(abci.RequestInitChain{}) From 2e6771ba68520cb331746cbfd7b21ce6b85ebad7 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 22:24:17 -0400 Subject: [PATCH 05/62] Add tracing store option and use --- baseapp/baseapp_test.go | 7 ++++--- baseapp/options.go | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index a378f1c46b1..8e7752a7bf1 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -30,6 +30,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -2184,8 +2185,8 @@ func TestGenerateFraudProof(t *testing.T) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - // traceWriter := &bytes.Buffer{} - // traceContext := types.TraceContext(map[string]interface{}{"blockHeight": 64}) + traceWriter := &bytes.Buffer{} + traceContext := types.TraceContext(map[string]interface{}{"blockHeight": 64}) routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { @@ -2197,7 +2198,7 @@ func TestGenerateFraudProof(t *testing.T) { app := setupBaseApp(t, AppOptionFunc(routerOpt), - SetTrace(true), + SetTracingEnabled(traceWriter, traceContext), ) app.InitChain(abci.RequestInitChain{}) diff --git a/baseapp/options.go b/baseapp/options.go index bbbaf16ea5e..0ede37caafe 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -21,6 +21,14 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { return func(config *multi.StoreParams, _ uint64) error { config.Pruning = opts; return nil } } +func SetTracingEnabled(w io.Writer, tc storetypes.TraceContext) StoreOption { + return func(cfg *multi.StoreParams, v uint64) error { + cfg.TraceWriter = w + cfg.TraceContext = tc + return nil + } +} + // SetMinGasPrices returns an option that sets the minimum gas prices on the app. func SetMinGasPrices(gasPricesStr string) AppOptionFunc { gasPrices, err := sdk.ParseDecCoins(gasPricesStr) From bc3e3a3843ca4cca226a9310f3d7e3d8c0f01c5a Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 22:29:14 -0400 Subject: [PATCH 06/62] Remove trace context since its automatically set --- baseapp/baseapp_test.go | 4 +--- baseapp/options.go | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 8e7752a7bf1..e226827c3c5 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -30,7 +30,6 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" "github.com/cosmos/cosmos-sdk/testutil/testdata" - "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -2186,7 +2185,6 @@ func TestGenerateFraudProof(t *testing.T) { registerTestCodec(codec) traceWriter := &bytes.Buffer{} - traceContext := types.TraceContext(map[string]interface{}{"blockHeight": 64}) routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { @@ -2198,7 +2196,7 @@ func TestGenerateFraudProof(t *testing.T) { app := setupBaseApp(t, AppOptionFunc(routerOpt), - SetTracingEnabled(traceWriter, traceContext), + SetTracingEnabled(traceWriter), ) app.InitChain(abci.RequestInitChain{}) diff --git a/baseapp/options.go b/baseapp/options.go index 0ede37caafe..deacfbe65b6 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -21,10 +21,9 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { return func(config *multi.StoreParams, _ uint64) error { config.Pruning = opts; return nil } } -func SetTracingEnabled(w io.Writer, tc storetypes.TraceContext) StoreOption { +func SetTracingEnabled(w io.Writer) StoreOption { return func(cfg *multi.StoreParams, v uint64) error { cfg.TraceWriter = w - cfg.TraceContext = tc return nil } } From 629fbc36440110ab01fede0f810376f5ff1be5df Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 22:31:46 -0400 Subject: [PATCH 07/62] Add updates in comments --- baseapp/baseapp_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index e226827c3c5..8e1e0073122 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2172,7 +2172,7 @@ func TestGenerateFraudProof(t *testing.T) { 4. Bad block, fraud proof needed, fraud proof works, chain halts TODO: - Figure out how the tracekv interacts with the SMT multistore + Figure out how the tracekv interacts with the SMT multistore - done Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx @@ -2212,7 +2212,7 @@ func TestGenerateFraudProof(t *testing.T) { txs := make([]txTest, txsPerBlock) for txNum := 0; txNum < txsPerBlock; txNum++ { tx := txTest{Msgs: []sdk.Msg{}} - for msgNum := 0; msgNum < 3; msgNum++ { + for msgNum := 0; msgNum < 1; msgNum++ { key := []byte(fmt.Sprintf("%v", keyCounter)) value := make([]byte, 10000) _, err := r.Read(value) @@ -2227,6 +2227,10 @@ func TestGenerateFraudProof(t *testing.T) { require.True(t, resp.IsOK(), "%v", resp.String()) } app.EndBlock(abci.RequestEndBlock{Height: height}) + + // Here, the store's traceKV should have been populated with all the operations that have taken place + // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure + commitResponse := app.Commit() _ = commitResponse.GetData() From c829b89d416e6878921567dc0621d07b7564aabf Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 23:12:35 -0400 Subject: [PATCH 08/62] Able to read one string while debugging, need multiple --- baseapp/baseapp_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 8e1e0073122..22e5c3b746a 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2212,7 +2212,7 @@ func TestGenerateFraudProof(t *testing.T) { txs := make([]txTest, txsPerBlock) for txNum := 0; txNum < txsPerBlock; txNum++ { tx := txTest{Msgs: []sdk.Msg{}} - for msgNum := 0; msgNum < 1; msgNum++ { + for msgNum := 0; msgNum < 2; msgNum++ { key := []byte(fmt.Sprintf("%v", keyCounter)) value := make([]byte, 10000) _, err := r.Read(value) @@ -2231,6 +2231,10 @@ func TestGenerateFraudProof(t *testing.T) { // Here, the store's traceKV should have been populated with all the operations that have taken place // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure + // reads one string + str := traceWriter.String() + _ = str + commitResponse := app.Commit() _ = commitResponse.GetData() From 91758410834658b938c6d3bb980bb5b393ec5e3e Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 23:16:05 -0400 Subject: [PATCH 09/62] Able to read two strings now, will keep following this pattern to interpret operations --- baseapp/baseapp_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 22e5c3b746a..fbba8d19ee0 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2232,8 +2232,11 @@ func TestGenerateFraudProof(t *testing.T) { // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure // reads one string - str := traceWriter.String() - _ = str + str1, _ := traceWriter.ReadString('\n') + _ = str1 + + str2, _ := traceWriter.ReadString('\n') + _ = str2 commitResponse := app.Commit() _ = commitResponse.GetData() From b29ccd456fe97916132013b4a524bc3b170e1568 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 23:28:22 -0400 Subject: [PATCH 10/62] Can loop over traceWriter strings now --- baseapp/baseapp_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index fbba8d19ee0..5dccbc4368c 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2231,12 +2231,12 @@ func TestGenerateFraudProof(t *testing.T) { // Here, the store's traceKV should have been populated with all the operations that have taken place // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure - // reads one string - str1, _ := traceWriter.ReadString('\n') - _ = str1 - - str2, _ := traceWriter.ReadString('\n') - _ = str2 + var line string + var err error + for err == nil { + line, err = traceWriter.ReadString('\n') + fmt.Println(line) + } commitResponse := app.Commit() _ = commitResponse.GetData() From 64a4d1be49c4633308bd1963c3f2f283c92509c8 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 3 Aug 2022 23:58:06 -0400 Subject: [PATCH 11/62] Add functionality to read operations from traceKV store --- store/tracekv/store.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/store/tracekv/store.go b/store/tracekv/store.go index 91f3c657682..eea85448d87 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -1,6 +1,7 @@ package tracekv import ( + "bytes" "encoding/base64" "encoding/json" "io" @@ -202,3 +203,19 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value io.WriteString(w, "\n") } + +// reaOperation reads a KVStore operation from the underlying buffer as +// JSON-encoded data where the key/value pair is base64 encoded. +func readOperation(r bytes.Buffer) traceOperation { + raw, err := r.ReadString('\n') + if err != nil { + panic(errors.Wrap(err, "failed to read trace operation")) + } + var traceOp traceOperation + err = json.Unmarshal([]byte(raw), &traceOp) + if err != nil { + panic(errors.Wrap(err, "failed to deserialize trace operation")) + } + + return traceOp +} From 813bda7ff836d78cdb0833d0bfa94afcf1f6d36c Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 00:10:22 -0400 Subject: [PATCH 12/62] Add functionality to get a set of all keys in traced operations --- store/tracekv/store.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/store/tracekv/store.go b/store/tracekv/store.go index eea85448d87..c35907d0a5f 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -91,6 +91,20 @@ func (tkv *Store) ReverseIterator(start, end []byte) types.Iterator { return tkv.iterator(start, end, false) } +// GetAllKeysUsedInTrace reads through all traced operations and returns +// a set of all the keys inside the trace operations +func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) map[string]bool { + var traceOp traceOperation + var err error + keys := make(map[string]bool) + for err == nil { + traceOp, err = readOperation(buf) + key := traceOp.Key + keys[key] = true + } + return keys +} + // iterator facilitates iteration over a KVStore. It delegates the necessary // calls to it's parent KVStore. func (tkv *Store) iterator(start, end []byte, ascending bool) types.Iterator { @@ -206,16 +220,16 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value // reaOperation reads a KVStore operation from the underlying buffer as // JSON-encoded data where the key/value pair is base64 encoded. -func readOperation(r bytes.Buffer) traceOperation { +func readOperation(r bytes.Buffer) (traceOperation, error) { + var traceOp traceOperation raw, err := r.ReadString('\n') if err != nil { - panic(errors.Wrap(err, "failed to read trace operation")) + return traceOp, errors.Wrap(err, "failed to read trace operation") } - var traceOp traceOperation err = json.Unmarshal([]byte(raw), &traceOp) if err != nil { - panic(errors.Wrap(err, "failed to deserialize trace operation")) + return traceOp, errors.Wrap(err, "failed to deserialize trace operation") } - return traceOp + return traceOp, nil } From 9ed6e7e095393b3099881580227938cc47b18738 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 00:34:32 -0400 Subject: [PATCH 13/62] Finish adding test for getAllKeysUsedInTrace --- store/tracekv/store.go | 13 +++++++++---- store/tracekv/store_test.go | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/store/tracekv/store.go b/store/tracekv/store.go index c35907d0a5f..62a006f7cbf 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -97,12 +97,14 @@ func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) map[string]bool { var traceOp traceOperation var err error keys := make(map[string]bool) - for err == nil { - traceOp, err = readOperation(buf) + for { + traceOp, err = readOperation(&buf) + if err != nil { + return keys + } key := traceOp.Key keys[key] = true } - return keys } // iterator facilitates iteration over a KVStore. It delegates the necessary @@ -220,9 +222,12 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value // reaOperation reads a KVStore operation from the underlying buffer as // JSON-encoded data where the key/value pair is base64 encoded. -func readOperation(r bytes.Buffer) (traceOperation, error) { +func readOperation(r *bytes.Buffer) (traceOperation, error) { var traceOp traceOperation raw, err := r.ReadString('\n') + if raw == "" { + return traceOp, errors.Wrap(err, "provided buffer is empty") + } if err != nil { return traceOp, errors.Wrap(err, "failed to read trace operation") } diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index 1b81e89bafd..57af7236d88 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -113,6 +113,25 @@ func TestTraceKVStoreSet(t *testing.T) { require.Panics(t, func() { store.Set(nil, []byte("value")) }, "setting a nil key should panic") } +func TestGetAllKeysUsedInTrace(t *testing.T) { + expectedKeys := map[string]bool{ + "a2V5MDAwMDAwMDE=": true, + "a2V5MDAwMDAwMDI=": true, + "a2V5MDAwMDAwMDM=": true, + } + + var buf bytes.Buffer + store := newEmptyTraceKVStore(&buf) + buf.Reset() + + for _, kvPair := range kvPairs { + store.Set(kvPair.Key, kvPair.Value) + } + + keys := store.GetAllKeysUsedInTrace(buf) + require.Equal(t, expectedKeys, keys) +} + func TestTraceKVStoreDelete(t *testing.T) { testCases := []struct { key []byte From 1c30ecdfd282168e58aef9a81a3ab49f05a40b3c Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 01:43:20 -0400 Subject: [PATCH 14/62] Able to use getAllKeys in baseapp test now --- baseapp/baseapp_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 5dccbc4368c..307c8ed1964 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -24,6 +24,7 @@ import ( pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" + "github.com/cosmos/cosmos-sdk/store/tracekv" storetypes "github.com/cosmos/cosmos-sdk/store/types" stypes "github.com/cosmos/cosmos-sdk/store/v2alpha1" "github.com/cosmos/cosmos-sdk/store/v2alpha1/multi" @@ -2184,7 +2185,7 @@ func TestGenerateFraudProof(t *testing.T) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - traceWriter := &bytes.Buffer{} + traceBuf := &bytes.Buffer{} routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { @@ -2196,7 +2197,7 @@ func TestGenerateFraudProof(t *testing.T) { app := setupBaseApp(t, AppOptionFunc(routerOpt), - SetTracingEnabled(traceWriter), + SetTracingEnabled(traceBuf), ) app.InitChain(abci.RequestInitChain{}) @@ -2231,12 +2232,9 @@ func TestGenerateFraudProof(t *testing.T) { // Here, the store's traceKV should have been populated with all the operations that have taken place // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure - var line string - var err error - for err == nil { - line, err = traceWriter.ReadString('\n') - fmt.Println(line) - } + traceKv := app.cms.GetKVStore(capKey2).(*tracekv.Store) + keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) + _ = keys commitResponse := app.Commit() _ = commitResponse.GetData() From b56b269fa96cb87ae83cbb9292a291e1a6da8b59 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 01:49:10 -0400 Subject: [PATCH 15/62] Add next step comments laying out thought process --- baseapp/baseapp_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 307c8ed1964..3a961d4648c 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2229,13 +2229,17 @@ func TestGenerateFraudProof(t *testing.T) { } app.EndBlock(abci.RequestEndBlock{Height: height}) - // Here, the store's traceKV should have been populated with all the operations that have taken place + // Here, the store's substores' traceKVs should have been populated with all the operations that have taken place // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure + // This is able to retrieve all the keys touched for a particular substore (capKey2 here) which makes up for one deepstree traceKv := app.cms.GetKVStore(capKey2).(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) _ = keys + // Next step: Figure out how to get all the storeKeys pertaining to each subStore now + // The number of storeKeys will be equal to number of deepSubTrees that the fraudproof data strucutre contains + commitResponse := app.Commit() _ = commitResponse.GetData() From 2f0859395902dfb32d29bb4ccf86d1c29e5deed2 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 16:06:38 -0400 Subject: [PATCH 16/62] Add more notes --- baseapp/baseapp_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 3a961d4648c..1c0161ae0a3 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2180,6 +2180,8 @@ func TestGenerateFraudProof(t *testing.T) { Question: What is a block? Right now abstract, need to make it more concrete Candidate Answer: For now, Block is just a list of a transactions and a header with an app hash + Note: In the current implementation, all substores might not be SMTs, do we assume they are? + */ codec := codec.NewLegacyAmino() @@ -2233,7 +2235,12 @@ func TestGenerateFraudProof(t *testing.T) { // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure // This is able to retrieve all the keys touched for a particular substore (capKey2 here) which makes up for one deepstree - traceKv := app.cms.GetKVStore(capKey2).(*tracekv.Store) + + // Seems like these substores are not SMT substores which is what's needed for deep subtrees, which blocks this currently. + // Current plan is to try to replace baseapp to use SMT store + + kvStore := app.cms.GetKVStore(capKey2) + traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) _ = keys From 8e733bd6b8566fc378ed658ef27e9fdaed9f67d8 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 16:07:37 -0400 Subject: [PATCH 17/62] Remove rocksdb for now because mac library issues locally, can revert back when merging --- db/internal/backends/imports.go | 1 - db/reexport.go | 1 - db/rocksdb/batch.go | 67 ----- db/rocksdb/creator.go | 17 -- db/rocksdb/db.go | 502 -------------------------------- db/rocksdb/db_test.go | 75 ----- db/rocksdb/iterator.go | 147 ---------- db/types/creator.go | 5 - orm/go.mod | 1 - 9 files changed, 816 deletions(-) delete mode 100644 db/rocksdb/batch.go delete mode 100644 db/rocksdb/creator.go delete mode 100644 db/rocksdb/db.go delete mode 100644 db/rocksdb/db_test.go delete mode 100644 db/rocksdb/iterator.go diff --git a/db/internal/backends/imports.go b/db/internal/backends/imports.go index 2069e37c386..2a57d4eca39 100644 --- a/db/internal/backends/imports.go +++ b/db/internal/backends/imports.go @@ -4,5 +4,4 @@ package backends import ( _ "github.com/cosmos/cosmos-sdk/db/badgerdb" _ "github.com/cosmos/cosmos-sdk/db/memdb" - _ "github.com/cosmos/cosmos-sdk/db/rocksdb" ) diff --git a/db/reexport.go b/db/reexport.go index 05214a42e23..f85c37507bc 100644 --- a/db/reexport.go +++ b/db/reexport.go @@ -21,7 +21,6 @@ var ( ErrVersionDoesNotExist = types.ErrVersionDoesNotExist MemDBBackend = types.MemDBBackend - RocksDBBackend = types.RocksDBBackend BadgerDBBackend = types.BadgerDBBackend NewDB = types.NewDB diff --git a/db/rocksdb/batch.go b/db/rocksdb/batch.go deleted file mode 100644 index 6f497ede3e2..00000000000 --- a/db/rocksdb/batch.go +++ /dev/null @@ -1,67 +0,0 @@ -package rocksdb - -import ( - "sync/atomic" - - dbutil "github.com/cosmos/cosmos-sdk/db/internal" - "github.com/cosmos/cosmos-sdk/db/types" - "github.com/cosmos/gorocksdb" -) - -type rocksDBBatch struct { - batch *gorocksdb.WriteBatch - mgr *dbManager -} - -var _ types.Writer = (*rocksDBBatch)(nil) - -func (mgr *dbManager) newRocksDBBatch() *rocksDBBatch { - return &rocksDBBatch{ - batch: gorocksdb.NewWriteBatch(), - mgr: mgr, - } -} - -// Set implements Writer. -func (b *rocksDBBatch) Set(key, value []byte) error { - if err := dbutil.ValidateKv(key, value); err != nil { - return err - } - if b.batch == nil { - return types.ErrTransactionClosed - } - b.batch.Put(key, value) - return nil -} - -// Delete implements Writer. -func (b *rocksDBBatch) Delete(key []byte) error { - if len(key) == 0 { - return types.ErrKeyEmpty - } - if b.batch == nil { - return types.ErrTransactionClosed - } - b.batch.Delete(key) - return nil -} - -// Write implements Writer. -func (b *rocksDBBatch) Commit() (err error) { - if b.batch == nil { - return types.ErrTransactionClosed - } - defer func() { err = dbutil.CombineErrors(err, b.Discard(), "Discard also failed") }() - err = b.mgr.current.Write(b.mgr.opts.wo, b.batch) - return -} - -// Close implements Writer. -func (b *rocksDBBatch) Discard() error { - if b.batch != nil { - defer atomic.AddInt32(&b.mgr.openWriters, -1) - b.batch.Destroy() - b.batch = nil - } - return nil -} diff --git a/db/rocksdb/creator.go b/db/rocksdb/creator.go deleted file mode 100644 index afedb4beb8e..00000000000 --- a/db/rocksdb/creator.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build rocksdb - -package rocksdb - -import ( - "path/filepath" - - "github.com/cosmos/cosmos-sdk/db/types" -) - -func init() { - creator := func(name string, dir string) (types.Connection, error) { - dir = filepath.Join(dir, name) - return NewDB(dir) - } - types.RegisterCreator(types.RocksDBBackend, creator, false) -} diff --git a/db/rocksdb/db.go b/db/rocksdb/db.go deleted file mode 100644 index d42bf377e7d..00000000000 --- a/db/rocksdb/db.go +++ /dev/null @@ -1,502 +0,0 @@ -package rocksdb - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" - "sync/atomic" - - dbutil "github.com/cosmos/cosmos-sdk/db/internal" - "github.com/cosmos/cosmos-sdk/db/types" - "github.com/cosmos/gorocksdb" -) - -var ( - currentDBFileName string = "current.db" - checkpointFileFormat string = "%020d.db" -) - -var ( - _ types.Connection = (*RocksDB)(nil) - _ types.Reader = (*dbTxn)(nil) - _ types.Writer = (*dbWriter)(nil) - _ types.ReadWriter = (*dbWriter)(nil) -) - -// RocksDB is a connection to a RocksDB key-value database. -type RocksDB = dbManager - -type dbManager struct { - current *dbConnection - dir string - opts dbOptions - vmgr *types.VersionManager - mtx sync.RWMutex - // Track open Writers - openWriters int32 - cpCache checkpointCache -} - -type dbConnection = gorocksdb.OptimisticTransactionDB - -type checkpointCache struct { - cache map[uint64]*cpCacheEntry - mtx sync.RWMutex -} - -type cpCacheEntry struct { - cxn *dbConnection - openCount uint -} - -type dbTxn struct { - txn *gorocksdb.Transaction - mgr *dbManager - version uint64 -} -type dbWriter struct{ dbTxn } - -type dbOptions struct { - dbo *gorocksdb.Options - txo *gorocksdb.OptimisticTransactionOptions - ro *gorocksdb.ReadOptions - wo *gorocksdb.WriteOptions -} - -// NewDB creates a new RocksDB key-value database with inside the given directory. -// If dir does not exist, it will be created. -func NewDB(dir string) (*dbManager, error) { - if err := os.MkdirAll(dir, 0o755); err != nil { - return nil, err - } - - // default rocksdb option, good enough for most cases, including heavy workloads. - // 1GB table cache, 512MB write buffer(may use 50% more on heavy workloads). - // compression: snappy as default, need to -lsnappy to enable. - bbto := gorocksdb.NewDefaultBlockBasedTableOptions() - bbto.SetBlockCache(gorocksdb.NewLRUCache(1 << 30)) - bbto.SetFilterPolicy(gorocksdb.NewBloomFilter(10)) - dbo := gorocksdb.NewDefaultOptions() - dbo.SetBlockBasedTableFactory(bbto) - dbo.SetCreateIfMissing(true) - dbo.IncreaseParallelism(runtime.NumCPU()) - // 1.5GB maximum memory use for writebuffer. - dbo.OptimizeLevelStyleCompaction(1<<30 + 512<<20) - - opts := dbOptions{ - dbo: dbo, - txo: gorocksdb.NewDefaultOptimisticTransactionOptions(), - ro: gorocksdb.NewDefaultReadOptions(), - wo: gorocksdb.NewDefaultWriteOptions(), - } - mgr := &dbManager{ - dir: dir, - opts: opts, - cpCache: checkpointCache{cache: map[uint64]*cpCacheEntry{}}, - } - - err := os.MkdirAll(mgr.checkpointsDir(), 0o755) - if err != nil { - return nil, err - } - if mgr.vmgr, err = readVersions(mgr.checkpointsDir()); err != nil { - return nil, err - } - dbPath := filepath.Join(dir, currentDBFileName) - // if the current db file is missing but there are checkpoints, restore it - if mgr.vmgr.Count() > 0 { - if _, err = os.Stat(dbPath); os.IsNotExist(err) { - err = mgr.restoreFromCheckpoint(mgr.vmgr.Last(), dbPath) - if err != nil { - return nil, err - } - } - } - mgr.current, err = gorocksdb.OpenOptimisticTransactionDb(dbo, dbPath) - if err != nil { - return nil, err - } - return mgr, nil -} - -func (mgr *dbManager) checkpointsDir() string { - return filepath.Join(mgr.dir, "checkpoints") -} - -// Reads directory for checkpoints files -func readVersions(dir string) (*types.VersionManager, error) { - files, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - var versions []uint64 - for _, f := range files { - var version uint64 - if _, err := fmt.Sscanf(f.Name(), checkpointFileFormat, &version); err != nil { - return nil, err - } - versions = append(versions, version) - } - return types.NewVersionManager(versions), nil -} - -func (mgr *dbManager) checkpointPath(version uint64) (string, error) { - dbPath := filepath.Join(mgr.checkpointsDir(), fmt.Sprintf(checkpointFileFormat, version)) - if stat, err := os.Stat(dbPath); err != nil { - if errors.Is(err, os.ErrNotExist) { - err = types.ErrVersionDoesNotExist - } - return "", err - } else if !stat.IsDir() { - return "", types.ErrVersionDoesNotExist - } - return dbPath, nil -} - -func (mgr *dbManager) openCheckpoint(version uint64) (*dbConnection, error) { - mgr.cpCache.mtx.Lock() - defer mgr.cpCache.mtx.Unlock() - cp, has := mgr.cpCache.cache[version] - if has { - cp.openCount += 1 - return cp.cxn, nil - } - dbPath, err := mgr.checkpointPath(version) - if err != nil { - return nil, err - } - db, err := gorocksdb.OpenOptimisticTransactionDb(mgr.opts.dbo, dbPath) - if err != nil { - return nil, err - } - mgr.cpCache.cache[version] = &cpCacheEntry{cxn: db, openCount: 1} - return db, nil -} - -func (mgr *dbManager) Reader() types.Reader { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - return &dbTxn{ - // Note: oldTransaction could be passed here as a small optimization to - // avoid allocating a new object. - txn: mgr.current.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), - mgr: mgr, - } -} - -func (mgr *dbManager) ReaderAt(version uint64) (types.Reader, error) { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - d, err := mgr.openCheckpoint(version) - if err != nil { - return nil, err - } - - return &dbTxn{ - txn: d.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), - mgr: mgr, - version: version, - }, nil -} - -func (mgr *dbManager) ReadWriter() types.ReadWriter { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - atomic.AddInt32(&mgr.openWriters, 1) - return &dbWriter{dbTxn{ - txn: mgr.current.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), - mgr: mgr, - }} -} - -func (mgr *dbManager) Writer() types.Writer { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - atomic.AddInt32(&mgr.openWriters, 1) - return mgr.newRocksDBBatch() -} - -func (mgr *dbManager) Versions() (types.VersionSet, error) { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - return mgr.vmgr, nil -} - -// SaveNextVersion implements Connection. -func (mgr *dbManager) SaveNextVersion() (uint64, error) { - return mgr.save(0) -} - -// SaveVersion implements Connection. -func (mgr *dbManager) SaveVersion(target uint64) error { - if target == 0 { - return types.ErrInvalidVersion - } - _, err := mgr.save(target) - return err -} - -func (mgr *dbManager) save(target uint64) (uint64, error) { - mgr.mtx.Lock() - defer mgr.mtx.Unlock() - if mgr.openWriters > 0 { - return 0, types.ErrOpenTransactions - } - newVmgr := mgr.vmgr.Copy() - target, err := newVmgr.Save(target) - if err != nil { - return 0, err - } - cp, err := mgr.current.NewCheckpoint() - if err != nil { - return 0, err - } - dir := filepath.Join(mgr.checkpointsDir(), fmt.Sprintf(checkpointFileFormat, target)) - if err := cp.CreateCheckpoint(dir, 0); err != nil { - return 0, err - } - cp.Destroy() - mgr.vmgr = newVmgr - return target, nil -} - -func (mgr *dbManager) DeleteVersion(ver uint64) error { - if mgr.cpCache.has(ver) { - return types.ErrOpenTransactions - } - mgr.mtx.Lock() - defer mgr.mtx.Unlock() - dbPath, err := mgr.checkpointPath(ver) - if err != nil { - return err - } - mgr.vmgr = mgr.vmgr.Copy() - mgr.vmgr.Delete(ver) - return os.RemoveAll(dbPath) -} - -func (mgr *dbManager) Revert() (err error) { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - if mgr.openWriters > 0 { - return types.ErrOpenTransactions - } - return mgr.revert(mgr.vmgr.Last()) -} - -func (mgr *dbManager) RevertTo(target uint64) (err error) { - mgr.mtx.RLock() - defer mgr.mtx.RUnlock() - if mgr.openWriters > 0 { - return types.ErrOpenTransactions - } - if !mgr.vmgr.Exists(target) { - return types.ErrVersionDoesNotExist - } - err = mgr.revert(target) - if err != nil { - return - } - mgr.vmgr.DeleteAbove(target) - return -} - -func (mgr *dbManager) revert(target uint64) (err error) { - // Close current connection and replace it with a checkpoint (created from the last checkpoint) - mgr.current.Close() - dbPath := filepath.Join(mgr.dir, currentDBFileName) - err = os.RemoveAll(dbPath) - if err != nil { - return - } - if target != 0 { // when target is 0, restore no checkpoints - err = mgr.restoreFromCheckpoint(target, dbPath) - if err != nil { - return - } - } - mgr.current, err = gorocksdb.OpenOptimisticTransactionDb(mgr.opts.dbo, dbPath) - return -} - -func (mgr *dbManager) restoreFromCheckpoint(version uint64, path string) error { - cxn, err := mgr.openCheckpoint(version) - if err != nil { - return err - } - defer mgr.cpCache.decrement(version) - cp, err := cxn.NewCheckpoint() - if err != nil { - return err - } - err = cp.CreateCheckpoint(path, 0) - if err != nil { - return err - } - cp.Destroy() - return nil -} - -// Close implements Connection. -func (mgr *dbManager) Close() error { - mgr.current.Close() - mgr.opts.destroy() - return nil -} - -// Stats implements Connection. -func (mgr *dbManager) Stats() map[string]string { - keys := []string{"rocksdb.stats"} - stats := make(map[string]string, len(keys)) - for _, key := range keys { - stats[key] = mgr.current.GetProperty(key) - } - return stats -} - -// Get implements Reader. -func (tx *dbTxn) Get(key []byte) ([]byte, error) { - if tx.txn == nil { - return nil, types.ErrTransactionClosed - } - if len(key) == 0 { - return nil, types.ErrKeyEmpty - } - res, err := tx.txn.Get(tx.mgr.opts.ro, key) - if err != nil { - return nil, err - } - return moveSliceToBytes(res), nil -} - -// Get implements Reader. -func (tx *dbWriter) Get(key []byte) ([]byte, error) { - if tx.txn == nil { - return nil, types.ErrTransactionClosed - } - if len(key) == 0 { - return nil, types.ErrKeyEmpty - } - res, err := tx.txn.GetForUpdate(tx.mgr.opts.ro, key) - if err != nil { - return nil, err - } - return moveSliceToBytes(res), nil -} - -// Has implements Reader. -func (tx *dbTxn) Has(key []byte) (bool, error) { - bytes, err := tx.Get(key) - if err != nil { - return false, err - } - return bytes != nil, nil -} - -// Set implements Writer. -func (tx *dbWriter) Set(key []byte, value []byte) error { - if tx.txn == nil { - return types.ErrTransactionClosed - } - if err := dbutil.ValidateKv(key, value); err != nil { - return err - } - return tx.txn.Put(key, value) -} - -// Delete implements Writer. -func (tx *dbWriter) Delete(key []byte) error { - if tx.txn == nil { - return types.ErrTransactionClosed - } - if len(key) == 0 { - return types.ErrKeyEmpty - } - return tx.txn.Delete(key) -} - -func (tx *dbWriter) Commit() (err error) { - if tx.txn == nil { - return types.ErrTransactionClosed - } - defer func() { err = dbutil.CombineErrors(err, tx.Discard(), "Discard also failed") }() - err = tx.txn.Commit() - return -} - -func (tx *dbTxn) Discard() error { - if tx.txn == nil { - return nil // Discard() is idempotent - } - defer func() { tx.txn.Destroy(); tx.txn = nil }() - if tx.version == 0 { - return nil - } - if !tx.mgr.cpCache.decrement(tx.version) { - return fmt.Errorf("transaction has no corresponding checkpoint cache entry: %v", tx.version) - } - return nil -} - -func (tx *dbWriter) Discard() error { - if tx.txn != nil { - defer atomic.AddInt32(&tx.mgr.openWriters, -1) - } - return tx.dbTxn.Discard() -} - -// Iterator implements Reader. -func (tx *dbTxn) Iterator(start, end []byte) (types.Iterator, error) { - if tx.txn == nil { - return nil, types.ErrTransactionClosed - } - if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { - return nil, types.ErrKeyEmpty - } - itr := tx.txn.NewIterator(tx.mgr.opts.ro) - return newRocksDBIterator(itr, start, end, false), nil -} - -// ReverseIterator implements Reader. -func (tx *dbTxn) ReverseIterator(start, end []byte) (types.Iterator, error) { - if tx.txn == nil { - return nil, types.ErrTransactionClosed - } - if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { - return nil, types.ErrKeyEmpty - } - itr := tx.txn.NewIterator(tx.mgr.opts.ro) - return newRocksDBIterator(itr, start, end, true), nil -} - -func (o dbOptions) destroy() { - o.ro.Destroy() - o.wo.Destroy() - o.txo.Destroy() - o.dbo.Destroy() -} - -func (cpc *checkpointCache) has(ver uint64) bool { - cpc.mtx.RLock() - defer cpc.mtx.RUnlock() - _, has := cpc.cache[ver] - return has -} - -func (cpc *checkpointCache) decrement(ver uint64) bool { - cpc.mtx.Lock() - defer cpc.mtx.Unlock() - cp, has := cpc.cache[ver] - if !has { - return false - } - cp.openCount -= 1 - if cp.openCount == 0 { - cp.cxn.Close() - delete(cpc.cache, ver) - } - return true -} diff --git a/db/rocksdb/db_test.go b/db/rocksdb/db_test.go deleted file mode 100644 index 0d67b55e6af..00000000000 --- a/db/rocksdb/db_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package rocksdb - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/db/dbtest" - "github.com/cosmos/cosmos-sdk/db/types" -) - -func load(t *testing.T, dir string) types.Connection { - d, err := NewDB(dir) - require.NoError(t, err) - return d -} - -func TestGetSetHasDelete(t *testing.T) { - dbtest.DoTestGetSetHasDelete(t, load) -} - -func TestIterators(t *testing.T) { - dbtest.DoTestIterators(t, load) -} - -func TestTransactions(t *testing.T) { - dbtest.DoTestTransactions(t, load, true) -} - -func TestVersioning(t *testing.T) { - dbtest.DoTestVersioning(t, load) -} - -func TestRevert(t *testing.T) { - dbtest.DoTestRevert(t, load, false) - dbtest.DoTestRevert(t, load, true) -} - -func TestReloadDB(t *testing.T) { - dbtest.DoTestReloadDB(t, load) -} - -// Test that the DB can be reloaded after a failed Revert -func TestRevertRecovery(t *testing.T) { - dir := t.TempDir() - d, err := NewDB(dir) - require.NoError(t, err) - txn := d.Writer() - require.NoError(t, txn.Set([]byte{1}, []byte{1})) - require.NoError(t, txn.Commit()) - _, err = d.SaveNextVersion() - require.NoError(t, err) - txn = d.Writer() - require.NoError(t, txn.Set([]byte{2}, []byte{2})) - require.NoError(t, txn.Commit()) - - // move checkpoints dir temporarily to trigger an error - hideDir := filepath.Join(dir, "hide_checkpoints") - require.NoError(t, os.Rename(d.checkpointsDir(), hideDir)) - require.Error(t, d.Revert()) - require.NoError(t, os.Rename(hideDir, d.checkpointsDir())) - - d, err = NewDB(dir) - require.NoError(t, err) - view := d.Reader() - val, err := view.Get([]byte{1}) - require.NoError(t, err) - require.Equal(t, []byte{1}, val) - val, err = view.Get([]byte{2}) - require.NoError(t, err) - require.Nil(t, val) - view.Discard() -} diff --git a/db/rocksdb/iterator.go b/db/rocksdb/iterator.go deleted file mode 100644 index 231d7dfbedb..00000000000 --- a/db/rocksdb/iterator.go +++ /dev/null @@ -1,147 +0,0 @@ -package rocksdb - -import ( - "bytes" - - "github.com/cosmos/cosmos-sdk/db/types" - "github.com/cosmos/gorocksdb" -) - -type rocksDBIterator struct { - source *gorocksdb.Iterator - start, end []byte - isReverse bool - isInvalid bool - // Whether iterator has been advanced to the first element (is fully initialized) - primed bool -} - -var _ types.Iterator = (*rocksDBIterator)(nil) - -func newRocksDBIterator(source *gorocksdb.Iterator, start, end []byte, isReverse bool) *rocksDBIterator { - if isReverse { - if end == nil { - source.SeekToLast() - } else { - source.Seek(end) - if source.Valid() { - eoakey := moveSliceToBytes(source.Key()) // end or after key - if bytes.Compare(end, eoakey) <= 0 { - source.Prev() - } - } else { - source.SeekToLast() - } - } - } else { - if start == nil { - source.SeekToFirst() - } else { - source.Seek(start) - } - } - return &rocksDBIterator{ - source: source, - start: start, - end: end, - isReverse: isReverse, - isInvalid: false, - primed: false, - } -} - -// Domain implements Iterator. -func (itr *rocksDBIterator) Domain() ([]byte, []byte) { - return itr.start, itr.end -} - -// Valid implements Iterator. -func (itr *rocksDBIterator) Valid() bool { - if !itr.primed { - return false - } - - if itr.isInvalid { - return false - } - - if !itr.source.Valid() { - itr.isInvalid = true - return false - } - - var ( - start = itr.start - end = itr.end - key = moveSliceToBytes(itr.source.Key()) - ) - // If key is end or past it, invalid. - if itr.isReverse { - if start != nil && bytes.Compare(key, start) < 0 { - itr.isInvalid = true - return false - } - } else { - if end != nil && bytes.Compare(key, end) >= 0 { - itr.isInvalid = true - return false - } - } - return true -} - -// Key implements Iterator. -func (itr *rocksDBIterator) Key() []byte { - itr.assertIsValid() - return moveSliceToBytes(itr.source.Key()) -} - -// Value implements Iterator. -func (itr *rocksDBIterator) Value() []byte { - itr.assertIsValid() - return moveSliceToBytes(itr.source.Value()) -} - -// Next implements Iterator. -func (itr *rocksDBIterator) Next() bool { - if !itr.primed { - itr.primed = true - } else { - if itr.isReverse { - itr.source.Prev() - } else { - itr.source.Next() - } - } - return itr.Valid() -} - -// Error implements Iterator. -func (itr *rocksDBIterator) Error() error { - return itr.source.Err() -} - -// Close implements Iterator. -func (itr *rocksDBIterator) Close() error { - itr.source.Close() - return nil -} - -func (itr *rocksDBIterator) assertIsValid() { - if !itr.Valid() { - panic("iterator is invalid") - } -} - -// moveSliceToBytes will free the slice and copy out a go []byte -// This function can be applied on *Slice returned from Key() and Value() -// of an Iterator, because they are marked as freed. -func moveSliceToBytes(s *gorocksdb.Slice) []byte { - defer s.Free() - if !s.Exists() { - return nil - } - v := make([]byte, s.Size()) - copy(v, s.Data()) - return v -} diff --git a/db/types/creator.go b/db/types/creator.go index 7492465beba..73c40d8dc36 100644 --- a/db/types/creator.go +++ b/db/types/creator.go @@ -12,11 +12,6 @@ const ( // MemDBBackend represents in-memory key value store, which is mostly used // for testing. MemDBBackend BackendType = "memdb" - // RocksDBBackend represents rocksdb (uses github.com/cosmos/gorocksdb) - // - EXPERIMENTAL - // - requires gcc - // - use rocksdb build tag (go build -tags rocksdb) - RocksDBBackend BackendType = "rocksdb" // BadgerDBBackend represents BadgerDB // - pure Go // - requires badgerdb build tag diff --git a/orm/go.mod b/orm/go.mod index 41f54ac2dc5..f4124d3260f 100644 --- a/orm/go.mod +++ b/orm/go.mod @@ -23,7 +23,6 @@ require ( github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cockroachdb/apd/v3 v3.1.0 // indirect - github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cucumber/common/gherkin/go/v22 v22.0.0 // indirect github.com/cucumber/common/messages/go/v17 v17.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect From c058060671b27bf9921d1217773b967f56095a58 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 19:27:06 -0400 Subject: [PATCH 18/62] Update baseapp to use new multistore --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index d1077fe2ca7..b9d377b2d4e 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -241,7 +241,7 @@ func (app *BaseApp) loadStore() error { return err } } - app.cms, err = multi.NewV1MultiStoreAsV2(app.db, config) + app.cms, err = multi.NewStore(app.db, config) if err != nil { return fmt.Errorf("failed to load store: %w", err) } From 8b667f41325e97e99393d82911d16cde986971df Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 20:12:15 -0400 Subject: [PATCH 19/62] Fix tracekv string decoding error --- store/tracekv/store.go | 7 +++++-- store/tracekv/store_test.go | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/store/tracekv/store.go b/store/tracekv/store.go index 62a006f7cbf..d10213f6704 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -102,8 +102,11 @@ func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) map[string]bool { if err != nil { return keys } - key := traceOp.Key - keys[key] = true + key, err := base64.StdEncoding.DecodeString(traceOp.Key) + if err != nil { + panic(errors.Wrap(err, "failed to decode key read from buf")) + } + keys[string(key)] = true } } diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index 57af7236d88..d5f8d093bba 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -115,9 +115,9 @@ func TestTraceKVStoreSet(t *testing.T) { func TestGetAllKeysUsedInTrace(t *testing.T) { expectedKeys := map[string]bool{ - "a2V5MDAwMDAwMDE=": true, - "a2V5MDAwMDAwMDI=": true, - "a2V5MDAwMDAwMDM=": true, + string(kvPairs[0].Key): true, + string(kvPairs[1].Key): true, + string(kvPairs[2].Key): true, } var buf bytes.Buffer From 91bb2267dc4c7f5c85526166c82036004407f8af Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 20:14:04 -0400 Subject: [PATCH 20/62] Add helper functions needd in v2alpha1 --- store/v2alpha1/multi/store.go | 8 ++++++++ store/v2alpha1/smt/store.go | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 7a42fd33683..54c806b2339 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -1040,3 +1040,11 @@ func (s *Store) GetPruning() pruningtypes.PruningOptions { func (s *Store) SetPruning(po pruningtypes.PruningOptions) { s.pruningManager.SetOptions(po) } + +func (s *Store) GetSubStoreSMT(key string) *smt.Store { + sub, err := s.getSubstore(key) + if err != nil { + panic(err) + } + return sub.stateCommitmentStore +} diff --git a/store/v2alpha1/smt/store.go b/store/v2alpha1/smt/store.go index ea902ff1c58..9bcb60433b5 100644 --- a/store/v2alpha1/smt/store.go +++ b/store/v2alpha1/smt/store.go @@ -77,6 +77,17 @@ func (s *Store) GetProofICS23(key []byte) (*ics23.CommitmentProof, error) { return createIcs23Proof(s, key) } +func (s *Store) GetSMTProof(key []byte) (*smt.SparseMerkleProof, error) { + if len(key) == 0 { + return nil, errKeyEmpty + } + proof, err := s.tree.Prove(key) + if err != nil { + return nil, err + } + return &proof, nil +} + func (s *Store) Root() []byte { return s.tree.Root() } // BasicKVStore interface below: From 15406ae9e7615f3bd59a32cfeabcd7a762441beb Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 20:14:17 -0400 Subject: [PATCH 21/62] Able to generate a deep subtree now --- baseapp/baseapp_test.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 1c0161ae0a3..b0dfb275aab 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2,6 +2,7 @@ package baseapp import ( "bytes" + "crypto/sha512" "encoding/binary" "encoding/json" "fmt" @@ -11,13 +12,6 @@ import ( "testing" "time" - "github.com/gogo/protobuf/jsonpb" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" dbm "github.com/cosmos/cosmos-sdk/db" @@ -34,6 +28,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + "github.com/gogo/protobuf/jsonpb" + "github.com/lazyledger/smt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) var ( @@ -2239,13 +2240,24 @@ func TestGenerateFraudProof(t *testing.T) { // Seems like these substores are not SMT substores which is what's needed for deep subtrees, which blocks this currently. // Current plan is to try to replace baseapp to use SMT store - kvStore := app.cms.GetKVStore(capKey2) + // Generates a deepsubtree for one substore + currKey := capKey2 + + kvStore := app.cms.GetKVStore(currKey) traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) - _ = keys - // Next step: Figure out how to get all the storeKeys pertaining to each subStore now - // The number of storeKeys will be equal to number of deepSubTrees that the fraudproof data strucutre contains + substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(currKey.Name()) + + deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), substoreSMT.Root()) + for key := range keys { + value := substoreSMT.Get([]byte(key)) + proof, err := substoreSMT.GetSMTProof([]byte(key)) + require.Nil(t, err) + deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + } + + // Next step: Go over all the storeKeys pertaining to each subStore now commitResponse := app.Commit() _ = commitResponse.GetData() From 6a0ef5b3bff73aff414ad1e3b4631485b43c50df Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 20:24:34 -0400 Subject: [PATCH 22/62] Able to generate deepsubtrees for all substores now --- baseapp/baseapp_test.go | 33 ++++++++++++++++++--------------- store/v2alpha1/multi/store.go | 8 ++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index b0dfb275aab..1364b02bbb5 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2240,21 +2240,24 @@ func TestGenerateFraudProof(t *testing.T) { // Seems like these substores are not SMT substores which is what's needed for deep subtrees, which blocks this currently. // Current plan is to try to replace baseapp to use SMT store - // Generates a deepsubtree for one substore - currKey := capKey2 - - kvStore := app.cms.GetKVStore(currKey) - traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) - - substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(currKey.Name()) - - deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), substoreSMT.Root()) - for key := range keys { - value := substoreSMT.Get([]byte(key)) - proof, err := substoreSMT.GetSMTProof([]byte(key)) - require.Nil(t, err) - deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + cms := app.cms.(*multi.Store) + storeKeys := cms.GetAllStoreKeys() + + for storeKey := range storeKeys { + // Generates a deepsubtree for one substore + kvStore := cms.GetKVStore(storeKey) + traceKv := kvStore.(*tracekv.Store) + keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) + + substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) + + deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), substoreSMT.Root()) + for key := range keys { + value := substoreSMT.Get([]byte(key)) + proof, err := substoreSMT.GetSMTProof([]byte(key)) + require.Nil(t, err) + deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + } } // Next step: Go over all the storeKeys pertaining to each subStore now diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 54c806b2339..9568416c641 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -1048,3 +1048,11 @@ func (s *Store) GetSubStoreSMT(key string) *smt.Store { } return sub.stateCommitmentStore } + +func (s *Store) GetAllStoreKeys() map[types.StoreKey]bool { + storeKeys := make(map[types.StoreKey]bool) + for key, _ := range s.schema { + storeKeys[key] = true + } + return storeKeys +} From 966aa89fe502374ce069ce304685d88cc9d83593 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 21:17:12 -0400 Subject: [PATCH 23/62] Add substore level tracing --- baseapp/options.go | 4 ++-- store/v2alpha1/multi/params.go | 8 +++++++ store/v2alpha1/multi/store.go | 42 +++++++++++++++++++--------------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/baseapp/options.go b/baseapp/options.go index deacfbe65b6..6a8da5b07ba 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -21,9 +21,9 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { return func(config *multi.StoreParams, _ uint64) error { config.Pruning = opts; return nil } } -func SetTracingEnabled(w io.Writer) StoreOption { +func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { return func(cfg *multi.StoreParams, v uint64) error { - cfg.TraceWriter = w + cfg.SetTracerFor(skey, w) return nil } } diff --git a/store/v2alpha1/multi/params.go b/store/v2alpha1/multi/params.go index 9d5fe006323..7da675ef44e 100644 --- a/store/v2alpha1/multi/params.go +++ b/store/v2alpha1/multi/params.go @@ -2,6 +2,7 @@ package multi import ( "fmt" + "io" pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types" types "github.com/cosmos/cosmos-sdk/store/v2alpha1" @@ -41,6 +42,13 @@ func (par *StoreParams) RegisterSubstore(skey types.StoreKey, typ types.StoreTyp return nil } +func (par *StoreParams) SetTracerFor(skey types.StoreKey, w io.Writer) { + tlm := newTraceListenMixin() + tlm.SetTracer(w) + tlm.SetTracingContext(par.TraceContext) + par.substoreTraceListenMixins[skey] = tlm +} + func (par *StoreParams) storeKey(key string) (types.StoreKey, error) { skey, ok := par.storeKeys[key] if !ok { diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 9568416c641..0cdbd905670 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -74,6 +74,7 @@ type StoreParams struct { Upgrades *types.StoreUpgrades // Contains The trace context and listeners that can also be set from store methods. *traceListenMixin + substoreTraceListenMixins map[types.StoreKey]*traceListenMixin } // StoreSchema defineds a mapping of substore keys to store types @@ -108,8 +109,9 @@ type Store struct { pruningManager *pruning.Manager - PersistentCache types.MultiStorePersistentCache - substoreCache map[string]*substore + PersistentCache types.MultiStorePersistentCache + substoreCache map[string]*substore + substoreTraceListenMixins map[types.StoreKey]*traceListenMixin } type substore struct { @@ -117,6 +119,7 @@ type substore struct { name string dataBucket dbm.ReadWriter stateCommitmentStore *smt.Store + *traceListenMixin } // Builder type used to create a valid schema with no prefix conflicts @@ -264,17 +267,18 @@ func NewStore(db dbm.Connection, opts StoreParams) (ret *Store, err error) { } ret = &Store{ - stateDB: db, - stateTxn: stateTxn, - StateCommitmentDB: opts.StateCommitmentDB, - stateCommitmentTxn: stateCommitmentTxn, - mem: mem.NewStore(), - tran: transient.NewStore(), - substoreCache: map[string]*substore{}, - traceListenMixin: opts.traceListenMixin, - PersistentCache: opts.PersistentCache, - pruningManager: pruningManager, - InitialVersion: opts.InitialVersion, + stateDB: db, + stateTxn: stateTxn, + StateCommitmentDB: opts.StateCommitmentDB, + stateCommitmentTxn: stateCommitmentTxn, + mem: mem.NewStore(), + tran: transient.NewStore(), + substoreCache: map[string]*substore{}, + traceListenMixin: opts.traceListenMixin, + PersistentCache: opts.PersistentCache, + pruningManager: pruningManager, + InitialVersion: opts.InitialVersion, + substoreTraceListenMixins: map[types.StoreKey]*traceListenMixin{}, } // Now load the substore schema @@ -1023,12 +1027,14 @@ func (tlm *traceListenMixin) getTracingContext() types.TraceContext { return ctx } -func (tlm *traceListenMixin) wrapTraceListen(store types.KVStore, skey types.StoreKey) types.KVStore { - if tlm.TracingEnabled() { - store = tracekv.NewStore(store, tlm.TraceWriter, tlm.getTracingContext()) +func (s *Store) wrapTraceListen(store types.KVStore, skey types.StoreKey) types.KVStore { + if s.TracingEnabled() { + subStoreTlm := s.substoreTraceListenMixins[skey] + store = tracekv.NewStore(store, subStoreTlm.TraceWriter, s.getTracingContext()) } - if tlm.ListeningEnabled(skey) { - store = listenkv.NewStore(store, skey, tlm.listeners[skey]) + if s.ListeningEnabled(skey) { + subStoreTlm := s.substoreTraceListenMixins[skey] + store = listenkv.NewStore(store, skey, subStoreTlm.listeners[skey]) } return store } From 53c071ba6040e55b57df3a30c3d920814214a10e Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 21:26:55 -0400 Subject: [PATCH 24/62] Update cacheStore for sake of completeness --- store/v2alpha1/multi/cache_store.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/store/v2alpha1/multi/cache_store.go b/store/v2alpha1/multi/cache_store.go index 390deca2836..3827ec93979 100644 --- a/store/v2alpha1/multi/cache_store.go +++ b/store/v2alpha1/multi/cache_store.go @@ -2,6 +2,8 @@ package multi import ( "github.com/cosmos/cosmos-sdk/store/cachekv" + "github.com/cosmos/cosmos-sdk/store/listenkv" + "github.com/cosmos/cosmos-sdk/store/tracekv" types "github.com/cosmos/cosmos-sdk/store/v2alpha1" ) @@ -61,3 +63,13 @@ func (noopCacheStore) Write() {} func CommitAsCacheStore(s types.CommitMultiStore) types.CacheMultiStore { return noopCacheStore{newCacheStore(s)} } + +func (cs *cacheStore) wrapTraceListen(store types.KVStore, skey types.StoreKey) types.KVStore { + if cs.TracingEnabled() { + store = tracekv.NewStore(store, cs.TraceWriter, cs.getTracingContext()) + } + if cs.ListeningEnabled(skey) { + store = listenkv.NewStore(store, skey, cs.listeners[skey]) + } + return store +} From 3a2bf0e888eb2ecb05ba212d200a9770f7d9811d Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 21:54:42 -0400 Subject: [PATCH 25/62] Add back store level tracer --- baseapp/baseapp_test.go | 25 ++++++++++--------------- baseapp/options.go | 7 +++++++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 1364b02bbb5..07726253222 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2188,7 +2188,8 @@ func TestGenerateFraudProof(t *testing.T) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - traceBuf := &bytes.Buffer{} + storeTraceBuf := &bytes.Buffer{} + subStoreTraceBuf := &bytes.Buffer{} routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { @@ -2200,7 +2201,8 @@ func TestGenerateFraudProof(t *testing.T) { app := setupBaseApp(t, AppOptionFunc(routerOpt), - SetTracingEnabled(traceBuf), + SetSubstoreTracer(storeTraceBuf), + SetTracerFor(capKey2, subStoreTraceBuf), ) app.InitChain(abci.RequestInitChain{}) @@ -2232,22 +2234,15 @@ func TestGenerateFraudProof(t *testing.T) { } app.EndBlock(abci.RequestEndBlock{Height: height}) - // Here, the store's substores' traceKVs should have been populated with all the operations that have taken place - // Try to read through the operations and figure out the minimal set of deepsubtrees that can be put inside a fraudproof data structure - - // This is able to retrieve all the keys touched for a particular substore (capKey2 here) which makes up for one deepstree - - // Seems like these substores are not SMT substores which is what's needed for deep subtrees, which blocks this currently. - // Current plan is to try to replace baseapp to use SMT store - cms := app.cms.(*multi.Store) - storeKeys := cms.GetAllStoreKeys() + // Go over all storeKeys inside app.cms and generate deepsubtrees for all of them + storeKeys := cms.GetAllStoreKeys() for storeKey := range storeKeys { - // Generates a deepsubtree for one substore + // Generates a deepsubtree for substore with given key kvStore := cms.GetKVStore(storeKey) traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*traceBuf) + keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) @@ -2258,9 +2253,9 @@ func TestGenerateFraudProof(t *testing.T) { require.Nil(t, err) deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) } - } + _ = deepsubtree - // Next step: Go over all the storeKeys pertaining to each subStore now + } commitResponse := app.Commit() _ = commitResponse.GetData() diff --git a/baseapp/options.go b/baseapp/options.go index 6a8da5b07ba..2558955951c 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -21,6 +21,13 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { return func(config *multi.StoreParams, _ uint64) error { config.Pruning = opts; return nil } } +func SetSubstoreTracer(w io.Writer) StoreOption { + return func(cfg *multi.StoreParams, v uint64) error { + cfg.SetTracer(w) + return nil + } +} + func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { return func(cfg *multi.StoreParams, v uint64) error { cfg.SetTracerFor(skey, w) From 8bfd1b122807b13943b635cbb1b2409b87578e75 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 21:55:27 -0400 Subject: [PATCH 26/62] Fix substore trace errors --- store/v2alpha1/multi/params.go | 9 +++++---- store/v2alpha1/multi/store.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/store/v2alpha1/multi/params.go b/store/v2alpha1/multi/params.go index 7da675ef44e..507984641b0 100644 --- a/store/v2alpha1/multi/params.go +++ b/store/v2alpha1/multi/params.go @@ -12,10 +12,11 @@ import ( // pruning with PruneDefault, no listeners and no tracer. func DefaultStoreParams() StoreParams { return StoreParams{ - Pruning: pruningtypes.NewPruningOptions(pruningtypes.PruningDefault), - SchemaBuilder: newSchemaBuilder(), - storeKeys: storeKeys{}, - traceListenMixin: newTraceListenMixin(), + Pruning: pruningtypes.NewPruningOptions(pruningtypes.PruningDefault), + SchemaBuilder: newSchemaBuilder(), + storeKeys: storeKeys{}, + traceListenMixin: newTraceListenMixin(), + substoreTraceListenMixins: make(map[types.StoreKey]*traceListenMixin), } } diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 0cdbd905670..ddb93a71428 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -278,7 +278,7 @@ func NewStore(db dbm.Connection, opts StoreParams) (ret *Store, err error) { PersistentCache: opts.PersistentCache, pruningManager: pruningManager, InitialVersion: opts.InitialVersion, - substoreTraceListenMixins: map[types.StoreKey]*traceListenMixin{}, + substoreTraceListenMixins: opts.substoreTraceListenMixins, } // Now load the substore schema From 4f7941ad5108261c2e4b8a7749693288e22f625a Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 22:23:35 -0400 Subject: [PATCH 27/62] Add fraudproof datastructure --- baseapp/fraudproof.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go index 32d78874a9c..079d7e8a8cd 100644 --- a/baseapp/fraudproof.go +++ b/baseapp/fraudproof.go @@ -1,8 +1,19 @@ package baseapp -// FraudProof is a fraud proof. +import "github.com/lazyledger/smt" + type FraudProof struct { blockHeight uint64 - // TODO: stateWitness StateWitness + stateWitness map[string]StateWitness +} + +type StateWitness struct { + WitnessData []WitnessData +} + +type WitnessData struct { + Key []byte + Value []byte + proof smt.SparseMerkleProof } From 8266b814b83986cfc9ea739a495f48b4df1d1aa4 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 22:49:39 -0400 Subject: [PATCH 28/62] Populate fraudproof data structure --- baseapp/baseapp_test.go | 21 +++++++++++++++------ baseapp/fraudproof.go | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 07726253222..c393d92da36 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2,7 +2,6 @@ package baseapp import ( "bytes" - "crypto/sha512" "encoding/binary" "encoding/json" "fmt" @@ -29,7 +28,6 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "github.com/gogo/protobuf/jsonpb" - "github.com/lazyledger/smt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -2236,6 +2234,8 @@ func TestGenerateFraudProof(t *testing.T) { cms := app.cms.(*multi.Store) + var fraudproof FraudProof + fraudproof.blockHeight = uint64(app.LastBlockHeight()) // Go over all storeKeys inside app.cms and generate deepsubtrees for all of them storeKeys := cms.GetAllStoreKeys() for storeKey := range storeKeys { @@ -2246,15 +2246,24 @@ func TestGenerateFraudProof(t *testing.T) { substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) - deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), substoreSMT.Root()) + root := substoreSMT.Root() + + var stateWitness StateWitness + stateWitness.root = root + + // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) for key := range keys { + var witnessData WitnessData value := substoreSMT.Get([]byte(key)) proof, err := substoreSMT.GetSMTProof([]byte(key)) require.Nil(t, err) - deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + witnessData.Key = []byte(key) + witnessData.Value = []byte(value) + witnessData.proof = *proof + stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) } - _ = deepsubtree - + fraudproof.stateWitness[storeKey.Name()] = stateWitness } commitResponse := app.Commit() diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go index 079d7e8a8cd..86e402fefb2 100644 --- a/baseapp/fraudproof.go +++ b/baseapp/fraudproof.go @@ -9,6 +9,7 @@ type FraudProof struct { } type StateWitness struct { + root []byte WitnessData []WitnessData } From ae3f9be386d36956152cc272a264f1c13aee0d74 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 4 Aug 2022 22:52:43 -0400 Subject: [PATCH 29/62] fraudproof generated, now we try to load it --- baseapp/baseapp_test.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index c393d92da36..0663030c227 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2213,7 +2213,6 @@ func TestGenerateFraudProof(t *testing.T) { for height := int64(1); height <= int64(blocks); height++ { app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) - txs := make([]txTest, txsPerBlock) for txNum := 0; txNum < txsPerBlock; txNum++ { tx := txTest{Msgs: []sdk.Msg{}} for msgNum := 0; msgNum < 2; msgNum++ { @@ -2224,19 +2223,20 @@ func TestGenerateFraudProof(t *testing.T) { tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) keyCounter++ } - txs = append(txs, tx) txBytes, err := codec.Marshal(tx) require.NoError(t, err) resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, resp.IsOK(), "%v", resp.String()) } app.EndBlock(abci.RequestEndBlock{Height: height}) + app.Commit() cms := app.cms.(*multi.Store) var fraudproof FraudProof fraudproof.blockHeight = uint64(app.LastBlockHeight()) - // Go over all storeKeys inside app.cms and generate deepsubtrees for all of them + + // Go over all storeKeys inside app.cms and populate values inside fraudproof storeKeys := cms.GetAllStoreKeys() for storeKey := range storeKeys { // Generates a deepsubtree for substore with given key @@ -2266,12 +2266,7 @@ func TestGenerateFraudProof(t *testing.T) { fraudproof.stateWitness[storeKey.Name()] = stateWitness } - commitResponse := app.Commit() - _ = commitResponse.GetData() - - // Check Block here - - _ = txs + // Next steps: Now we take contents of the fraud proof and try to populate a fresh baseapp with it :) } From 6572e6c2c6104fcd2bb5abe6b57690da3b351098 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 02:47:56 -0400 Subject: [PATCH 30/62] Clean up documentation and add setupBaseAppFromFraudProof --- baseapp/baseapp_test.go | 46 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 0663030c227..50b06606510 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -98,6 +98,34 @@ func setupBaseApp(t *testing.T, options ...AppOption) *BaseApp { return app } +// baseapp loaded from a fraudproof +func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ...AppOption) *BaseApp { + storeKeys := make([]string, 0, len(fraudProof.stateWitness)) + for k := range fraudProof.stateWitness { + storeKeys = append(storeKeys, k) + } + options = append(options, SetSubstores(storeKeys)) // TODO: unrwap array to values + options = append(options, SetBlockHeight(fraudProof.blockHeight)) // write this option + for _, storeKey := range storeKeys { + stateWitness := fraudProof.stateWitness[storeKey] + witnessData := stateWitness.WitnessData + for kv, val := range witnessData { + // could verify proof here lol but that's an optimization and I value speed tbh (https://twitter.com/KyleSamani/status/1418661490274439169?s=20&t=apw9LCI4zoPIPg1ZP1vszQ) + options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // write this option + } + } + // make list of options to pass by parsing fraudproof + app := newBaseApp(t.Name(), options...) + require.Equal(t, t.Name(), app.Name()) + + app.SetParamStore(mock.NewParamStore(dbm.NewMemDB())) + + // stores are mounted + err := app.Init() + require.Nil(t, err) + return app +} + // simple one store baseapp with data and snapshots. Each tx is 1 MB in size (uncompressed). func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*BaseApp, error) { codec := codec.NewLegacyAmino() @@ -2233,8 +2261,11 @@ func TestGenerateFraudProof(t *testing.T) { cms := app.cms.(*multi.Store) - var fraudproof FraudProof - fraudproof.blockHeight = uint64(app.LastBlockHeight()) + // Start // + // Exports all data inside current multistore into a fraudProof (S1) // + + var fraudProof FraudProof + fraudProof.blockHeight = uint64(app.LastBlockHeight()) // Go over all storeKeys inside app.cms and populate values inside fraudproof storeKeys := cms.GetAllStoreKeys() @@ -2263,10 +2294,19 @@ func TestGenerateFraudProof(t *testing.T) { witnessData.proof = *proof stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) } - fraudproof.stateWitness[storeKey.Name()] = stateWitness + fraudProof.stateWitness[storeKey.Name()] = stateWitness } + // End of exporting data (S1) + + // TODO: Make some set of transactions here (txs_0) + // Next steps: Now we take contents of the fraud proof and try to populate a fresh baseapp with it :) + newApp := setupBaseAppFromFraudproof(fraudProof) + + // TODO: Make the set of transactions txs_0 here + + // Compare appHash from commit, if same, BOOM } From fd664acdc43e98aa9f5703f1b85f18532622f465 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 03:22:04 -0400 Subject: [PATCH 31/62] Update test function header --- baseapp/baseapp_test.go | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 50b06606510..04d7a5f80b0 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2185,12 +2185,15 @@ func TestBaseApp_Init(t *testing.T) { func TestGenerateFraudProof(t *testing.T) { /* - 1. Create a fresh baseapp with a tracekv store - 2. Create a 'block' and put transactions that set certain key/value pairs in it - 3. We should be able to `generate fraud proof` IF block.hash not the same, trigger fraud: - - go through all transactions, run them, and keep track of which substores are being used - - export the SMTs inside those substores into a fraud proof data structure along with block height + 1. Create a fresh baseapp, B1, with a tracekv store (only happens when generating fraudProof) and state S0 + 2. Make some state transition to state S1 by doing some transactions + 3. Export that state S1 into a fraudProof data structure + 4. Load a fresh baseapp, B2, with the contents of fraud proof data structure from (3) so begin from state S1. Verify first (Optimization) + 5. Now, pick some set of transactions, txs1, to make some more state transitions. + 6. Execute txs1 on both B1 and B2 so they go through the same state transitions + 7. For the test to be successful, the state of both B1 and B2 has to converge at a state S2 + TODO: Tests to write: @@ -2199,16 +2202,9 @@ func TestGenerateFraudProof(t *testing.T) { 3. Corrupted Fraud Proof: bad SMT format, insufficient key-value pairs inside SMT needed to verify fraud 4. Bad block, fraud proof needed, fraud proof works, chain halts - TODO: - Figure out how the tracekv interacts with the SMT multistore - done + Notes: In the current implementation, all substores might not be SMTs, do we assume they are? Yes, we do for simplicity here Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx - - Question: What is a block? Right now abstract, need to make it more concrete - Candidate Answer: For now, Block is just a list of a transactions and a header with an app hash - - Note: In the current implementation, all substores might not be SMTs, do we assume they are? - */ codec := codec.NewLegacyAmino() @@ -2225,6 +2221,8 @@ func TestGenerateFraudProof(t *testing.T) { })) } + // BaseApp, B1 + app := setupBaseApp(t, AppOptionFunc(routerOpt), SetSubstoreTracer(storeTraceBuf), @@ -2233,6 +2231,8 @@ func TestGenerateFraudProof(t *testing.T) { app.InitChain(abci.RequestInitChain{}) + // State here: S0 + blocks := 1 txsPerBlock := 2 @@ -2299,16 +2299,14 @@ func TestGenerateFraudProof(t *testing.T) { // End of exporting data (S1) - // TODO: Make some set of transactions here (txs_0) + // TODO: Make some set of transactions here (txs1) - // Next steps: Now we take contents of the fraud proof and try to populate a fresh baseapp with it :) + // Next steps: Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) newApp := setupBaseAppFromFraudproof(fraudProof) - // TODO: Make the set of transactions txs_0 here + // TODO: Make the set of transactions txs1 here // Compare appHash from commit, if same, BOOM } - - return } From ad56e79819e3d289c1549e6796221afe6d1584ed Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 03:46:39 -0400 Subject: [PATCH 32/62] Remove initial height option --- baseapp/baseapp_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 04d7a5f80b0..1d98b69d8a1 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -104,14 +104,15 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... for k := range fraudProof.stateWitness { storeKeys = append(storeKeys, k) } - options = append(options, SetSubstores(storeKeys)) // TODO: unrwap array to values - options = append(options, SetBlockHeight(fraudProof.blockHeight)) // write this option + options = append(options, SetSubstores(storeKeys)) // TODO: unrwap array to values with some golang magic for _, storeKey := range storeKeys { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData for kv, val := range witnessData { - // could verify proof here lol but that's an optimization and I value speed tbh (https://twitter.com/KyleSamani/status/1418661490274439169?s=20&t=apw9LCI4zoPIPg1ZP1vszQ) - options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // write this option + // Optimization + // TODO: + // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) + options = append(options, SetSubstoreKVPair(storeKey, kv, val)) } } // make list of options to pass by parsing fraudproof @@ -2301,7 +2302,7 @@ func TestGenerateFraudProof(t *testing.T) { // TODO: Make some set of transactions here (txs1) - // Next steps: Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) + // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) newApp := setupBaseAppFromFraudproof(fraudProof) // TODO: Make the set of transactions txs1 here From 260871862af655b8268d54ce8a4836ca552fb423 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 03:58:46 -0400 Subject: [PATCH 33/62] Refactor executeBlockWithArbitraryTxs code --- baseapp/baseapp_test.go | 132 ++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 1d98b69d8a1..cdeda523065 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2183,6 +2183,34 @@ func TestBaseApp_Init(t *testing.T) { } } +func executeBlockWithArbitraryTxs(t *testing.T, numTransactions int, app *BaseApp, initialHeight int64) []txTest { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + r := rand.New(rand.NewSource(3920758213583)) + keyCounter := 0 + txs := make([]txTest, numTransactions) + + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: initialHeight}}) + for txNum := 0; txNum < numTransactions; txNum++ { + tx := txTest{Msgs: []sdk.Msg{}} + for msgNum := 0; msgNum < 2; msgNum++ { + key := []byte(fmt.Sprintf("%v", keyCounter)) + value := make([]byte, 10000) + _, err := r.Read(value) + require.NoError(t, err) + tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) + keyCounter++ + } + txBytes, err := codec.Marshal(tx) + require.NoError(t, err) + resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + require.True(t, resp.IsOK(), "%v", resp.String()) + txs = append(txs, tx) + } + app.EndBlock(abci.RequestEndBlock{Height: initialHeight}) + return txs +} + func TestGenerateFraudProof(t *testing.T) { /* @@ -2208,9 +2236,6 @@ func TestGenerateFraudProof(t *testing.T) { */ - codec := codec.NewLegacyAmino() - registerTestCodec(codec) - storeTraceBuf := &bytes.Buffer{} subStoreTraceBuf := &bytes.Buffer{} @@ -2234,80 +2259,57 @@ func TestGenerateFraudProof(t *testing.T) { // State here: S0 - blocks := 1 - txsPerBlock := 2 - - r := rand.New(rand.NewSource(3920758213583)) - keyCounter := 0 - for height := int64(1); height <= int64(blocks); height++ { - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) - - for txNum := 0; txNum < txsPerBlock; txNum++ { - tx := txTest{Msgs: []sdk.Msg{}} - for msgNum := 0; msgNum < 2; msgNum++ { - key := []byte(fmt.Sprintf("%v", keyCounter)) - value := make([]byte, 10000) - _, err := r.Read(value) - require.NoError(t, err) - tx.Msgs = append(tx.Msgs, msgKeyValue{Key: key, Value: value}) - keyCounter++ - } - txBytes, err := codec.Marshal(tx) - require.NoError(t, err) - resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - require.True(t, resp.IsOK(), "%v", resp.String()) - } - app.EndBlock(abci.RequestEndBlock{Height: height}) - app.Commit() + numTransactions := 2 + executeBlockWithArbitraryTxs(t, numTransactions, app, 0) + app.Commit() - cms := app.cms.(*multi.Store) + cms := app.cms.(*multi.Store) - // Start // - // Exports all data inside current multistore into a fraudProof (S1) // + // Start // + // Exports all data inside current multistore into a fraudProof (S1) // - var fraudProof FraudProof - fraudProof.blockHeight = uint64(app.LastBlockHeight()) + var fraudProof FraudProof + fraudProof.blockHeight = uint64(app.LastBlockHeight()) - // Go over all storeKeys inside app.cms and populate values inside fraudproof - storeKeys := cms.GetAllStoreKeys() - for storeKey := range storeKeys { - // Generates a deepsubtree for substore with given key - kvStore := cms.GetKVStore(storeKey) - traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) + // Go over all storeKeys inside app.cms and populate values inside fraudproof + storeKeys := cms.GetAllStoreKeys() + for storeKey := range storeKeys { + // Generates a deepsubtree for substore with given key + kvStore := cms.GetKVStore(storeKey) + traceKv := kvStore.(*tracekv.Store) + keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) + substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) - root := substoreSMT.Root() + root := substoreSMT.Root() - var stateWitness StateWitness - stateWitness.root = root + var stateWitness StateWitness + stateWitness.root = root - // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) - for key := range keys { - var witnessData WitnessData - value := substoreSMT.Get([]byte(key)) - proof, err := substoreSMT.GetSMTProof([]byte(key)) - require.Nil(t, err) - // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) - witnessData.Key = []byte(key) - witnessData.Value = []byte(value) - witnessData.proof = *proof - stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) - } - fraudProof.stateWitness[storeKey.Name()] = stateWitness + // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) + for key := range keys { + var witnessData WitnessData + value := substoreSMT.Get([]byte(key)) + proof, err := substoreSMT.GetSMTProof([]byte(key)) + require.Nil(t, err) + // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + witnessData.Key = []byte(key) + witnessData.Value = []byte(value) + witnessData.proof = *proof + stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) } + fraudProof.stateWitness[storeKey.Name()] = stateWitness + } - // End of exporting data (S1) - - // TODO: Make some set of transactions here (txs1) + // End of exporting data (S1) - // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) - newApp := setupBaseAppFromFraudproof(fraudProof) + // TODO: Make some set of transactions here (txs1) + txs1 := executeBlockWithArbitraryTxs(t, numTransactions, app, 0) - // TODO: Make the set of transactions txs1 here + // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) + newApp := setupBaseAppFromFraudproof(fraudProof) - // Compare appHash from commit, if same, BOOM + // TODO: Apply the set of transactions txs1 here - } + // Compare appHash from commit, if same, BOOM } From b0690cede05bdfa7eed4440bd1042951fdddd2ac Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 04:01:42 -0400 Subject: [PATCH 34/62] naming apps to B1 and B2 --- baseapp/baseapp_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index cdeda523065..0af96bb54eb 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2249,27 +2249,27 @@ func TestGenerateFraudProof(t *testing.T) { // BaseApp, B1 - app := setupBaseApp(t, + appB1 := setupBaseApp(t, AppOptionFunc(routerOpt), SetSubstoreTracer(storeTraceBuf), SetTracerFor(capKey2, subStoreTraceBuf), ) - app.InitChain(abci.RequestInitChain{}) + appB1.InitChain(abci.RequestInitChain{}) // State here: S0 numTransactions := 2 - executeBlockWithArbitraryTxs(t, numTransactions, app, 0) - app.Commit() + executeBlockWithArbitraryTxs(t, numTransactions, appB1, 0) + appB1.Commit() - cms := app.cms.(*multi.Store) + cms := appB1.cms.(*multi.Store) // Start // // Exports all data inside current multistore into a fraudProof (S1) // var fraudProof FraudProof - fraudProof.blockHeight = uint64(app.LastBlockHeight()) + fraudProof.blockHeight = uint64(appB1.LastBlockHeight()) // Go over all storeKeys inside app.cms and populate values inside fraudproof storeKeys := cms.GetAllStoreKeys() @@ -2279,7 +2279,7 @@ func TestGenerateFraudProof(t *testing.T) { traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - substoreSMT := app.cms.(*multi.Store).GetSubStoreSMT(storeKey.Name()) + substoreSMT := cms.GetSubStoreSMT(storeKey.Name()) root := substoreSMT.Root() @@ -2304,10 +2304,10 @@ func TestGenerateFraudProof(t *testing.T) { // End of exporting data (S1) // TODO: Make some set of transactions here (txs1) - txs1 := executeBlockWithArbitraryTxs(t, numTransactions, app, 0) + txs1 := executeBlockWithArbitraryTxs(t, numTransactions, appB1, 0) // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) - newApp := setupBaseAppFromFraudproof(fraudProof) + appB2 := setupBaseAppFromFraudproof(fraudProof) // TODO: Apply the set of transactions txs1 here From fdc86394636375971f9ea68d09a0ca787da960ba Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 04:08:35 -0400 Subject: [PATCH 35/62] Add helper function for executing block with specific txs and height --- baseapp/baseapp_test.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 0af96bb54eb..554ac70481a 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2183,7 +2183,7 @@ func TestBaseApp_Init(t *testing.T) { } } -func executeBlockWithArbitraryTxs(t *testing.T, numTransactions int, app *BaseApp, initialHeight int64) []txTest { +func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, initialHeight int64) []txTest { codec := codec.NewLegacyAmino() registerTestCodec(codec) r := rand.New(rand.NewSource(3920758213583)) @@ -2211,6 +2211,23 @@ func executeBlockWithArbitraryTxs(t *testing.T, numTransactions int, app *BaseAp return txs } +func executeBlock(t *testing.T, app *BaseApp, txs []txTest, initialHeight int64) { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + numTransactions := len(txs) + + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: initialHeight}}) + for txNum := 0; txNum < numTransactions; txNum++ { + tx := txs[txNum] + + txBytes, err := codec.Marshal(tx) + require.NoError(t, err) + resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + require.True(t, resp.IsOK(), "%v", resp.String()) + } + app.EndBlock(abci.RequestEndBlock{Height: initialHeight}) +} + func TestGenerateFraudProof(t *testing.T) { /* @@ -2260,7 +2277,7 @@ func TestGenerateFraudProof(t *testing.T) { // State here: S0 numTransactions := 2 - executeBlockWithArbitraryTxs(t, numTransactions, appB1, 0) + executeBlockWithArbitraryTxs(t, appB1, numTransactions, 0) appB1.Commit() cms := appB1.cms.(*multi.Store) @@ -2304,12 +2321,15 @@ func TestGenerateFraudProof(t *testing.T) { // End of exporting data (S1) // TODO: Make some set of transactions here (txs1) - txs1 := executeBlockWithArbitraryTxs(t, numTransactions, appB1, 0) + txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, 0) + commitHashB1 := appB1.Commit() // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) - appB2 := setupBaseAppFromFraudproof(fraudProof) + appB2 := setupBaseAppFromFraudProof(t, fraudProof) // TODO: Apply the set of transactions txs1 here + executeBlock(t, appB1, txs1, int64(fraudProof.blockHeight)) + commitHashB2 := appB2.Commit() - // Compare appHash from commit, if same, BOOM + // Compare appHash from B1 and B2, if same, BOOM } From 9dd5663adf859d2abfa2b2c01b1018f980b05c9b Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 04:18:32 -0400 Subject: [PATCH 36/62] Clean up comments and add final comparision for test to pass --- baseapp/baseapp_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 554ac70481a..b44d6da9fcc 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -112,7 +112,7 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... // Optimization // TODO: // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) - options = append(options, SetSubstoreKVPair(storeKey, kv, val)) + options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // TODO: write this option } } // make list of options to pass by parsing fraudproof @@ -2229,8 +2229,8 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, initialHeight int64) } func TestGenerateFraudProof(t *testing.T) { - /* + Happy case: 1. Create a fresh baseapp, B1, with a tracekv store (only happens when generating fraudProof) and state S0 2. Make some state transition to state S1 by doing some transactions 3. Export that state S1 into a fraudProof data structure @@ -2246,7 +2246,7 @@ func TestGenerateFraudProof(t *testing.T) { 1. Block with bad txs: Txs that exceed gas limits, validateBasic fails, unregistered messages (see TestRunInvalidTransaction) 2. Block with invalid appHash at the end 3. Corrupted Fraud Proof: bad SMT format, insufficient key-value pairs inside SMT needed to verify fraud - 4. Bad block, fraud proof needed, fraud proof works, chain halts + 4. Bad block, fraud proof needed, fraud proof works, chain halts (happy case) Notes: In the current implementation, all substores might not be SMTs, do we assume they are? Yes, we do for simplicity here Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx @@ -2320,16 +2320,17 @@ func TestGenerateFraudProof(t *testing.T) { // End of exporting data (S1) - // TODO: Make some set of transactions here (txs1) + // Make some set of transactions here (txs1) txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, 0) commitHashB1 := appB1.Commit() // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) appB2 := setupBaseAppFromFraudProof(t, fraudProof) - // TODO: Apply the set of transactions txs1 here + // Apply the set of transactions txs1 here executeBlock(t, appB1, txs1, int64(fraudProof.blockHeight)) commitHashB2 := appB2.Commit() // Compare appHash from B1 and B2, if same, BOOM + require.Equal(t, string(commitHashB1.Data), string(commitHashB2.Data)) } From 48f5126cc33a6b9db663c1f971b399ebb4c61b12 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 04:24:58 -0400 Subject: [PATCH 37/62] Fix all remaining errors, only setSubstoreKVPair option remaining --- baseapp/baseapp_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index b44d6da9fcc..3a034a1bcc5 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -20,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/tracekv" storetypes "github.com/cosmos/cosmos-sdk/store/types" stypes "github.com/cosmos/cosmos-sdk/store/v2alpha1" + types "github.com/cosmos/cosmos-sdk/store/v2alpha1" "github.com/cosmos/cosmos-sdk/store/v2alpha1/multi" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" @@ -100,12 +101,12 @@ func setupBaseApp(t *testing.T, options ...AppOption) *BaseApp { // baseapp loaded from a fraudproof func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ...AppOption) *BaseApp { - storeKeys := make([]string, 0, len(fraudProof.stateWitness)) - for k := range fraudProof.stateWitness { - storeKeys = append(storeKeys, k) + storeKeys := make([]types.StoreKey, 0, len(fraudProof.stateWitness)) + for storeKeyName := range fraudProof.stateWitness { + storeKeys = append(storeKeys, sdk.NewKVStoreKey(storeKeyName)) } - options = append(options, SetSubstores(storeKeys)) // TODO: unrwap array to values with some golang magic - for _, storeKey := range storeKeys { + options = append(options, SetSubstores(storeKeys...)) // TODO: unrwap array to values with some golang magic + for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData for kv, val := range witnessData { From f20ac9dceb3a0074edb3ee7ed625bc5325a1184b Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 10:10:10 -0400 Subject: [PATCH 38/62] Debug test --- baseapp/baseapp_test.go | 15 ++++++++------- store/v2alpha1/multi/store.go | 8 -------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 3a034a1bcc5..248bb488648 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -105,7 +105,7 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... for storeKeyName := range fraudProof.stateWitness { storeKeys = append(storeKeys, sdk.NewKVStoreKey(storeKeyName)) } - options = append(options, SetSubstores(storeKeys...)) // TODO: unrwap array to values with some golang magic + options = append(options, SetSubstores(storeKeys...)) for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData @@ -113,7 +113,8 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... // Optimization // TODO: // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) - options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // TODO: write this option + // options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // TODO: write this option + _, _ = kv, val } } // make list of options to pass by parsing fraudproof @@ -2278,7 +2279,7 @@ func TestGenerateFraudProof(t *testing.T) { // State here: S0 numTransactions := 2 - executeBlockWithArbitraryTxs(t, appB1, numTransactions, 0) + executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) appB1.Commit() cms := appB1.cms.(*multi.Store) @@ -2288,11 +2289,11 @@ func TestGenerateFraudProof(t *testing.T) { var fraudProof FraudProof fraudProof.blockHeight = uint64(appB1.LastBlockHeight()) + fraudProof.stateWitness = make(map[string]StateWitness) // Go over all storeKeys inside app.cms and populate values inside fraudproof - storeKeys := cms.GetAllStoreKeys() - for storeKey := range storeKeys { - // Generates a deepsubtree for substore with given key + storeKeys := []stypes.StoreKey{capKey2} + for _, storeKey := range storeKeys { kvStore := cms.GetKVStore(storeKey) traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) @@ -2322,7 +2323,7 @@ func TestGenerateFraudProof(t *testing.T) { // End of exporting data (S1) // Make some set of transactions here (txs1) - txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, 0) + txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, appB1.LastBlockHeight()+1) commitHashB1 := appB1.Commit() // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index ddb93a71428..239f14313b1 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -1054,11 +1054,3 @@ func (s *Store) GetSubStoreSMT(key string) *smt.Store { } return sub.stateCommitmentStore } - -func (s *Store) GetAllStoreKeys() map[types.StoreKey]bool { - storeKeys := make(map[types.StoreKey]bool) - for key, _ := range s.schema { - storeKeys[key] = true - } - return storeKeys -} From 079bef5873f79736c99cfe504ea9ea96b066ea45 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 10:55:01 -0400 Subject: [PATCH 39/62] Attempt to fix height issues --- baseapp/baseapp.go | 4 ++++ baseapp/baseapp_test.go | 27 +++++++++++++++++---------- baseapp/fraudproof.go | 2 +- baseapp/options.go | 5 +++++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b9d377b2d4e..b85f8f77629 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -293,6 +293,10 @@ func (app *BaseApp) setHaltHeight(haltHeight uint64) { app.haltHeight = haltHeight } +func (app *BaseApp) setInitialHeight(initialHeight int64) { + app.initialHeight = initialHeight +} + func (app *BaseApp) setHaltTime(haltTime uint64) { app.haltTime = haltTime } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 248bb488648..be8c8dd4db4 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -106,6 +106,7 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... storeKeys = append(storeKeys, sdk.NewKVStoreKey(storeKeyName)) } options = append(options, SetSubstores(storeKeys...)) + options = append(options, SetInitialHeight(fraudProof.blockHeight)) for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData @@ -2185,14 +2186,14 @@ func TestBaseApp_Init(t *testing.T) { } } -func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, initialHeight int64) []txTest { +func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, blockHeight int64) []txTest { codec := codec.NewLegacyAmino() registerTestCodec(codec) r := rand.New(rand.NewSource(3920758213583)) keyCounter := 0 txs := make([]txTest, numTransactions) - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: initialHeight}}) + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) for txNum := 0; txNum < numTransactions; txNum++ { tx := txTest{Msgs: []sdk.Msg{}} for msgNum := 0; msgNum < 2; msgNum++ { @@ -2209,16 +2210,16 @@ func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions in require.True(t, resp.IsOK(), "%v", resp.String()) txs = append(txs, tx) } - app.EndBlock(abci.RequestEndBlock{Height: initialHeight}) + app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) return txs } -func executeBlock(t *testing.T, app *BaseApp, txs []txTest, initialHeight int64) { +func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { codec := codec.NewLegacyAmino() registerTestCodec(codec) numTransactions := len(txs) - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: initialHeight}}) + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) for txNum := 0; txNum < numTransactions; txNum++ { tx := txs[txNum] @@ -2227,7 +2228,7 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, initialHeight int64) resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, resp.IsOK(), "%v", resp.String()) } - app.EndBlock(abci.RequestEndBlock{Height: initialHeight}) + app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) } func TestGenerateFraudProof(t *testing.T) { @@ -2288,7 +2289,6 @@ func TestGenerateFraudProof(t *testing.T) { // Exports all data inside current multistore into a fraudProof (S1) // var fraudProof FraudProof - fraudProof.blockHeight = uint64(appB1.LastBlockHeight()) fraudProof.stateWitness = make(map[string]StateWitness) // Go over all storeKeys inside app.cms and populate values inside fraudproof @@ -2320,17 +2320,24 @@ func TestGenerateFraudProof(t *testing.T) { fraudProof.stateWitness[storeKey.Name()] = stateWitness } + currentBlockHeight := appB1.LastBlockHeight() + fraudProof.blockHeight = currentBlockHeight + 1 + // End of exporting data (S1) // Make some set of transactions here (txs1) - txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, appB1.LastBlockHeight()+1) + txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) commitHashB1 := appB1.Commit() // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) - appB2 := setupBaseAppFromFraudProof(t, fraudProof) + appB2 := setupBaseAppFromFraudProof(t, fraudProof, + AppOptionFunc(routerOpt), + ) + + appB1.InitChain(abci.RequestInitChain{}) // Apply the set of transactions txs1 here - executeBlock(t, appB1, txs1, int64(fraudProof.blockHeight)) + executeBlock(t, appB1, txs1, fraudProof.blockHeight) commitHashB2 := appB2.Commit() // Compare appHash from B1 and B2, if same, BOOM diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go index 86e402fefb2..4ccb6c0c45b 100644 --- a/baseapp/fraudproof.go +++ b/baseapp/fraudproof.go @@ -3,7 +3,7 @@ package baseapp import "github.com/lazyledger/smt" type FraudProof struct { - blockHeight uint64 + blockHeight int64 stateWitness map[string]StateWitness } diff --git a/baseapp/options.go b/baseapp/options.go index 2558955951c..878a6d906d0 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -50,6 +50,11 @@ func SetHaltHeight(blockHeight uint64) AppOptionFunc { return func(bap *BaseApp) { bap.setHaltHeight(blockHeight) } } +// SetInitialHeight returns a BaseApp option function that sets the initial block height. +func SetInitialHeight(blockHeight int64) AppOptionFunc { + return func(bap *BaseApp) { bap.setInitialHeight(blockHeight) } +} + // SetHaltTime returns a BaseApp option function that sets the halt block time. func SetHaltTime(haltTime uint64) AppOptionFunc { return func(bap *BaseApp) { bap.setHaltTime(haltTime) } From ec5598a9c0e0328c32b9a3b1ab174e23ad28c262 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 11:01:13 -0400 Subject: [PATCH 40/62] Add correct baseapp usage --- baseapp/baseapp_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index be8c8dd4db4..2969d2319ac 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2334,10 +2334,8 @@ func TestGenerateFraudProof(t *testing.T) { AppOptionFunc(routerOpt), ) - appB1.InitChain(abci.RequestInitChain{}) - // Apply the set of transactions txs1 here - executeBlock(t, appB1, txs1, fraudProof.blockHeight) + executeBlock(t, appB2, txs1, fraudProof.blockHeight) commitHashB2 := appB2.Commit() // Compare appHash from B1 and B2, if same, BOOM From 55ac0c6fd8e5aa6683f7b1e6940a498581f076e9 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 11:30:00 -0400 Subject: [PATCH 41/62] Finish writing setSubstoreKVPair option --- baseapp/baseapp_test.go | 5 ++--- baseapp/options.go | 6 ++++++ store/v2alpha1/multi/store.go | 8 ++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 2969d2319ac..87a92357c63 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -110,12 +110,11 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData - for kv, val := range witnessData { + for _, witness := range witnessData { // Optimization // TODO: // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) - // options = append(options, SetSubstoreKVPair(storeKey, kv, val)) // TODO: write this option - _, _ = kv, val + options = append(options, SetSubstoreKVPair(storeKey, witness.Key, witness.Value)) } } // make list of options to pass by parsing fraudproof diff --git a/baseapp/options.go b/baseapp/options.go index 878a6d906d0..792b6846537 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -35,6 +35,12 @@ func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { } } +// SetSubstoreKVPair sets a key, value pair for the given substore inside a multistore +// Only works for v2alpha1/multi +func SetSubstoreKVPair(skeyName string, key, val []byte) AppOptionFunc { + return func(bapp *BaseApp) { bapp.cms.(*multi.Store).SetSubstoreKVPair(skeyName, key, val) } +} + // SetMinGasPrices returns an option that sets the minimum gas prices on the app. func SetMinGasPrices(gasPricesStr string) AppOptionFunc { gasPrices, err := sdk.ParseDecCoins(gasPricesStr) diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 239f14313b1..99e7159a310 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -522,6 +522,14 @@ func (s *Store) getSubstore(key string) (*substore, error) { }, nil } +func (s *Store) SetSubstoreKVPair(skeyName string, kv, val []byte) { + sub, err := s.getSubstore(skeyName) + if err != nil { + panic(err) + } + sub.Set(kv, val) +} + // Resets a substore's state after commit (because root stateTxn has been discarded) func (s *substore) refresh(rootHash []byte) { pfx := prefixSubstore(s.name) From 4905e627dcb430216b2740a069e2918dd4336427 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 12:32:21 -0400 Subject: [PATCH 42/62] Change setSubstoreKVPair appOption to ordered app option --- baseapp/options.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/baseapp/options.go b/baseapp/options.go index 792b6846537..cdf78f555b4 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -37,8 +37,11 @@ func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { // SetSubstoreKVPair sets a key, value pair for the given substore inside a multistore // Only works for v2alpha1/multi -func SetSubstoreKVPair(skeyName string, key, val []byte) AppOptionFunc { - return func(bapp *BaseApp) { bapp.cms.(*multi.Store).SetSubstoreKVPair(skeyName, key, val) } +func SetSubstoreKVPair(skeyName string, key, val []byte) AppOptionOrdered { + return AppOptionOrdered{ + func(bapp *BaseApp) { bapp.cms.(*multi.Store).SetSubstoreKVPair(skeyName, key, val) }, + OptionOrderAfterStore, + } } // SetMinGasPrices returns an option that sets the minimum gas prices on the app. From f135c699f757fbcd6dbd8e215b78a2ca91666d13 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 13:34:45 -0400 Subject: [PATCH 43/62] Create separate routerOpts for fraudproof baseapp init --- baseapp/baseapp_test.go | 25 +++++++++++++++++++------ store/v2alpha1/multi/store.go | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 87a92357c63..d806c610180 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -102,10 +102,25 @@ func setupBaseApp(t *testing.T, options ...AppOption) *BaseApp { // baseapp loaded from a fraudproof func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ...AppOption) *BaseApp { storeKeys := make([]types.StoreKey, 0, len(fraudProof.stateWitness)) + routerOpts := make([]func(bapp *BaseApp), 0) for storeKeyName := range fraudProof.stateWitness { - storeKeys = append(storeKeys, sdk.NewKVStoreKey(storeKeyName)) + storeKey := sdk.NewKVStoreKey(storeKeyName) + storeKeys = append(storeKeys, storeKey) + + routerOpt := func(bapp *BaseApp) { + bapp.Router().AddRoute(sdk.NewRoute(routeMsgKeyValue, func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + kv := msg.(*msgKeyValue) + bapp.cms.GetKVStore(storeKey).Set(kv.Key, kv.Value) + return &sdk.Result{}, nil + })) + } + routerOpts = append(routerOpts, routerOpt) } options = append(options, SetSubstores(storeKeys...)) + // RouterOpts should only be called after call to `SetSubstores` + for _, routerOpt := range routerOpts { + options = append(options, AppOptionFunc(routerOpt)) + } options = append(options, SetInitialHeight(fraudProof.blockHeight)) for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] @@ -2190,7 +2205,7 @@ func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions in registerTestCodec(codec) r := rand.New(rand.NewSource(3920758213583)) keyCounter := 0 - txs := make([]txTest, numTransactions) + txs := make([]txTest, 0) app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) for txNum := 0; txNum < numTransactions; txNum++ { @@ -2328,10 +2343,8 @@ func TestGenerateFraudProof(t *testing.T) { txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) commitHashB1 := appB1.Commit() - // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it :) - appB2 := setupBaseAppFromFraudProof(t, fraudProof, - AppOptionFunc(routerOpt), - ) + // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it + appB2 := setupBaseAppFromFraudProof(t, fraudProof) // Apply the set of transactions txs1 here executeBlock(t, appB2, txs1, fraudProof.blockHeight) diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 99e7159a310..070a24dccf6 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -456,6 +456,7 @@ func prefixNonpersistent(key string) []byte { func (s *Store) GetKVStore(skey types.StoreKey) types.KVStore { key := skey.Name() var parent types.KVStore + typ, has := s.schema[skey] if !has { panic(ErrStoreNotFound(key)) From 5f185844c5e7e776a0b50c98991f40a5aea785fa Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 13:54:15 -0400 Subject: [PATCH 44/62] Compare smt roots instead of commit hashes --- baseapp/baseapp_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index d806c610180..7c6b567bb8f 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2341,15 +2341,22 @@ func TestGenerateFraudProof(t *testing.T) { // Make some set of transactions here (txs1) txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - commitHashB1 := appB1.Commit() + appB1.Commit() // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it appB2 := setupBaseAppFromFraudProof(t, fraudProof) // Apply the set of transactions txs1 here executeBlock(t, appB2, txs1, fraudProof.blockHeight) - commitHashB2 := appB2.Commit() + appB2.Commit() + + // Note that the appHash from B1 and B2 will not be the same because the subset of storeKeys in both apps is different + + // Compare SMT store roots inside all the substores of the second app with the first app's SMT store roots + cmsB1 := appB1.cms.(*multi.Store) + cmsB2 := appB2.cms.(*multi.Store) + smtB1 := cmsB1.GetSubStoreSMT(capKey2.Name()) + smtB2 := cmsB2.GetSubStoreSMT(capKey2.Name()) - // Compare appHash from B1 and B2, if same, BOOM - require.Equal(t, string(commitHashB1.Data), string(commitHashB2.Data)) + require.Equal(t, string(smtB1.Root()), string(smtB2.Root())) } From e1e56cd727413a334847e49c476f2f5e07c5381b Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 13:57:55 -0400 Subject: [PATCH 45/62] Add note about using SMT root --- baseapp/baseapp_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 7c6b567bb8f..0924107aa0e 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -129,8 +129,10 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... // Optimization // TODO: // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) + options = append(options, SetSubstoreKVPair(storeKey, witness.Key, witness.Value)) } + // Q. How do we use stateWitness.root to ensure the root of the underlying subtree just initialized is the same? Do we need that? } // make list of options to pass by parsing fraudproof app := newBaseApp(t.Name(), options...) From ed75c1cbb02062ab492b0263bda51a9e2e1af2ab Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 14:23:11 -0400 Subject: [PATCH 46/62] Refactor generate fraudProof code --- baseapp/baseapp_test.go | 70 ++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 0924107aa0e..a74bc908f5b 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2247,7 +2247,41 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) } -func TestGenerateFraudProof(t *testing.T) { +func generateFraudProof(t *testing.T, store *multi.Store, storeKeyToSubstoreTraceBuf map[types.StoreKey]*bytes.Buffer) FraudProof { + var fraudProof FraudProof + fraudProof.stateWitness = make(map[string]StateWitness) + + for storeKey, subStoreTraceBuf := range storeKeyToSubstoreTraceBuf { + kvStore := store.GetKVStore(storeKey) + traceKv := kvStore.(*tracekv.Store) + keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) + + substoreSMT := store.GetSubStoreSMT(storeKey.Name()) + + root := substoreSMT.Root() + + var stateWitness StateWitness + stateWitness.root = root + + // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) + for key := range keys { + var witnessData WitnessData + value := substoreSMT.Get([]byte(key)) + proof, err := substoreSMT.GetSMTProof([]byte(key)) + require.Nil(t, err) + // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + witnessData.Key = []byte(key) + witnessData.Value = []byte(value) + witnessData.proof = *proof + stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) + } + fraudProof.stateWitness[storeKey.Name()] = stateWitness + } + + return fraudProof +} + +func TestFraudProofHappyCase(t *testing.T) { /* Happy case: 1. Create a fresh baseapp, B1, with a tracekv store (only happens when generating fraudProof) and state S0 @@ -2304,37 +2338,9 @@ func TestGenerateFraudProof(t *testing.T) { // Start // // Exports all data inside current multistore into a fraudProof (S1) // - var fraudProof FraudProof - fraudProof.stateWitness = make(map[string]StateWitness) - - // Go over all storeKeys inside app.cms and populate values inside fraudproof - storeKeys := []stypes.StoreKey{capKey2} - for _, storeKey := range storeKeys { - kvStore := cms.GetKVStore(storeKey) - traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - - substoreSMT := cms.GetSubStoreSMT(storeKey.Name()) - - root := substoreSMT.Root() - - var stateWitness StateWitness - stateWitness.root = root - - // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) - for key := range keys { - var witnessData WitnessData - value := substoreSMT.Get([]byte(key)) - proof, err := substoreSMT.GetSMTProof([]byte(key)) - require.Nil(t, err) - // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) - witnessData.Key = []byte(key) - witnessData.Value = []byte(value) - witnessData.proof = *proof - stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) - } - fraudProof.stateWitness[storeKey.Name()] = stateWitness - } + storeKeyToSubstoreTraceBuf := make(map[types.StoreKey]*bytes.Buffer) + storeKeyToSubstoreTraceBuf[capKey2] = subStoreTraceBuf + fraudProof := generateFraudProof(t, cms, storeKeyToSubstoreTraceBuf) currentBlockHeight := appB1.LastBlockHeight() fraudProof.blockHeight = currentBlockHeight + 1 From a0d4b611aff9a38a96a274e0903d2d3b75980fed Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Fri, 5 Aug 2022 14:25:38 -0400 Subject: [PATCH 47/62] Move generate fraudProof to baseapp --- baseapp/baseapp.go | 39 +++++++++++++++++++++++++++++++++++++++ baseapp/baseapp_test.go | 41 ++--------------------------------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b85f8f77629..e8bb91d02ca 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,6 +1,7 @@ package baseapp import ( + "bytes" "fmt" "strings" @@ -13,6 +14,8 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" dbm "github.com/cosmos/cosmos-sdk/db" "github.com/cosmos/cosmos-sdk/snapshots" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" stypes "github.com/cosmos/cosmos-sdk/store/v2alpha1" "github.com/cosmos/cosmos-sdk/store/v2alpha1/multi" sdk "github.com/cosmos/cosmos-sdk/types" @@ -765,3 +768,39 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) { return proto.Marshal(&sdk.TxMsgData{MsgResponses: msgResponses}) } + +func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types.StoreKey]*bytes.Buffer) FraudProof { + var fraudProof FraudProof + fraudProof.stateWitness = make(map[string]StateWitness) + + for storeKey, subStoreTraceBuf := range storeKeyToSubstoreTraceBuf { + kvStore := store.GetKVStore(storeKey) + traceKv := kvStore.(*tracekv.Store) + keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) + + substoreSMT := store.GetSubStoreSMT(storeKey.Name()) + + root := substoreSMT.Root() + + var stateWitness StateWitness + stateWitness.root = root + + // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) + for key := range keys { + var witnessData WitnessData + value := substoreSMT.Get([]byte(key)) + proof, err := substoreSMT.GetSMTProof([]byte(key)) + if err != nil { + panic(err) + } + // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) + witnessData.Key = []byte(key) + witnessData.Value = []byte(value) + witnessData.proof = *proof + stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) + } + fraudProof.stateWitness[storeKey.Name()] = stateWitness + } + + return fraudProof +} diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index a74bc908f5b..dc85f1009cd 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -17,7 +17,6 @@ import ( pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" - "github.com/cosmos/cosmos-sdk/store/tracekv" storetypes "github.com/cosmos/cosmos-sdk/store/types" stypes "github.com/cosmos/cosmos-sdk/store/v2alpha1" types "github.com/cosmos/cosmos-sdk/store/v2alpha1" @@ -2247,40 +2246,6 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) } -func generateFraudProof(t *testing.T, store *multi.Store, storeKeyToSubstoreTraceBuf map[types.StoreKey]*bytes.Buffer) FraudProof { - var fraudProof FraudProof - fraudProof.stateWitness = make(map[string]StateWitness) - - for storeKey, subStoreTraceBuf := range storeKeyToSubstoreTraceBuf { - kvStore := store.GetKVStore(storeKey) - traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - - substoreSMT := store.GetSubStoreSMT(storeKey.Name()) - - root := substoreSMT.Root() - - var stateWitness StateWitness - stateWitness.root = root - - // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) - for key := range keys { - var witnessData WitnessData - value := substoreSMT.Get([]byte(key)) - proof, err := substoreSMT.GetSMTProof([]byte(key)) - require.Nil(t, err) - // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) - witnessData.Key = []byte(key) - witnessData.Value = []byte(value) - witnessData.proof = *proof - stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) - } - fraudProof.stateWitness[storeKey.Name()] = stateWitness - } - - return fraudProof -} - func TestFraudProofHappyCase(t *testing.T) { /* Happy case: @@ -2335,18 +2300,16 @@ func TestFraudProofHappyCase(t *testing.T) { cms := appB1.cms.(*multi.Store) - // Start // // Exports all data inside current multistore into a fraudProof (S1) // storeKeyToSubstoreTraceBuf := make(map[types.StoreKey]*bytes.Buffer) storeKeyToSubstoreTraceBuf[capKey2] = subStoreTraceBuf - fraudProof := generateFraudProof(t, cms, storeKeyToSubstoreTraceBuf) + + fraudProof := generateFraudProof(cms, storeKeyToSubstoreTraceBuf) currentBlockHeight := appB1.LastBlockHeight() fraudProof.blockHeight = currentBlockHeight + 1 - // End of exporting data (S1) - // Make some set of transactions here (txs1) txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) appB1.Commit() From 406ca9d87b0e022f0092d0ead6ab27a115ba53d7 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 00:32:10 -0400 Subject: [PATCH 48/62] Update function header --- baseapp/baseapp_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index dc85f1009cd..f82f2bdb71a 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2246,27 +2246,27 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) } -func TestFraudProofHappyCase(t *testing.T) { +func TestGenerateAndLoadFraudProof(t *testing.T) { /* - Happy case: - 1. Create a fresh baseapp, B1, with a tracekv store (only happens when generating fraudProof) and state S0 + Covers some parts of the cycle of a fraudproof by simulating the following steps: + 1. Initialize a baseapp, B1, with some state, S0 2. Make some state transition to state S1 by doing some transactions - 3. Export that state S1 into a fraudProof data structure - 4. Load a fresh baseapp, B2, with the contents of fraud proof data structure from (3) so begin from state S1. Verify first (Optimization) + 3. Export that state S1 into a fraudProof data structure (minimal snapshot) + 4. Load a fresh baseapp, B2, with the contents of fraud proof data structure from (3) so it can begin from state S1. 5. Now, pick some set of transactions, txs1, to make some more state transitions. 6. Execute txs1 on both B1 and B2 so they go through the same state transitions - 7. For the test to be successful, the state of both B1 and B2 has to converge at a state S2 + 7. If the state of both B1 and B2 has to converge at a state S2, then we the test passes. - TODO: + This test passing means cosmos-sdk can switch between a baseapp and fraudproof. - Tests to write: + Tests to write in future: 1. Block with bad txs: Txs that exceed gas limits, validateBasic fails, unregistered messages (see TestRunInvalidTransaction) 2. Block with invalid appHash at the end 3. Corrupted Fraud Proof: bad SMT format, insufficient key-value pairs inside SMT needed to verify fraud 4. Bad block, fraud proof needed, fraud proof works, chain halts (happy case) - Notes: In the current implementation, all substores might not be SMTs, do we assume they are? Yes, we do for simplicity here + Notes: In the current implementation, all substores might not be SMTs, but we assume they are for the prototype here Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx */ @@ -2294,7 +2294,7 @@ func TestFraudProofHappyCase(t *testing.T) { // State here: S0 - numTransactions := 2 + numTransactions := 50 executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) appB1.Commit() From babc547b5985fb6299c7480b8988941741b80a4f Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 00:34:25 -0400 Subject: [PATCH 49/62] Revert "Remove rocksdb for now because mac library issues locally, can revert back when merging" This reverts commit 011f9d1345f1ded476b72493968a6cd101e2ed68. --- db/internal/backends/imports.go | 1 + db/reexport.go | 1 + db/rocksdb/batch.go | 67 +++++ db/rocksdb/creator.go | 17 ++ db/rocksdb/db.go | 502 ++++++++++++++++++++++++++++++++ db/rocksdb/db_test.go | 75 +++++ db/rocksdb/iterator.go | 147 ++++++++++ db/types/creator.go | 5 + orm/go.mod | 1 + 9 files changed, 816 insertions(+) create mode 100644 db/rocksdb/batch.go create mode 100644 db/rocksdb/creator.go create mode 100644 db/rocksdb/db.go create mode 100644 db/rocksdb/db_test.go create mode 100644 db/rocksdb/iterator.go diff --git a/db/internal/backends/imports.go b/db/internal/backends/imports.go index 2a57d4eca39..2069e37c386 100644 --- a/db/internal/backends/imports.go +++ b/db/internal/backends/imports.go @@ -4,4 +4,5 @@ package backends import ( _ "github.com/cosmos/cosmos-sdk/db/badgerdb" _ "github.com/cosmos/cosmos-sdk/db/memdb" + _ "github.com/cosmos/cosmos-sdk/db/rocksdb" ) diff --git a/db/reexport.go b/db/reexport.go index f85c37507bc..05214a42e23 100644 --- a/db/reexport.go +++ b/db/reexport.go @@ -21,6 +21,7 @@ var ( ErrVersionDoesNotExist = types.ErrVersionDoesNotExist MemDBBackend = types.MemDBBackend + RocksDBBackend = types.RocksDBBackend BadgerDBBackend = types.BadgerDBBackend NewDB = types.NewDB diff --git a/db/rocksdb/batch.go b/db/rocksdb/batch.go new file mode 100644 index 00000000000..6f497ede3e2 --- /dev/null +++ b/db/rocksdb/batch.go @@ -0,0 +1,67 @@ +package rocksdb + +import ( + "sync/atomic" + + dbutil "github.com/cosmos/cosmos-sdk/db/internal" + "github.com/cosmos/cosmos-sdk/db/types" + "github.com/cosmos/gorocksdb" +) + +type rocksDBBatch struct { + batch *gorocksdb.WriteBatch + mgr *dbManager +} + +var _ types.Writer = (*rocksDBBatch)(nil) + +func (mgr *dbManager) newRocksDBBatch() *rocksDBBatch { + return &rocksDBBatch{ + batch: gorocksdb.NewWriteBatch(), + mgr: mgr, + } +} + +// Set implements Writer. +func (b *rocksDBBatch) Set(key, value []byte) error { + if err := dbutil.ValidateKv(key, value); err != nil { + return err + } + if b.batch == nil { + return types.ErrTransactionClosed + } + b.batch.Put(key, value) + return nil +} + +// Delete implements Writer. +func (b *rocksDBBatch) Delete(key []byte) error { + if len(key) == 0 { + return types.ErrKeyEmpty + } + if b.batch == nil { + return types.ErrTransactionClosed + } + b.batch.Delete(key) + return nil +} + +// Write implements Writer. +func (b *rocksDBBatch) Commit() (err error) { + if b.batch == nil { + return types.ErrTransactionClosed + } + defer func() { err = dbutil.CombineErrors(err, b.Discard(), "Discard also failed") }() + err = b.mgr.current.Write(b.mgr.opts.wo, b.batch) + return +} + +// Close implements Writer. +func (b *rocksDBBatch) Discard() error { + if b.batch != nil { + defer atomic.AddInt32(&b.mgr.openWriters, -1) + b.batch.Destroy() + b.batch = nil + } + return nil +} diff --git a/db/rocksdb/creator.go b/db/rocksdb/creator.go new file mode 100644 index 00000000000..afedb4beb8e --- /dev/null +++ b/db/rocksdb/creator.go @@ -0,0 +1,17 @@ +//go:build rocksdb + +package rocksdb + +import ( + "path/filepath" + + "github.com/cosmos/cosmos-sdk/db/types" +) + +func init() { + creator := func(name string, dir string) (types.Connection, error) { + dir = filepath.Join(dir, name) + return NewDB(dir) + } + types.RegisterCreator(types.RocksDBBackend, creator, false) +} diff --git a/db/rocksdb/db.go b/db/rocksdb/db.go new file mode 100644 index 00000000000..d42bf377e7d --- /dev/null +++ b/db/rocksdb/db.go @@ -0,0 +1,502 @@ +package rocksdb + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "sync/atomic" + + dbutil "github.com/cosmos/cosmos-sdk/db/internal" + "github.com/cosmos/cosmos-sdk/db/types" + "github.com/cosmos/gorocksdb" +) + +var ( + currentDBFileName string = "current.db" + checkpointFileFormat string = "%020d.db" +) + +var ( + _ types.Connection = (*RocksDB)(nil) + _ types.Reader = (*dbTxn)(nil) + _ types.Writer = (*dbWriter)(nil) + _ types.ReadWriter = (*dbWriter)(nil) +) + +// RocksDB is a connection to a RocksDB key-value database. +type RocksDB = dbManager + +type dbManager struct { + current *dbConnection + dir string + opts dbOptions + vmgr *types.VersionManager + mtx sync.RWMutex + // Track open Writers + openWriters int32 + cpCache checkpointCache +} + +type dbConnection = gorocksdb.OptimisticTransactionDB + +type checkpointCache struct { + cache map[uint64]*cpCacheEntry + mtx sync.RWMutex +} + +type cpCacheEntry struct { + cxn *dbConnection + openCount uint +} + +type dbTxn struct { + txn *gorocksdb.Transaction + mgr *dbManager + version uint64 +} +type dbWriter struct{ dbTxn } + +type dbOptions struct { + dbo *gorocksdb.Options + txo *gorocksdb.OptimisticTransactionOptions + ro *gorocksdb.ReadOptions + wo *gorocksdb.WriteOptions +} + +// NewDB creates a new RocksDB key-value database with inside the given directory. +// If dir does not exist, it will be created. +func NewDB(dir string) (*dbManager, error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + + // default rocksdb option, good enough for most cases, including heavy workloads. + // 1GB table cache, 512MB write buffer(may use 50% more on heavy workloads). + // compression: snappy as default, need to -lsnappy to enable. + bbto := gorocksdb.NewDefaultBlockBasedTableOptions() + bbto.SetBlockCache(gorocksdb.NewLRUCache(1 << 30)) + bbto.SetFilterPolicy(gorocksdb.NewBloomFilter(10)) + dbo := gorocksdb.NewDefaultOptions() + dbo.SetBlockBasedTableFactory(bbto) + dbo.SetCreateIfMissing(true) + dbo.IncreaseParallelism(runtime.NumCPU()) + // 1.5GB maximum memory use for writebuffer. + dbo.OptimizeLevelStyleCompaction(1<<30 + 512<<20) + + opts := dbOptions{ + dbo: dbo, + txo: gorocksdb.NewDefaultOptimisticTransactionOptions(), + ro: gorocksdb.NewDefaultReadOptions(), + wo: gorocksdb.NewDefaultWriteOptions(), + } + mgr := &dbManager{ + dir: dir, + opts: opts, + cpCache: checkpointCache{cache: map[uint64]*cpCacheEntry{}}, + } + + err := os.MkdirAll(mgr.checkpointsDir(), 0o755) + if err != nil { + return nil, err + } + if mgr.vmgr, err = readVersions(mgr.checkpointsDir()); err != nil { + return nil, err + } + dbPath := filepath.Join(dir, currentDBFileName) + // if the current db file is missing but there are checkpoints, restore it + if mgr.vmgr.Count() > 0 { + if _, err = os.Stat(dbPath); os.IsNotExist(err) { + err = mgr.restoreFromCheckpoint(mgr.vmgr.Last(), dbPath) + if err != nil { + return nil, err + } + } + } + mgr.current, err = gorocksdb.OpenOptimisticTransactionDb(dbo, dbPath) + if err != nil { + return nil, err + } + return mgr, nil +} + +func (mgr *dbManager) checkpointsDir() string { + return filepath.Join(mgr.dir, "checkpoints") +} + +// Reads directory for checkpoints files +func readVersions(dir string) (*types.VersionManager, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + var versions []uint64 + for _, f := range files { + var version uint64 + if _, err := fmt.Sscanf(f.Name(), checkpointFileFormat, &version); err != nil { + return nil, err + } + versions = append(versions, version) + } + return types.NewVersionManager(versions), nil +} + +func (mgr *dbManager) checkpointPath(version uint64) (string, error) { + dbPath := filepath.Join(mgr.checkpointsDir(), fmt.Sprintf(checkpointFileFormat, version)) + if stat, err := os.Stat(dbPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + err = types.ErrVersionDoesNotExist + } + return "", err + } else if !stat.IsDir() { + return "", types.ErrVersionDoesNotExist + } + return dbPath, nil +} + +func (mgr *dbManager) openCheckpoint(version uint64) (*dbConnection, error) { + mgr.cpCache.mtx.Lock() + defer mgr.cpCache.mtx.Unlock() + cp, has := mgr.cpCache.cache[version] + if has { + cp.openCount += 1 + return cp.cxn, nil + } + dbPath, err := mgr.checkpointPath(version) + if err != nil { + return nil, err + } + db, err := gorocksdb.OpenOptimisticTransactionDb(mgr.opts.dbo, dbPath) + if err != nil { + return nil, err + } + mgr.cpCache.cache[version] = &cpCacheEntry{cxn: db, openCount: 1} + return db, nil +} + +func (mgr *dbManager) Reader() types.Reader { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + return &dbTxn{ + // Note: oldTransaction could be passed here as a small optimization to + // avoid allocating a new object. + txn: mgr.current.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), + mgr: mgr, + } +} + +func (mgr *dbManager) ReaderAt(version uint64) (types.Reader, error) { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + d, err := mgr.openCheckpoint(version) + if err != nil { + return nil, err + } + + return &dbTxn{ + txn: d.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), + mgr: mgr, + version: version, + }, nil +} + +func (mgr *dbManager) ReadWriter() types.ReadWriter { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + atomic.AddInt32(&mgr.openWriters, 1) + return &dbWriter{dbTxn{ + txn: mgr.current.TransactionBegin(mgr.opts.wo, mgr.opts.txo, nil), + mgr: mgr, + }} +} + +func (mgr *dbManager) Writer() types.Writer { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + atomic.AddInt32(&mgr.openWriters, 1) + return mgr.newRocksDBBatch() +} + +func (mgr *dbManager) Versions() (types.VersionSet, error) { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + return mgr.vmgr, nil +} + +// SaveNextVersion implements Connection. +func (mgr *dbManager) SaveNextVersion() (uint64, error) { + return mgr.save(0) +} + +// SaveVersion implements Connection. +func (mgr *dbManager) SaveVersion(target uint64) error { + if target == 0 { + return types.ErrInvalidVersion + } + _, err := mgr.save(target) + return err +} + +func (mgr *dbManager) save(target uint64) (uint64, error) { + mgr.mtx.Lock() + defer mgr.mtx.Unlock() + if mgr.openWriters > 0 { + return 0, types.ErrOpenTransactions + } + newVmgr := mgr.vmgr.Copy() + target, err := newVmgr.Save(target) + if err != nil { + return 0, err + } + cp, err := mgr.current.NewCheckpoint() + if err != nil { + return 0, err + } + dir := filepath.Join(mgr.checkpointsDir(), fmt.Sprintf(checkpointFileFormat, target)) + if err := cp.CreateCheckpoint(dir, 0); err != nil { + return 0, err + } + cp.Destroy() + mgr.vmgr = newVmgr + return target, nil +} + +func (mgr *dbManager) DeleteVersion(ver uint64) error { + if mgr.cpCache.has(ver) { + return types.ErrOpenTransactions + } + mgr.mtx.Lock() + defer mgr.mtx.Unlock() + dbPath, err := mgr.checkpointPath(ver) + if err != nil { + return err + } + mgr.vmgr = mgr.vmgr.Copy() + mgr.vmgr.Delete(ver) + return os.RemoveAll(dbPath) +} + +func (mgr *dbManager) Revert() (err error) { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + if mgr.openWriters > 0 { + return types.ErrOpenTransactions + } + return mgr.revert(mgr.vmgr.Last()) +} + +func (mgr *dbManager) RevertTo(target uint64) (err error) { + mgr.mtx.RLock() + defer mgr.mtx.RUnlock() + if mgr.openWriters > 0 { + return types.ErrOpenTransactions + } + if !mgr.vmgr.Exists(target) { + return types.ErrVersionDoesNotExist + } + err = mgr.revert(target) + if err != nil { + return + } + mgr.vmgr.DeleteAbove(target) + return +} + +func (mgr *dbManager) revert(target uint64) (err error) { + // Close current connection and replace it with a checkpoint (created from the last checkpoint) + mgr.current.Close() + dbPath := filepath.Join(mgr.dir, currentDBFileName) + err = os.RemoveAll(dbPath) + if err != nil { + return + } + if target != 0 { // when target is 0, restore no checkpoints + err = mgr.restoreFromCheckpoint(target, dbPath) + if err != nil { + return + } + } + mgr.current, err = gorocksdb.OpenOptimisticTransactionDb(mgr.opts.dbo, dbPath) + return +} + +func (mgr *dbManager) restoreFromCheckpoint(version uint64, path string) error { + cxn, err := mgr.openCheckpoint(version) + if err != nil { + return err + } + defer mgr.cpCache.decrement(version) + cp, err := cxn.NewCheckpoint() + if err != nil { + return err + } + err = cp.CreateCheckpoint(path, 0) + if err != nil { + return err + } + cp.Destroy() + return nil +} + +// Close implements Connection. +func (mgr *dbManager) Close() error { + mgr.current.Close() + mgr.opts.destroy() + return nil +} + +// Stats implements Connection. +func (mgr *dbManager) Stats() map[string]string { + keys := []string{"rocksdb.stats"} + stats := make(map[string]string, len(keys)) + for _, key := range keys { + stats[key] = mgr.current.GetProperty(key) + } + return stats +} + +// Get implements Reader. +func (tx *dbTxn) Get(key []byte) ([]byte, error) { + if tx.txn == nil { + return nil, types.ErrTransactionClosed + } + if len(key) == 0 { + return nil, types.ErrKeyEmpty + } + res, err := tx.txn.Get(tx.mgr.opts.ro, key) + if err != nil { + return nil, err + } + return moveSliceToBytes(res), nil +} + +// Get implements Reader. +func (tx *dbWriter) Get(key []byte) ([]byte, error) { + if tx.txn == nil { + return nil, types.ErrTransactionClosed + } + if len(key) == 0 { + return nil, types.ErrKeyEmpty + } + res, err := tx.txn.GetForUpdate(tx.mgr.opts.ro, key) + if err != nil { + return nil, err + } + return moveSliceToBytes(res), nil +} + +// Has implements Reader. +func (tx *dbTxn) Has(key []byte) (bool, error) { + bytes, err := tx.Get(key) + if err != nil { + return false, err + } + return bytes != nil, nil +} + +// Set implements Writer. +func (tx *dbWriter) Set(key []byte, value []byte) error { + if tx.txn == nil { + return types.ErrTransactionClosed + } + if err := dbutil.ValidateKv(key, value); err != nil { + return err + } + return tx.txn.Put(key, value) +} + +// Delete implements Writer. +func (tx *dbWriter) Delete(key []byte) error { + if tx.txn == nil { + return types.ErrTransactionClosed + } + if len(key) == 0 { + return types.ErrKeyEmpty + } + return tx.txn.Delete(key) +} + +func (tx *dbWriter) Commit() (err error) { + if tx.txn == nil { + return types.ErrTransactionClosed + } + defer func() { err = dbutil.CombineErrors(err, tx.Discard(), "Discard also failed") }() + err = tx.txn.Commit() + return +} + +func (tx *dbTxn) Discard() error { + if tx.txn == nil { + return nil // Discard() is idempotent + } + defer func() { tx.txn.Destroy(); tx.txn = nil }() + if tx.version == 0 { + return nil + } + if !tx.mgr.cpCache.decrement(tx.version) { + return fmt.Errorf("transaction has no corresponding checkpoint cache entry: %v", tx.version) + } + return nil +} + +func (tx *dbWriter) Discard() error { + if tx.txn != nil { + defer atomic.AddInt32(&tx.mgr.openWriters, -1) + } + return tx.dbTxn.Discard() +} + +// Iterator implements Reader. +func (tx *dbTxn) Iterator(start, end []byte) (types.Iterator, error) { + if tx.txn == nil { + return nil, types.ErrTransactionClosed + } + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, types.ErrKeyEmpty + } + itr := tx.txn.NewIterator(tx.mgr.opts.ro) + return newRocksDBIterator(itr, start, end, false), nil +} + +// ReverseIterator implements Reader. +func (tx *dbTxn) ReverseIterator(start, end []byte) (types.Iterator, error) { + if tx.txn == nil { + return nil, types.ErrTransactionClosed + } + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, types.ErrKeyEmpty + } + itr := tx.txn.NewIterator(tx.mgr.opts.ro) + return newRocksDBIterator(itr, start, end, true), nil +} + +func (o dbOptions) destroy() { + o.ro.Destroy() + o.wo.Destroy() + o.txo.Destroy() + o.dbo.Destroy() +} + +func (cpc *checkpointCache) has(ver uint64) bool { + cpc.mtx.RLock() + defer cpc.mtx.RUnlock() + _, has := cpc.cache[ver] + return has +} + +func (cpc *checkpointCache) decrement(ver uint64) bool { + cpc.mtx.Lock() + defer cpc.mtx.Unlock() + cp, has := cpc.cache[ver] + if !has { + return false + } + cp.openCount -= 1 + if cp.openCount == 0 { + cp.cxn.Close() + delete(cpc.cache, ver) + } + return true +} diff --git a/db/rocksdb/db_test.go b/db/rocksdb/db_test.go new file mode 100644 index 00000000000..0d67b55e6af --- /dev/null +++ b/db/rocksdb/db_test.go @@ -0,0 +1,75 @@ +package rocksdb + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/db/dbtest" + "github.com/cosmos/cosmos-sdk/db/types" +) + +func load(t *testing.T, dir string) types.Connection { + d, err := NewDB(dir) + require.NoError(t, err) + return d +} + +func TestGetSetHasDelete(t *testing.T) { + dbtest.DoTestGetSetHasDelete(t, load) +} + +func TestIterators(t *testing.T) { + dbtest.DoTestIterators(t, load) +} + +func TestTransactions(t *testing.T) { + dbtest.DoTestTransactions(t, load, true) +} + +func TestVersioning(t *testing.T) { + dbtest.DoTestVersioning(t, load) +} + +func TestRevert(t *testing.T) { + dbtest.DoTestRevert(t, load, false) + dbtest.DoTestRevert(t, load, true) +} + +func TestReloadDB(t *testing.T) { + dbtest.DoTestReloadDB(t, load) +} + +// Test that the DB can be reloaded after a failed Revert +func TestRevertRecovery(t *testing.T) { + dir := t.TempDir() + d, err := NewDB(dir) + require.NoError(t, err) + txn := d.Writer() + require.NoError(t, txn.Set([]byte{1}, []byte{1})) + require.NoError(t, txn.Commit()) + _, err = d.SaveNextVersion() + require.NoError(t, err) + txn = d.Writer() + require.NoError(t, txn.Set([]byte{2}, []byte{2})) + require.NoError(t, txn.Commit()) + + // move checkpoints dir temporarily to trigger an error + hideDir := filepath.Join(dir, "hide_checkpoints") + require.NoError(t, os.Rename(d.checkpointsDir(), hideDir)) + require.Error(t, d.Revert()) + require.NoError(t, os.Rename(hideDir, d.checkpointsDir())) + + d, err = NewDB(dir) + require.NoError(t, err) + view := d.Reader() + val, err := view.Get([]byte{1}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + val, err = view.Get([]byte{2}) + require.NoError(t, err) + require.Nil(t, val) + view.Discard() +} diff --git a/db/rocksdb/iterator.go b/db/rocksdb/iterator.go new file mode 100644 index 00000000000..231d7dfbedb --- /dev/null +++ b/db/rocksdb/iterator.go @@ -0,0 +1,147 @@ +package rocksdb + +import ( + "bytes" + + "github.com/cosmos/cosmos-sdk/db/types" + "github.com/cosmos/gorocksdb" +) + +type rocksDBIterator struct { + source *gorocksdb.Iterator + start, end []byte + isReverse bool + isInvalid bool + // Whether iterator has been advanced to the first element (is fully initialized) + primed bool +} + +var _ types.Iterator = (*rocksDBIterator)(nil) + +func newRocksDBIterator(source *gorocksdb.Iterator, start, end []byte, isReverse bool) *rocksDBIterator { + if isReverse { + if end == nil { + source.SeekToLast() + } else { + source.Seek(end) + if source.Valid() { + eoakey := moveSliceToBytes(source.Key()) // end or after key + if bytes.Compare(end, eoakey) <= 0 { + source.Prev() + } + } else { + source.SeekToLast() + } + } + } else { + if start == nil { + source.SeekToFirst() + } else { + source.Seek(start) + } + } + return &rocksDBIterator{ + source: source, + start: start, + end: end, + isReverse: isReverse, + isInvalid: false, + primed: false, + } +} + +// Domain implements Iterator. +func (itr *rocksDBIterator) Domain() ([]byte, []byte) { + return itr.start, itr.end +} + +// Valid implements Iterator. +func (itr *rocksDBIterator) Valid() bool { + if !itr.primed { + return false + } + + if itr.isInvalid { + return false + } + + if !itr.source.Valid() { + itr.isInvalid = true + return false + } + + var ( + start = itr.start + end = itr.end + key = moveSliceToBytes(itr.source.Key()) + ) + // If key is end or past it, invalid. + if itr.isReverse { + if start != nil && bytes.Compare(key, start) < 0 { + itr.isInvalid = true + return false + } + } else { + if end != nil && bytes.Compare(key, end) >= 0 { + itr.isInvalid = true + return false + } + } + return true +} + +// Key implements Iterator. +func (itr *rocksDBIterator) Key() []byte { + itr.assertIsValid() + return moveSliceToBytes(itr.source.Key()) +} + +// Value implements Iterator. +func (itr *rocksDBIterator) Value() []byte { + itr.assertIsValid() + return moveSliceToBytes(itr.source.Value()) +} + +// Next implements Iterator. +func (itr *rocksDBIterator) Next() bool { + if !itr.primed { + itr.primed = true + } else { + if itr.isReverse { + itr.source.Prev() + } else { + itr.source.Next() + } + } + return itr.Valid() +} + +// Error implements Iterator. +func (itr *rocksDBIterator) Error() error { + return itr.source.Err() +} + +// Close implements Iterator. +func (itr *rocksDBIterator) Close() error { + itr.source.Close() + return nil +} + +func (itr *rocksDBIterator) assertIsValid() { + if !itr.Valid() { + panic("iterator is invalid") + } +} + +// moveSliceToBytes will free the slice and copy out a go []byte +// This function can be applied on *Slice returned from Key() and Value() +// of an Iterator, because they are marked as freed. +func moveSliceToBytes(s *gorocksdb.Slice) []byte { + defer s.Free() + if !s.Exists() { + return nil + } + v := make([]byte, s.Size()) + copy(v, s.Data()) + return v +} diff --git a/db/types/creator.go b/db/types/creator.go index 73c40d8dc36..7492465beba 100644 --- a/db/types/creator.go +++ b/db/types/creator.go @@ -12,6 +12,11 @@ const ( // MemDBBackend represents in-memory key value store, which is mostly used // for testing. MemDBBackend BackendType = "memdb" + // RocksDBBackend represents rocksdb (uses github.com/cosmos/gorocksdb) + // - EXPERIMENTAL + // - requires gcc + // - use rocksdb build tag (go build -tags rocksdb) + RocksDBBackend BackendType = "rocksdb" // BadgerDBBackend represents BadgerDB // - pure Go // - requires badgerdb build tag diff --git a/orm/go.mod b/orm/go.mod index f4124d3260f..41f54ac2dc5 100644 --- a/orm/go.mod +++ b/orm/go.mod @@ -23,6 +23,7 @@ require ( github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cockroachdb/apd/v3 v3.1.0 // indirect + github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cucumber/common/gherkin/go/v22 v22.0.0 // indirect github.com/cucumber/common/messages/go/v17 v17.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect From 6659093cec457f9cb5cc5557d394412fc63b7e82 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 00:36:12 -0400 Subject: [PATCH 50/62] Remove uncommented lines --- baseapp/baseapp.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index e8bb91d02ca..b398030f44c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -785,7 +785,6 @@ func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types var stateWitness StateWitness stateWitness.root = root - // deepsubtree := smt.NewDeepSparseMerkleSubTree(smt.NewSimpleMap(), smt.NewSimpleMap(), sha512.New512_256(), root) for key := range keys { var witnessData WitnessData value := substoreSMT.Get([]byte(key)) @@ -793,7 +792,6 @@ func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types if err != nil { panic(err) } - // deepsubtree.AddBranch(*proof, []byte(key), []byte(value)) witnessData.Key = []byte(key) witnessData.Value = []byte(value) witnessData.proof = *proof From 7e5ac2a4c5636dd42b2fc50c1c6a793ec37b3ae8 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 02:31:00 -0400 Subject: [PATCH 51/62] Tidy up code --- baseapp/baseapp.go | 15 +++++---------- baseapp/baseapp_test.go | 10 ++++++---- baseapp/fraudproof.go | 7 ++++++- baseapp/options.go | 2 +- go.mod | 5 ++++- go.sum | 2 ++ store/tracekv/store.go | 30 +++++++++++++++++------------- store/tracekv/store_test.go | 11 ++++++----- 8 files changed, 47 insertions(+), 35 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b398030f44c..be996e61e17 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -779,22 +779,17 @@ func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) substoreSMT := store.GetSubStoreSMT(storeKey.Name()) - - root := substoreSMT.Root() - - var stateWitness StateWitness - stateWitness.root = root - + stateWitness := StateWitness{ + WitnessData: make([]WitnessData, 0, keys.Len()), + } for key := range keys { - var witnessData WitnessData value := substoreSMT.Get([]byte(key)) proof, err := substoreSMT.GetSMTProof([]byte(key)) if err != nil { panic(err) } - witnessData.Key = []byte(key) - witnessData.Value = []byte(value) - witnessData.proof = *proof + bKey, bVal := []byte(key), []byte(value) + witnessData := WitnessData{bKey, bVal, *proof} stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) } fraudProof.stateWitness[storeKey.Name()] = stateWitness diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index f82f2bdb71a..b6545be6b1b 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -19,7 +19,6 @@ import ( snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" stypes "github.com/cosmos/cosmos-sdk/store/v2alpha1" - types "github.com/cosmos/cosmos-sdk/store/v2alpha1" "github.com/cosmos/cosmos-sdk/store/v2alpha1/multi" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/mock" @@ -100,8 +99,8 @@ func setupBaseApp(t *testing.T, options ...AppOption) *BaseApp { // baseapp loaded from a fraudproof func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ...AppOption) *BaseApp { - storeKeys := make([]types.StoreKey, 0, len(fraudProof.stateWitness)) - routerOpts := make([]func(bapp *BaseApp), 0) + storeKeys := make([]stypes.StoreKey, 0, len(fraudProof.stateWitness)) + routerOpts := make([]func(bapp *BaseApp), 0, len(fraudProof.stateWitness)) for storeKeyName := range fraudProof.stateWitness { storeKey := sdk.NewKVStoreKey(storeKeyName) storeKeys = append(storeKeys, storeKey) @@ -120,7 +119,10 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... for _, routerOpt := range routerOpts { options = append(options, AppOptionFunc(routerOpt)) } + + // This initial height is used in `BeginBlock` in `validateHeight` options = append(options, SetInitialHeight(fraudProof.blockHeight)) + for storeKey := range fraudProof.stateWitness { stateWitness := fraudProof.stateWitness[storeKey] witnessData := stateWitness.WitnessData @@ -2302,7 +2304,7 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // Exports all data inside current multistore into a fraudProof (S1) // - storeKeyToSubstoreTraceBuf := make(map[types.StoreKey]*bytes.Buffer) + storeKeyToSubstoreTraceBuf := make(map[stypes.StoreKey]*bytes.Buffer) storeKeyToSubstoreTraceBuf[capKey2] = subStoreTraceBuf fraudProof := generateFraudProof(cms, storeKeyToSubstoreTraceBuf) diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go index 4ccb6c0c45b..7084c091117 100644 --- a/baseapp/fraudproof.go +++ b/baseapp/fraudproof.go @@ -2,17 +2,22 @@ package baseapp import "github.com/lazyledger/smt" +// Represents a single-round fraudProof type FraudProof struct { + // The block height to load state of blockHeight int64 + // A map from module name to state witness stateWitness map[string]StateWitness } +// State witness with a list of all witness data type StateWitness struct { - root []byte + // List of witness data WitnessData []WitnessData } +// Witness data containing a key/value pair and a SMT proof for said key/value pair type WitnessData struct { Key []byte Value []byte diff --git a/baseapp/options.go b/baseapp/options.go index cdf78f555b4..8b86d7235c1 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -89,7 +89,7 @@ func SetIndexEvents(ie []string) AppOptionFunc { // SetInterBlockCache provides a BaseApp option function that sets the // inter-block cache. func SetInterBlockCache(cache sdk.MultiStorePersistentCache) AppOptionFunc { - opt := func(cfg *multi.StoreParams, v uint64) error { + opt := func(cfg *multi.StoreParams, _ uint64) error { cfg.PersistentCache = cache return nil } diff --git a/go.mod b/go.mod index 8a6e92d49e4..c31fcbd209b 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,10 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require github.com/cosmos/cosmos-sdk/store/tools/ics23 v0.0.0-00010101000000-000000000000 +require ( + github.com/chrispappas/golang-generics-set v1.0.1 + github.com/cosmos/cosmos-sdk/store/tools/ics23 v0.0.0-00010101000000-000000000000 +) require ( 4d63.com/gochecknoglobals v0.1.0 // indirect diff --git a/go.sum b/go.sum index 36be613d7dc..399b5a31845 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,8 @@ github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4 h1:tFXjAxje9thrTF4 github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4/go.mod h1:W8EnPSQ8Nv4fUjc/v1/8tHFqhuOJXnRub0dTfuAQktU= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chrispappas/golang-generics-set v1.0.1 h1:91l8cInAWTxCPwZ8UNg7qkkPsdFdkYS9hytsd8UJsIU= +github.com/chrispappas/golang-generics-set v1.0.1/go.mod h1:cp8j73+rlDyFF9PrjUkrRvi8L4jSRIsRK6Q1nPPIoqo= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= diff --git a/store/tracekv/store.go b/store/tracekv/store.go index d10213f6704..17d4f6e4eda 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" + "github.com/chrispappas/golang-generics-set/set" "github.com/cosmos/cosmos-sdk/store/types" "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -93,20 +94,23 @@ func (tkv *Store) ReverseIterator(start, end []byte) types.Iterator { // GetAllKeysUsedInTrace reads through all traced operations and returns // a set of all the keys inside the trace operations -func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) map[string]bool { - var traceOp traceOperation - var err error - keys := make(map[string]bool) +func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) set.Set[string] { + + keys := make(set.Set[string], 0) for { - traceOp, err = readOperation(&buf) + traceOp, err := readOperation(&buf) if err != nil { - return keys + errString := err.Error() + if errString == "provided buffer is empty: EOF" { + return keys + } + panic(err) } key, err := base64.StdEncoding.DecodeString(traceOp.Key) if err != nil { panic(errors.Wrap(err, "failed to decode key read from buf")) } - keys[string(key)] = true + keys.Add(string(key)) } } @@ -225,19 +229,19 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value // reaOperation reads a KVStore operation from the underlying buffer as // JSON-encoded data where the key/value pair is base64 encoded. -func readOperation(r *bytes.Buffer) (traceOperation, error) { - var traceOp traceOperation +func readOperation(r *bytes.Buffer) (*traceOperation, error) { raw, err := r.ReadString('\n') if raw == "" { - return traceOp, errors.Wrap(err, "provided buffer is empty") + return nil, errors.Wrap(err, "provided buffer is empty") } if err != nil { - return traceOp, errors.Wrap(err, "failed to read trace operation") + return nil, errors.Wrap(err, "failed to read trace operation") } + traceOp := traceOperation{} err = json.Unmarshal([]byte(raw), &traceOp) if err != nil { - return traceOp, errors.Wrap(err, "failed to deserialize trace operation") + return nil, errors.Wrap(err, "failed to deserialize trace operation") } - return traceOp, nil + return &traceOp, nil } diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index d5f8d093bba..41fe24a6d42 100644 --- a/store/tracekv/store_test.go +++ b/store/tracekv/store_test.go @@ -6,6 +6,7 @@ import ( "io" "testing" + "github.com/chrispappas/golang-generics-set/set" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" @@ -114,11 +115,11 @@ func TestTraceKVStoreSet(t *testing.T) { } func TestGetAllKeysUsedInTrace(t *testing.T) { - expectedKeys := map[string]bool{ - string(kvPairs[0].Key): true, - string(kvPairs[1].Key): true, - string(kvPairs[2].Key): true, - } + expectedKeys := set.FromSlice([]string{ + string(kvPairs[0].Key), + string(kvPairs[1].Key), + string(kvPairs[2].Key), + }) var buf bytes.Buffer store := newEmptyTraceKVStore(&buf) From f59bed5a60c04e8324a78abfa55162399fcc0233 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 02:35:38 -0400 Subject: [PATCH 52/62] fix naming capitalization --- baseapp/baseapp.go | 2 +- baseapp/baseapp_test.go | 4 ++-- store/v2alpha1/multi/store.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index be996e61e17..9097a6741fd 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -778,7 +778,7 @@ func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - substoreSMT := store.GetSubStoreSMT(storeKey.Name()) + substoreSMT := store.GetSubstoreSMT(storeKey.Name()) stateWitness := StateWitness{ WitnessData: make([]WitnessData, 0, keys.Len()), } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index b6545be6b1b..bd29318cd82 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2328,8 +2328,8 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // Compare SMT store roots inside all the substores of the second app with the first app's SMT store roots cmsB1 := appB1.cms.(*multi.Store) cmsB2 := appB2.cms.(*multi.Store) - smtB1 := cmsB1.GetSubStoreSMT(capKey2.Name()) - smtB2 := cmsB2.GetSubStoreSMT(capKey2.Name()) + smtB1 := cmsB1.GetSubstoreSMT(capKey2.Name()) + smtB2 := cmsB2.GetSubstoreSMT(capKey2.Name()) require.Equal(t, string(smtB1.Root()), string(smtB2.Root())) } diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 070a24dccf6..1dc09d1dfe5 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -1056,7 +1056,7 @@ func (s *Store) SetPruning(po pruningtypes.PruningOptions) { s.pruningManager.SetOptions(po) } -func (s *Store) GetSubStoreSMT(key string) *smt.Store { +func (s *Store) GetSubstoreSMT(key string) *smt.Store { sub, err := s.getSubstore(key) if err != nil { panic(err) From 31e5b5e6caf407999943e4d85d7d13a39ba4a322 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Sat, 6 Aug 2022 13:35:15 -0400 Subject: [PATCH 53/62] Remove unused v from options --- baseapp/options.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/baseapp/options.go b/baseapp/options.go index 8b86d7235c1..cf90beeebdc 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -22,14 +22,14 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { } func SetSubstoreTracer(w io.Writer) StoreOption { - return func(cfg *multi.StoreParams, v uint64) error { + return func(cfg *multi.StoreParams, _ uint64) error { cfg.SetTracer(w) return nil } } func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { - return func(cfg *multi.StoreParams, v uint64) error { + return func(cfg *multi.StoreParams, _ uint64) error { cfg.SetTracerFor(skey, w) return nil } @@ -61,7 +61,7 @@ func SetHaltHeight(blockHeight uint64) AppOptionFunc { // SetInitialHeight returns a BaseApp option function that sets the initial block height. func SetInitialHeight(blockHeight int64) AppOptionFunc { - return func(bap *BaseApp) { bap.setInitialHeight(blockHeight) } + return func(bapp *BaseApp) { bapp.setInitialHeight(blockHeight) } } // SetHaltTime returns a BaseApp option function that sets the halt block time. @@ -89,7 +89,7 @@ func SetIndexEvents(ie []string) AppOptionFunc { // SetInterBlockCache provides a BaseApp option function that sets the // inter-block cache. func SetInterBlockCache(cache sdk.MultiStorePersistentCache) AppOptionFunc { - opt := func(cfg *multi.StoreParams, _ uint64) error { + opt := func(cfg *multi.StoreParams, v uint64) error { cfg.PersistentCache = cache return nil } From bc887b789cdf8c87adaab509aeae1f0266ee0b82 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Mon, 8 Aug 2022 18:36:57 -0400 Subject: [PATCH 54/62] Export non-exported errors --- store/v2alpha1/smt/store.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/store/v2alpha1/smt/store.go b/store/v2alpha1/smt/store.go index 9bcb60433b5..d298ab89dae 100644 --- a/store/v2alpha1/smt/store.go +++ b/store/v2alpha1/smt/store.go @@ -23,8 +23,8 @@ var ( valuesPrefix = []byte{1} preimagesPrefix = []byte{2} - errKeyEmpty = errors.New("key is empty or nil") - errValueNil = errors.New("value is nil") + ErrKeyEmpty = errors.New("key is empty or nil") + ErrValueNil = errors.New("value is nil") ) // Store Implements types.BasicKVStore. @@ -63,7 +63,7 @@ func LoadStore(db dbm.ReadWriter, root []byte) *Store { func (s *Store) GetProof(key []byte) (*tmcrypto.ProofOps, error) { if len(key) == 0 { - return nil, errKeyEmpty + return nil, ErrKeyEmpty } proof, err := s.tree.Prove(key) if err != nil { @@ -79,7 +79,7 @@ func (s *Store) GetProofICS23(key []byte) (*ics23.CommitmentProof, error) { func (s *Store) GetSMTProof(key []byte) (*smt.SparseMerkleProof, error) { if len(key) == 0 { - return nil, errKeyEmpty + return nil, ErrKeyEmpty } proof, err := s.tree.Prove(key) if err != nil { @@ -95,7 +95,7 @@ func (s *Store) Root() []byte { return s.tree.Root() } // Get returns nil iff key doesn't exist. Panics on nil or empty key. func (s *Store) Get(key []byte) []byte { if len(key) == 0 { - panic(errKeyEmpty) + panic(ErrKeyEmpty) } val, err := s.tree.Get(key) if err != nil { @@ -107,7 +107,7 @@ func (s *Store) Get(key []byte) []byte { // Has checks if a key exists. Panics on nil or empty key. func (s *Store) Has(key []byte) bool { if len(key) == 0 { - panic(errKeyEmpty) + panic(ErrKeyEmpty) } has, err := s.tree.Has(key) if err != nil { @@ -119,10 +119,10 @@ func (s *Store) Has(key []byte) bool { // Set sets the key. Panics on nil key or value. func (s *Store) Set(key []byte, value []byte) { if len(key) == 0 { - panic(errKeyEmpty) + panic(ErrKeyEmpty) } if value == nil { - panic(errValueNil) + panic(ErrValueNil) } _, err := s.tree.Update(key, value) if err != nil { @@ -135,7 +135,7 @@ func (s *Store) Set(key []byte, value []byte) { // Delete deletes the key. Panics on nil key. func (s *Store) Delete(key []byte) { if len(key) == 0 { - panic(errKeyEmpty) + panic(ErrKeyEmpty) } _, _ = s.tree.Delete(key) path := sha256.Sum256(key) From 5626ebd5fa21b7ff0d22c9669ecdc6445f30ae31 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Mon, 8 Aug 2022 19:00:39 -0400 Subject: [PATCH 55/62] Remove commit calls in prototype and update test function doc --- baseapp/baseapp_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index bd29318cd82..02e0ede00d7 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2250,7 +2250,7 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { func TestGenerateAndLoadFraudProof(t *testing.T) { /* - Covers some parts of the cycle of a fraudproof by simulating the following steps: + Tests switch between a baseapp and fraudproof and covers parts of the fraudproof cycle. Steps: 1. Initialize a baseapp, B1, with some state, S0 2. Make some state transition to state S1 by doing some transactions 3. Export that state S1 into a fraudProof data structure (minimal snapshot) @@ -2259,8 +2259,6 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { 6. Execute txs1 on both B1 and B2 so they go through the same state transitions 7. If the state of both B1 and B2 has to converge at a state S2, then we the test passes. - This test passing means cosmos-sdk can switch between a baseapp and fraudproof. - Tests to write in future: 1. Block with bad txs: Txs that exceed gas limits, validateBasic fails, unregistered messages (see TestRunInvalidTransaction) @@ -2298,7 +2296,6 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { numTransactions := 50 executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) - appB1.Commit() cms := appB1.cms.(*multi.Store) @@ -2314,14 +2311,16 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // Make some set of transactions here (txs1) txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - appB1.Commit() + + // Light Client + + // TODO: Insert fraudproof verification here // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it appB2 := setupBaseAppFromFraudProof(t, fraudProof) // Apply the set of transactions txs1 here executeBlock(t, appB2, txs1, fraudProof.blockHeight) - appB2.Commit() // Note that the appHash from B1 and B2 will not be the same because the subset of storeKeys in both apps is different From d1e4b8b9ae8828f902ae816bf8525b2cc5672aa1 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Mon, 8 Aug 2022 20:48:04 -0400 Subject: [PATCH 56/62] Refactor code to clarify order --- baseapp/baseapp.go | 7 ++++--- baseapp/baseapp_test.go | 46 +++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 9097a6741fd..b373a29ff40 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -769,16 +769,17 @@ func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) { return proto.Marshal(&sdk.TxMsgData{MsgResponses: msgResponses}) } -func generateFraudProof(store *multi.Store, storeKeyToSubstoreTraceBuf map[types.StoreKey]*bytes.Buffer) FraudProof { +func (app *BaseApp) generateFraudProof(storeKeyToSubstoreTraceBuf map[types.StoreKey]*bytes.Buffer) FraudProof { var fraudProof FraudProof fraudProof.stateWitness = make(map[string]StateWitness) + cms := app.cms.(*multi.Store) for storeKey, subStoreTraceBuf := range storeKeyToSubstoreTraceBuf { - kvStore := store.GetKVStore(storeKey) + kvStore := cms.GetKVStore(storeKey) traceKv := kvStore.(*tracekv.Store) keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) - substoreSMT := store.GetSubstoreSMT(storeKey.Name()) + substoreSMT := cms.GetSubstoreSMT(storeKey.Name()) stateWitness := StateWitness{ WitnessData: make([]WitnessData, 0, keys.Len()), } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 02e0ede00d7..9f50c1c856e 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2248,6 +2248,15 @@ func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { app.EndBlock(abci.RequestEndBlock{Height: blockHeight}) } +func checkSubstoreSMTsEqual(appB1 *BaseApp, appB2 *BaseApp, storeKeyName string) bool { + cmsB1 := appB1.cms.(*multi.Store) + cmsB2 := appB2.cms.(*multi.Store) + smtB1 := cmsB1.GetSubstoreSMT(storeKeyName) + smtB2 := cmsB2.GetSubstoreSMT(storeKeyName) + + return string(smtB1.Root()) == string(smtB2.Root()) +} + func TestGenerateAndLoadFraudProof(t *testing.T) { /* Tests switch between a baseapp and fraudproof and covers parts of the fraudproof cycle. Steps: @@ -2259,6 +2268,9 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { 6. Execute txs1 on both B1 and B2 so they go through the same state transitions 7. If the state of both B1 and B2 has to converge at a state S2, then we the test passes. + Note that the appHash from B1 and B2 will not be the same because the subset of storeKeys in both apps is different + so we compare subStores instead + Tests to write in future: 1. Block with bad txs: Txs that exceed gas limits, validateBasic fails, unregistered messages (see TestRunInvalidTransaction) @@ -2290,45 +2302,39 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { SetTracerFor(capKey2, subStoreTraceBuf), ) + // B1 <- S0 appB1.InitChain(abci.RequestInitChain{}) - // State here: S0 - - numTransactions := 50 + numTransactions := 1 + // B1 <- S1 executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) - cms := appB1.cms.(*multi.Store) - // Exports all data inside current multistore into a fraudProof (S1) // storeKeyToSubstoreTraceBuf := make(map[stypes.StoreKey]*bytes.Buffer) storeKeyToSubstoreTraceBuf[capKey2] = subStoreTraceBuf - fraudProof := generateFraudProof(cms, storeKeyToSubstoreTraceBuf) - + // Records S1 in fraudproof + fraudProof := appB1.generateFraudProof(storeKeyToSubstoreTraceBuf) currentBlockHeight := appB1.LastBlockHeight() fraudProof.blockHeight = currentBlockHeight + 1 - // Make some set of transactions here (txs1) - txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - // Light Client // TODO: Insert fraudproof verification here - // Now we take contents of the fraud proof and try to populate a fresh baseapp B2 with it + // Now we take contents of the fraud proof which was recorded with S1 and try to populate a fresh baseapp B2 with it + // B2 <- S1 appB2 := setupBaseAppFromFraudProof(t, fraudProof) + // require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) + + // B1 <- S2 + txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) + require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) // Apply the set of transactions txs1 here + // B2 <- S2 executeBlock(t, appB2, txs1, fraudProof.blockHeight) - // Note that the appHash from B1 and B2 will not be the same because the subset of storeKeys in both apps is different - - // Compare SMT store roots inside all the substores of the second app with the first app's SMT store roots - cmsB1 := appB1.cms.(*multi.Store) - cmsB2 := appB2.cms.(*multi.Store) - smtB1 := cmsB1.GetSubstoreSMT(capKey2.Name()) - smtB2 := cmsB2.GetSubstoreSMT(capKey2.Name()) - - require.Equal(t, string(smtB1.Root()), string(smtB2.Root())) + require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) } From e2199e83ee9865ec99f93939291d3ec247846aa3 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Mon, 8 Aug 2022 21:11:15 -0400 Subject: [PATCH 57/62] Fix loadFromFraudProof issue --- baseapp/baseapp_test.go | 4 ++-- store/v2alpha1/multi/store.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 9f50c1c856e..95e2d367300 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2326,11 +2326,11 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // Now we take contents of the fraud proof which was recorded with S1 and try to populate a fresh baseapp B2 with it // B2 <- S1 appB2 := setupBaseAppFromFraudProof(t, fraudProof) - // require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) + require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) // B1 <- S2 txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) + //require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) // Apply the set of transactions txs1 here // B2 <- S2 diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index 1dc09d1dfe5..d327ae5bdb1 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -525,6 +525,7 @@ func (s *Store) getSubstore(key string) (*substore, error) { func (s *Store) SetSubstoreKVPair(skeyName string, kv, val []byte) { sub, err := s.getSubstore(skeyName) + s.substoreCache[skeyName] = sub if err != nil { panic(err) } From 5dfd83b1d83406a3ccf1a8864c2c59c36dbeafe3 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Tue, 9 Aug 2022 01:04:57 -0400 Subject: [PATCH 58/62] Add more checks comparing states --- baseapp/baseapp_test.go | 20 ++++++++++---------- baseapp/options.go | 4 ++-- store/v2alpha1/multi/store.go | 8 ++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 95e2d367300..dd27b673d10 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -35,8 +35,9 @@ import ( ) var ( - capKey1 = sdk.NewKVStoreKey("key1") - capKey2 = sdk.NewKVStoreKey("key2") + capKey1 = sdk.NewKVStoreKey("key1") + capKey2 = sdk.NewKVStoreKey("key2") + randSource = int64(3920758213583) // testTxPriority is the CheckTx priority that we set in the test // antehandler. @@ -123,17 +124,15 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... // This initial height is used in `BeginBlock` in `validateHeight` options = append(options, SetInitialHeight(fraudProof.blockHeight)) - for storeKey := range fraudProof.stateWitness { - stateWitness := fraudProof.stateWitness[storeKey] + for _, storeKey := range storeKeys { + stateWitness := fraudProof.stateWitness[storeKey.Name()] witnessData := stateWitness.WitnessData for _, witness := range witnessData { - // Optimization // TODO: - // Verify proof inside WitnessData: Not sure since canot do it before setting up without doing the redundant work on creating deepSubTrees here (ideally optimint does it) + // Verify proof inside WitnessData options = append(options, SetSubstoreKVPair(storeKey, witness.Key, witness.Value)) } - // Q. How do we use stateWitness.root to ensure the root of the underlying subtree just initialized is the same? Do we need that? } // make list of options to pass by parsing fraudproof app := newBaseApp(t.Name(), options...) @@ -2206,8 +2205,9 @@ func TestBaseApp_Init(t *testing.T) { func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, blockHeight int64) []txTest { codec := codec.NewLegacyAmino() registerTestCodec(codec) - r := rand.New(rand.NewSource(3920758213583)) - keyCounter := 0 + r := rand.New(rand.NewSource(randSource)) + randSource *= 2 + keyCounter := r.Intn(100) txs := make([]txTest, 0) app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) @@ -2330,7 +2330,7 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // B1 <- S2 txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - //require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) + require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) // Apply the set of transactions txs1 here // B2 <- S2 diff --git a/baseapp/options.go b/baseapp/options.go index cf90beeebdc..cf6694d154e 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -37,9 +37,9 @@ func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { // SetSubstoreKVPair sets a key, value pair for the given substore inside a multistore // Only works for v2alpha1/multi -func SetSubstoreKVPair(skeyName string, key, val []byte) AppOptionOrdered { +func SetSubstoreKVPair(skey storetypes.StoreKey, key, val []byte) AppOptionOrdered { return AppOptionOrdered{ - func(bapp *BaseApp) { bapp.cms.(*multi.Store).SetSubstoreKVPair(skeyName, key, val) }, + func(bapp *BaseApp) { bapp.cms.(*multi.Store).SetSubstoreKVPair(skey, key, val) }, OptionOrderAfterStore, } } diff --git a/store/v2alpha1/multi/store.go b/store/v2alpha1/multi/store.go index d327ae5bdb1..b33e08582aa 100644 --- a/store/v2alpha1/multi/store.go +++ b/store/v2alpha1/multi/store.go @@ -523,12 +523,8 @@ func (s *Store) getSubstore(key string) (*substore, error) { }, nil } -func (s *Store) SetSubstoreKVPair(skeyName string, kv, val []byte) { - sub, err := s.getSubstore(skeyName) - s.substoreCache[skeyName] = sub - if err != nil { - panic(err) - } +func (s *Store) SetSubstoreKVPair(skey types.StoreKey, kv, val []byte) { + sub := s.GetKVStore(skey) sub.Set(kv, val) } From 73bc9af8e1ab5facfd46fd9dac0dbeddb3b86abb Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Tue, 9 Aug 2022 01:42:46 -0400 Subject: [PATCH 59/62] Wrap GetSMTProof with Must --- baseapp/baseapp.go | 6 ++---- store/v2alpha1/smt/store.go | 9 +++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b373a29ff40..42f259e623a 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -785,10 +785,8 @@ func (app *BaseApp) generateFraudProof(storeKeyToSubstoreTraceBuf map[types.Stor } for key := range keys { value := substoreSMT.Get([]byte(key)) - proof, err := substoreSMT.GetSMTProof([]byte(key)) - if err != nil { - panic(err) - } + // Assumption: The keys exist in the SMT because they were traced + proof := substoreSMT.MustGetSMTProof([]byte(key)) bKey, bVal := []byte(key), []byte(value) witnessData := WitnessData{bKey, bVal, *proof} stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) diff --git a/store/v2alpha1/smt/store.go b/store/v2alpha1/smt/store.go index d298ab89dae..fead1c0fcd8 100644 --- a/store/v2alpha1/smt/store.go +++ b/store/v2alpha1/smt/store.go @@ -88,6 +88,15 @@ func (s *Store) GetSMTProof(key []byte) (*smt.SparseMerkleProof, error) { return &proof, nil } +func (s *Store) MustGetSMTProof(key []byte) *smt.SparseMerkleProof { + proof, err := s.GetSMTProof(key) + if err != nil { + panic(err) + } + + return proof +} + func (s *Store) Root() []byte { return s.tree.Root() } // BasicKVStore interface below: From c6683f08cdfa4d5d6a2e33050ee23c0908bc54dc Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Tue, 9 Aug 2022 21:11:45 -0400 Subject: [PATCH 60/62] Address Josh comments --- baseapp/baseapp.go | 4 +--- baseapp/baseapp_test.go | 31 ++++++++++++++----------------- baseapp/options.go | 4 ++++ store/tracekv/store.go | 17 +++++++++++------ 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 42f259e623a..5978edab2ab 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -775,9 +775,7 @@ func (app *BaseApp) generateFraudProof(storeKeyToSubstoreTraceBuf map[types.Stor cms := app.cms.(*multi.Store) for storeKey, subStoreTraceBuf := range storeKeyToSubstoreTraceBuf { - kvStore := cms.GetKVStore(storeKey) - traceKv := kvStore.(*tracekv.Store) - keys := traceKv.GetAllKeysUsedInTrace(*subStoreTraceBuf) + keys := cms.GetKVStore(storeKey).(*tracekv.Store).GetAllKeysUsedInTrace(*subStoreTraceBuf) substoreSMT := cms.GetSubstoreSMT(storeKey.Name()) stateWitness := StateWitness{ diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index dd27b673d10..d82f3059c54 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -37,7 +37,7 @@ import ( var ( capKey1 = sdk.NewKVStoreKey("key1") capKey2 = sdk.NewKVStoreKey("key2") - randSource = int64(3920758213583) + randSource = int64(123456789) // testTxPriority is the CheckTx priority that we set in the test // antehandler. @@ -114,8 +114,18 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... })) } routerOpts = append(routerOpts, routerOpt) + + stateWitness := fraudProof.stateWitness[storeKeyName] + witnessData := stateWitness.WitnessData + for _, witness := range witnessData { + // TODO: + // Verify proof inside WitnessData + + options = append(options, SetSubstoreKVPair(storeKey, witness.Key, witness.Value)) + } } options = append(options, SetSubstores(storeKeys...)) + // RouterOpts should only be called after call to `SetSubstores` for _, routerOpt := range routerOpts { options = append(options, AppOptionFunc(routerOpt)) @@ -124,16 +134,6 @@ func setupBaseAppFromFraudProof(t *testing.T, fraudProof FraudProof, options ... // This initial height is used in `BeginBlock` in `validateHeight` options = append(options, SetInitialHeight(fraudProof.blockHeight)) - for _, storeKey := range storeKeys { - stateWitness := fraudProof.stateWitness[storeKey.Name()] - witnessData := stateWitness.WitnessData - for _, witness := range witnessData { - // TODO: - // Verify proof inside WitnessData - - options = append(options, SetSubstoreKVPair(storeKey, witness.Key, witness.Value)) - } - } // make list of options to pass by parsing fraudproof app := newBaseApp(t.Name(), options...) require.Equal(t, t.Name(), app.Name()) @@ -2206,7 +2206,7 @@ func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions in codec := codec.NewLegacyAmino() registerTestCodec(codec) r := rand.New(rand.NewSource(randSource)) - randSource *= 2 + randSource += 1 keyCounter := r.Intn(100) txs := make([]txTest, 0) @@ -2234,12 +2234,9 @@ func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions in func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { codec := codec.NewLegacyAmino() registerTestCodec(codec) - numTransactions := len(txs) app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) - for txNum := 0; txNum < numTransactions; txNum++ { - tx := txs[txNum] - + for _, tx := range txs { txBytes, err := codec.Marshal(tx) require.NoError(t, err) resp := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) @@ -2305,7 +2302,7 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { // B1 <- S0 appB1.InitChain(abci.RequestInitChain{}) - numTransactions := 1 + numTransactions := 5 // B1 <- S1 executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) diff --git a/baseapp/options.go b/baseapp/options.go index cf6694d154e..ee39238782d 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -21,6 +21,8 @@ func SetPruning(opts pruningtypes.PruningOptions) StoreOption { return func(config *multi.StoreParams, _ uint64) error { config.Pruning = opts; return nil } } +// SetSubstoreTracer provides a BaseApp option function that sets the +// tracer for a multistore. func SetSubstoreTracer(w io.Writer) StoreOption { return func(cfg *multi.StoreParams, _ uint64) error { cfg.SetTracer(w) @@ -28,6 +30,8 @@ func SetSubstoreTracer(w io.Writer) StoreOption { } } +// SetTracerFor provides a BaseApp option function that sets the +// tracer for a substore with given skey inside a multistore. func SetTracerFor(skey storetypes.StoreKey, w io.Writer) StoreOption { return func(cfg *multi.StoreParams, _ uint64) error { cfg.SetTracerFor(skey, w) diff --git a/store/tracekv/store.go b/store/tracekv/store.go index 17d4f6e4eda..210835870d8 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "fmt" "io" "github.com/chrispappas/golang-generics-set/set" @@ -19,6 +20,10 @@ const ( iterValueOp operation = "iterValue" ) +var ( + ErrBufferEmpty = fmt.Errorf("provided buffer is empty") +) + type ( // Store implements the KVStore interface with tracing enabled. // Operations are traced on each core KVStore call and written to the @@ -99,11 +104,11 @@ func (tkv *Store) GetAllKeysUsedInTrace(buf bytes.Buffer) set.Set[string] { keys := make(set.Set[string], 0) for { traceOp, err := readOperation(&buf) + // Reached end of buffer + if err == ErrBufferEmpty { + return keys + } if err != nil { - errString := err.Error() - if errString == "provided buffer is empty: EOF" { - return keys - } panic(err) } key, err := base64.StdEncoding.DecodeString(traceOp.Key) @@ -227,12 +232,12 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value io.WriteString(w, "\n") } -// reaOperation reads a KVStore operation from the underlying buffer as +// readOperation reads a KVStore operation from the underlying buffer as // JSON-encoded data where the key/value pair is base64 encoded. func readOperation(r *bytes.Buffer) (*traceOperation, error) { raw, err := r.ReadString('\n') if raw == "" { - return nil, errors.Wrap(err, "provided buffer is empty") + return nil, ErrBufferEmpty } if err != nil { return nil, errors.Wrap(err, "failed to read trace operation") From 94eeb93ea2a8d5013a28cf02be05ff1b5b861d77 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Wed, 10 Aug 2022 15:01:54 -0400 Subject: [PATCH 61/62] Add some comments --- baseapp/baseapp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5978edab2ab..b850c30439e 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -784,6 +784,7 @@ func (app *BaseApp) generateFraudProof(storeKeyToSubstoreTraceBuf map[types.Stor for key := range keys { value := substoreSMT.Get([]byte(key)) // Assumption: The keys exist in the SMT because they were traced + // TOOD: Investigate default value leaves proof := substoreSMT.MustGetSMTProof([]byte(key)) bKey, bVal := []byte(key), []byte(value) witnessData := WitnessData{bKey, bVal, *proof} From c088b814479015b6ddc325a6dca96bdbcc7dbf61 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 11 Aug 2022 21:54:10 -0400 Subject: [PATCH 62/62] Export fraudproof after S2 now --- baseapp/baseapp_test.go | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index d82f3059c54..c96fbad9324 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -2258,12 +2258,10 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { /* Tests switch between a baseapp and fraudproof and covers parts of the fraudproof cycle. Steps: 1. Initialize a baseapp, B1, with some state, S0 - 2. Make some state transition to state S1 by doing some transactions + 2. Make some state transition to state S1 by doing some transactions and commit 3. Export that state S1 into a fraudProof data structure (minimal snapshot) 4. Load a fresh baseapp, B2, with the contents of fraud proof data structure from (3) so it can begin from state S1. - 5. Now, pick some set of transactions, txs1, to make some more state transitions. - 6. Execute txs1 on both B1 and B2 so they go through the same state transitions - 7. If the state of both B1 and B2 has to converge at a state S2, then we the test passes. + 5. If the state of both B1 and B2 has to converge at a state S2, then we the test passes. Note that the appHash from B1 and B2 will not be the same because the subset of storeKeys in both apps is different so we compare subStores instead @@ -2275,9 +2273,6 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { 3. Corrupted Fraud Proof: bad SMT format, insufficient key-value pairs inside SMT needed to verify fraud 4. Bad block, fraud proof needed, fraud proof works, chain halts (happy case) - Notes: In the current implementation, all substores might not be SMTs, but we assume they are for the prototype here - Try to keep tx as generic as possible so you don't need to care about the messages inside a Tx - */ storeTraceBuf := &bytes.Buffer{} @@ -2305,33 +2300,23 @@ func TestGenerateAndLoadFraudProof(t *testing.T) { numTransactions := 5 // B1 <- S1 executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) + appB1.Commit() - // Exports all data inside current multistore into a fraudProof (S1) // - + // Exports all data inside current multistore into a fraudProof (S0 -> S1) // storeKeyToSubstoreTraceBuf := make(map[stypes.StoreKey]*bytes.Buffer) storeKeyToSubstoreTraceBuf[capKey2] = subStoreTraceBuf // Records S1 in fraudproof fraudProof := appB1.generateFraudProof(storeKeyToSubstoreTraceBuf) - currentBlockHeight := appB1.LastBlockHeight() + currentBlockHeight := appB1.LastBlockHeight() // Only changes on a Commit fraudProof.blockHeight = currentBlockHeight + 1 // Light Client // TODO: Insert fraudproof verification here - // Now we take contents of the fraud proof which was recorded with S1 and try to populate a fresh baseapp B2 with it + // Now we take contents of the fraud proof which was recorded with S2 and try to populate a fresh baseapp B2 with it // B2 <- S1 appB2 := setupBaseAppFromFraudProof(t, fraudProof) require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) - - // B1 <- S2 - txs1 := executeBlockWithArbitraryTxs(t, appB1, numTransactions, fraudProof.blockHeight) - require.False(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) - - // Apply the set of transactions txs1 here - // B2 <- S2 - executeBlock(t, appB2, txs1, fraudProof.blockHeight) - - require.True(t, checkSubstoreSMTsEqual(appB1, appB2, capKey2.Name())) }