Skip to content

Commit 76951bd

Browse files
authored
Merge pull request #4 from binance-chain/mev
[R4R] add mev module
2 parents 69ce7f1 + dca85f8 commit 76951bd

File tree

15 files changed

+700
-20
lines changed

15 files changed

+700
-20
lines changed

core/tx_pool.go

+136-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import (
2424
"sync"
2525
"time"
2626

27+
"github.com/ethereum/go-ethereum/rlp"
28+
29+
"github.com/ethereum/go-ethereum/crypto"
30+
2731
"github.com/ethereum/go-ethereum/common"
2832
"github.com/ethereum/go-ethereum/common/prque"
2933
"github.com/ethereum/go-ethereum/core/state"
@@ -83,6 +87,9 @@ var (
8387
// than some meaningful limit a user might use. This is not a consensus error
8488
// making the transaction invalid, rather a DOS protection.
8589
ErrOversizedData = errors.New("oversized data")
90+
91+
// ErrorBundlePoolIsFull is returned if the number of bundle exceed the limit
92+
ErrorBundlePoolIsFull = errors.New("bundle pool is full")
8693
)
8794

8895
var (
@@ -115,6 +122,8 @@ var (
115122
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
116123
localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
117124
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
125+
126+
bundleGauge = metrics.NewRegisteredGauge("txpool/bundles", nil)
118127
)
119128

120129
// TxStatus is the current status of a transaction as seen by the pool.
@@ -137,6 +146,10 @@ type blockChain interface {
137146
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
138147
}
139148

149+
type BundleSimulator interface {
150+
SimulateBundle(bundle types.MevBundle) (*big.Int, error)
151+
}
152+
140153
// TxPoolConfig are the configuration parameters of the transaction pool.
141154
type TxPoolConfig struct {
142155
Locals []common.Address // Addresses that should be treated by default as local
@@ -147,12 +160,12 @@ type TxPoolConfig struct {
147160
PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
148161
PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)
149162

150-
AccountSlots uint64 // Number of executable transaction slots guaranteed per account
151-
GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
152-
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
153-
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
154-
155-
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
163+
AccountSlots uint64 // Number of executable transaction slots guaranteed per account
164+
GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
165+
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
166+
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
167+
BundleSlot uint64 // Maximum number of bundle slots for all accounts
168+
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
156169
}
157170

158171
// DefaultTxPoolConfig contains the default configurations for the transaction
@@ -226,6 +239,7 @@ type TxPool struct {
226239
txFeed event.Feed
227240
scope event.SubscriptionScope
228241
signer types.Signer
242+
simulator BundleSimulator
229243
mu sync.RWMutex
230244

231245
istanbul bool // Fork indicator whether we are in the istanbul stage.
@@ -238,11 +252,12 @@ type TxPool struct {
238252
locals *accountSet // Set of local transaction to exempt from eviction rules
239253
journal *txJournal // Journal of local transaction to back up to disk
240254

241-
pending map[common.Address]*txList // All currently processable transactions
242-
queue map[common.Address]*txList // Queued but non-processable transactions
243-
beats map[common.Address]time.Time // Last heartbeat from each known account
244-
all *txLookup // All transactions to allow lookups
245-
priced *txPricedList // All transactions sorted by price
255+
pending map[common.Address]*txList // All currently processable transactions
256+
queue map[common.Address]*txList // Queued but non-processable transactions
257+
beats map[common.Address]time.Time // Last heartbeat from each known account
258+
mevBundles map[common.Hash]*types.MevBundle
259+
all *txLookup // All transactions to allow lookups
260+
priced *txPricedList // All transactions sorted by price
246261

247262
chainHeadCh chan ChainHeadEvent
248263
chainHeadSub event.Subscription
@@ -281,6 +296,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
281296
reorgDoneCh: make(chan chan struct{}),
282297
reorgShutdownCh: make(chan struct{}),
283298
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
299+
mevBundles: make(map[common.Hash]*types.MevBundle),
284300
}
285301
pool.locals = newAccountSet(pool.signer)
286302
for _, addr := range config.Locals {
@@ -406,6 +422,10 @@ func (pool *TxPool) Stop() {
406422
log.Info("Transaction pool stopped")
407423
}
408424

425+
func (pool *TxPool) SetBundleSimulator(simulator BundleSimulator) {
426+
pool.simulator = simulator
427+
}
428+
409429
// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
410430
// starts sending event to the given channel.
411431
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription {
@@ -496,6 +516,111 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
496516
return pending, nil
497517
}
498518

519+
/// AllMevBundles returns all the MEV Bundles currently in the pool
520+
func (pool *TxPool) AllMevBundles() []*types.MevBundle {
521+
pool.mu.Lock()
522+
defer pool.mu.Unlock()
523+
bundles := make([]*types.MevBundle, 0, len(pool.mevBundles))
524+
for _, bundle := range pool.mevBundles {
525+
bundles = append(bundles, bundle)
526+
}
527+
return bundles
528+
}
529+
530+
func (pool *TxPool) GetMevBundles(hash common.Hash) *types.MevBundle {
531+
pool.mu.Lock()
532+
defer pool.mu.Unlock()
533+
534+
return pool.mevBundles[hash]
535+
}
536+
537+
// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
538+
// also prunes bundles that are outdated
539+
func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.MevBundle, error) {
540+
pool.mu.Lock()
541+
defer pool.mu.Unlock()
542+
543+
// returned values
544+
var ret []types.MevBundle
545+
// rolled over values
546+
bundles := make(map[common.Hash]*types.MevBundle)
547+
548+
for uid, bundle := range pool.mevBundles {
549+
// Prune outdated bundles
550+
if (bundle.MaxTimestamp != 0 && blockTimestamp > bundle.MaxTimestamp) || (bundle.MaxBlockNumber != nil && bundle.MaxBlockNumber.Int64() != 0 && blockNumber.Cmp(bundle.MaxBlockNumber) > 0) {
551+
continue
552+
}
553+
554+
// Roll over future bundles
555+
if bundle.MinTimestamp != 0 && blockTimestamp < bundle.MinTimestamp {
556+
bundles[uid] = bundle
557+
continue
558+
}
559+
560+
// return the ones which are in time
561+
ret = append(ret, *bundle)
562+
// keep the bundles around internally until they need to be pruned
563+
bundles[uid] = bundle
564+
}
565+
566+
pool.mevBundles = bundles
567+
bundleGauge.Update(int64(len(pool.mevBundles)))
568+
return ret, nil
569+
}
570+
571+
func (pool *TxPool) PruneBundle(bundle common.Hash) {
572+
pool.mu.Lock()
573+
defer pool.mu.Unlock()
574+
delete(pool.mevBundles, bundle)
575+
}
576+
577+
// AddMevBundle adds a mev bundle to the pool
578+
func (pool *TxPool) AddMevBundle(txs types.Transactions, maxBlockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) (common.Hash, error) {
579+
if pool.simulator == nil {
580+
return common.Hash{}, errors.New("bundle simulator is nil")
581+
}
582+
bundle := types.MevBundle{
583+
Txs: txs,
584+
MaxBlockNumber: maxBlockNumber,
585+
MinTimestamp: minTimestamp,
586+
MaxTimestamp: maxTimestamp,
587+
RevertingTxHashes: revertingTxHashes,
588+
}
589+
bz, err := rlp.EncodeToBytes(bundle)
590+
if err != nil {
591+
return common.Hash{}, err
592+
}
593+
hash := crypto.Keccak256Hash(bz)
594+
bundle.Hash = hash
595+
price, err := pool.simulator.SimulateBundle(bundle)
596+
if err != nil {
597+
return common.Hash{}, err
598+
}
599+
bundle.Price = price
600+
pool.mu.Lock()
601+
defer pool.mu.Unlock()
602+
if _, ok := pool.mevBundles[hash]; ok {
603+
return common.Hash{}, errors.New("bundle already exist")
604+
}
605+
if len(pool.mevBundles) > int(pool.config.BundleSlot) {
606+
leastPrice := big.NewInt(math.MaxInt64)
607+
leastBundleHash := common.Hash{}
608+
for h, b := range pool.mevBundles {
609+
if b.Price.Cmp(leastPrice) < 0 {
610+
leastPrice = b.Price
611+
leastBundleHash = h
612+
}
613+
}
614+
if bundle.Price.Cmp(leastPrice) < 0 {
615+
return common.Hash{}, ErrorBundlePoolIsFull
616+
}
617+
delete(pool.mevBundles, leastBundleHash)
618+
}
619+
pool.mevBundles[hash] = &bundle
620+
bundleGauge.Update(int64(len(pool.mevBundles)))
621+
return hash, nil
622+
}
623+
499624
// Locals retrieves the accounts currently considered local by the pool.
500625
func (pool *TxPool) Locals() []common.Address {
501626
pool.mu.Lock()

core/types/transaction.go

+10
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,13 @@ func (m Message) Nonce() uint64 { return m.nonce }
535535
func (m Message) Data() []byte { return m.data }
536536
func (m Message) AccessList() AccessList { return m.accessList }
537537
func (m Message) CheckNonce() bool { return m.checkNonce }
538+
539+
type MevBundle struct {
540+
Txs Transactions
541+
MaxBlockNumber *big.Int
542+
MinTimestamp uint64
543+
MaxTimestamp uint64
544+
RevertingTxHashes []common.Hash
545+
Hash common.Hash `rlp:"-"`
546+
Price *big.Int
547+
}

eth/api_backend.go

+29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"math/big"
23+
"sort"
2324

2425
"github.com/ethereum/go-ethereum/accounts"
2526
"github.com/ethereum/go-ethereum/common"
@@ -242,6 +243,25 @@ func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
242243
return txs, nil
243244
}
244245

246+
func (b *EthAPIBackend) SendBundle(ctx context.Context, txs types.Transactions, maxBlockNumber rpc.BlockNumber, minTimestamp uint64, maxTimestamp uint64, revertingTxHashes []common.Hash) (common.Hash, error) {
247+
return b.eth.txPool.AddMevBundle(txs, big.NewInt(maxBlockNumber.Int64()), minTimestamp, maxTimestamp, revertingTxHashes)
248+
}
249+
250+
func (b *EthAPIBackend) BundlePrice() (*big.Int, error) {
251+
bundles := b.eth.txPool.AllMevBundles()
252+
if len(bundles) == 0 {
253+
return big.NewInt(b.eth.config.Miner.MevGasPriceFloor), nil
254+
}
255+
sort.SliceStable(bundles, func(i, j int) bool {
256+
return bundles[j].Price.Cmp(bundles[i].Price) < 0
257+
})
258+
idx := len(bundles) / 2
259+
if bundles[idx].Price.Cmp(big.NewInt(b.eth.config.Miner.MevGasPriceFloor)) < 0 {
260+
return big.NewInt(b.eth.config.Miner.MevGasPriceFloor), nil
261+
}
262+
return bundles[idx].Price, nil
263+
}
264+
245265
func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
246266
return b.eth.txPool.Get(hash)
247267
}
@@ -263,6 +283,15 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
263283
return b.eth.TxPool().Content()
264284
}
265285

286+
func (b *EthAPIBackend) Bundles() []*types.MevBundle {
287+
return b.eth.TxPool().AllMevBundles()
288+
}
289+
290+
func (b *EthAPIBackend) GetBundleByHash(ctx context.Context, bundleHash common.Hash) *types.MevBundle {
291+
b.eth.TxPool().AllMevBundles()
292+
return b.eth.TxPool().GetMevBundles(bundleHash)
293+
}
294+
266295
func (b *EthAPIBackend) TxPool() *core.TxPool {
267296
return b.eth.TxPool()
268297
}

eth/backend.go

+1
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
238238

239239
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
240240
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
241+
eth.txPool.SetBundleSimulator(eth.miner)
241242

242243
gpoParams := config.GPO
243244
if gpoParams.Default == nil {

ethclient/ethclient.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
"errors"
2424
"fmt"
2525
"math/big"
26-
2726
"github.com/ethereum/go-ethereum"
2827
"github.com/ethereum/go-ethereum/common"
2928
"github.com/ethereum/go-ethereum/common/hexutil"
3029
"github.com/ethereum/go-ethereum/core/types"
30+
"github.com/ethereum/go-ethereum/internal/ethapi"
3131
"github.com/ethereum/go-ethereum/rpc"
3232
)
3333

@@ -565,6 +565,29 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
565565
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
566566
}
567567

568+
func (ec * Client) SendBundle(ctx context.Context, txs types.Transactions, maxBlockNumber int64, maxTime, minTime uint64, reverseHash []common.Hash) (common.Hash, error) {
569+
txNum := len(txs)
570+
var hash common.Hash
571+
bundle := ethapi.SendBundleArgs{
572+
Txs: make([]hexutil.Bytes, txNum),
573+
MaxBlockNumber: rpc.BlockNumber(maxBlockNumber),
574+
MaxTimestamp: &maxTime,
575+
MinTimestamp: &minTime,
576+
RevertingTxHashes: reverseHash,
577+
}
578+
579+
for i, tx := range txs {
580+
txb, err := tx.MarshalBinary()
581+
if err != nil {
582+
return common.Hash{}, err
583+
}
584+
bundle.Txs[i] = []byte(hexutil.Encode(txb))
585+
}
586+
587+
err := ec.c.CallContext(context.Background(), &hash, "eth_sendBundle", bundle)
588+
return hash, err
589+
}
590+
568591
func toCallArg(msg ethereum.CallMsg) interface{} {
569592
arg := map[string]interface{}{
570593
"from": msg.From,

go.sum

-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
21
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
32
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
43
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -48,7 +47,6 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn
4847
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
4948
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
5049
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
51-
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
5250
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5351
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
5452
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -248,7 +246,6 @@ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv
248246
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
249247
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
250248
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
251-
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
252249
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
253250
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
254251
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=

interfaces.go

+4
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ type TransactionSender interface {
174174
SendTransaction(ctx context.Context, tx *types.Transaction) error
175175
}
176176

177+
type BundleSender interface {
178+
SendBundle(ctx context.Context, txs types.Transactions, maxBlockNumber int64, maxTime, minTime uint64,reverseHash []common.Hash) (common.Hash, error)
179+
}
180+
177181
// GasPricer wraps the gas price oracle, which monitors the blockchain to determine the
178182
// optimal gas price given current fee market conditions.
179183
type GasPricer interface {

0 commit comments

Comments
 (0)