From 6621a5c89a92e7593f702e4c82e69d1215b2ca59 Mon Sep 17 00:00:00 2001 From: David <david@taiko.xyz> Date: Tue, 7 Mar 2023 20:00:27 +0800 Subject: [PATCH] feat(proposer): new flag to propose empty blocks (#175) --- cmd/flags/proposer.go | 6 +++++ proposer/config.go | 50 ++++++++++++++++++++-------------- proposer/proposer.go | 56 ++++++++++++++++++++++++++++++++++----- proposer/proposer_test.go | 6 ++++- testutils/helper.go | 7 ++--- testutils/interfaces.go | 1 + version/version.go | 2 +- 7 files changed, 95 insertions(+), 33 deletions(-) diff --git a/cmd/flags/proposer.go b/cmd/flags/proposer.go index e22beec65..fad1412c9 100644 --- a/cmd/flags/proposer.go +++ b/cmd/flags/proposer.go @@ -47,6 +47,11 @@ var ( Value: "Comma separated accounts to treat as locals (priority inclusion)", Category: proposerCategory, } + ProposeEmptyBlocksInterval = &cli.StringFlag{ + Name: "proposeEmptyBlockInterval", + Usage: "Time interval to propose empty blocks", + Category: proposerCategory, + } ) // All proposer flags. @@ -58,4 +63,5 @@ var ProposerFlags = MergeFlags(CommonFlags, []cli.Flag{ ShufflePoolContent, CommitSlot, TxPoolLocals, + ProposeEmptyBlocksInterval, }) diff --git a/proposer/config.go b/proposer/config.go index ef6db2ab6..d98858743 100644 --- a/proposer/config.go +++ b/proposer/config.go @@ -14,16 +14,17 @@ import ( // Config contains all configurations to initialize a Taiko proposer. type Config struct { - L1Endpoint string - L2Endpoint string - TaikoL1Address common.Address - TaikoL2Address common.Address - L1ProposerPrivKey *ecdsa.PrivateKey - L2SuggestedFeeRecipient common.Address - ProposeInterval *time.Duration - ShufflePoolContent bool - CommitSlot uint64 - LocalAddresses []common.Address + L1Endpoint string + L2Endpoint string + TaikoL1Address common.Address + TaikoL2Address common.Address + L1ProposerPrivKey *ecdsa.PrivateKey + L2SuggestedFeeRecipient common.Address + ProposeInterval *time.Duration + ShufflePoolContent bool + CommitSlot uint64 + LocalAddresses []common.Address + ProposeEmptyBlocksInterval *time.Duration } // NewConfigFromCliContext initializes a Config instance from @@ -45,6 +46,14 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { } proposingInterval = &interval } + var proposeEmptyBlocksInterval *time.Duration + if c.IsSet(flags.ProposeEmptyBlocksInterval.Name) { + interval, err := time.ParseDuration(c.String(flags.ProposeEmptyBlocksInterval.Name)) + if err != nil { + return nil, fmt.Errorf("invalid proposing empty blocks interval: %w", err) + } + proposeEmptyBlocksInterval = &interval + } l2SuggestedFeeRecipient := c.String(flags.L2SuggestedFeeRecipient.Name) if !common.IsHexAddress(l2SuggestedFeeRecipient) { @@ -63,15 +72,16 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { } return &Config{ - L1Endpoint: c.String(flags.L1WSEndpoint.Name), - L2Endpoint: c.String(flags.L2HTTPEndpoint.Name), - TaikoL1Address: common.HexToAddress(c.String(flags.TaikoL1Address.Name)), - TaikoL2Address: common.HexToAddress(c.String(flags.TaikoL2Address.Name)), - L1ProposerPrivKey: l1ProposerPrivKey, - L2SuggestedFeeRecipient: common.HexToAddress(l2SuggestedFeeRecipient), - ProposeInterval: proposingInterval, - ShufflePoolContent: c.Bool(flags.ShufflePoolContent.Name), - CommitSlot: c.Uint64(flags.CommitSlot.Name), - LocalAddresses: localAddresses, + L1Endpoint: c.String(flags.L1WSEndpoint.Name), + L2Endpoint: c.String(flags.L2HTTPEndpoint.Name), + TaikoL1Address: common.HexToAddress(c.String(flags.TaikoL1Address.Name)), + TaikoL2Address: common.HexToAddress(c.String(flags.TaikoL2Address.Name)), + L1ProposerPrivKey: l1ProposerPrivKey, + L2SuggestedFeeRecipient: common.HexToAddress(l2SuggestedFeeRecipient), + ProposeInterval: proposingInterval, + ShufflePoolContent: c.Bool(flags.ShufflePoolContent.Name), + CommitSlot: c.Uint64(flags.CommitSlot.Name), + LocalAddresses: localAddresses, + ProposeEmptyBlocksInterval: proposeEmptyBlocksInterval, }, nil } diff --git a/proposer/proposer.go b/proposer/proposer.go index 8406c3c17..52094e049 100644 --- a/proposer/proposer.go +++ b/proposer/proposer.go @@ -3,6 +3,7 @@ package proposer import ( "context" "crypto/ecdsa" + "errors" "fmt" "math/big" "math/rand" @@ -23,6 +24,10 @@ import ( "github.com/urfave/cli/v2" ) +var ( + errNoNewTxs = errors.New("no new transactions") +) + // Proposer keep proposing new transactions from L2 execution engine's tx pool at a fixed interval. type Proposer struct { // RPC clients @@ -33,10 +38,11 @@ type Proposer struct { l2SuggestedFeeRecipient common.Address // Proposing configurations - proposingInterval *time.Duration - proposingTimer *time.Timer - commitSlot uint64 - locals []common.Address + proposingInterval *time.Duration + proposeEmptyBlocksInterval *time.Duration + proposingTimer *time.Timer + commitSlot uint64 + locals []common.Address // Protocol configurations protocolConfigs *bindings.TaikoDataConfig @@ -64,6 +70,7 @@ func InitFromConfig(ctx context.Context, p *Proposer, cfg *Config) (err error) { p.l1ProposerPrivKey = cfg.L1ProposerPrivKey p.l2SuggestedFeeRecipient = cfg.L2SuggestedFeeRecipient p.proposingInterval = cfg.ProposeInterval + p.proposeEmptyBlocksInterval = cfg.ProposeEmptyBlocksInterval p.wg = sync.WaitGroup{} p.locals = cfg.LocalAddresses p.commitSlot = cfg.CommitSlot @@ -105,6 +112,7 @@ func (p *Proposer) eventLoop() { p.wg.Done() }() + var lastNonEmptyBlockProposedAt = time.Now() for { p.updateProposingTicker() @@ -115,9 +123,27 @@ func (p *Proposer) eventLoop() { metrics.ProposerProposeEpochCounter.Inc(1) if err := p.ProposeOp(p.ctx); err != nil { - log.Error("Proposing operation error", "error", err) + if !errors.Is(err, errNoNewTxs) { + log.Error("Proposing operation error", "error", err) + continue + } + + if p.proposeEmptyBlocksInterval != nil { + if time.Now().Before(lastNonEmptyBlockProposedAt.Add(*p.proposeEmptyBlocksInterval)) { + continue + } + + if err := p.ProposeEmptyBlockOp(p.ctx); err != nil { + log.Error("Proposing an empty block operation error", "error", err) + } + + lastNonEmptyBlockProposedAt = time.Now() + } + continue } + + lastNonEmptyBlockProposedAt = time.Now() } } } @@ -154,7 +180,11 @@ func (p *Proposer) ProposeOp(ctx context.Context) error { return fmt.Errorf("failed to fetch transaction pool content: %w", err) } - log.Info("Transactions count", "count", len(txLists)) + log.Info("Transactions lists count", "count", len(txLists)) + + if len(txLists) == 0 { + return errNoNewTxs + } var commitTxListResQueue []*commitTxListRes for i, txs := range txLists { @@ -300,6 +330,20 @@ func (p *Proposer) ProposeTxList( return nil } +// ProposeEmptyBlockOp performs a proposing one empty block operation. +func (p *Proposer) ProposeEmptyBlockOp(ctx context.Context) error { + meta, commitTx, err := p.CommitTxList(ctx, []byte{}, 21000, 0) + if err != nil { + return fmt.Errorf("failed to commit an empty block: %w", err) + } + + if err := p.ProposeTxList(ctx, meta, commitTx, []byte{}, 0); err != nil { + return fmt.Errorf("failed to propose an empty block: %w", err) + } + + return nil +} + // updateProposingTicker updates the internal proposing timer. func (p *Proposer) updateProposingTicker() { if p.proposingTimer != nil { diff --git a/proposer/proposer_test.go b/proposer/proposer_test.go index 098a1d12a..3fe177011 100644 --- a/proposer/proposer_test.go +++ b/proposer/proposer_test.go @@ -62,7 +62,7 @@ func (s *ProposerTestSuite) TestName() { func (s *ProposerTestSuite) TestProposeOp() { // Nothing to propose - s.Nil(s.p.ProposeOp(context.Background())) + s.EqualError(errNoNewTxs, s.p.ProposeOp(context.Background()).Error()) // Propose txs in L2 execution engine's mempool sink := make(chan *bindings.TaikoL1ClientBlockProposed) @@ -103,6 +103,10 @@ func (s *ProposerTestSuite) TestProposeOp() { s.Equal(types.ReceiptStatusSuccessful, receipt.Status) } +func (s *ProposerTestSuite) TestProposeEmptyBlockOp() { + s.Nil(s.p.ProposeEmptyBlockOp(context.Background())) +} + func (s *ProposerTestSuite) TestCommitTxList() { txListBytes := testutils.RandomBytes(1024) gasLimit := uint64(102400) diff --git a/testutils/helper.go b/testutils/helper.go index 1056f4d01..bc151a51a 100644 --- a/testutils/helper.go +++ b/testutils/helper.go @@ -49,17 +49,14 @@ func ProposeAndInsertEmptyBlocks( }() // Zero byte txList - meta, commitTx, err := proposer.CommitTxList(context.Background(), []byte{}, 1024, 0) - s.Nil(err) - - s.Nil(proposer.ProposeTxList(context.Background(), meta, commitTx, []byte{}, 0)) + s.Nil(proposer.ProposeEmptyBlockOp(context.Background())) // RLP encoded empty list var emptyTxs []types.Transaction encoded, err := rlp.EncodeToBytes(emptyTxs) s.Nil(err) - meta, commitTx, err = proposer.CommitTxList(context.Background(), encoded, 1024, 0) + meta, commitTx, err := proposer.CommitTxList(context.Background(), encoded, 1024, 0) s.Nil(err) s.Nil(proposer.ProposeTxList(context.Background(), meta, commitTx, encoded, 0)) diff --git a/testutils/interfaces.go b/testutils/interfaces.go index 77455f70c..140f05d43 100644 --- a/testutils/interfaces.go +++ b/testutils/interfaces.go @@ -15,6 +15,7 @@ type CalldataSyncer interface { type Proposer interface { utils.SubcommandApplication ProposeOp(ctx context.Context) error + ProposeEmptyBlockOp(ctx context.Context) error CommitTxList(ctx context.Context, txListBytes []byte, gasLimit uint64, splittedIdx int) ( *bindings.TaikoDataBlockMetadata, *types.Transaction, diff --git a/version/version.go b/version/version.go index 2cccb6e9d..5759df03d 100644 --- a/version/version.go +++ b/version/version.go @@ -2,7 +2,7 @@ package version // Version info. var ( - Version = "0.4.0" + Version = "0.5.0" Meta = "dev" )