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

feat: Fraud Proof generation and partial verification using IAVL store #1

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
51d06e8
Replace tendermint with celestiaorg tendermint version
Manav-Aggarwal Oct 14, 2022
9396aab
Add new ABCI method skeleton: build works now
Manav-Aggarwal Oct 14, 2022
1975e89
Add fraudproof data structure
Manav-Aggarwal Oct 14, 2022
8168e32
Add helper methods to baseapp test for fraudproofs
Manav-Aggarwal Oct 14, 2022
20e50ff
Add TODOs in baseapp test method
Manav-Aggarwal Oct 14, 2022
9801e3e
Add SetupBaseParams with SMT, WIP to IAVL
Manav-Aggarwal Oct 14, 2022
b3737ae
Add tracekv readOperations and getAllKeysUsedInTrace
Manav-Aggarwal Oct 15, 2022
a841e31
Cleanup errors
Manav-Aggarwal Oct 15, 2022
afda438
Add loadLastVersion to IAVL rootmulti store
Manav-Aggarwal Oct 15, 2022
e9e96ef
Remove enableFraudProofGenerationMode
Manav-Aggarwal Oct 15, 2022
255115b
Add substore tracing to rootmulti store
Manav-Aggarwal Oct 15, 2022
3730b3c
Add ability to reset trace writer buffers after each commit
Manav-Aggarwal Oct 15, 2022
f61fc8d
Able to get the trace made by fraudulent state transition now
Manav-Aggarwal Oct 15, 2022
f47e13f
Start writing getFraudProof method in BaseApp
Manav-Aggarwal Oct 15, 2022
cd8e9cf
Substitute SMT with IAVL trees inside getFraudProof
Manav-Aggarwal Oct 15, 2022
3a5ebed
Implement getStoreProof inside rootmulti store
Manav-Aggarwal Oct 15, 2022
4587509
Completed generateFraudProof with IAVL store
Manav-Aggarwal Oct 15, 2022
3d27e3e
Remove routerOpts from baseapp test when generating fraud proofs
Manav-Aggarwal Oct 15, 2022
afc7229
Switch out proof decoder
Manav-Aggarwal Oct 15, 2022
edcefbb
Add Root in IAVL to use working hash instead of lastCommitID hash
Manav-Aggarwal Oct 15, 2022
e0b809f
Fix getAppHash to use working hashes of IAVL stores
Manav-Aggarwal Oct 15, 2022
7ca2868
Implement ABCI method to verify fraud proofs
Manav-Aggarwal Oct 15, 2022
d3279d0
Start setup BaseApp from IAVL store WIP
Manav-Aggarwal Oct 15, 2022
3f51f21
Add option to SetInitialHeight
Manav-Aggarwal Oct 15, 2022
2ee763e
Initialize baseapp from IAVL trees
Manav-Aggarwal Oct 15, 2022
d3bba65
Use deepIAVLTree in loadVersion
Manav-Aggarwal Oct 15, 2022
48b968c
Add SetTracerFor to mock store
Manav-Aggarwal Oct 16, 2022
f52e1e2
Add gdocs to new ABCI methods
Manav-Aggarwal Oct 20, 2022
536d58b
Refactor from fmt.errorf into errors.New
Manav-Aggarwal Oct 20, 2022
6a6c843
Update error packages used
Manav-Aggarwal Oct 20, 2022
d3e0a32
Update docs to remove SMT references
Manav-Aggarwal Oct 20, 2022
19edc8e
Add unit test for checkFraudulentStateTransition
Manav-Aggarwal Oct 20, 2022
da75457
modify executeNonFraudulentStateTransactions to accomodate case when …
Manav-Aggarwal Oct 20, 2022
7db9131
Refactor code
Manav-Aggarwal Nov 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
tzdybal marked this conversation as resolved.
Show resolved Hide resolved
tzdybal marked this conversation as resolved.
Show resolved Hide resolved
cms := app.cms.(*rootmulti.Store)

appHash, err := cms.GetAppHash()
if err != nil {
panic(err)
Copy link

Choose a reason for hiding this comment

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

Is calling panic a standard pulled from the cosmos sdk?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, for ABCI methods, if something goes wrong, there's no way to gracefully throw an error other than panic. If you look at the BeginBlock implementation, you'll see panic calls.

}
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) {
tzdybal marked this conversation as resolved.
Show resolved Hide resolved
// 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)
Copy link
Member

Choose a reason for hiding this comment

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

Is there any situation when this can happen? Because of pruning settings, genesis block?

Copy link
Member Author

Choose a reason for hiding this comment

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

Happens when there is no last state to load form, added a inline comment mentioning it

}

// 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{})
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Added "chain update" case in rollkit/rollkit#518

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}})
Copy link
Member

Choose a reason for hiding this comment

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

Is "dummy" enough? What about other params in RequestBeginBlock?

Copy link
Member Author

Choose a reason for hiding this comment

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

Documented in rollkit/rollkit#572

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) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we actually need to export this function? It seems it's used only in SetupBaseAppFromFraudProof, so it can be private.

Copy link
Member Author

Choose a reason for hiding this comment

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

Resolved in #4

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