Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Commit

Permalink
feat: Fraud Proof generation and partial verification using IAVL store (
Browse files Browse the repository at this point in the history
#1)

* Replace tendermint with celestiaorg tendermint version

* Add new ABCI method skeleton: build works now

* Add fraudproof data structure

* Add helper methods to baseapp test for fraudproofs

* Add TODOs in baseapp test method

* Add SetupBaseParams with SMT, WIP to IAVL

* Add tracekv readOperations and getAllKeysUsedInTrace

* Cleanup errors

* Add loadLastVersion to IAVL rootmulti store

* Remove enableFraudProofGenerationMode

* Add substore tracing to rootmulti store

* Add ability to reset trace writer buffers after each commit

* Able to get the trace made by fraudulent state transition now

* Start writing getFraudProof method in BaseApp

* Substitute SMT with IAVL trees inside getFraudProof

* Implement getStoreProof inside rootmulti store

* Completed generateFraudProof with IAVL store

* Remove routerOpts from baseapp test when generating fraud proofs

* Switch out proof decoder

* Add Root in IAVL to use working hash instead of lastCommitID hash

* Fix getAppHash to use working hashes of IAVL stores

* Implement ABCI method to verify fraud proofs

* Start setup BaseApp from IAVL store WIP

* Add option to SetInitialHeight

* Initialize baseapp from IAVL trees

* Use deepIAVLTree in loadVersion

* Add SetTracerFor to mock store

* Add gdocs to new ABCI methods

* Refactor from fmt.errorf into errors.New

* Update error packages used

* Update docs to remove SMT references

* Add unit test for checkFraudulentStateTransition

* modify executeNonFraudulentStateTransactions to accomodate case when EndBlock is fraudulent

* Refactor code
  • Loading branch information
Manav-Aggarwal authored Nov 8, 2022
1 parent 462e2d2 commit 55d350f
Show file tree
Hide file tree
Showing 16 changed files with 1,001 additions and 32 deletions.
161 changes: 161 additions & 0 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package baseapp

import (
"bytes"
"crypto/sha256"
"encoding/json"
"errors"
Expand All @@ -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"
Expand Down Expand Up @@ -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() {
Expand Down
88 changes: 88 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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...)
}
Loading

0 comments on commit 55d350f

Please sign in to comment.