-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(taiko-client): introduce
TxBuilderWithFallback
(#18690)
- Loading branch information
1 parent
c7c01a1
commit f1d7b20
Showing
8 changed files
with
295 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
packages/taiko-client/proposer/transaction_builder/fallback.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
81 changes: 81 additions & 0 deletions
81
packages/taiko-client/proposer/transaction_builder/fallback_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} |