diff --git a/block/manager.go b/block/manager.go index 165ea0c08..811b78f95 100644 --- a/block/manager.go +++ b/block/manager.go @@ -394,8 +394,11 @@ func (m *Manager) applyBlock(ctx context.Context, block *types.Block, commit *ty return err } + // Currently we're assuming proposer is never nil as it's a pre-condition for + // dymint to start + proposer := m.settlementClient.GetProposer() // Apply the block but DONT commit - newState, responses, err := m.executor.ApplyBlock(ctx, m.lastState, block) + newState, responses, err := m.executor.ApplyBlock(ctx, m.lastState, block, commit, proposer) if err != nil { m.logger.Error("failed to ApplyBlock", "error", err) return err @@ -534,11 +537,11 @@ func (m *Manager) produceBlock(ctx context.Context) error { m.logger.Debug("block info", "num_tx", len(block.Data.Txs)) abciHeaderPb := abciconv.ToABCIHeaderPB(&block.Header) - headerBytes, err := abciHeaderPb.Marshal() + abciHeaderBytes, err := abciHeaderPb.Marshal() if err != nil { return err } - sign, err := m.proposerKey.Sign(headerBytes) + sign, err := m.proposerKey.Sign(abciHeaderBytes) if err != nil { return err } diff --git a/block/manager_test.go b/block/manager_test.go index e2cfd4561..a42c0a54b 100644 --- a/block/manager_test.go +++ b/block/manager_test.go @@ -43,7 +43,6 @@ const batchNotFoundErrorMessage = "batch not found" func TestInitialState(t *testing.T) { assert := assert.New(t) - genesis := testutil.GenerateGenesis(123) sampleState := testutil.GenerateState(1, 128) key, _, _ := crypto.GenerateEd25519Key(rand.Reader) @@ -142,7 +141,8 @@ func TestWaitUntilSynced(t *testing.T) { t.Log("Taking the manager out of sync by submitting a batch") startHeight := atomic.LoadUint64(&manager.syncTarget) + 1 endHeight := startHeight + uint64(defaultBatchSize-1)*2 - batch := testutil.GenerateBatch(startHeight, endHeight) + batch, err := testutil.GenerateBatch(startHeight, endHeight, manager.proposerKey) + require.NoError(t, err) daResult := &da.ResultSubmitBatch{ BaseResult: da.BaseResult{ DAHeight: 1, @@ -179,7 +179,8 @@ func TestPublishAfterSynced(t *testing.T) { nextBatchStartHeight := atomic.LoadUint64(&manager.syncTarget) + 1 var batch *types.Batch for i := 0; i < numBatchesToAdd; i++ { - batch = testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1)) + batch, err = testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1), manager.proposerKey) + assert.NoError(t, err) daResultSubmitBatch := manager.dalc.SubmitBatch(batch) assert.Equal(t, daResultSubmitBatch.Code, da.StatusSuccess) resultSubmitBatch := manager.settlementClient.SubmitBatch(batch, manager.dalc.GetClientType(), &daResultSubmitBatch) @@ -195,11 +196,11 @@ func TestPublishAfterSynced(t *testing.T) { go manager.ProduceBlockLoop(ctx) select { case <-ctx.Done(): - assert.Equal(t, manager.store.Height(), lastStoreHeight) + assert.Equal(t, lastStoreHeight, manager.store.Height()) } // Sync the manager - ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel = context.WithTimeout(context.Background(), time.Second*2) defer cancel() go manager.SyncTargetLoop(ctx) go manager.RetriveLoop(ctx) @@ -207,7 +208,7 @@ func TestPublishAfterSynced(t *testing.T) { select { case <-ctx.Done(): assert.Greater(t, manager.store.Height(), lastStoreHeight) - assert.Equal(t, manager.store.Height(), batch.EndHeight) + assert.Equal(t, batch.EndHeight, manager.store.Height()) } // Validate blocks are produced @@ -227,7 +228,8 @@ func TestPublishWhenSettlementLayerDisconnected(t *testing.T) { require.NotNil(t, manager) nextBatchStartHeight := atomic.LoadUint64(&manager.syncTarget) + 1 - var batch = testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1)) + batch, err := testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1), manager.proposerKey) + assert.NoError(t, err) daResultSubmitBatch := manager.dalc.SubmitBatch(batch) assert.Equal(t, daResultSubmitBatch.Code, da.StatusSuccess) resultSubmitBatch := manager.settlementClient.SubmitBatch(batch, manager.dalc.GetClientType(), &daResultSubmitBatch) @@ -247,7 +249,8 @@ func TestPublishWhenDALayerDisconnected(t *testing.T) { require.NotNil(t, manager) nextBatchStartHeight := atomic.LoadUint64(&manager.syncTarget) + 1 - var batch = testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1)) + batch, err := testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(defaultBatchSize-1), manager.proposerKey) + assert.NoError(t, err) daResultSubmitBatch := manager.dalc.SubmitBatch(batch) assert.Equal(t, daResultSubmitBatch.Code, da.StatusError) @@ -311,7 +314,9 @@ func TestProducePendingBlock(t *testing.T) { manager, err := getManager(nil, nil, 1, 1, 0, proxyApp) require.NoError(t, err) // Generate block and commit and save it to the store - block := testutil.GenerateBlocks(1, 1)[0] + blocks, err := testutil.GenerateBlocks(1, 1, manager.proposerKey) + require.NoError(t, err) + block := blocks[0] _, err = manager.store.SaveBlock(block, &block.LastCommit, nil) require.NoError(t, err) // Produce block @@ -364,16 +369,28 @@ func getManager(settlementlc settlement.LayerClient, dalc da.DataAvailabilityLay if _, err := store.UpdateState(state, nil); err != nil { return nil, err } - key, _, _ := crypto.GenerateEd25519Key(rand.Reader) conf := getManagerConfig() logger := log.TestingLogger() pubsubServer := pubsub.NewServer() pubsubServer.Start() + // Init the settlement layer mock if settlementlc == nil { settlementlc = slregistry.GetClient(slregistry.Mock) } - _ = initSettlementLayerMock(settlementlc, defaultBatchSize, uint64(state.LastBlockHeight), uint64(state.LastBlockHeight)+1, pubsubServer, logger) + //TODO(omritoptix): Change the initialization. a bit dirty. + proposerKey, proposerPubKey, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + return nil, err + } + pubKeybytes, err := proposerPubKey.Raw() + if err != nil { + return nil, err + } + err = initSettlementLayerMock(settlementlc, defaultBatchSize, pubKeybytes, pubsubServer, logger) + if err != nil { + return nil, err + } if dalc == nil { dalc = &mockda.DataAvailabilityLayerClient{} @@ -394,8 +411,8 @@ func getManager(settlementlc settlement.LayerClient, dalc da.DataAvailabilityLay mpIDs := nodemempool.NewMempoolIDs() // Init p2p client and validator - privKey, _, _ := crypto.GenerateEd25519Key(rand.Reader) - p2pClient, err := p2p.NewClient(config.P2PConfig{}, privKey, "TestChain", logger) + p2pKey, _, _ := crypto.GenerateEd25519Key(rand.Reader) + p2pClient, err := p2p.NewClient(config.P2PConfig{}, p2pKey, "TestChain", logger) if err != nil { return nil, err } @@ -407,7 +424,7 @@ func getManager(settlementlc settlement.LayerClient, dalc da.DataAvailabilityLay return nil, err } - manager, err := NewManager(key, conf, genesis, store, mp, proxyApp, dalc, settlementlc, nil, pubsubServer, p2pClient, logger) + manager, err := NewManager(proposerKey, conf, genesis, store, mp, proxyApp, dalc, settlementlc, nil, pubsubServer, p2pClient, logger) if err != nil { return nil, err } @@ -428,14 +445,23 @@ func initDALCMock(dalc da.DataAvailabilityLayerClient, daBlockTime time.Duration } // TODO(omritoptix): Possible move out to a generic testutil -func initSettlementLayerMock(settlementlc settlement.LayerClient, batchSize uint64, latestHeight uint64, batchOffsetHeight uint64, pubsubServer *pubsub.Server, logger log.Logger) error { +func initSettlementLayerMock(settlementlc settlement.LayerClient, batchSize uint64, proposerPubKey []byte, pubsubServer *pubsub.Server, logger log.Logger) error { conf := slmock.Config{ Config: &settlement.Config{ BatchSize: batchSize, }, + ProposerPubKey: proposerPubKey, } byteconf, _ := json.Marshal(conf) - return settlementlc.Init(byteconf, pubsubServer, logger) + err := settlementlc.Init(byteconf, pubsubServer, logger) + if err != nil { + return err + } + err = settlementlc.Start() + if err != nil { + return err + } + return nil } func getManagerConfig() config.BlockManagerConfig { diff --git a/rpc/client/client_test.go b/rpc/client/client_test.go index 6af79158d..a0a55fe6a 100644 --- a/rpc/client/client_test.go +++ b/rpc/client/client_test.go @@ -3,6 +3,7 @@ package client import ( "context" crand "crypto/rand" + "encoding/json" "fmt" "math/rand" "testing" @@ -28,6 +29,7 @@ import ( abciconv "github.com/dymensionxyz/dymint/conv/abci" "github.com/dymensionxyz/dymint/mocks" "github.com/dymensionxyz/dymint/node" + slmock "github.com/dymensionxyz/dymint/settlement/mock" "github.com/dymensionxyz/dymint/types" ) @@ -404,7 +406,13 @@ func TestTx(t *testing.T) { mockApp := &mocks.Application{} mockApp.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{}) key, _, _ := crypto.GenerateEd25519Key(crand.Reader) - signingKey, _, _ := crypto.GenerateEd25519Key(crand.Reader) + signingKey, proposerPubKey, err := crypto.GenerateEd25519Key(crand.Reader) + require.NoError(err) + + proposerPubKeyBytes, err := proposerPubKey.Raw() + settlementLayerConfig, err := json.Marshal(slmock.Config{ProposerPubKey: proposerPubKeyBytes}) + require.NoError(err) + node, err := node.NewNode(context.Background(), config.NodeConfig{ DALayer: "mock", SettlementLayer: "mock", @@ -412,7 +420,9 @@ func TestTx(t *testing.T) { BlockManagerConfig: config.BlockManagerConfig{ BlockTime: 200 * time.Millisecond, BatchSyncInterval: time.Second, - }}, + }, + SettlementConfig: string(settlementLayerConfig), + }, key, signingKey, proxy.NewLocalClientCreator(mockApp), &tmtypes.GenesisDoc{ChainID: "test"}, log.TestingLogger()) @@ -644,7 +654,12 @@ func TestValidatorSetHandling(t *testing.T) { app.On("Info", mock.Anything).Return(abci.ResponseInfo{LastBlockHeight: 0, LastBlockAppHash: []byte{0}}) key, _, _ := crypto.GenerateEd25519Key(crand.Reader) - signingKey, _, _ := crypto.GenerateEd25519Key(crand.Reader) + signingKey, proposerPubKey, err := crypto.GenerateEd25519Key(crand.Reader) + require.NoError(err) + + proposerPubKeyBytes, err := proposerPubKey.Raw() + settlementLayerConfig, err := json.Marshal(slmock.Config{ProposerPubKey: proposerPubKeyBytes}) + require.NoError(err) vKeys := make([]tmcrypto.PrivKey, 4) genesisValidators := make([]tmtypes.GenesisValidator, len(vKeys)) @@ -666,7 +681,15 @@ func TestValidatorSetHandling(t *testing.T) { waitCh <- nil }) - node, err := node.NewNode(context.Background(), config.NodeConfig{DALayer: "mock", SettlementLayer: "mock", Aggregator: true, BlockManagerConfig: config.BlockManagerConfig{BlockTime: 10 * time.Millisecond, BatchSyncInterval: time.Second}}, key, signingKey, proxy.NewLocalClientCreator(app), &tmtypes.GenesisDoc{ChainID: "test", Validators: genesisValidators}, log.TestingLogger()) + nodeConfig := config.NodeConfig{ + DALayer: "mock", + SettlementLayer: "mock", + Aggregator: true, + BlockManagerConfig: config.BlockManagerConfig{BlockTime: 10 * time.Millisecond, BatchSyncInterval: time.Second}, + SettlementConfig: string(settlementLayerConfig), + } + + node, err := node.NewNode(context.Background(), nodeConfig, key, signingKey, proxy.NewLocalClientCreator(app), &tmtypes.GenesisDoc{ChainID: "test", Validators: genesisValidators}, log.TestingLogger()) require.NoError(err) require.NotNil(node) diff --git a/rpc/json/service_test.go b/rpc/json/service_test.go index 3923914a5..a4a69cfb2 100644 --- a/rpc/json/service_test.go +++ b/rpc/json/service_test.go @@ -28,6 +28,7 @@ import ( "github.com/dymensionxyz/dymint/mocks" "github.com/dymensionxyz/dymint/node" "github.com/dymensionxyz/dymint/rpc/client" + slmock "github.com/dymensionxyz/dymint/settlement/mock" ) func TestHandlerMapping(t *testing.T) { @@ -289,8 +290,11 @@ func getRPC(t *testing.T) (*mocks.Application, *client.Client) { LastBlockAppHash: nil, }) key, _, _ := crypto.GenerateEd25519Key(rand.Reader) - signingKey, _, _ := crypto.GenerateEd25519Key(rand.Reader) - config := config.NodeConfig{Aggregator: true, DALayer: "mock", SettlementLayer: "mock", BlockManagerConfig: config.BlockManagerConfig{BlockTime: 1 * time.Second, BatchSyncInterval: time.Second}} + signingKey, proposerPubKey, _ := crypto.GenerateEd25519Key(rand.Reader) + proposerPubKeyBytes, err := proposerPubKey.Raw() + settlementLayerConfig, err := json.Marshal(slmock.Config{ProposerPubKey: proposerPubKeyBytes}) + require.NoError(err) + config := config.NodeConfig{Aggregator: true, DALayer: "mock", SettlementLayer: "mock", BlockManagerConfig: config.BlockManagerConfig{BlockTime: 1 * time.Second, BatchSyncInterval: time.Second}, SettlementConfig: string(settlementLayerConfig)} node, err := node.NewNode(context.Background(), config, key, signingKey, proxy.NewLocalClientCreator(app), &types.GenesisDoc{ChainID: "test"}, log.TestingLogger()) require.NoError(err) require.NotNil(node) diff --git a/rpc/json/ws_test.go b/rpc/json/ws_test.go index c5f6d48b8..c4b1da705 100644 --- a/rpc/json/ws_test.go +++ b/rpc/json/ws_test.go @@ -49,7 +49,7 @@ func TestWebSockets(t *testing.T) { `)) assert.NoError(err) - err = conn.SetReadDeadline(time.Now().Add(1 * time.Second)) + err = conn.SetReadDeadline(time.Now().Add(100 * time.Second)) assert.NoError(err) typ, msg, err := conn.ReadMessage() assert.NoError(err) diff --git a/settlement/mock/mock.go b/settlement/mock/mock.go index 6f8d12214..4bf6bc5a1 100644 --- a/settlement/mock/mock.go +++ b/settlement/mock/mock.go @@ -7,7 +7,7 @@ import ( "errors" "sync/atomic" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" rollapptypes "github.com/dymensionxyz/dymension/x/rollapp/types" "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/log" @@ -56,9 +56,10 @@ func (m *SettlementLayerClient) Init(config []byte, pubsub *pubsub.Server, logge // Config for the HubClient type Config struct { *settlement.Config - DBPath string `json:"db_path"` - RootDir string `json:"root_dir"` - store store.KVStore + DBPath string `json:"db_path"` + RootDir string `json:"root_dir"` + ProposerPubKey []byte `json:"proposer_pub_key"` + store store.KVStore } // HubClient implements The HubClient interface @@ -202,9 +203,10 @@ func (c *HubClient) GetBatchAtIndex(rollappID string, index uint64) (*settlement // GetSequencers returns a list of sequencers. Currently only returns a single sequencer func (c *HubClient) GetSequencers(rollappID string) ([]*types.Sequencer, error) { + pubKey := &ed25519.PubKey{Key: c.config.ProposerPubKey} return []*types.Sequencer{ { - PublicKey: secp256k1.GenPrivKey().PubKey(), + PublicKey: pubKey, Status: types.Proposer, }, }, nil diff --git a/settlement/settlement_test.go b/settlement/settlement_test.go index f3fe0c264..8511ea332 100644 --- a/settlement/settlement_test.go +++ b/settlement/settlement_test.go @@ -3,6 +3,7 @@ package settlement_test import ( "testing" + "github.com/libp2p/go-libp2p-core/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/pubsub" @@ -97,7 +98,10 @@ func TestSubmitAndRetrieve(t *testing.T) { for i := 0; i < numBatches; i++ { startHeight := uint64(i)*batchSize + 1 // Create the batch - batch = testutil.GenerateBatch(startHeight, uint64(startHeight+batchSize-1)) + propserKey, _, err := crypto.GenerateEd25519Key(nil) + require.NoError(err) + batch, err = testutil.GenerateBatch(startHeight, uint64(startHeight+batchSize-1), propserKey) + require.NoError(err) // Submit the batch daResult := &da.ResultSubmitBatch{ BaseResult: da.BaseResult{ diff --git a/state/executor.go b/state/executor.go index b86a0f22e..682fce370 100644 --- a/state/executor.go +++ b/state/executor.go @@ -125,12 +125,15 @@ func (e *BlockExecutor) CreateBlock(height uint64, lastCommit *types.Commit, las } // ApplyBlock validates and executes the block. -func (e *BlockExecutor) ApplyBlock(ctx context.Context, state types.State, block *types.Block) (types.State, *tmstate.ABCIResponses, error) { - err := e.validate(state, block) +func (e *BlockExecutor) ApplyBlock(ctx context.Context, state types.State, block *types.Block, commit *types.Commit, proposer *types.Sequencer) (types.State, *tmstate.ABCIResponses, error) { + err := e.validateBlock(state, block) + if err != nil { + return types.State{}, nil, err + } + err = e.validateCommit(proposer, commit, &block.Header) if err != nil { return types.State{}, nil, err } - // This makes calls to the ProxyApp resp, err := e.execute(ctx, state, block) if err != nil { @@ -253,7 +256,7 @@ func (e *BlockExecutor) commit(ctx context.Context, state *types.State, block *t return resp.Data, err } -func (e *BlockExecutor) validate(state types.State, block *types.Block) error { +func (e *BlockExecutor) validateBlock(state types.State, block *types.Block) error { err := block.ValidateBasic() if err != nil { return err @@ -279,6 +282,18 @@ func (e *BlockExecutor) validate(state types.State, block *types.Block) error { return nil } +func (e *BlockExecutor) validateCommit(proposer *types.Sequencer, commit *types.Commit, header *types.Header) error { + abciHeaderPb := abciconv.ToABCIHeaderPB(header) + abciHeaderBytes, err := abciHeaderPb.Marshal() + if err != nil { + return err + } + if err = commit.Validate(proposer, abciHeaderBytes); err != nil { + return err + } + return nil +} + func (e *BlockExecutor) execute(ctx context.Context, state types.State, block *types.Block) (*tmstate.ABCIResponses, error) { abciResponses := new(tmstate.ABCIResponses) abciResponses.DeliverTxs = make([]*abci.ResponseDeliverTx, len(block.Data.Txs)) diff --git a/state/executor_test.go b/state/executor_test.go index 619aff09a..32708a28e 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -17,6 +18,7 @@ import ( "github.com/tendermint/tendermint/proxy" tmtypes "github.com/tendermint/tendermint/types" + abciconv "github.com/dymensionxyz/dymint/conv/abci" "github.com/dymensionxyz/dymint/mempool" mempoolv1 "github.com/dymensionxyz/dymint/mempool/v1" "github.com/dymensionxyz/dymint/mocks" @@ -78,6 +80,7 @@ func TestApplyBlock(t *testing.T) { logger := log.TestingLogger() + // Mock ABCI app app := &mocks.Application{} app.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{}) app.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{}) @@ -94,6 +97,7 @@ func TestApplyBlock(t *testing.T) { LastBlockAppHash: []byte{0}, }) + // Mock ABCI client clientCreator := proxy.NewLocalClientCreator(app) abciClient, err := clientCreator.NewABCIClient() require.NoError(err) @@ -103,27 +107,32 @@ func TestApplyBlock(t *testing.T) { nsID := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} chainID := "test" + // Init mempool mpool := mempoolv1.NewTxMempool(logger, cfg.DefaultMempoolConfig(), proxy.NewAppConnMempool(abciClient), 0) eventBus := tmtypes.NewEventBus() require.NoError(eventBus.Start()) + // Mock app connections appConns := &mocks.AppConns{} appConns.On("Consensus").Return(abciClient) appConns.On("Query").Return(abciClient) executor := NewBlockExecutor([]byte("test address"), nsID, chainID, mpool, appConns, eventBus, logger) + // Subscribe to tx events txQuery, err := query.New("tm.event='Tx'") require.NoError(err) txSub, err := eventBus.Subscribe(context.Background(), "test", txQuery, 1000) require.NoError(err) require.NotNil(txSub) + // Subscribe to block header events headerQuery, err := query.New("tm.event='NewBlockHeader'") require.NoError(err) headerSub, err := eventBus.Subscribe(context.Background(), "test", headerQuery, 100) require.NoError(err) require.NotNil(headerSub) + // Init state state := types.State{ NextValidators: tmtypes.NewValidatorSet(nil), Validators: tmtypes.NewValidatorSet(nil), @@ -134,14 +143,33 @@ func TestApplyBlock(t *testing.T) { state.ConsensusParams.Block.MaxBytes = 100 state.ConsensusParams.Block.MaxGas = 100000 + // Create first block with one Tx from mempool _ = mpool.CheckTx([]byte{1, 2, 3, 4}, func(r *abci.Response) {}, mempool.TxInfo{}) require.NoError(err) - block := executor.CreateBlock(1, &types.Commit{}, [32]byte{}, state) + block := executor.CreateBlock(1, &types.Commit{Height: 0}, [32]byte{}, state) require.NotNil(block) assert.Equal(uint64(1), block.Header.Height) assert.Len(block.Data.Txs, 1) - newState, resp, err := executor.ApplyBlock(context.Background(), state, block) + // Create proposer for the block + proposerKey := ed25519.GenPrivKey() + proposer := &types.Sequencer{ + PublicKey: proposerKey.PubKey(), + } + // Create commit for the block + abciHeaderPb := abciconv.ToABCIHeaderPB(&block.Header) + abciHeaderBytes, err := abciHeaderPb.Marshal() + require.NoError(err) + signature, err := proposerKey.Sign(abciHeaderBytes) + require.NoError(err) + commit := &types.Commit{ + Height: block.Header.Height, + HeaderHash: block.Header.Hash(), + Signatures: []types.Signature{signature}, + } + + // Apply the block + newState, resp, err := executor.ApplyBlock(context.Background(), state, block, commit, proposer) require.NoError(err) require.NotNil(newState) require.NotNil(resp) @@ -150,16 +178,46 @@ func TestApplyBlock(t *testing.T) { require.NoError(err) assert.Equal(mockAppHash, newState.AppHash) + // Create another block with multiple Tx from mempool require.NoError(mpool.CheckTx([]byte{0, 1, 2, 3, 4}, func(r *abci.Response) {}, mempool.TxInfo{})) require.NoError(mpool.CheckTx([]byte{5, 6, 7, 8, 9}, func(r *abci.Response) {}, mempool.TxInfo{})) require.NoError(mpool.CheckTx([]byte{1, 2, 3, 4, 5}, func(r *abci.Response) {}, mempool.TxInfo{})) require.NoError(mpool.CheckTx(make([]byte, 90), func(r *abci.Response) {}, mempool.TxInfo{})) - block = executor.CreateBlock(2, &types.Commit{}, [32]byte{}, newState) + block = executor.CreateBlock(2, commit, [32]byte{}, newState) require.NotNil(block) assert.Equal(uint64(2), block.Header.Height) assert.Len(block.Data.Txs, 3) - newState, resp, err = executor.ApplyBlock(context.Background(), newState, block) + // Get the header bytes + abciHeaderPb = abciconv.ToABCIHeaderPB(&block.Header) + abciHeaderBytes, err = abciHeaderPb.Marshal() + require.NoError(err) + + // Create invalid commit for the block with wrong signature + invalidProposerKey := ed25519.GenPrivKey() + invalidSignature, err := invalidProposerKey.Sign(abciHeaderBytes) + require.NoError(err) + invalidCommit := &types.Commit{ + Height: block.Header.Height, + HeaderHash: block.Header.Hash(), + Signatures: []types.Signature{invalidSignature}, + } + + // Apply the block with an invalid commit + _, _, err = executor.ApplyBlock(context.Background(), newState, block, invalidCommit, proposer) + require.Error(types.ErrInvalidSignature) + + // Create a valid commit for the block + signature, err = proposerKey.Sign(abciHeaderBytes) + require.NoError(err) + commit = &types.Commit{ + Height: block.Header.Height, + HeaderHash: block.Header.Hash(), + Signatures: []types.Signature{signature}, + } + + // Apply the block + newState, resp, err = executor.ApplyBlock(context.Background(), newState, block, commit, proposer) require.NoError(err) require.NotNil(newState) require.NotNil(resp) diff --git a/testutil/types.go b/testutil/types.go index 586015e1c..6c158f1e6 100644 --- a/testutil/types.go +++ b/testutil/types.go @@ -4,6 +4,9 @@ import ( "crypto/rand" "time" + "github.com/libp2p/go-libp2p-core/crypto" + + abciconv "github.com/dymensionxyz/dymint/conv/abci" "github.com/dymensionxyz/dymint/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" @@ -36,7 +39,7 @@ func createRandomHashes() [][32]byte { } // GenerateBlocks generates random blocks. -func GenerateBlocks(startHeight uint64, num uint64) []*types.Block { +func GenerateBlocks(startHeight uint64, num uint64, proposerKey crypto.PrivKey) ([]*types.Block, error) { blocks := make([]*types.Block, num) for i := uint64(0); i < num; i++ { h := createRandomHashes() @@ -67,38 +70,66 @@ func GenerateBlocks(startHeight uint64, num uint64) []*types.Block { LastCommit: types.Commit{ Height: 8, HeaderHash: h[7], - Signatures: []types.Signature{types.Signature([]byte{1, 1, 1}), types.Signature([]byte{2, 2, 2})}, + Signatures: []types.Signature{}, }, } + signature, err := generateSignature(proposerKey, &block.Header) + if err != nil { + return nil, err + } + block.LastCommit.Signatures = []types.Signature{signature} blocks[i] = block } - return blocks + return blocks, nil } // GenerateCommits generates commits based on passed blocks. -func GenerateCommits(blocks []*types.Block) []*types.Commit { +func GenerateCommits(blocks []*types.Block, proposerKey crypto.PrivKey) ([]*types.Commit, error) { commits := make([]*types.Commit, len(blocks)) for i, block := range blocks { + signature, err := generateSignature(proposerKey, &block.Header) + if err != nil { + return nil, err + } commits[i] = &types.Commit{ Height: block.Header.Height, HeaderHash: block.Header.Hash(), - Signatures: []types.Signature{types.Signature([]byte{1, 1, 1}), types.Signature([]byte{2, 2, 2})}, + Signatures: []types.Signature{signature}, } } - return commits + return commits, nil +} + +func generateSignature(proposerKey crypto.PrivKey, header *types.Header) ([]byte, error) { + abciHeaderPb := abciconv.ToABCIHeaderPB(header) + abciHeaderBytes, err := abciHeaderPb.Marshal() + if err != nil { + return nil, err + } + sign, err := proposerKey.Sign(abciHeaderBytes) + if err != nil { + return nil, err + } + return sign, nil } // GenerateBatch generates a batch out of random blocks -func GenerateBatch(startHeight uint64, endHeight uint64) *types.Batch { - blocks := GenerateBlocks(startHeight, endHeight-startHeight+1) - commits := GenerateCommits(blocks) +func GenerateBatch(startHeight uint64, endHeight uint64, proposerKey crypto.PrivKey) (*types.Batch, error) { + blocks, err := GenerateBlocks(startHeight, endHeight-startHeight+1, proposerKey) + if err != nil { + return nil, err + } + commits, err := GenerateCommits(blocks, proposerKey) + if err != nil { + return nil, err + } batch := &types.Batch{ StartHeight: startHeight, EndHeight: endHeight, Blocks: blocks, Commits: commits, } - return batch + return batch, nil } // GenerateRandomValidatorSet generates random validator sets diff --git a/types/block.go b/types/block.go index b2e30a41f..a3654dbe7 100644 --- a/types/block.go +++ b/types/block.go @@ -81,7 +81,8 @@ type EvidenceData struct { type Commit struct { Height uint64 HeaderHash [32]byte - Signatures []Signature // most of the time this is a single signature + // TODO(omritoptix): Change from []Signature to Signature as it should be one signature per block + Signatures []Signature } // Signature represents signature of block creator. diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 000000000..0a3a8fd93 --- /dev/null +++ b/types/errors.go @@ -0,0 +1,8 @@ +package types + +import "errors" + +var ( + // ErrInvalidSignature is returned when a signature is invalid. + ErrInvalidSignature = errors.New("invalid signature") +) diff --git a/types/validation.go b/types/validation.go index b55abfd83..91dab1901 100644 --- a/types/validation.go +++ b/types/validation.go @@ -1,6 +1,10 @@ package types -import "errors" +import ( + "errors" + + tmtypes "github.com/tendermint/tendermint/types" +) // ValidateBasic performs basic validation of a block. func (b *Block) ValidateBasic() error { @@ -40,9 +44,23 @@ func (d *Data) ValidateBasic() error { // ValidateBasic performs basic validation of a commit. func (c *Commit) ValidateBasic() error { if c.Height > 0 { - if len(c.Signatures) == 0 { - return errors.New("no signatures") + if len(c.Signatures) != 1 { + return errors.New("there should be 1 signature") + } + if len(c.Signatures[0]) > tmtypes.MaxSignatureSize { + return errors.New("signature is too big") } } return nil } + +// Validate performs full validation of a commit. +func (c *Commit) Validate(proposer *Sequencer, msg []byte) error { + if err := c.ValidateBasic(); err != nil { + return err + } + if !proposer.PublicKey.VerifySignature(msg, c.Signatures[0]) { + return ErrInvalidSignature + } + return nil +}