Skip to content

Commit

Permalink
WIP: manually bring over RIP-7560 related code; compiles but does not…
Browse files Browse the repository at this point in the history
… run
  • Loading branch information
forshtat committed Apr 29, 2024
1 parent 87246f3 commit e466097
Show file tree
Hide file tree
Showing 15 changed files with 1,358 additions and 1 deletion.
653 changes: 653 additions & 0 deletions core/state_processor_rip7560.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type Message struct {
// account nonce in state. It also disables checking that the sender is an EOA.
// This field will be set to true for operations like RPC eth_call.
SkipAccountChecks bool
IsRip7560Frame bool
}

// TransactionToMessage converts a transaction into a Message.
Expand Down Expand Up @@ -213,6 +214,7 @@ type StateTransition struct {
initialGas uint64
state vm.StateDB
evm *vm.EVM
rip7560Frame bool
}

// NewStateTransition initialises and returns a new state transition object.
Expand Down
15 changes: 15 additions & 0 deletions core/txpool/blobpool/blobpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1653,3 +1653,18 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
}
return txpool.TxStatusUnknown
}

func (pool *BlobPool) SubmitRip7560Bundle(_ *types.ExternallyReceivedBundle) error {
// nothing to do here
return nil
}

func (pool *BlobPool) GetRip7560BundleStatus(_ common.Hash) (*types.BundleReceipt, error) {
// nothing to do here
return nil, nil
}

func (pool *BlobPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// nothing to do here
return nil, nil
}
17 changes: 17 additions & 0 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ type BlockChain interface {

// StateAt returns a state database for a given root hash (generally the head).
StateAt(root common.Hash) (*state.StateDB, error)

GetReceiptsByHash(hash common.Hash) types.Receipts
}

// Config are the configuration parameters of the transaction pool.
Expand Down Expand Up @@ -1959,3 +1961,18 @@ func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
}

func (pool *LegacyPool) SubmitRip7560Bundle(_ *types.ExternallyReceivedBundle) error {
// nothing to do here
return nil
}

func (pool *LegacyPool) GetRip7560BundleStatus(_ common.Hash) (*types.BundleReceipt, error) {
// nothing to do here
return nil, nil
}

func (pool *LegacyPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// nothing to do here
return nil, nil
}
273 changes: 273 additions & 0 deletions core/txpool/rip7560pool/rip7560pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package rip7560pool

import (
"github.com/ethereum/go-ethereum/common"
"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/log"
"math/big"
"sync"
"sync/atomic"
)

type Config struct {
MaxBundleSize uint
MaxBundleGas uint
}

// Rip7560BundlerPool is the transaction pool dedicated to RIP-7560 AA transactions.
// This implementation relies on an external bundler process to perform most of the hard work.
type Rip7560BundlerPool struct {
config Config
chain legacypool.BlockChain
txFeed event.Feed
currentHead atomic.Pointer[types.Header] // Current head of the blockchain

pendingBundles []*types.ExternallyReceivedBundle
includedBundles map[common.Hash]*types.BundleReceipt

mu sync.Mutex

coinbase common.Address
}

func (pool *Rip7560BundlerPool) Init(_ uint64, head *types.Header, _ txpool.AddressReserver) error {
pool.pendingBundles = make([]*types.ExternallyReceivedBundle, 0)
pool.includedBundles = make(map[common.Hash]*types.BundleReceipt)
pool.currentHead.Store(head)
return nil
}

func (pool *Rip7560BundlerPool) Close() error {
return nil
}

func (pool *Rip7560BundlerPool) Reset(oldHead, newHead *types.Header) {
pool.mu.Lock()
defer pool.mu.Unlock()

newIncludedBundles := pool.gatherIncludedBundlesStats(newHead)
for _, included := range newIncludedBundles {
pool.includedBundles[included.BundleHash] = included
}

pendingBundles := make([]*types.ExternallyReceivedBundle, 0, len(pool.pendingBundles))
for _, bundle := range pool.pendingBundles {
nextBlock := big.NewInt(0).Add(newHead.Number, big.NewInt(1))
if bundle.ValidForBlock.Cmp(nextBlock) == 0 {
pendingBundles = append(pendingBundles, bundle)
}
}
pool.pendingBundles = pendingBundles
pool.currentHead.Store(newHead)
}

// For simplicity, this function assumes 'Reset' called for each new block sequentially.
func (pool *Rip7560BundlerPool) gatherIncludedBundlesStats(newHead *types.Header) map[common.Hash]*types.BundleReceipt {
// 1. Is there a bundle included in the block?

// note that in 'clique' mode Coinbase is always set to 0x000...000
if newHead.Coinbase.Cmp(pool.coinbase) != 0 && newHead.Coinbase.Cmp(common.Address{}) != 0 {
// not our block
return nil
}

// get all transaction hashes in block
add := pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
block := add.Transactions()

receipts := pool.chain.GetReceiptsByHash(add.Hash())
// match transactions in block to bundle ?

includedBundles := make(map[common.Hash]*types.BundleReceipt)

// 'pendingBundles' length is expected to be single digits, probably a single bundle in most cases
for _, bundle := range pool.pendingBundles {
if len(block) < len(bundle.Transactions) {
// this bundle does not even fit this block
continue
}
for i := 0; i < len(block); i++ {
transactions := make(types.Transactions, 0)
for j := 0; j < len(bundle.Transactions); j++ {
blockTx := block[i]
bundleTx := bundle.Transactions[j]
if bundleTx.Hash().Cmp(blockTx.Hash()) == 0 {
// tx hash has matched
transactions = append(transactions, blockTx)
if j == len(bundle.Transactions)-1 {
// FOUND BUNDLE IN BLOCK
receipt := createBundleReceipt(add, bundle.BundleHash, transactions, receipts)
includedBundles[bundle.BundleHash] = receipt
} else {
// let's see if next tx in bundle matches
i++
}
}
}
}

}
return includedBundles
}

func createBundleReceipt(block *types.Block, BundleHash common.Hash, transactions types.Transactions, blockReceipts types.Receipts) *types.BundleReceipt {
receipts := make(types.Receipts, 0)

OuterLoop:
for _, transaction := range transactions {
for _, receipt := range blockReceipts {
if receipt.TxHash == transaction.Hash() {
receipts = append(receipts, receipt)
continue OuterLoop
}
}
panic("receipt not found for transaction")
}

var gasUsed uint64 = 0
var gasPaidPriority = big.NewInt(0)

for _, receipt := range receipts {
gasUsed += receipt.GasUsed
priorityFeePerGas := big.NewInt(0).Sub(receipt.EffectiveGasPrice, block.BaseFee())
priorityFeePaid := big.NewInt(0).Mul(big.NewInt(int64(gasUsed)), priorityFeePerGas)
gasPaidPriority = big.NewInt(0).Add(gasPaidPriority, priorityFeePaid)
}

return &types.BundleReceipt{
BundleHash: BundleHash,
Count: uint64(len(transactions)),
Status: 0,
BlockNumber: block.NumberU64(),
BlockHash: block.Hash(),
TransactionReceipts: receipts,
GasUsed: gasUsed,
GasPaidPriority: gasPaidPriority,
BlockTimestamp: block.Time(),
}
}

// SetGasTip is ignored by the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) SetGasTip(_ *big.Int) {}

func (pool *Rip7560BundlerPool) Has(hash common.Hash) bool {
pool.mu.Lock()
defer pool.mu.Unlock()

tx := pool.Get(hash)
return tx != nil
}

func (pool *Rip7560BundlerPool) Get(hash common.Hash) *types.Transaction {
pool.mu.Lock()
defer pool.mu.Unlock()

for _, bundle := range pool.pendingBundles {
for _, tx := range bundle.Transactions {
if tx.Hash().Cmp(hash) == 0 {
return tx
}
}
}
return nil
}

func (pool *Rip7560BundlerPool) Add(_ []*types.Transaction, _ bool, _ bool) []error {
return nil
}

func (pool *Rip7560BundlerPool) Pending(_ txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
return nil
}

func (pool *Rip7560BundlerPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
pool.mu.Lock()
defer pool.mu.Unlock()

bundle := pool.selectExternalBundle()
return bundle, nil
}

// SubscribeTransactions is not needed for the External Bundler AA sub pool and 'ch' will never be sent anything.
func (pool *Rip7560BundlerPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, _ bool) event.Subscription {
return pool.txFeed.Subscribe(ch)
}

// Nonce is only used from 'GetPoolNonce' which is not relevant for AA transactions.
func (pool *Rip7560BundlerPool) Nonce(_ common.Address) uint64 {
return 0
}

// Stats function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Stats() (int, int) {
return 0, 0
}

// Content function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
return nil, nil
}

// ContentFrom function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) ContentFrom(_ common.Address) ([]*types.Transaction, []*types.Transaction) {
return nil, nil
}

// Locals are not necessary for AA Pool
func (pool *Rip7560BundlerPool) Locals() []common.Address {
return []common.Address{}
}

func (pool *Rip7560BundlerPool) Status(_ common.Hash) txpool.TxStatus {
panic("implement me")
}

// New creates a new RIP-7560 Account Abstraction Bundler transaction pool.
func New(config Config, chain legacypool.BlockChain, coinbase common.Address) *Rip7560BundlerPool {
return &Rip7560BundlerPool{
config: config,
chain: chain,
coinbase: coinbase,
}
}

// Filter rejects all individual transactions for External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Filter(_ *types.Transaction) bool {
return false
}

func (pool *Rip7560BundlerPool) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error {
pool.mu.Lock()
defer pool.mu.Unlock()

currentBlock := pool.currentHead.Load().Number
nextBlock := big.NewInt(0).Add(currentBlock, big.NewInt(1))
log.Error("RIP-7560 bundle submitted", "validForBlock", bundle.ValidForBlock.String(), "nextBlock", nextBlock.String())
pool.pendingBundles = append(pool.pendingBundles, bundle)
if nextBlock.Cmp(bundle.ValidForBlock) == 0 {
pool.txFeed.Send(core.NewTxsEvent{Txs: bundle.Transactions})
}
return nil
}

func (pool *Rip7560BundlerPool) GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, error) {
pool.mu.Lock()
defer pool.mu.Unlock()

return pool.includedBundles[hash], nil
}

// Simply returns the bundle with the highest promised revenue by fully trusting the bundler-provided value.
func (pool *Rip7560BundlerPool) selectExternalBundle() *types.ExternallyReceivedBundle {
var selectedBundle *types.ExternallyReceivedBundle
for _, bundle := range pool.pendingBundles {
if selectedBundle == nil || selectedBundle.ExpectedRevenue.Cmp(bundle.ExpectedRevenue) == -1 {
selectedBundle = bundle
}
}
return selectedBundle
}
6 changes: 6 additions & 0 deletions core/txpool/subpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,10 @@ type SubPool interface {
// Status returns the known status (unknown/pending/queued) of a transaction
// identified by their hashes.
Status(hash common.Hash) TxStatus

// RIP-7560 specific subpool functions, other subpools should ignore these

SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error
GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, error)
PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error)
}
46 changes: 46 additions & 0 deletions core/txpool/txpool_rip7560.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package txpool

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

// SubmitBundle 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 {
err := subpool.SubmitRip7560Bundle(bundle)
if err != nil {
return err
}
}
return nil
}

func (p *TxPool) GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, 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 {
bundleStats, err := subpool.GetRip7560BundleStatus(hash)
if err != nil {
return nil, err
}
if bundleStats != nil {
return bundleStats, nil
}
}
return nil, nil
}

func (p *TxPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// todo: we cannot 'filter-out' the AA pool so just passing to all pools - only AA pool has code in PendingBundle
for _, subpool := range p.subpools {
pendingBundle, err := subpool.PendingRip7560Bundle()
if err != nil {
return nil, err
}
if pendingBundle != nil {
return pendingBundle, nil
}
}
return nil, nil
}
Loading

0 comments on commit e466097

Please sign in to comment.