Skip to content

Commit

Permalink
Add eth_sendBundle RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
jparyani committed Feb 19, 2021
1 parent 4d48980 commit 8104d5d
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 5 deletions.
30 changes: 30 additions & 0 deletions core/mev_bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package core

import (
"math/big"

"github.com/ethereum/go-ethereum/core/types"
)

type mevBundle struct {
txs types.Transactions
blockNumber *big.Int
minTimestamp uint64
maxTimestamp uint64
}
63 changes: 58 additions & 5 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ type TxPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk

pending map[common.Address]*txList // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account
all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price
pending map[common.Address]*txList // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account
mevBundles []mevBundle
all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price

chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
Expand Down Expand Up @@ -495,6 +496,58 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
return pending, nil
}

/// AllMevBundles returns all the MEV Bundles currently in the pool
func (pool *TxPool) AllMevBundles() []mevBundle {
return pool.mevBundles
}

// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
// also prunes bundles that are outdated
func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.Transactions, error) {
pool.mu.Lock()
defer pool.mu.Unlock()

// returned values
var txBundles []types.Transactions
// rolled over values
var bundles []mevBundle

for _, bundle := range pool.mevBundles {
// Prune outdated bundles
if (bundle.maxTimestamp != 0 && blockTimestamp > bundle.maxTimestamp) || blockNumber.Cmp(bundle.blockNumber) > 0 {
continue
}

// Roll over future bundles
if (bundle.minTimestamp != 0 && blockTimestamp < bundle.minTimestamp) || blockNumber.Cmp(bundle.blockNumber) < 0 {
bundles = append(bundles, bundle)
continue
}

// return the ones which are in time
txBundles = append(txBundles, bundle.txs)
// keep the bundles around internally until they need to be pruned
bundles = append(bundles, bundle)
}

pool.mevBundles = bundles
return txBundles, nil
}

// AddMevBundle adds a mev bundle to the pool
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64) error {
pool.mu.Lock()
defer pool.mu.Unlock()

pool.mevBundles = append(pool.mevBundles, mevBundle{
txs: txs,
blockNumber: blockNumber,
minTimestamp: minTimestamp,
maxTimestamp: maxTimestamp,
})
return nil
}

// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
Expand Down
59 changes: 59 additions & 0 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2052,3 +2052,62 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
pool.Stop()
}
}

// MEV Bundle tests
func TestBundles(t *testing.T) {
pool := TxPool{}
var empty types.Transactions
empty = append(empty, &types.Transaction{})

pool.AddMevBundle(empty, big.NewInt(4), 0, 0)
pool.AddMevBundle(empty, big.NewInt(5), 0, 0)
pool.AddMevBundle(empty, big.NewInt(6), 0, 0)
pool.AddMevBundle(empty, big.NewInt(9), 0, 0)
pool.AddMevBundle(empty, big.NewInt(9), 0, 0)
pool.AddMevBundle(empty, big.NewInt(12), 0, 0)
pool.AddMevBundle(empty, big.NewInt(15), 0, 0)

type bundleTestData struct {
block int64
testTimestamp uint64
expectedRes int
expectedRemaining int
action func()
}

testData := []bundleTestData{
// prunes outdated ones, too early for the rest
{8, 0, 0, 4, nil},
// 2 at 9, nothing to prune
{9, 0, 2, 4, nil},
// adds a bundle at 10 which has a min/max timestamp that's outdated
// the only bundles remaining are 12 and 15
{10, 8, 0, 2, func() { pool.AddMevBundle(empty, big.NewInt(10), 5, 7) }},
// nothing returned, remaining is the same
{11, 0, 0, 2, nil},
// one bundle at 12
{12, 0, 1, 2, nil},
// no bundle, 12 got pruned
{13, 0, 0, 1, nil},
{14, 0, 0, 1, nil},
{15, 0, 1, 1, nil},
{16, 0, 0, 0, nil},
}

for _, v := range testData {
if v.action != nil {
v.action()
}
checkBundles(t, &pool, v.block, v.testTimestamp, v.expectedRes, v.expectedRemaining)
}
}

func checkBundles(t *testing.T, pool *TxPool, block int64, timestamp uint64, expectedRes int, expectedRemaining int) {
res, _ := pool.MevBundles(big.NewInt(block), timestamp)
if len(res) != expectedRes {
t.Fatalf("expected returned bundles did not match got %d, expected %d", len(res), expectedRes)
}
if len(pool.mevBundles) != expectedRemaining {
t.Fatalf("expected remaining bundles did not match got %d, expected %d", len(pool.mevBundles), expectedRemaining)
}
}
4 changes: 4 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
return b.eth.txPool.AddLocal(signedTx)
}

func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) error {
return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp)
}

func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending, err := b.eth.txPool.Pending()
if err != nil {
Expand Down
36 changes: 36 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1952,3 +1952,39 @@ func toHexSlice(b [][]byte) []string {
}
return r
}

// ---------------------------------------------------------------- FlashBots ----------------------------------------------------------------

// PrivateTxBundleAPI offers an API for accepting bundled transactions
type PrivateTxBundleAPI struct {
b Backend
}

// NewPrivateTxBundleAPI creates a new Tx Bundle API instance.
func NewPrivateTxBundleAPI(b Backend) *PrivateTxBundleAPI {
return &PrivateTxBundleAPI{b}
}

// SendBundle will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce and ensuring validity
func (s *PrivateTxBundleAPI) SendBundle(ctx context.Context, encodedTxs []hexutil.Bytes, blockNumber rpc.BlockNumber, minTimestampPtr, maxTimestampPtr *uint64) error {
var txs types.Transactions

for _, encodedTx := range encodedTxs {
tx := new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return err
}
txs = append(txs, tx)
}

var minTimestamp, maxTimestamp uint64
if minTimestampPtr != nil {
minTimestamp = *minTimestampPtr
}
if maxTimestampPtr != nil {
maxTimestamp = *maxTimestampPtr
}

return s.b.SendBundle(ctx, txs, blockNumber, minTimestamp, maxTimestamp)
}
6 changes: 6 additions & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type Backend interface {

// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
Expand Down Expand Up @@ -132,6 +133,11 @@ func GetAPIs(apiBackend Backend) []rpc.API {
Version: "1.0",
Service: NewPrivateAccountAPI(apiBackend, nonceLock),
Public: false,
}, {
Namespace: "eth",
Version: "1.0",
Service: NewPrivateTxBundleAPI(apiBackend),
Public: true,
},
}
}
5 changes: 5 additions & 0 deletions internal/web3ext/web3ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ web3._extend({
params: 3,
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter]
}),
new web3._extend.Method({
name: 'sendBundle',
call: 'eth_sendBundle',
params: 4
}),
],
properties: [
new web3._extend.Property({
Expand Down
3 changes: 3 additions & 0 deletions les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
b.eth.txPool.RemoveTx(txHash)
}
func (b *LesApiBackend) SendBundle(ctx context.Context, txs types.Transactions, blockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64) error {
return b.eth.txPool.AddMevBundle(txs, big.NewInt(blockNumber.Int64()), minTimestamp, maxTimestamp)
}

func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
return b.eth.txPool.GetTransactions()
Expand Down
11 changes: 11 additions & 0 deletions light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,14 @@ func (pool *TxPool) RemoveTx(hash common.Hash) {
pool.chainDb.Delete(hash[:])
pool.relay.Discard([]common.Hash{hash})
}

// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
// also prunes bundles that are outdated
func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.Transactions, error) {
return nil, nil
}

// AddMevBundle adds a mev bundle to the pool
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp uint64, maxTimestamp uint64) error {
return nil
}

0 comments on commit 8104d5d

Please sign in to comment.