Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

Commit

Permalink
feat: add L2ContentFrom method
Browse files Browse the repository at this point in the history
  • Loading branch information
davidtaikocha committed Jul 19, 2023
1 parent 8bc63c4 commit c515584
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 85 deletions.
8 changes: 8 additions & 0 deletions cmd/flags/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ var (
}
ProposeBlockTxGasLimit = &cli.Uint64Flag{
Name: "proposeBlockTxGasLimit",
Usage: "Gas limit will be used for TaikoL1.proposeBlock transactions",
Category: proposerCategory,
}
ProposeBlockTxReplacementMultiplier = &cli.Uint64Flag{
Name: "proposeBlockTxReplacementMultiplier",
Value: 2,
Usage: "Gas tip multiplier when replacing a TaikoL1.proposeBlock transaction with same nonce",
Category: proposerCategory,
}
)
Expand All @@ -73,4 +80,5 @@ var ProposerFlags = MergeFlags(CommonFlags, []cli.Flag{
MinBlockGasLimit,
MaxProposedTxListsPerEpoch,
ProposeBlockTxGasLimit,
ProposeBlockTxReplacementMultiplier,
})
18 changes: 18 additions & 0 deletions pkg/rpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,24 @@ func (c *Client) GetPoolContent(
return result, err
}

type AccountPoolContent map[string]map[string]*types.Transaction

// L2ContentFrom fetches a given account's transactions list from L2 execution engine's transactions pool.
func (c *Client) L2ContentFrom(
ctx context.Context,
address common.Address,
) (AccountPoolContent, error) {
var result AccountPoolContent
err := c.L2RawRPC.CallContext(
ctx,
&result,
"txpool_contentFrom",
address,
)

return result, err
}

// L2AccountNonce fetches the nonce of the given L2 account at a specified height.
func (c *Client) L2AccountNonce(
ctx context.Context,
Expand Down
37 changes: 37 additions & 0 deletions pkg/rpc/methods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package rpc

import (
"context"
"os"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -63,3 +66,37 @@ func TestGetProtocolStateVariables(t *testing.T) {
_, err := client.GetProtocolStateVariables(nil)
require.Nil(t, err)
}

func TestL2ContentFrom(t *testing.T) {
client := newTestClient(t)
l2Head, err := client.L2.HeaderByNumber(context.Background(), nil)
require.Nil(t, err)

baseFee, err := client.TaikoL2.GetBasefee(nil, 0, 60000000, uint32(l2Head.GasUsed))
require.Nil(t, err)

testAddrPrivKey, err := crypto.ToECDSA(common.Hex2Bytes(os.Getenv("L1_PROPOSER_PRIVATE_KEY")))
require.Nil(t, err)

testAddr := crypto.PubkeyToAddress(testAddrPrivKey.PublicKey)

nonce, err := client.L2.PendingNonceAt(context.Background(), testAddr)
require.Nil(t, err)

tx := types.NewTransaction(
nonce,
testAddr,
common.Big1,
100000,
baseFee,
[]byte{},
)
signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(client.L2ChainID), testAddrPrivKey)
require.Nil(t, err)
require.Nil(t, client.L2.SendTransaction(context.Background(), signedTx))

content, err := client.L2ContentFrom(context.Background(), testAddr)
require.Nil(t, err)

require.NotZero(t, len(content["pending"]))
}
24 changes: 24 additions & 0 deletions pkg/rpc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/big"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -119,6 +120,29 @@ func NeedNewProof(
return false, nil
}

// GetPendingTxByNonce tries to retrieve a pending transaction with a given nonce in a node's mempool.
func GetPendingTxByNonce(
ctx context.Context,
cli *Client,
address common.Address,
nonce uint64,
) (*types.Transaction, error) {
content, err := cli.L2ContentFrom(ctx, address)
if err != nil {
return nil, err
}

for _, txMap := range content {
for txNonce, tx := range txMap {
if txNonce == strconv.Itoa(int(nonce)) {
return tx, nil
}
}
}

return nil, nil
}

// SetHead makes a `debug_setHead` RPC call to set the chain's head, should only be used
// for testing purpose.
func SetHead(ctx context.Context, rpc *rpc.Client, headNum *big.Int) error {
Expand Down
66 changes: 38 additions & 28 deletions proposer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ 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
CommitSlot uint64
LocalAddresses []common.Address
ProposeEmptyBlocksInterval *time.Duration
MinBlockGasLimit uint64
MaxProposedTxListsPerEpoch uint64
ProposeBlockTxGasLimit *uint64
BackOffRetryInterval time.Duration
L1Endpoint string
L2Endpoint string
TaikoL1Address common.Address
TaikoL2Address common.Address
L1ProposerPrivKey *ecdsa.PrivateKey
L2SuggestedFeeRecipient common.Address
ProposeInterval *time.Duration
CommitSlot uint64
LocalAddresses []common.Address
ProposeEmptyBlocksInterval *time.Duration
MinBlockGasLimit uint64
MaxProposedTxListsPerEpoch uint64
ProposeBlockTxGasLimit *uint64
BackOffRetryInterval time.Duration
ProposeBlockTxReplacementMultiplier uint64
}

// NewConfigFromCliContext initializes a Config instance from
Expand Down Expand Up @@ -80,20 +81,29 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
proposeBlockTxGasLimit = &gasLimit
}

proposeBlockTxReplacementMultiplier := c.Uint64(flags.ProposeBlockTxReplacementMultiplier.Name)
if proposeBlockTxReplacementMultiplier == 0 {
return nil, fmt.Errorf(
"invalid --proposeBlockTxReplacementMultiplier value: %d",
proposeBlockTxReplacementMultiplier,
)
}

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,
CommitSlot: c.Uint64(flags.CommitSlot.Name),
LocalAddresses: localAddresses,
ProposeEmptyBlocksInterval: proposeEmptyBlocksInterval,
MinBlockGasLimit: c.Uint64(flags.MinBlockGasLimit.Name),
MaxProposedTxListsPerEpoch: c.Uint64(flags.MaxProposedTxListsPerEpoch.Name),
ProposeBlockTxGasLimit: proposeBlockTxGasLimit,
BackOffRetryInterval: time.Duration(c.Uint64(flags.BackOffRetryInterval.Name)) * time.Second,
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,
CommitSlot: c.Uint64(flags.CommitSlot.Name),
LocalAddresses: localAddresses,
ProposeEmptyBlocksInterval: proposeEmptyBlocksInterval,
MinBlockGasLimit: c.Uint64(flags.MinBlockGasLimit.Name),
MaxProposedTxListsPerEpoch: c.Uint64(flags.MaxProposedTxListsPerEpoch.Name),
ProposeBlockTxGasLimit: proposeBlockTxGasLimit,
BackOffRetryInterval: time.Duration(c.Uint64(flags.BackOffRetryInterval.Name)) * time.Second,
ProposeBlockTxReplacementMultiplier: proposeBlockTxReplacementMultiplier,
}, nil
}
3 changes: 3 additions & 0 deletions proposer/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() {
&cli.StringFlag{Name: flags.ProposeInterval.Name},
&cli.Uint64Flag{Name: flags.CommitSlot.Name},
&cli.StringFlag{Name: flags.TxPoolLocals.Name},
&cli.Uint64Flag{Name: flags.ProposeBlockTxReplacementMultiplier.Name},
}
app.Action = func(ctx *cli.Context) error {
c, err := NewConfigFromCliContext(ctx)
Expand All @@ -50,6 +51,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() {
s.Equal(uint64(commitSlot), c.CommitSlot)
s.Equal(1, len(c.LocalAddresses))
s.Equal(goldenTouchAddress, c.LocalAddresses[0])
s.Equal(uint64(5), c.ProposeBlockTxReplacementMultiplier)
s.Nil(new(Proposer).InitFromCli(context.Background(), ctx))

return err
Expand All @@ -66,5 +68,6 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() {
"-" + flags.ProposeInterval.Name, proposeInterval,
"-" + flags.CommitSlot.Name, strconv.Itoa(commitSlot),
"-" + flags.TxPoolLocals.Name, goldenTouchAddress.Hex(),
"-" + flags.ProposeBlockTxReplacementMultiplier.Name, "5",
}))
}
111 changes: 62 additions & 49 deletions proposer/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sync"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -31,7 +32,6 @@ var (
errNoNewTxs = errors.New("no new transactions")
waitReceiptTimeout = 1 * time.Minute
maxSendProposeBlockTxRetry = 10
txReplacementTipMultiplier = 2
)

// Proposer keep proposing new transactions from L2 execution engine's tx pool at a fixed interval.
Expand All @@ -53,6 +53,7 @@ type Proposer struct {
minBlockGasLimit *uint64
maxProposedTxListsPerEpoch uint64
proposeBlockTxGasLimit *uint64
txReplacementTipMultiplier uint64

// Protocol configurations
protocolConfigs *bindings.TaikoDataConfig
Expand Down Expand Up @@ -87,6 +88,7 @@ func InitFromConfig(ctx context.Context, p *Proposer, cfg *Config) (err error) {
p.locals = cfg.LocalAddresses
p.commitSlot = cfg.CommitSlot
p.maxProposedTxListsPerEpoch = cfg.MaxProposedTxListsPerEpoch
p.txReplacementTipMultiplier = cfg.ProposeBlockTxReplacementMultiplier
p.ctx = ctx

// RPC clients
Expand Down Expand Up @@ -273,70 +275,81 @@ func (p *Proposer) ProposeOp(ctx context.Context) error {
return nil
}

// ProposeTxList proposes the given transactions list to TaikoL1 smart contract.
func (p *Proposer) ProposeTxList(
// sendProposeBlockTx tries to send a TaikoL1.proposeBlock transaction.
func (p *Proposer) sendProposeBlockTx(
ctx context.Context,
meta *encoding.TaikoL1BlockMetadataInput,
txListBytes []byte,
txNum uint,
nonce *uint64,
) error {
sendProposeBlockTx := func(isReplacement bool) (*types.Transaction, error) {
if p.minBlockGasLimit != nil && meta.GasLimit < uint32(*p.minBlockGasLimit) {
meta.GasLimit = uint32(*p.minBlockGasLimit)
}

// Propose the transactions list
inputs, err := encoding.EncodeProposeBlockInput(meta)
if err != nil {
return nil, err
}

opts, err := getTxOpts(ctx, p.rpc.L1, p.l1ProposerPrivKey, p.rpc.L1ChainID)
if err != nil {
return nil, err
}
if nonce != nil {
opts.Nonce = new(big.Int).SetUint64(*nonce)
}
if p.proposeBlockTxGasLimit != nil {
opts.GasLimit = *p.proposeBlockTxGasLimit
}

if isReplacement {
opts.GasTipCap = new(big.Int).Mul(opts.GasTipCap, new(big.Int).SetUint64(uint64(txReplacementTipMultiplier)))
}
isReplacement bool,
) (*types.Transaction, error) {
if p.minBlockGasLimit != nil && meta.GasLimit < uint32(*p.minBlockGasLimit) {
meta.GasLimit = uint32(*p.minBlockGasLimit)
}

proposeTx, err := p.rpc.TaikoL1.ProposeBlock(opts, inputs, txListBytes)
if err != nil {
return nil, encoding.TryParsingCustomError(err)
}
// Propose the transactions list
inputs, err := encoding.EncodeProposeBlockInput(meta)
if err != nil {
return nil, err
}
opts, err := getTxOpts(ctx, p.rpc.L1, p.l1ProposerPrivKey, p.rpc.L1ChainID)
if err != nil {
return nil, err
}
if nonce != nil {
opts.Nonce = new(big.Int).SetUint64(*nonce)
}
if p.proposeBlockTxGasLimit != nil {
opts.GasLimit = *p.proposeBlockTxGasLimit
}
if isReplacement {
opts.GasTipCap = new(big.Int).Mul(opts.GasTipCap, new(big.Int).SetUint64(uint64(p.txReplacementTipMultiplier)))
}

return proposeTx, nil
proposeTx, err := p.rpc.TaikoL1.ProposeBlock(opts, inputs, txListBytes)
if err != nil {
return nil, encoding.TryParsingCustomError(err)
}

return proposeTx, nil
}

// ProposeTxList proposes the given transactions list to TaikoL1 smart contract.
func (p *Proposer) ProposeTxList(
ctx context.Context,
meta *encoding.TaikoL1BlockMetadataInput,
txListBytes []byte,
txNum uint,
nonce *uint64,
) error {
var (
try = 0
tx *types.Transaction
isReplacement bool
tx *types.Transaction
err error
)

for try < maxSendProposeBlockTxRetry {
if tx, err = sendProposeBlockTx(isReplacement); err != nil {
try += 1
log.Warn("Failed to send propose block transaction, retrying", "error", err)
if strings.Contains(err.Error(), "replacement transaction underpriced") {
isReplacement = true
} else {
isReplacement = false
backoff.Retry(
func() error {
if ctx.Err() != nil {
return nil
}
if tx, err = p.sendProposeBlockTx(ctx, meta, txListBytes, nonce, isReplacement); err != nil {
log.Warn("Failed to send propose block transaction, retrying", "error", err)
if strings.Contains(err.Error(), "replacement transaction underpriced") {
isReplacement = true
} else {
isReplacement = false
}
return err
}
continue
}

break
return nil
},
backoff.WithMaxRetries(backoff.NewExponentialBackOff(), uint64(maxSendProposeBlockTxRetry)),
)
if ctx.Err() != nil {
return ctx.Err()
}

if err != nil {
return err
}
Expand Down
Loading

0 comments on commit c515584

Please sign in to comment.