Skip to content

Commit

Permalink
feat(taiko-client): introduce TxBuilderWithFallback (#18690)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidtaikocha authored Jan 2, 2025
1 parent c7c01a1 commit f1d7b20
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 30 deletions.
8 changes: 8 additions & 0 deletions packages/taiko-client/cmd/flags/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ var (
Value: false,
EnvVars: []string{"L1_BLOB_ALLOWED"},
}
FallbackToCalldata = &cli.BoolFlag{
Name: "l1.fallbackToCalldata",
Usage: "If set to true, proposer will use calldata as DA when blob fee is more expensive than using calldata",
Value: false,
Category: proposerCategory,
EnvVars: []string{"L1_FALLBACK_TO_CALLDATA"},
}
RevertProtectionEnabled = &cli.BoolFlag{
Name: "revertProtection",
Usage: "Enable revert protection with the support of endpoint and contract",
Expand Down Expand Up @@ -133,5 +140,6 @@ var ProposerFlags = MergeFlags(CommonFlags, []cli.Flag{
AllowZeroInterval,
MaxProposedTxListsPerEpoch,
BlobAllowed,
FallbackToCalldata,
RevertProtectionEnabled,
}, TxmgrFlags)
2 changes: 2 additions & 0 deletions packages/taiko-client/internal/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ var (
ProposerProposedTxListsCounter = factory.NewCounter(prometheus.CounterOpts{Name: "proposer_proposed_txLists"})
ProposerProposedTxsCounter = factory.NewCounter(prometheus.CounterOpts{Name: "proposer_proposed_txs"})
ProposerPoolContentFetchTime = factory.NewGauge(prometheus.GaugeOpts{Name: "proposer_pool_content_fetch_time"})
ProposerEstimatedCostCalldata = factory.NewGauge(prometheus.GaugeOpts{Name: "proposer_estimated_cost_calldata"})
ProposerEstimatedCostBlob = factory.NewGauge(prometheus.GaugeOpts{Name: "proposer_estimated_cost_blob"})

// Prover
ProverLatestVerifiedIDGauge = factory.NewGauge(prometheus.GaugeOpts{Name: "prover_latestVerified_id"})
Expand Down
2 changes: 1 addition & 1 deletion packages/taiko-client/pkg/rpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ func (c *Client) checkSyncedL1SnippetFromAnchor(
blockID *big.Int,
l1Height uint64,
) (bool, error) {
log.Info("Check synced L1 snippet from anchor", "blockID", blockID, "l1Height", l1Height)
log.Debug("Check synced L1 snippet from anchor", "blockID", blockID, "l1Height", l1Height)
block, err := c.L2.BlockByNumber(ctx, blockID)
if err != nil {
return false, err
Expand Down
2 changes: 2 additions & 0 deletions packages/taiko-client/proposer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Config struct {
MaxProposedTxListsPerEpoch uint64
ProposeBlockTxGasLimit uint64
BlobAllowed bool
FallbackToCalldata bool
RevertProtectionEnabled bool
TxmgrConfigs *txmgr.CLIConfig
PrivateTxmgrConfigs *txmgr.CLIConfig
Expand Down Expand Up @@ -104,6 +105,7 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
AllowZeroInterval: c.Uint64(flags.AllowZeroInterval.Name),
ProposeBlockTxGasLimit: c.Uint64(flags.TxGasLimit.Name),
BlobAllowed: c.Bool(flags.BlobAllowed.Name),
FallbackToCalldata: c.Bool(flags.FallbackToCalldata.Name),
RevertProtectionEnabled: c.Bool(flags.RevertProtectionEnabled.Name),
TxmgrConfigs: pkgFlags.InitTxmgrConfigsFromCli(
c.String(flags.L1WSEndpoint.Name),
Expand Down
44 changes: 15 additions & 29 deletions packages/taiko-client/proposer/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,35 +114,21 @@ func (p *Proposer) InitFromConfig(
}

p.txmgrSelector = utils.NewTxMgrSelector(txMgr, privateTxMgr, nil)

chainConfig := config.NewChainConfig(p.protocolConfigs)
p.chainConfig = chainConfig

if cfg.BlobAllowed {
p.txBuilder = builder.NewBlobTransactionBuilder(
p.rpc,
p.L1ProposerPrivKey,
cfg.TaikoL1Address,
cfg.ProverSetAddress,
cfg.L2SuggestedFeeRecipient,
cfg.ProposeBlockTxGasLimit,
cfg.ExtraData,
chainConfig,
cfg.RevertProtectionEnabled,
)
} else {
p.txBuilder = builder.NewCalldataTransactionBuilder(
p.rpc,
p.L1ProposerPrivKey,
cfg.L2SuggestedFeeRecipient,
cfg.TaikoL1Address,
cfg.ProverSetAddress,
cfg.ProposeBlockTxGasLimit,
cfg.ExtraData,
chainConfig,
cfg.RevertProtectionEnabled,
)
}
p.chainConfig = config.NewChainConfig(p.protocolConfigs)
p.txBuilder = builder.NewBuilderWithFallback(
p.rpc,
p.L1ProposerPrivKey,
cfg.L2SuggestedFeeRecipient,
cfg.TaikoL1Address,
cfg.ProverSetAddress,
cfg.ProposeBlockTxGasLimit,
cfg.ExtraData,
p.chainConfig,
p.txmgrSelector,
cfg.RevertProtectionEnabled,
cfg.BlobAllowed,
cfg.FallbackToCalldata,
)

return nil
}
Expand Down
1 change: 1 addition & 0 deletions packages/taiko-client/proposer/proposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func (s *ProposerTestSuite) SetupTest() {
MaxProposedTxListsPerEpoch: 1,
ExtraData: "test",
ProposeBlockTxGasLimit: 10_000_000,
FallbackToCalldata: true,
TxmgrConfigs: &txmgr.CLIConfig{
L1RPCURL: os.Getenv("L1_WS"),
NumConfirmations: 0,
Expand Down
185 changes: 185 additions & 0 deletions packages/taiko-client/proposer/transaction_builder/fallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package builder

import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"

"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/sync/errgroup"

"github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/metrics"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/config"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/utils"
)

// TxBuilderWithFallback builds type-2 or type-3 transactions based on the
// the realtime onchain cost, if the fallback feature is enabled.
type TxBuilderWithFallback struct {
rpc *rpc.Client
blobTransactionBuilder *BlobTransactionBuilder
calldataTransactionBuilder *CalldataTransactionBuilder
txmgrSelector *utils.TxMgrSelector
fallback bool
}

// NewBuilderWithFallback creates a new TxBuilderWithFallback instance.
func NewBuilderWithFallback(
rpc *rpc.Client,
proposerPrivateKey *ecdsa.PrivateKey,
l2SuggestedFeeRecipient common.Address,
taikoL1Address common.Address,
proverSetAddress common.Address,
gasLimit uint64,
extraData string,
chainConfig *config.ChainConfig,
txmgrSelector *utils.TxMgrSelector,
revertProtectionEnabled bool,
blobAllowed bool,
fallback bool,
) *TxBuilderWithFallback {
builder := &TxBuilderWithFallback{
rpc: rpc,
fallback: fallback,
txmgrSelector: txmgrSelector,
}

if blobAllowed {
builder.blobTransactionBuilder = NewBlobTransactionBuilder(
rpc,
proposerPrivateKey,
taikoL1Address,
proverSetAddress,
l2SuggestedFeeRecipient,
gasLimit,
extraData,
chainConfig,
revertProtectionEnabled,
)
}

builder.calldataTransactionBuilder = NewCalldataTransactionBuilder(
rpc,
proposerPrivateKey,
l2SuggestedFeeRecipient,
taikoL1Address,
proverSetAddress,
gasLimit,
extraData,
chainConfig,
revertProtectionEnabled,
)

return builder
}

// BuildOntake builds a type-2 or type-3 transaction based on the
// the realtime onchain cost, if the fallback feature is enabled.
func (b *TxBuilderWithFallback) BuildOntake(
ctx context.Context,
txListBytesArray [][]byte,
) (*txmgr.TxCandidate, error) {
// If calldata is the only option, just use it.
if b.blobTransactionBuilder == nil {
return b.calldataTransactionBuilder.BuildOntake(ctx, txListBytesArray)
}
// If blob is enabled, and fallback is not enabled, just build a blob transaction.
if !b.fallback {
return b.blobTransactionBuilder.BuildOntake(ctx, txListBytesArray)
}

// Otherwise, compare the cost, and choose the cheaper option.
var (
g = new(errgroup.Group)
txWithCalldata *txmgr.TxCandidate
txWithBlob *txmgr.TxCandidate
costCalldata *big.Int
costBlob *big.Int
err error
)

g.Go(func() error {
if txWithCalldata, err = b.calldataTransactionBuilder.BuildOntake(ctx, txListBytesArray); err != nil {
return err
}
if costCalldata, err = b.estimateCandidateCost(ctx, txWithCalldata); err != nil {
return err
}
return nil
})
g.Go(func() error {
if txWithBlob, err = b.blobTransactionBuilder.BuildOntake(ctx, txListBytesArray); err != nil {
return err
}
if costBlob, err = b.estimateCandidateCost(ctx, txWithBlob); err != nil {
return err
}
return nil
})

if err = g.Wait(); err != nil {
return nil, err
}

metrics.ProposerEstimatedCostCalldata.Set(float64(costCalldata.Uint64()))
metrics.ProposerEstimatedCostBlob.Set(float64(costBlob.Uint64()))

if costCalldata.Cmp(costBlob) < 0 {
log.Info("Building a type-2 transaction", "costCalldata", costCalldata, "costBlob", costBlob)
return txWithCalldata, nil
}

log.Info("Building a type-3 transaction", "costCalldata", costCalldata, "costBlob", costBlob)
return txWithBlob, nil
}

// estimateCandidateCost estimates the realtime onchain cost of the given transaction.
func (b *TxBuilderWithFallback) estimateCandidateCost(
ctx context.Context,
candidate *txmgr.TxCandidate,
) (*big.Int, error) {
txmgr, _ := b.txmgrSelector.Select()
gasTipCap, baseFee, blobBaseFee, err := txmgr.SuggestGasPriceCaps(ctx)
if err != nil {
return nil, err
}
log.Debug("Suggested gas price", "gasTipCap", gasTipCap, "baseFee", baseFee, "blobBaseFee", blobBaseFee)

gasPrice := new(big.Int).Add(baseFee, gasTipCap)
gasUsed, err := b.rpc.L1.EstimateGas(ctx, ethereum.CallMsg{
From: txmgr.From(),
To: candidate.To,
Gas: candidate.GasLimit,
GasPrice: gasPrice,
GasFeeCap: gasPrice,
GasTipCap: gasTipCap,
Value: candidate.Value,
Data: candidate.TxData,
})
if err != nil {
return nil, fmt.Errorf("failed to estimate gas used: %w", err)
}

feeWithoutBlob := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gasUsed))

// If its a type-2 transaction, we won't calculate blob fee.
if len(candidate.Blobs) == 0 {
return feeWithoutBlob, nil
}

// Otherwise, we add blob fee to the cost.
return new(big.Int).Add(
feeWithoutBlob,
new(big.Int).Mul(new(big.Int).SetUint64(uint64(len(candidate.Blobs))), blobBaseFee),
), nil
}

// TxBuilderWithFallback returns whether the blob transactions is enabled.
func (b *TxBuilderWithFallback) BlobAllow() bool {
return b.blobTransactionBuilder != nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package builder

import (
"context"
"os"
"time"

"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"

"github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/metrics"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/config"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc"
"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/utils"
)

func (s *TransactionBuilderTestSuite) TestBuildCalldataOnly() {
builder := s.newTestBuilderWithFallback(false, false)
candidate, err := builder.BuildOntake(context.Background(), [][]byte{{1}, {2}})
s.Nil(err)
s.Zero(len(candidate.Blobs))
}

func (s *TransactionBuilderTestSuite) TestBuildCalldataWithBlobAllowed() {
builder := s.newTestBuilderWithFallback(true, false)
candidate, err := builder.BuildOntake(context.Background(), [][]byte{{1}, {2}})
s.Nil(err)
s.NotZero(len(candidate.Blobs))
}

func (s *TransactionBuilderTestSuite) newTestBuilderWithFallback(blobAllowed, fallback bool) *TxBuilderWithFallback {
l1ProposerPrivKey, err := crypto.ToECDSA(common.FromHex(os.Getenv("L1_PROPOSER_PRIVATE_KEY")))
s.Nil(err)

protocolConfigs, err := rpc.GetProtocolConfigs(s.RPCClient.TaikoL1, nil)
s.Nil(err)

chainConfig := config.NewChainConfig(&protocolConfigs)

txMgr, err := txmgr.NewSimpleTxManager(
"tx_builder_test",
log.Root(),
&metrics.TxMgrMetrics,
txmgr.CLIConfig{
L1RPCURL: os.Getenv("L1_WS"),
NumConfirmations: 0,
SafeAbortNonceTooLowCount: txmgr.DefaultBatcherFlagValues.SafeAbortNonceTooLowCount,
PrivateKey: common.Bytes2Hex(crypto.FromECDSA(l1ProposerPrivKey)),
FeeLimitMultiplier: txmgr.DefaultBatcherFlagValues.FeeLimitMultiplier,
FeeLimitThresholdGwei: txmgr.DefaultBatcherFlagValues.FeeLimitThresholdGwei,
MinBaseFeeGwei: txmgr.DefaultBatcherFlagValues.MinBaseFeeGwei,
MinTipCapGwei: txmgr.DefaultBatcherFlagValues.MinTipCapGwei,
ResubmissionTimeout: txmgr.DefaultBatcherFlagValues.ResubmissionTimeout,
ReceiptQueryInterval: 1 * time.Second,
NetworkTimeout: txmgr.DefaultBatcherFlagValues.NetworkTimeout,
TxSendTimeout: txmgr.DefaultBatcherFlagValues.TxSendTimeout,
TxNotInMempoolTimeout: txmgr.DefaultBatcherFlagValues.TxNotInMempoolTimeout,
},
)

s.Nil(err)

txmgrSelector := utils.NewTxMgrSelector(txMgr, nil, nil)

return NewBuilderWithFallback(
s.RPCClient,
l1ProposerPrivKey,
common.HexToAddress(os.Getenv("TAIKO_L2")),
common.HexToAddress(os.Getenv("TAIKO_L1")),
common.Address{},
10_000_000,
"test_fallback_builder",
chainConfig,
txmgrSelector,
true,
blobAllowed,
fallback,
)
}

0 comments on commit f1d7b20

Please sign in to comment.