Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit a9ebe2b

Browse files
Ruteriavalonche
authored andcommitted
Bundle cancellations (#36)
Introduces bundle replacement and cancellation via replacementUuid. Since the replacement is tied to a specific sender, eth_sendBundle gets two additional optional fields: the replacement uuid and the signingAddress of the bundle submission. The DB requests are done in the background, and cancellations are resolved while non-cancelable bundles are already being simulated to avoid waiting for DB to reply. If anything goes wrong with the cancellations, the cancelable bundles are not considered. Note: every block is now sent to the relay, as we can no longer rely on the highest-profit rule!
1 parent 0861afa commit a9ebe2b

20 files changed

Lines changed: 355 additions & 55 deletions

builder/builder.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package builder
33
import (
44
"context"
55
"errors"
6-
"math/big"
76
_ "os"
87
"sync"
98
"time"
109

10+
"github.com/ethereum/go-ethereum/common"
1111
blockvalidation "github.com/ethereum/go-ethereum/eth/block-validation"
1212
"golang.org/x/time/rate"
1313

@@ -236,23 +236,22 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
236236
var (
237237
queueSignal = make(chan struct{}, 1)
238238

239-
queueMu sync.Mutex
240-
queueLastSubmittedProfit = new(big.Int)
241-
queueBestProfit = new(big.Int)
242-
queueBestEntry blockQueueEntry
239+
queueMu sync.Mutex
240+
queueLastSubmittedHash common.Hash
241+
queueBestEntry blockQueueEntry
243242
)
244243

245244
log.Debug("runBuildingJob", "slot", attrs.Slot, "parent", attrs.HeadHash)
246245

247246
submitBestBlock := func() {
248247
queueMu.Lock()
249-
if queueLastSubmittedProfit.Cmp(queueBestProfit) < 0 {
248+
if queueBestEntry.block.Hash() != queueLastSubmittedHash {
250249
err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt, queueBestEntry.commitedBundles, queueBestEntry.allBundles, proposerPubkey, vd, attrs)
251250

252251
if err != nil {
253252
log.Error("could not run sealed block hook", "err", err)
254253
} else {
255-
queueLastSubmittedProfit.Set(queueBestProfit)
254+
queueLastSubmittedHash = queueBestEntry.block.Hash()
256255
}
257256
}
258257
queueMu.Unlock()
@@ -271,15 +270,14 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
271270

272271
queueMu.Lock()
273272
defer queueMu.Unlock()
274-
if block.Profit.Cmp(queueBestProfit) > 0 {
273+
if block.Hash() != queueLastSubmittedHash {
275274
queueBestEntry = blockQueueEntry{
276275
block: block,
277276
ordersCloseTime: ordersCloseTime,
278277
sealedAt: sealedAt,
279278
commitedBundles: commitedBundles,
280279
allBundles: allBundles,
281280
}
282-
queueBestProfit.Set(block.Profit)
283281

284282
select {
285283
case queueSignal <- struct{}{}:

builder/builder_test.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,20 @@ func TestOnPayloadAttributes(t *testing.T) {
126126

127127
require.Equal(t, uint64(25), testRelay.requestedSlot)
128128

129-
// Clear the submitted message and check that the job will be ran again and but a new message will not be submitted since the profit is the same
129+
// Clear the submitted message and check that the job will be ran again and but a new message will not be submitted since the hash is the same
130+
testBlock.Profit = big.NewInt(10)
130131
testRelay.submittedMsg = nil
131132
time.Sleep(2200 * time.Millisecond)
132133
require.Nil(t, testRelay.submittedMsg)
133134

134-
// Up the profit, expect to get the block
135-
testEthService.testBlock.Profit.SetInt64(11)
135+
// Change the hash, expect to get the block
136+
testExecutableData.ExtraData = hexutil.MustDecode("0x0042fafd")
137+
testExecutableData.BlockHash = common.HexToHash("0x0579b1aaca5c079c91e5774bac72c7f9bc2ddf2b126e9c632be68a1cb8f3fc71")
138+
testBlock, err = beacon.ExecutableDataToBlock(*testExecutableData)
139+
testBlock.Profit = big.NewInt(10)
140+
require.NoError(t, err)
141+
testEthService.testBlock = testBlock
142+
136143
time.Sleep(2200 * time.Millisecond)
137144
require.NotNil(t, testRelay.submittedMsg)
138145
}

builder/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *Config) error {
185185
mevBundleCh := make(chan []types.MevBundle)
186186
blockNumCh := make(chan int64)
187187
bundleFetcher := flashbotsextra.NewBundleFetcher(backend, ds, blockNumCh, mevBundleCh, true)
188+
backend.RegisterBundleFetcher(bundleFetcher)
188189
go bundleFetcher.Run()
189190
}
190191

core/state_processor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, b
200200
// for the transaction, gas used and an error if the transaction failed,
201201
// indicating the block was invalid.
202202
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, preFinalizeHook func() error) (*types.Receipt, error) {
203-
msg, err := TransactionToMessage(types.MakeSigner(config, header.Number), header.BaseFee)
203+
msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number), header.BaseFee)
204204
if err != nil {
205205
return nil, err
206206
}
@@ -211,7 +211,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
211211
}
212212

213213
func ApplyTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, error) {
214-
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
214+
msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number), header.BaseFee)
215215
if err != nil {
216216
return nil, nil, err
217217
}

core/txpool/txpool.go

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package txpool
1818

1919
import (
2020
"container/heap"
21+
"context"
2122
"errors"
2223
"fmt"
2324
"math"
@@ -37,6 +38,7 @@ import (
3738
"github.com/ethereum/go-ethereum/log"
3839
"github.com/ethereum/go-ethereum/metrics"
3940
"github.com/ethereum/go-ethereum/params"
41+
"github.com/google/uuid"
4042
"golang.org/x/crypto/sha3"
4143
)
4244

@@ -291,6 +293,8 @@ type TxPool struct {
291293
initDoneCh chan struct{} // is closed once the pool is initialized (for tests)
292294

293295
changesSinceReorg int // A counter for how many drops we've performed in-between reorg.
296+
297+
bundleFetcher IFetcher
294298
}
295299

296300
type txpoolResetRequest struct {
@@ -354,6 +358,17 @@ func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain)
354358
return pool
355359
}
356360

361+
type IFetcher interface {
362+
GetLatestUuidBundles(ctx context.Context, blockNum int64) ([]types.LatestUuidBundle, error)
363+
}
364+
365+
func (pool *TxPool) RegisterBundleFetcher(fetcher IFetcher) {
366+
pool.mu.Lock()
367+
defer pool.mu.Unlock()
368+
369+
pool.bundleFetcher = fetcher
370+
}
371+
357372
// loop is the transaction pool's main event loop, waiting for and reacting to
358373
// outside blockchain events as well as for various reporting and transaction
359374
// eviction events.
@@ -591,21 +606,79 @@ func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transacti
591606
return pending
592607
}
593608

594-
/// AllMevBundles returns all the MEV Bundles currently in the pool
595-
func (pool *TxPool) AllMevBundles() []types.MevBundle {
596-
return pool.mevBundles
609+
type uuidBundleKey struct {
610+
Uuid uuid.UUID
611+
SigningAddress common.Address
612+
}
613+
614+
func (pool *TxPool) fetchLatestCancellableBundles(ctx context.Context, blockNumber *big.Int) (chan []types.LatestUuidBundle, chan error) {
615+
if pool.bundleFetcher == nil {
616+
return nil, nil
617+
}
618+
errCh := make(chan error, 1)
619+
lubCh := make(chan []types.LatestUuidBundle, 1)
620+
go func(blockNum int64) {
621+
lub, err := pool.bundleFetcher.GetLatestUuidBundles(ctx, blockNum)
622+
errCh <- err
623+
lubCh <- lub
624+
}(blockNumber.Int64())
625+
return lubCh, errCh
626+
}
627+
628+
func resolveCancellableBundles(lubCh chan []types.LatestUuidBundle, errCh chan error, uuidBundles map[uuidBundleKey][]types.MevBundle) []types.MevBundle {
629+
if lubCh == nil || errCh == nil {
630+
return nil
631+
}
632+
633+
if len(uuidBundles) == 0 {
634+
return nil
635+
}
636+
637+
err := <-errCh
638+
if err != nil {
639+
log.Error("could not fetch latest bundles uuid map", "err", err)
640+
return nil
641+
}
642+
643+
currentCancellableBundles := []types.MevBundle{}
644+
645+
log.Trace("Processing uuid bundles", "uuidBundles", uuidBundles)
646+
647+
lubs := <-lubCh
648+
for _, lub := range lubs {
649+
ubk := uuidBundleKey{lub.Uuid, lub.SigningAddress}
650+
bundles, found := uuidBundles[ubk]
651+
if !found {
652+
log.Trace("missing uuid bundle", "ubk", ubk)
653+
continue
654+
}
655+
for _, bundle := range bundles {
656+
if bundle.Hash == lub.BundleHash {
657+
log.Trace("adding uuid bundle", "bundle hash", bundle.Hash.String(), "lub", lub)
658+
currentCancellableBundles = append(currentCancellableBundles, bundle)
659+
break
660+
}
661+
}
662+
}
663+
return currentCancellableBundles
597664
}
598665

599666
// MevBundles returns a list of bundles valid for the given blockNumber/blockTimestamp
600667
// also prunes bundles that are outdated
601-
func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) []types.MevBundle {
668+
// Returns regular bundles and a function resolving to current cancellable bundles
669+
func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]types.MevBundle, chan []types.MevBundle) {
602670
pool.mu.Lock()
603671
defer pool.mu.Unlock()
604672

673+
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
674+
lubCh, errCh := pool.fetchLatestCancellableBundles(ctx, blockNumber)
675+
605676
// returned values
606677
var ret []types.MevBundle
607678
// rolled over values
608679
var bundles []types.MevBundle
680+
// (uuid, signingAddress) -> list of bundles
681+
var uuidBundles = make(map[uuidBundleKey][]types.MevBundle)
609682

610683
for _, bundle := range pool.mevBundles {
611684
// Prune outdated bundles
@@ -619,14 +692,31 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) []ty
619692
continue
620693
}
621694

622-
// return the ones which are in time
623-
ret = append(ret, bundle)
624695
// keep the bundles around internally until they need to be pruned
625696
bundles = append(bundles, bundle)
697+
698+
// TODO: omit duplicates
699+
700+
// do not append to the return quite yet, check the DB for the latest bundle for that uuid
701+
if bundle.Uuid != types.EmptyUUID {
702+
ubk := uuidBundleKey{bundle.Uuid, bundle.SigningAddress}
703+
uuidBundles[ubk] = append(uuidBundles[ubk], bundle)
704+
continue
705+
}
706+
707+
// return the ones which are in time
708+
ret = append(ret, bundle)
626709
}
627710

628711
pool.mevBundles = bundles
629-
return ret
712+
713+
cancellableBundlesCh := make(chan []types.MevBundle, 1)
714+
go func() {
715+
cancellableBundlesCh <- resolveCancellableBundles(lubCh, errCh, uuidBundles)
716+
cancel()
717+
}()
718+
719+
return ret, cancellableBundlesCh
630720
}
631721

632722
// AddMevBundles adds a mev bundles to the pool
@@ -639,7 +729,7 @@ func (pool *TxPool) AddMevBundles(mevBundles []types.MevBundle) error {
639729
}
640730

641731
// AddMevBundle adds a mev bundle to the pool
642-
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
732+
func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, replacementUuid uuid.UUID, signingAddress common.Address, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error {
643733
bundleHasher := sha3.NewLegacyKeccak256()
644734
for _, tx := range txs {
645735
bundleHasher.Write(tx.Hash().Bytes())
@@ -652,6 +742,8 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m
652742
pool.mevBundles = append(pool.mevBundles, types.MevBundle{
653743
Txs: txs,
654744
BlockNumber: blockNumber,
745+
Uuid: replacementUuid,
746+
SigningAddress: signingAddress,
655747
MinTimestamp: minTimestamp,
656748
MaxTimestamp: maxTimestamp,
657749
RevertingTxHashes: revertingTxHashes,

0 commit comments

Comments
 (0)