Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(p2p): validating gossiped block is created by the proposer #737

Merged
merged 8 commits into from
May 5, 2024
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# [](https://github.com/dymensionxyz/dymint/compare/v1.1.0-rc02...v) (2024-05-03)
# [](https://github.com/dymensionxyz/dymint/compare/v1.1.0-rc02...v) (2024-05-05)


Check failure on line 3 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 2]
### Bug Fixes

Check failure on line 4 in CHANGELOG.md

GitHub Actions / markdownlint

Heading levels should only increment by one level at a time [Expected: h2; Actual: h3]

* **bug:** sync from da and p2p when starting a node ([#763](https://github.com/dymensionxyz/dymint/issues/763)) ([68ffd05](https://github.com/dymensionxyz/dymint/commit/68ffd05794949ddc42df1c132d1fde5f21b505f4))
* **celestia test:** fix race in test ([#755](https://github.com/dymensionxyz/dymint/issues/755)) ([0b36781](https://github.com/dymensionxyz/dymint/commit/0b367818bf6aa8da4a4fd8e4e5c78223b60b44e0))
@@ -13,27 +13,29 @@
* **doc:** manager cache comment ([#767](https://github.com/dymensionxyz/dymint/issues/767)) ([b88bf6e](https://github.com/dymensionxyz/dymint/commit/b88bf6e72820c944b290147724255cc8466ada50))
* **logging:** added reason for websocket closed debug msg ([#746](https://github.com/dymensionxyz/dymint/issues/746)) ([3aa7d80](https://github.com/dymensionxyz/dymint/commit/3aa7d80ace92b3b0f79e4f338f10bb94c96ab6dd))
* **logs:** make logs more readable in a couple places, fix race cond ([#749](https://github.com/dymensionxyz/dymint/issues/749)) ([f05ef39](https://github.com/dymensionxyz/dymint/commit/f05ef3957b754c05fbc90aa39eabce80bbe65933))
* **manager:** get fresh height in loop ([#781](https://github.com/dymensionxyz/dymint/issues/781)) ([e4df480](https://github.com/dymensionxyz/dymint/commit/e4df48037a78965dbac9e747dd296f39360e396c))
* **p2p:** validate block before applying and not before caching in p2p gossiping ([#723](https://github.com/dymensionxyz/dymint/issues/723)) ([98371b5](https://github.com/dymensionxyz/dymint/commit/98371b5220613e70f3274fab5593e02ba532f7db))
* **produce loop:** handle unauthenticated error in settlement layer ([#726](https://github.com/dymensionxyz/dymint/issues/726)) ([33e78d1](https://github.com/dymensionxyz/dymint/commit/33e78d116b5f14b91b8b3bda2b6cbfee9040e2d3))
* **rpc:** nil panic in rpc/json/handler.go WriteError ([#750](https://github.com/dymensionxyz/dymint/issues/750)) ([e09709b](https://github.com/dymensionxyz/dymint/commit/e09709b428a33da002defb9f13178fa19b81a69b))
* **settlement:** remove state index from proto ([#777](https://github.com/dymensionxyz/dymint/issues/777)) ([767b8fd](https://github.com/dymensionxyz/dymint/commit/767b8fdb490c37deee43ac023688410bbb98ccb0))
* **sync:** make sure we use a latest state index as a start point ([#760](https://github.com/dymensionxyz/dymint/issues/760)) ([43e2d96](https://github.com/dymensionxyz/dymint/commit/43e2d965f2b505751f8e5260549e909c976141ee))
* **sync:** removing height condition for applying cached blocks from p2p ([#787](https://github.com/dymensionxyz/dymint/issues/787)) ([b97299c](https://github.com/dymensionxyz/dymint/commit/b97299ce7f78168863c5e1c2d7fc479aed2ae6da))
* **tests:** fix unit tests, mocks, cleanup/dry hub queries ([#782](https://github.com/dymensionxyz/dymint/issues/782)) ([c276aea](https://github.com/dymensionxyz/dymint/commit/c276aea12c9cd37f62fcf9d684c4efe901a510bf))


Check failure on line 25 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 2]
### Features

* **produce:** limiting block size by maxBatchSize ([#784](https://github.com/dymensionxyz/dymint/issues/784)) ([f90042c](https://github.com/dymensionxyz/dymint/commit/f90042cd61fc6b60093478cd65491f8aa1106457))


Check failure on line 30 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 2]

Check failure on line 31 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 3]
# [1.1.0-rc02](https://github.com/dymensionxyz/dymint/compare/v1.1.0-rc01...v1.1.0-rc02) (2024-04-26)

Check failure on line 32 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple top-level headings in the same document [Context: "# [1.1.0-rc02](https://github...."]


Check failure on line 34 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 2]

Check failure on line 35 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 3]
# [1.1.0-rc01](https://github.com/dymensionxyz/dymint/compare/v1.0.1-alpha...v1.1.0-rc01) (2024-04-25)

Check failure on line 36 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple top-level headings in the same document [Context: "# [1.1.0-rc01](https://github...."]


Check failure on line 38 in CHANGELOG.md

GitHub Actions / markdownlint

Multiple consecutive blank lines [Expected: 1; Actual: 2]
### Bug Fixes

* **block production:** apply block before gossiping ([#695](https://github.com/dymensionxyz/dymint/issues/695)) ([5c496b4](https://github.com/dymensionxyz/dymint/commit/5c496b453e98bbcc67feb6df3a2d4ad340586816))
2 changes: 1 addition & 1 deletion node/node.go
Original file line number Diff line number Diff line change
@@ -202,7 +202,7 @@ func NewNode(
mpIDs := nodemempool.NewMempoolIDs()

// Set p2p client and it's validators
p2pValidator := p2p.NewValidator(logger.With("module", "p2p_validator"), pubsubServer)
p2pValidator := p2p.NewValidator(logger.With("module", "p2p_validator"), pubsubServer, settlementlc)

conf.P2P.GossipCacheSize = conf.BlockManagerConfig.GossipedBlocksCacheSize
conf.P2P.BoostrapTime = conf.BootstrapTime
60 changes: 0 additions & 60 deletions p2p/events.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package p2p

import (
"github.com/dymensionxyz/dymint/p2p/pb"
"github.com/dymensionxyz/dymint/types"
uevent "github.com/dymensionxyz/dymint/utils/event"
)

@@ -19,64 +17,6 @@ const (
EventNewGossipedBlock = "NewGossipedBlock"
)

/* -------------------------------------------------------------------------- */
/* Event Data */
/* -------------------------------------------------------------------------- */

// GossipedBlock defines the struct of the event data for the GossipedBlock
type GossipedBlock struct {
// Block is the block that was gossiped
Block types.Block
// Commit is the commit that was gossiped
Commit types.Commit
}

// MarshalBinary encodes GossipedBlock into binary form and returns it.
func (e *GossipedBlock) MarshalBinary() ([]byte, error) {
return e.ToProto().Marshal()
}

// UnmarshalBinary decodes binary form of GossipedBlock into object.
func (e *GossipedBlock) UnmarshalBinary(data []byte) error {
var pbGossipedBlock pb.GossipedBlock
err := pbGossipedBlock.Unmarshal(data)
if err != nil {
return err
}
err = e.FromProto(&pbGossipedBlock)
return err
}

// ToProto converts Data into protobuf representation and returns it.
func (e *GossipedBlock) ToProto() *pb.GossipedBlock {
return &pb.GossipedBlock{
Block: e.Block.ToProto(),
Commit: e.Commit.ToProto(),
}
}

// FromProto fills GossipedBlock with data from its protobuf representation.
func (e *GossipedBlock) FromProto(other *pb.GossipedBlock) error {
if err := e.Block.FromProto(other.Block); err != nil {
return err
}
if err := e.Commit.FromProto(other.Commit); err != nil {
return err
}
return nil
}

// Validate run basic validation on the gossiped block
func (e *GossipedBlock) Validate() error {
if err := e.Block.ValidateBasic(); err != nil {
return err
}
if err := e.Commit.ValidateBasic(); err != nil {
return err
}
return nil
}

/* -------------------------------------------------------------------------- */
/* Queries */
/* -------------------------------------------------------------------------- */
74 changes: 74 additions & 0 deletions p2p/gossiped_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package p2p

import (
"github.com/dymensionxyz/dymint/p2p/pb"
"github.com/dymensionxyz/dymint/types"
tmtypes "github.com/tendermint/tendermint/types"
)

/* -------------------------------------------------------------------------- */
/* Event Data */
/* -------------------------------------------------------------------------- */

// GossipedBlock defines the struct of the event data for the GossipedBlock
type GossipedBlock struct {
// Block is the block that was gossiped
Block types.Block
// Commit is the commit that was gossiped
Commit types.Commit
}

// MarshalBinary encodes GossipedBlock into binary form and returns it.
func (e *GossipedBlock) MarshalBinary() ([]byte, error) {
return e.ToProto().Marshal()
}

// UnmarshalBinary decodes binary form of GossipedBlock into object.
func (e *GossipedBlock) UnmarshalBinary(data []byte) error {
var pbGossipedBlock pb.GossipedBlock
err := pbGossipedBlock.Unmarshal(data)
if err != nil {
return err
}
err = e.FromProto(&pbGossipedBlock)
return err
}

// ToProto converts Data into protobuf representation and returns it.
func (e *GossipedBlock) ToProto() *pb.GossipedBlock {
return &pb.GossipedBlock{
Block: e.Block.ToProto(),
Commit: e.Commit.ToProto(),
}
}

// FromProto fills GossipedBlock with data from its protobuf representation.
func (e *GossipedBlock) FromProto(other *pb.GossipedBlock) error {
if err := e.Block.FromProto(other.Block); err != nil {
return err
}
if err := e.Commit.FromProto(other.Commit); err != nil {
return err
}
return nil
}

// Validate run basic validation on the gossiped block
func (e *GossipedBlock) Validate(proposer *types.Sequencer) error {
if err := e.Block.ValidateBasic(); err != nil {
return err
}
if err := e.Commit.ValidateBasic(); err != nil {
return err
}
if err := e.Commit.ValidateWithHeader(proposer, &e.Block.Header); err != nil {
return err
}
abciData := tmtypes.Data{
Txs: types.ToABCIBlockDataTxs(&e.Block.Data),
}
if e.Block.Header.DataHash != [32]byte(abciData.Hash()) {
return types.ErrInvalidHeaderDataHash
}
return nil
}
91 changes: 46 additions & 45 deletions p2p/pb/events.pb.go → p2p/pb/gossiped_block.pb.go
8 changes: 6 additions & 2 deletions p2p/validator.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import (

"github.com/dymensionxyz/dymint/mempool"
nodemempool "github.com/dymensionxyz/dymint/node/mempool"
"github.com/dymensionxyz/dymint/settlement"
"github.com/dymensionxyz/dymint/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/pubsub"
@@ -26,15 +27,17 @@ type IValidator interface {
type Validator struct {
logger types.Logger
localPubsubServer *pubsub.Server
slClient settlement.LayerI
}

var _ IValidator = (*Validator)(nil)

// NewValidator creates a new Validator.
func NewValidator(logger types.Logger, pusbsubServer *pubsub.Server) *Validator {
func NewValidator(logger types.Logger, pusbsubServer *pubsub.Server, slClient settlement.LayerI) *Validator {
return &Validator{
logger: logger,
localPubsubServer: pusbsubServer,
slClient: slClient,
}
}

@@ -78,10 +81,11 @@ func (v *Validator) BlockValidator() GossipValidator {
v.logger.Error("deserialize gossiped block", "error", err)
return false
}
if err := gossipedBlock.Validate(); err != nil {
if err := gossipedBlock.Validate(v.slClient.GetProposer()); err != nil {
v.logger.Error("Invalid gossiped block", "error", err)
return false
}

err := v.localPubsubServer.PublishWithEvents(context.Background(), gossipedBlock, map[string][]string{EventTypeKey: {EventNewGossipedBlock}})
if err != nil {
v.logger.Error("publishing event", "err", err)
113 changes: 110 additions & 3 deletions p2p/validator_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
package p2p_test

import (
"encoding/hex"
"testing"

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
mempoolv1 "github.com/dymensionxyz/dymint/mempool/v1"
"github.com/dymensionxyz/dymint/types"

"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/pubsub"
"github.com/tendermint/tendermint/proxy"

"github.com/tendermint/tendermint/types"
cfg "github.com/tendermint/tendermint/config"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/dymensionxyz/dymint/block"
"github.com/dymensionxyz/dymint/mempool"

tmmocks "github.com/dymensionxyz/dymint/mocks/github.com/tendermint/tendermint/abci/types"

nodemempool "github.com/dymensionxyz/dymint/node/mempool"
"github.com/dymensionxyz/dymint/p2p"
"github.com/dymensionxyz/dymint/settlement"
"github.com/dymensionxyz/dymint/settlement/registry"
)

func TestValidator_TxValidator(t *testing.T) {
@@ -72,19 +87,111 @@ func TestValidator_TxValidator(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := log.TestingLogger()
validateTx := p2p.NewValidator(logger, nil).TxValidator(tt.args.mp, nodemempool.NewMempoolIDs())
validateTx := p2p.NewValidator(logger, nil, nil).TxValidator(tt.args.mp, nodemempool.NewMempoolIDs())
valid := validateTx(txMsg)
assert.Equalf(t, tt.want, valid, "validateTx() = %v, want %v", valid, tt.want)
})
}
}

func TestValidator_BlockValidator(t *testing.T) {

//Create proposer for the block
proposerKey := ed25519.GenPrivKey()
//Create another key
attackerKey := ed25519.GenPrivKey()

tests := []struct {
name string
proposerKey *ed25519.PrivKey
valid bool
}{
{
name: "valid: block signed by proposer",
proposerKey: proposerKey,
valid: true,
}, {
name: "invalid: bad signer",
proposerKey: attackerKey,
valid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := log.TestingLogger()

//Create Block executor
app := &tmmocks.MockApplication{}

clientCreator := proxy.NewLocalClientCreator(app)
abciClient, err := clientCreator.NewABCIClient()
require.NoError(t, err)
require.NotNil(t, clientCreator)
require.NotNil(t, abciClient)
namespaceId := "0102030405060708"
mpool := mempoolv1.NewTxMempool(logger, cfg.DefaultMempoolConfig(), proxy.NewAppConnMempool(abciClient), 0)
executor, err := block.NewExecutor([]byte("test address"), namespaceId, "test", mpool, proxy.NewAppConns(clientCreator), nil, logger)
assert.NoError(t, err)

//Create state
maxBytes := uint64(100)
state := types.State{}
state.ConsensusParams.Block.MaxBytes = int64(maxBytes)
state.ConsensusParams.Block.MaxGas = 100000
state.Validators = tmtypes.NewValidatorSet(nil)

//Create empty block
block := executor.CreateBlock(1, &types.Commit{}, [32]byte{}, state, maxBytes)

//Create slclient
client := registry.GetClient(registry.Local)
pubsubServer := pubsub.NewServer()
err = pubsubServer.Start()
require.NoError(t, err)
err = client.Init(settlement.Config{ProposerPubKey: hex.EncodeToString(proposerKey.PubKey().Bytes())}, pubsubServer, log.TestingLogger())
require.NoError(t, err)

// Create commit for the block
abciHeaderPb := types.ToABCIHeaderPB(&block.Header)
abciHeaderBytes, err := abciHeaderPb.Marshal()
require.NoError(t, err)
var signature []byte
if tt.valid {
signature, err = proposerKey.Sign(abciHeaderBytes)
require.NoError(t, err)
} else {
signature, err = attackerKey.Sign(abciHeaderBytes)
require.NoError(t, err)
}
commit := &types.Commit{
Height: block.Header.Height,
HeaderHash: block.Header.Hash(),
Signatures: []types.Signature{signature},
}

//Create gossiped block
gossipedBlock := p2p.GossipedBlock{Block: *block, Commit: *commit}
gossipedBlockBytes, err := gossipedBlock.MarshalBinary()
require.NoError(t, err)
var blockMsg = &p2p.GossipMessage{
Data: gossipedBlockBytes,
From: peer.ID("from"),
}

//Check block validity
validateBlock := p2p.NewValidator(logger, pubsubServer, client).BlockValidator()
valid := validateBlock(blockMsg)
require.Equal(t, tt.valid, valid)
})
}
}

type mockMP struct {
mempool.Mempool
err error
}

func (m *mockMP) CheckTx(_ types.Tx, cb func(*abci.Response), _ mempool.TxInfo) error {
func (m *mockMP) CheckTx(_ tmtypes.Tx, cb func(*abci.Response), _ mempool.TxInfo) error {
if cb != nil {
code := abci.CodeTypeOK
if m.err != nil {
File renamed without changes.
3 changes: 3 additions & 0 deletions settlement/base.go
Original file line number Diff line number Diff line change
@@ -43,6 +43,9 @@ func (b *BaseLayerClient) Init(config Config, pubsub *pubsub.Server, logger type
apply(b)
}

//TODO(srene): For a correct validation, sequencer list would need to be updated after a sequencer list change on the Hub.
//e.g. after receiving an event from the Hub. Right now, node will need to be restarted after a sequencer change, since it is
//only getting the sequencers list during Init.
b.sequencersList, err = b.fetchSequencersList()
if err != nil {
return err
1 change: 1 addition & 0 deletions settlement/dymension/dymension.go
Original file line number Diff line number Diff line change
@@ -459,6 +459,7 @@ func (d *HubClient) convertBatchToMsgUpdateState(batch *types.Batch, daResult *d
}
blockDescriptors[index] = blockDescriptor
}

settlementBatch := &rollapptypes.MsgUpdateState{
Creator: addr,
RollappId: d.config.RollappID,
2 changes: 1 addition & 1 deletion testutil/block.go
Original file line number Diff line number Diff line change
@@ -97,7 +97,7 @@ func GetManagerWithProposerKey(conf config.BlockManagerConfig, proposerKey crypt
if err != nil {
return nil, err
}
p2pValidator := p2p.NewValidator(logger, pubsubServer)
p2pValidator := p2p.NewValidator(logger, pubsubServer, settlementlc)
p2pClient.SetTxValidator(p2pValidator.TxValidator(mp, mpIDs))
p2pClient.SetBlockValidator(p2pValidator.BlockValidator())

9 changes: 5 additions & 4 deletions types/errors.go
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@ package types
import "errors"

var (
ErrInvalidSignature = errors.New("invalid signature")
ErrNoStateFound = errors.New("no state found")
ErrSkippedEmptyBlock = errors.New("skipped empty block")
ErrInvalidBlockHeight = errors.New("invalid block height")
ErrInvalidSignature = errors.New("invalid signature")
ErrNoStateFound = errors.New("no state found")
ErrSkippedEmptyBlock = errors.New("skipped empty block")
ErrInvalidBlockHeight = errors.New("invalid block height")
ErrInvalidHeaderDataHash = errors.New("header not matching block data")
)