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

Commit 9711488

Browse files
committed
Mev share sbundle (#60)
* basic sbundle * sbundle pool * sbundle api * local builder * move sim bundle to core * working builder * db for sbundles * report sbundle stat * mev_simBundle nested logs * refundConfig * pay kickback from refundable value * lints * percentof * sbundle pool with separate lock * don't wait for error when adding sbundle
1 parent 66893df commit 9711488

31 files changed

Lines changed: 1230 additions & 75 deletions

builder/builder.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,15 @@ func (b *Builder) Stop() error {
183183
return nil
184184
}
185185

186-
func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time, commitedBundles, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
186+
func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time,
187+
commitedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
188+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
187189
if b.eth.Config().IsShanghai(block.Time()) {
188-
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
190+
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
189191
return err
190192
}
191193
} else {
192-
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
194+
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
193195
return err
194196
}
195197
}
@@ -200,7 +202,9 @@ func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersC
200202
return nil
201203
}
202204

203-
func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time, commitedBundles, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
205+
func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time,
206+
commitedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
207+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
204208
executableData := engine.BlockToExecutableData(block, blockValue)
205209
payload, err := executableDataToExecutionPayload(executableData.ExecutionPayload)
206210
if err != nil {
@@ -245,7 +249,7 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
245249
log.Error("could not validate bellatrix block", "err", err)
246250
}
247251
} else {
248-
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &blockBidMsg)
252+
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &blockBidMsg)
249253
err = b.relay.SubmitBlock(&blockSubmitReq, vd)
250254
if err != nil {
251255
log.Error("could not submit bellatrix block", "err", err, "#commitedBundles", len(commitedBundles))
@@ -258,7 +262,9 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
258262
return nil
259263
}
260264

261-
func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time, commitedBundles, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
265+
func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, ordersClosedAt, sealedAt time.Time,
266+
commitedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
267+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
262268
executableData := engine.BlockToExecutableData(block, blockValue)
263269
payload, err := executableDataToCapellaExecutionPayload(executableData.ExecutionPayload)
264270
if err != nil {
@@ -308,7 +314,7 @@ func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, or
308314
log.Error("could not validate block for capella", "err", err)
309315
}
310316
} else {
311-
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &boostBidTrace)
317+
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &boostBidTrace)
312318
err = b.relay.SubmitBlockCapella(&blockSubmitReq, vd)
313319
if err != nil {
314320
log.Error("could not submit capella block", "err", err, "#commitedBundles", len(commitedBundles))
@@ -375,6 +381,7 @@ type blockQueueEntry struct {
375381
sealedAt time.Time
376382
commitedBundles []types.SimulatedBundle
377383
allBundles []types.SimulatedBundle
384+
usedSbundles []types.UsedSBundle
378385
}
379386

380387
func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) {
@@ -401,7 +408,8 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
401408
submitBestBlock := func() {
402409
queueMu.Lock()
403410
if queueBestEntry.block.Hash() != queueLastSubmittedHash {
404-
err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.blockValue, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt, queueBestEntry.commitedBundles, queueBestEntry.allBundles, proposerPubkey, vd, attrs)
411+
err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.blockValue, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt,
412+
queueBestEntry.commitedBundles, queueBestEntry.allBundles, queueBestEntry.usedSbundles, proposerPubkey, vd, attrs)
405413

406414
if err != nil {
407415
log.Error("could not run sealed block hook", "err", err)
@@ -422,7 +430,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
422430

423431
// Populates queue with submissions that increase block profit
424432
blockHook := func(block *types.Block, blockValue *big.Int, ordersCloseTime time.Time,
425-
committedBundles, allBundles []types.SimulatedBundle,
433+
committedBundles, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
426434
) {
427435
if ctx.Err() != nil {
428436
return
@@ -440,6 +448,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
440448
sealedAt: sealedAt,
441449
commitedBundles: committedBundles,
442450
allBundles: allBundles,
451+
usedSbundles: usedSbundles,
443452
}
444453

445454
select {

builder/eth_service.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ type testEthereumService struct {
2828
testBlockValue *big.Int
2929
testBundlesMerged []types.SimulatedBundle
3030
testAllBundles []types.SimulatedBundle
31+
testUsedSbundles []types.UsedSBundle
3132
}
3233

3334
func (t *testEthereumService) BuildBlock(attrs *types.BuilderPayloadAttributes, sealedBlockCallback miner.BlockHookFn) error {
34-
sealedBlockCallback(t.testBlock, t.testBlockValue, time.Now(), t.testBundlesMerged, t.testAllBundles)
35+
sealedBlockCallback(t.testBlock, t.testBlockValue, time.Now(), t.testBundlesMerged, t.testAllBundles, t.testUsedSbundles)
3536
return nil
3637
}
3738

builder/eth_service_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestBuildBlock(t *testing.T) {
9494
service := NewEthereumService(ethservice)
9595
service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11})
9696

97-
err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, blockValue *big.Int, _ time.Time, _, _ []types.SimulatedBundle) {
97+
err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, blockValue *big.Int, _ time.Time, _, _ []types.SimulatedBundle, _ []types.UsedSBundle) {
9898
executableData := engine.BlockToExecutableData(block, blockValue)
9999
require.Equal(t, common.Address{0x05, 0x11}, executableData.ExecutionPayload.FeeRecipient)
100100
require.Equal(t, common.Hash{0x05, 0x10}, executableData.ExecutionPayload.Random)

common/big.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ var (
2525
Big3 = big.NewInt(3)
2626
Big0 = big.NewInt(0)
2727
Big32 = big.NewInt(32)
28+
Big100 = big.NewInt(100)
2829
Big256 = big.NewInt(256)
2930
Big257 = big.NewInt(257)
3031
)
32+
33+
func PercentOf(val *big.Int, percent int) *big.Int {
34+
res := new(big.Int).Mul(val, big.NewInt(int64(percent)))
35+
return new(big.Int).Div(res, Big100)
36+
}

core/sbundle_sim.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package core
2+
3+
import (
4+
"errors"
5+
"math/big"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/ethereum/go-ethereum/core/state"
9+
"github.com/ethereum/go-ethereum/core/types"
10+
"github.com/ethereum/go-ethereum/core/vm"
11+
"github.com/ethereum/go-ethereum/params"
12+
)
13+
14+
var (
15+
ErrInvalidInclusion = errors.New("invalid inclusion")
16+
17+
ErrTxFailed = errors.New("tx failed")
18+
ErrNegativeProfit = errors.New("negative profit")
19+
ErrInvalidBundle = errors.New("invalid bundle")
20+
21+
SbundlePayoutMaxCostInt uint64 = 30_000
22+
SbundlePayoutMaxCost = big.NewInt(30_000)
23+
)
24+
25+
type SimBundleResult struct {
26+
TotalProfit *big.Int
27+
RefundableValue *big.Int
28+
GasUsed uint64
29+
MevGasPrice *big.Int
30+
BodyLogs []SimBundleBodyLogs
31+
}
32+
33+
type SimBundleBodyLogs struct {
34+
TxLogs []*types.Log `json:"txLogs,omitempty"`
35+
BundleLogs []SimBundleBodyLogs `json:"bundleLogs,omitempty"`
36+
}
37+
38+
func NewSimBundleResult() SimBundleResult {
39+
return SimBundleResult{
40+
TotalProfit: big.NewInt(0),
41+
RefundableValue: big.NewInt(0),
42+
GasUsed: 0,
43+
MevGasPrice: big.NewInt(0),
44+
BodyLogs: nil,
45+
}
46+
}
47+
48+
func SimBundle(chainConfig *params.ChainConfig, chain *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, b *types.SBundle, logs bool) (SimBundleResult, error) {
49+
res := NewSimBundleResult()
50+
51+
currBlock := header.Number.Uint64()
52+
if currBlock < b.Inclusion.BlockNumber || currBlock > b.Inclusion.MaxBlockNumber {
53+
return res, ErrInvalidInclusion
54+
}
55+
56+
// extract constraints into convenient format
57+
refundIdx := make([]bool, len(b.Body))
58+
refundPercents := make([]int, len(b.Body))
59+
for _, el := range b.Validity.Refund {
60+
refundIdx[el.BodyIdx] = true
61+
refundPercents[el.BodyIdx] = el.Percent
62+
}
63+
64+
var (
65+
coinbaseDelta = new(big.Int)
66+
coinbaseBefore *big.Int
67+
)
68+
for i, el := range b.Body {
69+
coinbaseDelta.Set(common.Big0)
70+
coinbaseBefore = statedb.GetBalance(header.Coinbase)
71+
72+
if el.Tx != nil {
73+
vmconfig := vm.Config{}
74+
receipt, err := ApplyTransaction(chainConfig, chain, &header.Coinbase, gp, statedb, header, el.Tx, &header.GasUsed, vmconfig, nil)
75+
if err != nil {
76+
return res, err
77+
}
78+
if receipt.Status != types.ReceiptStatusSuccessful && !el.CanRevert {
79+
return res, ErrTxFailed
80+
}
81+
res.GasUsed += receipt.GasUsed
82+
if logs {
83+
res.BodyLogs = append(res.BodyLogs, SimBundleBodyLogs{TxLogs: receipt.Logs})
84+
}
85+
} else if el.Bundle != nil {
86+
innerRes, err := SimBundle(chainConfig, chain, gp, statedb, header, el.Bundle, logs)
87+
if err != nil {
88+
return res, err
89+
}
90+
res.GasUsed += innerRes.GasUsed
91+
if logs {
92+
res.BodyLogs = append(res.BodyLogs, SimBundleBodyLogs{BundleLogs: innerRes.BodyLogs})
93+
}
94+
} else {
95+
return res, ErrInvalidBundle
96+
}
97+
98+
coinbaseDelta.Set(statedb.GetBalance(header.Coinbase))
99+
coinbaseDelta.Sub(coinbaseDelta, coinbaseBefore)
100+
101+
res.TotalProfit.Add(res.TotalProfit, coinbaseDelta)
102+
if !refundIdx[i] {
103+
res.RefundableValue.Add(res.RefundableValue, coinbaseDelta)
104+
}
105+
}
106+
107+
// estimate payout value and subtract from total profit
108+
signer := types.MakeSigner(chainConfig, header.Number)
109+
for i, el := range refundPercents {
110+
if !refundIdx[i] {
111+
continue
112+
}
113+
// we pay tx cost out of the refundable value
114+
115+
// cost
116+
refundConfig, err := types.GetRefundConfig(&b.Body[i], signer)
117+
if err != nil {
118+
return res, err
119+
}
120+
payoutTxFee := new(big.Int).Mul(header.BaseFee, SbundlePayoutMaxCost)
121+
payoutTxFee.Mul(payoutTxFee, new(big.Int).SetInt64(int64(len(refundConfig))))
122+
res.GasUsed += SbundlePayoutMaxCost.Uint64() * uint64(len(refundConfig))
123+
124+
// allocated refundable value
125+
payoutValue := common.PercentOf(res.RefundableValue, el)
126+
127+
if payoutTxFee.Cmp(payoutValue) > 0 {
128+
return res, ErrNegativeProfit
129+
}
130+
131+
res.TotalProfit.Sub(res.TotalProfit, payoutValue)
132+
}
133+
134+
if res.TotalProfit.Sign() < 0 {
135+
res.TotalProfit.Set(common.Big0)
136+
return res, ErrNegativeProfit
137+
}
138+
res.MevGasPrice.Div(res.TotalProfit, new(big.Int).SetUint64(res.GasUsed))
139+
return res, nil
140+
}

0 commit comments

Comments
 (0)