diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b26e7da..c6365f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This document outlines major changes between releases. New features: Behaviour changes: + * specify rejected hashes on ChangeView construction (#158) Improvements: * minimum required Go version is 1.25 (#144, #156) diff --git a/config.go b/config.go index 11aedb63..1da2118b 100644 --- a/config.go +++ b/config.go @@ -58,8 +58,8 @@ type Config[H Hash] struct { GetVerified func() []Transaction[H] // VerifyPreBlock verifies if preBlock is valid. VerifyPreBlock func(b PreBlock[H]) bool - // VerifyBlock verifies if block is valid. - VerifyBlock func(b Block[H]) bool + // VerifyBlock verifies if block is valid and optionally returns the hash of invalid transaction. + VerifyBlock func(b Block[H]) (bool, H) // Broadcast should broadcast payload m to the consensus nodes. Broadcast func(m ConsensusPayload[H]) // ProcessBlock is called every time new preBlock is accepted. @@ -86,7 +86,7 @@ type Config[H Hash] struct { // NewPrepareResponse is a constructor for payload.PrepareResponse. NewPrepareResponse func(preparationHash H) PrepareResponse[H] // NewChangeView is a constructor for payload.ChangeView. - NewChangeView func(newViewNumber byte, reason ChangeViewReason, timestamp uint64) ChangeView + NewChangeView func(newViewNumber byte, reason ChangeViewReason, timestamp uint64, rejectedHash ...H) ChangeView // NewPreCommit is a constructor for payload.PreCommit. NewPreCommit func(data []byte) PreCommit // NewCommit is a constructor for payload.Commit. @@ -124,7 +124,7 @@ func defaultConfig[H Hash]() *Config[H] { StopTxFlow: func() {}, GetTx: func(H) Transaction[H] { return nil }, GetVerified: func() []Transaction[H] { return make([]Transaction[H], 0) }, - VerifyBlock: func(Block[H]) bool { return true }, + VerifyBlock: func(Block[H]) (bool, H) { return true, *new(H) }, Broadcast: func(ConsensusPayload[H]) {}, ProcessBlock: func(Block[H]) error { return nil }, GetBlock: func(H) Block[H] { return nil }, @@ -317,7 +317,7 @@ func WithVerifyPreBlock[H Hash](f func(b PreBlock[H]) bool) func(config *Config[ } // WithVerifyBlock sets VerifyBlock. -func WithVerifyBlock[H Hash](f func(b Block[H]) bool) func(config *Config[H]) { +func WithVerifyBlock[H Hash](f func(b Block[H]) (bool, H)) func(config *Config[H]) { return func(cfg *Config[H]) { cfg.VerifyBlock = f } @@ -402,7 +402,7 @@ func WithNewPrepareResponse[H Hash](f func(preparationHash H) PrepareResponse[H] } // WithNewChangeView sets NewChangeView. -func WithNewChangeView[H Hash](f func(newViewNumber byte, reason ChangeViewReason, ts uint64) ChangeView) func(config *Config[H]) { +func WithNewChangeView[H Hash](f func(newViewNumber byte, reason ChangeViewReason, ts uint64, rejectedHash ...H) ChangeView) func(config *Config[H]) { return func(cfg *Config[H]) { cfg.NewChangeView = f } diff --git a/dbft.go b/dbft.go index c97c0166..43730dc0 100644 --- a/dbft.go +++ b/dbft.go @@ -388,7 +388,10 @@ func (d *DBFT[H]) processMissingTx() { // with it, it sends a changeView request and returns false. It's only valid to // call it when all transactions for this block are already collected. func (d *DBFT[H]) createAndCheckBlock() bool { - var blockOK bool + var ( + blockOK bool + invalidH H + ) if d.isAntiMEVExtensionEnabled() { b := d.CreatePreBlock() blockOK = d.VerifyPreBlock(b) @@ -397,13 +400,13 @@ func (d *DBFT[H]) createAndCheckBlock() bool { } } else { b := d.CreateBlock() - blockOK = d.VerifyBlock(b) + blockOK, invalidH = d.VerifyBlock(b) if !blockOK { d.Logger.Warn("proposed block fails verification") } } if !blockOK { - d.sendChangeView(CVTxInvalid) + d.sendChangeView(CVTxInvalid, invalidH) return false } return true diff --git a/dbft_test.go b/dbft_test.go index a151a48e..f24643b2 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -28,7 +28,7 @@ type testState struct { pool *testPool preBlocks []dbft.PreBlock[crypto.Uint256] blocks []dbft.Block[crypto.Uint256] - verify func(b dbft.Block[crypto.Uint256]) bool + verify func(b dbft.Block[crypto.Uint256]) (bool, crypto.Uint256) } type ( @@ -136,14 +136,14 @@ func TestDBFT_SingleNode(t *testing.T) { func TestDBFT_OnReceiveRequestSendResponse(t *testing.T) { s := newTestState(2, 7) - s.verify = func(b dbft.Block[crypto.Uint256]) bool { + s.verify = func(b dbft.Block[crypto.Uint256]) (bool, crypto.Uint256) { for _, tx := range b.Transactions() { if tx.(testTx)%10 == 0 { - return false + return false, crypto.Uint256{} } } - return true + return true, crypto.Uint256{} } t.Run("receive request from primary", func(t *testing.T) { @@ -551,7 +551,7 @@ func TestDBFT_Invalid(t *testing.T) { require.Error(t, err) }) - opts = append(opts, dbft.WithNewChangeView[crypto.Uint256](func(byte, dbft.ChangeViewReason, uint64) dbft.ChangeView { + opts = append(opts, dbft.WithNewChangeView[crypto.Uint256](func(byte, dbft.ChangeViewReason, uint64, ...crypto.Uint256) dbft.ChangeView { return nil })) t.Run("without NewCommit", func(t *testing.T) { @@ -1165,7 +1165,7 @@ func (s *testState) getOptions() []func(*dbft.Config[crypto.Uint256]) { verify := s.verify if verify == nil { - verify = func(dbft.Block[crypto.Uint256]) bool { return true } + verify = func(dbft.Block[crypto.Uint256]) (bool, crypto.Uint256) { return true, crypto.Uint256{} } } opts = append(opts, dbft.WithVerifyBlock(verify)) diff --git a/internal/consensus/constructors.go b/internal/consensus/constructors.go index 096fa37d..815bd19a 100644 --- a/internal/consensus/constructors.go +++ b/internal/consensus/constructors.go @@ -37,7 +37,7 @@ func NewPrepareResponse(preparationHash crypto.Uint256) dbft.PrepareResponse[cry } // NewChangeView returns minimal ChangeView implementation. -func NewChangeView(newViewNumber byte, _ dbft.ChangeViewReason, ts uint64) dbft.ChangeView { +func NewChangeView(newViewNumber byte, _ dbft.ChangeViewReason, ts uint64, rejected ...crypto.Uint256) dbft.ChangeView { return &changeView{ newViewNumber: newViewNumber, timestamp: nanoSecToSec(ts), diff --git a/send.go b/send.go index 6c40d952..f35d1a3a 100644 --- a/send.go +++ b/send.go @@ -57,8 +57,8 @@ func (d *DBFT[H]) sendPrepareRequest(force bool) { d.checkPrepare() } -func (c *Context[H]) makeChangeView(ts uint64, reason ChangeViewReason) ConsensusPayload[H] { - cv := c.Config.NewChangeView(c.ViewNumber+1, reason, ts) +func (c *Context[H]) makeChangeView(ts uint64, reason ChangeViewReason, invalidH ...H) ConsensusPayload[H] { + cv := c.Config.NewChangeView(c.ViewNumber+1, reason, ts, invalidH...) msg := c.Config.NewConsensusPayload(c, ChangeViewType, cv) c.ChangeViewPayloads[c.MyIndex] = msg @@ -66,7 +66,7 @@ func (c *Context[H]) makeChangeView(ts uint64, reason ChangeViewReason) Consensu return msg } -func (d *DBFT[H]) sendChangeView(reason ChangeViewReason) { +func (d *DBFT[H]) sendChangeView(reason ChangeViewReason, invalidH ...H) { if d.Context.WatchOnly() { return } @@ -97,7 +97,7 @@ func (d *DBFT[H]) sendChangeView(reason ChangeViewReason) { zap.Int("nc", nc), zap.Int("nf", nf)) - msg := d.makeChangeView(uint64(d.Timer.Now().UnixNano()), reason) + msg := d.makeChangeView(uint64(d.Timer.Now().UnixNano()), reason, invalidH...) d.StopTxFlow() d.broadcast(msg) d.checkChangeView(newView)