From c283b803d91e0dcd1002a9c79fb601a821391a9f Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 12:39:21 +0300 Subject: [PATCH 1/9] *: remove NextConsensus mentions from dBFT API NextConsensus API is not used by the users of dBFT. NextConsensus handling (proposal, verification and agrreement) is moved to the upper level of dBFT users. Starting from this commit NextConsensus verification should be performed by dBFT user manually in WithVerifyPrepareRequest callback. A part of #84. Signed-off-by: Anna Shaleva --- block.go | 4 +- check.go | 6 +- config.go | 163 ++++++++++++-------------- consensus_message.go | 6 +- consensus_payload.go | 4 +- context.go | 73 ++++++------ dbft.go | 68 +++++------ dbft_test.go | 142 ++++++++++------------ helpers.go | 34 +++--- helpers_test.go | 11 +- identity.go | 10 -- internal/block/block.go | 12 +- internal/block/block_test.go | 3 - internal/payload/consensus_message.go | 10 +- internal/payload/constructors.go | 7 +- internal/payload/message.go | 2 +- internal/payload/message_test.go | 4 +- internal/payload/prepare_request.go | 11 +- internal/payload/recovery_message.go | 23 ++-- internal/simulation/main.go | 63 +++++----- prepare_request.go | 5 +- recovery_message.go | 12 +- send.go | 26 ++-- 23 files changed, 312 insertions(+), 387 deletions(-) diff --git a/block.go b/block.go index c849fc89..af3bf6b2 100644 --- a/block.go +++ b/block.go @@ -1,7 +1,7 @@ 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 @@ -16,8 +16,6 @@ type Block[H Hash, A Address] interface { 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/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..014785e4 100644 --- a/config.go +++ b/config.go @@ -10,7 +10,7 @@ import ( ) // 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 @@ -26,7 +26,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 +39,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 +57,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,20 +70,20 @@ 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, @@ -95,21 +93,21 @@ 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.CurrentHeight == nil { @@ -120,8 +118,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 +139,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 +162,190 @@ 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.Timer) func(config *Config[H]) { + return func(cfg *Config[H]) { cfg.Timer = t } } // 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..15eb2b85 100644 --- a/context.go +++ b/context.go @@ -10,17 +10,17 @@ import ( // 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 @@ -40,8 +40,6 @@ type Context[H Hash, A Address] struct { 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,18 +54,18 @@ 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 @@ -78,16 +76,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 +95,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,7 +119,7 @@ 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) { count++ @@ -133,18 +131,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 +159,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 +173,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 +184,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,7 +198,7 @@ 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) @@ -223,11 +221,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 @@ -244,7 +242,7 @@ func (c *Context[H, A]) reset(view byte, ts uint64) { } // 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 +259,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 +267,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 +292,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 +305,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..26a33cb4 100644 --- a/dbft.go +++ b/dbft.go @@ -13,12 +13,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 +27,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 +38,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 +49,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 +69,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 +80,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 +137,7 @@ func (d *DBFT[H, A]) initializeConsensus(view byte, ts uint64) { } // OnTransaction notifies service about receiving new transaction. -func (d *DBFT[H, A]) OnTransaction(tx 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,7 +169,7 @@ 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 timer.HV) { if d.Context.WatchOnly() || d.BlockSent() { return } @@ -200,7 +200,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 @@ -269,7 +269,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 +291,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 +327,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 +342,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 +368,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 +377,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 +400,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 +452,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 +483,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 +525,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 +547,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,7 +607,7 @@ 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)), @@ -625,7 +615,7 @@ func (d *DBFT[H, A]) changeTimer(delay time.Duration) { d.Timer.Reset(timer.HV{Height: d.BlockIndex, View: 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 +623,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..48209e9d 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -15,7 +15,7 @@ import ( "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 +26,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 +44,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 +52,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() @@ -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,7 +161,7 @@ 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) @@ -189,7 +189,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 +237,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 +257,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 +279,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() @@ -339,7 +339,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 +361,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 +384,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) @@ -411,92 +411,85 @@ 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 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 +522,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. @@ -760,7 +753,7 @@ 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 := payload.NewPrepareRequest(0, 0, hashes) p := payload.NewConsensusPayload(dbft.PrepareRequestType, height, from, 0, req) return p @@ -789,7 +782,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 +805,38 @@ 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] { +func (s *testState) getOptions() []func(*dbft.Config[crypto.Uint256]) { + opts := []func(*dbft.Config[crypto.Uint256]){ + 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](payload.NewPrepareRequest), + dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), + dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), + dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), + dbft.WithNewRecoveryRequest[crypto.Uint256](payload.NewRecoveryRequest), + dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { return payload.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,23 +845,23 @@ 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 := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Version, 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] { +func newConsensusPayload(c *dbft.Context[crypto.Uint256], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256] { cp := payload.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/block/block.go index 19940e7d..ddbf4266 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -52,11 +52,6 @@ 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 @@ -78,11 +73,14 @@ 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, version uint32, 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 information is not provided by dBFT context, it's an implementation-specific field, + // and thus, should be managed outside the dBFT library. For simulation simplicity, let's assume + // that NextConsensus is filled by every CN separately and is not verified. + block.base.NextConsensus = crypto.Uint160{1, 2, 3} block.base.PrevHash = prevHash block.base.Version = version block.base.ConsensusData = nonce diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 25e27e86..96f98035 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -29,9 +29,6 @@ func TestNeoBlock_Setters(t *testing.T) { 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()) diff --git a/internal/payload/consensus_message.go b/internal/payload/consensus_message.go index dd0e2436..f0285aa7 100644 --- a/internal/payload/consensus_message.go +++ b/internal/payload/consensus_message.go @@ -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/payload/constructors.go index 68f19fe7..712b13dd 100644 --- a/internal/payload/constructors.go +++ b/internal/payload/constructors.go @@ -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/message.go b/internal/payload/message.go index a39c49a4..b11f4127 100644 --- a/internal/payload/message.go +++ b/internal/payload/message.go @@ -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/payload/message_test.go index 59fc44a6..aa764a64 100644 --- a/internal/payload/message_test.go +++ b/internal/payload/message_test.go @@ -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) diff --git a/internal/payload/prepare_request.go b/internal/payload/prepare_request.go index 1fffb097..2c67f670 100644 --- a/internal/payload/prepare_request.go +++ b/internal/payload/prepare_request.go @@ -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/recovery_message.go b/internal/payload/recovery_message.go index f98647f3..20831564 100644 --- a/internal/payload/recovery_message.go +++ b/internal/payload/recovery_message.go @@ -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/simulation/main.go b/internal/simulation/main.go index 8cc655db..387007f3 100644 --- a/internal/simulation/main.go +++ b/internal/simulation/main.go @@ -27,8 +27,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,17 +110,17 @@ func initNodes(nodes []*simNode, log *zap.Logger) { } } -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 := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, 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] { +func defaultNewConsensusPayload(c *dbft.Context[crypto.Uint256], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256] { return payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) } @@ -128,7 +128,7 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { key, pub := crypto.Generate(rand.Reader) nodes[i] = &simNode{ id: i, - messages: make(chan dbft.ConsensusPayload[crypto.Uint256, crypto.Uint160], defaultChanSize), + messages: make(chan dbft.ConsensusPayload[crypto.Uint256], defaultChanSize), key: key, pub: pub, pool: newMemoryPool(), @@ -136,31 +136,30 @@ 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] { + nodes[i].d = dbft.New[crypto.Uint256]( + dbft.WithLogger[crypto.Uint256](nodes[i].log), + dbft.WithSecondsPerBlock[crypto.Uint256](time.Second*5), + dbft.WithKeyPair[crypto.Uint256](key, pub), + dbft.WithGetTx[crypto.Uint256](nodes[i].pool.Get), + dbft.WithGetVerified[crypto.Uint256](nodes[i].pool.GetVerified), + dbft.WithBroadcast[crypto.Uint256](nodes[i].Broadcast), + dbft.WithProcessBlock[crypto.Uint256](nodes[i].ProcessBlock), + dbft.WithCurrentHeight[crypto.Uint256](nodes[i].CurrentHeight), + dbft.WithCurrentBlockHash[crypto.Uint256](nodes[i].CurrentBlockHash), + dbft.WithGetValidators[crypto.Uint256](nodes[i].GetValidators), + dbft.WithVerifyPrepareRequest[crypto.Uint256](nodes[i].VerifyPayload), + dbft.WithVerifyPrepareResponse[crypto.Uint256](nodes[i].VerifyPayload), + + dbft.WithNewBlockFromContext[crypto.Uint256](newBlockFromContext), + dbft.WithNewConsensusPayload[crypto.Uint256](defaultNewConsensusPayload), + dbft.WithNewPrepareRequest[crypto.Uint256](payload.NewPrepareRequest), + dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), + dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), + dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), + dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { return payload.NewRecoveryMessage(nil) }), - dbft.WithNewRecoveryRequest[crypto.Uint256, crypto.Uint160](payload.NewRecoveryRequest), + dbft.WithNewRecoveryRequest[crypto.Uint256](payload.NewRecoveryRequest), ) if nodes[i].d == nil { @@ -193,7 +192,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 +212,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 +224,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()) } From d49b50136e24ca734788001e3b0e9775c2742223 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 14:28:05 +0300 Subject: [PATCH 2/9] dbft: remove version from context Version context field is always set to 0. There is no API to change Version in dBFT context. Given a possibility of Block version change, we either need to introduce this Version initializer or need to move Version handling out of dBFT. The latter case is chosen, because in this case block/PrepareRequest version can be handled directly by node depending on block index fetched from dBFT context. Also remove Version() block API, it's unused. A part of #84. Signed-off-by: Anna Shaleva --- block.go | 2 -- context.go | 1 - dbft_test.go | 2 +- internal/block/block.go | 18 ++++++++---------- internal/block/block_test.go | 3 --- internal/simulation/main.go | 2 +- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/block.go b/block.go index af3bf6b2..52842b0d 100644 --- a/block.go +++ b/block.go @@ -4,8 +4,6 @@ package dbft 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. diff --git a/context.go b/context.go index 15eb2b85..370e8913 100644 --- a/context.go +++ b/context.go @@ -38,7 +38,6 @@ type Context[H Hash] struct { MyIndex int // PrimaryIndex is an index of the primary node in the current epoch. PrimaryIndex uint - Version uint32 // PrevHash is a hash of the previous block. PrevHash H diff --git a/dbft_test.go b/dbft_test.go index 48209e9d..f123b2a8 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -855,7 +855,7 @@ func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Ui if ctx.TransactionHashes == nil { return nil } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) + block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) return block } diff --git a/internal/block/block.go b/internal/block/block.go index ddbf4266..169a1aa1 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -32,11 +32,6 @@ type ( } ) -// 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 @@ -73,16 +68,19 @@ func (b *neoBlock) SetTransactions(txx []dbft.Transaction[crypto.Uint256]) { } // NewBlock returns new block. -func NewBlock(timestamp uint64, index uint32, prevHash crypto.Uint256, version uint32, nonce uint64, txHashes []crypto.Uint256) dbft.Block[crypto.Uint256] { +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 - // NextConsensus information is not provided by dBFT context, it's an implementation-specific field, - // and thus, should be managed outside the dBFT library. For simulation simplicity, let's assume - // that NextConsensus is filled by every CN separately and is not verified. + + // 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/block/block_test.go index 96f98035..fce0ce92 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -26,9 +26,6 @@ func TestNeoBlock_Setters(t *testing.T) { b.consensusData = 123 assert.EqualValues(t, 123, b.ConsensusData()) - b.base.Version = 42 - assert.EqualValues(t, 42, b.Version()) - b.base.PrevHash = crypto.Uint256{3, 7} assert.Equal(t, crypto.Uint256{3, 7}, b.PrevHash()) diff --git a/internal/simulation/main.go b/internal/simulation/main.go index 387007f3..75ce3fd1 100644 --- a/internal/simulation/main.go +++ b/internal/simulation/main.go @@ -114,7 +114,7 @@ func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Ui if ctx.TransactionHashes == nil { return nil } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Version, ctx.Nonce, ctx.TransactionHashes) + block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) return block } From f2ac5ce55c7b1680d3ad46a14c557ed6fce18555 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 14:38:05 +0300 Subject: [PATCH 3/9] dbft: remove Timestamp() block API It's unused by dBFT, and thus, it's not needed in this interface. If this property is used by dBFT user, then the user need to convert dBFT Block to user's Block and retrieve all necessary info. A part of #84. Signed-off-by: Anna Shaleva --- block.go | 2 -- internal/block/block.go | 5 ----- internal/block/block_test.go | 4 ---- 3 files changed, 11 deletions(-) diff --git a/block.go b/block.go index 52842b0d..c24670de 100644 --- a/block.go +++ b/block.go @@ -8,8 +8,6 @@ type Block[H Hash] interface { 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. diff --git a/internal/block/block.go b/internal/block/block.go index 169a1aa1..fbcd3f86 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -37,11 +37,6 @@ 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 diff --git a/internal/block/block_test.go b/internal/block/block_test.go index fce0ce92..66ed9726 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -32,10 +32,6 @@ func TestNeoBlock_Setters(t *testing.T) { 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()) From 76d083299d685b4186bfbc2126d1d58141491365 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 14:49:47 +0300 Subject: [PATCH 4/9] internal: split simulation from consensus service implementation All dBFT payloads implementations should be kept in the same package or have exported fields. It's inevitable since dBFT user have to refer to payload fields via converting dBFT interfaces to user-defined local structures. dBFT test (`dbft_test` package) imports custom payloads implementations from `internal`. Since all payloads will be moved to the same package, we need to split this package from `main` package of simulation. A consequence of #84. Signed-off-by: Anna Shaleva --- internal/consensus/consensus.go | 61 +++++++++++++++++++++++++++++++++ internal/simulation/main.go | 49 +++++--------------------- 2 files changed, 70 insertions(+), 40 deletions(-) create mode 100644 internal/consensus/consensus.go diff --git a/internal/consensus/consensus.go b/internal/consensus/consensus.go new file mode 100644 index 00000000..97924896 --- /dev/null +++ b/internal/consensus/consensus.go @@ -0,0 +1,61 @@ +package consensus + +import ( + "time" + + "github.com/nspcc-dev/dbft" + "github.com/nspcc-dev/dbft/internal/block" + "github.com/nspcc-dev/dbft/internal/crypto" + "github.com/nspcc-dev/dbft/internal/payload" + "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.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](payload.NewPrepareRequest), + dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), + dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), + dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), + dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { + return payload.NewRecoveryMessage(nil) + }), + dbft.WithNewRecoveryRequest[crypto.Uint256](payload.NewRecoveryRequest), + ) +} + +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.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 payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) +} diff --git a/internal/simulation/main.go b/internal/simulation/main.go index 75ce3fd1..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" ) @@ -110,20 +109,6 @@ func initNodes(nodes []*simNode, log *zap.Logger) { } } -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.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 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{ @@ -136,30 +121,14 @@ func initSimNode(nodes []*simNode, i int, log *zap.Logger) error { cluster: nodes, } - nodes[i].d = dbft.New[crypto.Uint256]( - dbft.WithLogger[crypto.Uint256](nodes[i].log), - dbft.WithSecondsPerBlock[crypto.Uint256](time.Second*5), - dbft.WithKeyPair[crypto.Uint256](key, pub), - dbft.WithGetTx[crypto.Uint256](nodes[i].pool.Get), - dbft.WithGetVerified[crypto.Uint256](nodes[i].pool.GetVerified), - dbft.WithBroadcast[crypto.Uint256](nodes[i].Broadcast), - dbft.WithProcessBlock[crypto.Uint256](nodes[i].ProcessBlock), - dbft.WithCurrentHeight[crypto.Uint256](nodes[i].CurrentHeight), - dbft.WithCurrentBlockHash[crypto.Uint256](nodes[i].CurrentBlockHash), - dbft.WithGetValidators[crypto.Uint256](nodes[i].GetValidators), - dbft.WithVerifyPrepareRequest[crypto.Uint256](nodes[i].VerifyPayload), - dbft.WithVerifyPrepareResponse[crypto.Uint256](nodes[i].VerifyPayload), - - dbft.WithNewBlockFromContext[crypto.Uint256](newBlockFromContext), - dbft.WithNewConsensusPayload[crypto.Uint256](defaultNewConsensusPayload), - dbft.WithNewPrepareRequest[crypto.Uint256](payload.NewPrepareRequest), - dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), - dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), - dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), - dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { - return payload.NewRecoveryMessage(nil) - }), - dbft.WithNewRecoveryRequest[crypto.Uint256](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 { From 0cf24d06812be796e3ff01e63f9f4bb85695fa0e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 15:21:05 +0300 Subject: [PATCH 5/9] internal: move dBFT payloads implementations to a single package It's needed to be able to cast dBFT payload interfaces to local user-defined payloads implementations during consensus service functioning. A psrt of #84. Signed-off-by: Anna Shaleva --- dbft_test.go | 37 +++++++++---------- internal/{block => consensus}/block.go | 2 +- internal/{block => consensus}/block_test.go | 2 +- .../{payload => consensus}/change_view.go | 2 +- internal/{payload => consensus}/commit.go | 2 +- internal/{payload => consensus}/compact.go | 2 +- internal/consensus/consensus.go | 18 ++++----- .../consensus_message.go | 2 +- .../{payload => consensus}/constructors.go | 2 +- internal/{payload => consensus}/helpers.go | 2 +- internal/{payload => consensus}/message.go | 2 +- .../{payload => consensus}/message_test.go | 2 +- .../{payload => consensus}/prepare_request.go | 2 +- .../prepare_response.go | 2 +- .../recovery_message.go | 2 +- .../recovery_request.go | 2 +- 16 files changed, 40 insertions(+), 43 deletions(-) rename internal/{block => consensus}/block.go (99%) rename internal/{block => consensus}/block_test.go (99%) rename internal/{payload => consensus}/change_view.go (98%) rename internal/{payload => consensus}/commit.go (97%) rename internal/{payload => consensus}/compact.go (98%) rename internal/{payload => consensus}/consensus_message.go (99%) rename internal/{payload => consensus}/constructors.go (99%) rename internal/{payload => consensus}/helpers.go (88%) rename internal/{payload => consensus}/message.go (99%) rename internal/{payload => consensus}/message_test.go (99%) rename internal/{payload => consensus}/prepare_request.go (98%) rename internal/{payload => consensus}/prepare_response.go (98%) rename internal/{payload => consensus}/recovery_message.go (99%) rename internal/{payload => consensus}/recovery_request.go (97%) diff --git a/dbft_test.go b/dbft_test.go index f123b2a8..5a1bbc77 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -7,9 +7,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/nspcc-dev/dbft/timer" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -724,27 +723,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 } @@ -753,9 +752,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, 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 } @@ -824,13 +823,13 @@ func (s *testState) getOptions() []func(*dbft.Config[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](payload.NewPrepareRequest), - dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), - dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), - dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), - dbft.WithNewRecoveryRequest[crypto.Uint256](payload.NewRecoveryRequest), + 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 payload.NewRecoveryMessage(nil) + return consensus.NewRecoveryMessage(nil) }), } @@ -855,14 +854,14 @@ func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Ui if ctx.TransactionHashes == nil { return nil } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, 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], t dbft.MessageType, msg any) dbft.ConsensusPayload[crypto.Uint256] { - cp := payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) + cp := consensus.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) return cp } diff --git a/internal/block/block.go b/internal/consensus/block.go similarity index 99% rename from internal/block/block.go rename to internal/consensus/block.go index fbcd3f86..fdf36c87 100644 --- a/internal/block/block.go +++ b/internal/consensus/block.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" diff --git a/internal/block/block_test.go b/internal/consensus/block_test.go similarity index 99% rename from internal/block/block_test.go rename to internal/consensus/block_test.go index 66ed9726..09b2d08b 100644 --- a/internal/block/block_test.go +++ b/internal/consensus/block_test.go @@ -1,4 +1,4 @@ -package block +package consensus import ( "bytes" diff --git a/internal/payload/change_view.go b/internal/consensus/change_view.go similarity index 98% rename from internal/payload/change_view.go rename to internal/consensus/change_view.go index de3d03a9..eddac5c3 100644 --- a/internal/payload/change_view.go +++ b/internal/consensus/change_view.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "encoding/gob" 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 index 97924896..f4064e68 100644 --- a/internal/consensus/consensus.go +++ b/internal/consensus/consensus.go @@ -4,9 +4,7 @@ import ( "time" "github.com/nspcc-dev/dbft" - "github.com/nspcc-dev/dbft/internal/block" "github.com/nspcc-dev/dbft/internal/crypto" - "github.com/nspcc-dev/dbft/internal/payload" "go.uber.org/zap" ) @@ -35,14 +33,14 @@ func New(logger *zap.Logger, key dbft.PrivateKey, pub dbft.PublicKey, dbft.WithNewBlockFromContext[crypto.Uint256](newBlockFromContext), dbft.WithNewConsensusPayload[crypto.Uint256](defaultNewConsensusPayload), - dbft.WithNewPrepareRequest[crypto.Uint256](payload.NewPrepareRequest), - dbft.WithNewPrepareResponse[crypto.Uint256](payload.NewPrepareResponse), - dbft.WithNewChangeView[crypto.Uint256](payload.NewChangeView), - dbft.WithNewCommit[crypto.Uint256](payload.NewCommit), + 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 payload.NewRecoveryMessage(nil) + return NewRecoveryMessage(nil) }), - dbft.WithNewRecoveryRequest[crypto.Uint256](payload.NewRecoveryRequest), + dbft.WithNewRecoveryRequest[crypto.Uint256](NewRecoveryRequest), ) } @@ -50,12 +48,12 @@ func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Ui if ctx.TransactionHashes == nil { return nil } - block := block.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) + 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 payload.NewConsensusPayload(t, c.BlockIndex, uint16(c.MyIndex), c.ViewNumber, msg) + 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 99% rename from internal/payload/consensus_message.go rename to internal/consensus/consensus_message.go index f0285aa7..cd51b967 100644 --- a/internal/payload/consensus_message.go +++ b/internal/consensus/consensus_message.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" diff --git a/internal/payload/constructors.go b/internal/consensus/constructors.go similarity index 99% rename from internal/payload/constructors.go rename to internal/consensus/constructors.go index 712b13dd..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" 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 99% rename from internal/payload/message.go rename to internal/consensus/message.go index b11f4127..5b5b01c0 100644 --- a/internal/payload/message.go +++ b/internal/consensus/message.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" diff --git a/internal/payload/message_test.go b/internal/consensus/message_test.go similarity index 99% rename from internal/payload/message_test.go rename to internal/consensus/message_test.go index aa764a64..bfb5320f 100644 --- a/internal/payload/message_test.go +++ b/internal/consensus/message_test.go @@ -1,4 +1,4 @@ -package payload +package consensus import ( "bytes" diff --git a/internal/payload/prepare_request.go b/internal/consensus/prepare_request.go similarity index 98% rename from internal/payload/prepare_request.go rename to internal/consensus/prepare_request.go index 2c67f670..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" 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 99% rename from internal/payload/recovery_message.go rename to internal/consensus/recovery_message.go index 20831564..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" 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" From f3de891af1118b05775f794c5f7652a3390cfaab Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 15:24:15 +0300 Subject: [PATCH 6/9] dbft: remove ConsensusData() API of dbft.Block It's unused by dBFT and thus, should be removed from Block interface. If user need to access this data, then he should convert dbft.Block to the local user-defined Block implementation. A part of #84. Signed-off-by: Anna Shaleva --- block.go | 2 -- internal/consensus/block.go | 12 +++--------- internal/consensus/block_test.go | 3 --- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/block.go b/block.go index c24670de..3c51e846 100644 --- a/block.go +++ b/block.go @@ -10,8 +10,6 @@ type Block[H Hash] interface { MerkleRoot() H // Index returns block index. Index() uint32 - // ConsensusData is a random nonce. - ConsensusData() uint64 // Signature returns block's signature. Signature() []byte diff --git a/internal/consensus/block.go b/internal/consensus/block.go index fdf36c87..b24d7ccb 100644 --- a/internal/consensus/block.go +++ b/internal/consensus/block.go @@ -25,10 +25,9 @@ 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 } ) @@ -47,11 +46,6 @@ 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 diff --git a/internal/consensus/block_test.go b/internal/consensus/block_test.go index 09b2d08b..fc67807e 100644 --- a/internal/consensus/block_test.go +++ b/internal/consensus/block_test.go @@ -23,9 +23,6 @@ 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.PrevHash = crypto.Uint256{3, 7} assert.Equal(t, crypto.Uint256{3, 7}, b.PrevHash()) From 32e1df2a152d7c19c929bb9f9297fa9a476b3329 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 15:30:54 +0300 Subject: [PATCH 7/9] dbft: remove Timestamp() API from dbft.ChangeView interface This API is unused by dBFT. If a user needs to access ChangeView timestamp, he has to convert dbft.ChangeView to the user-defined ChangeView implementation. A part of #84. Signed-off-by: Anna Shaleva --- change_view.go | 3 --- internal/consensus/change_view.go | 5 ----- internal/consensus/message_test.go | 1 - 3 files changed, 9 deletions(-) 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/internal/consensus/change_view.go b/internal/consensus/change_view.go index eddac5c3..8265e02f 100644 --- a/internal/consensus/change_view.go +++ b/internal/consensus/change_view.go @@ -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/consensus/message_test.go b/internal/consensus/message_test.go index bfb5320f..c35cb1b5 100644 --- a/internal/consensus/message_test.go +++ b/internal/consensus/message_test.go @@ -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()) }) From c50ee1ecc1fb0f570bd3841078636c0d28f935fe Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 17:13:26 +0300 Subject: [PATCH 8/9] timer: move Timer inteface to `dbft` package Store Timer interface along with other dBFT interfaces and provide default timer implementation in `timer` package. Create `dbft.HV` interface and configuration for its customisation. Provide default implementation of `dbft.HV` in `timer` package. A part of #84. Signed-off-by: Anna Shaleva --- config.go | 19 ++++++-- context.go | 14 +++--- dbft.go | 23 +++++----- dbft_test.go | 44 ++++++++++++------- internal/consensus/consensus.go | 3 ++ timer.go | 30 +++++++++++++ timer/timer.go | 77 ++++++++++++++++++--------------- timer/timer_test.go | 24 +++++----- 8 files changed, 146 insertions(+), 88 deletions(-) create mode 100644 timer.go diff --git a/config.go b/config.go index 014785e4..31bea995 100644 --- a/config.go +++ b/config.go @@ -5,7 +5,6 @@ import ( "errors" "time" - "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) @@ -14,7 +13,9 @@ 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 @@ -85,7 +86,6 @@ func defaultConfig[H Hash]() *Config[H] { // fields which are set to nil must be provided from client return &Config[H]{ Logger: zap.NewNop(), - Timer: timer.New(), SecondsPerBlock: defaultSecondsPerBlock, TimestampIncrement: defaultTimestampIncrement, GetKeyPair: nil, @@ -110,6 +110,10 @@ func defaultConfig[H Hash]() *Config[H] { 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 { @@ -176,12 +180,19 @@ func WithLogger[H Hash](log *zap.Logger) func(config *Config[H]) { } // WithTimer sets Timer. -func WithTimer[H Hash](t timer.Timer) func(config *Config[H]) { +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](d time.Duration) func(config *Config[H]) { return func(cfg *Config[H]) { diff --git a/context.go b/context.go index 370e8913..334a17b6 100644 --- a/context.go +++ b/context.go @@ -4,8 +4,6 @@ import ( "crypto/rand" "encoding/binary" "time" - - "github.com/nspcc-dev/dbft/timer" ) // Context is a main dBFT structure which @@ -67,7 +65,7 @@ type Context[H Hash] struct { 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). @@ -120,7 +118,7 @@ func (c *Context[H]) CountCommitted() (count int) { // for this view and that hasn't sent the Commit message at the previous views. 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++ } } @@ -200,7 +198,7 @@ func (c *Context[H]) reset(view byte, ts uint64) { 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 { @@ -233,10 +231,8 @@ func (c *Context[H]) 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 } } diff --git a/dbft.go b/dbft.go index 26a33cb4..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" ) @@ -169,22 +168,22 @@ func (d *DBFT[H]) OnTransaction(tx Transaction[H]) { } // OnTimeout advances state machine as if timeout was fired. -func (d *DBFT[H]) 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() @@ -237,11 +236,9 @@ func (d *DBFT[H]) OnReceive(msg ConsensusPayload[H]) { } 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 { @@ -612,7 +609,7 @@ func (d *DBFT[H]) changeTimer(delay time.Duration) { 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]) extendTimer(count time.Duration) { diff --git a/dbft_test.go b/dbft_test.go index 5a1bbc77..86826b4b 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -63,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() @@ -73,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) @@ -166,7 +167,8 @@ func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { 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()) @@ -303,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) @@ -394,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()) @@ -418,6 +420,16 @@ func TestDBFT_Invalid(t *testing.T) { require.NotNil(t, 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...)) }) @@ -570,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()) @@ -580,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()) @@ -589,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()) @@ -605,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()) @@ -628,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()) @@ -663,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()) @@ -671,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()) @@ -806,6 +818,8 @@ func (s testState) copyWithIndex(myIndex int) *testState { 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 }), diff --git a/internal/consensus/consensus.go b/internal/consensus/consensus.go index f4064e68..04265092 100644 --- a/internal/consensus/consensus.go +++ b/internal/consensus/consensus.go @@ -5,6 +5,7 @@ import ( "github.com/nspcc-dev/dbft" "github.com/nspcc-dev/dbft/internal/crypto" + "github.com/nspcc-dev/dbft/timer" "go.uber.org/zap" ) @@ -18,6 +19,8 @@ func New(logger *zap.Logger, key dbft.PrivateKey, pub dbft.PublicKey, 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), 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) From d504ad872356e9f119f5fdd035aabccd47632597 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 6 Mar 2024 17:24:24 +0300 Subject: [PATCH 9/9] README: update documentation according to dBFT API refactoring Also add a record to CHANGELOG.md. A part of #84. Signed-off-by: Anna Shaleva --- CHANGELOG.md | 1 + README.md | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) 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.