From 5a8dfed8342ffd4475f1a5ea07423419f3b355d4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 19 Feb 2024 21:14:08 +0300 Subject: [PATCH] dbft: move payloads/block/tx/crypto interfaces to dbft package Close #90. Signed-off-by: Anna Shaleva --- README.md | 34 ++--- block.go | 33 ++++ block/block.go | 45 +----- block/block_test.go | 3 +- change_view.go | 22 +++ ...ngeview_reason.go => change_view_reason.go | 2 +- ..._string.go => change_view_reason_string.go | 2 +- check.go | 5 +- commit.go | 11 ++ config.go | 123 ++++++++------- consensus_message.go | 32 ++++ consensus_message_type.go | 36 +++++ consensus_payload.go | 20 +++ context.go | 50 +++--- crypto/crypto.go | 45 +----- crypto/ecdsa.go | 7 +- dbft.go | 57 ++++--- dbft_test.go | 142 +++++++++--------- helpers.go | 33 ++-- helpers_test.go | 6 +- identity.go | 44 ++++++ payload/change_view.go | 30 +--- payload/commit.go | 13 +- payload/consensus_message.go | 105 +++---------- payload/constructors.go | 19 ++- payload/message.go | 22 +-- payload/message_test.go | 31 ++-- payload/prepare_request.go | 27 +--- payload/prepare_response.go | 12 +- payload/recovery_message.go | 59 +++----- payload/recovery_request.go | 11 +- prepare_request.go | 25 +++ prepare_response.go | 10 ++ recovery_message.go | 21 +++ recovery_request.go | 9 ++ send.go | 33 ++-- simulation/main.go | 52 ++++--- block/transaction.go => transaction.go | 8 +- 38 files changed, 628 insertions(+), 611 deletions(-) create mode 100644 block.go create mode 100644 change_view.go rename payload/changeview_reason.go => change_view_reason.go (97%) rename payload/changeviewreason_string.go => change_view_reason_string.go (98%) create mode 100644 commit.go create mode 100644 consensus_message.go create mode 100644 consensus_message_type.go create mode 100644 consensus_payload.go create mode 100644 identity.go create mode 100644 prepare_request.go create mode 100644 prepare_response.go create mode 100644 recovery_message.go create mode 100644 recovery_request.go rename block/transaction.go => transaction.go (63%) diff --git a/README.md b/README.md index 3142ab9f..7c88a051 100644 --- a/README.md +++ b/README.md @@ -7,30 +7,28 @@ This repo contains Go implementation of the dBFT 2.0 consensus algorithm and its written in [TLA⁺](https://lamport.azurewebsites.net/tla/tla.html) language. ## Design and structure -1. All control flow is done in main package. Most of the code which communicates with external +1. All control flow is done in main `dbft` package. Most of the code which communicates with external world (event time events) is hidden behind interfaces, callbacks and generic parameters. As a consequence it is highly flexible and extendable. Description of config options can be found in `config.go`. -2. `crypto` package contains `PrivateKey`/`PublicKey` interfaces which permits usage of one's own -cryptography for signing blocks on `Commit` stage. -Default implementation with ECDSA signatures is provided, BLS multisignatures could be added -in the nearest future. -3. `crypto` package contains `Hash`/`Address` interfaces which permits usage of one's own +2. `dbft` package contains `PrivateKey`/`PublicKey` interfaces which permits usage of one's own +cryptography for signing blocks on `Commit` stage. Refer to `identity.go` for `PrivateKey`/`PublicKey` +description. No default implementation is provided. +3. `dbft` package contains `Hash`/`Address` interfaces which permits usage of one's own hash/address implementation without additional overhead on conversions. Instantiate dBFT with custom hash/address implementation that matches requirements specified in the corresponding -documentation. -3. `block` package contains `Block` and `Transaction` abstractions. -Every block must be able to be signed and verified as well as -implement setters and getters for main fields. Minimal default implementation is provided. -`Transaction` is an entity which can be hashed. Two transactions having equal hashes are considered -equal. -4. `payload` contains interfaces for payloads and minimal implementations. Note that -default implementations do not contain any signatures, so you must wrap them or implement your -own payloads in order to sign and verify messages. -5. `timer` contains default time provider. It should make it easier to write tests +documentation. Refer to `identity.go` for `Hash`/`Address` description. No default implementation is +provided. +4. `dbft` package contains `Block` and `Transaction` abstractions located at the `block.go` and +`transaction.go` files. Every block must be able to be signed and verified as well as implement setters +and getters for main fields. `Transaction` is an entity which can be hashed. Two transactions having +equal hashes are considered equal. No default implementation is provided. +5. `dbft` contains interfaces for payloads. No default implementation is +provided. +6. `timer` contains default time provider. It should make it easier to write tests concerning dBFT's time depending behaviour. -6. `simulation` contains an example of dBFT's usage with 6-node consensus. -7. `formal-models` contains the set of dBFT's models written in [TLA⁺](https://lamport.azurewebsites.net/tla/tla.html) +7. `simulation` contains an example of dBFT's usage with 6-node consensus. +8. `formal-models` contains the set of dBFT's models written in [TLA⁺](https://lamport.azurewebsites.net/tla/tla.html) language and instructions on how to run and check them. Please, refer to the [README](./formal-models/README.md) for more details. diff --git a/block.go b/block.go new file mode 100644 index 00000000..c849fc89 --- /dev/null +++ b/block.go @@ -0,0 +1,33 @@ +package dbft + +// Block is a generic interface for a block used by dbft. +type Block[H Hash, A Address] interface { + // Hash returns block hash. + Hash() H + + Version() uint32 + // PrevHash returns previous block hash. + PrevHash() H + // MerkleRoot returns a merkle root of the transaction hashes. + MerkleRoot() H + // Timestamp returns block's proposal timestamp. + Timestamp() uint64 + // Index returns block index. + Index() uint32 + // ConsensusData is a random nonce. + ConsensusData() uint64 + // NextConsensus returns hash of the validators of the next block. + NextConsensus() A + + // Signature returns block's signature. + Signature() []byte + // Sign signs block and sets it's signature. + Sign(key PrivateKey) error + // Verify checks if signature is correct. + Verify(key PublicKey, sign []byte) error + + // Transactions returns block's transaction list. + Transactions() []Transaction[H] + // SetTransactions sets block's transaction list. + SetTransactions([]Transaction[H]) +} diff --git a/block/block.go b/block/block.go index f041b12a..d52fbac0 100644 --- a/block/block.go +++ b/block/block.go @@ -3,6 +3,7 @@ package block import ( "bytes" "encoding/gob" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/merkle" @@ -21,43 +22,11 @@ type ( NextConsensus crypto.Uint160 } - // Block is a generic interface for a block used by dbft. - Block[H crypto.Hash, A crypto.Address] interface { - // Hash returns block hash. - Hash() H - - Version() uint32 - // PrevHash returns previous block hash. - PrevHash() H - // MerkleRoot returns a merkle root of the transaction hashes. - MerkleRoot() H - // Timestamp returns block's proposal timestamp. - Timestamp() uint64 - // Index returns block index. - Index() uint32 - // ConsensusData is a random nonce. - ConsensusData() uint64 - // NextConsensus returns hash of the validators of the next block. - NextConsensus() A - - // Signature returns block's signature. - Signature() []byte - // Sign signs block and sets it's signature. - Sign(key crypto.PrivateKey) error - // Verify checks if signature is correct. - Verify(key crypto.PublicKey, sign []byte) error - - // Transactions returns block's transaction list. - Transactions() []Transaction[H] - // SetTransactions sets block's transaction list. - SetTransactions([]Transaction[H]) - } - neoBlock struct { base consensusData uint64 - transactions []Transaction[crypto.Uint256] + transactions []dbft.Transaction[crypto.Uint256] signature []byte hash *crypto.Uint256 } @@ -99,17 +68,17 @@ func (b *neoBlock) ConsensusData() uint64 { } // Transactions implements Block interface. -func (b *neoBlock) Transactions() []Transaction[crypto.Uint256] { +func (b *neoBlock) Transactions() []dbft.Transaction[crypto.Uint256] { return b.transactions } // SetTransactions implements Block interface. -func (b *neoBlock) SetTransactions(txx []Transaction[crypto.Uint256]) { +func (b *neoBlock) SetTransactions(txx []dbft.Transaction[crypto.Uint256]) { b.transactions = txx } // NewBlock returns new block. -func NewBlock(timestamp uint64, index uint32, nextConsensus crypto.Uint160, prevHash crypto.Uint256, version uint32, nonce uint64, txHashes []crypto.Uint256) Block[crypto.Uint256, crypto.Uint160] { +func NewBlock(timestamp uint64, index uint32, nextConsensus crypto.Uint160, prevHash crypto.Uint256, version uint32, nonce uint64, txHashes []crypto.Uint256) dbft.Block[crypto.Uint256, crypto.Uint160] { block := new(neoBlock) block.base.Timestamp = uint32(timestamp / 1000000000) block.base.Index = index @@ -144,7 +113,7 @@ func (b *neoBlock) GetHashData() []byte { } // Sign implements Block interface. -func (b *neoBlock) Sign(key crypto.PrivateKey) error { +func (b *neoBlock) Sign(key dbft.PrivateKey) error { data := b.GetHashData() sign, err := key.Sign(data) @@ -158,7 +127,7 @@ func (b *neoBlock) Sign(key crypto.PrivateKey) error { } // Verify implements Block interface. -func (b *neoBlock) Verify(pub crypto.PublicKey, sign []byte) error { +func (b *neoBlock) Verify(pub dbft.PublicKey, sign []byte) error { data := b.GetHashData() return pub.Verify(data, sign) } diff --git a/block/block_test.go b/block/block_test.go index 35cb5461..6e42c1ff 100644 --- a/block/block_test.go +++ b/block/block_test.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "encoding/gob" "errors" + "github.com/nspcc-dev/dbft" "testing" "github.com/nspcc-dev/dbft/crypto" @@ -18,7 +19,7 @@ func TestNeoBlock_Setters(t *testing.T) { require.Equal(t, crypto.Uint256{}, b.Hash()) - txs := []Transaction[crypto.Uint256]{testTx(1), testTx(2)} + txs := []dbft.Transaction[crypto.Uint256]{testTx(1), testTx(2)} b.SetTransactions(txs) assert.Equal(t, txs, b.Transactions()) diff --git a/change_view.go b/change_view.go new file mode 100644 index 00000000..93067f7c --- /dev/null +++ b/change_view.go @@ -0,0 +1,22 @@ +package dbft + +// ChangeView represents dBFT ChangeView message. +type ChangeView interface { + // NewViewNumber returns proposed view number. + NewViewNumber() byte + + // SetNewViewNumber sets the proposed view number. + SetNewViewNumber(view byte) + + // Timestamp returns message's timestamp. + Timestamp() uint64 + + // SetTimestamp sets message's timestamp. + SetTimestamp(ts uint64) + + // Reason returns change view reason. + Reason() ChangeViewReason + + // SetReason sets change view reason. + SetReason(reason ChangeViewReason) +} diff --git a/payload/changeview_reason.go b/change_view_reason.go similarity index 97% rename from payload/changeview_reason.go rename to change_view_reason.go index d87424c8..4895420c 100644 --- a/payload/changeview_reason.go +++ b/change_view_reason.go @@ -1,4 +1,4 @@ -package payload +package dbft //go:generate stringer -type=ChangeViewReason -linecomment diff --git a/payload/changeviewreason_string.go b/change_view_reason_string.go similarity index 98% rename from payload/changeviewreason_string.go rename to change_view_reason_string.go index a8b6d16b..0921ea7a 100644 --- a/payload/changeviewreason_string.go +++ b/change_view_reason_string.go @@ -1,6 +1,6 @@ // Code generated by "stringer -type=ChangeViewReason -linecomment"; DO NOT EDIT. -package payload +package dbft import "strconv" diff --git a/check.go b/check.go index 11822fd3..a3a3d72d 100644 --- a/check.go +++ b/check.go @@ -1,7 +1,6 @@ package dbft import ( - "github.com/nspcc-dev/dbft/payload" "go.uber.org/zap" ) @@ -20,7 +19,7 @@ func (d *DBFT[H, A]) checkPrepare() { count++ } - if msg.Type() == payload.PrepareRequestType { + if msg.Type() == PrepareRequestType { hasRequest = true } } @@ -98,7 +97,7 @@ func (d *DBFT[H, A]) checkChangeView(view byte) { if !d.Context.WatchOnly() { msg := d.ChangeViewPayloads[d.MyIndex] if msg != nil && msg.GetChangeView().NewViewNumber() < view { - d.broadcast(d.makeChangeView(uint64(d.Timer.Now().UnixNano()), payload.CVChangeAgreement)) + d.broadcast(d.makeChangeView(uint64(d.Timer.Now().UnixNano()), CVChangeAgreement)) } } diff --git a/commit.go b/commit.go new file mode 100644 index 00000000..7b5a38c6 --- /dev/null +++ b/commit.go @@ -0,0 +1,11 @@ +package dbft + +// Commit is an interface for dBFT Commit message. +type Commit interface { + // Signature returns commit's signature field + // which is a block signature for the current epoch. + Signature() []byte + + // SetSignature sets commit's signature. + SetSignature(signature []byte) +} diff --git a/config.go b/config.go index 13399da2..8bad7283 100644 --- a/config.go +++ b/config.go @@ -5,15 +5,12 @@ import ( "errors" "time" - "github.com/nspcc-dev/dbft/block" - "github.com/nspcc-dev/dbft/crypto" - "github.com/nspcc-dev/dbft/payload" "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) // Config contains initialization and working parameters for dBFT. -type Config[H crypto.Hash, A crypto.Address] struct { +type Config[H Hash, A Address] struct { // Logger Logger *zap.Logger // Timer @@ -27,9 +24,9 @@ type Config[H crypto.Hash, A crypto.Address] struct { TimestampIncrement uint64 // GetKeyPair returns an index of the node in the list of validators // together with it's key pair. - GetKeyPair func([]crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) + GetKeyPair func([]PublicKey) (int, PrivateKey, PublicKey) // NewBlockFromContext should allocate, fill from Context and return new block.Block. - NewBlockFromContext func(ctx *Context[H, A]) block.Block[H, A] + NewBlockFromContext func(ctx *Context[H, A]) Block[H, A] // RequestTx is a callback which is called when transaction contained // in current block can't be found in memory pool. RequestTx func(h ...H) @@ -37,18 +34,18 @@ type Config[H crypto.Hash, A crypto.Address] struct { // any transactions. StopTxFlow func() // GetTx returns a transaction from memory pool. - GetTx func(h H) block.Transaction[H] + GetTx func(h H) Transaction[H] // GetVerified returns a slice of verified transactions // to be proposed in a new block. - GetVerified func() []block.Transaction[H] + GetVerified func() []Transaction[H] // VerifyBlock verifies if block is valid. - VerifyBlock func(b block.Block[H, A]) bool + VerifyBlock func(b Block[H, A]) bool // Broadcast should broadcast payload m to the consensus nodes. - Broadcast func(m payload.ConsensusPayload[H, A]) + Broadcast func(m ConsensusPayload[H, A]) // ProcessBlock is called every time new block is accepted. - ProcessBlock func(b block.Block[H, A]) + ProcessBlock func(b Block[H, A]) // GetBlock should return block with hash. - GetBlock func(h H) block.Block[H, A] + GetBlock func(h H) Block[H, A] // WatchOnly tells if a node should only watch. WatchOnly func() bool // CurrentHeight returns index of the last accepted block. @@ -59,34 +56,34 @@ type Config[H crypto.Hash, A crypto.Address] struct { // When called with a transaction list it must return // list of the validators of the next block. // If this function ever returns 0-length slice, dbft will panic. - GetValidators func(...block.Transaction[H]) []crypto.PublicKey + GetValidators func(...Transaction[H]) []PublicKey // GetConsensusAddress returns hash of the validator list. - GetConsensusAddress func(...crypto.PublicKey) A + GetConsensusAddress func(...PublicKey) A // NewConsensusPayload is a constructor for payload.ConsensusPayload. - NewConsensusPayload func(*Context[H, A], payload.MessageType, any) payload.ConsensusPayload[H, A] + NewConsensusPayload func(*Context[H, A], MessageType, any) ConsensusPayload[H, A] // NewPrepareRequest is a constructor for payload.PrepareRequest. - NewPrepareRequest func() payload.PrepareRequest[H, A] + NewPrepareRequest func() PrepareRequest[H, A] // NewPrepareResponse is a constructor for payload.PrepareResponse. - NewPrepareResponse func() payload.PrepareResponse[H] + NewPrepareResponse func() PrepareResponse[H] // NewChangeView is a constructor for payload.ChangeView. - NewChangeView func() payload.ChangeView + NewChangeView func() ChangeView // NewCommit is a constructor for payload.Commit. - NewCommit func() payload.Commit + NewCommit func() Commit // NewRecoveryRequest is a constructor for payload.RecoveryRequest. - NewRecoveryRequest func() payload.RecoveryRequest + NewRecoveryRequest func() RecoveryRequest // NewRecoveryMessage is a constructor for payload.RecoveryMessage. - NewRecoveryMessage func() payload.RecoveryMessage[H, A] + NewRecoveryMessage func() RecoveryMessage[H, A] // VerifyPrepareRequest can perform external payload verification and returns true iff it was successful. - VerifyPrepareRequest func(p payload.ConsensusPayload[H, A]) error + VerifyPrepareRequest func(p ConsensusPayload[H, A]) error // VerifyPrepareResponse performs external PrepareResponse verification and returns nil if it's successful. - VerifyPrepareResponse func(p payload.ConsensusPayload[H, A]) error + VerifyPrepareResponse func(p ConsensusPayload[H, A]) error } const defaultSecondsPerBlock = time.Second * 15 const defaultTimestampIncrement = uint64(time.Millisecond / time.Nanosecond) -func defaultConfig[H crypto.Hash, A crypto.Address]() *Config[H, A] { +func defaultConfig[H Hash, A Address]() *Config[H, A] { // fields which are set to nil must be provided from client return &Config[H, A]{ Logger: zap.NewNop(), @@ -96,23 +93,23 @@ func defaultConfig[H crypto.Hash, A crypto.Address]() *Config[H, A] { GetKeyPair: nil, RequestTx: func(...H) {}, StopTxFlow: func() {}, - GetTx: func(H) block.Transaction[H] { return nil }, - GetVerified: func() []block.Transaction[H] { return make([]block.Transaction[H], 0) }, - VerifyBlock: func(block.Block[H, A]) bool { return true }, - Broadcast: func(payload.ConsensusPayload[H, A]) {}, - ProcessBlock: func(block.Block[H, A]) {}, - GetBlock: func(H) block.Block[H, A] { return nil }, + GetTx: func(H) Transaction[H] { return nil }, + GetVerified: func() []Transaction[H] { return make([]Transaction[H], 0) }, + VerifyBlock: func(Block[H, A]) bool { return true }, + Broadcast: func(ConsensusPayload[H, A]) {}, + ProcessBlock: func(Block[H, A]) {}, + GetBlock: func(H) Block[H, A] { return nil }, WatchOnly: func() bool { return false }, CurrentHeight: nil, CurrentBlockHash: nil, GetValidators: nil, - VerifyPrepareRequest: func(payload.ConsensusPayload[H, A]) error { return nil }, - VerifyPrepareResponse: func(payload.ConsensusPayload[H, A]) error { return nil }, + VerifyPrepareRequest: func(ConsensusPayload[H, A]) error { return nil }, + VerifyPrepareResponse: func(ConsensusPayload[H, A]) error { return nil }, } } -func checkConfig[H crypto.Hash, A crypto.Address](cfg *Config[H, A]) error { +func checkConfig[H Hash, A Address](cfg *Config[H, A]) error { if cfg.GetKeyPair == nil { return errors.New("private key is nil") } else if cfg.CurrentHeight == nil { @@ -146,14 +143,14 @@ func checkConfig[H crypto.Hash, A crypto.Address](cfg *Config[H, A]) error { // WithKeyPair sets GetKeyPair to a function returning default key pair // if it is present in a list of validators. -func WithKeyPair[H crypto.Hash, A crypto.Address](priv crypto.PrivateKey, pub crypto.PublicKey) func(config *Config[H, A]) { +func WithKeyPair[H Hash, A Address](priv PrivateKey, pub PublicKey) func(config *Config[H, A]) { myPub, err := pub.MarshalBinary() if err != nil { return nil } return func(cfg *Config[H, A]) { - cfg.GetKeyPair = func(ps []crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey) { + cfg.GetKeyPair = func(ps []PublicKey) (int, PrivateKey, PublicKey) { for i := range ps { pi, err := ps[i].MarshalBinary() if err != nil { @@ -169,196 +166,196 @@ func WithKeyPair[H crypto.Hash, A crypto.Address](priv crypto.PrivateKey, pub cr } // WithGetKeyPair sets GetKeyPair. -func WithGetKeyPair[H crypto.Hash, A crypto.Address](f func([]crypto.PublicKey) (int, crypto.PrivateKey, crypto.PublicKey)) func(config *Config[H, A]) { +func WithGetKeyPair[H Hash, A Address](f func([]PublicKey) (int, PrivateKey, PublicKey)) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetKeyPair = f } } // WithLogger sets Logger. -func WithLogger[H crypto.Hash, A crypto.Address](log *zap.Logger) func(config *Config[H, A]) { +func WithLogger[H Hash, A Address](log *zap.Logger) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.Logger = log } } // WithTimer sets Timer. -func WithTimer[H crypto.Hash, A crypto.Address](t timer.Timer) func(config *Config[H, A]) { +func WithTimer[H Hash, A Address](t timer.Timer) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.Timer = t } } // WithSecondsPerBlock sets SecondsPerBlock. -func WithSecondsPerBlock[H crypto.Hash, A crypto.Address](d time.Duration) func(config *Config[H, A]) { +func WithSecondsPerBlock[H Hash, A Address](d time.Duration) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.SecondsPerBlock = d } } // WithTimestampIncrement sets TimestampIncrement. -func WithTimestampIncrement[H crypto.Hash, A crypto.Address](u uint64) func(config *Config[H, A]) { +func WithTimestampIncrement[H Hash, A Address](u uint64) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.TimestampIncrement = u } } // WithNewBlockFromContext sets NewBlockFromContext. -func WithNewBlockFromContext[H crypto.Hash, A crypto.Address](f func(ctx *Context[H, A]) block.Block[H, A]) func(config *Config[H, A]) { +func WithNewBlockFromContext[H Hash, A Address](f func(ctx *Context[H, A]) Block[H, A]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewBlockFromContext = f } } // WithRequestTx sets RequestTx. -func WithRequestTx[H crypto.Hash, A crypto.Address](f func(h ...H)) func(config *Config[H, A]) { +func WithRequestTx[H Hash, A Address](f func(h ...H)) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.RequestTx = f } } // WithStopTxFlow sets StopTxFlow. -func WithStopTxFlow[H crypto.Hash, A crypto.Address](f func()) func(config *Config[H, A]) { +func WithStopTxFlow[H Hash, A Address](f func()) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.StopTxFlow = f } } // WithGetTx sets GetTx. -func WithGetTx[H crypto.Hash, A crypto.Address](f func(h H) block.Transaction[H]) func(config *Config[H, A]) { +func WithGetTx[H Hash, A Address](f func(h H) Transaction[H]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetTx = f } } // WithGetVerified sets GetVerified. -func WithGetVerified[H crypto.Hash, A crypto.Address](f func() []block.Transaction[H]) func(config *Config[H, A]) { +func WithGetVerified[H Hash, A Address](f func() []Transaction[H]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetVerified = f } } // WithVerifyBlock sets VerifyBlock. -func WithVerifyBlock[H crypto.Hash, A crypto.Address](f func(b block.Block[H, A]) bool) func(config *Config[H, A]) { +func WithVerifyBlock[H Hash, A Address](f func(b Block[H, A]) bool) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.VerifyBlock = f } } // WithBroadcast sets Broadcast. -func WithBroadcast[H crypto.Hash, A crypto.Address](f func(m payload.ConsensusPayload[H, A])) func(config *Config[H, A]) { +func WithBroadcast[H Hash, A Address](f func(m ConsensusPayload[H, A])) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.Broadcast = f } } // WithProcessBlock sets ProcessBlock. -func WithProcessBlock[H crypto.Hash, A crypto.Address](f func(b block.Block[H, A])) func(config *Config[H, A]) { +func WithProcessBlock[H Hash, A Address](f func(b Block[H, A])) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.ProcessBlock = f } } // WithGetBlock sets GetBlock. -func WithGetBlock[H crypto.Hash, A crypto.Address](f func(h H) block.Block[H, A]) func(config *Config[H, A]) { +func WithGetBlock[H Hash, A Address](f func(h H) Block[H, A]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetBlock = f } } // WithWatchOnly sets WatchOnly. -func WithWatchOnly[H crypto.Hash, A crypto.Address](f func() bool) func(config *Config[H, A]) { +func WithWatchOnly[H Hash, A Address](f func() bool) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.WatchOnly = f } } // WithCurrentHeight sets CurrentHeight. -func WithCurrentHeight[H crypto.Hash, A crypto.Address](f func() uint32) func(config *Config[H, A]) { +func WithCurrentHeight[H Hash, A Address](f func() uint32) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.CurrentHeight = f } } // WithCurrentBlockHash sets CurrentBlockHash. -func WithCurrentBlockHash[H crypto.Hash, A crypto.Address](f func() H) func(config *Config[H, A]) { +func WithCurrentBlockHash[H Hash, A Address](f func() H) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.CurrentBlockHash = f } } // WithGetValidators sets GetValidators. -func WithGetValidators[H crypto.Hash, A crypto.Address](f func(...block.Transaction[H]) []crypto.PublicKey) func(config *Config[H, A]) { +func WithGetValidators[H Hash, A Address](f func(...Transaction[H]) []PublicKey) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetValidators = f } } // WithGetConsensusAddress sets GetConsensusAddress. -func WithGetConsensusAddress[H crypto.Hash, A crypto.Address](f func(keys ...crypto.PublicKey) A) func(config *Config[H, A]) { +func WithGetConsensusAddress[H Hash, A Address](f func(keys ...PublicKey) A) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.GetConsensusAddress = f } } // WithNewConsensusPayload sets NewConsensusPayload. -func WithNewConsensusPayload[H crypto.Hash, A crypto.Address](f func(*Context[H, A], payload.MessageType, any) payload.ConsensusPayload[H, A]) func(config *Config[H, A]) { +func WithNewConsensusPayload[H Hash, A Address](f func(*Context[H, A], MessageType, any) ConsensusPayload[H, A]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewConsensusPayload = f } } // WithNewPrepareRequest sets NewPrepareRequest. -func WithNewPrepareRequest[H crypto.Hash, A crypto.Address](f func() payload.PrepareRequest[H, A]) func(config *Config[H, A]) { +func WithNewPrepareRequest[H Hash, A Address](f func() PrepareRequest[H, A]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewPrepareRequest = f } } // WithNewPrepareResponse sets NewPrepareResponse. -func WithNewPrepareResponse[H crypto.Hash, A crypto.Address](f func() payload.PrepareResponse[H]) func(config *Config[H, A]) { +func WithNewPrepareResponse[H Hash, A Address](f func() PrepareResponse[H]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewPrepareResponse = f } } // WithNewChangeView sets NewChangeView. -func WithNewChangeView[H crypto.Hash, A crypto.Address](f func() payload.ChangeView) func(config *Config[H, A]) { +func WithNewChangeView[H Hash, A Address](f func() ChangeView) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewChangeView = f } } // WithNewCommit sets NewCommit. -func WithNewCommit[H crypto.Hash, A crypto.Address](f func() payload.Commit) func(config *Config[H, A]) { +func WithNewCommit[H Hash, A Address](f func() Commit) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewCommit = f } } // WithNewRecoveryRequest sets NewRecoveryRequest. -func WithNewRecoveryRequest[H crypto.Hash, A crypto.Address](f func() payload.RecoveryRequest) func(config *Config[H, A]) { +func WithNewRecoveryRequest[H Hash, A Address](f func() RecoveryRequest) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewRecoveryRequest = f } } // WithNewRecoveryMessage sets NewRecoveryMessage. -func WithNewRecoveryMessage[H crypto.Hash, A crypto.Address](f func() payload.RecoveryMessage[H, A]) func(config *Config[H, A]) { +func WithNewRecoveryMessage[H Hash, A Address](f func() RecoveryMessage[H, A]) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.NewRecoveryMessage = f } } // WithVerifyPrepareRequest sets VerifyPrepareRequest. -func WithVerifyPrepareRequest[H crypto.Hash, A crypto.Address](f func(payload.ConsensusPayload[H, A]) error) func(config *Config[H, A]) { +func WithVerifyPrepareRequest[H Hash, A Address](f func(ConsensusPayload[H, A]) error) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.VerifyPrepareRequest = f } } // WithVerifyPrepareResponse sets VerifyPrepareResponse. -func WithVerifyPrepareResponse[H crypto.Hash, A crypto.Address](f func(payload.ConsensusPayload[H, A]) error) func(config *Config[H, A]) { +func WithVerifyPrepareResponse[H Hash, A Address](f func(ConsensusPayload[H, A]) error) func(config *Config[H, A]) { return func(cfg *Config[H, A]) { cfg.VerifyPrepareResponse = f } diff --git a/consensus_message.go b/consensus_message.go new file mode 100644 index 00000000..5552e469 --- /dev/null +++ b/consensus_message.go @@ -0,0 +1,32 @@ +package dbft + +// ConsensusMessage is an interface for generic dBFT message. +type ConsensusMessage[H Hash, A Address] interface { + // ViewNumber returns view number when this message was originated. + ViewNumber() byte + // SetViewNumber sets view number. + SetViewNumber(view byte) + + // Type returns type of this message. + Type() MessageType + // SetType sets the type of this message. + SetType(t MessageType) + + // Payload returns this message's actual payload. + Payload() any + // SetPayload sets this message's payload to p. + SetPayload(p any) + + // GetChangeView returns payload as if it was ChangeView. + GetChangeView() ChangeView + // GetPrepareRequest returns payload as if it was PrepareRequest. + GetPrepareRequest() PrepareRequest[H, A] + // GetPrepareResponse returns payload as if it was PrepareResponse. + GetPrepareResponse() PrepareResponse[H] + // GetCommit returns payload as if it was Commit. + GetCommit() Commit + // GetRecoveryRequest returns payload as if it was RecoveryRequest. + GetRecoveryRequest() RecoveryRequest + // GetRecoveryMessage returns payload as if it was RecoveryMessage. + GetRecoveryMessage() RecoveryMessage[H, A] +} diff --git a/consensus_message_type.go b/consensus_message_type.go new file mode 100644 index 00000000..faed09b7 --- /dev/null +++ b/consensus_message_type.go @@ -0,0 +1,36 @@ +package dbft + +import "fmt" + +// MessageType is a type for dBFT consensus messages. +type MessageType byte + +// 6 following constants enumerate all possible type of consensus message. +const ( + ChangeViewType MessageType = 0x00 + PrepareRequestType MessageType = 0x20 + PrepareResponseType MessageType = 0x21 + CommitType MessageType = 0x30 + RecoveryRequestType MessageType = 0x40 + RecoveryMessageType MessageType = 0x41 +) + +// String implements fmt.Stringer interface. +func (m MessageType) String() string { + switch m { + case ChangeViewType: + return "ChangeView" + case PrepareRequestType: + return "PrepareRequest" + case PrepareResponseType: + return "PrepareResponse" + case CommitType: + return "Commit" + case RecoveryRequestType: + return "RecoveryRequest" + case RecoveryMessageType: + return "RecoveryMessage" + default: + return fmt.Sprintf("UNKNOWN(%02x)", byte(m)) + } +} diff --git a/consensus_payload.go b/consensus_payload.go new file mode 100644 index 00000000..af6bf533 --- /dev/null +++ b/consensus_payload.go @@ -0,0 +1,20 @@ +package dbft + +// ConsensusPayload is a generic payload type which is exchanged +// between the nodes. +type ConsensusPayload[H Hash, A Address] interface { + ConsensusMessage[H, A] + + // ValidatorIndex returns index of validator from which + // payload was originated from. + ValidatorIndex() uint16 + + // SetValidatorIndex sets validator index. + SetValidatorIndex(i uint16) + + Height() uint32 + SetHeight(h uint32) + + // Hash returns 32-byte checksum of the payload. + Hash() H +} diff --git a/context.go b/context.go index 8bb97eaf..a6725649 100644 --- a/context.go +++ b/context.go @@ -5,25 +5,22 @@ import ( "encoding/binary" "time" - "github.com/nspcc-dev/dbft/block" - "github.com/nspcc-dev/dbft/crypto" - "github.com/nspcc-dev/dbft/payload" "github.com/nspcc-dev/dbft/timer" ) // Context is a main dBFT structure which // contains all information needed for performing transitions. -type Context[H crypto.Hash, A crypto.Address] struct { +type Context[H Hash, A Address] struct { // Config is dBFT's Config instance. Config *Config[H, A] // Priv is node's private key. - Priv crypto.PrivateKey + Priv PrivateKey // Pub is node's public key. - Pub crypto.PublicKey + Pub PublicKey - block block.Block[H, A] - header block.Block[H, A] + block Block[H, A] + header Block[H, A] // blockProcessed denotes whether Config.ProcessBlock callback was called for the current // height. If so, then no second call must happen. After new block is received by the user, // dBFT stops any new transaction or messages processing as far as timeouts handling till @@ -35,7 +32,7 @@ type Context[H crypto.Hash, A crypto.Address] struct { // ViewNumber is current view number. ViewNumber byte // Validators is a current validator list. - Validators []crypto.PublicKey + Validators []PublicKey // MyIndex is an index of the current node in the Validators array. // It is equal to -1 if node is not a validator or is WatchOnly. MyIndex int @@ -56,21 +53,21 @@ type Context[H crypto.Hash, A crypto.Address] struct { // MissingTransactions is a slice of hashes containing missing transactions for the current block. MissingTransactions []H // Transactions is a map containing actual transactions for the current block. - Transactions map[H]block.Transaction[H] + Transactions map[H]Transaction[H] // PreparationPayloads stores consensus Prepare* payloads for the current epoch. - PreparationPayloads []payload.ConsensusPayload[H, A] + PreparationPayloads []ConsensusPayload[H, A] // CommitPayloads stores consensus Commit payloads sent throughout all epochs. It // is assumed that valid Commit payload can only be sent once by a single node per // the whole set of consensus epochs for particular block. Invalid commit payloads // are kicked off this list immediately (if PrepareRequest was received for the // current round, so it's possible to verify Commit against it) or stored till // the corresponding PrepareRequest receiving. - CommitPayloads []payload.ConsensusPayload[H, A] + CommitPayloads []ConsensusPayload[H, A] // ChangeViewPayloads stores consensus ChangeView payloads for the current epoch. - ChangeViewPayloads []payload.ConsensusPayload[H, A] + ChangeViewPayloads []ConsensusPayload[H, A] // LastChangeViewPayloads stores consensus ChangeView payloads for the last epoch. - LastChangeViewPayloads []payload.ConsensusPayload[H, A] + LastChangeViewPayloads []ConsensusPayload[H, A] // LastSeenMessage array stores the height of the last seen message, for each validator. // if this node never heard from validator i, LastSeenMessage[i] will be -1. LastSeenMessage []*timer.HV @@ -203,7 +200,7 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { c.Validators = c.Config.GetValidators() n := len(c.Validators) - c.LastChangeViewPayloads = make([]payload.ConsensusPayload[H, A], n) + c.LastChangeViewPayloads = make([]ConsensusPayload[H, A], n) if c.LastSeenMessage == nil { c.LastSeenMessage = make([]*timer.HV, n) @@ -226,13 +223,13 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { c.header = nil n := len(c.Validators) - c.ChangeViewPayloads = make([]payload.ConsensusPayload[H, A], n) + c.ChangeViewPayloads = make([]ConsensusPayload[H, A], n) if view == 0 { - c.CommitPayloads = make([]payload.ConsensusPayload[H, A], n) + c.CommitPayloads = make([]ConsensusPayload[H, A], n) } - c.PreparationPayloads = make([]payload.ConsensusPayload[H, A], n) + c.PreparationPayloads = make([]ConsensusPayload[H, A], n) - c.Transactions = make(map[H]block.Transaction[H]) + c.Transactions = make(map[H]Transaction[H]) c.TransactionHashes = nil c.MissingTransactions = nil c.PrimaryIndex = c.GetPrimaryIndex(view) @@ -280,13 +277,13 @@ func (c *Context[H, A]) getTimestamp() uint64 { } // CreateBlock returns resulting block for the current epoch. -func (c *Context[H, A]) CreateBlock() block.Block[H, A] { +func (c *Context[H, A]) CreateBlock() Block[H, A] { if c.block == nil { if c.block = c.MakeHeader(); c.block == nil { return nil } - txx := make([]block.Transaction[H], len(c.TransactionHashes)) + txx := make([]Transaction[H], len(c.TransactionHashes)) for i, h := range c.TransactionHashes { txx[i] = c.Transactions[h] @@ -300,7 +297,7 @@ func (c *Context[H, A]) CreateBlock() block.Block[H, A] { // MakeHeader returns half-filled block for the current epoch. // All hashable fields will be filled. -func (c *Context[H, A]) MakeHeader() block.Block[H, A] { +func (c *Context[H, A]) MakeHeader() Block[H, A] { if c.header == nil { if !c.RequestSentOrReceived() { return nil @@ -311,15 +308,6 @@ func (c *Context[H, A]) MakeHeader() block.Block[H, A] { return c.header } -// NewBlockFromContext returns new block filled with given contexet. -func NewBlockFromContext(ctx *Context[crypto.Uint256, crypto.Uint160]) block.Block[crypto.Uint256, crypto.Uint160] { - if ctx.TransactionHashes == nil { - return nil - } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.NextConsensus, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) - return block -} - // hasAllTransactions returns true iff all transactions were received // for the proposed block. func (c *Context[H, A]) hasAllTransactions() bool { diff --git a/crypto/crypto.go b/crypto/crypto.go index 3c1d5021..2a279828 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,49 +1,10 @@ package crypto import ( - "encoding" - "fmt" + "github.com/nspcc-dev/dbft" "io" ) -type ( - // PublicKey is a generic public key interface used by dbft. - PublicKey interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - - // Verify verifies if sig is indeed msg's signature. - Verify(msg, sig []byte) error - } - - // PrivateKey is a generic private key interface used by dbft. - PrivateKey interface { - // Sign returns msg's signature and error on failure. - Sign(msg []byte) (sig []byte, err error) - } - - // Hash is a generic hash interface used by dbft for payloads, blocks and - // transactions identification. It is recommended to implement this interface - // using hash functions with low hash collision probability. The following - // requirements must be met: - // 1. Hashes of two equal payloads/blocks/transactions are equal. - // 2. Hashes of two different payloads/blocks/transactions are different. - Hash interface { - comparable - fmt.Stringer - } - - // Address is a generic address interface used by dbft for operations related - // to consensus address. It is recommended to implement this interface - // using hash functions with low hash collision probability. The following - // requirements must be met: - // 1. Addresses of two equal sets of consensus members are equal. - // 2. Addresses of two different sets of consensus members are different. - Address interface { - comparable - } -) - type suiteType byte const ( @@ -56,13 +17,13 @@ const defaultSuite = SuiteECDSA // Generate generates new key pair using r // as a source of entropy. -func Generate(r io.Reader) (PrivateKey, PublicKey) { +func Generate(r io.Reader) (dbft.PrivateKey, dbft.PublicKey) { return GenerateWith(defaultSuite, r) } // GenerateWith generates new key pair for suite t // using r as a source of entropy. -func GenerateWith(t suiteType, r io.Reader) (PrivateKey, PublicKey) { +func GenerateWith(t suiteType, r io.Reader) (dbft.PrivateKey, dbft.PublicKey) { if t == SuiteECDSA { return generateECDSA(r) } diff --git a/crypto/ecdsa.go b/crypto/ecdsa.go index a0fb9560..3eea846c 100644 --- a/crypto/ecdsa.go +++ b/crypto/ecdsa.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "errors" + "github.com/nspcc-dev/dbft" "io" crypto "github.com/nspcc-dev/neofs-crypto" @@ -21,7 +22,7 @@ type ( } ) -func generateECDSA(r io.Reader) (PrivateKey, PublicKey) { +func generateECDSA(r io.Reader) (dbft.PrivateKey, dbft.PublicKey) { key, err := ecdsa.GenerateKey(elliptic.P256(), r) if err != nil { return nil, nil @@ -31,14 +32,14 @@ func generateECDSA(r io.Reader) (PrivateKey, PublicKey) { } // NewECDSAPublicKey returns new PublicKey from *ecdsa.PublicKey. -func NewECDSAPublicKey(pub *ecdsa.PublicKey) PublicKey { +func NewECDSAPublicKey(pub *ecdsa.PublicKey) dbft.PublicKey { return &ECDSAPub{ PublicKey: pub, } } // NewECDSAPrivateKey returns new PublicKey from *ecdsa.PrivateKey. -func NewECDSAPrivateKey(key *ecdsa.PrivateKey) PrivateKey { +func NewECDSAPrivateKey(key *ecdsa.PrivateKey) dbft.PrivateKey { return &ECDSAPriv{ PrivateKey: key, } diff --git a/dbft.go b/dbft.go index 3271b09e..01a167dc 100644 --- a/dbft.go +++ b/dbft.go @@ -4,9 +4,6 @@ import ( "sync" "time" - "github.com/nspcc-dev/dbft/block" - "github.com/nspcc-dev/dbft/crypto" - "github.com/nspcc-dev/dbft/payload" "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) @@ -16,7 +13,7 @@ type ( // and [Config] (service configuration). Data exposed from these fields // is supposed to be read-only, state is changed via methods of this // structure. - DBFT[H crypto.Hash, A crypto.Address] struct { + DBFT[H Hash, A Address] struct { Context[H, A] Config[H, A] @@ -30,7 +27,7 @@ type ( // using provided options or nil if some of the options are missing or invalid. // H and A generic parameters are used as hash and address representation for // dBFT consensus messages, blocks and transactions. -func New[H crypto.Hash, A crypto.Address](options ...func(config *Config[H, A])) *DBFT[H, A] { +func New[H Hash, A Address](options ...func(config *Config[H, A])) *DBFT[H, A] { cfg := defaultConfig[H, A]() for _, option := range options { @@ -52,7 +49,7 @@ func New[H crypto.Hash, A crypto.Address](options ...func(config *Config[H, A])) return d } -func (d *DBFT[H, A]) addTransaction(tx block.Transaction[H]) { +func (d *DBFT[H, A]) addTransaction(tx Transaction[H]) { d.Transactions[tx.Hash()] = tx if d.hasAllTransactions() { if d.IsPrimary() || d.Context.WatchOnly() { @@ -140,7 +137,7 @@ func (d *DBFT[H, A]) initializeConsensus(view byte, ts uint64) { } // OnTransaction notifies service about receiving new transaction. -func (d *DBFT[H, A]) OnTransaction(tx block.Transaction[H]) { +func (d *DBFT[H, A]) OnTransaction(tx Transaction[H]) { // d.Logger.Debug("OnTransaction", // zap.Bool("backup", d.IsBackup()), // zap.Bool("not_accepting", d.NotAcceptingPayloadsDueToViewChanging()), @@ -197,13 +194,13 @@ func (d *DBFT[H, A]) OnTimeout(hv timer.HV) { d.sendRecoveryMessage() d.changeTimer(d.SecondsPerBlock << 1) } else { - d.sendChangeView(payload.CVTimeout) + d.sendChangeView(CVTimeout) } } } // OnReceive advances state machine in accordance with msg. -func (d *DBFT[H, A]) OnReceive(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) OnReceive(msg ConsensusPayload[H, A]) { if int(msg.ValidatorIndex()) >= len(d.Validators) { d.Logger.Error("too big validator index", zap.Uint16("from", msg.ValidatorIndex())) return @@ -227,8 +224,8 @@ func (d *DBFT[H, A]) OnReceive(msg payload.ConsensusPayload[H, A]) { return } else if msg.Height() > d.BlockIndex || (msg.ViewNumber() > d.ViewNumber && - msg.Type() != payload.ChangeViewType && - msg.Type() != payload.RecoveryMessageType) { + msg.Type() != ChangeViewType && + msg.Type() != RecoveryMessageType) { d.Logger.Debug("caching message from future", zap.Uint32("height", msg.Height()), zap.Uint("view", uint(msg.ViewNumber())), @@ -247,23 +244,23 @@ func (d *DBFT[H, A]) OnReceive(msg payload.ConsensusPayload[H, A]) { } } - if d.BlockSent() && msg.Type() != payload.RecoveryRequestType { + if d.BlockSent() && msg.Type() != RecoveryRequestType { // We've already collected the block, only recovery request must be handled. return } switch msg.Type() { - case payload.ChangeViewType: + case ChangeViewType: d.onChangeView(msg) - case payload.PrepareRequestType: + case PrepareRequestType: d.onPrepareRequest(msg) - case payload.PrepareResponseType: + case PrepareResponseType: d.onPrepareResponse(msg) - case payload.CommitType: + case CommitType: d.onCommit(msg) - case payload.RecoveryRequestType: + case RecoveryRequestType: d.onRecoveryRequest(msg) - case payload.RecoveryMessageType: + case RecoveryMessageType: d.onRecoveryMessage(msg) default: d.Logger.DPanic("wrong message type") @@ -294,7 +291,7 @@ func (d *DBFT[H, A]) start() { d.sendPrepareRequest() } -func (d *DBFT[H, A]) onPrepareRequest(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onPrepareRequest(msg ConsensusPayload[H, A]) { // ignore prepareRequest if we had already received it or // are in process of changing view if d.RequestSentOrReceived() { //|| (d.ViewChanging() && !d.MoreThanFNodesCommittedOrLost()) { @@ -317,7 +314,7 @@ func (d *DBFT[H, A]) onPrepareRequest(msg payload.ConsensusPayload[H, A]) { if err := d.VerifyPrepareRequest(msg); err != nil { // We should change view if we receive signed PrepareRequest from the expected validator but it is invalid. d.Logger.Warn("invalid PrepareRequest", zap.Uint16("from", msg.ValidatorIndex()), zap.String("error", err.Error())) - d.sendChangeView(payload.CVBlockRejectedByPolicy) + d.sendChangeView(CVBlockRejectedByPolicy) return } @@ -373,26 +370,26 @@ func (d *DBFT[H, A]) processMissingTx() { // with it, it sends a changeView request and returns false. It's only valid to // call it when all transactions for this block are already collected. func (d *DBFT[H, A]) createAndCheckBlock() bool { - txx := make([]block.Transaction[H], 0, len(d.TransactionHashes)) + txx := make([]Transaction[H], 0, len(d.TransactionHashes)) for _, h := range d.TransactionHashes { txx = append(txx, d.Transactions[h]) } if d.NextConsensus != d.GetConsensusAddress(d.GetValidators(txx...)...) { d.Logger.Error("invalid nextConsensus in proposed block") - d.sendChangeView(payload.CVBlockRejectedByPolicy) + d.sendChangeView(CVBlockRejectedByPolicy) return false } if b := d.Context.CreateBlock(); !d.VerifyBlock(b) { d.Logger.Warn("proposed block fails verification") - d.sendChangeView(payload.CVTxInvalid) + d.sendChangeView(CVTxInvalid) return false } return true } -func (d *DBFT[H, A]) updateExistingPayloads(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) updateExistingPayloads(msg ConsensusPayload[H, A]) { for i, m := range d.PreparationPayloads { - if m != nil && m.Type() == payload.PrepareResponseType { + if m != nil && m.Type() == PrepareResponseType { resp := m.GetPrepareResponse() if resp != nil && resp.PreparationHash() != msg.Hash() { d.PreparationPayloads[i] = nil @@ -413,7 +410,7 @@ func (d *DBFT[H, A]) updateExistingPayloads(msg payload.ConsensusPayload[H, A]) } } -func (d *DBFT[H, A]) onPrepareResponse(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onPrepareResponse(msg ConsensusPayload[H, A]) { if d.ViewNumber != msg.ViewNumber() { d.Logger.Debug("ignoring wrong view number", zap.Uint("view", uint(msg.ViewNumber()))) return @@ -465,7 +462,7 @@ func (d *DBFT[H, A]) onPrepareResponse(msg payload.ConsensusPayload[H, A]) { } } -func (d *DBFT[H, A]) onChangeView(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onChangeView(msg ConsensusPayload[H, A]) { p := msg.GetChangeView() if p.NewViewNumber() <= d.ViewNumber { @@ -496,7 +493,7 @@ func (d *DBFT[H, A]) onChangeView(msg payload.ConsensusPayload[H, A]) { d.checkChangeView(p.NewViewNumber()) } -func (d *DBFT[H, A]) onCommit(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onCommit(msg ConsensusPayload[H, A]) { existing := d.CommitPayloads[msg.ValidatorIndex()] if existing != nil { if existing.Hash() != msg.Hash() { @@ -538,7 +535,7 @@ func (d *DBFT[H, A]) onCommit(msg payload.ConsensusPayload[H, A]) { d.CommitPayloads[msg.ValidatorIndex()] = msg } -func (d *DBFT[H, A]) onRecoveryRequest(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onRecoveryRequest(msg ConsensusPayload[H, A]) { if !d.CommitSent() { // Limit recoveries to be sent from no more than F nodes // TODO replace loop with a single if @@ -560,7 +557,7 @@ func (d *DBFT[H, A]) onRecoveryRequest(msg payload.ConsensusPayload[H, A]) { d.sendRecoveryMessage() } -func (d *DBFT[H, A]) onRecoveryMessage(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) onRecoveryMessage(msg ConsensusPayload[H, A]) { d.Logger.Debug("recovery message received", zap.Any("dump", msg)) var ( diff --git a/dbft_test.go b/dbft_test.go index e2c1b42a..c64aff94 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -3,10 +3,10 @@ package dbft import ( "crypto/rand" "encoding/binary" + "github.com/nspcc-dev/dbft/block" "testing" "time" - "github.com/nspcc-dev/dbft/block" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/payload" "github.com/nspcc-dev/dbft/timer" @@ -14,19 +14,19 @@ import ( "go.uber.org/zap" ) -type Payload = payload.ConsensusPayload[crypto.Uint256, crypto.Uint160] +type Payload = ConsensusPayload[crypto.Uint256, crypto.Uint160] type testState struct { myIndex int count int - privs []crypto.PrivateKey - pubs []crypto.PublicKey + privs []PrivateKey + pubs []PublicKey ch []Payload currHeight uint32 currHash crypto.Uint256 pool *testPool - blocks []block.Block[crypto.Uint256, crypto.Uint160] - verify func(b block.Block[crypto.Uint256, crypto.Uint160]) bool + blocks []Block[crypto.Uint256, crypto.Uint160] + verify func(b Block[crypto.Uint256, crypto.Uint160]) bool } type ( @@ -56,7 +56,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { service.Start(0) p := s.tryRecv() require.NotNil(t, p) - require.Equal(t, payload.PrepareRequestType, p.Type()) + require.Equal(t, PrepareRequestType, p.Type()) require.EqualValues(t, 2, p.Height()) require.EqualValues(t, 0, p.ViewNumber()) require.NotNil(t, p.Payload()) @@ -68,7 +68,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { // if there are many faulty must send RecoveryRequest cv := s.tryRecv() require.NotNil(t, cv) - require.Equal(t, payload.RecoveryRequestType, cv.Type()) + require.Equal(t, RecoveryRequestType, cv.Type()) require.Nil(t, s.tryRecv()) // if all nodes are up must send ChangeView @@ -79,7 +79,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { cv = s.tryRecv() require.NotNil(t, cv) - require.Equal(t, payload.ChangeViewType, cv.Type()) + require.Equal(t, ChangeViewType, cv.Type()) require.EqualValues(t, 1, cv.GetChangeView().NewViewNumber()) require.Nil(t, s.tryRecv()) }) @@ -95,7 +95,7 @@ func TestDBFT_SingleNode(t *testing.T) { service.Start(0) p := s.tryRecv() require.NotNil(t, p) - require.Equal(t, payload.PrepareRequestType, p.Type()) + require.Equal(t, PrepareRequestType, p.Type()) require.EqualValues(t, 3, p.Height()) require.EqualValues(t, 0, p.ViewNumber()) require.NotNil(t, p.Payload()) @@ -103,7 +103,7 @@ func TestDBFT_SingleNode(t *testing.T) { cm := s.tryRecv() require.NotNil(t, cm) - require.Equal(t, payload.CommitType, cm.Type()) + require.Equal(t, CommitType, cm.Type()) require.EqualValues(t, s.currHeight+1, cm.Height()) require.EqualValues(t, 0, cm.ViewNumber()) require.NotNil(t, cm.Payload()) @@ -116,7 +116,7 @@ func TestDBFT_SingleNode(t *testing.T) { func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { s := newTestState(2, 7) - s.verify = func(b block.Block[crypto.Uint256, crypto.Uint160]) bool { + s.verify = func(b Block[crypto.Uint256, crypto.Uint160]) bool { for _, tx := range b.Transactions() { if tx.(testTx)%10 == 0 { return false @@ -139,7 +139,7 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { resp := s.tryRecv() require.NotNil(t, resp) - require.Equal(t, payload.PrepareResponseType, resp.Type()) + require.Equal(t, PrepareResponseType, resp.Type()) require.EqualValues(t, s.currHeight+1, resp.Height()) require.EqualValues(t, 0, resp.ViewNumber()) require.EqualValues(t, s.myIndex, resp.ValidatorIndex()) @@ -178,7 +178,7 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { cv := s.tryRecv() require.NotNil(t, cv) - require.Equal(t, payload.ChangeViewType, cv.Type()) + require.Equal(t, ChangeViewType, cv.Type()) require.EqualValues(t, s.currHeight+1, cv.Height()) require.EqualValues(t, 0, cv.ViewNumber()) require.EqualValues(t, s.myIndex, cv.ValidatorIndex()) @@ -219,7 +219,7 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { service.OnTransaction(txs[1]) resp := s.tryRecv() require.NotNil(t, resp) - require.Equal(t, payload.PrepareResponseType, resp.Type()) + require.Equal(t, PrepareResponseType, resp.Type()) require.EqualValues(t, s.currHeight+1, resp.Height()) require.EqualValues(t, 0, resp.ViewNumber()) require.EqualValues(t, s.myIndex, resp.ValidatorIndex()) @@ -294,7 +294,7 @@ func TestDBFT_OnReceiveCommit(t *testing.T) { cm := s.tryRecv() require.NotNil(t, cm) - require.Equal(t, payload.CommitType, cm.Type()) + require.Equal(t, CommitType, cm.Type()) require.EqualValues(t, s.currHeight+1, cm.Height()) require.EqualValues(t, 0, cm.ViewNumber()) require.EqualValues(t, s.myIndex, cm.ValidatorIndex()) @@ -311,7 +311,7 @@ func TestDBFT_OnReceiveCommit(t *testing.T) { r := s.tryRecv() require.NotNil(t, r) - require.Equal(t, payload.RecoveryMessageType, r.Type()) + require.Equal(t, RecoveryMessageType, r.Type()) }) t.Run("process block after enough commits", func(t *testing.T) { @@ -358,7 +358,7 @@ func TestDBFT_OnReceiveRecoveryRequest(t *testing.T) { service.OnReceive(rr) rm := s.tryRecv() require.NotNil(t, rm) - require.Equal(t, payload.RecoveryMessageType, rm.Type()) + require.Equal(t, RecoveryMessageType, rm.Type()) other := s.copyWithIndex(3) srv2 := New[crypto.Uint256, crypto.Uint160](other.getOptions()...) @@ -367,11 +367,11 @@ func TestDBFT_OnReceiveRecoveryRequest(t *testing.T) { r2 := other.tryRecv() require.NotNil(t, r2) - require.Equal(t, payload.PrepareResponseType, r2.Type()) + require.Equal(t, PrepareResponseType, r2.Type()) cm2 := other.tryRecv() require.NotNil(t, cm2) - require.Equal(t, payload.CommitType, cm2.Type()) + require.Equal(t, CommitType, cm2.Type()) pub := other.pubs[other.myIndex] require.NoError(t, service.header.Verify(pub, cm2.GetCommit().Signature())) @@ -398,13 +398,13 @@ func TestDBFT_OnReceiveChangeView(t *testing.T) { service.OnTimeout(timer.HV{Height: s.currHeight + 1}) cv := s.tryRecv() require.NotNil(t, cv) - require.Equal(t, payload.ChangeViewType, cv.Type()) + require.Equal(t, ChangeViewType, cv.Type()) t.Run("primary sends prepare request after timeout", func(t *testing.T) { service.OnTimeout(timer.HV{Height: s.currHeight + 1, View: 1}) pr := s.tryRecv() require.NotNil(t, pr) - require.Equal(t, payload.PrepareRequestType, pr.Type()) + require.Equal(t, PrepareRequestType, pr.Type()) }) }) } @@ -433,70 +433,70 @@ func TestDBFT_Invalid(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithGetValidators[crypto.Uint256, crypto.Uint160](func(...block.Transaction[crypto.Uint256]) []crypto.PublicKey { - return []crypto.PublicKey{pub} + opts = append(opts, WithGetValidators[crypto.Uint256, crypto.Uint160](func(...Transaction[crypto.Uint256]) []PublicKey { + return []PublicKey{pub} })) t.Run("without NewBlockFromContext", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](func(_ *Context[crypto.Uint256, crypto.Uint160]) block.Block[crypto.Uint256, crypto.Uint160] { + opts = append(opts, WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](func(_ *Context[crypto.Uint256, crypto.Uint160]) Block[crypto.Uint256, crypto.Uint160] { return nil })) t.Run("without GetConsensusAddress", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](func(_ ...crypto.PublicKey) crypto.Uint160 { + opts = append(opts, WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](func(_ ...PublicKey) crypto.Uint160 { return crypto.Uint160{} })) t.Run("without NewConsensusPayload", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](func(_ *Context[crypto.Uint256, crypto.Uint160], _ payload.MessageType, _ any) payload.ConsensusPayload[crypto.Uint256, crypto.Uint160] { + opts = append(opts, WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](func(_ *Context[crypto.Uint256, crypto.Uint160], _ MessageType, _ any) ConsensusPayload[crypto.Uint256, crypto.Uint160] { return nil })) t.Run("without NewPrepareRequest", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](func() payload.PrepareRequest[crypto.Uint256, crypto.Uint160] { + opts = append(opts, WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](func() PrepareRequest[crypto.Uint256, crypto.Uint160] { return nil })) t.Run("without NewPrepareResponse", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewPrepareResponse[crypto.Uint256, crypto.Uint160](func() payload.PrepareResponse[crypto.Uint256] { + opts = append(opts, WithNewPrepareResponse[crypto.Uint256, crypto.Uint160](func() PrepareResponse[crypto.Uint256] { return nil })) t.Run("without NewChangeView", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewChangeView[crypto.Uint256, crypto.Uint160](func() payload.ChangeView { + opts = append(opts, WithNewChangeView[crypto.Uint256, crypto.Uint160](func() ChangeView { return nil })) t.Run("without NewCommit", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewCommit[crypto.Uint256, crypto.Uint160](func() payload.Commit { + opts = append(opts, WithNewCommit[crypto.Uint256, crypto.Uint160](func() Commit { return nil })) t.Run("without NewRecoveryRequest", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](func() payload.RecoveryRequest { + opts = append(opts, WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](func() RecoveryRequest { return nil })) t.Run("without NewRecoveryMessage", func(t *testing.T) { require.Nil(t, New(opts...)) }) - opts = append(opts, WithNewRecoveryMessage[crypto.Uint256, crypto.Uint160](func() payload.RecoveryMessage[crypto.Uint256, crypto.Uint160] { + opts = append(opts, WithNewRecoveryMessage[crypto.Uint256, crypto.Uint160](func() RecoveryMessage[crypto.Uint256, crypto.Uint160] { return nil })) t.Run("with all defaults", func(t *testing.T) { @@ -547,7 +547,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // Step 1. The primary (at view 0) replica 1 sends the PrepareRequest message. reqV0 := r1.tryRecv() require.NotNil(t, reqV0) - require.Equal(t, payload.PrepareRequestType, reqV0.Type()) + require.Equal(t, PrepareRequestType, reqV0.Type()) // Step 2 will be performed later, see the comment to Step 2. @@ -556,7 +556,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnReceive(reqV0) resp0V0 := r0.tryRecv() require.NotNil(t, resp0V0) - require.Equal(t, payload.PrepareResponseType, resp0V0.Type()) + require.Equal(t, PrepareResponseType, resp0V0.Type()) // Step 4 will be performed later, see the comment to Step 4. @@ -565,14 +565,14 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s2.OnReceive(reqV0) resp2V0 := r2.tryRecv() require.NotNil(t, resp2V0) - require.Equal(t, payload.PrepareResponseType, resp2V0.Type()) + require.Equal(t, PrepareResponseType, resp2V0.Type()) // Step 6. The backup (at view 0) replica 2 collects M prepare messages (from // itself and replicas 0, 1) and broadcasts the Commit message for view 0. s2.OnReceive(resp0V0) cm2V0 := r2.tryRecv() require.NotNil(t, cm2V0) - require.Equal(t, payload.CommitType, cm2V0.Type()) + require.Equal(t, CommitType, cm2V0.Type()) // Step 7. The backup (at view 0) replica 3 decides to change its view // (possible on timeout) and sends the ChangeView message. @@ -581,7 +581,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s3.OnTimeout(timer.HV{Height: r3.currHeight + 1, View: 0}) cv3V0 := r3.tryRecv() require.NotNil(t, cv3V0) - require.Equal(t, payload.ChangeViewType, cv3V0.Type()) + require.Equal(t, ChangeViewType, cv3V0.Type()) // Step 2. The primary (at view 0) replica 1 decides to change its view // (possible on timeout after receiving at least M non-commit messages from the @@ -591,7 +591,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s1.OnTimeout(timer.HV{Height: r1.currHeight + 1, View: 0}) cv1V0 := r1.tryRecv() require.NotNil(t, cv1V0) - require.Equal(t, payload.ChangeViewType, cv1V0.Type()) + require.Equal(t, ChangeViewType, cv1V0.Type()) // Step 4. The backup (at view 0) replica 0 decides to change its view // (possible on timeout after receiving at least M non-commit messages from the @@ -600,7 +600,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 0}) cv0V0 := r0.tryRecv() require.NotNil(t, cv0V0) - require.Equal(t, payload.ChangeViewType, cv0V0.Type()) + require.Equal(t, ChangeViewType, cv0V0.Type()) // Step 8. The primary (at view 0) replica 1 collects M ChangeView messages // (from itself and replicas 1, 3) and changes its view to 1. @@ -616,14 +616,14 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 1}) reqV1 := r0.tryRecv() require.NotNil(t, reqV1) - require.Equal(t, payload.PrepareRequestType, reqV1.Type()) + require.Equal(t, PrepareRequestType, reqV1.Type()) // Step 11. The backup (at view 1) replica 1 receives the PrepareRequest of // view 1 and sends the PrepareResponse. s1.OnReceive(reqV1) resp1V1 := r1.tryRecv() require.NotNil(t, resp1V1) - require.Equal(t, payload.PrepareResponseType, resp1V1.Type()) + require.Equal(t, PrepareResponseType, resp1V1.Type()) // Steps 12, 13 will be performed later, see the comments to Step 12, 13. @@ -639,7 +639,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s3.OnTimeout(timer.HV{Height: r3.currHeight + 1, View: 1}) rcvr3V1 := r3.tryRecv() require.NotNil(t, rcvr3V1) - require.Equal(t, payload.RecoveryRequestType, rcvr3V1.Type()) + require.Equal(t, RecoveryRequestType, rcvr3V1.Type()) // Intermediate step B. The backup (at view 1) replica 1 should receive any // message from replica 3 to be able to change view. However, it couldn't be @@ -649,7 +649,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s1.OnReceive(rcvr3V1) rcvrResp1V1 := r1.tryRecv() require.NotNil(t, rcvrResp1V1) - require.Equal(t, payload.RecoveryMessageType, rcvrResp1V1.Type()) + require.Equal(t, RecoveryMessageType, rcvrResp1V1.Type()) // Intermediate step C. The primary (at view 1) replica 0 should receive // RecoveryRequest from replica 3. The purpose of this step is the same as @@ -657,7 +657,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnReceive(rcvr3V1) rcvrResp0V1 := r0.tryRecv() require.NotNil(t, rcvrResp0V1) - require.Equal(t, payload.RecoveryMessageType, rcvrResp0V1.Type()) + require.Equal(t, RecoveryMessageType, rcvrResp0V1.Type()) // Step 12. According to the neo-project/neo#792, at this step the backup (at view 1) // replica 1 decides to change its view (possible on timeout) and sends the @@ -674,7 +674,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s1.OnTimeout(timer.HV{Height: r1.currHeight + 1, View: 1}) cv1V1 := r1.tryRecv() require.NotNil(t, cv1V1) - require.Equal(t, payload.ChangeViewType, cv1V1.Type()) + require.Equal(t, ChangeViewType, cv1V1.Type()) // Step 13. The primary (at view 1) replica 0 decides to change its view // (possible on timeout) and sends the ChangeView message. @@ -682,21 +682,21 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 1}) cv0V1 := r0.tryRecv() require.NotNil(t, cv0V1) - require.Equal(t, payload.ChangeViewType, cv0V1.Type()) + require.Equal(t, ChangeViewType, cv0V1.Type()) // Step 15. The backup (at view 1) replica 3 receives PrepareRequest of view // 1 and broadcasts its PrepareResponse. s3.OnReceive(reqV1) resp3V1 := r3.tryRecv() require.NotNil(t, resp3V1) - require.Equal(t, payload.PrepareResponseType, resp3V1.Type()) + require.Equal(t, PrepareResponseType, resp3V1.Type()) // Step 16. The backup (at view 1) replica 3 collects M prepare messages and // broadcasts the Commit message for view 1. s3.OnReceive(resp1V1) cm3V1 := r3.tryRecv() require.NotNil(t, cm3V1) - require.Equal(t, payload.CommitType, cm3V1.Type()) + require.Equal(t, CommitType, cm3V1.Type()) // Intermediate step D. It is needed to enable step 17 and to check that // MoreThanFNodesCommittedOrLost works properly and counts Commit messages from @@ -711,13 +711,13 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { s0.OnReceive(resp3V1) cm0V1 := r0.tryRecv() require.NotNil(t, cm0V1) - require.Equal(t, payload.CommitType, cm0V1.Type()) + require.Equal(t, CommitType, cm0V1.Type()) s1.OnReceive(cm0V1) s1.OnReceive(resp3V1) cm1V1 := r1.tryRecv() require.NotNil(t, cm1V1) - require.Equal(t, payload.CommitType, cm1V1.Type()) + require.Equal(t, CommitType, cm1V1.Type()) // Finally, send missing Commit message to replicas 0 and 1, they should accept // the block. @@ -735,7 +735,7 @@ func (s testState) getChangeView(from uint16, view byte) Payload { cv.SetNewViewNumber(view) p := s.getPayload(from) - p.SetType(payload.ChangeViewType) + p.SetType(ChangeViewType) p.SetPayload(cv) return p @@ -743,7 +743,7 @@ func (s testState) getChangeView(from uint16, view byte) Payload { func (s testState) getRecoveryRequest(from uint16) Payload { p := s.getPayload(from) - p.SetType(payload.RecoveryRequestType) + p.SetType(RecoveryRequestType) p.SetPayload(payload.NewRecoveryRequest()) return p @@ -754,7 +754,7 @@ func (s testState) getCommit(from uint16, sign []byte) Payload { c.SetSignature(sign) p := s.getPayload(from) - p.SetType(payload.CommitType) + p.SetType(CommitType) p.SetPayload(c) return p @@ -765,7 +765,7 @@ func (s testState) getPrepareResponse(from uint16, phash crypto.Uint256) Payload resp.SetPreparationHash(phash) p := s.getPayload(from) - p.SetType(payload.PrepareResponseType) + p.SetType(PrepareResponseType) p.SetPayload(resp) return p @@ -777,7 +777,7 @@ func (s testState) getPrepareRequest(from uint16, hashes ...crypto.Uint256) Payl req.SetNextConsensus(s.nextConsensus()) p := s.getPayload(from) - p.SetType(payload.PrepareRequestType) + p.SetType(PrepareRequestType) p.SetPayload(req) return p @@ -814,7 +814,7 @@ func (s *testState) tryRecv() Payload { return p } -func (s *testState) nextBlock() block.Block[crypto.Uint256, crypto.Uint160] { +func (s *testState) nextBlock() Block[crypto.Uint256, crypto.Uint160] { if len(s.blocks) == 0 { return nil } @@ -837,7 +837,7 @@ func (s testState) copyWithIndex(myIndex int) *testState { } } -func (s testState) nextConsensus(...crypto.PublicKey) crypto.Uint160 { +func (s testState) nextConsensus(...PublicKey) crypto.Uint160 { return crypto.Uint160{1} } @@ -845,20 +845,20 @@ func (s *testState) getOptions() []func(*Config[crypto.Uint256, crypto.Uint160]) opts := []func(*Config[crypto.Uint256, crypto.Uint160]){ WithCurrentHeight[crypto.Uint256, crypto.Uint160](func() uint32 { return s.currHeight }), WithCurrentBlockHash[crypto.Uint256, crypto.Uint160](func() crypto.Uint256 { return s.currHash }), - WithGetValidators[crypto.Uint256, crypto.Uint160](func(...block.Transaction[crypto.Uint256]) []crypto.PublicKey { return s.pubs }), + WithGetValidators[crypto.Uint256, crypto.Uint160](func(...Transaction[crypto.Uint256]) []PublicKey { return s.pubs }), WithKeyPair[crypto.Uint256, crypto.Uint160](s.privs[s.myIndex], s.pubs[s.myIndex]), WithBroadcast[crypto.Uint256, crypto.Uint160](func(p Payload) { s.ch = append(s.ch, p) }), WithGetTx[crypto.Uint256, crypto.Uint160](s.pool.Get), - WithProcessBlock[crypto.Uint256, crypto.Uint160](func(b block.Block[crypto.Uint256, crypto.Uint160]) { s.blocks = append(s.blocks, b) }), + WithProcessBlock[crypto.Uint256, crypto.Uint160](func(b Block[crypto.Uint256, crypto.Uint160]) { s.blocks = append(s.blocks, b) }), WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](s.nextConsensus), WithWatchOnly[crypto.Uint256, crypto.Uint160](func() bool { return false }), - WithGetBlock[crypto.Uint256, crypto.Uint160](func(crypto.Uint256) block.Block[crypto.Uint256, crypto.Uint160] { return nil }), + WithGetBlock[crypto.Uint256, crypto.Uint160](func(crypto.Uint256) Block[crypto.Uint256, crypto.Uint160] { return nil }), WithTimer[crypto.Uint256, crypto.Uint160](timer.New()), WithLogger[crypto.Uint256, crypto.Uint160](zap.NewNop()), - WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](NewBlockFromContext), + WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](newBlockFromContext), WithSecondsPerBlock[crypto.Uint256, crypto.Uint160](time.Second * 10), WithRequestTx[crypto.Uint256, crypto.Uint160](func(...crypto.Uint256) {}), - WithGetVerified[crypto.Uint256, crypto.Uint160](func() []block.Transaction[crypto.Uint256] { return []block.Transaction[crypto.Uint256]{} }), + WithGetVerified[crypto.Uint256, crypto.Uint160](func() []Transaction[crypto.Uint256] { return []Transaction[crypto.Uint256]{} }), WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](newConsensusPayload), WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](payload.NewPrepareRequest), @@ -871,7 +871,7 @@ func (s *testState) getOptions() []func(*Config[crypto.Uint256, crypto.Uint160]) verify := s.verify if verify == nil { - verify = func(block.Block[crypto.Uint256, crypto.Uint160]) bool { return true } + verify = func(Block[crypto.Uint256, crypto.Uint160]) bool { return true } } opts = append(opts, WithVerifyBlock(verify)) @@ -886,9 +886,17 @@ func (s *testState) getOptions() []func(*Config[crypto.Uint256, crypto.Uint160]) return opts } +func newBlockFromContext(ctx *Context[crypto.Uint256, crypto.Uint160]) Block[crypto.Uint256, crypto.Uint160] { + if ctx.TransactionHashes == nil { + return nil + } + block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.NextConsensus, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) + return block +} + // newConsensusPayload is a function for creating consensus payload of specific // type. -func newConsensusPayload(c *Context[crypto.Uint256, crypto.Uint160], t payload.MessageType, msg any) payload.ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func newConsensusPayload(c *Context[crypto.Uint256, crypto.Uint160], t MessageType, msg any) ConsensusPayload[crypto.Uint256, crypto.Uint160] { cp := payload.NewConsensusPayload() cp.SetHeight(c.BlockIndex) cp.SetValidatorIndex(uint16(c.MyIndex)) @@ -899,7 +907,7 @@ func newConsensusPayload(c *Context[crypto.Uint256, crypto.Uint160], t payload.M return cp } -func getTestValidators(n int) (privs []crypto.PrivateKey, pubs []crypto.PublicKey) { +func getTestValidators(n int) (privs []PrivateKey, pubs []PublicKey) { for i := 0; i < n; i++ { priv, pub := crypto.Generate(rand.Reader) privs = append(privs, priv) @@ -924,7 +932,7 @@ func (p *testPool) Add(tx testTx) { p.storage[tx.Hash()] = tx } -func (p *testPool) Get(h crypto.Uint256) block.Transaction[crypto.Uint256] { +func (p *testPool) Get(h crypto.Uint256) Transaction[crypto.Uint256] { if tx, ok := p.storage[h]; ok { return tx } diff --git a/helpers.go b/helpers.go index c0b0fcc3..eff55904 100644 --- a/helpers.go +++ b/helpers.go @@ -1,34 +1,29 @@ package dbft -import ( - "github.com/nspcc-dev/dbft/crypto" - "github.com/nspcc-dev/dbft/payload" -) - type ( // inbox is a structure storing messages from a single epoch. - inbox[H crypto.Hash, A crypto.Address] struct { - prepare map[uint16]payload.ConsensusPayload[H, A] - chViews map[uint16]payload.ConsensusPayload[H, A] - commit map[uint16]payload.ConsensusPayload[H, A] + inbox[H Hash, A Address] struct { + prepare map[uint16]ConsensusPayload[H, A] + chViews map[uint16]ConsensusPayload[H, A] + commit map[uint16]ConsensusPayload[H, A] } // cache is an auxiliary structure storing messages // from future epochs. - cache[H crypto.Hash, A crypto.Address] struct { + cache[H Hash, A Address] struct { mail map[uint32]*inbox[H, A] } ) -func newInbox[H crypto.Hash, A crypto.Address]() *inbox[H, A] { +func newInbox[H Hash, A Address]() *inbox[H, A] { return &inbox[H, A]{ - prepare: make(map[uint16]payload.ConsensusPayload[H, A]), - chViews: make(map[uint16]payload.ConsensusPayload[H, A]), - commit: make(map[uint16]payload.ConsensusPayload[H, A]), + prepare: make(map[uint16]ConsensusPayload[H, A]), + chViews: make(map[uint16]ConsensusPayload[H, A]), + commit: make(map[uint16]ConsensusPayload[H, A]), } } -func newCache[H crypto.Hash, A crypto.Address]() cache[H, A] { +func newCache[H Hash, A Address]() cache[H, A] { return cache[H, A]{ mail: make(map[uint32]*inbox[H, A]), } @@ -43,7 +38,7 @@ func (c *cache[H, A]) getHeight(h uint32) *inbox[H, A] { return nil } -func (c *cache[H, A]) addMessage(m payload.ConsensusPayload[H, A]) { +func (c *cache[H, A]) addMessage(m ConsensusPayload[H, A]) { msgs, ok := c.mail[m.Height()] if !ok { msgs = newInbox[H, A]() @@ -51,11 +46,11 @@ func (c *cache[H, A]) addMessage(m payload.ConsensusPayload[H, A]) { } switch m.Type() { - case payload.PrepareRequestType, payload.PrepareResponseType: + case PrepareRequestType, PrepareResponseType: msgs.prepare[m.ValidatorIndex()] = m - case payload.ChangeViewType: + case ChangeViewType: msgs.chViews[m.ValidatorIndex()] = m - case payload.CommitType: + case CommitType: msgs.commit[m.ValidatorIndex()] = m } } diff --git a/helpers_test.go b/helpers_test.go index 10831ca2..710d571d 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -14,17 +14,17 @@ func TestMessageCache(t *testing.T) { p1 := payload.NewConsensusPayload() p1.SetHeight(3) - p1.SetType(payload.PrepareRequestType) + p1.SetType(PrepareRequestType) c.addMessage(p1) p2 := payload.NewConsensusPayload() p2.SetHeight(4) - p2.SetType(payload.ChangeViewType) + p2.SetType(ChangeViewType) c.addMessage(p2) p3 := payload.NewConsensusPayload() p3.SetHeight(4) - p3.SetType(payload.CommitType) + p3.SetType(CommitType) c.addMessage(p3) box := c.getHeight(3) diff --git a/identity.go b/identity.go new file mode 100644 index 00000000..420dc361 --- /dev/null +++ b/identity.go @@ -0,0 +1,44 @@ +package dbft + +import ( + "encoding" + "fmt" +) + +type ( + // PublicKey is a generic public key interface used by dbft. + PublicKey interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + + // Verify verifies if sig is indeed msg's signature. + Verify(msg, sig []byte) error + } + + // PrivateKey is a generic private key interface used by dbft. + PrivateKey interface { + // Sign returns msg's signature and error on failure. + Sign(msg []byte) (sig []byte, err error) + } + + // Hash is a generic hash interface used by dbft for payloads, blocks and + // transactions identification. It is recommended to implement this interface + // using hash functions with low hash collision probability. The following + // requirements must be met: + // 1. Hashes of two equal payloads/blocks/transactions are equal. + // 2. Hashes of two different payloads/blocks/transactions are different. + Hash interface { + comparable + fmt.Stringer + } + + // Address is a generic address interface used by dbft for operations related + // to consensus address. It is recommended to implement this interface + // using hash functions with low hash collision probability. The following + // requirements must be met: + // 1. Addresses of two equal sets of consensus members are equal. + // 2. Addresses of two different sets of consensus members are different. + Address interface { + comparable + } +) diff --git a/payload/change_view.go b/payload/change_view.go index 3024564d..036b53a0 100644 --- a/payload/change_view.go +++ b/payload/change_view.go @@ -2,29 +2,9 @@ package payload import ( "encoding/gob" + "github.com/nspcc-dev/dbft" ) -// ChangeView represents dBFT ChangeView message. -type ChangeView interface { - // NewViewNumber returns proposed view number. - NewViewNumber() byte - - // SetNewViewNumber sets the proposed view number. - SetNewViewNumber(view byte) - - // Timestamp returns message's timestamp. - Timestamp() uint64 - - // SetTimestamp sets message's timestamp. - SetTimestamp(ts uint64) - - // Reason returns change view reason. - Reason() ChangeViewReason - - // SetReason sets change view reason. - SetReason(reason ChangeViewReason) -} - type ( changeView struct { newViewNumber byte @@ -36,7 +16,7 @@ type ( } ) -var _ ChangeView = (*changeView)(nil) +var _ dbft.ChangeView = (*changeView)(nil) // EncodeBinary implements Serializable interface. func (c changeView) EncodeBinary(w *gob.Encoder) error { @@ -76,10 +56,10 @@ func (c *changeView) SetTimestamp(ts uint64) { } // Reason implements ChangeView interface. -func (c changeView) Reason() ChangeViewReason { - return CVUnknown +func (c changeView) Reason() dbft.ChangeViewReason { + return dbft.CVUnknown } // SetReason implements ChangeView interface. -func (c *changeView) SetReason(_ ChangeViewReason) { +func (c *changeView) SetReason(_ dbft.ChangeViewReason) { } diff --git a/payload/commit.go b/payload/commit.go index 64f7676e..62cca293 100644 --- a/payload/commit.go +++ b/payload/commit.go @@ -2,18 +2,9 @@ package payload import ( "encoding/gob" + "github.com/nspcc-dev/dbft" ) -// Commit is an interface for dBFT Commit message. -type Commit interface { - // Signature returns commit's signature field - // which is a block signature for the current epoch. - Signature() []byte - - // SetSignature sets commit's signature. - SetSignature(signature []byte) -} - type ( commit struct { signature [signatureSize]byte @@ -26,7 +17,7 @@ type ( const signatureSize = 64 -var _ Commit = (*commit)(nil) +var _ dbft.Commit = (*commit)(nil) // EncodeBinary implements Serializable interface. func (c commit) EncodeBinary(w *gob.Encoder) error { diff --git a/payload/consensus_message.go b/payload/consensus_message.go index 65d1d192..139411ef 100644 --- a/payload/consensus_message.go +++ b/payload/consensus_message.go @@ -3,54 +3,21 @@ package payload import ( "bytes" "encoding/gob" - "fmt" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" "github.com/pkg/errors" ) type ( - // MessageType is a type for dBFT consensus messages. - MessageType byte - // Serializable is an interface for serializing consensus messages. Serializable interface { EncodeBinary(encoder *gob.Encoder) error DecodeBinary(decoder *gob.Decoder) error } - consensusMessage[H crypto.Hash, A crypto.Address] interface { - // ViewNumber returns view number when this message was originated. - ViewNumber() byte - // SetViewNumber sets view number. - SetViewNumber(view byte) - - // Type returns type of this message. - Type() MessageType - // SetType sets the type of this message. - SetType(t MessageType) - - // Payload returns this message's actual payload. - Payload() any - // SetPayload sets this message's payload to p. - SetPayload(p any) - - // GetChangeView returns payload as if it was ChangeView. - GetChangeView() ChangeView - // GetPrepareRequest returns payload as if it was PrepareRequest. - GetPrepareRequest() PrepareRequest[H, A] - // GetPrepareResponse returns payload as if it was PrepareResponse. - GetPrepareResponse() PrepareResponse[H] - // GetCommit returns payload as if it was Commit. - GetCommit() Commit - // GetRecoveryRequest returns payload as if it was RecoveryRequest. - GetRecoveryRequest() RecoveryRequest - // GetRecoveryMessage returns payload as if it was RecoveryMessage. - GetRecoveryMessage() RecoveryMessage[H, A] - } - message struct { - cmType MessageType + cmType dbft.MessageType viewNumber byte payload any @@ -64,37 +31,7 @@ type ( } ) -// 6 following constants enumerate all possible type of consensus message. -const ( - ChangeViewType MessageType = 0x00 - PrepareRequestType MessageType = 0x20 - PrepareResponseType MessageType = 0x21 - CommitType MessageType = 0x30 - RecoveryRequestType MessageType = 0x40 - RecoveryMessageType MessageType = 0x41 -) - -var _ consensusMessage[crypto.Uint256, crypto.Uint160] = (*message)(nil) - -// String implements fmt.Stringer interface. -func (m MessageType) String() string { - switch m { - case ChangeViewType: - return "ChangeView" - case PrepareRequestType: - return "PrepareRequest" - case PrepareResponseType: - return "PrepareResponse" - case CommitType: - return "Commit" - case RecoveryRequestType: - return "RecoveryRequest" - case RecoveryMessageType: - return "RecoveryMessage" - default: - return fmt.Sprintf("UNKNOWN(%02x)", byte(m)) - } -} +var _ dbft.ConsensusMessage[crypto.Uint256, crypto.Uint160] = (*message)(nil) // EncodeBinary implements Serializable interface. func (m message) EncodeBinary(w *gob.Encoder) error { @@ -116,23 +53,23 @@ func (m *message) DecodeBinary(r *gob.Decoder) error { if err := r.Decode(aux); err != nil { return err } - m.cmType = MessageType(aux.CMType) + m.cmType = dbft.MessageType(aux.CMType) m.viewNumber = aux.ViewNumber switch m.cmType { - case ChangeViewType: + case dbft.ChangeViewType: cv := new(changeView) cv.newViewNumber = m.viewNumber + 1 m.payload = cv - case PrepareRequestType: + case dbft.PrepareRequestType: m.payload = new(prepareRequest) - case PrepareResponseType: + case dbft.PrepareResponseType: m.payload = new(prepareResponse) - case CommitType: + case dbft.CommitType: m.payload = new(commit) - case RecoveryRequestType: + case dbft.RecoveryRequestType: m.payload = new(recoveryRequest) - case RecoveryMessageType: + case dbft.RecoveryMessageType: m.payload = new(recoveryMessage) default: return errors.Errorf("invalid type: 0x%02x", byte(m.cmType)) @@ -143,17 +80,17 @@ func (m *message) DecodeBinary(r *gob.Decoder) error { return m.payload.(Serializable).DecodeBinary(dec) } -func (m message) GetChangeView() ChangeView { return m.payload.(ChangeView) } -func (m message) GetPrepareRequest() PrepareRequest[crypto.Uint256, crypto.Uint160] { - return m.payload.(PrepareRequest[crypto.Uint256, crypto.Uint160]) +func (m message) GetChangeView() dbft.ChangeView { return m.payload.(dbft.ChangeView) } +func (m message) GetPrepareRequest() dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] { + return m.payload.(dbft.PrepareRequest[crypto.Uint256, crypto.Uint160]) } -func (m message) GetPrepareResponse() PrepareResponse[crypto.Uint256] { - return m.payload.(PrepareResponse[crypto.Uint256]) +func (m message) GetPrepareResponse() dbft.PrepareResponse[crypto.Uint256] { + return m.payload.(dbft.PrepareResponse[crypto.Uint256]) } -func (m message) GetCommit() Commit { return m.payload.(Commit) } -func (m message) GetRecoveryRequest() RecoveryRequest { return m.payload.(RecoveryRequest) } -func (m message) GetRecoveryMessage() RecoveryMessage[crypto.Uint256, crypto.Uint160] { - return m.payload.(RecoveryMessage[crypto.Uint256, crypto.Uint160]) +func (m message) GetCommit() dbft.Commit { return m.payload.(dbft.Commit) } +func (m message) GetRecoveryRequest() dbft.RecoveryRequest { return m.payload.(dbft.RecoveryRequest) } +func (m message) GetRecoveryMessage() dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { + return m.payload.(dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160]) } // ViewNumber implements ConsensusMessage interface. @@ -167,12 +104,12 @@ func (m *message) SetViewNumber(view byte) { } // Type implements ConsensusMessage interface. -func (m message) Type() MessageType { +func (m message) Type() dbft.MessageType { return m.cmType } // SetType implements ConsensusMessage interface. -func (m *message) SetType(t MessageType) { +func (m *message) SetType(t dbft.MessageType) { m.cmType = t } diff --git a/payload/constructors.go b/payload/constructors.go index 696c3a64..bf1e81bd 100644 --- a/payload/constructors.go +++ b/payload/constructors.go @@ -1,39 +1,42 @@ package payload -import "github.com/nspcc-dev/dbft/crypto" +import ( + "github.com/nspcc-dev/dbft" + "github.com/nspcc-dev/dbft/crypto" +) // NewConsensusPayload returns minimal ConsensusPayload implementation. -func NewConsensusPayload() ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func NewConsensusPayload() dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { return &Payload{} } // NewPrepareRequest returns minimal prepareRequest implementation. -func NewPrepareRequest() PrepareRequest[crypto.Uint256, crypto.Uint160] { +func NewPrepareRequest() dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] { return new(prepareRequest) } // NewPrepareResponse returns minimal PrepareResponse implementation. -func NewPrepareResponse() PrepareResponse[crypto.Uint256] { +func NewPrepareResponse() dbft.PrepareResponse[crypto.Uint256] { return new(prepareResponse) } // NewChangeView returns minimal ChangeView implementation. -func NewChangeView() ChangeView { +func NewChangeView() dbft.ChangeView { return new(changeView) } // NewCommit returns minimal Commit implementation. -func NewCommit() Commit { +func NewCommit() dbft.Commit { return new(commit) } // NewRecoveryRequest returns minimal RecoveryRequest implementation. -func NewRecoveryRequest() RecoveryRequest { +func NewRecoveryRequest() dbft.RecoveryRequest { return new(recoveryRequest) } // NewRecoveryMessage returns minimal RecoveryMessage implementation. -func NewRecoveryMessage() RecoveryMessage[crypto.Uint256, crypto.Uint160] { +func NewRecoveryMessage() dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { return &recoveryMessage{ preparationPayloads: make([]preparationCompact, 0), commitPayloads: make([]commitCompact, 0), diff --git a/payload/message.go b/payload/message.go index 17e3a5ce..e07f3bb8 100644 --- a/payload/message.go +++ b/payload/message.go @@ -3,30 +3,12 @@ package payload import ( "bytes" "encoding/gob" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" ) type ( - // ConsensusPayload is a generic payload type which is exchanged - // between the nodes. - ConsensusPayload[H crypto.Hash, A crypto.Address] interface { - consensusMessage[H, A] - - // ValidatorIndex returns index of validator from which - // payload was originated from. - ValidatorIndex() uint16 - - // SetValidatorIndex sets validator index. - SetValidatorIndex(i uint16) - - Height() uint32 - SetHeight(h uint32) - - // Hash returns 32-byte checksum of the payload. - Hash() H - } - // Payload represents minimal payload containing all necessary fields. Payload struct { message @@ -50,7 +32,7 @@ type ( } ) -var _ ConsensusPayload[crypto.Uint256, crypto.Uint160] = (*Payload)(nil) +var _ dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] = (*Payload)(nil) // EncodeBinary implements Serializable interface. func (p Payload) EncodeBinary(w *gob.Encoder) error { diff --git a/payload/message_test.go b/payload/message_test.go index 24e1ef56..22fe9f3c 100644 --- a/payload/message_test.go +++ b/payload/message_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rand" "encoding/gob" + "github.com/nspcc-dev/dbft" "testing" "github.com/nspcc-dev/dbft/crypto" @@ -20,7 +21,7 @@ func TestPayload_EncodeDecode(t *testing.T) { m.SetViewNumber(3) t.Run("PrepareRequest", func(t *testing.T) { - m.SetType(PrepareRequestType) + m.SetType(dbft.PrepareRequestType) m.SetPayload(&prepareRequest{ nonce: 123, timestamp: 345, @@ -35,7 +36,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("PrepareResponse", func(t *testing.T) { - m.SetType(PrepareResponseType) + m.SetType(dbft.PrepareResponseType) m.SetPayload(&prepareResponse{ preparationHash: crypto.Uint256{3}, }) @@ -45,7 +46,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("Commit", func(t *testing.T) { - m.SetType(CommitType) + m.SetType(dbft.CommitType) var cc commit fillRandom(t, cc.signature[:]) m.SetPayload(&cc) @@ -55,7 +56,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("ChangeView", func(t *testing.T) { - m.SetType(ChangeViewType) + m.SetType(dbft.ChangeViewType) m.SetPayload(&changeView{ timestamp: 12345, newViewNumber: 4, @@ -66,7 +67,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("RecoveryMessage", func(t *testing.T) { - m.SetType(RecoveryMessageType) + m.SetType(dbft.RecoveryMessageType) m.SetPayload(&recoveryMessage{ changeViewPayloads: []changeViewCompact{ { @@ -96,7 +97,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("RecoveryRequest", func(t *testing.T) { - m.SetType(RecoveryRequestType) + m.SetType(dbft.RecoveryRequestType) m.SetPayload(&recoveryRequest{ timestamp: 17334, }) @@ -115,17 +116,17 @@ func TestRecoveryMessage_NoPayloads(t *testing.T) { m.SetViewNumber(3) m.SetPayload(&recoveryMessage{}) - validators := make([]crypto.PublicKey, 1) + validators := make([]dbft.PublicKey, 1) _, validators[0] = crypto.Generate(rand.Reader) rec := m.GetRecoveryMessage() require.NotNil(t, rec) - var p ConsensusPayload[crypto.Uint256, crypto.Uint160] + var p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] require.NotPanics(t, func() { p = rec.GetPrepareRequest(p, validators, 0) }) require.Nil(t, p) - var ps []ConsensusPayload[crypto.Uint256, crypto.Uint160] + var ps []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] require.NotPanics(t, func() { ps = rec.GetPrepareResponses(p, validators) }) require.Len(t, ps, 0) @@ -193,12 +194,12 @@ func TestPayload_Setters(t *testing.T) { } func TestMessageType_String(t *testing.T) { - require.Equal(t, "ChangeView", ChangeViewType.String()) - require.Equal(t, "PrepareRequest", PrepareRequestType.String()) - require.Equal(t, "PrepareResponse", PrepareResponseType.String()) - require.Equal(t, "Commit", CommitType.String()) - require.Equal(t, "RecoveryRequest", RecoveryRequestType.String()) - require.Equal(t, "RecoveryMessage", RecoveryMessageType.String()) + require.Equal(t, "ChangeView", dbft.ChangeViewType.String()) + require.Equal(t, "PrepareRequest", dbft.PrepareRequestType.String()) + require.Equal(t, "PrepareResponse", dbft.PrepareResponseType.String()) + require.Equal(t, "Commit", dbft.CommitType.String()) + require.Equal(t, "RecoveryRequest", dbft.RecoveryRequestType.String()) + require.Equal(t, "RecoveryMessage", dbft.RecoveryMessageType.String()) } func testEncodeDecode(t *testing.T, expected, actual Serializable) { diff --git a/payload/prepare_request.go b/payload/prepare_request.go index e34e459f..9098cfb0 100644 --- a/payload/prepare_request.go +++ b/payload/prepare_request.go @@ -2,34 +2,11 @@ package payload import ( "encoding/gob" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" ) -// PrepareRequest represents dBFT PrepareRequest message. -type PrepareRequest[H crypto.Hash, A crypto.Address] interface { - // Timestamp returns this message's timestamp. - Timestamp() uint64 - // SetTimestamp sets timestamp of this message. - SetTimestamp(ts uint64) - - // Nonce is a random nonce. - Nonce() uint64 - // SetNonce sets Nonce. - SetNonce(nonce uint64) - - // TransactionHashes returns hashes of all transaction in a proposed block. - TransactionHashes() []H - // SetTransactionHashes sets transaction's hashes. - SetTransactionHashes(hs []H) - - // NextConsensus returns hash which is based on which validators will - // try to agree on a block in the current epoch. - NextConsensus() A - // SetNextConsensus sets next consensus field. - SetNextConsensus(nc A) -} - type ( prepareRequest struct { transactionHashes []crypto.Uint256 @@ -46,7 +23,7 @@ type ( } ) -var _ PrepareRequest[crypto.Uint256, crypto.Uint160] = (*prepareRequest)(nil) +var _ dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] = (*prepareRequest)(nil) // EncodeBinary implements Serializable interface. func (p prepareRequest) EncodeBinary(w *gob.Encoder) error { diff --git a/payload/prepare_response.go b/payload/prepare_response.go index 42e32d44..f66f0c97 100644 --- a/payload/prepare_response.go +++ b/payload/prepare_response.go @@ -2,19 +2,11 @@ package payload import ( "encoding/gob" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" ) -// PrepareResponse represents dBFT PrepareResponse message. -type PrepareResponse[H crypto.Hash] interface { - // PreparationHash returns the hash of PrepareRequest payload - // for this epoch. - PreparationHash() H - // SetPreparationHash sets preparations hash. - SetPreparationHash(h H) -} - type ( prepareResponse struct { preparationHash crypto.Uint256 @@ -25,7 +17,7 @@ type ( } ) -var _ PrepareResponse[crypto.Uint256] = (*prepareResponse)(nil) +var _ dbft.PrepareResponse[crypto.Uint256] = (*prepareResponse)(nil) // EncodeBinary implements Serializable interface. func (p prepareResponse) EncodeBinary(w *gob.Encoder) error { diff --git a/payload/recovery_message.go b/payload/recovery_message.go index 4292d34a..1491aa0f 100644 --- a/payload/recovery_message.go +++ b/payload/recovery_message.go @@ -3,37 +3,18 @@ package payload import ( "encoding/gob" "errors" + "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/crypto" ) type ( - // RecoveryMessage represents dBFT Recovery message. - RecoveryMessage[H crypto.Hash, A crypto.Address] interface { - // AddPayload adds payload from this epoch to be recovered. - AddPayload(p ConsensusPayload[H, A]) - // GetPrepareRequest returns PrepareRequest to be processed. - GetPrepareRequest(p ConsensusPayload[H, A], validators []crypto.PublicKey, primary uint16) ConsensusPayload[H, A] - // GetPrepareResponses returns a slice of PrepareResponse in any order. - GetPrepareResponses(p ConsensusPayload[H, A], validators []crypto.PublicKey) []ConsensusPayload[H, A] - // GetChangeViews returns a slice of ChangeView in any order. - GetChangeViews(p ConsensusPayload[H, A], validators []crypto.PublicKey) []ConsensusPayload[H, A] - // GetCommits returns a slice of Commit in any order. - GetCommits(p ConsensusPayload[H, A], validators []crypto.PublicKey) []ConsensusPayload[H, A] - - // PreparationHash returns has of PrepareRequest payload for this epoch. - // It can be useful in case only PrepareResponse payloads were received. - PreparationHash() *H - // SetPreparationHash sets preparation hash. - SetPreparationHash(h *H) - } - recoveryMessage struct { preparationHash *crypto.Uint256 preparationPayloads []preparationCompact commitPayloads []commitCompact changeViewPayloads []changeViewCompact - prepareRequest PrepareRequest[crypto.Uint256, crypto.Uint160] + prepareRequest dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] } // recoveryMessageAux is an auxiliary structure for recoveryMessage encoding. recoveryMessageAux struct { @@ -43,7 +24,7 @@ type ( } ) -var _ RecoveryMessage[crypto.Uint256, crypto.Uint160] = (*recoveryMessage)(nil) +var _ dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] = (*recoveryMessage)(nil) // PreparationHash implements RecoveryMessage interface. func (m *recoveryMessage) PreparationHash() *crypto.Uint256 { @@ -56,23 +37,23 @@ func (m *recoveryMessage) SetPreparationHash(h *crypto.Uint256) { } // AddPayload implements RecoveryMessage interface. -func (m *recoveryMessage) AddPayload(p ConsensusPayload[crypto.Uint256, crypto.Uint160]) { +func (m *recoveryMessage) AddPayload(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) { switch p.Type() { - case PrepareRequestType: + case dbft.PrepareRequestType: m.prepareRequest = p.GetPrepareRequest() prepHash := p.Hash() m.preparationHash = &prepHash - case PrepareResponseType: + case dbft.PrepareResponseType: m.preparationPayloads = append(m.preparationPayloads, preparationCompact{ ValidatorIndex: p.ValidatorIndex(), }) - case ChangeViewType: + case dbft.ChangeViewType: m.changeViewPayloads = append(m.changeViewPayloads, changeViewCompact{ ValidatorIndex: p.ValidatorIndex(), OriginalViewNumber: p.ViewNumber(), Timestamp: 0, }) - case CommitType: + case dbft.CommitType: cc := commitCompact{ ViewNumber: p.ViewNumber(), ValidatorIndex: p.ValidatorIndex(), @@ -82,7 +63,7 @@ func (m *recoveryMessage) AddPayload(p ConsensusPayload[crypto.Uint256, crypto.U } } -func fromPayload(t MessageType, recovery ConsensusPayload[crypto.Uint256, crypto.Uint160], p Serializable) *Payload { +func fromPayload(t dbft.MessageType, recovery dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], p Serializable) *Payload { return &Payload{ message: message{ cmType: t, @@ -94,12 +75,12 @@ func fromPayload(t MessageType, recovery ConsensusPayload[crypto.Uint256, crypto } // GetPrepareRequest implements RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareRequest(p ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []crypto.PublicKey, ind uint16) ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey, ind uint16) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { if m.prepareRequest == nil { return nil } - req := fromPayload(PrepareRequestType, p, &prepareRequest{ + req := fromPayload(dbft.PrepareRequestType, p, &prepareRequest{ // prepareRequest.Timestamp() here returns nanoseconds-precision value, so convert it to seconds again timestamp: nanoSecToSec(m.prepareRequest.Timestamp()), nonce: m.prepareRequest.Nonce(), @@ -112,15 +93,15 @@ func (m *recoveryMessage) GetPrepareRequest(p ConsensusPayload[crypto.Uint256, c } // GetPrepareResponses implements RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareResponses(p ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []crypto.PublicKey) []ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func (m *recoveryMessage) GetPrepareResponses(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { if m.preparationHash == nil { return nil } - payloads := make([]ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.preparationPayloads)) + payloads := make([]dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.preparationPayloads)) for i, resp := range m.preparationPayloads { - payloads[i] = fromPayload(PrepareResponseType, p, &prepareResponse{ + payloads[i] = fromPayload(dbft.PrepareResponseType, p, &prepareResponse{ preparationHash: *m.preparationHash, }) payloads[i].SetValidatorIndex(resp.ValidatorIndex) @@ -130,11 +111,11 @@ func (m *recoveryMessage) GetPrepareResponses(p ConsensusPayload[crypto.Uint256, } // GetChangeViews implements RecoveryMessage interface. -func (m *recoveryMessage) GetChangeViews(p ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []crypto.PublicKey) []ConsensusPayload[crypto.Uint256, crypto.Uint160] { - payloads := make([]ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.changeViewPayloads)) +func (m *recoveryMessage) GetChangeViews(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { + payloads := make([]dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.changeViewPayloads)) for i, cv := range m.changeViewPayloads { - payloads[i] = fromPayload(ChangeViewType, p, &changeView{ + payloads[i] = fromPayload(dbft.ChangeViewType, p, &changeView{ newViewNumber: cv.OriginalViewNumber + 1, timestamp: cv.Timestamp, }) @@ -145,11 +126,11 @@ func (m *recoveryMessage) GetChangeViews(p ConsensusPayload[crypto.Uint256, cryp } // GetCommits implements RecoveryMessage interface. -func (m *recoveryMessage) GetCommits(p ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []crypto.PublicKey) []ConsensusPayload[crypto.Uint256, crypto.Uint160] { - payloads := make([]ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.commitPayloads)) +func (m *recoveryMessage) GetCommits(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { + payloads := make([]dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.commitPayloads)) for i, c := range m.commitPayloads { - payloads[i] = fromPayload(CommitType, p, &commit{signature: c.Signature}) + payloads[i] = fromPayload(dbft.CommitType, p, &commit{signature: c.Signature}) payloads[i].SetValidatorIndex(c.ValidatorIndex) } diff --git a/payload/recovery_request.go b/payload/recovery_request.go index 4a8f1cc7..0cc73930 100644 --- a/payload/recovery_request.go +++ b/payload/recovery_request.go @@ -2,16 +2,9 @@ package payload import ( "encoding/gob" + "github.com/nspcc-dev/dbft" ) -// RecoveryRequest represents dBFT RecoveryRequest message. -type RecoveryRequest interface { - // Timestamp returns this message's timestamp. - Timestamp() uint64 - // SetTimestamp sets this message's timestamp. - SetTimestamp(ts uint64) -} - type ( recoveryRequest struct { timestamp uint32 @@ -22,7 +15,7 @@ type ( } ) -var _ RecoveryRequest = (*recoveryRequest)(nil) +var _ dbft.RecoveryRequest = (*recoveryRequest)(nil) // EncodeBinary implements Serializable interface. func (m recoveryRequest) EncodeBinary(w *gob.Encoder) error { diff --git a/prepare_request.go b/prepare_request.go new file mode 100644 index 00000000..9f96be16 --- /dev/null +++ b/prepare_request.go @@ -0,0 +1,25 @@ +package dbft + +// PrepareRequest represents dBFT PrepareRequest message. +type PrepareRequest[H Hash, A Address] interface { + // Timestamp returns this message's timestamp. + Timestamp() uint64 + // SetTimestamp sets timestamp of this message. + SetTimestamp(ts uint64) + + // Nonce is a random nonce. + Nonce() uint64 + // SetNonce sets Nonce. + SetNonce(nonce uint64) + + // TransactionHashes returns hashes of all transaction in a proposed block. + TransactionHashes() []H + // SetTransactionHashes sets transaction's hashes. + SetTransactionHashes(hs []H) + + // NextConsensus returns hash which is based on which validators will + // try to agree on a block in the current epoch. + NextConsensus() A + // SetNextConsensus sets next consensus field. + SetNextConsensus(nc A) +} diff --git a/prepare_response.go b/prepare_response.go new file mode 100644 index 00000000..917e1e2c --- /dev/null +++ b/prepare_response.go @@ -0,0 +1,10 @@ +package dbft + +// PrepareResponse represents dBFT PrepareResponse message. +type PrepareResponse[H Hash] interface { + // PreparationHash returns the hash of PrepareRequest payload + // for this epoch. + PreparationHash() H + // SetPreparationHash sets preparations hash. + SetPreparationHash(h H) +} diff --git a/recovery_message.go b/recovery_message.go new file mode 100644 index 00000000..be14aa2c --- /dev/null +++ b/recovery_message.go @@ -0,0 +1,21 @@ +package dbft + +// RecoveryMessage represents dBFT Recovery message. +type RecoveryMessage[H Hash, A Address] interface { + // AddPayload adds payload from this epoch to be recovered. + AddPayload(p ConsensusPayload[H, A]) + // GetPrepareRequest returns PrepareRequest to be processed. + GetPrepareRequest(p ConsensusPayload[H, A], validators []PublicKey, primary uint16) ConsensusPayload[H, A] + // GetPrepareResponses returns a slice of PrepareResponse in any order. + GetPrepareResponses(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + // GetChangeViews returns a slice of ChangeView in any order. + GetChangeViews(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + // GetCommits returns a slice of Commit in any order. + GetCommits(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + + // PreparationHash returns has of PrepareRequest payload for this epoch. + // It can be useful in case only PrepareResponse payloads were received. + PreparationHash() *H + // SetPreparationHash sets preparation hash. + SetPreparationHash(h *H) +} diff --git a/recovery_request.go b/recovery_request.go new file mode 100644 index 00000000..08d74137 --- /dev/null +++ b/recovery_request.go @@ -0,0 +1,9 @@ +package dbft + +// RecoveryRequest represents dBFT RecoveryRequest message. +type RecoveryRequest interface { + // Timestamp returns this message's timestamp. + Timestamp() uint64 + // SetTimestamp sets this message's timestamp. + SetTimestamp(ts uint64) +} diff --git a/send.go b/send.go index 64f71bca..a48fc4bb 100644 --- a/send.go +++ b/send.go @@ -1,11 +1,10 @@ package dbft import ( - "github.com/nspcc-dev/dbft/payload" "go.uber.org/zap" ) -func (d *DBFT[H, A]) broadcast(msg payload.ConsensusPayload[H, A]) { +func (d *DBFT[H, A]) broadcast(msg ConsensusPayload[H, A]) { d.Logger.Debug("broadcasting message", zap.Stringer("type", msg.Type()), zap.Uint32("height", d.BlockIndex), @@ -15,7 +14,7 @@ func (d *DBFT[H, A]) broadcast(msg payload.ConsensusPayload[H, A]) { d.Broadcast(msg) } -func (c *Context[H, A]) makePrepareRequest() payload.ConsensusPayload[H, A] { +func (c *Context[H, A]) makePrepareRequest() ConsensusPayload[H, A] { c.Fill() req := c.Config.NewPrepareRequest() @@ -24,7 +23,7 @@ func (c *Context[H, A]) makePrepareRequest() payload.ConsensusPayload[H, A] { req.SetNextConsensus(c.NextConsensus) req.SetTransactionHashes(c.TransactionHashes) - return c.Config.NewConsensusPayload(c, payload.PrepareRequestType, req) + return c.Config.NewConsensusPayload(c, PrepareRequestType, req) } func (d *DBFT[H, A]) sendPrepareRequest() { @@ -42,19 +41,19 @@ func (d *DBFT[H, A]) sendPrepareRequest() { d.checkPrepare() } -func (c *Context[H, A]) makeChangeView(ts uint64, reason payload.ChangeViewReason) payload.ConsensusPayload[H, A] { +func (c *Context[H, A]) makeChangeView(ts uint64, reason ChangeViewReason) ConsensusPayload[H, A] { cv := c.Config.NewChangeView() cv.SetNewViewNumber(c.ViewNumber + 1) cv.SetTimestamp(ts) cv.SetReason(reason) - msg := c.Config.NewConsensusPayload(c, payload.ChangeViewType, cv) + msg := c.Config.NewConsensusPayload(c, ChangeViewType, cv) c.ChangeViewPayloads[c.MyIndex] = msg return msg } -func (d *DBFT[H, A]) sendChangeView(reason payload.ChangeViewReason) { +func (d *DBFT[H, A]) sendChangeView(reason ChangeViewReason) { if d.Context.WatchOnly() { return } @@ -65,7 +64,7 @@ func (d *DBFT[H, A]) sendChangeView(reason payload.ChangeViewReason) { nc := d.CountCommitted() nf := d.CountFailed() - if reason == payload.CVTimeout && nc+nf > d.F() { + if reason == CVTimeout && nc+nf > d.F() { d.Logger.Info("skip change view", zap.Int("nc", nc), zap.Int("nf", nf)) d.sendRecoveryRequest() @@ -73,8 +72,8 @@ func (d *DBFT[H, A]) sendChangeView(reason payload.ChangeViewReason) { } // Timeout while missing transactions, set the real reason. - if !d.hasAllTransactions() && reason == payload.CVTimeout { - reason = payload.CVTxNotFound + if !d.hasAllTransactions() && reason == CVTimeout { + reason = CVTxNotFound } d.Logger.Info("request change view", @@ -91,11 +90,11 @@ func (d *DBFT[H, A]) sendChangeView(reason payload.ChangeViewReason) { d.checkChangeView(newView) } -func (c *Context[H, A]) makePrepareResponse() payload.ConsensusPayload[H, A] { +func (c *Context[H, A]) makePrepareResponse() ConsensusPayload[H, A] { resp := c.Config.NewPrepareResponse() resp.SetPreparationHash(c.PreparationPayloads[c.PrimaryIndex].Hash()) - msg := c.Config.NewConsensusPayload(c, payload.PrepareResponseType, resp) + msg := c.Config.NewConsensusPayload(c, PrepareResponseType, resp) c.PreparationPayloads[c.MyIndex] = msg return msg @@ -108,7 +107,7 @@ func (d *DBFT[H, A]) sendPrepareResponse() { d.broadcast(msg) } -func (c *Context[H, A]) makeCommit() payload.ConsensusPayload[H, A] { +func (c *Context[H, A]) makeCommit() ConsensusPayload[H, A] { if msg := c.CommitPayloads[c.MyIndex]; msg != nil { return msg } @@ -122,7 +121,7 @@ func (c *Context[H, A]) makeCommit() payload.ConsensusPayload[H, A] { commit := c.Config.NewCommit() commit.SetSignature(sign) - return c.Config.NewConsensusPayload(c, payload.CommitType, commit) + return c.Config.NewConsensusPayload(c, CommitType, commit) } return nil @@ -143,10 +142,10 @@ func (d *DBFT[H, A]) sendRecoveryRequest() { } req := d.NewRecoveryRequest() req.SetTimestamp(uint64(d.Timer.Now().UnixNano())) - d.broadcast(d.Config.NewConsensusPayload(&d.Context, payload.RecoveryRequestType, req)) + d.broadcast(d.Config.NewConsensusPayload(&d.Context, RecoveryRequestType, req)) } -func (c *Context[H, A]) makeRecoveryMessage() payload.ConsensusPayload[H, A] { +func (c *Context[H, A]) makeRecoveryMessage() ConsensusPayload[H, A] { recovery := c.Config.NewRecoveryMessage() for _, p := range c.PreparationPayloads { @@ -173,7 +172,7 @@ func (c *Context[H, A]) makeRecoveryMessage() payload.ConsensusPayload[H, A] { } } - return c.Config.NewConsensusPayload(c, payload.RecoveryMessageType, recovery) + return c.Config.NewConsensusPayload(c, RecoveryMessageType, recovery) } func (d *DBFT[H, A]) sendRecoveryMessage() { diff --git a/simulation/main.go b/simulation/main.go index 8482bfd2..bf776ec5 100644 --- a/simulation/main.go +++ b/simulation/main.go @@ -7,6 +7,7 @@ import ( "errors" "flag" "fmt" + "github.com/nspcc-dev/dbft/block" "net/http" "net/http/pprof" "os" @@ -17,7 +18,6 @@ import ( "time" "github.com/nspcc-dev/dbft" - "github.com/nspcc-dev/dbft/block" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/dbft/payload" "github.com/spaolacci/murmur3" @@ -28,16 +28,16 @@ type ( simNode struct { id int d *dbft.DBFT[crypto.Uint256, crypto.Uint160] - messages chan payload.ConsensusPayload[crypto.Uint256, crypto.Uint160] - key crypto.PrivateKey - pub crypto.PublicKey + messages chan dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] + key dbft.PrivateKey + pub dbft.PublicKey pool *memPool cluster []*simNode log *zap.Logger height uint32 lastHash crypto.Uint256 - validators []crypto.PublicKey + validators []dbft.PublicKey } ) @@ -110,9 +110,17 @@ func initNodes(nodes []*simNode, log *zap.Logger) { } } +func newBlockFromContext(ctx *dbft.Context[crypto.Uint256, crypto.Uint160]) dbft.Block[crypto.Uint256, crypto.Uint160] { + if ctx.TransactionHashes == nil { + return nil + } + block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.NextConsensus, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) + return block +} + // defaultNewConsensusPayload is default function for creating // consensus payload of specific type. -func defaultNewConsensusPayload(c *dbft.Context[crypto.Uint256, crypto.Uint160], t payload.MessageType, msg any) payload.ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func defaultNewConsensusPayload(c *dbft.Context[crypto.Uint256, crypto.Uint160], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { cp := payload.NewConsensusPayload() cp.SetHeight(c.BlockIndex) cp.SetValidatorIndex(uint16(c.MyIndex)) @@ -127,7 +135,7 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { key, pub := crypto.Generate(rand.Reader) nodes[i] = &simNode{ id: i, - messages: make(chan payload.ConsensusPayload[crypto.Uint256, crypto.Uint160], defaultChanSize), + messages: make(chan dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], defaultChanSize), key: key, pub: pub, pool: newMemoryPool(), @@ -149,8 +157,8 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { dbft.WithVerifyPrepareRequest[crypto.Uint256, crypto.Uint160](nodes[i].VerifyPayload), dbft.WithVerifyPrepareResponse[crypto.Uint256, crypto.Uint160](nodes[i].VerifyPayload), - dbft.WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](dbft.NewBlockFromContext), - dbft.WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](func(...crypto.PublicKey) crypto.Uint160 { return crypto.Uint160{} }), + dbft.WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](newBlockFromContext), + dbft.WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](func(...dbft.PublicKey) crypto.Uint160 { return crypto.Uint160{} }), dbft.WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](defaultNewConsensusPayload), dbft.WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](payload.NewPrepareRequest), dbft.WithNewPrepareResponse[crypto.Uint256, crypto.Uint160](payload.NewPrepareResponse), @@ -170,7 +178,7 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { } func updatePublicKeys(nodes []*simNode, n int) { - pubs := make([]crypto.PublicKey, n) + pubs := make([]dbft.PublicKey, n) for i := range pubs { pubs[i] = nodes[i].pub } @@ -182,7 +190,7 @@ func updatePublicKeys(nodes []*simNode, n int) { } } -func sortValidators(pubs []crypto.PublicKey) { +func sortValidators(pubs []dbft.PublicKey) { sort.Slice(pubs, func(i, j int) bool { p1, _ := pubs[i].MarshalBinary() p2, _ := pubs[j].MarshalBinary() @@ -190,7 +198,7 @@ func sortValidators(pubs []crypto.PublicKey) { }) } -func (n *simNode) Broadcast(m payload.ConsensusPayload[crypto.Uint256, crypto.Uint160]) { +func (n *simNode) Broadcast(m dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) { for i, node := range n.cluster { if i != n.id { select { @@ -206,11 +214,11 @@ func (n *simNode) CurrentHeight() uint32 { return n.height } func (n *simNode) CurrentBlockHash() crypto.Uint256 { return n.lastHash } // GetValidators always returns the same list of validators. -func (n *simNode) GetValidators(...block.Transaction[crypto.Uint256]) []crypto.PublicKey { +func (n *simNode) GetValidators(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey { return n.validators } -func (n *simNode) ProcessBlock(b block.Block[crypto.Uint256, crypto.Uint160]) { +func (n *simNode) ProcessBlock(b dbft.Block[crypto.Uint256, crypto.Uint160]) { n.d.Logger.Debug("received block", zap.Uint32("height", b.Index())) for _, tx := range b.Transactions() { @@ -222,7 +230,7 @@ func (n *simNode) ProcessBlock(b block.Block[crypto.Uint256, crypto.Uint160]) { } // VerifyPrepareRequest verifies that payload was received from a good validator. -func (n *simNode) VerifyPayload(p payload.ConsensusPayload[crypto.Uint256, crypto.Uint160]) error { +func (n *simNode) VerifyPayload(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) error { if *blocked != -1 && p.ValidatorIndex() == uint16(*blocked) { return fmt.Errorf("message from blocked validator: %d", *blocked) } @@ -242,7 +250,7 @@ func (n *simNode) addTx(count int) { type tx64 uint64 -var _ block.Transaction[crypto.Uint256] = (*tx64)(nil) +var _ dbft.Transaction[crypto.Uint256] = (*tx64)(nil) func (t *tx64) Hash() (h crypto.Uint256) { binary.LittleEndian.PutUint64(h[:], uint64(*t)) @@ -274,17 +282,17 @@ func (t *tx64) UnmarshalBinary(data []byte) error { type memPool struct { mtx *sync.RWMutex - store map[crypto.Uint256]block.Transaction[crypto.Uint256] + store map[crypto.Uint256]dbft.Transaction[crypto.Uint256] } func newMemoryPool() *memPool { return &memPool{ mtx: new(sync.RWMutex), - store: make(map[crypto.Uint256]block.Transaction[crypto.Uint256]), + store: make(map[crypto.Uint256]dbft.Transaction[crypto.Uint256]), } } -func (p *memPool) Add(tx block.Transaction[crypto.Uint256]) { +func (p *memPool) Add(tx dbft.Transaction[crypto.Uint256]) { p.mtx.Lock() h := tx.Hash() @@ -295,7 +303,7 @@ func (p *memPool) Add(tx block.Transaction[crypto.Uint256]) { p.mtx.Unlock() } -func (p *memPool) Get(h crypto.Uint256) (tx block.Transaction[crypto.Uint256]) { +func (p *memPool) Get(h crypto.Uint256) (tx dbft.Transaction[crypto.Uint256]) { p.mtx.RLock() tx = p.store[h] p.mtx.RUnlock() @@ -309,13 +317,13 @@ func (p *memPool) Delete(h crypto.Uint256) { p.mtx.Unlock() } -func (p *memPool) GetVerified() (txx []block.Transaction[crypto.Uint256]) { +func (p *memPool) GetVerified() (txx []dbft.Transaction[crypto.Uint256]) { n := *txPerBlock if n == 0 { return } - txx = make([]block.Transaction[crypto.Uint256], 0, n) + txx = make([]dbft.Transaction[crypto.Uint256], 0, n) for _, tx := range p.store { txx = append(txx, tx) diff --git a/block/transaction.go b/transaction.go similarity index 63% rename from block/transaction.go rename to transaction.go index cb15ceeb..ae3277c7 100644 --- a/block/transaction.go +++ b/transaction.go @@ -1,11 +1,7 @@ -package block - -import ( - "github.com/nspcc-dev/dbft/crypto" -) +package dbft // Transaction is a generic transaction interface. -type Transaction[H crypto.Hash] interface { +type Transaction[H Hash] interface { // Hash must return cryptographic hash of the transaction. // Transactions which have equal hashes are considered equal. Hash() H