Skip to content

Commit

Permalink
Merge pull request #4 from binance-chain/mev
Browse files Browse the repository at this point in the history
[R4R] add mev module
  • Loading branch information
unclezoro authored Aug 11, 2021
2 parents 69ce7f1 + dca85f8 commit 76951bd
Show file tree
Hide file tree
Showing 15 changed files with 700 additions and 20 deletions.
147 changes: 136 additions & 11 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
"sync"
"time"

"github.com/ethereum/go-ethereum/rlp"

"github.com/ethereum/go-ethereum/crypto"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/state"
Expand Down Expand Up @@ -83,6 +87,9 @@ var (
// than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")

// ErrorBundlePoolIsFull is returned if the number of bundle exceed the limit
ErrorBundlePoolIsFull = errors.New("bundle pool is full")
)

var (
Expand Down Expand Up @@ -115,6 +122,8 @@ var (
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)

bundleGauge = metrics.NewRegisteredGauge("txpool/bundles", nil)
)

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

type BundleSimulator interface {
SimulateBundle(bundle types.MevBundle) (*big.Int, error)
}

// TxPoolConfig are the configuration parameters of the transaction pool.
type TxPoolConfig struct {
Locals []common.Address // Addresses that should be treated by default as local
Expand All @@ -147,12 +160,12 @@ type TxPoolConfig struct {
PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)

AccountSlots uint64 // Number of executable transaction slots guaranteed per account
GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts

Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
AccountSlots uint64 // Number of executable transaction slots guaranteed per account
GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
BundleSlot uint64 // Maximum number of bundle slots for all accounts
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
}

// DefaultTxPoolConfig contains the default configurations for the transaction
Expand Down Expand Up @@ -226,6 +239,7 @@ type TxPool struct {
txFeed event.Feed
scope event.SubscriptionScope
signer types.Signer
simulator BundleSimulator
mu sync.RWMutex

istanbul bool // Fork indicator whether we are in the istanbul stage.
Expand All @@ -238,11 +252,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 map[common.Hash]*types.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 @@ -281,6 +296,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
reorgDoneCh: make(chan chan struct{}),
reorgShutdownCh: make(chan struct{}),
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
mevBundles: make(map[common.Hash]*types.MevBundle),
}
pool.locals = newAccountSet(pool.signer)
for _, addr := range config.Locals {
Expand Down Expand Up @@ -406,6 +422,10 @@ func (pool *TxPool) Stop() {
log.Info("Transaction pool stopped")
}

func (pool *TxPool) SetBundleSimulator(simulator BundleSimulator) {
pool.simulator = simulator
}

// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
// starts sending event to the given channel.
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription {
Expand Down Expand Up @@ -496,6 +516,111 @@ 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() []*types.MevBundle {
pool.mu.Lock()
defer pool.mu.Unlock()
bundles := make([]*types.MevBundle, 0, len(pool.mevBundles))
for _, bundle := range pool.mevBundles {
bundles = append(bundles, bundle)
}
return bundles
}

func (pool *TxPool) GetMevBundles(hash common.Hash) *types.MevBundle {
pool.mu.Lock()
defer pool.mu.Unlock()

return pool.mevBundles[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.MevBundle, error) {
pool.mu.Lock()
defer pool.mu.Unlock()

// returned values
var ret []types.MevBundle
// rolled over values
bundles := make(map[common.Hash]*types.MevBundle)

for uid, bundle := range pool.mevBundles {
// Prune outdated bundles
if (bundle.MaxTimestamp != 0 && blockTimestamp > bundle.MaxTimestamp) || (bundle.MaxBlockNumber != nil && bundle.MaxBlockNumber.Int64() != 0 && blockNumber.Cmp(bundle.MaxBlockNumber) > 0) {
continue
}

// Roll over future bundles
if bundle.MinTimestamp != 0 && blockTimestamp < bundle.MinTimestamp {
bundles[uid] = bundle
continue
}

// return the ones which are in time
ret = append(ret, *bundle)
// keep the bundles around internally until they need to be pruned
bundles[uid] = bundle
}

pool.mevBundles = bundles
bundleGauge.Update(int64(len(pool.mevBundles)))
return ret, nil
}

func (pool *TxPool) PruneBundle(bundle common.Hash) {
pool.mu.Lock()
defer pool.mu.Unlock()
delete(pool.mevBundles, bundle)
}

// AddMevBundle adds a mev bundle to the pool
func (pool *TxPool) AddMevBundle(txs types.Transactions, maxBlockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) (common.Hash, error) {
if pool.simulator == nil {
return common.Hash{}, errors.New("bundle simulator is nil")
}
bundle := types.MevBundle{
Txs: txs,
MaxBlockNumber: maxBlockNumber,
MinTimestamp: minTimestamp,
MaxTimestamp: maxTimestamp,
RevertingTxHashes: revertingTxHashes,
}
bz, err := rlp.EncodeToBytes(bundle)
if err != nil {
return common.Hash{}, err
}
hash := crypto.Keccak256Hash(bz)
bundle.Hash = hash
price, err := pool.simulator.SimulateBundle(bundle)
if err != nil {
return common.Hash{}, err
}
bundle.Price = price
pool.mu.Lock()
defer pool.mu.Unlock()
if _, ok := pool.mevBundles[hash]; ok {
return common.Hash{}, errors.New("bundle already exist")
}
if len(pool.mevBundles) > int(pool.config.BundleSlot) {
leastPrice := big.NewInt(math.MaxInt64)
leastBundleHash := common.Hash{}
for h, b := range pool.mevBundles {
if b.Price.Cmp(leastPrice) < 0 {
leastPrice = b.Price
leastBundleHash = h
}
}
if bundle.Price.Cmp(leastPrice) < 0 {
return common.Hash{}, ErrorBundlePoolIsFull
}
delete(pool.mevBundles, leastBundleHash)
}
pool.mevBundles[hash] = &bundle
bundleGauge.Update(int64(len(pool.mevBundles)))
return hash, nil
}

// Locals retrieves the accounts currently considered local by the pool.
func (pool *TxPool) Locals() []common.Address {
pool.mu.Lock()
Expand Down
10 changes: 10 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,13 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) CheckNonce() bool { return m.checkNonce }

type MevBundle struct {
Txs Transactions
MaxBlockNumber *big.Int
MinTimestamp uint64
MaxTimestamp uint64
RevertingTxHashes []common.Hash
Hash common.Hash `rlp:"-"`
Price *big.Int
}
29 changes: 29 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"math/big"
"sort"

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

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

func (b *EthAPIBackend) BundlePrice() (*big.Int, error) {
bundles := b.eth.txPool.AllMevBundles()
if len(bundles) == 0 {
return big.NewInt(b.eth.config.Miner.MevGasPriceFloor), nil
}
sort.SliceStable(bundles, func(i, j int) bool {
return bundles[j].Price.Cmp(bundles[i].Price) < 0
})
idx := len(bundles) / 2
if bundles[idx].Price.Cmp(big.NewInt(b.eth.config.Miner.MevGasPriceFloor)) < 0 {
return big.NewInt(b.eth.config.Miner.MevGasPriceFloor), nil
}
return bundles[idx].Price, nil
}

func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
return b.eth.txPool.Get(hash)
}
Expand All @@ -263,6 +283,15 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.TxPool().Content()
}

func (b *EthAPIBackend) Bundles() []*types.MevBundle {
return b.eth.TxPool().AllMevBundles()
}

func (b *EthAPIBackend) GetBundleByHash(ctx context.Context, bundleHash common.Hash) *types.MevBundle {
b.eth.TxPool().AllMevBundles()
return b.eth.TxPool().GetMevBundles(bundleHash)
}

func (b *EthAPIBackend) TxPool() *core.TxPool {
return b.eth.TxPool()
}
Expand Down
1 change: 1 addition & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {

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

gpoParams := config.GPO
if gpoParams.Default == nil {
Expand Down
25 changes: 24 additions & 1 deletion ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import (
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
)

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

func (ec * Client) SendBundle(ctx context.Context, txs types.Transactions, maxBlockNumber int64, maxTime, minTime uint64, reverseHash []common.Hash) (common.Hash, error) {
txNum := len(txs)
var hash common.Hash
bundle := ethapi.SendBundleArgs{
Txs: make([]hexutil.Bytes, txNum),
MaxBlockNumber: rpc.BlockNumber(maxBlockNumber),
MaxTimestamp: &maxTime,
MinTimestamp: &minTime,
RevertingTxHashes: reverseHash,
}

for i, tx := range txs {
txb, err := tx.MarshalBinary()
if err != nil {
return common.Hash{}, err
}
bundle.Txs[i] = []byte(hexutil.Encode(txb))
}

err := ec.c.CallContext(context.Background(), &hash, "eth_sendBundle", bundle)
return hash, err
}

func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
Expand Down
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
Expand Down Expand Up @@ -48,7 +47,6 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
Expand Down Expand Up @@ -248,7 +246,6 @@ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand Down
4 changes: 4 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ type TransactionSender interface {
SendTransaction(ctx context.Context, tx *types.Transaction) error
}

type BundleSender interface {
SendBundle(ctx context.Context, txs types.Transactions, maxBlockNumber int64, maxTime, minTime uint64,reverseHash []common.Hash) (common.Hash, error)
}

// GasPricer wraps the gas price oracle, which monitors the blockchain to determine the
// optimal gas price given current fee market conditions.
type GasPricer interface {
Expand Down
Loading

0 comments on commit 76951bd

Please sign in to comment.