diff --git a/mocks/Application.go b/mocks/Application.go index 0f0b29049a..2164d6f162 100644 --- a/mocks/Application.go +++ b/mocks/Application.go @@ -96,21 +96,6 @@ func (_m *Application) GetAppHash(_a0 types.RequestGetAppHash) types.ResponseGet return r0 } -// Commit provides a mock function with given fields: -func (_m *Application) TriggerFraudProofGenerationMode(_a0 types.RequestTriggerFraudProofGenerationMode) types.ResponseTriggerFraudProofGenerationMode { - ret := _m.Called(_a0) - - var r0 types.ResponseTriggerFraudProofGenerationMode - if rf, ok := ret.Get(0).(func(types.RequestTriggerFraudProofGenerationMode) types.ResponseTriggerFraudProofGenerationMode); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(types.ResponseTriggerFraudProofGenerationMode) - } - - return r0 -} - - // DeliverTx provides a mock function with given fields: _a0 func (_m *Application) DeliverTx(_a0 types.RequestDeliverTx) types.ResponseDeliverTx { ret := _m.Called(_a0) diff --git a/node/integration_test.go b/node/integration_test.go index 9b201c5d01..e46ecefe90 100644 --- a/node/integration_test.go +++ b/node/integration_test.go @@ -209,7 +209,7 @@ func TestFraudProofTrigger(t *testing.T) { beginCnt := 0 endCnt := 0 commitCnt := 0 - triggerFraudProofGenerationModeCnt := 0 + generateFraudProofCnt := 0 for _, call := range app.Calls { switch call.Method { case "BeginBlock": @@ -218,8 +218,8 @@ func TestFraudProofTrigger(t *testing.T) { endCnt++ case "Commit": commitCnt++ - case "TriggerFraudProofGenerationMode": - triggerFraudProofGenerationModeCnt++ + case "GenerateFraudProof": + generateFraudProofCnt++ } } aggregatorHeight := nodes[0].Store.Height() @@ -228,7 +228,7 @@ func TestFraudProofTrigger(t *testing.T) { assert.GreaterOrEqual(endCnt, adjustedHeight) assert.GreaterOrEqual(commitCnt, adjustedHeight) - assert.Equal(triggerFraudProofGenerationModeCnt, beginCnt+endCnt+clientNodes) + assert.Equal(generateFraudProofCnt, beginCnt+endCnt+clientNodes) // assert that all blocks known to node are same as produced by aggregator for h := uint64(1); h <= nodes[i].Store.Height(); h++ { @@ -300,7 +300,7 @@ func createNode(n int, isMalicious bool, aggregator bool, dalc da.DataAvailabili } if isMalicious && !aggregator { - app.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{}) + app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{FraudProof: &abci.FraudProof{}}) } app.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{}).Run(func(args mock.Arguments) { wg.Done() diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index 0e02d0ad44..177fcd2cb2 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -38,7 +38,7 @@ message Request { RequestApplySnapshotChunk apply_snapshot_chunk = 15; RequestGetAppHash get_app_hash = 16; RequestGenerateFraudProof generate_fraud_proof = 17; - RequestTriggerFraudProofGenerationMode trigger_fraud_proof_generation_mode = 18; + RequestVerifyFraudProof verify_fraud_proof = 18; } } @@ -131,10 +131,17 @@ message RequestApplySnapshotChunk { message RequestGetAppHash {} // Generates a fraud proof -message RequestGenerateFraudProof {} +message RequestGenerateFraudProof { + RequestBeginBlock beginBlockRequest = 1 [(gogoproto.nullable) = false]; + repeated RequestDeliverTx deliverTxRequests = 2; + RequestEndBlock endBlockRequest = 3; +} -// Triggers fraud proof generation mode -message RequestTriggerFraudProofGenerationMode {} +// Verifies a fraud proof +message RequestVerifyFraudProof { + FraudProof fraud_proof = 1; + bytes expected_app_hash = 2; +} //---------------------------------------- // Response types @@ -159,7 +166,7 @@ message Response { ResponseApplySnapshotChunk apply_snapshot_chunk = 16; ResponseGetAppHash get_app_hash = 17; ResponseGenerateFraudProof generate_fraud_proof = 18; - ResponseTriggerFraudProofGenerationMode trigger_fraud_proof_generation_mode = 19; + ResponseVerifyFraudProof verify_fraud_proof = 19; } } @@ -302,10 +309,10 @@ message ResponseGetAppHash { } message ResponseGenerateFraudProof { - FraudProof fraudProof = 1; + FraudProof fraud_proof = 1; } -message ResponseTriggerFraudProofGenerationMode { +message ResponseVerifyFraudProof { bool success = 1; } @@ -419,8 +426,12 @@ message Snapshot { // Represents a single-round fraudProof message FraudProof { int64 block_height = 1; - bytes appHash = 2; - map stateWitness = 3; + bytes app_hash = 2; + map state_witness = 3; + + RequestBeginBlock fraudulentBeginBlock = 4; + RequestDeliverTx fraudulentDeliverTx = 5; + RequestEndBlock fraudulentEndBlock = 6; } // State witness with a list of all witness data @@ -428,9 +439,9 @@ message StateWitness { // store level proof tendermint.crypto.ProofOp proof_op = 1; // substore level hash - bytes rootHash = 2; + bytes root_hash = 2; // List of witness data - repeated WitnessData witnessData = 3; + repeated WitnessData witness_data = 3; } // Witness data containing a key/value pair and a SMT proof for said key/value pair @@ -463,5 +474,5 @@ service ABCIApplication { returns (ResponseApplySnapshotChunk); rpc GetAppHash(RequestGetAppHash) returns (ResponseGetAppHash); rpc GenerateFraudProof(RequestGenerateFraudProof) returns (ResponseGenerateFraudProof); - rpc TriggerFraudProofGenerationMode(RequestTriggerFraudProofGenerationMode) returns (ResponseTriggerFraudProofGenerationMode); + rpc VerifyFraudProof(RequestVerifyFraudProof) returns (ResponseVerifyFraudProof); } diff --git a/proto/tendermint/version/types.proto b/proto/tendermint/version/types.proto new file mode 100644 index 0000000000..6061868bd4 --- /dev/null +++ b/proto/tendermint/version/types.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; +package tendermint.version; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/version"; + +import "gogoproto/gogo.proto"; + +// App includes the protocol and software version for the application. +// This information is included in ResponseInfo. The App.Protocol can be +// updated in ResponseEndBlock. +message App { + uint64 protocol = 1; + string software = 2; +} + +// Consensus captures the consensus rules for processing a block in the blockchain, +// including all blockchain data structures and the rules of the application's +// state transition machine. +message Consensus { + option (gogoproto.equal) = true; + + uint64 block = 1; + uint64 app = 2; +} diff --git a/rpc/client/client_test.go b/rpc/client/client_test.go index 10fe3217b9..b9f371cc9c 100644 --- a/rpc/client/client_test.go +++ b/rpc/client/client_test.go @@ -421,7 +421,7 @@ func TestTx(t *testing.T) { mockApp.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{}) mockApp.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{}) mockApp.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{}) - mockApp.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{}) + mockApp.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{}) err = rpc.node.Start() require.NoError(err) @@ -636,7 +636,7 @@ func TestValidatorSetHandling(t *testing.T) { app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{}) app.On("Commit", mock.Anything).Return(abci.ResponseCommit{}) app.On("GetAppHash", mock.Anything).Return(abci.ResponseGetAppHash{}) - app.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{}) + app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{}) key, _, _ := crypto.GenerateEd25519Key(crand.Reader) signingKey, _, _ := crypto.GenerateEd25519Key(crand.Reader) diff --git a/state/executor.go b/state/executor.go index af9fd33f68..59f186b704 100644 --- a/state/executor.go +++ b/state/executor.go @@ -21,6 +21,8 @@ import ( "github.com/celestiaorg/optimint/types" ) +var fraudProofsEnabled = true + // BlockExecutor creates and applies blocks and maintains state. type BlockExecutor struct { proposerAddress []byte @@ -268,18 +270,18 @@ func (e *BlockExecutor) execute(ctx context.Context, state types.State, block *t currentIsrs := block.Data.IntermediateStateRoots.RawRootsList currentIsrIndex := 0 - if currentIsrs != nil { - expectedLength := len(block.Data.Txs) + 2 - // BeginBlock + DeliverTxs + EndBlock - if len(currentIsrs) != expectedLength { - return nil, fmt.Errorf("invalid length of ISR list: %d, expected length: %d", len(currentIsrs), expectedLength) + if fraudProofsEnabled { + if currentIsrs != nil { + expectedLength := len(block.Data.Txs) + 2 + // BeginBlock + DeliverTxs + EndBlock + if len(currentIsrs) != expectedLength { + return nil, fmt.Errorf("invalid length of ISR list: %d, expected length: %d", len(currentIsrs), expectedLength) + } } } ISRs := make([][]byte, 0) - var err error - e.proxyApp.SetResponseCallback(func(req *abci.Request, res *abci.Response) { if r, ok := res.Value.(*abci.Response_DeliverTx); ok { txRes := r.DeliverTx @@ -301,80 +303,130 @@ func (e *BlockExecutor) execute(ctx context.Context, state types.State, block *t } abciHeader.ChainID = e.chainID abciHeader.ValidatorsHash = state.Validators.Hash() - abciResponses.BeginBlock, err = e.proxyApp.BeginBlockSync( - abci.RequestBeginBlock{ - Hash: hash[:], - Header: abciHeader, - LastCommitInfo: abci.LastCommitInfo{ - Round: 0, - Votes: nil, - }, - ByzantineValidators: nil, - }) - if err != nil { - return nil, err - } - isr, err := e.getAppHash() - if err != nil { - return nil, err + beginBlockRequest := abci.RequestBeginBlock{ + Hash: hash[:], + Header: abciHeader, + LastCommitInfo: abci.LastCommitInfo{ + Round: 0, + Votes: nil, + }, + ByzantineValidators: nil, } - ISRs = append(ISRs, isr) - err = e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) + abciResponses.BeginBlock, err = e.proxyApp.BeginBlockSync(beginBlockRequest) if err != nil { return nil, err } - currentIsrIndex++ - for _, tx := range block.Data.Txs { - res := e.proxyApp.DeliverTxAsync(abci.RequestDeliverTx{Tx: tx}) - if res.GetException() != nil { - return nil, errors.New(res.GetException().GetError()) - } + if fraudProofsEnabled { isr, err := e.getAppHash() if err != nil { return nil, err } ISRs = append(ISRs, isr) - err = e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) - if err != nil { - return nil, err + isFraud := e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) + if isFraud { + fraudProof, err := e.generateFraudProof(&beginBlockRequest, nil, nil) + if err != nil { + return nil, err + } + // TODO: gossip fraudProof to P2P network + // fraudTx: BeginBlock + _ = fraudProof } currentIsrIndex++ } + deliverTxRequests := make([]*abci.RequestDeliverTx, len(block.Data.Txs)) + for _, tx := range block.Data.Txs { + deliverTxRequest := abci.RequestDeliverTx{Tx: tx} + deliverTxRequests = append(deliverTxRequests, &deliverTxRequest) + res := e.proxyApp.DeliverTxAsync(deliverTxRequest) + if res.GetException() != nil { + return nil, errors.New(res.GetException().GetError()) + } - abciResponses.EndBlock, err = e.proxyApp.EndBlockSync(abci.RequestEndBlock{Height: int64(block.Header.Height)}) - if err != nil { - return nil, err - } - isr, err = e.getAppHash() - if err != nil { - return nil, err + if fraudProofsEnabled { + isr, err := e.getAppHash() + if err != nil { + return nil, err + } + ISRs = append(ISRs, isr) + isFraud := e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) + if isFraud { + fraudProof, err := e.generateFraudProof(&beginBlockRequest, deliverTxRequests, nil) + if err != nil { + return nil, err + } + // TODO: gossip fraudProof to P2P network + // fraudTx: current DeliverTx + _ = fraudProof + } + currentIsrIndex++ + } } - ISRs = append(ISRs, isr) - err = e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) + endBlockRequest := abci.RequestEndBlock{Height: int64(block.Header.Height)} + abciResponses.EndBlock, err = e.proxyApp.EndBlockSync(endBlockRequest) if err != nil { return nil, err } - if block.Data.IntermediateStateRoots.RawRootsList == nil { - // Block producer: Initial ISRs generated here - block.Data.IntermediateStateRoots.RawRootsList = ISRs + + if fraudProofsEnabled { + isr, err := e.getAppHash() + if err != nil { + return nil, err + } + ISRs = append(ISRs, isr) + isFraud := e.checkFraudProofTrigger(isr, currentIsrs, currentIsrIndex) + if isFraud { + fraudProof, err := e.generateFraudProof(&beginBlockRequest, deliverTxRequests, &endBlockRequest) + if err != nil { + return nil, err + } + // TODO: gossip fraudProof to P2P network + // fraudTx: EndBlock + _ = fraudProof + } + if block.Data.IntermediateStateRoots.RawRootsList == nil { + // Block producer: Initial ISRs generated here + block.Data.IntermediateStateRoots.RawRootsList = ISRs + } } return abciResponses, nil } -func (e *BlockExecutor) checkFraudProofTrigger(generatedIsr []byte, currentIsrs [][]byte, index int) error { +func (e *BlockExecutor) checkFraudProofTrigger(generatedIsr []byte, currentIsrs [][]byte, index int) bool { if currentIsrs != nil { stateIsr := currentIsrs[index] if !bytes.Equal(stateIsr, generatedIsr) { e.logger.Debug("ISR Mismatch", "given_isr", stateIsr, "generated_isr", generatedIsr) - _, err := e.proxyApp.TriggerFraudProofGenerationModeSync(abci.RequestTriggerFraudProofGenerationMode{}) - if err != nil { - return err - } + return true } } - return nil + return false +} + +func (e *BlockExecutor) generateFraudProof(beginBlockRequest *abci.RequestBeginBlock, deliverTxRequests []*abci.RequestDeliverTx, endBlockRequest *abci.RequestEndBlock) (*abci.FraudProof, error) { + generateFraudProofRequest := abci.RequestGenerateFraudProof{} + if beginBlockRequest == nil { + return nil, fmt.Errorf("begin block request cannot be a nil parameter") + } + generateFraudProofRequest.BeginBlockRequest = *beginBlockRequest + if deliverTxRequests != nil { + generateFraudProofRequest.DeliverTxRequests = deliverTxRequests + if endBlockRequest != nil { + generateFraudProofRequest.EndBlockRequest = endBlockRequest + } + } + resp, err := e.proxyApp.GenerateFraudProofSync(generateFraudProofRequest) + if err != nil { + return nil, err + } + if resp.FraudProof != nil { + return resp.FraudProof, nil + + } else { + return nil, fmt.Errorf("fraud proof generation failed") + } } func (e *BlockExecutor) getLastCommitHash(lastCommit *types.Commit, header *types.Header) []byte { diff --git a/state/executor_test.go b/state/executor_test.go index 9b851f6d1d..b1a9db65b8 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -80,7 +80,7 @@ func TestApplyBlock(t *testing.T) { app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{}) app.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{}) app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}) - app.On("TriggerFraudProofGenerationMode", mock.Anything).Return(abci.ResponseTriggerFraudProofGenerationMode{}) + app.On("GenerateFraudProof", mock.Anything).Return(abci.ResponseGenerateFraudProof{}) var mockAppHash [32]byte _, err := rand.Read(mockAppHash[:]) require.NoError(err)