diff --git a/circleciconfig.toml b/circleciconfig.toml new file mode 100644 index 0000000000..edc119fa42 --- /dev/null +++ b/circleciconfig.toml @@ -0,0 +1,5 @@ +[Eth] +Rip7560MaxBundleSize = 0 +Rip7560MaxBundleGas = 0 +Rip7560PullUrls = ["http://localhost:3001/rpc"] +Rip7560AcceptPush = false diff --git a/core/txpool/rip7560pool/rip7560pool.go b/core/txpool/rip7560pool/rip7560pool.go index 19bcad417d..b93b1d20ca 100644 --- a/core/txpool/rip7560pool/rip7560pool.go +++ b/core/txpool/rip7560pool/rip7560pool.go @@ -1,21 +1,30 @@ package rip7560pool import ( + "context" + "errors" + "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "math/big" + "net/http" "sync" "sync/atomic" + "time" ) type Config struct { - MaxBundleSize uint - MaxBundleGas uint + MaxBundleSize *uint64 + MaxBundleGas *uint64 + PullUrls []string } // Rip7560BundlerPool is the transaction pool dedicated to RIP-7560 AA transactions. @@ -189,7 +198,10 @@ func (pool *Rip7560BundlerPool) PendingRip7560Bundle() (*types.ExternallyReceive defer pool.mu.Unlock() bundle := pool.selectExternalBundle() - return bundle, nil + if bundle != nil { + return bundle, nil + } + return pool.fetchBundleFromBundler() } // SubscribeTransactions is not needed for the External Bundler AA sub pool and 'ch' will never be sent anything. @@ -261,6 +273,64 @@ func (pool *Rip7560BundlerPool) GetRip7560BundleStatus(hash common.Hash) (*types return pool.includedBundles[hash], nil } +type GetRip7560BundleArgs struct { + MinBaseFee uint64 + MaxBundleGas uint64 + MaxBundleSize uint64 +} + +type GetRip7560BundleResult struct { + Bundle []ethapi.TransactionArgs + ValidForBlock *hexutil.Big +} + +func (pool *Rip7560BundlerPool) fetchBundleFromBundler() (*types.ExternallyReceivedBundle, error) { + if len(pool.config.PullUrls) == 0 { + return nil, nil + } + currentHead := pool.currentHead.Load() + chosenBundle := make([]ethapi.TransactionArgs, 0) + pullErrors := make([]error, 0) + for _, url := range pool.config.PullUrls { + client := rpc.WithHTTPClient(&http.Client{Timeout: 500 * time.Millisecond}) + cl, err := rpc.DialOptions(context.Background(), url, client) + if err != nil { + log.Warn(fmt.Sprintf("Failed to dial RIP-7560 bundler URL (%s): %v", url, err)) + } + maxBundleGas := min(*pool.config.MaxBundleGas, currentHead.GasLimit) + args := &GetRip7560BundleArgs{ + MinBaseFee: currentHead.BaseFee.Uint64(), // todo: adjust to account for possible change! + MaxBundleGas: maxBundleGas, + MaxBundleSize: *pool.config.MaxBundleSize, + } + result := &GetRip7560BundleResult{ + Bundle: make([]ethapi.TransactionArgs, 0), + } + err = cl.Call(result, "aa_getRip7560Bundle", args) + if err != nil { + log.Warn(fmt.Sprintf("Failed to fetch RIP-7560 bundle from URL (%s): %v", url, err)) + pullErrors = append(pullErrors, err) + continue + } + chosenBundle = result.Bundle + break + } + if len(pullErrors) == len(pool.config.PullUrls) { + return nil, errors.New("failed to fetch a new RIP-7560 bundle from any bundler") + } + txs := make([]*types.Transaction, len(chosenBundle)) + for i, tx := range chosenBundle { + txs[i] = tx.ToTransaction() + } + bundleHash := ethapi.CalculateBundleHash(txs) + return &types.ExternallyReceivedBundle{ + BundlerId: "result.String", + BundleHash: bundleHash, + ValidForBlock: big.NewInt(0), + Transactions: txs, + }, nil +} + // return first bundle func (pool *Rip7560BundlerPool) selectExternalBundle() *types.ExternallyReceivedBundle { if len(pool.pendingBundles) == 0 { diff --git a/core/txpool/txpool_rip7560.go b/core/txpool/txpool_rip7560.go index 5e5ef8f8e7..2d3ab0c243 100644 --- a/core/txpool/txpool_rip7560.go +++ b/core/txpool/txpool_rip7560.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// SubmitBundle inserts the entire bundle of Type 4 transactions into the relevant pool. +// SubmitRip7560Bundle inserts the entire bundle of Type 4 transactions into the relevant pool. func (p *TxPool) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error { // todo: we cannot 'filter-out' the AA pool so just passing to all pools - only AA pool has code in SubmitBundle for _, subpool := range p.subpools { diff --git a/eth/api_backend.go b/eth/api_backend.go index 8a9898b956..3262661c55 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -44,6 +44,7 @@ import ( // EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes type EthAPIBackend struct { + rip7560AcceptPush bool extRPCEnabled bool allowUnprotectedTxs bool eth *Ethereum diff --git a/eth/api_backend_rip7560.go b/eth/api_backend_rip7560.go index 4a8ad2f36e..359e8e7166 100644 --- a/eth/api_backend_rip7560.go +++ b/eth/api_backend_rip7560.go @@ -2,11 +2,15 @@ package eth import ( "context" + "errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) func (b *EthAPIBackend) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error { + if !b.rip7560AcceptPush { + return errors.New("illegal call to eth_sendRip7560TransactionsBundle: Config.Eth.Rip7560AcceptPush is not set") + } return b.eth.txPool.SubmitRip7560Bundle(bundle) } diff --git a/eth/backend.go b/eth/backend.go index 3a1c4eb923..92b8ec9e3e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -238,8 +238,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { legacyPool := legacypool.New(config.TxPool, eth.blockchain) rip7560PoolConfig := rip7560pool.Config{ - MaxBundleGas: 10000000, - MaxBundleSize: 100, + MaxBundleGas: config.Rip7560MaxBundleGas, + MaxBundleSize: config.Rip7560MaxBundleSize, + PullUrls: config.Rip7560PullUrls, } rip7560 := rip7560pool.New(rip7560PoolConfig, eth.blockchain, config.Miner.Etherbase) @@ -266,7 +267,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.miner = miner.New(eth, config.Miner, eth.engine) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + eth.APIBackend = &EthAPIBackend{config.Rip7560AcceptPush, stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} if eth.APIBackend.allowUnprotectedTxs { log.Info("Unprotected transactions allowed") } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index f36f212d9c..30c1a15df5 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -163,6 +163,18 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` + + // Rip7560MaxBundleGas is the maximum amount of gas that can be used by an RIP-7560 bundle + Rip7560MaxBundleGas *uint64 `toml:",omitempty"` + + // Rip7560MaxBundleSize is the maximum number of transactions an RIP-7560 bundle can contain + Rip7560MaxBundleSize *uint64 `toml:",omitempty"` + + // Rip7560PullUrls provides a list of bundlers the node will ask for new bundles for each block + Rip7560PullUrls []string + + // Rip7560AcceptPush when set to "true" the node will accept incoming 'eth_sendRip7560TransactionsBundle' + Rip7560AcceptPush bool `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index b8b9eee294..e76c6f79de 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -58,6 +58,10 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCTxFeeCap float64 OverrideCancun *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` + Rip7560MaxBundleGas *uint64 `toml:",omitempty"` + Rip7560MaxBundleSize *uint64 `toml:",omitempty"` + Rip7560PullUrls []string + Rip7560AcceptPush bool `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -101,6 +105,10 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RPCTxFeeCap = c.RPCTxFeeCap enc.OverrideCancun = c.OverrideCancun enc.OverrideVerkle = c.OverrideVerkle + enc.Rip7560MaxBundleGas = c.Rip7560MaxBundleGas + enc.Rip7560MaxBundleSize = c.Rip7560MaxBundleSize + enc.Rip7560PullUrls = c.Rip7560PullUrls + enc.Rip7560AcceptPush = c.Rip7560AcceptPush return &enc, nil } @@ -148,6 +156,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCTxFeeCap *float64 OverrideCancun *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` + Rip7560MaxBundleGas *uint64 `toml:",omitempty"` + Rip7560MaxBundleSize *uint64 `toml:",omitempty"` + Rip7560PullUrls []string + Rip7560AcceptPush *bool `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -276,5 +288,17 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideVerkle != nil { c.OverrideVerkle = dec.OverrideVerkle } + if dec.Rip7560MaxBundleGas != nil { + c.Rip7560MaxBundleGas = dec.Rip7560MaxBundleGas + } + if dec.Rip7560MaxBundleSize != nil { + c.Rip7560MaxBundleSize = dec.Rip7560MaxBundleSize + } + if dec.Rip7560PullUrls != nil { + c.Rip7560PullUrls = dec.Rip7560PullUrls + } + if dec.Rip7560AcceptPush != nil { + c.Rip7560AcceptPush = *dec.Rip7560AcceptPush + } return nil } diff --git a/internal/ethapi/rip7560api.go b/internal/ethapi/rip7560api.go index 4e669cc19c..4043cfc00c 100644 --- a/internal/ethapi/rip7560api.go +++ b/internal/ethapi/rip7560api.go @@ -23,7 +23,7 @@ func (s *TransactionAPI) SendRip7560TransactionsBundle(ctx context.Context, args ValidForBlock: creationBlock, Transactions: txs, } - bundleHash := calculateBundleHash(txs) + bundleHash := CalculateBundleHash(txs) bundle.BundleHash = bundleHash err := SubmitRip7560Bundle(ctx, s.b, bundle) if err != nil { @@ -37,15 +37,19 @@ func (s *TransactionAPI) GetRip7560BundleStatus(ctx context.Context, hash common return bundleStats, err } +// CalculateBundleHash // TODO: If this code is indeed necessary, keep it in utils; better - remove altogether. -func calculateBundleHash(txs []*types.Transaction) common.Hash { +func CalculateBundleHash(txs []*types.Transaction) common.Hash { appendedTxIds := make([]byte, 0) for _, tx := range txs { txHash := tx.Hash() appendedTxIds = append(appendedTxIds, txHash[:]...) } - return rlpHash(appendedTxIds) + bundleHash := rlpHash(appendedTxIds) + println("calculateBundleHash") + println(bundleHash.String()) + return bundleHash } func rlpHash(x interface{}) (h common.Hash) {