diff --git a/baseapp/abci.go b/baseapp/abci.go index 74a5b04f51b6..c13f21dd16e1 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -1,6 +1,7 @@ package baseapp import ( + "bytes" "crypto/sha256" "encoding/json" "errors" @@ -14,11 +15,13 @@ import ( "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + db "github.com/tendermint/tm-db" "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" "github.com/cosmos/cosmos-sdk/codec" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" + "github.com/cosmos/cosmos-sdk/store/rootmulti" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -131,6 +134,164 @@ func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { } } +// GetAppHash implements the ABCI application interface. It returns an App Hash +// whiich acts as a unique ID of the current state of the BaseApp. +func (app *BaseApp) GetAppHash(req abci.RequestGetAppHash) (res abci.ResponseGetAppHash) { + cms := app.cms.(*rootmulti.Store) + + appHash, err := cms.GetAppHash() + if err != nil { + panic(err) + } + res = abci.ResponseGetAppHash{ + AppHash: appHash, + } + return res +} + +func (app *BaseApp) executeNonFraudulentTransactions(req abci.RequestGenerateFraudProof, isDeliverTxFraudulent bool) { + numNonFraudulentRequests := len(req.DeliverTxRequests) + if isDeliverTxFraudulent { + numNonFraudulentRequests-- + } + nonFraudulentRequests := req.DeliverTxRequests[:numNonFraudulentRequests] + for _, deliverTxRequest := range nonFraudulentRequests { + app.DeliverTx(*deliverTxRequest) + } +} + +// GenerateFraudProof implements the ABCI application interface. The BaseApp reverts to +// previous state, runs the given fraudulent state transition, and gets the trace representing +// the operations that this state transition makes. It then uses this trace to filter and export +// the pre-fraudulent state transition execution state of the BaseApp and generates a Fraud Proof +// representing it. It returns this generated Fraud Proof. +func (app *BaseApp) GenerateFraudProof(req abci.RequestGenerateFraudProof) (res abci.ResponseGenerateFraudProof) { + // Revert app to previous state + cms := app.cms.(*rootmulti.Store) + err := cms.LoadLastVersion() + if err != nil { + // Happens when there is no last state to load form + panic(err) + } + + // Run the set of all nonFradulent and fraudulent state transitions + beginBlockRequest := req.BeginBlockRequest + isBeginBlockFraudulent := req.DeliverTxRequests == nil + isDeliverTxFraudulent := req.EndBlockRequest == nil + app.BeginBlock(beginBlockRequest) + if !isBeginBlockFraudulent { + // BeginBlock is not the fraudulent state transition + app.executeNonFraudulentTransactions(req, isDeliverTxFraudulent) + + cms.ResetAllTraceWriters() + + // Record the trace made by the fraudulent state transitions + if isDeliverTxFraudulent { + // The last DeliverTx is the fraudulent state transition + fraudulentDeliverTx := req.DeliverTxRequests[len(req.DeliverTxRequests)-1] + app.DeliverTx(*fraudulentDeliverTx) + } else { + // EndBlock is the fraudulent state transition + app.EndBlock(*req.EndBlockRequest) + } + } + + // SubStore trace buffers now record the trace made by the fradulent state transition + + // Revert app to previous state + err = cms.LoadLastVersion() + if err != nil { + panic(err) + } + + // Fast-forward to right before fradulent state transition occurred + app.BeginBlock(beginBlockRequest) + if !isBeginBlockFraudulent { + app.executeNonFraudulentTransactions(req, isDeliverTxFraudulent) + } + + // Export the app's current trace-filtered state into a Fraud Proof and return it + fraudProof, err := app.getFraudProof() + if err != nil { + panic(err) + } + + switch { + case isBeginBlockFraudulent: + fraudProof.fraudulentBeginBlock = &beginBlockRequest + case isDeliverTxFraudulent: + fraudProof.fraudulentDeliverTx = req.DeliverTxRequests[len(req.DeliverTxRequests)-1] + default: + fraudProof.fraudulentEndBlock = req.EndBlockRequest + } + abciFraudProof := fraudProof.toABCI() + res = abci.ResponseGenerateFraudProof{ + FraudProof: &abciFraudProof, + } + return res +} + +// VerifyFraudProof implements the ABCI application interface. It loads a fresh BaseApp using +// the given Fraud Proof, runs the given fraudulent state transition within the Fraud Proof, +// and gets the app hash representing state of the resulting BaseApp. It returns a boolean +// representing whether this app hash is equivalent to the expected app hash given. +func (app *BaseApp) VerifyFraudProof(req abci.RequestVerifyFraudProof) (res abci.ResponseVerifyFraudProof) { + abciFraudProof := req.FraudProof + fraudProof := FraudProof{} + fraudProof.fromABCI(*abciFraudProof) + + // First two levels of verification + success, err := fraudProof.verifyFraudProof() + if err != nil { + panic(err) + } + + if success { + // Third level of verification + + // Setup a new app from fraud proof + appFromFraudProof, err := SetupBaseAppFromFraudProof( + app.Name()+"FromFraudProof", + app.logger, + db.NewMemDB(), + app.txDecoder, + fraudProof, + ) + if err != nil { + panic(err) + } + appFromFraudProof.InitChain(abci.RequestInitChain{}) + appHash := appFromFraudProof.GetAppHash(abci.RequestGetAppHash{}).AppHash + + if !bytes.Equal(fraudProof.appHash, appHash) { + return abci.ResponseVerifyFraudProof{ + Success: false, + } + } + + // Execute fraudulent state transition + if fraudProof.fraudulentBeginBlock != nil { + appFromFraudProof.BeginBlock(*fraudProof.fraudulentBeginBlock) + } else { + // Need to add some dummy begin block here since its a new app + + appFromFraudProof.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: fraudProof.blockHeight}}) + if fraudProof.fraudulentDeliverTx != nil { + appFromFraudProof.DeliverTx(*fraudProof.fraudulentDeliverTx) + } else { + appFromFraudProof.EndBlock(*fraudProof.fraudulentEndBlock) + } + } + + appHash = appFromFraudProof.GetAppHash(abci.RequestGetAppHash{}).AppHash + success = bytes.Equal(appHash, req.ExpectedAppHash) + } + res = abci.ResponseVerifyFraudProof{ + Success: success, + } + return res +} + // BeginBlock implements the ABCI application interface. func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { if app.cms.TracingEnabled() { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 448ef792fd05..3fd809ac360a 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/cosmos/iavl" "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" @@ -15,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/cosmos/cosmos-sdk/store/tracekv" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -366,6 +368,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 } @@ -838,3 +844,85 @@ 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}) } + +// Generate a fraudproof for an app with the given trace buffers +func (app *BaseApp) getFraudProof() (FraudProof, error) { + fraudProof := FraudProof{} + fraudProof.stateWitness = make(map[string]StateWitness) + fraudProof.blockHeight = app.LastBlockHeight() + cms := app.cms.(*rootmulti.Store) + + appHash, err := cms.GetAppHash() + if err != nil { + return FraudProof{}, err + } + fraudProof.appHash = appHash + storeKeys := cms.GetStoreKeys() + for _, storeKey := range storeKeys { + if subStoreTraceBuf := cms.GetTracerBufferFor(storeKey.Name()); subStoreTraceBuf != nil { + keys := cms.GetKVStore(storeKey).(*tracekv.Store).GetAllKeysUsedInTrace(*subStoreTraceBuf) + iavlStore, err := cms.GetIAVLStore(storeKey.Name()) + if err != nil { + return FraudProof{}, err + } + rootHash, err := iavlStore.Root() + if err != nil { + return FraudProof{}, err + } + if rootHash == nil { + continue + } + proof, err := cms.GetStoreProof(storeKey.Name()) + if err != nil { + return FraudProof{}, err + } + stateWitness := StateWitness{ + Proof: *proof, + RootHash: rootHash, + WitnessData: make([]WitnessData, 0), + } + for key := range keys { + bKey := []byte(key) + has := iavlStore.Has(bKey) + if has { + bVal := iavlStore.Get(bKey) + proof := iavlStore.GetProofFromTree(bKey) + witnessData := WitnessData{bKey, bVal, proof.GetOps()[0]} + stateWitness.WitnessData = append(stateWitness.WitnessData, witnessData) + } + } + fraudProof.stateWitness[storeKey.Name()] = stateWitness + } + } + + return fraudProof, nil +} + +// set up a new baseapp from given params +func SetupBaseAppFromParams(appName string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, storeKeyNames []string, storeKeyToIAVLTree map[string]*iavl.MutableTree, blockHeight int64, options ...func(*BaseApp)) (*BaseApp, error) { + storeKeys := make([]storetypes.StoreKey, 0, len(storeKeyNames)) + for _, storeKeyName := range storeKeyNames { + storeKeys = append(storeKeys, sdk.NewKVStoreKey(storeKeyName)) + iavlTree := storeKeyToIAVLTree[storeKeyName] + options = append(options, SetDeepIAVLTree(storeKeyName, iavlTree)) + } + // This initial height is used in `BeginBlock` in `validateHeight` + options = append(options, SetInitialHeight(blockHeight)) + + app := NewBaseApp(appName, logger, db, txDecoder, options...) + + // stores are mounted + app.MountStores(storeKeys...) + + err := app.LoadLatestVersion() + return app, err +} + +// set up a new baseapp from a fraudproof +func SetupBaseAppFromFraudProof(appName string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, fraudProof FraudProof, options ...func(*BaseApp)) (*BaseApp, error) { + storeKeyToIAVLTree, err := fraudProof.getDeepIAVLTrees() + if err != nil { + return nil, err + } + return SetupBaseAppFromParams(appName, logger, db, txDecoder, fraudProof.getModules(), storeKeyToIAVLTree, fraudProof.blockHeight, options...) +} diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index bf5d6b213e8d..8d3eee8ecc12 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -34,8 +34,9 @@ import ( ) var ( - capKey1 = sdk.NewKVStoreKey("key1") - capKey2 = sdk.NewKVStoreKey("key2") + capKey1 = sdk.NewKVStoreKey("key1") + capKey2 = sdk.NewKVStoreKey("key2") + randSource = int64(123456789) // testTxPriority is the CheckTx priority that we set in the test // antehandler. @@ -2173,3 +2174,210 @@ func TestBaseApp_EndBlock(t *testing.T) { require.Equal(t, int64(100), res.GetValidatorUpdates()[0].Power) require.Equal(t, cp.Block.MaxGas, res.ConsensusParamUpdates.Block.MaxGas) } + +func executeBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, blockHeight int64) []txTest { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + r := rand.New(rand.NewSource(randSource)) + randSource += 1 + keyCounter := r.Intn(10000) + txs := make([]txTest, 0) + + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) + for txNum := 0; txNum < numTransactions; txNum++ { + tx := txTest{Msgs: []sdk.Msg{}} + for msgNum := 0; msgNum < 1; 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: blockHeight}) + return txs +} + +func getBlockWithArbitraryTxs(t *testing.T, app *BaseApp, numTransactions int, blockHeight int64) (*abci.RequestBeginBlock, []txTest, []*abci.RequestDeliverTx, *abci.RequestEndBlock) { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + r := rand.New(rand.NewSource(randSource)) + randSource += 1 + keyCounter := r.Intn(10000) + txs := make([]txTest, 0) + + beginRequest := abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}} + deliverRequests := make([]*abci.RequestDeliverTx, 0) + for txNum := 0; txNum < numTransactions; txNum++ { + tx := txTest{Msgs: []sdk.Msg{}} + for msgNum := 0; msgNum < 1; 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) + deliverRequest := abci.RequestDeliverTx{Tx: txBytes} + deliverRequests = append(deliverRequests, &deliverRequest) + txs = append(txs, tx) + } + endBlockRequest := abci.RequestEndBlock{Height: blockHeight} + return &beginRequest, txs, deliverRequests, &endBlockRequest +} + +func executeBlock(t *testing.T, app *BaseApp, txs []txTest, blockHeight int64) { + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: blockHeight}}) + for _, tx := range txs { + 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: blockHeight}) +} + +func executeBlockWithRequests(t *testing.T, app *BaseApp, beginRequest *abci.RequestBeginBlock, deliverRequests []*abci.RequestDeliverTx, endRequest *abci.RequestEndBlock, blockHeight int64) { + if beginRequest != nil { + if blockHeight != 0 { + beginRequest.Header.Height = blockHeight + } + app.BeginBlock(*beginRequest) + } + for _, deliverRequest := range deliverRequests { + require.NotNil(t, deliverRequest) + resp := app.DeliverTx(*deliverRequest) + require.True(t, resp.IsOK(), "%v", resp.String()) + } + if endRequest != nil { + app.EndBlock(*endRequest) + } +} + +// Takes the key embedded in given message, and returns a +// deliverRequest with a tx that has the same key but a different value +func getFraudTx(t *testing.T, tx txTest) *abci.RequestDeliverTx { + msgs := tx.GetMsgs() + require.NotEmpty(t, msgs) + msgKV := msgs[0].(msgKeyValue) + key := msgKV.Key + codec := codec.NewLegacyAmino() + registerTestCodec(codec) + + fraudTx := txTest{Msgs: []sdk.Msg{}} + r := rand.New(rand.NewSource(randSource)) + randSource += 1 + + newValue := make([]byte, 10000) + _, err := r.Read(newValue) + require.NoError(t, err) + fraudTx.Msgs = append(fraudTx.Msgs, msgKeyValue{Key: key, Value: newValue}) + + fraudTxBytes, err := codec.Marshal(fraudTx) + require.Nil(t, err) + fraudDeliverRequest := abci.RequestDeliverTx{Tx: fraudTxBytes} + + return &fraudDeliverRequest +} + +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, commit those transactions, and save + resulting root hash + 3. Make another set of state transitions to state S2 but do not commit + 4. Generate a fraudproof which should ignore the uncommitted set of transactions, and export S1 into a fraudProof data structure (minimal snapshot) + 5. Verify the fraudproof and check verification passes (done in light client) + 6. Load a fresh baseapp, B2, with the contents of fraud proof data structure from (4) so it can begin from state S1. + 7. Check if the root hashes of the new app with root hash + + 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 underlying store format, insufficient key-value pairs inside the underlying store + needed to verify fraud + 4. Bad block, fraud proof needed, fraud proof works, chain halts (happy case) + */ + + 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) { + kv := msg.(*msgKeyValue) + bapp.cms.GetKVStore(capKey2).Set(kv.Key, kv.Value) + return &sdk.Result{}, nil + })) + } + + // BaseApp, B1 + appB1 := setupBaseApp(t, + routerOpt, + ) + appB1.SetCommitMultiStoreTracer(storeTraceBuf) + appB1.SetCommitKVStoreTracer(capKey2.Name(), subStoreTraceBuf) + + // B1 <- S0 + appB1.InitChain(abci.RequestInitChain{}) + + numTransactions := 2 + // B1 <- S1 + executeBlockWithArbitraryTxs(t, appB1, numTransactions, 1) + appB1.Commit() + + // B1 <- S2 + beginRequest, txs, deliverRequests, _ := getBlockWithArbitraryTxs(t, appB1, numTransactions, 2) + + // Modify deliverRequests to discard last tx in the block + nonFraudulentDeliverRequests := deliverRequests[0 : len(deliverRequests)-1] + txs = txs[0 : len(txs)-1] + require.NotEmpty(t, txs) + fraudDeliverRequest := getFraudTx(t, txs[0]) + + executeBlockWithRequests(t, appB1, beginRequest, nonFraudulentDeliverRequests, nil, 0) + + // Save appHash, substoreHash here for comparision later + appHashB1, err := appB1.cms.(*rootmulti.Store).GetAppHash() + require.Nil(t, err) + + //TODO: Write iavl equivalent somehow + // storeHashB1 := appB1.cms.(*multi.Store).GetSubstoreSMT(capKey2.Name()).Root() + + resp := appB1.GenerateFraudProof( + abci.RequestGenerateFraudProof{ + BeginBlockRequest: *beginRequest, DeliverTxRequests: append(nonFraudulentDeliverRequests, fraudDeliverRequest), EndBlockRequest: nil, + }, + ) + + // Light Client + fraudProof := FraudProof{} + fraudProof.fromABCI(*resp.FraudProof) + require.Equal(t, appHashB1, fraudProof.appHash) + fraudProofVerified, err := fraudProof.verifyFraudProof() + require.Nil(t, err) + require.True(t, fraudProofVerified) + + // Now we take contents of the fraud proof which was recorded with S2 and try to populate a fresh baseapp B2 with it + // B2 <- S2 + // codec := codec.NewLegacyAmino() + // registerTestCodec(codec) + // appB2, err := SetupBaseAppFromFraudProof(t.Name(), defaultLogger(), dbm.NewMemDB(), testTxDecoder(codec), fraudProof, routerOpt) + // require.Nil(t, err) + // appB2Hash := appB2.cms.(*rootmulti.Store).GetAppHash() + // require.Equal(t, appHashB1, appB2Hash) + // storeHashB2 := appB2.cms.(*multi.Store).GetSubstoreSMT(capKey2.Name()).Root() + // require.Equal(t, storeHashB1, storeHashB2) +} diff --git a/baseapp/fraudproof.go b/baseapp/fraudproof.go new file mode 100644 index 000000000000..e33e91e0ee26 --- /dev/null +++ b/baseapp/fraudproof.go @@ -0,0 +1,196 @@ +package baseapp + +import ( + "bytes" + "crypto/sha256" + "errors" + "fmt" + + smtlib "github.com/celestiaorg/smt" + "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/store/v2alpha1/smt" + iavltree "github.com/cosmos/iavl" + abci "github.com/tendermint/tendermint/abci/types" + tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" +) + +var ( + ErrMoreThanOneBlockTypeUsed = errors.New("fraudProof has more than one type of fradulent state transitions marked nil") +) + +// Represents a single-round fraudProof +type FraudProof struct { + // The block height to load state of + blockHeight int64 + + // TODO: Add Proof that appHash is inside merklized ISRs in block header at block height + + appHash []byte + // A map from module name to state witness + stateWitness map[string]StateWitness + + // Fraudulent state transition has to be one of these + // Only one have of these three can be non-nil + fraudulentBeginBlock *abci.RequestBeginBlock + fraudulentDeliverTx *abci.RequestDeliverTx + fraudulentEndBlock *abci.RequestEndBlock + + // TODO: Add Proof that fraudulent state transition is inside merkelizied transactions in block header +} + +// State witness with a list of all witness data +type StateWitness struct { + // store level proof + Proof tmcrypto.ProofOp + RootHash []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 + Proof tmcrypto.ProofOp +} + +func (fraudProof *FraudProof) getModules() []string { + keys := make([]string, 0, len(fraudProof.stateWitness)) + for k := range fraudProof.stateWitness { + keys = append(keys, k) + } + return keys +} + +func (fraudProof *FraudProof) getDeepIAVLTrees() (map[string]*iavltree.MutableTree, error) { + storeKeyToIAVLTree := make(map[string]*iavltree.MutableTree) + for storeKey, stateWitness := range fraudProof.stateWitness { + rootHash := stateWitness.RootHash + // TODO(manav): Replace with IAVL Deep Subtrees once implementation is done + substoreDeepSMT := smtlib.NewDeepSparseMerkleSubTree(smtlib.NewSimpleMap(), smtlib.NewSimpleMap(), sha256.New(), rootHash) + for _, witnessData := range stateWitness.WitnessData { + proofOp, key, val := witnessData.Proof, witnessData.Key, witnessData.Value + proof, err := types.CommitmentOpDecoder(proofOp) + if err != nil { + return nil, err + } + // TODO(manav): Replace with an IAVL proof instead of SMT here + smtProof := proof.(*smt.ProofOp).GetProof() + substoreDeepSMT.AddBranch(smtProof, key, val) + } + // TODO(manav): Replace with actual iavl stores + storeKeyToIAVLTree[storeKey] = &iavltree.MutableTree{} + } + return storeKeyToIAVLTree, nil +} + +// Returns true only if only one of the three pointers is nil +func (fraudProof *FraudProof) checkFraudulentStateTransition() bool { + if fraudProof.fraudulentBeginBlock != nil { + return fraudProof.fraudulentDeliverTx == nil && fraudProof.fraudulentEndBlock == nil + } + if fraudProof.fraudulentDeliverTx != nil { + return fraudProof.fraudulentEndBlock == nil + } + return fraudProof.fraudulentEndBlock != nil +} + +func (fraudProof *FraudProof) verifyFraudProof() (bool, error) { + if !fraudProof.checkFraudulentStateTransition() { + return false, ErrMoreThanOneBlockTypeUsed + } + for storeKey, stateWitness := range fraudProof.stateWitness { + proofOp := stateWitness.Proof + proof, err := types.CommitmentOpDecoder(proofOp) + if err != nil { + return false, err + } + if !bytes.Equal(proof.GetKey(), []byte(storeKey)) { + return false, fmt.Errorf("got storeKey: %s, expected: %s", string(proof.GetKey()), storeKey) + } + appHash, err := proof.Run([][]byte{stateWitness.RootHash}) + if err != nil { + return false, err + } + if !bytes.Equal(appHash[0], fraudProof.appHash) { + return false, fmt.Errorf("got appHash: %s, expected: %s", string(fraudProof.appHash), string(fraudProof.appHash)) + } + // Fraudproof verification on a substore level + for _, witness := range stateWitness.WitnessData { + proofOp, key, value := witness.Proof, witness.Key, witness.Value + if err != nil { + return false, err + } + if !bytes.Equal(key, proofOp.GetKey()) { + return false, fmt.Errorf("got key: %s, expected: %s for storeKey: %s", string(key), string(proof.GetKey()), storeKey) + } + proof, err := types.CommitmentOpDecoder(proofOp) + if err != nil { + return false, err + } + rootHash, err := proof.Run([][]byte{value}) + if err != nil { + return false, err + } + if !bytes.Equal(rootHash[0], stateWitness.RootHash) { + return false, fmt.Errorf("got rootHash: %s, expected: %s for storeKey: %s", string(rootHash[0]), string(stateWitness.RootHash), storeKey) + } + } + } + return true, nil +} + +func (fraudProof *FraudProof) toABCI() abci.FraudProof { + abciStateWitness := make(map[string]*abci.StateWitness) + for storeKey, stateWitness := range fraudProof.stateWitness { + abciWitnessData := make([]*abci.WitnessData, 0, len(stateWitness.WitnessData)) + for _, witnessData := range stateWitness.WitnessData { + abciWitness := abci.WitnessData{ + Key: witnessData.Key, + Value: witnessData.Value, + Proof: &witnessData.Proof, + } + abciWitnessData = append(abciWitnessData, &abciWitness) + } + proof := stateWitness.Proof + abciStateWitness[storeKey] = &abci.StateWitness{ + Proof: &proof, + RootHash: stateWitness.RootHash, + WitnessData: abciWitnessData, + } + } + return abci.FraudProof{ + BlockHeight: fraudProof.blockHeight, + AppHash: fraudProof.appHash, + StateWitness: abciStateWitness, + FraudulentBeginBlock: fraudProof.fraudulentBeginBlock, + FraudulentDeliverTx: fraudProof.fraudulentDeliverTx, + FraudulentEndBlock: fraudProof.fraudulentEndBlock, + } +} + +func (fraudProof *FraudProof) fromABCI(abciFraudProof abci.FraudProof) { + stateWitness := make(map[string]StateWitness) + for storeKey, abciStateWitness := range abciFraudProof.StateWitness { + witnessData := make([]WitnessData, 0, len(abciStateWitness.WitnessData)) + for _, abciWitnessData := range abciStateWitness.WitnessData { + witness := WitnessData{ + Key: abciWitnessData.Key, + Value: abciWitnessData.Value, + Proof: *abciWitnessData.Proof, + } + witnessData = append(witnessData, witness) + } + stateWitness[storeKey] = StateWitness{ + Proof: *abciStateWitness.Proof, + RootHash: abciStateWitness.RootHash, + WitnessData: witnessData, + } + } + fraudProof.blockHeight = abciFraudProof.BlockHeight + fraudProof.appHash = abciFraudProof.AppHash + fraudProof.stateWitness = stateWitness + fraudProof.fraudulentBeginBlock = abciFraudProof.FraudulentBeginBlock + fraudProof.fraudulentDeliverTx = abciFraudProof.FraudulentDeliverTx + fraudProof.fraudulentEndBlock = abciFraudProof.FraudulentEndBlock +} diff --git a/baseapp/fraudproof_test.go b/baseapp/fraudproof_test.go new file mode 100644 index 000000000000..d7d74fcbab94 --- /dev/null +++ b/baseapp/fraudproof_test.go @@ -0,0 +1,60 @@ +package baseapp + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/abci/types" +) + +// Tests the checkFraudulentStateTransition for all possible cases. +func TestCheckFraudulentStateTransition(t *testing.T) { + + //Case when only BeginBlock is set + fraudProof := FraudProof{} + fraudProof.fraudulentBeginBlock = &types.RequestBeginBlock{} + result := fraudProof.checkFraudulentStateTransition() + require.True(t, result) + + //Case when only DeliverTx is set + fraudProof = FraudProof{} + fraudProof.fraudulentDeliverTx = &types.RequestDeliverTx{} + result = fraudProof.checkFraudulentStateTransition() + require.True(t, result) + + //Case when only EndBlock is set + fraudProof = FraudProof{} + fraudProof.fraudulentEndBlock = &types.RequestEndBlock{} + result = fraudProof.checkFraudulentStateTransition() + require.True(t, result) + + //Case when both BeginBlock and DeliverTx are set + fraudProof = FraudProof{} + fraudProof.fraudulentBeginBlock = &types.RequestBeginBlock{} + fraudProof.fraudulentDeliverTx = &types.RequestDeliverTx{} + result = fraudProof.checkFraudulentStateTransition() + require.False(t, result) + + //Case when both BeginBlock and EndBlock are set + fraudProof = FraudProof{} + fraudProof.fraudulentBeginBlock = &types.RequestBeginBlock{} + fraudProof.fraudulentEndBlock = &types.RequestEndBlock{} + result = fraudProof.checkFraudulentStateTransition() + require.False(t, result) + + //Case when both DeliverTx and EndBlock are set + fraudProof = FraudProof{} + fraudProof.fraudulentDeliverTx = &types.RequestDeliverTx{} + fraudProof.fraudulentEndBlock = &types.RequestEndBlock{} + result = fraudProof.checkFraudulentStateTransition() + require.False(t, result) + + //Case when both DeliverTx and EndBlock are set + fraudProof = FraudProof{} + fraudProof.fraudulentBeginBlock = &types.RequestBeginBlock{} + fraudProof.fraudulentDeliverTx = &types.RequestDeliverTx{} + fraudProof.fraudulentEndBlock = &types.RequestEndBlock{} + result = fraudProof.checkFraudulentStateTransition() + require.False(t, result) + +} diff --git a/baseapp/options.go b/baseapp/options.go index ad8b7f9d5e7f..d9690d529011 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -11,7 +11,9 @@ import ( "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/store/rootmulti" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/iavl" ) // File for storing in-package BaseApp optional functions, @@ -32,6 +34,20 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { return func(bapp *BaseApp) { bapp.setMinGasPrices(gasPrices) } } +// SetInitialHeight returns a BaseApp option function that sets the initial block height. +func SetInitialHeight(blockHeight int64) func(*BaseApp) { + return func(bapp *BaseApp) { bapp.setInitialHeight(blockHeight) } +} + +// SetDeepIAVLTree sets the storeParam to have the given deep IAVL tree +// for the given skey. +func SetDeepIAVLTree(skey string, iavlTree *iavl.MutableTree) func(*BaseApp) { + return func(bapp *BaseApp) { + cms := bapp.cms.(*rootmulti.Store) + cms.SetDeepIAVLTree(skey, iavlTree) + } +} + // SetHaltHeight returns a BaseApp option function that sets the halt block height. func SetHaltHeight(blockHeight uint64) func(*BaseApp) { return func(bapp *BaseApp) { bapp.setHaltHeight(blockHeight) } @@ -196,6 +212,12 @@ func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) { app.cms.SetTracer(w) } +// SetCommitKVStoreTracer sets the store tracer on the BaseApp's underlying +// tracer for a substore with given skey. +func (app *BaseApp) SetCommitKVStoreTracer(skey string, w io.Writer) { + app.cms.SetTracerFor(skey, w) +} + // SetStoreLoader allows us to customize the rootMultiStore initialization. func (app *BaseApp) SetStoreLoader(loader StoreLoader) { if app.sealed { diff --git a/go.mod b/go.mod index d9f756183bc5..cd7428313d18 100644 --- a/go.mod +++ b/go.mod @@ -56,13 +56,15 @@ require ( github.com/tendermint/tm-db v0.6.7 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e - google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959 + google.golang.org/genproto v0.0.0-20221013201013-33fc6f83cba4 google.golang.org/grpc v1.50.0 google.golang.org/protobuf v1.28.1 pgregory.net/rapid v0.4.7 sigs.k8s.io/yaml v1.3.0 ) +require github.com/chrispappas/golang-generics-set v1.0.1 + require ( cloud.google.com/go v0.102.1 // indirect cloud.google.com/go/compute v1.7.0 // indirect @@ -229,12 +231,12 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.23.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect + golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect - golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect + golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/tools v0.1.12 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.93.0 // indirect @@ -256,8 +258,7 @@ replace ( // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - - github.com/jhump/protoreflect => github.com/jhump/protoreflect v1.9.0 + github.com/tendermint/tendermint => github.com/celestiaorg/tendermint v0.34.18-0.20221013213714-8be9b54c8c21 ) retract v0.46.2 diff --git a/go.sum b/go.sum index d2fc92f7ab2e..911c97859407 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/celestiaorg/rollmint v0.4.0 h1:+eu2m25ALhlHj9pbAElzq2pSg6WJ4Bn8/WqL2H github.com/celestiaorg/rollmint v0.4.0/go.mod h1:EBnqxgmcH0MIgNgiI8NyVa1sNSzCyCpvAlAHd9myhk4= github.com/celestiaorg/smt v0.3.0 h1:Hc6m8fIVRajrg/Saf8ivX4xw551LHzOs8kqeadd6h9s= github.com/celestiaorg/smt v0.3.0/go.mod h1:/sdYDakowo/XaxS2Fl7CBqtuf/O2uTqF2zmAUFAtAiw= +github.com/celestiaorg/tendermint v0.34.18-0.20221013213714-8be9b54c8c21 h1:eRc/DaqHOw35b4Kf2Fqu1frD/88TD3enIcbzWJu2MIg= +github.com/celestiaorg/tendermint v0.34.18-0.20221013213714-8be9b54c8c21/go.mod h1:zoyyiiihvTW8DnOr63YLxhYn/WK/QmE74CeIpS++hBE= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= @@ -208,6 +210,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 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 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= @@ -566,7 +570,6 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk= -github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -715,8 +718,12 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= -github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= +github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -1000,7 +1007,6 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= -github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1248,8 +1254,6 @@ github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RM github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/tendermint v0.34.22 h1:XMhtC8s8QqJO4l/dn+TkQvevTRSow3Vixjclr41o+2Q= -github.com/tendermint/tendermint v0.34.22/go.mod h1:YpP5vBEAKUT4g6oyfjKgFeZmdB/GjkJAxfF+cgmJg6Y= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -1490,8 +1494,8 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 h1:MgJ6t2zo8v0tbmLCueaCbF1RM+TtB0rs3Lv8DGtOIpY= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1645,8 +1649,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1660,8 +1664,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1714,10 +1719,8 @@ golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWc golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1900,8 +1903,8 @@ google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljW google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959 h1:hw4Y42zL1VyVKxPgRHHh191fpVBGV8sNVmcow5Z8VXY= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20221013201013-33fc6f83cba4 h1:nZ28yoLJWNLTcERW43BN+JDsNQOdiZOFB9Dly/IUrjw= +google.golang.org/genproto v0.0.0-20221013201013-33fc6f83cba4/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1955,7 +1958,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/server/mock/store.go b/server/mock/store.go index 2f4d7601001d..ca58336866a7 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -50,6 +50,10 @@ func (ms multiStore) SetTracer(w io.Writer) sdk.MultiStore { panic("not implemented") } +func (ms multiStore) SetTracerFor(skey string, w io.Writer) sdk.MultiStore { + panic("not implemented") +} + func (ms multiStore) AddListeners(key storetypes.StoreKey, listeners []storetypes.WriteListener) { panic("not implemented") } diff --git a/store/cachemulti/store.go b/store/cachemulti/store.go index deb1d46272dd..64b8e7aa60fa 100644 --- a/store/cachemulti/store.go +++ b/store/cachemulti/store.go @@ -100,6 +100,12 @@ func (cms Store) SetTracer(w io.Writer) types.MultiStore { return cms } +// SetTracerFor sets the tracer for a particular underlying store in the +// Multistore that will utilize to trace operations. A MultiStore is returned. +func (cms Store) SetTracerFor(_ string, _ io.Writer) types.MultiStore { + return cms +} + // SetTracingContext updates the tracing context for the MultiStore by merging // the given context with the existing context by key. Any existing keys will // be overwritten. It is implied that the caller should update the context when diff --git a/store/iavl/store.go b/store/iavl/store.go index 398a66995a50..a9cde062b852 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -40,6 +40,13 @@ type Store struct { tree Tree } +// LoadStoreWithDeepIAVLTree returns an IAVL Store as a CommitKVStore with given deep tree. +func LoadStoreWithDeepIAVLTree(tree *iavl.MutableTree) (types.CommitKVStore, error) { + return &Store{ + tree: tree, + }, nil +} + // LoadStore returns an IAVL Store as a CommitKVStore. Internally, it will load the // store's version (id) from the provided DB. An error is returned if the version // fails to load, or if called with a positive version on an empty tree. @@ -286,6 +293,18 @@ func (st *Store) Import(version int64) (*iavl.Importer, error) { return tree.Import(version) } +// Wrapper for getProofFromTree +func (st *Store) GetProofFromTree(key []byte) *tmcrypto.ProofOps { + iavlTree := st.tree.((*iavl.MutableTree)) + return getProofFromTree(iavlTree, key, true) +} + +func (st *Store) Root() ([]byte, error) { + iavlTree := st.tree.((*iavl.MutableTree)) + hash, err := iavlTree.WorkingHash() + return hash, err +} + // Handle gatest the latest height, if height is 0 func getHeight(tree Tree, req abci.RequestQuery) int64 { height := req.Height diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index b6a7eeab4ab1..19da271a55af 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -1,6 +1,7 @@ package rootmulti import ( + "bytes" "fmt" "io" "math" @@ -8,6 +9,7 @@ import ( "strings" "sync" + sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps" iavltree "github.com/cosmos/iavl" protoio "github.com/gogo/protobuf/io" gogotypes "github.com/gogo/protobuf/types" @@ -28,6 +30,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/transient" "github.com/cosmos/cosmos-sdk/store/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) const ( @@ -163,6 +166,14 @@ func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore { return rs.stores[key] } +func (s *Store) GetStoreKeys() []types.StoreKey { + storeKeys := make([]types.StoreKey, 0, len(s.keysByName)) + for _, sk := range s.keysByName { + storeKeys = append(storeKeys, sk) + } + return storeKeys +} + // StoreKeysByName returns mapping storeNames -> StoreKeys func (rs *Store) StoreKeysByName() map[string]types.StoreKey { return rs.keysByName @@ -185,6 +196,18 @@ func (rs *Store) LoadLatestVersion() error { return rs.loadVersion(ver, nil) } +func (rs *Store) LoadLastVersion() error { + + if rs.lastCommitInfo.GetVersion() == 0 { + // This case means that no commit has been made in the store, so + // there is no last version. + return fmt.Errorf("no previous commit found") + } + + lastVersion := rs.lastCommitInfo.GetVersion() + return rs.loadVersion(lastVersion, nil) +} + // LoadVersion implements CommitMultiStore. func (rs *Store) LoadVersion(ver int64) error { return rs.loadVersion(ver, nil) @@ -338,6 +361,13 @@ func (rs *Store) SetInterBlockCache(c types.MultiStorePersistentCache) { rs.interBlockCache = c } +func (rs *Store) SetDeepIAVLTree(skey string, iavlTree *iavltree.MutableTree) { + key := rs.keysByName[skey] + storeParams := rs.storesParams[key] + storeParams.deepIAVLTree = iavlTree + rs.storesParams[key] = storeParams +} + // SetTracer sets the tracer for the MultiStore that the underlying // stores will utilize to trace operations. A MultiStore is returned. func (rs *Store) SetTracer(w io.Writer) types.MultiStore { @@ -345,6 +375,45 @@ func (rs *Store) SetTracer(w io.Writer) types.MultiStore { return rs } +// SetTracerFor sets the tracer for a particular underlying store in the +// Multistore that it will utilize to trace operations. A MultiStore is returned. +func (rs *Store) SetTracerFor(skey string, w io.Writer) types.MultiStore { + key := rs.keysByName[skey] + storeParams := rs.storesParams[key] + storeParams.traceWriter = w + rs.storesParams[key] = storeParams + return rs +} + +// GetTracerFor gets the tracer for a particular underlying store in the +// Multistore that it will utilize to trace operations. A MultiStore is returned. +func (rs *Store) GetTracerBufferFor(skey string) *bytes.Buffer { + key := rs.keysByName[skey] + storeParams, exists := rs.storesParams[key] + if exists { + buf, ok := storeParams.traceWriter.(*bytes.Buffer) + if ok { + return buf + } + } + return nil +} + +func (rs *Store) ResetAllTraceWriters() { + buf, ok := rs.traceWriter.(*bytes.Buffer) + if ok { + buf.Reset() + } + for _, storeParams := range rs.storesParams { + if storeParams.traceWriter != nil { + buf, ok := storeParams.traceWriter.(*bytes.Buffer) + if ok { + buf.Reset() + } + } + } +} + // SetTracingContext updates the tracing context for the MultiStore by merging // the given context with the existing context by key. Any existing keys will // be overwritten. It is implied that the caller should update the context when @@ -436,6 +505,7 @@ func (rs *Store) Commit() types.CommitID { } // reset the removalMap rs.removalMap = make(map[types.StoreKey]bool) + rs.ResetAllTraceWriters() if err := rs.handlePruning(version); err != nil { panic(err) @@ -531,7 +601,11 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { store := s.(types.KVStore) if rs.TracingEnabled() { - store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext()) + if rs.storesParams[key].traceWriter != nil { + store = tracekv.NewStore(store, rs.storesParams[key].traceWriter, rs.getTracingContext()) + } else { + store = tracekv.NewStore(store, rs.traceWriter, rs.getTracingContext()) + } } if rs.ListeningEnabled(key) { store = listenkv.NewStore(store, key, rs.listeners[key]) @@ -882,10 +956,12 @@ func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID case types.StoreTypeIAVL: var store types.CommitKVStore var err error - - if params.initialVersion == 0 { - store, err = iavl.LoadStore(db, rs.logger, key, id, rs.lazyLoading, rs.iavlCacheSize, rs.iavlDisableFastNode) - } else { + switch { + case params.deepIAVLTree != nil: + store, err = iavl.LoadStoreWithDeepIAVLTree(params.deepIAVLTree) + case params.initialVersion == 0: + store, err = iavl.LoadStore(db, rs.logger, key, id, rs.lazyLoading, rs.iavlCacheSize, true) + default: store, err = iavl.LoadStoreWithInitialVersion(db, rs.logger, key, id, rs.lazyLoading, params.initialVersion, rs.iavlCacheSize, rs.iavlDisableFastNode) } @@ -984,11 +1060,40 @@ func (rs *Store) flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo rs.logger.Debug("flushing metadata finished", "height", version) } +func (rs *Store) GetAppHash() ([]byte, error) { + m, err := rs.getWorkingMap() + if err != nil { + return nil, err + } + return sdkmaps.HashFromMap(m), nil +} + +func (rs *Store) getWorkingMap() (map[string][]byte, error) { + stores := rs.stores + m := make(map[string][]byte, len(stores)) + for key := range stores { + name := key.Name() + iavlStore, err := rs.GetIAVLStore(name) + if err != nil { + return nil, err + } + m[name], err = iavlStore.Root() + if err != nil { + return nil, err + } + } + return m, nil +} + type storeParams struct { key types.StoreKey db dbm.DB typ types.StoreType initialVersion uint64 + + deepIAVLTree *iavltree.MutableTree + + traceWriter io.Writer } func GetLatestVersion(db dbm.DB) int64 { @@ -1082,3 +1187,23 @@ func flushLatestVersion(batch dbm.Batch, version int64) { batch.Set([]byte(latestVersionKey), bz) } + +func (rs *Store) GetIAVLStore(key string) (*iavl.Store, error) { + store := rs.GetStoreByName(key) + if store.GetStoreType() != types.StoreTypeIAVL { + return nil, fmt.Errorf("non-IAVL store not supported") + } + return store.(*iavl.Store), nil +} + +func (rs *Store) GetStoreProof(storeKeyName string) (*tmcrypto.ProofOp, error) { + m, err := rs.getWorkingMap() + if err != nil { + return nil, err + } + proofOp, err := types.ProofOpFromMap(m, storeKeyName) + if err != nil { + return nil, err + } + return &proofOp, nil +} diff --git a/store/tracekv/store.go b/store/tracekv/store.go index 91f3c657682c..817dcfb794e6 100644 --- a/store/tracekv/store.go +++ b/store/tracekv/store.go @@ -1,12 +1,15 @@ package tracekv import ( + "bytes" "encoding/base64" "encoding/json" + "errors" "io" + sdkerrors "cosmossdk.io/errors" + "github.com/chrispappas/golang-generics-set/set" "github.com/cosmos/cosmos-sdk/store/types" - "github.com/cosmos/cosmos-sdk/types/errors" ) const ( @@ -17,6 +20,10 @@ const ( iterValueOp operation = "iterValue" ) +var ( + ErrBufferEmpty = errors.New("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 @@ -90,6 +97,28 @@ 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) 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 { + panic(err) + } + key, err := base64.StdEncoding.DecodeString(traceOp.Key) + if err != nil { + panic(sdkerrors.Wrap(err, "failed to decode key read from buf")) + } + keys.Add(string(key)) + } +} + // 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 { @@ -193,12 +222,31 @@ func writeOperation(w io.Writer, op operation, tc types.TraceContext, key, value raw, err := json.Marshal(traceOp) if err != nil { - panic(errors.Wrap(err, "failed to serialize trace operation")) + panic(sdkerrors.Wrap(err, "failed to serialize trace operation")) } if _, err := w.Write(raw); err != nil { - panic(errors.Wrap(err, "failed to write trace operation")) + panic(sdkerrors.Wrap(err, "failed to write trace operation")) } io.WriteString(w, "\n") } + +// 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, ErrBufferEmpty + } + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to read trace operation") + } + traceOp := traceOperation{} + err = json.Unmarshal([]byte(raw), &traceOp) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to deserialize trace operation") + } + + return &traceOp, nil +} diff --git a/store/tracekv/store_test.go b/store/tracekv/store_test.go index 1b81e89bafd2..41fe24a6d427 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" @@ -113,6 +114,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 := set.FromSlice([]string{ + string(kvPairs[0].Key), + string(kvPairs[1].Key), + string(kvPairs[2].Key), + }) + + 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 diff --git a/store/types/store.go b/store/types/store.go index e3c0f0822ad2..0cf153f40477 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -123,6 +123,11 @@ type MultiStore interface { // returned. SetTracer(w io.Writer) MultiStore + // SetTracerFor sets the tracer for a particular underlying store in the + // Multistore that will utilize to trace operations. The modified MultiStore is + // returned. + SetTracerFor(skey string, w io.Writer) MultiStore + // SetTracingContext sets the tracing context for a MultiStore. It is // implied that the caller should update the context when necessary between // tracing operations. The modified MultiStore is returned. diff --git a/store/v2alpha1/smt/proof.go b/store/v2alpha1/smt/proof.go index d247f1bf6617..32433d5085df 100644 --- a/store/v2alpha1/smt/proof.go +++ b/store/v2alpha1/smt/proof.go @@ -62,6 +62,10 @@ func (p *ProofOp) GetKey() []byte { return p.Key } +func (p *ProofOp) GetProof() smt.SparseMerkleProof { + return p.Proof +} + func (p *ProofOp) ProofOp() tmmerkle.ProofOp { var data bytes.Buffer enc := gob.NewEncoder(&data)