AA-381: Create a "pull bundle" block building mode and a 'getRip7560Bundle' method (#18)

This commit is contained in:
Alex Forshtat 2024-08-05 17:21:37 +03:00 committed by GitHub
parent 2ee6caf9c5
commit 0d8f19200d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 131 additions and 10 deletions

5
circleciconfig.toml Normal file
View file

@ -0,0 +1,5 @@
[Eth]
Rip7560MaxBundleSize = 0
Rip7560MaxBundleGas = 0
Rip7560PullUrls = ["http://localhost:3001/rpc"]
Rip7560AcceptPush = false

View file

@ -1,21 +1,30 @@
package rip7560pool package rip7560pool
import ( import (
"context"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"math/big" "math/big"
"net/http"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
) )
type Config struct { type Config struct {
MaxBundleSize uint MaxBundleSize *uint64
MaxBundleGas uint MaxBundleGas *uint64
PullUrls []string
} }
// Rip7560BundlerPool is the transaction pool dedicated to RIP-7560 AA transactions. // 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() defer pool.mu.Unlock()
bundle := pool.selectExternalBundle() bundle := pool.selectExternalBundle()
if bundle != nil {
return 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. // 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 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 // return first bundle
func (pool *Rip7560BundlerPool) selectExternalBundle() *types.ExternallyReceivedBundle { func (pool *Rip7560BundlerPool) selectExternalBundle() *types.ExternallyReceivedBundle {
if len(pool.pendingBundles) == 0 { if len(pool.pendingBundles) == 0 {

View file

@ -5,7 +5,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "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 { 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 // 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 { for _, subpool := range p.subpools {

View file

@ -44,6 +44,7 @@ import (
// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes // EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes
type EthAPIBackend struct { type EthAPIBackend struct {
rip7560AcceptPush bool
extRPCEnabled bool extRPCEnabled bool
allowUnprotectedTxs bool allowUnprotectedTxs bool
eth *Ethereum eth *Ethereum

View file

@ -2,11 +2,15 @@ package eth
import ( import (
"context" "context"
"errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
func (b *EthAPIBackend) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error { 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) return b.eth.txPool.SubmitRip7560Bundle(bundle)
} }

View file

@ -238,8 +238,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
legacyPool := legacypool.New(config.TxPool, eth.blockchain) legacyPool := legacypool.New(config.TxPool, eth.blockchain)
rip7560PoolConfig := rip7560pool.Config{ rip7560PoolConfig := rip7560pool.Config{
MaxBundleGas: 10000000, MaxBundleGas: config.Rip7560MaxBundleGas,
MaxBundleSize: 100, MaxBundleSize: config.Rip7560MaxBundleSize,
PullUrls: config.Rip7560PullUrls,
} }
rip7560 := rip7560pool.New(rip7560PoolConfig, eth.blockchain, config.Miner.Etherbase) 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 = miner.New(eth, config.Miner, eth.engine)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) 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 { if eth.APIBackend.allowUnprotectedTxs {
log.Info("Unprotected transactions allowed") log.Info("Unprotected transactions allowed")
} }

View file

@ -163,6 +163,18 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork) // OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"` 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. // CreateConsensusEngine creates a consensus engine for the given chain config.

View file

@ -58,6 +58,10 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCTxFeeCap float64 RPCTxFeeCap float64
OverrideCancun *uint64 `toml:",omitempty"` OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
Rip7560MaxBundleGas *uint64 `toml:",omitempty"`
Rip7560MaxBundleSize *uint64 `toml:",omitempty"`
Rip7560PullUrls []string
Rip7560AcceptPush bool `toml:",omitempty"`
} }
var enc Config var enc Config
enc.Genesis = c.Genesis enc.Genesis = c.Genesis
@ -101,6 +105,10 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.RPCTxFeeCap = c.RPCTxFeeCap enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.OverrideCancun = c.OverrideCancun enc.OverrideCancun = c.OverrideCancun
enc.OverrideVerkle = c.OverrideVerkle enc.OverrideVerkle = c.OverrideVerkle
enc.Rip7560MaxBundleGas = c.Rip7560MaxBundleGas
enc.Rip7560MaxBundleSize = c.Rip7560MaxBundleSize
enc.Rip7560PullUrls = c.Rip7560PullUrls
enc.Rip7560AcceptPush = c.Rip7560AcceptPush
return &enc, nil return &enc, nil
} }
@ -148,6 +156,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCTxFeeCap *float64 RPCTxFeeCap *float64
OverrideCancun *uint64 `toml:",omitempty"` OverrideCancun *uint64 `toml:",omitempty"`
OverrideVerkle *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"`
Rip7560MaxBundleGas *uint64 `toml:",omitempty"`
Rip7560MaxBundleSize *uint64 `toml:",omitempty"`
Rip7560PullUrls []string
Rip7560AcceptPush *bool `toml:",omitempty"`
} }
var dec Config var dec Config
if err := unmarshal(&dec); err != nil { if err := unmarshal(&dec); err != nil {
@ -276,5 +288,17 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.OverrideVerkle != nil { if dec.OverrideVerkle != nil {
c.OverrideVerkle = dec.OverrideVerkle 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 return nil
} }

View file

@ -23,7 +23,7 @@ func (s *TransactionAPI) SendRip7560TransactionsBundle(ctx context.Context, args
ValidForBlock: creationBlock, ValidForBlock: creationBlock,
Transactions: txs, Transactions: txs,
} }
bundleHash := calculateBundleHash(txs) bundleHash := CalculateBundleHash(txs)
bundle.BundleHash = bundleHash bundle.BundleHash = bundleHash
err := SubmitRip7560Bundle(ctx, s.b, bundle) err := SubmitRip7560Bundle(ctx, s.b, bundle)
if err != nil { if err != nil {
@ -37,15 +37,19 @@ func (s *TransactionAPI) GetRip7560BundleStatus(ctx context.Context, hash common
return bundleStats, err return bundleStats, err
} }
// CalculateBundleHash
// TODO: If this code is indeed necessary, keep it in utils; better - remove altogether. // 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) appendedTxIds := make([]byte, 0)
for _, tx := range txs { for _, tx := range txs {
txHash := tx.Hash() txHash := tx.Hash()
appendedTxIds = append(appendedTxIds, txHash[:]...) appendedTxIds = append(appendedTxIds, txHash[:]...)
} }
return rlpHash(appendedTxIds) bundleHash := rlpHash(appendedTxIds)
println("calculateBundleHash")
println(bundleHash.String())
return bundleHash
} }
func rlpHash(x interface{}) (h common.Hash) { func rlpHash(x interface{}) (h common.Hash) {