diff --git a/CHANGELOG.md b/CHANGELOG.md index 155c6115..4e331e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Behaviour changes: * rename `InitializeConsensus` dBFT method to `Reset` (#95) * drop outdated dBFT `Service` interface (#95) * move all default implementations to `internal` package (#97) + * remove unused APIs of dBFT and payload interfaces (#104) Improvements: * add MIT License (#78, #79) diff --git a/README.md b/README.md index 10a490d6..99b4faba 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,19 @@ in `config.go`. 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. Refer to `identity.go` for `Hash`/`Address` description. No default implementation is +3. `dbft` package contains `Hash` interface which permits usage of one's own +hash implementation without additional overhead on conversions. Instantiate dBFT with +custom hash implementation that matches requirements specified in the corresponding +documentation. Refer to `identity.go` for `Hash` 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 entities having +`transaction.go` files. Every block must be able to be signed and verified as well as implement getters +for main fields. `Transaction` is an entity which can be hashed. Two entities having equal hashes are considered equal. No default implementation is provided. 5. `dbft` contains generic 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. `dbft` contains generic interfaces for time-related operations (`Timer` and `HV`). `timer` package contains +default time and height-view providers, it contains minimal required timer functionality and may safely be used in +production code. It should make it easier to write tests concerning dBFT's time depending behaviour. 7. `internal` contains an example of custom identity types and payloads implementation used to implement an example of dBFT's usage with 6-node consensus. Refer to `internal` subpackages for type-specific dBFT implementation and tests. Refer to `internal/simulation` for an example of dBFT library usage. diff --git a/block.go b/block.go index c849fc89..3c51e846 100644 --- a/block.go +++ b/block.go @@ -1,23 +1,15 @@ package dbft // Block is a generic interface for a block used by dbft. -type Block[H Hash, A Address] interface { +type Block[H Hash] 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 diff --git a/change_view.go b/change_view.go index ec0ac7a9..5152c639 100644 --- a/change_view.go +++ b/change_view.go @@ -5,9 +5,6 @@ type ChangeView interface { // NewViewNumber returns proposed view number. NewViewNumber() byte - // Timestamp returns message's timestamp. - Timestamp() uint64 - // Reason returns change view reason. Reason() ChangeViewReason } diff --git a/check.go b/check.go index a3a3d72d..4b4b9e15 100644 --- a/check.go +++ b/check.go @@ -4,7 +4,7 @@ import ( "go.uber.org/zap" ) -func (d *DBFT[H, A]) checkPrepare() { +func (d *DBFT[H]) checkPrepare() { if !d.hasAllTransactions() { d.Logger.Debug("check prepare: some transactions are missing", zap.Any("hashes", d.MissingTransactions)) return @@ -36,7 +36,7 @@ func (d *DBFT[H, A]) checkPrepare() { } } -func (d *DBFT[H, A]) checkCommit() { +func (d *DBFT[H]) checkCommit() { if !d.hasAllTransactions() { d.Logger.Debug("check commit: some transactions are missing", zap.Any("hashes", d.MissingTransactions)) return @@ -77,7 +77,7 @@ func (d *DBFT[H, A]) checkCommit() { // new height. } -func (d *DBFT[H, A]) checkChangeView(view byte) { +func (d *DBFT[H]) checkChangeView(view byte) { if d.ViewNumber >= view { return } diff --git a/config.go b/config.go index d0b3f316..31bea995 100644 --- a/config.go +++ b/config.go @@ -5,16 +5,17 @@ import ( "errors" "time" - "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) // Config contains initialization and working parameters for dBFT. -type Config[H Hash, A Address] struct { +type Config[H Hash] struct { // Logger Logger *zap.Logger // Timer - Timer timer.Timer + Timer Timer + // NewHeightView is a constructor for [dbft.HV] object. + NewHeightView func(height uint32, view byte) HV // SecondsPerBlock is the number of seconds that // need to pass before another block will be accepted. SecondsPerBlock time.Duration @@ -26,7 +27,7 @@ type Config[H Hash, A Address] struct { // together with it's key pair. GetKeyPair func([]PublicKey) (int, PrivateKey, PublicKey) // NewBlockFromContext should allocate, fill from Context and return new block.Block. - NewBlockFromContext func(ctx *Context[H, A]) Block[H, A] + NewBlockFromContext func(ctx *Context[H]) Block[H] // RequestTx is a callback which is called when transaction contained // in current block can't be found in memory pool. RequestTx func(h ...H) @@ -39,13 +40,13 @@ type Config[H Hash, A Address] struct { // to be proposed in a new block. GetVerified func() []Transaction[H] // VerifyBlock verifies if block is valid. - VerifyBlock func(b Block[H, A]) bool + VerifyBlock func(b Block[H]) bool // Broadcast should broadcast payload m to the consensus nodes. - Broadcast func(m ConsensusPayload[H, A]) + Broadcast func(m ConsensusPayload[H]) // ProcessBlock is called every time new block is accepted. - ProcessBlock func(b Block[H, A]) + ProcessBlock func(b Block[H]) // GetBlock should return block with hash. - GetBlock func(h H) Block[H, A] + GetBlock func(h H) Block[H] // WatchOnly tells if a node should only watch. WatchOnly func() bool // CurrentHeight returns index of the last accepted block. @@ -57,12 +58,10 @@ type Config[H Hash, A Address] struct { // list of the validators of the next block. // If this function ever returns 0-length slice, dbft will panic. GetValidators func(...Transaction[H]) []PublicKey - // GetConsensusAddress returns hash of the validator list. - GetConsensusAddress func(...PublicKey) A // NewConsensusPayload is a constructor for payload.ConsensusPayload. - NewConsensusPayload func(*Context[H, A], MessageType, any) ConsensusPayload[H, A] + NewConsensusPayload func(*Context[H], MessageType, any) ConsensusPayload[H] // NewPrepareRequest is a constructor for payload.PrepareRequest. - NewPrepareRequest func(ts uint64, nonce uint64, nextConsensus A, transactionHashes []H) PrepareRequest[H, A] + NewPrepareRequest func(ts uint64, nonce uint64, transactionHashes []H) PrepareRequest[H] // NewPrepareResponse is a constructor for payload.PrepareResponse. NewPrepareResponse func(preparationHash H) PrepareResponse[H] // NewChangeView is a constructor for payload.ChangeView. @@ -72,22 +71,21 @@ type Config[H Hash, A Address] struct { // NewRecoveryRequest is a constructor for payload.RecoveryRequest. NewRecoveryRequest func(ts uint64) RecoveryRequest // NewRecoveryMessage is a constructor for payload.RecoveryMessage. - NewRecoveryMessage func() RecoveryMessage[H, A] + NewRecoveryMessage func() RecoveryMessage[H] // VerifyPrepareRequest can perform external payload verification and returns true iff it was successful. - VerifyPrepareRequest func(p ConsensusPayload[H, A]) error + VerifyPrepareRequest func(p ConsensusPayload[H]) error // VerifyPrepareResponse performs external PrepareResponse verification and returns nil if it's successful. - VerifyPrepareResponse func(p ConsensusPayload[H, A]) error + VerifyPrepareResponse func(p ConsensusPayload[H]) error } const defaultSecondsPerBlock = time.Second * 15 const defaultTimestampIncrement = uint64(time.Millisecond / time.Nanosecond) -func defaultConfig[H Hash, A Address]() *Config[H, A] { +func defaultConfig[H Hash]() *Config[H] { // fields which are set to nil must be provided from client - return &Config[H, A]{ + return &Config[H]{ Logger: zap.NewNop(), - Timer: timer.New(), SecondsPerBlock: defaultSecondsPerBlock, TimestampIncrement: defaultTimestampIncrement, GetKeyPair: nil, @@ -95,23 +93,27 @@ func defaultConfig[H Hash, A Address]() *Config[H, A] { StopTxFlow: func() {}, 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 }, + VerifyBlock: func(Block[H]) bool { return true }, + Broadcast: func(ConsensusPayload[H]) {}, + ProcessBlock: func(Block[H]) {}, + GetBlock: func(H) Block[H] { return nil }, WatchOnly: func() bool { return false }, CurrentHeight: nil, CurrentBlockHash: nil, GetValidators: nil, - VerifyPrepareRequest: func(ConsensusPayload[H, A]) error { return nil }, - VerifyPrepareResponse: func(ConsensusPayload[H, A]) error { return nil }, + VerifyPrepareRequest: func(ConsensusPayload[H]) error { return nil }, + VerifyPrepareResponse: func(ConsensusPayload[H]) error { return nil }, } } -func checkConfig[H Hash, A Address](cfg *Config[H, A]) error { +func checkConfig[H Hash](cfg *Config[H]) error { if cfg.GetKeyPair == nil { return errors.New("private key is nil") + } else if cfg.Timer == nil { + return errors.New("Timer is nil") + } else if cfg.NewHeightView == nil { + return errors.New("NewHeightView is nil") } else if cfg.CurrentHeight == nil { return errors.New("CurrentHeight is nil") } else if cfg.CurrentBlockHash == nil { @@ -120,8 +122,6 @@ func checkConfig[H Hash, A Address](cfg *Config[H, A]) error { return errors.New("GetValidators is nil") } else if cfg.NewBlockFromContext == nil { return errors.New("NewBlockFromContext is nil") - } else if cfg.GetConsensusAddress == nil { - return errors.New("GetConsensusAddress is nil") } else if cfg.NewConsensusPayload == nil { return errors.New("NewConsensusPayload is nil") } else if cfg.NewPrepareRequest == nil { @@ -143,13 +143,13 @@ func checkConfig[H Hash, A 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 Hash, A Address](priv PrivateKey, pub PublicKey) func(config *Config[H, A]) { +func WithKeyPair[H Hash](priv PrivateKey, pub PublicKey) func(config *Config[H]) { myPub, err := pub.MarshalBinary() if err != nil { return nil } - return func(cfg *Config[H, A]) { + return func(cfg *Config[H]) { cfg.GetKeyPair = func(ps []PublicKey) (int, PrivateKey, PublicKey) { for i := range ps { pi, err := ps[i].MarshalBinary() @@ -166,197 +166,197 @@ func WithKeyPair[H Hash, A Address](priv PrivateKey, pub PublicKey) func(config } // WithGetKeyPair sets GetKeyPair. -func WithGetKeyPair[H Hash, A Address](f func([]PublicKey) (int, PrivateKey, PublicKey)) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithGetKeyPair[H Hash](f func([]PublicKey) (int, PrivateKey, PublicKey)) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.GetKeyPair = f } } // WithLogger sets Logger. -func WithLogger[H Hash, A Address](log *zap.Logger) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithLogger[H Hash](log *zap.Logger) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.Logger = log } } // WithTimer sets Timer. -func WithTimer[H Hash, A Address](t timer.Timer) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithTimer[H Hash](t Timer) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.Timer = t } } +// WithNewHeightView sets NewHeightView constructor. +func WithNewHeightView[H Hash](f func(height uint32, view byte) HV) func(config *Config[H]) { + return func(cfg *Config[H]) { + cfg.NewHeightView = f + } +} + // WithSecondsPerBlock sets SecondsPerBlock. -func WithSecondsPerBlock[H Hash, A Address](d time.Duration) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithSecondsPerBlock[H Hash](d time.Duration) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.SecondsPerBlock = d } } // WithTimestampIncrement sets TimestampIncrement. -func WithTimestampIncrement[H Hash, A Address](u uint64) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithTimestampIncrement[H Hash](u uint64) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.TimestampIncrement = u } } // WithNewBlockFromContext sets NewBlockFromContext. -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]) { +func WithNewBlockFromContext[H Hash](f func(ctx *Context[H]) Block[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewBlockFromContext = f } } // WithRequestTx sets RequestTx. -func WithRequestTx[H Hash, A Address](f func(h ...H)) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithRequestTx[H Hash](f func(h ...H)) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.RequestTx = f } } // WithStopTxFlow sets StopTxFlow. -func WithStopTxFlow[H Hash, A Address](f func()) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithStopTxFlow[H Hash](f func()) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.StopTxFlow = f } } // WithGetTx sets GetTx. -func WithGetTx[H Hash, A Address](f func(h H) Transaction[H]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithGetTx[H Hash](f func(h H) Transaction[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.GetTx = f } } // WithGetVerified sets GetVerified. -func WithGetVerified[H Hash, A Address](f func() []Transaction[H]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithGetVerified[H Hash](f func() []Transaction[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.GetVerified = f } } // WithVerifyBlock sets VerifyBlock. -func WithVerifyBlock[H Hash, A Address](f func(b Block[H, A]) bool) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithVerifyBlock[H Hash](f func(b Block[H]) bool) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.VerifyBlock = f } } // WithBroadcast sets Broadcast. -func WithBroadcast[H Hash, A Address](f func(m ConsensusPayload[H, A])) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithBroadcast[H Hash](f func(m ConsensusPayload[H])) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.Broadcast = f } } // WithProcessBlock sets ProcessBlock. -func WithProcessBlock[H Hash, A Address](f func(b Block[H, A])) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithProcessBlock[H Hash](f func(b Block[H])) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.ProcessBlock = f } } // WithGetBlock sets GetBlock. -func WithGetBlock[H Hash, A Address](f func(h H) Block[H, A]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithGetBlock[H Hash](f func(h H) Block[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.GetBlock = f } } // WithWatchOnly sets WatchOnly. -func WithWatchOnly[H Hash, A Address](f func() bool) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithWatchOnly[H Hash](f func() bool) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.WatchOnly = f } } // WithCurrentHeight sets CurrentHeight. -func WithCurrentHeight[H Hash, A Address](f func() uint32) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithCurrentHeight[H Hash](f func() uint32) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.CurrentHeight = f } } // WithCurrentBlockHash sets CurrentBlockHash. -func WithCurrentBlockHash[H Hash, A Address](f func() H) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithCurrentBlockHash[H Hash](f func() H) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.CurrentBlockHash = f } } // WithGetValidators sets GetValidators. -func WithGetValidators[H Hash, A Address](f func(...Transaction[H]) []PublicKey) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithGetValidators[H Hash](f func(...Transaction[H]) []PublicKey) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.GetValidators = f } } -// WithGetConsensusAddress sets GetConsensusAddress. -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 Hash, A Address](f func(*Context[H, A], MessageType, any) ConsensusPayload[H, A]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewConsensusPayload[H Hash](f func(*Context[H], MessageType, any) ConsensusPayload[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewConsensusPayload = f } } // WithNewPrepareRequest sets NewPrepareRequest. -func WithNewPrepareRequest[H Hash, A Address](f func(ts uint64, nonce uint64, nextConsensus A, transactionsHashes []H) PrepareRequest[H, A]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewPrepareRequest[H Hash](f func(ts uint64, nonce uint64, transactionsHashes []H) PrepareRequest[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewPrepareRequest = f } } // WithNewPrepareResponse sets NewPrepareResponse. -func WithNewPrepareResponse[H Hash, A Address](f func(preparationHash H) PrepareResponse[H]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewPrepareResponse[H Hash](f func(preparationHash H) PrepareResponse[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewPrepareResponse = f } } // WithNewChangeView sets NewChangeView. -func WithNewChangeView[H Hash, A Address](f func(byte, ChangeViewReason, uint64) ChangeView) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewChangeView[H Hash](f func(byte, ChangeViewReason, uint64) ChangeView) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewChangeView = f } } // WithNewCommit sets NewCommit. -func WithNewCommit[H Hash, A Address](f func([]byte) Commit) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewCommit[H Hash](f func([]byte) Commit) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewCommit = f } } // WithNewRecoveryRequest sets NewRecoveryRequest. -func WithNewRecoveryRequest[H Hash, A Address](f func(ts uint64) RecoveryRequest) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewRecoveryRequest[H Hash](f func(ts uint64) RecoveryRequest) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewRecoveryRequest = f } } // WithNewRecoveryMessage sets NewRecoveryMessage. -func WithNewRecoveryMessage[H Hash, A Address](f func() RecoveryMessage[H, A]) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithNewRecoveryMessage[H Hash](f func() RecoveryMessage[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.NewRecoveryMessage = f } } // WithVerifyPrepareRequest sets VerifyPrepareRequest. -func WithVerifyPrepareRequest[H Hash, A Address](f func(ConsensusPayload[H, A]) error) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithVerifyPrepareRequest[H Hash](f func(ConsensusPayload[H]) error) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.VerifyPrepareRequest = f } } // WithVerifyPrepareResponse sets VerifyPrepareResponse. -func WithVerifyPrepareResponse[H Hash, A Address](f func(ConsensusPayload[H, A]) error) func(config *Config[H, A]) { - return func(cfg *Config[H, A]) { +func WithVerifyPrepareResponse[H Hash](f func(ConsensusPayload[H]) error) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.VerifyPrepareResponse = f } } diff --git a/consensus_message.go b/consensus_message.go index 7e72029f..d8859dea 100644 --- a/consensus_message.go +++ b/consensus_message.go @@ -1,7 +1,7 @@ package dbft // ConsensusMessage is an interface for generic dBFT message. -type ConsensusMessage[H Hash, A Address] interface { +type ConsensusMessage[H Hash] interface { // ViewNumber returns view number when this message was originated. ViewNumber() byte // Type returns type of this message. @@ -12,7 +12,7 @@ type ConsensusMessage[H Hash, A Address] interface { // GetChangeView returns payload as if it was ChangeView. GetChangeView() ChangeView // GetPrepareRequest returns payload as if it was PrepareRequest. - GetPrepareRequest() PrepareRequest[H, A] + GetPrepareRequest() PrepareRequest[H] // GetPrepareResponse returns payload as if it was PrepareResponse. GetPrepareResponse() PrepareResponse[H] // GetCommit returns payload as if it was Commit. @@ -20,5 +20,5 @@ type ConsensusMessage[H Hash, A Address] interface { // GetRecoveryRequest returns payload as if it was RecoveryRequest. GetRecoveryRequest() RecoveryRequest // GetRecoveryMessage returns payload as if it was RecoveryMessage. - GetRecoveryMessage() RecoveryMessage[H, A] + GetRecoveryMessage() RecoveryMessage[H] } diff --git a/consensus_payload.go b/consensus_payload.go index e537d3e0..c24699ef 100644 --- a/consensus_payload.go +++ b/consensus_payload.go @@ -2,8 +2,8 @@ package dbft // ConsensusPayload is a generic payload type which is exchanged // between the nodes. -type ConsensusPayload[H Hash, A Address] interface { - ConsensusMessage[H, A] +type ConsensusPayload[H Hash] interface { + ConsensusMessage[H] // ValidatorIndex returns index of validator from which // payload was originated from. diff --git a/context.go b/context.go index a6725649..334a17b6 100644 --- a/context.go +++ b/context.go @@ -4,23 +4,21 @@ import ( "crypto/rand" "encoding/binary" "time" - - "github.com/nspcc-dev/dbft/timer" ) // Context is a main dBFT structure which // contains all information needed for performing transitions. -type Context[H Hash, A Address] struct { +type Context[H Hash] struct { // Config is dBFT's Config instance. - Config *Config[H, A] + Config *Config[H] // Priv is node's private key. Priv PrivateKey // Pub is node's public key. Pub PublicKey - block Block[H, A] - header Block[H, A] + block Block[H] + header Block[H] // 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 @@ -38,10 +36,7 @@ type Context[H Hash, A Address] struct { MyIndex int // PrimaryIndex is an index of the primary node in the current epoch. PrimaryIndex uint - Version uint32 - // NextConsensus is a hash of the validators which will be accepting the next block. - NextConsensus A // PrevHash is a hash of the previous block. PrevHash H @@ -56,21 +51,21 @@ type Context[H Hash, A Address] struct { Transactions map[H]Transaction[H] // PreparationPayloads stores consensus Prepare* payloads for the current epoch. - PreparationPayloads []ConsensusPayload[H, A] + PreparationPayloads []ConsensusPayload[H] // 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 []ConsensusPayload[H, A] + CommitPayloads []ConsensusPayload[H] // ChangeViewPayloads stores consensus ChangeView payloads for the current epoch. - ChangeViewPayloads []ConsensusPayload[H, A] + ChangeViewPayloads []ConsensusPayload[H] // LastChangeViewPayloads stores consensus ChangeView payloads for the last epoch. - LastChangeViewPayloads []ConsensusPayload[H, A] + LastChangeViewPayloads []ConsensusPayload[H] // 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 + LastSeenMessage []*HV lastBlockTimestamp uint64 // ns-precision timestamp from the last header (used for the next block timestamp calculations). lastBlockTime time.Time // Wall clock time of when the last block was first seen (used for timer adjustments). @@ -78,16 +73,16 @@ type Context[H Hash, A Address] struct { } // N returns total number of validators. -func (c *Context[H, A]) N() int { return len(c.Validators) } +func (c *Context[H]) N() int { return len(c.Validators) } // F returns number of validators which can be faulty. -func (c *Context[H, A]) F() int { return (len(c.Validators) - 1) / 3 } +func (c *Context[H]) F() int { return (len(c.Validators) - 1) / 3 } // M returns number of validators which must function correctly. -func (c *Context[H, A]) M() int { return len(c.Validators) - c.F() } +func (c *Context[H]) M() int { return len(c.Validators) - c.F() } // GetPrimaryIndex returns index of a primary node for the specified view. -func (c *Context[H, A]) GetPrimaryIndex(viewNumber byte) uint { +func (c *Context[H]) GetPrimaryIndex(viewNumber byte) uint { p := (int(c.BlockIndex) - int(viewNumber)) % len(c.Validators) if p >= 0 { return uint(p) @@ -97,19 +92,19 @@ func (c *Context[H, A]) GetPrimaryIndex(viewNumber byte) uint { } // IsPrimary returns true iff node is primary for current height and view. -func (c *Context[H, A]) IsPrimary() bool { return c.MyIndex == int(c.PrimaryIndex) } +func (c *Context[H]) IsPrimary() bool { return c.MyIndex == int(c.PrimaryIndex) } // IsBackup returns true iff node is backup for current height and view. -func (c *Context[H, A]) IsBackup() bool { +func (c *Context[H]) IsBackup() bool { return c.MyIndex >= 0 && !c.IsPrimary() } // WatchOnly returns true iff node takes no active part in consensus. -func (c *Context[H, A]) WatchOnly() bool { return c.MyIndex < 0 || c.Config.WatchOnly() } +func (c *Context[H]) WatchOnly() bool { return c.MyIndex < 0 || c.Config.WatchOnly() } // CountCommitted returns number of received Commit messages not only for the current // epoch but also for any other epoch. -func (c *Context[H, A]) CountCommitted() (count int) { +func (c *Context[H]) CountCommitted() (count int) { for i := range c.CommitPayloads { if c.CommitPayloads[i] != nil { count++ @@ -121,9 +116,9 @@ func (c *Context[H, A]) CountCommitted() (count int) { // CountFailed returns number of nodes with which no communication was performed // for this view and that hasn't sent the Commit message at the previous views. -func (c *Context[H, A]) CountFailed() (count int) { +func (c *Context[H]) CountFailed() (count int) { for i, hv := range c.LastSeenMessage { - if c.CommitPayloads[i] == nil && (hv == nil || hv.Height < c.BlockIndex || hv.View < c.ViewNumber) { + if c.CommitPayloads[i] == nil && (hv == nil || (*hv).Height() < c.BlockIndex || (*hv).View() < c.ViewNumber) { count++ } } @@ -133,18 +128,18 @@ func (c *Context[H, A]) CountFailed() (count int) { // RequestSentOrReceived returns true iff PrepareRequest // was sent or received for the current epoch. -func (c *Context[H, A]) RequestSentOrReceived() bool { +func (c *Context[H]) RequestSentOrReceived() bool { return c.PreparationPayloads[c.PrimaryIndex] != nil } // ResponseSent returns true iff Prepare* message was sent for the current epoch. -func (c *Context[H, A]) ResponseSent() bool { +func (c *Context[H]) ResponseSent() bool { return !c.WatchOnly() && c.PreparationPayloads[c.MyIndex] != nil } // CommitSent returns true iff Commit message was sent for the current epoch // assuming that the node can't go further than current epoch after commit was sent. -func (c *Context[H, A]) CommitSent() bool { +func (c *Context[H]) CommitSent() bool { return !c.WatchOnly() && c.CommitPayloads[c.MyIndex] != nil } @@ -161,10 +156,10 @@ func (c *Context[H, A]) CommitSent() bool { // several places where the call to CreateBlock happens (one of them is right after // PrepareRequest receiving). Thus, we have a separate Context.blockProcessed field // for the described purpose. -func (c *Context[H, A]) BlockSent() bool { return c.blockProcessed } +func (c *Context[H]) BlockSent() bool { return c.blockProcessed } // ViewChanging returns true iff node is in a process of changing view. -func (c *Context[H, A]) ViewChanging() bool { +func (c *Context[H]) ViewChanging() bool { if c.WatchOnly() { return false } @@ -175,7 +170,7 @@ func (c *Context[H, A]) ViewChanging() bool { } // NotAcceptingPayloadsDueToViewChanging returns true if node should not accept new payloads. -func (c *Context[H, A]) NotAcceptingPayloadsDueToViewChanging() bool { +func (c *Context[H]) NotAcceptingPayloadsDueToViewChanging() bool { return c.ViewChanging() && !c.MoreThanFNodesCommittedOrLost() } @@ -186,11 +181,11 @@ func (c *Context[H, A]) NotAcceptingPayloadsDueToViewChanging() bool { // asking change views loses network or crashes and comes back when nodes are committed in more than one higher // numbered view, it is possible for the node accepting recovery to commit in any of the higher views, thus // potentially splitting nodes among views and stalling the network. -func (c *Context[H, A]) MoreThanFNodesCommittedOrLost() bool { +func (c *Context[H]) MoreThanFNodesCommittedOrLost() bool { return c.CountCommitted()+c.CountFailed() > c.F() } -func (c *Context[H, A]) reset(view byte, ts uint64) { +func (c *Context[H]) reset(view byte, ts uint64) { c.MyIndex = -1 c.lastBlockTimestamp = ts @@ -200,10 +195,10 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { c.Validators = c.Config.GetValidators() n := len(c.Validators) - c.LastChangeViewPayloads = make([]ConsensusPayload[H, A], n) + c.LastChangeViewPayloads = make([]ConsensusPayload[H], n) if c.LastSeenMessage == nil { - c.LastSeenMessage = make([]*timer.HV, n) + c.LastSeenMessage = make([]*HV, n) } c.blockProcessed = false } else { @@ -223,11 +218,11 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { c.header = nil n := len(c.Validators) - c.ChangeViewPayloads = make([]ConsensusPayload[H, A], n) + c.ChangeViewPayloads = make([]ConsensusPayload[H], n) if view == 0 { - c.CommitPayloads = make([]ConsensusPayload[H, A], n) + c.CommitPayloads = make([]ConsensusPayload[H], n) } - c.PreparationPayloads = make([]ConsensusPayload[H, A], n) + c.PreparationPayloads = make([]ConsensusPayload[H], n) c.Transactions = make(map[H]Transaction[H]) c.TransactionHashes = nil @@ -236,15 +231,13 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { c.ViewNumber = view if c.MyIndex >= 0 { - c.LastSeenMessage[c.MyIndex] = &timer.HV{ - Height: c.BlockIndex, - View: c.ViewNumber, - } + hv := c.Config.NewHeightView(c.BlockIndex, c.ViewNumber) + c.LastSeenMessage[c.MyIndex] = &hv } } // Fill initializes consensus when node is a speaker. -func (c *Context[H, A]) Fill() { +func (c *Context[H]) Fill() { b := make([]byte, 8) _, err := rand.Read(b) if err != nil { @@ -261,9 +254,6 @@ func (c *Context[H, A]) Fill() { c.Transactions[h] = txx[i] } - validators := c.Config.GetValidators(txx...) - c.NextConsensus = c.Config.GetConsensusAddress(validators...) - c.Timestamp = c.lastBlockTimestamp + c.Config.TimestampIncrement if now := c.getTimestamp(); now > c.Timestamp { c.Timestamp = now @@ -272,12 +262,12 @@ func (c *Context[H, A]) Fill() { // getTimestamp returns nanoseconds-precision timestamp using // current context config. -func (c *Context[H, A]) getTimestamp() uint64 { +func (c *Context[H]) getTimestamp() uint64 { return uint64(c.Config.Timer.Now().UnixNano()) / c.Config.TimestampIncrement * c.Config.TimestampIncrement } // CreateBlock returns resulting block for the current epoch. -func (c *Context[H, A]) CreateBlock() Block[H, A] { +func (c *Context[H]) CreateBlock() Block[H] { if c.block == nil { if c.block = c.MakeHeader(); c.block == nil { return nil @@ -297,7 +287,7 @@ func (c *Context[H, A]) CreateBlock() 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[H, A] { +func (c *Context[H]) MakeHeader() Block[H] { if c.header == nil { if !c.RequestSentOrReceived() { return nil @@ -310,6 +300,6 @@ func (c *Context[H, A]) MakeHeader() Block[H, A] { // hasAllTransactions returns true iff all transactions were received // for the proposed block. -func (c *Context[H, A]) hasAllTransactions() bool { +func (c *Context[H]) hasAllTransactions() bool { return len(c.TransactionHashes) == len(c.Transactions) } diff --git a/dbft.go b/dbft.go index 2fec0cd8..ab50d44f 100644 --- a/dbft.go +++ b/dbft.go @@ -4,7 +4,6 @@ import ( "sync" "time" - "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) @@ -13,12 +12,12 @@ 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 Hash, A Address] struct { - Context[H, A] - Config[H, A] + DBFT[H Hash] struct { + Context[H] + Config[H] *sync.Mutex - cache cache[H, A] + cache cache[H] recovering bool } ) @@ -27,8 +26,8 @@ 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 Hash, A Address](options ...func(config *Config[H, A])) *DBFT[H, A] { - cfg := defaultConfig[H, A]() +func New[H Hash](options ...func(config *Config[H])) *DBFT[H] { + cfg := defaultConfig[H]() for _, option := range options { option(cfg) @@ -38,10 +37,10 @@ func New[H Hash, A Address](options ...func(config *Config[H, A])) *DBFT[H, A] { return nil } - d := &DBFT[H, A]{ + d := &DBFT[H]{ Mutex: new(sync.Mutex), Config: *cfg, - Context: Context[H, A]{ + Context: Context[H]{ Config: cfg, }, } @@ -49,7 +48,7 @@ func New[H Hash, A Address](options ...func(config *Config[H, A])) *DBFT[H, A] { return d } -func (d *DBFT[H, A]) addTransaction(tx Transaction[H]) { +func (d *DBFT[H]) addTransaction(tx Transaction[H]) { d.Transactions[tx.Hash()] = tx if d.hasAllTransactions() { if d.IsPrimary() || d.Context.WatchOnly() { @@ -69,8 +68,8 @@ func (d *DBFT[H, A]) addTransaction(tx Transaction[H]) { // Start initializes dBFT instance and starts the protocol if node is primary. // It accepts the timestamp of the previous block. It should be called once // per DBFT lifetime. -func (d *DBFT[H, A]) Start(ts uint64) { - d.cache = newCache[H, A]() +func (d *DBFT[H]) Start(ts uint64) { + d.cache = newCache[H]() d.initializeConsensus(0, ts) d.start() } @@ -80,11 +79,11 @@ func (d *DBFT[H, A]) Start(ts uint64) { // after new block is processed by ledger (the block can come from dBFT or be // received by other means). The height is to be derived from the configured // CurrentHeight callback and view will be set to 0. -func (d *DBFT[H, A]) Reset(ts uint64) { +func (d *DBFT[H]) Reset(ts uint64) { d.initializeConsensus(0, ts) } -func (d *DBFT[H, A]) initializeConsensus(view byte, ts uint64) { +func (d *DBFT[H]) initializeConsensus(view byte, ts uint64) { d.reset(view, ts) var role string @@ -137,7 +136,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 Transaction[H]) { +func (d *DBFT[H]) OnTransaction(tx Transaction[H]) { // d.Logger.Debug("OnTransaction", // zap.Bool("backup", d.IsBackup()), // zap.Bool("not_accepting", d.NotAcceptingPayloadsDueToViewChanging()), @@ -169,22 +168,22 @@ func (d *DBFT[H, A]) OnTransaction(tx Transaction[H]) { } // OnTimeout advances state machine as if timeout was fired. -func (d *DBFT[H, A]) OnTimeout(hv timer.HV) { +func (d *DBFT[H]) OnTimeout(hv HV) { if d.Context.WatchOnly() || d.BlockSent() { return } - if hv.Height != d.BlockIndex || hv.View != d.ViewNumber { + if hv.Height() != d.BlockIndex || hv.View() != d.ViewNumber { d.Logger.Debug("timeout: ignore old timer", - zap.Uint32("height", hv.Height), - zap.Uint("view", uint(hv.View))) + zap.Uint32("height", hv.Height()), + zap.Uint("view", uint(hv.View()))) return } d.Logger.Debug("timeout", - zap.Uint32("height", hv.Height), - zap.Uint("view", uint(hv.View))) + zap.Uint32("height", hv.Height()), + zap.Uint("view", uint(hv.View()))) if d.IsPrimary() && !d.RequestSentOrReceived() { d.sendPrepareRequest() @@ -200,7 +199,7 @@ func (d *DBFT[H, A]) OnTimeout(hv timer.HV) { } // OnReceive advances state machine in accordance with msg. -func (d *DBFT[H, A]) OnReceive(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) OnReceive(msg ConsensusPayload[H]) { if int(msg.ValidatorIndex()) >= len(d.Validators) { d.Logger.Error("too big validator index", zap.Uint16("from", msg.ValidatorIndex())) return @@ -237,11 +236,9 @@ func (d *DBFT[H, A]) OnReceive(msg ConsensusPayload[H, A]) { } hv := d.LastSeenMessage[msg.ValidatorIndex()] - if hv == nil || hv.Height < msg.Height() || hv.View < msg.ViewNumber() { - d.LastSeenMessage[msg.ValidatorIndex()] = &timer.HV{ - Height: msg.Height(), - View: msg.ViewNumber(), - } + if hv == nil || (*hv).Height() < msg.Height() || (*hv).View() < msg.ViewNumber() { + hv := d.Config.NewHeightView(msg.Height(), msg.ViewNumber()) + d.LastSeenMessage[msg.ValidatorIndex()] = &hv } if d.BlockSent() && msg.Type() != RecoveryRequestType { @@ -269,7 +266,7 @@ func (d *DBFT[H, A]) OnReceive(msg ConsensusPayload[H, A]) { // start performs initial operations and returns messages to be sent. // It must be called after every height or view increment. -func (d *DBFT[H, A]) start() { +func (d *DBFT[H]) start() { if !d.IsPrimary() { if msgs := d.cache.getHeight(d.BlockIndex); msgs != nil { for _, m := range msgs.prepare { @@ -291,7 +288,7 @@ func (d *DBFT[H, A]) start() { d.sendPrepareRequest() } -func (d *DBFT[H, A]) onPrepareRequest(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onPrepareRequest(msg ConsensusPayload[H]) { // ignore prepareRequest if we had already received it or // are in process of changing view if d.RequestSentOrReceived() { //|| (d.ViewChanging() && !d.MoreThanFNodesCommittedOrLost()) { @@ -327,7 +324,6 @@ func (d *DBFT[H, A]) onPrepareRequest(msg ConsensusPayload[H, A]) { d.Timestamp = p.Timestamp() d.Nonce = p.Nonce() - d.NextConsensus = p.NextConsensus() d.TransactionHashes = p.TransactionHashes() d.Logger.Info("received PrepareRequest", zap.Uint16("validator", msg.ValidatorIndex()), zap.Int("tx", len(d.TransactionHashes))) @@ -343,7 +339,7 @@ func (d *DBFT[H, A]) onPrepareRequest(msg ConsensusPayload[H, A]) { d.checkPrepare() } -func (d *DBFT[H, A]) processMissingTx() { +func (d *DBFT[H]) processMissingTx() { missing := make([]H, 0, len(d.TransactionHashes)/2) for _, h := range d.TransactionHashes { @@ -369,16 +365,7 @@ func (d *DBFT[H, A]) processMissingTx() { // the new proposed block, if it's fine it returns true, if something is wrong // 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([]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(CVBlockRejectedByPolicy) - return false - } +func (d *DBFT[H]) createAndCheckBlock() bool { if b := d.Context.CreateBlock(); !d.VerifyBlock(b) { d.Logger.Warn("proposed block fails verification") d.sendChangeView(CVTxInvalid) @@ -387,7 +374,7 @@ func (d *DBFT[H, A]) createAndCheckBlock() bool { return true } -func (d *DBFT[H, A]) updateExistingPayloads(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) updateExistingPayloads(msg ConsensusPayload[H]) { for i, m := range d.PreparationPayloads { if m != nil && m.Type() == PrepareResponseType { resp := m.GetPrepareResponse() @@ -410,7 +397,7 @@ func (d *DBFT[H, A]) updateExistingPayloads(msg ConsensusPayload[H, A]) { } } -func (d *DBFT[H, A]) onPrepareResponse(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onPrepareResponse(msg ConsensusPayload[H]) { if d.ViewNumber != msg.ViewNumber() { d.Logger.Debug("ignoring wrong view number", zap.Uint("view", uint(msg.ViewNumber()))) return @@ -462,7 +449,7 @@ func (d *DBFT[H, A]) onPrepareResponse(msg ConsensusPayload[H, A]) { } } -func (d *DBFT[H, A]) onChangeView(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onChangeView(msg ConsensusPayload[H]) { p := msg.GetChangeView() if p.NewViewNumber() <= d.ViewNumber { @@ -493,7 +480,7 @@ func (d *DBFT[H, A]) onChangeView(msg ConsensusPayload[H, A]) { d.checkChangeView(p.NewViewNumber()) } -func (d *DBFT[H, A]) onCommit(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onCommit(msg ConsensusPayload[H]) { existing := d.CommitPayloads[msg.ValidatorIndex()] if existing != nil { if existing.Hash() != msg.Hash() { @@ -535,7 +522,7 @@ func (d *DBFT[H, A]) onCommit(msg ConsensusPayload[H, A]) { d.CommitPayloads[msg.ValidatorIndex()] = msg } -func (d *DBFT[H, A]) onRecoveryRequest(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onRecoveryRequest(msg ConsensusPayload[H]) { if !d.CommitSent() { // Limit recoveries to be sent from no more than F nodes // TODO replace loop with a single if @@ -557,7 +544,7 @@ func (d *DBFT[H, A]) onRecoveryRequest(msg ConsensusPayload[H, A]) { d.sendRecoveryMessage() } -func (d *DBFT[H, A]) onRecoveryMessage(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) onRecoveryMessage(msg ConsensusPayload[H]) { d.Logger.Debug("recovery message received", zap.Any("dump", msg)) var ( @@ -617,15 +604,15 @@ func (d *DBFT[H, A]) onRecoveryMessage(msg ConsensusPayload[H, A]) { } } -func (d *DBFT[H, A]) changeTimer(delay time.Duration) { +func (d *DBFT[H]) changeTimer(delay time.Duration) { d.Logger.Debug("reset timer", zap.Uint32("h", d.BlockIndex), zap.Int("v", int(d.ViewNumber)), zap.Duration("delay", delay)) - d.Timer.Reset(timer.HV{Height: d.BlockIndex, View: d.ViewNumber}, delay) + d.Timer.Reset(d.Config.NewHeightView(d.BlockIndex, d.ViewNumber), delay) } -func (d *DBFT[H, A]) extendTimer(count time.Duration) { +func (d *DBFT[H]) extendTimer(count time.Duration) { if !d.CommitSent() && !d.ViewChanging() { d.Timer.Extend(count * d.SecondsPerBlock / time.Duration(d.M())) } @@ -633,6 +620,6 @@ func (d *DBFT[H, A]) extendTimer(count time.Duration) { // Header returns current header from context. May be nil in case if no // header is constructed yet. Do not change the resulting header. -func (d *DBFT[H, A]) Header() Block[H, A] { +func (d *DBFT[H]) Header() Block[H] { return d.header } diff --git a/dbft_test.go b/dbft_test.go index abd7a056..86826b4b 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -7,15 +7,14 @@ import ( "time" "github.com/nspcc-dev/dbft" - "github.com/nspcc-dev/dbft/internal/block" + "github.com/nspcc-dev/dbft/internal/consensus" "github.com/nspcc-dev/dbft/internal/crypto" - "github.com/nspcc-dev/dbft/internal/payload" "github.com/nspcc-dev/dbft/timer" "github.com/stretchr/testify/require" "go.uber.org/zap" ) -type Payload = dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] +type Payload = dbft.ConsensusPayload[crypto.Uint256] type testState struct { myIndex int @@ -26,8 +25,8 @@ type testState struct { currHeight uint32 currHash crypto.Uint256 pool *testPool - blocks []dbft.Block[crypto.Uint256, crypto.Uint160] - verify func(b dbft.Block[crypto.Uint256, crypto.Uint160]) bool + blocks []dbft.Block[crypto.Uint256] + verify func(b dbft.Block[crypto.Uint256]) bool } type ( @@ -44,7 +43,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { t.Run("backup sends nothing on start", func(t *testing.T) { s.currHeight = 0 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) require.Nil(t, s.tryRecv()) @@ -52,7 +51,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { t.Run("primary send PrepareRequest on start", func(t *testing.T) { s.currHeight = 1 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) p := s.tryRecv() @@ -64,7 +63,7 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { require.EqualValues(t, 2, p.ValidatorIndex()) t.Run("primary send ChangeView on timeout", func(t *testing.T) { - service.OnTimeout(timer.HV{Height: s.currHeight + 1}) + service.OnTimeout(timer.HV{H: s.currHeight + 1}) // if there are many faulty must send RecoveryRequest cv := s.tryRecv() @@ -74,9 +73,10 @@ func TestDBFT_OnStartPrimarySendPrepareRequest(t *testing.T) { // if all nodes are up must send ChangeView for i := range service.LastSeenMessage { - service.LastSeenMessage[i] = &timer.HV{Height: s.currHeight + 1} + var hv dbft.HV = timer.HV{H: s.currHeight + 1} + service.LastSeenMessage[i] = &hv } - service.OnTimeout(timer.HV{Height: s.currHeight + 1}) + service.OnTimeout(timer.HV{H: s.currHeight + 1}) cv = s.tryRecv() require.NotNil(t, cv) @@ -91,7 +91,7 @@ func TestDBFT_SingleNode(t *testing.T) { s := newTestState(0, 1) s.currHeight = 2 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) p := s.tryRecv() @@ -117,7 +117,7 @@ func TestDBFT_SingleNode(t *testing.T) { func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { s := newTestState(2, 7) - s.verify = func(b dbft.Block[crypto.Uint256, crypto.Uint160]) bool { + s.verify = func(b dbft.Block[crypto.Uint256]) bool { for _, tx := range b.Transactions() { if tx.(testTx)%10 == 0 { return false @@ -129,7 +129,7 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { t.Run("receive request from primary", func(t *testing.T) { s.currHeight = 4 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) txs := []testTx{1} s.pool.Add(txs[0]) @@ -161,13 +161,14 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { t.Run("change view on invalid tx", func(t *testing.T) { s.currHeight = 4 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) txs := []testTx{10} service.Start(0) for i := range service.LastSeenMessage { - service.LastSeenMessage[i] = &timer.HV{Height: s.currHeight + 1} + var hv dbft.HV = timer.HV{H: s.currHeight + 1} + service.LastSeenMessage[i] = &hv } p := s.getPrepareRequest(5, txs[0].Hash()) @@ -189,7 +190,7 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { t.Run("receive invalid prepare request", func(t *testing.T) { s.currHeight = 4 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) txs := []testTx{1, 2} s.pool.Add(txs[0]) @@ -237,7 +238,7 @@ func TestDBFT_CommitOnTransaction(t *testing.T) { s := newTestState(0, 4) s.currHeight = 1 - srv := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + srv := dbft.New[crypto.Uint256](s.getOptions()...) srv.Start(0) require.Nil(t, s.tryRecv()) @@ -257,7 +258,7 @@ func TestDBFT_CommitOnTransaction(t *testing.T) { privs: s.privs, } s1.pool.Add(tx) - srv1 := dbft.New[crypto.Uint256, crypto.Uint160](s1.getOptions()...) + srv1 := dbft.New[crypto.Uint256](s1.getOptions()...) srv1.Start(0) srv1.OnReceive(req) srv1.OnReceive(s1.getPrepareResponse(1, req.Hash())) @@ -279,7 +280,7 @@ func TestDBFT_OnReceiveCommit(t *testing.T) { s := newTestState(2, 4) t.Run("send commit after enough responses", func(t *testing.T) { s.currHeight = 1 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) req := s.tryRecv() @@ -304,10 +305,10 @@ func TestDBFT_OnReceiveCommit(t *testing.T) { require.NoError(t, service.Header().Verify(pub, cm.GetCommit().Signature())) t.Run("send recovery message on timeout", func(t *testing.T) { - service.OnTimeout(timer.HV{Height: 1}) + service.OnTimeout(timer.HV{H: 1}) require.Nil(t, s.tryRecv()) - service.OnTimeout(timer.HV{Height: s.currHeight + 1}) + service.OnTimeout(timer.HV{H: s.currHeight + 1}) r := s.tryRecv() require.NotNil(t, r) @@ -339,7 +340,7 @@ func TestDBFT_OnReceiveRecoveryRequest(t *testing.T) { s := newTestState(2, 4) t.Run("send recovery message", func(t *testing.T) { s.currHeight = 1 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) req := s.tryRecv() @@ -361,7 +362,7 @@ func TestDBFT_OnReceiveRecoveryRequest(t *testing.T) { require.Equal(t, dbft.RecoveryMessageType, rm.Type()) other := s.copyWithIndex(3) - srv2 := dbft.New[crypto.Uint256, crypto.Uint160](other.getOptions()...) + srv2 := dbft.New[crypto.Uint256](other.getOptions()...) srv2.Start(0) srv2.OnReceive(rm) @@ -384,7 +385,7 @@ func TestDBFT_OnReceiveChangeView(t *testing.T) { s := newTestState(2, 4) t.Run("change view correctly", func(t *testing.T) { s.currHeight = 6 - service := dbft.New[crypto.Uint256, crypto.Uint160](s.getOptions()...) + service := dbft.New[crypto.Uint256](s.getOptions()...) service.Start(0) resp := s.getChangeView(1, 1) @@ -395,13 +396,13 @@ func TestDBFT_OnReceiveChangeView(t *testing.T) { service.OnReceive(resp) require.Nil(t, s.tryRecv()) - service.OnTimeout(timer.HV{Height: s.currHeight + 1}) + service.OnTimeout(timer.HV{H: s.currHeight + 1}) cv := s.tryRecv() require.NotNil(t, cv) require.Equal(t, dbft.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}) + service.OnTimeout(timer.HV{H: s.currHeight + 1, V: 1}) pr := s.tryRecv() require.NotNil(t, pr) require.Equal(t, dbft.PrepareRequestType, pr.Type()) @@ -411,92 +412,95 @@ func TestDBFT_OnReceiveChangeView(t *testing.T) { func TestDBFT_Invalid(t *testing.T) { t.Run("without keys", func(t *testing.T) { - require.Nil(t, dbft.New[crypto.Uint256, crypto.Uint160]()) + require.Nil(t, dbft.New[crypto.Uint256]()) }) priv, pub := crypto.Generate(rand.Reader) require.NotNil(t, priv) require.NotNil(t, pub) - opts := []func(*dbft.Config[crypto.Uint256, crypto.Uint160]){dbft.WithKeyPair[crypto.Uint256, crypto.Uint160](priv, pub)} + opts := []func(*dbft.Config[crypto.Uint256]){dbft.WithKeyPair[crypto.Uint256](priv, pub)} + t.Run("without Timer", func(t *testing.T) { + require.Nil(t, dbft.New(opts...)) + }) + + opts = append(opts, dbft.WithTimer[crypto.Uint256](timer.New())) + t.Run("without NewHeightView", func(t *testing.T) { + require.Nil(t, dbft.New(opts...)) + }) + + opts = append(opts, dbft.WithNewHeightView[crypto.Uint256](timer.NewHV)) t.Run("without CurrentHeight", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithCurrentHeight[crypto.Uint256, crypto.Uint160](func() uint32 { return 0 })) + opts = append(opts, dbft.WithCurrentHeight[crypto.Uint256](func() uint32 { return 0 })) t.Run("without CurrentBlockHash", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithCurrentBlockHash[crypto.Uint256, crypto.Uint160](func() crypto.Uint256 { return crypto.Uint256{} })) + opts = append(opts, dbft.WithCurrentBlockHash[crypto.Uint256](func() crypto.Uint256 { return crypto.Uint256{} })) t.Run("without GetValidators", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithGetValidators[crypto.Uint256, crypto.Uint160](func(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey { + opts = append(opts, dbft.WithGetValidators[crypto.Uint256](func(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey { return []dbft.PublicKey{pub} })) t.Run("without NewBlockFromContext", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](func(_ *dbft.Context[crypto.Uint256, crypto.Uint160]) dbft.Block[crypto.Uint256, crypto.Uint160] { + opts = append(opts, dbft.WithNewBlockFromContext[crypto.Uint256](func(_ *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { return nil })) - t.Run("without GetConsensusAddress", func(t *testing.T) { - require.Nil(t, dbft.New(opts...)) - }) - - opts = append(opts, dbft.WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](func(_ ...dbft.PublicKey) crypto.Uint160 { - return crypto.Uint160{} - })) t.Run("without NewConsensusPayload", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](func(_ *dbft.Context[crypto.Uint256, crypto.Uint160], _ dbft.MessageType, _ any) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { + opts = append(opts, dbft.WithNewConsensusPayload[crypto.Uint256](func(_ *dbft.Context[crypto.Uint256], _ dbft.MessageType, _ any) dbft.ConsensusPayload[crypto.Uint256] { return nil })) t.Run("without NewPrepareRequest", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](func(uint64, uint64, crypto.Uint160, []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] { + opts = append(opts, dbft.WithNewPrepareRequest[crypto.Uint256](func(uint64, uint64, []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256] { return nil })) t.Run("without NewPrepareResponse", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewPrepareResponse[crypto.Uint256, crypto.Uint160](func(crypto.Uint256) dbft.PrepareResponse[crypto.Uint256] { + opts = append(opts, dbft.WithNewPrepareResponse[crypto.Uint256](func(crypto.Uint256) dbft.PrepareResponse[crypto.Uint256] { return nil })) t.Run("without NewChangeView", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewChangeView[crypto.Uint256, crypto.Uint160](func(byte, dbft.ChangeViewReason, uint64) dbft.ChangeView { + opts = append(opts, dbft.WithNewChangeView[crypto.Uint256](func(byte, dbft.ChangeViewReason, uint64) dbft.ChangeView { return nil })) t.Run("without NewCommit", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewCommit[crypto.Uint256, crypto.Uint160](func([]byte) dbft.Commit { + opts = append(opts, dbft.WithNewCommit[crypto.Uint256](func([]byte) dbft.Commit { return nil })) t.Run("without NewRecoveryRequest", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](func(uint64) dbft.RecoveryRequest { + opts = append(opts, dbft.WithNewRecoveryRequest[crypto.Uint256](func(uint64) dbft.RecoveryRequest { return nil })) t.Run("without NewRecoveryMessage", func(t *testing.T) { require.Nil(t, dbft.New(opts...)) }) - opts = append(opts, dbft.WithNewRecoveryMessage[crypto.Uint256, crypto.Uint160](func() dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { + opts = append(opts, dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { return nil })) t.Run("with all defaults", func(t *testing.T) { @@ -529,19 +533,19 @@ func TestDBFT_Invalid(t *testing.T) { func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { r0 := newTestState(0, 4) r0.currHeight = 4 - s0 := dbft.New[crypto.Uint256, crypto.Uint160](r0.getOptions()...) + s0 := dbft.New[crypto.Uint256](r0.getOptions()...) s0.Start(0) r1 := r0.copyWithIndex(1) - s1 := dbft.New[crypto.Uint256, crypto.Uint160](r1.getOptions()...) + s1 := dbft.New[crypto.Uint256](r1.getOptions()...) s1.Start(0) r2 := r0.copyWithIndex(2) - s2 := dbft.New[crypto.Uint256, crypto.Uint160](r2.getOptions()...) + s2 := dbft.New[crypto.Uint256](r2.getOptions()...) s2.Start(0) r3 := r0.copyWithIndex(3) - s3 := dbft.New[crypto.Uint256, crypto.Uint160](r3.getOptions()...) + s3 := dbft.New[crypto.Uint256](r3.getOptions()...) s3.Start(0) // Step 1. The primary (at view 0) replica 1 sends the PrepareRequest message. @@ -578,7 +582,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // (possible on timeout) and sends the ChangeView message. s3.OnReceive(resp0V0) s3.OnReceive(resp2V0) - s3.OnTimeout(timer.HV{Height: r3.currHeight + 1, View: 0}) + s3.OnTimeout(timer.HV{H: r3.currHeight + 1, V: 0}) cv3V0 := r3.tryRecv() require.NotNil(t, cv3V0) require.Equal(t, dbft.ChangeViewType, cv3V0.Type()) @@ -588,7 +592,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // current view) and sends the ChangeView message. s1.OnReceive(resp0V0) s1.OnReceive(cv3V0) - s1.OnTimeout(timer.HV{Height: r1.currHeight + 1, View: 0}) + s1.OnTimeout(timer.HV{H: r1.currHeight + 1, V: 0}) cv1V0 := r1.tryRecv() require.NotNil(t, cv1V0) require.Equal(t, dbft.ChangeViewType, cv1V0.Type()) @@ -597,7 +601,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // (possible on timeout after receiving at least M non-commit messages from the // current view) and sends the ChangeView message. s0.OnReceive(cv3V0) - s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 0}) + s0.OnTimeout(timer.HV{H: r0.currHeight + 1, V: 0}) cv0V0 := r0.tryRecv() require.NotNil(t, cv0V0) require.Equal(t, dbft.ChangeViewType, cv0V0.Type()) @@ -613,7 +617,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { require.Equal(t, uint8(1), s0.ViewNumber) // Step 10. The primary (at view 1) replica 0 sends the PrepareRequest message. - s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 1}) + s0.OnTimeout(timer.HV{H: r0.currHeight + 1, V: 1}) reqV1 := r0.tryRecv() require.NotNil(t, reqV1) require.Equal(t, dbft.PrepareRequestType, reqV1.Type()) @@ -636,7 +640,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // Intermediate step A. It is added to make step 14 possible. The backup (at // view 1) replica 3 doesn't receive anything for a long time and sends // RecoveryRequest. - s3.OnTimeout(timer.HV{Height: r3.currHeight + 1, View: 1}) + s3.OnTimeout(timer.HV{H: r3.currHeight + 1, V: 1}) rcvr3V1 := r3.tryRecv() require.NotNil(t, rcvr3V1) require.Equal(t, dbft.RecoveryRequestType, rcvr3V1.Type()) @@ -671,7 +675,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // of "lost" nodes. That's why we'he added Intermediate steps A and B. // // After that replica 1 is allowed to send the CV message. - s1.OnTimeout(timer.HV{Height: r1.currHeight + 1, View: 1}) + s1.OnTimeout(timer.HV{H: r1.currHeight + 1, V: 1}) cv1V1 := r1.tryRecv() require.NotNil(t, cv1V1) require.Equal(t, dbft.ChangeViewType, cv1V1.Type()) @@ -679,7 +683,7 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { // Step 13. The primary (at view 1) replica 0 decides to change its view // (possible on timeout) and sends the ChangeView message. s0.OnReceive(resp1V1) - s0.OnTimeout(timer.HV{Height: r0.currHeight + 1, View: 1}) + s0.OnTimeout(timer.HV{H: r0.currHeight + 1, V: 1}) cv0V1 := r0.tryRecv() require.NotNil(t, cv0V1) require.Equal(t, dbft.ChangeViewType, cv0V1.Type()) @@ -731,27 +735,27 @@ func TestDBFT_FourGoodNodesDeadlock(t *testing.T) { } func (s testState) getChangeView(from uint16, view byte) Payload { - cv := payload.NewChangeView(view, 0, 0) + cv := consensus.NewChangeView(view, 0, 0) - p := payload.NewConsensusPayload(dbft.ChangeViewType, s.currHeight+1, from, 0, cv) + p := consensus.NewConsensusPayload(dbft.ChangeViewType, s.currHeight+1, from, 0, cv) return p } func (s testState) getRecoveryRequest(from uint16) Payload { - p := payload.NewConsensusPayload(dbft.RecoveryRequestType, s.currHeight+1, from, 0, payload.NewRecoveryRequest(0)) + p := consensus.NewConsensusPayload(dbft.RecoveryRequestType, s.currHeight+1, from, 0, consensus.NewRecoveryRequest(0)) return p } func (s testState) getCommit(from uint16, sign []byte) Payload { - c := payload.NewCommit(sign) - p := payload.NewConsensusPayload(dbft.CommitType, s.currHeight+1, from, 0, c) + c := consensus.NewCommit(sign) + p := consensus.NewConsensusPayload(dbft.CommitType, s.currHeight+1, from, 0, c) return p } func (s testState) getPrepareResponse(from uint16, phash crypto.Uint256) Payload { - resp := payload.NewPrepareResponse(phash) + resp := consensus.NewPrepareResponse(phash) - p := payload.NewConsensusPayload(dbft.PrepareResponseType, s.currHeight+1, from, 0, resp) + p := consensus.NewConsensusPayload(dbft.PrepareResponseType, s.currHeight+1, from, 0, resp) return p } @@ -760,9 +764,9 @@ func (s testState) getPrepareRequest(from uint16, hashes ...crypto.Uint256) Payl } func (s testState) getPrepareRequestWithHeight(from uint16, height uint32, hashes ...crypto.Uint256) Payload { - req := payload.NewPrepareRequest(0, 0, s.nextConsensus(), hashes) + req := consensus.NewPrepareRequest(0, 0, hashes) - p := payload.NewConsensusPayload(dbft.PrepareRequestType, height, from, 0, req) + p := consensus.NewConsensusPayload(dbft.PrepareRequestType, height, from, 0, req) return p } @@ -789,7 +793,7 @@ func (s *testState) tryRecv() Payload { return p } -func (s *testState) nextBlock() dbft.Block[crypto.Uint256, crypto.Uint160] { +func (s *testState) nextBlock() dbft.Block[crypto.Uint256] { if len(s.blocks) == 0 { return nil } @@ -812,43 +816,40 @@ func (s testState) copyWithIndex(myIndex int) *testState { } } -func (s testState) nextConsensus(...dbft.PublicKey) crypto.Uint160 { - return crypto.Uint160{1} -} - -func (s *testState) getOptions() []func(*dbft.Config[crypto.Uint256, crypto.Uint160]) { - opts := []func(*dbft.Config[crypto.Uint256, crypto.Uint160]){ - dbft.WithCurrentHeight[crypto.Uint256, crypto.Uint160](func() uint32 { return s.currHeight }), - dbft.WithCurrentBlockHash[crypto.Uint256, crypto.Uint160](func() crypto.Uint256 { return s.currHash }), - dbft.WithGetValidators[crypto.Uint256, crypto.Uint160](func(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey { return s.pubs }), - dbft.WithKeyPair[crypto.Uint256, crypto.Uint160](s.privs[s.myIndex], s.pubs[s.myIndex]), - dbft.WithBroadcast[crypto.Uint256, crypto.Uint160](func(p Payload) { s.ch = append(s.ch, p) }), - dbft.WithGetTx[crypto.Uint256, crypto.Uint160](s.pool.Get), - dbft.WithProcessBlock[crypto.Uint256, crypto.Uint160](func(b dbft.Block[crypto.Uint256, crypto.Uint160]) { s.blocks = append(s.blocks, b) }), - dbft.WithGetConsensusAddress[crypto.Uint256, crypto.Uint160](s.nextConsensus), - dbft.WithWatchOnly[crypto.Uint256, crypto.Uint160](func() bool { return false }), - dbft.WithGetBlock[crypto.Uint256, crypto.Uint160](func(crypto.Uint256) dbft.Block[crypto.Uint256, crypto.Uint160] { return nil }), - dbft.WithTimer[crypto.Uint256, crypto.Uint160](timer.New()), - dbft.WithLogger[crypto.Uint256, crypto.Uint160](zap.NewNop()), - dbft.WithNewBlockFromContext[crypto.Uint256, crypto.Uint160](newBlockFromContext), - dbft.WithSecondsPerBlock[crypto.Uint256, crypto.Uint160](time.Second * 10), - dbft.WithRequestTx[crypto.Uint256, crypto.Uint160](func(...crypto.Uint256) {}), - dbft.WithGetVerified[crypto.Uint256, crypto.Uint160](func() []dbft.Transaction[crypto.Uint256] { return []dbft.Transaction[crypto.Uint256]{} }), - - dbft.WithNewConsensusPayload[crypto.Uint256, crypto.Uint160](newConsensusPayload), - dbft.WithNewPrepareRequest[crypto.Uint256, crypto.Uint160](payload.NewPrepareRequest), - dbft.WithNewPrepareResponse[crypto.Uint256, crypto.Uint160](payload.NewPrepareResponse), - dbft.WithNewChangeView[crypto.Uint256, crypto.Uint160](payload.NewChangeView), - dbft.WithNewCommit[crypto.Uint256, crypto.Uint160](payload.NewCommit), - dbft.WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](payload.NewRecoveryRequest), - dbft.WithNewRecoveryMessage[crypto.Uint256, crypto.Uint160](func() dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { - return payload.NewRecoveryMessage(nil) +func (s *testState) getOptions() []func(*dbft.Config[crypto.Uint256]) { + opts := []func(*dbft.Config[crypto.Uint256]){ + dbft.WithTimer[crypto.Uint256](timer.New()), + dbft.WithNewHeightView[crypto.Uint256](timer.NewHV), + dbft.WithCurrentHeight[crypto.Uint256](func() uint32 { return s.currHeight }), + dbft.WithCurrentBlockHash[crypto.Uint256](func() crypto.Uint256 { return s.currHash }), + dbft.WithGetValidators[crypto.Uint256](func(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey { return s.pubs }), + dbft.WithKeyPair[crypto.Uint256](s.privs[s.myIndex], s.pubs[s.myIndex]), + dbft.WithBroadcast[crypto.Uint256](func(p Payload) { s.ch = append(s.ch, p) }), + dbft.WithGetTx[crypto.Uint256](s.pool.Get), + dbft.WithProcessBlock[crypto.Uint256](func(b dbft.Block[crypto.Uint256]) { s.blocks = append(s.blocks, b) }), + dbft.WithWatchOnly[crypto.Uint256](func() bool { return false }), + dbft.WithGetBlock[crypto.Uint256](func(crypto.Uint256) dbft.Block[crypto.Uint256] { return nil }), + dbft.WithTimer[crypto.Uint256](timer.New()), + dbft.WithLogger[crypto.Uint256](zap.NewNop()), + dbft.WithNewBlockFromContext[crypto.Uint256](newBlockFromContext), + dbft.WithSecondsPerBlock[crypto.Uint256](time.Second * 10), + dbft.WithRequestTx[crypto.Uint256](func(...crypto.Uint256) {}), + dbft.WithGetVerified[crypto.Uint256](func() []dbft.Transaction[crypto.Uint256] { return []dbft.Transaction[crypto.Uint256]{} }), + + dbft.WithNewConsensusPayload[crypto.Uint256](newConsensusPayload), + dbft.WithNewPrepareRequest[crypto.Uint256](consensus.NewPrepareRequest), + dbft.WithNewPrepareResponse[crypto.Uint256](consensus.NewPrepareResponse), + dbft.WithNewChangeView[crypto.Uint256](consensus.NewChangeView), + dbft.WithNewCommit[crypto.Uint256](consensus.NewCommit), + dbft.WithNewRecoveryRequest[crypto.Uint256](consensus.NewRecoveryRequest), + dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { + return consensus.NewRecoveryMessage(nil) }), } verify := s.verify if verify == nil { - verify = func(dbft.Block[crypto.Uint256, crypto.Uint160]) bool { return true } + verify = func(dbft.Block[crypto.Uint256]) bool { return true } } opts = append(opts, dbft.WithVerifyBlock(verify)) @@ -857,24 +858,24 @@ func (s *testState) getOptions() []func(*dbft.Config[crypto.Uint256, crypto.Uint cfg := zap.NewDevelopmentConfig() cfg.DisableStacktrace = true logger, _ := cfg.Build() - opts = append(opts, dbft.WithLogger[crypto.Uint256, crypto.Uint160](logger)) + opts = append(opts, dbft.WithLogger[crypto.Uint256](logger)) } return opts } -func newBlockFromContext(ctx *dbft.Context[crypto.Uint256, crypto.Uint160]) dbft.Block[crypto.Uint256, crypto.Uint160] { +func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { if ctx.TransactionHashes == nil { return nil } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.NextConsensus, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) + block := consensus.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) return block } // newConsensusPayload is a function for creating consensus payload of specific // type. -func newConsensusPayload(c *dbft.Context[crypto.Uint256, crypto.Uint160], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { - cp := payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) +func newConsensusPayload(c *dbft.Context[crypto.Uint256], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256] { + cp := consensus.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) return cp } diff --git a/helpers.go b/helpers.go index eff55904..21f34e32 100644 --- a/helpers.go +++ b/helpers.go @@ -2,34 +2,34 @@ package dbft type ( // inbox is a structure storing messages from a single epoch. - inbox[H Hash, A Address] struct { - prepare map[uint16]ConsensusPayload[H, A] - chViews map[uint16]ConsensusPayload[H, A] - commit map[uint16]ConsensusPayload[H, A] + inbox[H Hash] struct { + prepare map[uint16]ConsensusPayload[H] + chViews map[uint16]ConsensusPayload[H] + commit map[uint16]ConsensusPayload[H] } // cache is an auxiliary structure storing messages // from future epochs. - cache[H Hash, A Address] struct { - mail map[uint32]*inbox[H, A] + cache[H Hash] struct { + mail map[uint32]*inbox[H] } ) -func newInbox[H Hash, A Address]() *inbox[H, A] { - return &inbox[H, A]{ - prepare: make(map[uint16]ConsensusPayload[H, A]), - chViews: make(map[uint16]ConsensusPayload[H, A]), - commit: make(map[uint16]ConsensusPayload[H, A]), +func newInbox[H Hash]() *inbox[H] { + return &inbox[H]{ + prepare: make(map[uint16]ConsensusPayload[H]), + chViews: make(map[uint16]ConsensusPayload[H]), + commit: make(map[uint16]ConsensusPayload[H]), } } -func newCache[H Hash, A Address]() cache[H, A] { - return cache[H, A]{ - mail: make(map[uint32]*inbox[H, A]), +func newCache[H Hash]() cache[H] { + return cache[H]{ + mail: make(map[uint32]*inbox[H]), } } -func (c *cache[H, A]) getHeight(h uint32) *inbox[H, A] { +func (c *cache[H]) getHeight(h uint32) *inbox[H] { if m, ok := c.mail[h]; ok { delete(c.mail, h) return m @@ -38,10 +38,10 @@ func (c *cache[H, A]) getHeight(h uint32) *inbox[H, A] { return nil } -func (c *cache[H, A]) addMessage(m ConsensusPayload[H, A]) { +func (c *cache[H]) addMessage(m ConsensusPayload[H]) { msgs, ok := c.mail[m.Height()] if !ok { - msgs = newInbox[H, A]() + msgs = newInbox[H]() c.mail[m.Height()] = msgs } diff --git a/helpers_test.go b/helpers_test.go index a24f86db..33a60920 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -9,7 +9,6 @@ import ( // Structures used for type-specific dBFT/payloads implementation to avoid cyclic // dependency. type ( - address struct{} hash struct{} payloadStub struct { height uint32 @@ -22,10 +21,6 @@ func (hash) String() string { return "" } -func (address) String() string { - return "" -} - func (p payloadStub) ViewNumber() byte { panic("TODO") } @@ -47,7 +42,7 @@ func (p payloadStub) SetPayload(any) { func (p payloadStub) GetChangeView() ChangeView { panic("TODO") } -func (p payloadStub) GetPrepareRequest() PrepareRequest[hash, address] { +func (p payloadStub) GetPrepareRequest() PrepareRequest[hash] { panic("TODO") } func (p payloadStub) GetPrepareResponse() PrepareResponse[hash] { @@ -59,7 +54,7 @@ func (p payloadStub) GetCommit() Commit { func (p payloadStub) GetRecoveryRequest() RecoveryRequest { panic("TODO") } -func (p payloadStub) GetRecoveryMessage() RecoveryMessage[hash, address] { +func (p payloadStub) GetRecoveryMessage() RecoveryMessage[hash] { panic("TODO") } func (p payloadStub) ValidatorIndex() uint16 { @@ -79,7 +74,7 @@ func (p payloadStub) Hash() hash { } func TestMessageCache(t *testing.T) { - c := newCache[hash, address]() + c := newCache[hash]() p1 := payloadStub{ height: 3, diff --git a/identity.go b/identity.go index 420dc361..8ab67b81 100644 --- a/identity.go +++ b/identity.go @@ -31,14 +31,4 @@ type ( 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/internal/block/block.go b/internal/consensus/block.go similarity index 75% rename from internal/block/block.go rename to internal/consensus/block.go index 19940e7d..b24d7ccb 100644 --- a/internal/block/block.go +++ b/internal/consensus/block.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -25,48 +25,27 @@ type ( neoBlock struct { base - consensusData uint64 - transactions []dbft.Transaction[crypto.Uint256] - signature []byte - hash *crypto.Uint256 + transactions []dbft.Transaction[crypto.Uint256] + signature []byte + hash *crypto.Uint256 } ) -// Version implements Block interface. -func (b neoBlock) Version() uint32 { - return b.base.Version -} - // PrevHash implements Block interface. func (b *neoBlock) PrevHash() crypto.Uint256 { return b.base.PrevHash } -// Timestamp implements Block interface. -func (b *neoBlock) Timestamp() uint64 { - return uint64(b.base.Timestamp) * 1000000000 -} - // Index implements Block interface. func (b *neoBlock) Index() uint32 { return b.base.Index } -// NextConsensus implements Block interface. -func (b *neoBlock) NextConsensus() crypto.Uint160 { - return b.base.NextConsensus -} - // MerkleRoot implements Block interface. func (b *neoBlock) MerkleRoot() crypto.Uint256 { return b.base.MerkleRoot } -// ConsensusData implements Block interface. -func (b *neoBlock) ConsensusData() uint64 { - return b.consensusData -} - // Transactions implements Block interface. func (b *neoBlock) Transactions() []dbft.Transaction[crypto.Uint256] { return b.transactions @@ -78,13 +57,19 @@ func (b *neoBlock) SetTransactions(txx []dbft.Transaction[crypto.Uint256]) { } // NewBlock returns new block. -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] { +func NewBlock(timestamp uint64, index uint32, prevHash crypto.Uint256, nonce uint64, txHashes []crypto.Uint256) dbft.Block[crypto.Uint256] { block := new(neoBlock) block.base.Timestamp = uint32(timestamp / 1000000000) block.base.Index = index - block.base.NextConsensus = nextConsensus + + // NextConsensus and Version information is not provided by dBFT context, + // these are implementation-specific fields, and thus, should be managed outside the + // dBFT library. For simulation simplicity, let's assume that these fields are filled + // by every CN separately and is not verified. + block.base.NextConsensus = crypto.Uint160{1, 2, 3} + block.base.Version = 0 + block.base.PrevHash = prevHash - block.base.Version = version block.base.ConsensusData = nonce if len(txHashes) != 0 { diff --git a/internal/block/block_test.go b/internal/consensus/block_test.go similarity index 83% rename from internal/block/block_test.go rename to internal/consensus/block_test.go index 25e27e86..fc67807e 100644 --- a/internal/block/block_test.go +++ b/internal/consensus/block_test.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" @@ -23,25 +23,12 @@ func TestNeoBlock_Setters(t *testing.T) { b.SetTransactions(txs) assert.Equal(t, txs, b.Transactions()) - b.consensusData = 123 - assert.EqualValues(t, 123, b.ConsensusData()) - - b.base.Version = 42 - assert.EqualValues(t, 42, b.Version()) - - b.base.NextConsensus = crypto.Uint160{1} - assert.Equal(t, crypto.Uint160{1}, b.NextConsensus()) - b.base.PrevHash = crypto.Uint256{3, 7} assert.Equal(t, crypto.Uint256{3, 7}, b.PrevHash()) b.base.MerkleRoot = crypto.Uint256{13} assert.Equal(t, crypto.Uint256{13}, b.MerkleRoot()) - b.base.Timestamp = 1234 - // 1234s -> 1234000000000ns - assert.EqualValues(t, uint64(1234000000000), b.Timestamp()) - b.base.Index = 100 assert.EqualValues(t, 100, b.Index()) diff --git a/internal/payload/change_view.go b/internal/consensus/change_view.go similarity index 87% rename from internal/payload/change_view.go rename to internal/consensus/change_view.go index de3d03a9..8265e02f 100644 --- a/internal/payload/change_view.go +++ b/internal/consensus/change_view.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" @@ -41,11 +41,6 @@ func (c changeView) NewViewNumber() byte { return c.newViewNumber } -// Timestamp implements ChangeView interface. -func (c changeView) Timestamp() uint64 { - return secToNanoSec(c.timestamp) -} - // Reason implements ChangeView interface. func (c changeView) Reason() dbft.ChangeViewReason { return dbft.CVUnknown diff --git a/internal/payload/commit.go b/internal/consensus/commit.go similarity index 97% rename from internal/payload/commit.go rename to internal/consensus/commit.go index 9c8fe0b6..e6b6a03f 100644 --- a/internal/payload/commit.go +++ b/internal/consensus/commit.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" diff --git a/internal/payload/compact.go b/internal/consensus/compact.go similarity index 98% rename from internal/payload/compact.go rename to internal/consensus/compact.go index e1382232..35a98eca 100644 --- a/internal/payload/compact.go +++ b/internal/consensus/compact.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" diff --git a/internal/consensus/consensus.go b/internal/consensus/consensus.go new file mode 100644 index 00000000..04265092 --- /dev/null +++ b/internal/consensus/consensus.go @@ -0,0 +1,62 @@ +package consensus + +import ( + "time" + + "github.com/nspcc-dev/dbft" + "github.com/nspcc-dev/dbft/internal/crypto" + "github.com/nspcc-dev/dbft/timer" + "go.uber.org/zap" +) + +func New(logger *zap.Logger, key dbft.PrivateKey, pub dbft.PublicKey, + getTx func(uint256 crypto.Uint256) dbft.Transaction[crypto.Uint256], + getVerified func() []dbft.Transaction[crypto.Uint256], + broadcast func(dbft.ConsensusPayload[crypto.Uint256]), + processBlock func(dbft.Block[crypto.Uint256]), + currentHeight func() uint32, + currentBlockHash func() crypto.Uint256, + getValidators func(...dbft.Transaction[crypto.Uint256]) []dbft.PublicKey, + verifyPayload func(consensusPayload dbft.ConsensusPayload[crypto.Uint256]) error) *dbft.DBFT[crypto.Uint256] { + return dbft.New[crypto.Uint256]( + dbft.WithTimer[crypto.Uint256](timer.New()), + dbft.WithNewHeightView[crypto.Uint256](timer.NewHV), + dbft.WithLogger[crypto.Uint256](logger), + dbft.WithSecondsPerBlock[crypto.Uint256](time.Second*5), + dbft.WithKeyPair[crypto.Uint256](key, pub), + dbft.WithGetTx[crypto.Uint256](getTx), + dbft.WithGetVerified[crypto.Uint256](getVerified), + dbft.WithBroadcast[crypto.Uint256](broadcast), + dbft.WithProcessBlock[crypto.Uint256](processBlock), + dbft.WithCurrentHeight[crypto.Uint256](currentHeight), + dbft.WithCurrentBlockHash[crypto.Uint256](currentBlockHash), + dbft.WithGetValidators[crypto.Uint256](getValidators), + dbft.WithVerifyPrepareRequest[crypto.Uint256](verifyPayload), + dbft.WithVerifyPrepareResponse[crypto.Uint256](verifyPayload), + + dbft.WithNewBlockFromContext[crypto.Uint256](newBlockFromContext), + dbft.WithNewConsensusPayload[crypto.Uint256](defaultNewConsensusPayload), + dbft.WithNewPrepareRequest[crypto.Uint256](NewPrepareRequest), + dbft.WithNewPrepareResponse[crypto.Uint256](NewPrepareResponse), + dbft.WithNewChangeView[crypto.Uint256](NewChangeView), + dbft.WithNewCommit[crypto.Uint256](NewCommit), + dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { + return NewRecoveryMessage(nil) + }), + dbft.WithNewRecoveryRequest[crypto.Uint256](NewRecoveryRequest), + ) +} + +func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { + if ctx.TransactionHashes == nil { + return nil + } + block := NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) + return block +} + +// defaultNewConsensusPayload is default function for creating +// consensus payload of specific type. +func defaultNewConsensusPayload(c *dbft.Context[crypto.Uint256], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256] { + return NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) +} diff --git a/internal/payload/consensus_message.go b/internal/consensus/consensus_message.go similarity index 90% rename from internal/payload/consensus_message.go rename to internal/consensus/consensus_message.go index dd0e2436..cd51b967 100644 --- a/internal/payload/consensus_message.go +++ b/internal/consensus/consensus_message.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" @@ -31,7 +31,7 @@ type ( } ) -var _ dbft.ConsensusMessage[crypto.Uint256, crypto.Uint160] = (*message)(nil) +var _ dbft.ConsensusMessage[crypto.Uint256] = (*message)(nil) // EncodeBinary implements Serializable interface. func (m message) EncodeBinary(w *gob.Encoder) error { @@ -81,16 +81,16 @@ func (m *message) DecodeBinary(r *gob.Decoder) error { } 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) GetPrepareRequest() dbft.PrepareRequest[crypto.Uint256] { + return m.payload.(dbft.PrepareRequest[crypto.Uint256]) } func (m message) GetPrepareResponse() dbft.PrepareResponse[crypto.Uint256] { return m.payload.(dbft.PrepareResponse[crypto.Uint256]) } 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]) +func (m message) GetRecoveryMessage() dbft.RecoveryMessage[crypto.Uint256] { + return m.payload.(dbft.RecoveryMessage[crypto.Uint256]) } // ViewNumber implements ConsensusMessage interface. diff --git a/internal/payload/constructors.go b/internal/consensus/constructors.go similarity index 85% rename from internal/payload/constructors.go rename to internal/consensus/constructors.go index 68f19fe7..44326b02 100644 --- a/internal/payload/constructors.go +++ b/internal/consensus/constructors.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "github.com/nspcc-dev/dbft" @@ -6,7 +6,7 @@ import ( ) // NewConsensusPayload returns minimal ConsensusPayload implementation. -func NewConsensusPayload(t dbft.MessageType, height uint32, validatorIndex uint16, viewNumber byte, consensusMessage any) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func NewConsensusPayload(t dbft.MessageType, height uint32, validatorIndex uint16, viewNumber byte, consensusMessage any) dbft.ConsensusPayload[crypto.Uint256] { return &Payload{ message: message{ cmType: t, @@ -19,12 +19,11 @@ func NewConsensusPayload(t dbft.MessageType, height uint32, validatorIndex uint1 } // NewPrepareRequest returns minimal prepareRequest implementation. -func NewPrepareRequest(ts uint64, nonce uint64, nextConsensus crypto.Uint160, transactionsHashes []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] { +func NewPrepareRequest(ts uint64, nonce uint64, transactionsHashes []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256] { return &prepareRequest{ transactionHashes: transactionsHashes, nonce: nonce, timestamp: nanoSecToSec(ts), - nextConsensus: nextConsensus, } } @@ -58,7 +57,7 @@ func NewRecoveryRequest(ts uint64) dbft.RecoveryRequest { } // NewRecoveryMessage returns minimal RecoveryMessage implementation. -func NewRecoveryMessage(preparationHash *crypto.Uint256) dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { +func NewRecoveryMessage(preparationHash *crypto.Uint256) dbft.RecoveryMessage[crypto.Uint256] { return &recoveryMessage{ preparationHash: preparationHash, preparationPayloads: make([]preparationCompact, 0), diff --git a/internal/payload/helpers.go b/internal/consensus/helpers.go similarity index 88% rename from internal/payload/helpers.go rename to internal/consensus/helpers.go index 16cca94d..e794c41d 100644 --- a/internal/payload/helpers.go +++ b/internal/consensus/helpers.go @@ -1,4 +1,4 @@ -package payload +package consensus func secToNanoSec(s uint32) uint64 { return uint64(s) * 1000000000 diff --git a/internal/payload/message.go b/internal/consensus/message.go similarity index 96% rename from internal/payload/message.go rename to internal/consensus/message.go index a39c49a4..5b5b01c0 100644 --- a/internal/payload/message.go +++ b/internal/consensus/message.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" @@ -32,7 +32,7 @@ type ( } ) -var _ dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] = (*Payload)(nil) +var _ dbft.ConsensusPayload[crypto.Uint256] = (*Payload)(nil) // EncodeBinary implements Serializable interface. func (p Payload) EncodeBinary(w *gob.Encoder) error { diff --git a/internal/payload/message_test.go b/internal/consensus/message_test.go similarity index 96% rename from internal/payload/message_test.go rename to internal/consensus/message_test.go index 59fc44a6..c35cb1b5 100644 --- a/internal/payload/message_test.go +++ b/internal/consensus/message_test.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" @@ -107,11 +107,11 @@ func TestRecoveryMessage_NoPayloads(t *testing.T) { rec := m.GetRecoveryMessage() require.NotNil(t, rec) - var p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] + var p dbft.ConsensusPayload[crypto.Uint256] require.NotPanics(t, func() { p = rec.GetPrepareRequest(p, validators, 0) }) require.Nil(t, p) - var ps []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] + var ps []dbft.ConsensusPayload[crypto.Uint256] require.NotPanics(t, func() { ps = rec.GetPrepareResponses(p, validators) }) require.Len(t, ps, 0) @@ -156,7 +156,6 @@ func TestPayload_Setters(t *testing.T) { t.Run("ChangeView", func(t *testing.T) { cv := NewChangeView(4, 0, secToNanoSec(1234)) - assert.EqualValues(t, secToNanoSec(1234), cv.Timestamp()) assert.EqualValues(t, 4, cv.NewViewNumber()) }) diff --git a/internal/payload/prepare_request.go b/internal/consensus/prepare_request.go similarity index 78% rename from internal/payload/prepare_request.go rename to internal/consensus/prepare_request.go index 1fffb097..4955cb3e 100644 --- a/internal/payload/prepare_request.go +++ b/internal/consensus/prepare_request.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" @@ -12,18 +12,16 @@ type ( transactionHashes []crypto.Uint256 nonce uint64 timestamp uint32 - nextConsensus crypto.Uint160 } // prepareRequestAux is an auxiliary structure for prepareRequest encoding. prepareRequestAux struct { TransactionHashes []crypto.Uint256 Nonce uint64 Timestamp uint32 - NextConsensus crypto.Uint160 } ) -var _ dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] = (*prepareRequest)(nil) +var _ dbft.PrepareRequest[crypto.Uint256] = (*prepareRequest)(nil) // EncodeBinary implements Serializable interface. func (p prepareRequest) EncodeBinary(w *gob.Encoder) error { @@ -31,7 +29,6 @@ func (p prepareRequest) EncodeBinary(w *gob.Encoder) error { TransactionHashes: p.transactionHashes, Nonce: p.nonce, Timestamp: p.timestamp, - NextConsensus: p.nextConsensus, }) } @@ -44,7 +41,6 @@ func (p *prepareRequest) DecodeBinary(r *gob.Decoder) error { p.timestamp = aux.Timestamp p.nonce = aux.Nonce - p.nextConsensus = aux.NextConsensus p.transactionHashes = aux.TransactionHashes return nil } @@ -63,8 +59,3 @@ func (p prepareRequest) Nonce() uint64 { func (p prepareRequest) TransactionHashes() []crypto.Uint256 { return p.transactionHashes } - -// NextConsensus implements PrepareRequest interface. -func (p prepareRequest) NextConsensus() crypto.Uint160 { - return p.nextConsensus -} diff --git a/internal/payload/prepare_response.go b/internal/consensus/prepare_response.go similarity index 98% rename from internal/payload/prepare_response.go rename to internal/consensus/prepare_response.go index 2d5d5e47..7fbb5746 100644 --- a/internal/payload/prepare_response.go +++ b/internal/consensus/prepare_response.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" diff --git a/internal/payload/recovery_message.go b/internal/consensus/recovery_message.go similarity index 83% rename from internal/payload/recovery_message.go rename to internal/consensus/recovery_message.go index f98647f3..686b7cee 100644 --- a/internal/payload/recovery_message.go +++ b/internal/consensus/recovery_message.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" @@ -14,7 +14,7 @@ type ( preparationPayloads []preparationCompact commitPayloads []commitCompact changeViewPayloads []changeViewCompact - prepareRequest dbft.PrepareRequest[crypto.Uint256, crypto.Uint160] + prepareRequest dbft.PrepareRequest[crypto.Uint256] } // recoveryMessageAux is an auxiliary structure for recoveryMessage encoding. recoveryMessageAux struct { @@ -24,7 +24,7 @@ type ( } ) -var _ dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] = (*recoveryMessage)(nil) +var _ dbft.RecoveryMessage[crypto.Uint256] = (*recoveryMessage)(nil) // PreparationHash implements RecoveryMessage interface. func (m *recoveryMessage) PreparationHash() *crypto.Uint256 { @@ -32,7 +32,7 @@ func (m *recoveryMessage) PreparationHash() *crypto.Uint256 { } // AddPayload implements RecoveryMessage interface. -func (m *recoveryMessage) AddPayload(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) { +func (m *recoveryMessage) AddPayload(p dbft.ConsensusPayload[crypto.Uint256]) { switch p.Type() { case dbft.PrepareRequestType: m.prepareRequest = p.GetPrepareRequest() @@ -58,7 +58,7 @@ func (m *recoveryMessage) AddPayload(p dbft.ConsensusPayload[crypto.Uint256, cry } } -func fromPayload(t dbft.MessageType, recovery dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], p Serializable) *Payload { +func fromPayload(t dbft.MessageType, recovery dbft.ConsensusPayload[crypto.Uint256], p Serializable) *Payload { return &Payload{ message: message{ cmType: t, @@ -70,7 +70,7 @@ func fromPayload(t dbft.MessageType, recovery dbft.ConsensusPayload[crypto.Uint2 } // GetPrepareRequest implements RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey, ind uint16) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey, ind uint16) dbft.ConsensusPayload[crypto.Uint256] { if m.prepareRequest == nil { return nil } @@ -80,7 +80,6 @@ func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint2 timestamp: nanoSecToSec(m.prepareRequest.Timestamp()), nonce: m.prepareRequest.Nonce(), transactionHashes: m.prepareRequest.TransactionHashes(), - nextConsensus: m.prepareRequest.NextConsensus(), }) req.SetValidatorIndex(ind) @@ -88,12 +87,12 @@ func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint2 } // GetPrepareResponses implements RecoveryMessage interface. -func (m *recoveryMessage) GetPrepareResponses(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { +func (m *recoveryMessage) GetPrepareResponses(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { if m.preparationHash == nil { return nil } - payloads := make([]dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], len(m.preparationPayloads)) + payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.preparationPayloads)) for i, resp := range m.preparationPayloads { payloads[i] = fromPayload(dbft.PrepareResponseType, p, &prepareResponse{ @@ -106,8 +105,8 @@ func (m *recoveryMessage) GetPrepareResponses(p dbft.ConsensusPayload[crypto.Uin } // GetChangeViews implements RecoveryMessage interface. -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)) +func (m *recoveryMessage) GetChangeViews(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { + payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.changeViewPayloads)) for i, cv := range m.changeViewPayloads { payloads[i] = fromPayload(dbft.ChangeViewType, p, &changeView{ @@ -121,8 +120,8 @@ func (m *recoveryMessage) GetChangeViews(p dbft.ConsensusPayload[crypto.Uint256, } // GetCommits implements RecoveryMessage interface. -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)) +func (m *recoveryMessage) GetCommits(p dbft.ConsensusPayload[crypto.Uint256], _ []dbft.PublicKey) []dbft.ConsensusPayload[crypto.Uint256] { + payloads := make([]dbft.ConsensusPayload[crypto.Uint256], len(m.commitPayloads)) for i, c := range m.commitPayloads { payloads[i] = fromPayload(dbft.CommitType, p, &commit{signature: c.Signature}) diff --git a/internal/payload/recovery_request.go b/internal/consensus/recovery_request.go similarity index 97% rename from internal/payload/recovery_request.go rename to internal/consensus/recovery_request.go index 43576139..e774bc78 100644 --- a/internal/payload/recovery_request.go +++ b/internal/consensus/recovery_request.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" diff --git a/internal/simulation/main.go b/internal/simulation/main.go index 8cc655db..c2cf1691 100644 --- a/internal/simulation/main.go +++ b/internal/simulation/main.go @@ -17,9 +17,8 @@ import ( "time" "github.com/nspcc-dev/dbft" - "github.com/nspcc-dev/dbft/internal/block" + "github.com/nspcc-dev/dbft/internal/consensus" "github.com/nspcc-dev/dbft/internal/crypto" - "github.com/nspcc-dev/dbft/internal/payload" "github.com/twmb/murmur3" "go.uber.org/zap" ) @@ -27,8 +26,8 @@ import ( type ( simNode struct { id int - d *dbft.DBFT[crypto.Uint256, crypto.Uint160] - messages chan dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] + d *dbft.DBFT[crypto.Uint256] + messages chan dbft.ConsensusPayload[crypto.Uint256] key dbft.PrivateKey pub dbft.PublicKey pool *memPool @@ -110,25 +109,11 @@ 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 dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160] { - return payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) -} - func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { key, pub := crypto.Generate(rand.Reader) nodes[i] = &simNode{ id: i, - messages: make(chan dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], defaultChanSize), + messages: make(chan dbft.ConsensusPayload[crypto.Uint256], defaultChanSize), key: key, pub: pub, pool: newMemoryPool(), @@ -136,31 +121,14 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { cluster: nodes, } - nodes[i].d = dbft.New[crypto.Uint256, crypto.Uint160]( - dbft.WithLogger[crypto.Uint256, crypto.Uint160](nodes[i].log), - dbft.WithSecondsPerBlock[crypto.Uint256, crypto.Uint160](time.Second*5), - dbft.WithKeyPair[crypto.Uint256, crypto.Uint160](key, pub), - dbft.WithGetTx[crypto.Uint256, crypto.Uint160](nodes[i].pool.Get), - dbft.WithGetVerified[crypto.Uint256, crypto.Uint160](nodes[i].pool.GetVerified), - dbft.WithBroadcast[crypto.Uint256, crypto.Uint160](nodes[i].Broadcast), - dbft.WithProcessBlock[crypto.Uint256, crypto.Uint160](nodes[i].ProcessBlock), - dbft.WithCurrentHeight[crypto.Uint256, crypto.Uint160](nodes[i].CurrentHeight), - dbft.WithCurrentBlockHash[crypto.Uint256, crypto.Uint160](nodes[i].CurrentBlockHash), - dbft.WithGetValidators[crypto.Uint256, crypto.Uint160](nodes[i].GetValidators), - dbft.WithVerifyPrepareRequest[crypto.Uint256, crypto.Uint160](nodes[i].VerifyPayload), - dbft.WithVerifyPrepareResponse[crypto.Uint256, crypto.Uint160](nodes[i].VerifyPayload), - - 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), - dbft.WithNewChangeView[crypto.Uint256, crypto.Uint160](payload.NewChangeView), - dbft.WithNewCommit[crypto.Uint256, crypto.Uint160](payload.NewCommit), - dbft.WithNewRecoveryMessage[crypto.Uint256, crypto.Uint160](func() dbft.RecoveryMessage[crypto.Uint256, crypto.Uint160] { - return payload.NewRecoveryMessage(nil) - }), - dbft.WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](payload.NewRecoveryRequest), + nodes[i].d = consensus.New(nodes[i].log, key, pub, nodes[i].pool.Get, + nodes[i].pool.GetVerified, + nodes[i].Broadcast, + nodes[i].ProcessBlock, + nodes[i].CurrentHeight, + nodes[i].CurrentBlockHash, + nodes[i].GetValidators, + nodes[i].VerifyPayload, ) if nodes[i].d == nil { @@ -193,7 +161,7 @@ func sortValidators(pubs []dbft.PublicKey) { }) } -func (n *simNode) Broadcast(m dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) { +func (n *simNode) Broadcast(m dbft.ConsensusPayload[crypto.Uint256]) { for i, node := range n.cluster { if i != n.id { select { @@ -213,7 +181,7 @@ func (n *simNode) GetValidators(...dbft.Transaction[crypto.Uint256]) []dbft.Publ return n.validators } -func (n *simNode) ProcessBlock(b dbft.Block[crypto.Uint256, crypto.Uint160]) { +func (n *simNode) ProcessBlock(b dbft.Block[crypto.Uint256]) { n.d.Logger.Debug("received block", zap.Uint32("height", b.Index())) for _, tx := range b.Transactions() { @@ -225,7 +193,7 @@ func (n *simNode) ProcessBlock(b dbft.Block[crypto.Uint256, crypto.Uint160]) { } // VerifyPayload verifies that payload was received from a good validator. -func (n *simNode) VerifyPayload(p dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160]) error { +func (n *simNode) VerifyPayload(p dbft.ConsensusPayload[crypto.Uint256]) error { if *blocked != -1 && p.ValidatorIndex() == uint16(*blocked) { return fmt.Errorf("message from blocked validator: %d", *blocked) } diff --git a/prepare_request.go b/prepare_request.go index c27a2318..3ba594c9 100644 --- a/prepare_request.go +++ b/prepare_request.go @@ -1,14 +1,11 @@ package dbft // PrepareRequest represents dBFT PrepareRequest message. -type PrepareRequest[H Hash, A Address] interface { +type PrepareRequest[H Hash] interface { // Timestamp returns this message's timestamp. Timestamp() uint64 // Nonce is a random nonce. Nonce() uint64 // TransactionHashes returns hashes of all transaction in a proposed block. TransactionHashes() []H - // NextConsensus returns hash which is based on which validators will - // try to agree on a block in the current epoch. - NextConsensus() A } diff --git a/recovery_message.go b/recovery_message.go index 8e6f760d..303ecad0 100644 --- a/recovery_message.go +++ b/recovery_message.go @@ -1,17 +1,17 @@ package dbft // RecoveryMessage represents dBFT Recovery message. -type RecoveryMessage[H Hash, A Address] interface { +type RecoveryMessage[H Hash] interface { // AddPayload adds payload from this epoch to be recovered. - AddPayload(p ConsensusPayload[H, A]) + AddPayload(p ConsensusPayload[H]) // GetPrepareRequest returns PrepareRequest to be processed. - GetPrepareRequest(p ConsensusPayload[H, A], validators []PublicKey, primary uint16) ConsensusPayload[H, A] + GetPrepareRequest(p ConsensusPayload[H], validators []PublicKey, primary uint16) ConsensusPayload[H] // GetPrepareResponses returns a slice of PrepareResponse in any order. - GetPrepareResponses(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + GetPrepareResponses(p ConsensusPayload[H], validators []PublicKey) []ConsensusPayload[H] // GetChangeViews returns a slice of ChangeView in any order. - GetChangeViews(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + GetChangeViews(p ConsensusPayload[H], validators []PublicKey) []ConsensusPayload[H] // GetCommits returns a slice of Commit in any order. - GetCommits(p ConsensusPayload[H, A], validators []PublicKey) []ConsensusPayload[H, A] + GetCommits(p ConsensusPayload[H], validators []PublicKey) []ConsensusPayload[H] // PreparationHash returns has of PrepareRequest payload for this epoch. // It can be useful in case only PrepareResponse payloads were received. diff --git a/send.go b/send.go index 4e32149a..9ab7ac76 100644 --- a/send.go +++ b/send.go @@ -4,7 +4,7 @@ import ( "go.uber.org/zap" ) -func (d *DBFT[H, A]) broadcast(msg ConsensusPayload[H, A]) { +func (d *DBFT[H]) broadcast(msg ConsensusPayload[H]) { d.Logger.Debug("broadcasting message", zap.Stringer("type", msg.Type()), zap.Uint32("height", d.BlockIndex), @@ -14,15 +14,15 @@ func (d *DBFT[H, A]) broadcast(msg ConsensusPayload[H, A]) { d.Broadcast(msg) } -func (c *Context[H, A]) makePrepareRequest() ConsensusPayload[H, A] { +func (c *Context[H]) makePrepareRequest() ConsensusPayload[H] { c.Fill() - req := c.Config.NewPrepareRequest(c.Timestamp, c.Nonce, c.NextConsensus, c.TransactionHashes) + req := c.Config.NewPrepareRequest(c.Timestamp, c.Nonce, c.TransactionHashes) return c.Config.NewConsensusPayload(c, PrepareRequestType, req) } -func (d *DBFT[H, A]) sendPrepareRequest() { +func (d *DBFT[H]) sendPrepareRequest() { msg := d.makePrepareRequest() d.PreparationPayloads[d.MyIndex] = msg d.broadcast(msg) @@ -37,7 +37,7 @@ func (d *DBFT[H, A]) sendPrepareRequest() { d.checkPrepare() } -func (c *Context[H, A]) makeChangeView(ts uint64, reason ChangeViewReason) ConsensusPayload[H, A] { +func (c *Context[H]) makeChangeView(ts uint64, reason ChangeViewReason) ConsensusPayload[H] { cv := c.Config.NewChangeView(c.ViewNumber+1, reason, ts) msg := c.Config.NewConsensusPayload(c, ChangeViewType, cv) @@ -46,7 +46,7 @@ func (c *Context[H, A]) makeChangeView(ts uint64, reason ChangeViewReason) Conse return msg } -func (d *DBFT[H, A]) sendChangeView(reason ChangeViewReason) { +func (d *DBFT[H]) sendChangeView(reason ChangeViewReason) { if d.Context.WatchOnly() { return } @@ -83,7 +83,7 @@ func (d *DBFT[H, A]) sendChangeView(reason ChangeViewReason) { d.checkChangeView(newView) } -func (c *Context[H, A]) makePrepareResponse() ConsensusPayload[H, A] { +func (c *Context[H]) makePrepareResponse() ConsensusPayload[H] { resp := c.Config.NewPrepareResponse(c.PreparationPayloads[c.PrimaryIndex].Hash()) msg := c.Config.NewConsensusPayload(c, PrepareResponseType, resp) @@ -92,14 +92,14 @@ func (c *Context[H, A]) makePrepareResponse() ConsensusPayload[H, A] { return msg } -func (d *DBFT[H, A]) sendPrepareResponse() { +func (d *DBFT[H]) sendPrepareResponse() { msg := d.makePrepareResponse() d.Logger.Info("sending PrepareResponse", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber))) d.StopTxFlow() d.broadcast(msg) } -func (c *Context[H, A]) makeCommit() ConsensusPayload[H, A] { +func (c *Context[H]) makeCommit() ConsensusPayload[H] { if msg := c.CommitPayloads[c.MyIndex]; msg != nil { return msg } @@ -118,14 +118,14 @@ func (c *Context[H, A]) makeCommit() ConsensusPayload[H, A] { return nil } -func (d *DBFT[H, A]) sendCommit() { +func (d *DBFT[H]) sendCommit() { msg := d.makeCommit() d.CommitPayloads[d.MyIndex] = msg d.Logger.Info("sending Commit", zap.Uint32("height", d.BlockIndex), zap.Uint("view", uint(d.ViewNumber))) d.broadcast(msg) } -func (d *DBFT[H, A]) sendRecoveryRequest() { +func (d *DBFT[H]) sendRecoveryRequest() { // If we're here, something is wrong, we either missing some messages or // transactions or both, so re-request missing transactions here too. if d.RequestSentOrReceived() && !d.hasAllTransactions() { @@ -135,7 +135,7 @@ func (d *DBFT[H, A]) sendRecoveryRequest() { d.broadcast(d.Config.NewConsensusPayload(&d.Context, RecoveryRequestType, req)) } -func (c *Context[H, A]) makeRecoveryMessage() ConsensusPayload[H, A] { +func (c *Context[H]) makeRecoveryMessage() ConsensusPayload[H] { recovery := c.Config.NewRecoveryMessage() for _, p := range c.PreparationPayloads { @@ -165,6 +165,6 @@ func (c *Context[H, A]) makeRecoveryMessage() ConsensusPayload[H, A] { return c.Config.NewConsensusPayload(c, RecoveryMessageType, recovery) } -func (d *DBFT[H, A]) sendRecoveryMessage() { +func (d *DBFT[H]) sendRecoveryMessage() { d.broadcast(d.makeRecoveryMessage()) } diff --git a/timer.go b/timer.go new file mode 100644 index 00000000..dedfb2a7 --- /dev/null +++ b/timer.go @@ -0,0 +1,30 @@ +package dbft + +import ( + "time" +) + +// Timer is an interface which implements all time-related +// functions. It can be mocked for testing. +type Timer interface { + // Now returns current time. + Now() time.Time + // Reset resets timer to the specified block height and view. + Reset(hv HV, d time.Duration) + // Sleep stops execution for duration d. + Sleep(d time.Duration) + // Extend extends current timer with duration d. + Extend(d time.Duration) + // Stop stops timer. + Stop() + // HV returns current height and view set for the timer. + HV() HV + // C returns channel for timer events. + C() <-chan time.Time +} + +// HV is an abstraction for pair of a Height and a View. +type HV interface { + Height() uint32 + View() byte +} diff --git a/timer/timer.go b/timer/timer.go index caeda8d5..7bc961e3 100644 --- a/timer/timer.go +++ b/timer/timer.go @@ -1,53 +1,39 @@ +/* +Package timer contains default implementation of [dbft.Timer] interface and provides +all necessary timer-related functionality to [dbft.DBFT] service. +*/ package timer import ( "time" + + "github.com/nspcc-dev/dbft" ) type ( - // Timer is an interface which implements all time-related - // functions. It can be mocked for testing. - Timer interface { - // Now returns current time. - Now() time.Time - // Reset - Reset(s HV, d time.Duration) - // Sleep stops execution for duration d. - Sleep(d time.Duration) - // Extend extends current timer with duration d. - Extend(d time.Duration) - // Stop stops timer. - Stop() - // HV returns current height and view set for the timer. - HV() HV - // C returns channel for timer events. - C() <-chan time.Time - } - value struct { HV s time.Time d time.Duration } - // HV is a pair of a Height and a View. + // HV is a pair of a H and a V that implements [dbft.HV] interface. HV struct { - Height uint32 - View byte + H uint32 + V byte } - timer struct { + // Timer is a default [dbft.Timer] implementation. + Timer struct { val value tt *time.Timer ch chan time.Time } ) -var _ Timer = (*timer)(nil) - // New returns default Timer implementation. -func New() Timer { - t := &timer{ +func New() *Timer { + t := &Timer{ ch: make(chan time.Time, 1), } @@ -55,7 +41,7 @@ func New() Timer { } // C implements Timer interface. -func (t *timer) C() <-chan time.Time { +func (t *Timer) C() <-chan time.Time { if t.tt == nil { return t.ch } @@ -64,17 +50,20 @@ func (t *timer) C() <-chan time.Time { } // HV implements Timer interface. -func (t *timer) HV() HV { +func (t *Timer) HV() dbft.HV { return t.val.HV } // Reset implements Timer interface. -func (t *timer) Reset(hv HV, d time.Duration) { +func (t *Timer) Reset(hv dbft.HV, d time.Duration) { t.Stop() t.val.s = t.Now() t.val.d = d - t.val.HV = hv + t.val.HV = HV{ + H: hv.Height(), + V: hv.View(), + } if t.val.d != 0 { t.tt = time.NewTimer(t.val.d) @@ -93,7 +82,7 @@ func drain(ch <-chan time.Time) { } // Stop implements Timer interface. -func (t *timer) Stop() { +func (t *Timer) Stop() { if t.tt != nil { t.tt.Stop() t.tt = nil @@ -101,12 +90,12 @@ func (t *timer) Stop() { } // Sleep implements Timer interface. -func (t *timer) Sleep(d time.Duration) { +func (t *Timer) Sleep(d time.Duration) { time.Sleep(d) } // Extend implements Timer interface. -func (t *timer) Extend(d time.Duration) { +func (t *Timer) Extend(d time.Duration) { t.val.d += d if elapsed := time.Since(t.val.s); t.val.d > elapsed { @@ -116,6 +105,24 @@ func (t *timer) Extend(d time.Duration) { } // Now implements Timer interface. -func (t *timer) Now() time.Time { +func (t *Timer) Now() time.Time { return time.Now() } + +// NewHV is a constructor of HV. +func NewHV(height uint32, view byte) dbft.HV { + return HV{ + H: height, + V: view, + } +} + +// Height implements [dbft.HV] interface. +func (hv HV) Height() uint32 { + return hv.H +} + +// View implements [dbft.HV] interface. +func (hv HV) View() byte { + return hv.V +} diff --git a/timer/timer_test.go b/timer/timer_test.go index 566f376a..df52c212 100644 --- a/timer/timer_test.go +++ b/timer/timer_test.go @@ -10,21 +10,21 @@ import ( func TestTimer_Reset(t *testing.T) { tt := New() - tt.Reset(HV{Height: 1, View: 2}, time.Millisecond*100) + tt.Reset(HV{H: 1, V: 2}, time.Millisecond*100) tt.Sleep(time.Millisecond * 200) - shouldReceive(t, tt, HV{Height: 1, View: 2}, "no value in timer") + shouldReceive(t, tt, HV{H: 1, V: 2}, "no value in timer") - tt.Reset(HV{Height: 1, View: 2}, time.Second) - tt.Reset(HV{Height: 2, View: 3}, 0) - shouldReceive(t, tt, HV{Height: 2, View: 3}, "no value in timer after reset(0)") + tt.Reset(HV{H: 1, V: 2}, time.Second) + tt.Reset(HV{H: 2, V: 3}, 0) + shouldReceive(t, tt, HV{H: 2, V: 3}, "no value in timer after reset(0)") - tt.Reset(HV{Height: 1, View: 2}, time.Millisecond*100) + tt.Reset(HV{H: 1, V: 2}, time.Millisecond*100) tt.Sleep(time.Millisecond * 200) - tt.Reset(HV{Height: 1, View: 3}, time.Millisecond*100) + tt.Reset(HV{H: 1, V: 3}, time.Millisecond*100) tt.Sleep(time.Millisecond * 200) - shouldReceive(t, tt, HV{Height: 1, View: 3}, "invalid value after reset") + shouldReceive(t, tt, HV{H: 1, V: 3}, "invalid value after reset") - tt.Reset(HV{Height: 3, View: 1}, time.Millisecond*100) + tt.Reset(HV{H: 3, V: 1}, time.Millisecond*100) shouldNotReceive(t, tt, "value arrived too early") tt.Extend(time.Millisecond * 300) @@ -32,7 +32,7 @@ func TestTimer_Reset(t *testing.T) { shouldNotReceive(t, tt, "value arrived too early after extend") tt.Sleep(time.Millisecond * 300) - shouldReceive(t, tt, HV{Height: 3, View: 1}, "no value in timer after extend") + shouldReceive(t, tt, HV{H: 3, V: 1}, "no value in timer after extend") tt.Reset(HV{1, 1}, time.Millisecond*100) tt.Stop() @@ -40,7 +40,7 @@ func TestTimer_Reset(t *testing.T) { shouldNotReceive(t, tt, "timer was not stopped") } -func shouldReceive(t *testing.T, tt Timer, hv HV, msg string) { +func shouldReceive(t *testing.T, tt *Timer, hv HV, msg string) { select { case <-tt.C(): got := tt.HV() @@ -50,7 +50,7 @@ func shouldReceive(t *testing.T, tt Timer, hv HV, msg string) { } } -func shouldNotReceive(t *testing.T, tt Timer, msg string) { +func shouldNotReceive(t *testing.T, tt *Timer, msg string) { select { case <-tt.C(): require.Fail(t, msg)