diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b26e7da4..208e5ba25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This document outlines major changes between releases. ## [Unreleased] New features: + * `PrepareRequestExtensionEnablingHeight` configuration parameter and + `NewPrepareRequestExtended` callback to attach full transaction list to + `PrepareRequest` instead of hashes starting from the given height (#160) Behaviour changes: diff --git a/config.go b/config.go index 11aedb639..849c61f1f 100644 --- a/config.go +++ b/config.go @@ -32,6 +32,9 @@ type Config[H Hash] struct { // AntiMEVExtensionEnablingHeight denotes the height starting from which dBFT // Anti-MEV extensions should be enabled. -1 means no extension is enabled. AntiMEVExtensionEnablingHeight int64 + // PrepareRequestExtensionEnablingHeight denotes the height starting from which + // an extended PrepareRequest format should be enabled. -1 means no extension is enabled. + PrepareRequestExtensionEnablingHeight int64 // GetKeyPair returns an index of the node in the list of validators // together with it's key pair. GetKeyPair func([]PublicKey) (int, PrivateKey, PublicKey) @@ -53,6 +56,8 @@ type Config[H Hash] struct { StopTxFlow func() // GetTx returns a transaction from memory pool. GetTx func(h H) Transaction[H] + // GetTxData returns arbitrary verified data associated with the transaction. + GetTxData func(h H) any // GetVerified returns a slice of verified transactions // to be proposed in a new block. GetVerified func() []Transaction[H] @@ -81,8 +86,12 @@ type Config[H Hash] struct { GetValidators func(...Transaction[H]) []PublicKey // NewConsensusPayload is a constructor for payload.ConsensusPayload. NewConsensusPayload func(*Context[H], MessageType, any) ConsensusPayload[H] - // NewPrepareRequest is a constructor for payload.PrepareRequest. + // NewPrepareRequest is a constructor for payload.PrepareRequest that + // builds a request carrying transaction hashes only. NewPrepareRequest func(ts uint64, nonce uint64, transactionHashes []H) PrepareRequest[H] + // NewPrepareRequestExtended is a constructor for payload.PrepareRequest that + // builds a request carrying full transaction list. + NewPrepareRequestExtended func(ts uint64, nonce uint64, transactions []Transaction[H]) PrepareRequest[H] // NewPrepareResponse is a constructor for payload.PrepareResponse. NewPrepareResponse func(preparationHash H) PrepareResponse[H] // NewChangeView is a constructor for payload.ChangeView. @@ -137,9 +146,10 @@ func defaultConfig[H Hash]() *Config[H] { VerifyPrepareResponse: func(ConsensusPayload[H]) error { return nil }, VerifyCommit: func(ConsensusPayload[H]) error { return nil }, - AntiMEVExtensionEnablingHeight: -1, - VerifyPreBlock: func(PreBlock[H]) bool { return true }, - VerifyPreCommit: func(ConsensusPayload[H]) error { return nil }, + AntiMEVExtensionEnablingHeight: -1, + PrepareRequestExtensionEnablingHeight: -1, + VerifyPreBlock: func(PreBlock[H]) bool { return true }, + VerifyPreCommit: func(ConsensusPayload[H]) error { return nil }, } } @@ -204,6 +214,15 @@ func checkConfig[H Hash](cfg *Config[H]) error { return errors.New("NewPreCommit is set, but AntiMEVExtensionEnablingHeight is not specified") } } + if cfg.PrepareRequestExtensionEnablingHeight >= 0 { + if cfg.NewPrepareRequestExtended == nil { + return errors.New("NewPrepareRequestExtended is nil") + } + } else { + if cfg.NewPrepareRequestExtended != nil { + return errors.New("NewPrepareRequestExtended is set, but PrepareRequestExtensionEnablingHeight is not specified") + } + } if (cfg.MaxTimePerBlock == nil) != (cfg.SubscribeForTxs == nil) { return errors.New("MaxTimePerBlock and SubscribeForTxs should be specified/not specified at the same time") } @@ -253,6 +272,13 @@ func WithAntiMEVExtensionEnablingHeight[H Hash](h int64) func(config *Config[H]) } } +// WithPrepareRequestExtensionEnablingHeight sets PrepareRequestExtensionEnablingHeight. +func WithPrepareRequestExtensionEnablingHeight[H Hash](h int64) func(config *Config[H]) { + return func(cfg *Config[H]) { + cfg.PrepareRequestExtensionEnablingHeight = h + } +} + // WithTimestampIncrement sets TimestampIncrement. func WithTimestampIncrement[H Hash](u uint64) func(config *Config[H]) { return func(cfg *Config[H]) { @@ -388,12 +414,19 @@ func WithNewConsensusPayload[H Hash](f func(ctx *Context[H], typ MessageType, ms } // WithNewPrepareRequest sets NewPrepareRequest. -func WithNewPrepareRequest[H Hash](f func(ts uint64, nonce uint64, transactionsHashes []H) PrepareRequest[H]) func(config *Config[H]) { +func WithNewPrepareRequest[H Hash](f func(ts uint64, nonce uint64, transactionHashes []H) PrepareRequest[H]) func(config *Config[H]) { return func(cfg *Config[H]) { cfg.NewPrepareRequest = f } } +// WithNewPrepareRequestExtended sets NewPrepareRequestExtended. +func WithNewPrepareRequestExtended[H Hash](f func(ts uint64, nonce uint64, transactions []Transaction[H]) PrepareRequest[H]) func(config *Config[H]) { + return func(cfg *Config[H]) { + cfg.NewPrepareRequestExtended = f + } +} + // WithNewPrepareResponse sets NewPrepareResponse. func WithNewPrepareResponse[H Hash](f func(preparationHash H) PrepareResponse[H]) func(config *Config[H]) { return func(cfg *Config[H]) { diff --git a/context.go b/context.go index 28918446f..6e80a2899 100644 --- a/context.go +++ b/context.go @@ -56,12 +56,24 @@ type Context[H Hash] struct { Timestamp uint64 Nonce uint64 // TransactionHashes is a slice of hashes of proposed transactions in the current block. + // It's used when PrepareRequestExtensionEnabled is false. TransactionHashes []H - // MissingTransactions is a slice of hashes containing missing transactions for the current block. + // MissingTransactions is a slice of hashes containing missing transactions (in case of + // disabled PrepareRequestExtension) or hashes of those transactions that require + // additional data to be fetched prior to the proposal verification (in case of enabled + // PrepareRequestExtension). MissingTransactions []H // Transactions is a map containing actual transactions for the current block. + // It's used when PrepareRequestExtensionEnabled is false. Transactions map[H]Transaction[H] + // TransactionList is a full ordered list of transactions proposed in the current block. + // It's used when PrepareRequestExtensionEnabled is true. + TransactionList []Transaction[H] + // PrepareRequestExtensionEnabled tells whether full transaction list is used for the + // PrepareRequest construction. Refreshed once per dBFT reset. + PrepareRequestExtensionEnabled bool + // PreparationPayloads stores consensus Prepare* payloads for the current epoch. PreparationPayloads []ConsensusPayload[H] // PreCommitPayloads stores consensus PreCommit payloads sent through all epochs @@ -287,6 +299,7 @@ func (c *Context[H]) reset(view byte, ts uint64) { } c.PreparationPayloads = emptyReusableSlice(c.PreparationPayloads, n) + c.TransactionList = nil if c.Transactions == nil { // Init. c.Transactions = make(map[H]Transaction[H]) } else { // Regular use. @@ -302,6 +315,7 @@ func (c *Context[H]) reset(view byte, ts uint64) { if c.MyIndex >= 0 { c.LastSeenMessage[c.MyIndex] = &HeightView{c.BlockIndex, c.ViewNumber} } + c.PrepareRequestExtensionEnabled = c.isPrepareRequestExtensionEnabled() } func emptyReusableSlice[E any](s []E, n int) []E { @@ -325,12 +339,15 @@ func (c *Context[H]) Fill(force bool) bool { _, _ = rand.Read(b) c.Nonce = binary.LittleEndian.Uint64(b) - c.TransactionHashes = make([]H, len(txx)) - - for i := range txx { - h := txx[i].Hash() - c.TransactionHashes[i] = h - c.Transactions[h] = txx[i] + if c.PrepareRequestExtensionEnabled { + c.TransactionList = txx + } else { + c.TransactionHashes = make([]H, len(txx)) + for i := range txx { + h := txx[i].Hash() + c.TransactionHashes[i] = h + c.Transactions[h] = txx[i] + } } c.Timestamp = c.lastBlockTimestamp + c.Config.TimestampIncrement @@ -353,18 +370,12 @@ func (c *Context[H]) CreateBlock() Block[H] { return nil } - txx := make([]Transaction[H], len(c.TransactionHashes)) - - for i, h := range c.TransactionHashes { - txx[i] = c.Transactions[h] - } - // Anti-MEV extension properly sets PreBlock transactions once during PreBlock // construction and then never updates these transactions in the dBFT context. // Thus, user must not reuse txx if anti-MEV extension is enabled. However, // we don't skip a call to Block.SetTransactions since it may be used as a // signal to the user's code to finalize the block. - c.block.SetTransactions(txx) + c.block.SetTransactions(c.collectTransactions()) } return c.block @@ -377,24 +388,39 @@ func (c *Context[H]) CreatePreBlock() PreBlock[H] { return nil } - txx := make([]Transaction[H], len(c.TransactionHashes)) - - for i, h := range c.TransactionHashes { - txx[i] = c.Transactions[h] - } - - c.preBlock.SetTransactions(txx) + c.preBlock.SetTransactions(c.collectTransactions()) } return c.preBlock } +// collectTransactions returns the ordered list of transactions to be proposed in +// the resulting block. If PrepareRequestExtension is enabled, they are taken +// directly from TransactionList; otherwise they are reconstructed, preserving +// TransactionHashes order, from the already-collected Transactions map. +func (c *Context[H]) collectTransactions() []Transaction[H] { + if c.PrepareRequestExtensionEnabled { + return c.TransactionList + } + txx := make([]Transaction[H], len(c.TransactionHashes)) + for i, h := range c.TransactionHashes { + txx[i] = c.Transactions[h] + } + return txx +} + // isAntiMEVExtensionEnabled returns whether Anti-MEV dBFT extension is enabled // at the currently processing block height. func (c *Context[H]) isAntiMEVExtensionEnabled() bool { return c.Config.AntiMEVExtensionEnablingHeight >= 0 && uint32(c.Config.AntiMEVExtensionEnablingHeight) <= c.BlockIndex } +// isPrepareRequestExtensionEnabled returns whether PrepareRequest dBFT extension is enabled +// at the currently processing block height. +func (c *Context[H]) isPrepareRequestExtensionEnabled() bool { + return c.Config.PrepareRequestExtensionEnablingHeight >= 0 && uint32(c.Config.PrepareRequestExtensionEnablingHeight) <= c.BlockIndex +} + // MakeHeader returns half-filled block for the current epoch. // All hashable fields will be filled. func (c *Context[H]) MakeHeader() Block[H] { @@ -432,7 +458,7 @@ func (c *Context[H]) MakePreHeader() PreBlock[H] { // hasAllTransactions returns true iff all transactions were received // for the proposed block. func (c *Context[H]) hasAllTransactions() bool { - return len(c.TransactionHashes) == len(c.Transactions) + return len(c.MissingTransactions) == 0 } func (c *Context[H]) subscribeForTransactions() { diff --git a/dbft.go b/dbft.go index c97c0166f..991eed338 100644 --- a/dbft.go +++ b/dbft.go @@ -50,8 +50,14 @@ func New[H Hash](options ...func(config *Config[H])) (*DBFT[H], error) { return d, nil } +// addTransaction adds a missing transaction to the context (in case of disabled +// PrepareRequestExtension) and advances the state machine if all transactions +// (or dependent data in case of enabled PrepareRequestExtension) are collected +// and it's possible to build a valid block. func (d *DBFT[H]) addTransaction(tx Transaction[H]) { - d.Transactions[tx.Hash()] = tx + if !d.PrepareRequestExtensionEnabled { + d.Transactions[tx.Hash()] = tx + } if d.hasAllTransactions() { if d.IsPrimary() || d.Context.WatchOnly() { return @@ -159,8 +165,9 @@ func (d *DBFT[H]) initializeConsensus(view byte, ts uint64) { d.changeTimer(timeout) } -// OnTransaction notifies service about receiving new transaction from the -// proposed list of transactions. +// OnTransaction notifies service about receiving new transaction (in case of +// disabled PrepareRequestExtension) or transaction data (in case of enabled +// PrepareRequestExtension) from the proposed list of transactions. func (d *DBFT[H]) OnTransaction(tx Transaction[H]) { // d.Logger.Debug("OnTransaction", // zap.Bool("backup", d.IsBackup()), @@ -178,13 +185,8 @@ func (d *DBFT[H]) OnTransaction(tx Transaction[H]) { if i < 0 { return } - d.addTransaction(tx) - // `addTransaction` checks for responses and commits. If this was the last transaction - // Context could be initialized on a new height, clearing this field. - if len(d.MissingTransactions) == 0 { - return - } d.MissingTransactions = slices.Delete(d.MissingTransactions, i, i+1) + d.addTransaction(tx) } // OnTimeout advances state machine as if timeout was fired. @@ -349,9 +351,16 @@ func (d *DBFT[H]) onPrepareRequest(msg ConsensusPayload[H]) { d.Timestamp = p.Timestamp() d.Nonce = p.Nonce() - d.TransactionHashes = p.TransactionHashes() + var txCount int + if d.PrepareRequestExtensionEnabled { + d.TransactionList = p.Transactions() + txCount = len(d.TransactionList) + } else { + d.TransactionHashes = p.TransactionHashes() + txCount = len(d.TransactionHashes) + } - d.Logger.Info("received PrepareRequest", zap.Uint16("validator", msg.ValidatorIndex()), zap.Int("tx", len(d.TransactionHashes))) + d.Logger.Info("received PrepareRequest", zap.Uint16("validator", msg.ValidatorIndex()), zap.Int("tx", txCount)) d.processMissingTx() d.updateExistingPayloads(msg) d.PreparationPayloads[msg.ValidatorIndex()] = msg @@ -365,6 +374,19 @@ func (d *DBFT[H]) onPrepareRequest(msg ConsensusPayload[H]) { } func (d *DBFT[H]) processMissingTx() { + if d.PrepareRequestExtensionEnabled { + for _, tx := range d.TransactionList { + if tx.HasData() { + continue + } + h := tx.Hash() + if data := d.GetTxData(tx.Hash()); data == nil { + d.MissingTransactions = append(d.MissingTransactions, h) + } + } + + return + } for _, h := range d.TransactionHashes { if _, ok := d.Transactions[h]; ok { continue diff --git a/dbft_test.go b/dbft_test.go index a151a48ef..14cf0dcbb 100644 --- a/dbft_test.go +++ b/dbft_test.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/binary" "fmt" + "slices" "testing" "time" @@ -577,7 +578,22 @@ func TestDBFT_Invalid(t *testing.T) { opts = append(opts, dbft.WithNewRecoveryMessage[crypto.Uint256](func() dbft.RecoveryMessage[crypto.Uint256] { return nil - }), dbft.WithMaxTimePerBlock[crypto.Uint256](func() time.Duration { + })) + t.Run("NewPrepareRequestExtended without PrepareRequestExtensionEnablingHeight", func(t *testing.T) { + invalidOpts := append(slices.Clone(opts), dbft.WithNewPrepareRequestExtended[crypto.Uint256](func(uint64, uint64, []dbft.Transaction[crypto.Uint256]) dbft.PrepareRequest[crypto.Uint256] { + return nil + })) + _, err := dbft.New(invalidOpts...) + require.ErrorContains(t, err, "NewPrepareRequestExtended is set, but PrepareRequestExtensionEnablingHeight is not specified") + }) + + t.Run("PrepareRequestExtensionEnablingHeight without NewPrepareRequestExtended", func(t *testing.T) { + invalidOpts := append(slices.Clone(opts), dbft.WithPrepareRequestExtensionEnablingHeight[crypto.Uint256](0)) + _, err := dbft.New(invalidOpts...) + require.ErrorContains(t, err, "NewPrepareRequestExtended is nil") + }) + + opts = append(opts, dbft.WithMaxTimePerBlock[crypto.Uint256](func() time.Duration { return 0 })) t.Run("MaxTimePerBlock without SubscribeForTxs", func(t *testing.T) { @@ -1023,6 +1039,52 @@ func TestDBFT_CachedMessages(t *testing.T) { } } +func TestDBFT_PrepareRequestExtensionEnabledSwitch(t *testing.T) { + s := newTestState(0, 1) + + opts := s.getOptions() + opts = append(opts, + dbft.WithPrepareRequestExtensionEnablingHeight[crypto.Uint256](2), + dbft.WithNewPrepareRequestExtended[crypto.Uint256](consensus.NewPrepareRequestExtended)) + + service, err := dbft.New[crypto.Uint256](opts...) + require.NoError(t, err) + + service.Start(0) + require.False(t, service.PrepareRequestExtensionEnabled) + + p := s.tryRecv() + require.NotNil(t, p) + require.Equal(t, dbft.PrepareRequestType, p.Type()) + require.EqualValues(t, s.currHeight+1, p.Height()) + + cm := s.tryRecv() + require.NotNil(t, cm) + require.Equal(t, dbft.CommitType, cm.Type()) + + b := s.nextBlock() + require.NotNil(t, b) + require.Equal(t, s.currHeight+1, b.Index()) + + s.currHeight++ + service.Reset(0) + require.True(t, service.PrepareRequestExtensionEnabled) + service.OnTimeout(s.currHeight+1, 0) + + p = s.tryRecv() + require.NotNil(t, p) + require.Equal(t, dbft.PrepareRequestType, p.Type()) + require.EqualValues(t, s.currHeight+1, p.Height()) + + cm = s.tryRecv() + require.NotNil(t, cm) + require.Equal(t, dbft.CommitType, cm.Type()) + + b = s.nextBlock() + require.NotNil(t, b) + require.Equal(t, s.currHeight+1, b.Index()) +} + func (s testState) getChangeView(from uint16, view byte) Payload { cv := consensus.NewChangeView(view, 0, 0) @@ -1197,24 +1259,47 @@ func (s *testState) getAMEVOptions() []func(*dbft.Config[crypto.Uint256]) { return opts } -func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { +func txHashesFromList(ctx *dbft.Context[crypto.Uint256]) []crypto.Uint256 { + txHashes := make([]crypto.Uint256, len(ctx.TransactionList)) + for i, tx := range ctx.TransactionList { + txHashes[i] = tx.Hash() + } + return txHashes +} + +func blockTxHashes(ctx *dbft.Context[crypto.Uint256]) ([]crypto.Uint256, bool) { + if ctx.PrepareRequestExtensionEnabled { + if ctx.TransactionList == nil { + return nil, false + } + return txHashesFromList(ctx), true + } if ctx.TransactionHashes == nil { + return nil, false + } + return ctx.TransactionHashes, true +} + +func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { + txHashes, ok := blockTxHashes(ctx) + if !ok { return nil } - block := consensus.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) + block := consensus.NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, txHashes) return block } func newPreBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.PreBlock[crypto.Uint256] { - if ctx.TransactionHashes == nil { + txHashes, ok := blockTxHashes(ctx) + if !ok { return nil } - pre := consensus.NewPreBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) + pre := consensus.NewPreBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, txHashes) return pre } func newAMEVBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { - if ctx.TransactionHashes == nil { + if _, ok := blockTxHashes(ctx); !ok { return nil } var data [][]byte diff --git a/internal/consensus/consensus.go b/internal/consensus/consensus.go index 459f6c92f..a4a2ae408 100644 --- a/internal/consensus/consensus.go +++ b/internal/consensus/consensus.go @@ -58,10 +58,22 @@ func New(logger *zap.Logger, key dbft.PrivateKey, pub dbft.PublicKey, } func newBlockFromContext(ctx *dbft.Context[crypto.Uint256]) dbft.Block[crypto.Uint256] { - if ctx.TransactionHashes == nil { - return nil + var txHashes []crypto.Uint256 + if ctx.PrepareRequestExtensionEnabled { + if ctx.TransactionList == nil { + return nil + } + txHashes = make([]crypto.Uint256, len(ctx.TransactionList)) + for i, tx := range ctx.TransactionList { + txHashes[i] = tx.Hash() + } + } else { + if ctx.TransactionHashes == nil { + return nil + } + txHashes = ctx.TransactionHashes } - block := NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, ctx.TransactionHashes) + block := NewBlock(ctx.Timestamp, ctx.BlockIndex, ctx.PrevHash, ctx.Nonce, txHashes) return block } diff --git a/internal/consensus/constructors.go b/internal/consensus/constructors.go index 096fa37d6..2d7544dad 100644 --- a/internal/consensus/constructors.go +++ b/internal/consensus/constructors.go @@ -20,15 +20,26 @@ func NewConsensusPayload(t dbft.MessageType, height uint32, validatorIndex uint1 } } -// NewPrepareRequest returns minimal prepareRequest implementation. -func NewPrepareRequest(ts uint64, nonce uint64, transactionsHashes []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256] { +// NewPrepareRequest returns minimal prepareRequest implementation carrying +// transaction hashes only. +func NewPrepareRequest(ts uint64, nonce uint64, transactionHashes []crypto.Uint256) dbft.PrepareRequest[crypto.Uint256] { return &prepareRequest{ - transactionHashes: transactionsHashes, + transactionHashes: transactionHashes, nonce: nonce, timestamp: nanoSecToSec(ts), } } +// NewPrepareRequestExtended returns minimal prepareRequest implementation +// carrying full transaction list. +func NewPrepareRequestExtended(ts uint64, nonce uint64, transactions []dbft.Transaction[crypto.Uint256]) dbft.PrepareRequest[crypto.Uint256] { + return &prepareRequest{ + transactions: transactions, + nonce: nonce, + timestamp: nanoSecToSec(ts), + } +} + // NewPrepareResponse returns minimal PrepareResponse implementation. func NewPrepareResponse(preparationHash crypto.Uint256) dbft.PrepareResponse[crypto.Uint256] { return &prepareResponse{ diff --git a/internal/consensus/message_test.go b/internal/consensus/message_test.go index ff149cd28..e4bacf897 100644 --- a/internal/consensus/message_test.go +++ b/internal/consensus/message_test.go @@ -18,6 +18,7 @@ func TestPayload_EncodeDecode(t *testing.T) { } t.Run("PrepareRequest", func(t *testing.T) { + tx1, tx2 := Tx64(1), Tx64(2) m := generateMessage(dbft.PrepareRequestType, &prepareRequest{ nonce: 123, timestamp: 345, @@ -25,6 +26,10 @@ func TestPayload_EncodeDecode(t *testing.T) { {1, 2, 3}, {5, 6, 7}, }, + transactions: []dbft.Transaction[crypto.Uint256]{ + &tx1, + &tx2, + }, }) testEncodeDecode(t, m, new(Payload)) @@ -60,6 +65,7 @@ func TestPayload_EncodeDecode(t *testing.T) { }) t.Run("RecoveryMessage", func(t *testing.T) { + tx1, tx2 := Tx64(1), Tx64(2) m := generateMessage(dbft.RecoveryMessageType, &recoveryMessage{ changeViewPayloads: []changeViewCompact{ { @@ -81,6 +87,10 @@ func TestPayload_EncodeDecode(t *testing.T) { {1, 2, 3}, {5, 6, 7}, }, + transactions: []dbft.Transaction[crypto.Uint256]{ + &tx1, + &tx2, + }, }, }) diff --git a/internal/consensus/prepare_request.go b/internal/consensus/prepare_request.go index 4955cb3e3..3acd56ad8 100644 --- a/internal/consensus/prepare_request.go +++ b/internal/consensus/prepare_request.go @@ -10,12 +10,14 @@ import ( type ( prepareRequest struct { transactionHashes []crypto.Uint256 + transactions []dbft.Transaction[crypto.Uint256] nonce uint64 timestamp uint32 } // prepareRequestAux is an auxiliary structure for prepareRequest encoding. prepareRequestAux struct { TransactionHashes []crypto.Uint256 + Transactions []dbft.Transaction[crypto.Uint256] Nonce uint64 Timestamp uint32 } @@ -27,6 +29,7 @@ var _ dbft.PrepareRequest[crypto.Uint256] = (*prepareRequest)(nil) func (p prepareRequest) EncodeBinary(w *gob.Encoder) error { return w.Encode(&prepareRequestAux{ TransactionHashes: p.transactionHashes, + Transactions: p.transactions, Nonce: p.nonce, Timestamp: p.timestamp, }) @@ -42,6 +45,7 @@ func (p *prepareRequest) DecodeBinary(r *gob.Decoder) error { p.timestamp = aux.Timestamp p.nonce = aux.Nonce p.transactionHashes = aux.TransactionHashes + p.transactions = aux.Transactions return nil } @@ -59,3 +63,8 @@ func (p prepareRequest) Nonce() uint64 { func (p prepareRequest) TransactionHashes() []crypto.Uint256 { return p.transactionHashes } + +// Transactions implements PrepareRequest interface. +func (p prepareRequest) Transactions() []dbft.Transaction[crypto.Uint256] { + return p.transactions +} diff --git a/internal/consensus/recovery_message.go b/internal/consensus/recovery_message.go index d6b43b184..5b90bea47 100644 --- a/internal/consensus/recovery_message.go +++ b/internal/consensus/recovery_message.go @@ -91,7 +91,8 @@ func (m *recoveryMessage) GetPrepareRequest(p dbft.ConsensusPayload[crypto.Uint2 // prepareRequest.Timestamp() here returns nanoseconds-precision value, so convert it to seconds again timestamp: nanoSecToSec(m.prepareRequest.Timestamp()), nonce: m.prepareRequest.Nonce(), - transactionHashes: m.prepareRequest.TransactionHashes(), + transactionHashes: m.prepareRequest.(*prepareRequest).transactionHashes, + transactions: m.prepareRequest.(*prepareRequest).transactions, }) req.SetValidatorIndex(ind) diff --git a/internal/consensus/transaction.go b/internal/consensus/transaction.go index cc4f3b90d..eeb2a331e 100644 --- a/internal/consensus/transaction.go +++ b/internal/consensus/transaction.go @@ -2,6 +2,7 @@ package consensus import ( "encoding/binary" + "encoding/gob" "errors" "github.com/nspcc-dev/dbft" @@ -16,6 +17,10 @@ type Tx64 uint64 var _ dbft.Transaction[crypto.Uint256] = (*Tx64)(nil) +func init() { + gob.Register(new(Tx64)) +} + func (t *Tx64) Hash() (h crypto.Uint256) { binary.LittleEndian.PutUint64(h[:], uint64(*t)) return diff --git a/prepare_request.go b/prepare_request.go index 3ba594c9c..34e40a74e 100644 --- a/prepare_request.go +++ b/prepare_request.go @@ -7,5 +7,9 @@ type PrepareRequest[H Hash] interface { // Nonce is a random nonce. Nonce() uint64 // TransactionHashes returns hashes of all transaction in a proposed block. + // It's used when PrepareRequestExtensionEnabled is false. TransactionHashes() []H + // Transactions returns full transaction list attached to this PrepareRequest. + // It's used when PrepareRequestExtensionEnabled is true. + Transactions() []Transaction[H] } diff --git a/send.go b/send.go index 6c40d952e..a6c0a2c2c 100644 --- a/send.go +++ b/send.go @@ -21,7 +21,12 @@ func (c *Context[H]) makePrepareRequest(force bool) ConsensusPayload[H] { return nil } - req := c.Config.NewPrepareRequest(c.Timestamp, c.Nonce, c.TransactionHashes) + var req PrepareRequest[H] + if c.PrepareRequestExtensionEnabled { + req = c.Config.NewPrepareRequestExtended(c.Timestamp, c.Nonce, c.TransactionList) + } else { + req = c.Config.NewPrepareRequest(c.Timestamp, c.Nonce, c.TransactionHashes) + } return c.Config.NewConsensusPayload(c, PrepareRequestType, req) } diff --git a/transaction.go b/transaction.go index ae3277c71..b434f8c42 100644 --- a/transaction.go +++ b/transaction.go @@ -5,4 +5,7 @@ type Transaction[H Hash] interface { // Hash must return cryptographic hash of the transaction. // Transactions which have equal hashes are considered equal. Hash() H + // HasData returns true if the transaction has the associated and valid data + // fetched. + HasData() bool }