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

Commit a491310

Browse files
authored
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 23d6ba4 commit a491310

31 files changed

+1231
-75
lines changed

builder/builder.go

+19-9
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,15 @@ func (b *Builder) Stop() error {
136136
return nil
137137
}
138138

139-
func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
139+
func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
140+
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
141+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
140142
if b.eth.Config().IsShanghai(block.Time()) {
141-
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
143+
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
142144
return err
143145
}
144146
} else {
145-
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
147+
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
146148
return err
147149
}
148150
}
@@ -152,7 +154,9 @@ func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersC
152154
return nil
153155
}
154156

155-
func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
157+
func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
158+
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
159+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
156160
executableData := engine.BlockToExecutableData(block, blockValue)
157161
payload, err := executableDataToExecutionPayload(executableData.ExecutionPayload)
158162
if err != nil {
@@ -197,7 +201,7 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
197201
log.Error("could not validate bellatrix block", "err", err)
198202
}
199203
} else {
200-
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &blockBidMsg)
204+
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &blockBidMsg)
201205
err = b.relay.SubmitBlock(&blockSubmitReq, vd)
202206
if err != nil {
203207
log.Error("could not submit bellatrix block", "err", err, "#commitedBundles", len(commitedBundles))
@@ -210,7 +214,9 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
210214
return nil
211215
}
212216

213-
func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
217+
func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
218+
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
219+
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
214220
executableData := engine.BlockToExecutableData(block, blockValue)
215221
payload, err := executableDataToCapellaExecutionPayload(executableData.ExecutionPayload)
216222
if err != nil {
@@ -260,7 +266,7 @@ func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, or
260266
log.Error("could not validate block for capella", "err", err)
261267
}
262268
} else {
263-
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &boostBidTrace)
269+
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &boostBidTrace)
264270
err = b.relay.SubmitBlockCapella(&blockSubmitReq, vd)
265271
if err != nil {
266272
log.Error("could not submit capella block", "err", err, "#commitedBundles", len(commitedBundles))
@@ -330,6 +336,7 @@ type blockQueueEntry struct {
330336
sealedAt time.Time
331337
commitedBundles []types.SimulatedBundle
332338
allBundles []types.SimulatedBundle
339+
usedSbundles []types.UsedSBundle
333340
}
334341

335342
func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) {
@@ -356,7 +363,8 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
356363
submitBestBlock := func() {
357364
queueMu.Lock()
358365
if queueBestEntry.block.Hash() != queueLastSubmittedHash {
359-
err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.blockValue, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt, queueBestEntry.commitedBundles, queueBestEntry.allBundles, proposerPubkey, vd, attrs)
366+
err := b.onSealedBlock(queueBestEntry.block, queueBestEntry.blockValue, queueBestEntry.ordersCloseTime, queueBestEntry.sealedAt,
367+
queueBestEntry.commitedBundles, queueBestEntry.allBundles, queueBestEntry.usedSbundles, proposerPubkey, vd, attrs)
360368

361369
if err != nil {
362370
log.Error("could not run sealed block hook", "err", err)
@@ -371,7 +379,8 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
371379
go runResubmitLoop(ctx, b.limiter, queueSignal, submitBestBlock)
372380

373381
// Populates queue with submissions that increase block profit
374-
blockHook := func(block *types.Block, blockValue *big.Int, ordersCloseTime time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle) {
382+
blockHook := func(block *types.Block, blockValue *big.Int, ordersCloseTime time.Time,
383+
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle) {
375384
if ctx.Err() != nil {
376385
return
377386
}
@@ -388,6 +397,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
388397
sealedAt: sealedAt,
389398
commitedBundles: commitedBundles,
390399
allBundles: allBundles,
400+
usedSbundles: usedSbundles,
391401
}
392402

393403
select {

builder/eth_service.go

+2-1
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

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

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

common/big.go

+6
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

+140
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)