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

Commit

Permalink
Mev share sbundle (#60)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dvush authored Apr 18, 2023
1 parent 23d6ba4 commit a491310
Show file tree
Hide file tree
Showing 31 changed files with 1,231 additions and 75 deletions.
28 changes: 19 additions & 9 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,15 @@ func (b *Builder) Stop() error {
return nil
}

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 {
func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
if b.eth.Config().IsShanghai(block.Time()) {
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
if err := b.submitCapellaBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
return err
}
} else {
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, proposerPubkey, vd, attrs); err != nil {
if err := b.submitBellatrixBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, proposerPubkey, vd, attrs); err != nil {
return err
}
}
Expand All @@ -152,7 +154,9 @@ func (b *Builder) onSealedBlock(block *types.Block, blockValue *big.Int, ordersC
return nil
}

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 {
func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
executableData := engine.BlockToExecutableData(block, blockValue)
payload, err := executableDataToExecutionPayload(executableData.ExecutionPayload)
if err != nil {
Expand Down Expand Up @@ -197,7 +201,7 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
log.Error("could not validate bellatrix block", "err", err)
}
} else {
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &blockBidMsg)
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &blockBidMsg)
err = b.relay.SubmitBlock(&blockSubmitReq, vd)
if err != nil {
log.Error("could not submit bellatrix block", "err", err, "#commitedBundles", len(commitedBundles))
Expand All @@ -210,7 +214,9 @@ func (b *Builder) submitBellatrixBlock(block *types.Block, blockValue *big.Int,
return nil
}

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 {
func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, ordersClosedAt time.Time, sealedAt time.Time,
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle,
proposerPubkey boostTypes.PublicKey, vd ValidatorData, attrs *types.BuilderPayloadAttributes) error {
executableData := engine.BlockToExecutableData(block, blockValue)
payload, err := executableDataToCapellaExecutionPayload(executableData.ExecutionPayload)
if err != nil {
Expand Down Expand Up @@ -260,7 +266,7 @@ func (b *Builder) submitCapellaBlock(block *types.Block, blockValue *big.Int, or
log.Error("could not validate block for capella", "err", err)
}
} else {
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, &boostBidTrace)
go b.ds.ConsumeBuiltBlock(block, blockValue, ordersClosedAt, sealedAt, commitedBundles, allBundles, usedSbundles, &boostBidTrace)
err = b.relay.SubmitBlockCapella(&blockSubmitReq, vd)
if err != nil {
log.Error("could not submit capella block", "err", err, "#commitedBundles", len(commitedBundles))
Expand Down Expand Up @@ -330,6 +336,7 @@ type blockQueueEntry struct {
sealedAt time.Time
commitedBundles []types.SimulatedBundle
allBundles []types.SimulatedBundle
usedSbundles []types.UsedSBundle
}

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

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

// Populates queue with submissions that increase block profit
blockHook := func(block *types.Block, blockValue *big.Int, ordersCloseTime time.Time, commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle) {
blockHook := func(block *types.Block, blockValue *big.Int, ordersCloseTime time.Time,
commitedBundles []types.SimulatedBundle, allBundles []types.SimulatedBundle, usedSbundles []types.UsedSBundle) {
if ctx.Err() != nil {
return
}
Expand All @@ -388,6 +397,7 @@ func (b *Builder) runBuildingJob(slotCtx context.Context, proposerPubkey boostTy
sealedAt: sealedAt,
commitedBundles: commitedBundles,
allBundles: allBundles,
usedSbundles: usedSbundles,
}

select {
Expand Down
3 changes: 2 additions & 1 deletion builder/eth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ type testEthereumService struct {
testBlockValue *big.Int
testBundlesMerged []types.SimulatedBundle
testAllBundles []types.SimulatedBundle
testUsedSbundles []types.UsedSBundle
}

func (t *testEthereumService) BuildBlock(attrs *types.BuilderPayloadAttributes, sealedBlockCallback miner.BlockHookFn) error {
sealedBlockCallback(t.testBlock, t.testBlockValue, time.Now(), t.testBundlesMerged, t.testAllBundles)
sealedBlockCallback(t.testBlock, t.testBlockValue, time.Now(), t.testBundlesMerged, t.testAllBundles, t.testUsedSbundles)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion builder/eth_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestBuildBlock(t *testing.T) {
service := NewEthereumService(ethservice)
service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11})

err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, blockValue *big.Int, _ time.Time, _ []types.SimulatedBundle, _ []types.SimulatedBundle) {
err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, blockValue *big.Int, _ time.Time, _ []types.SimulatedBundle, _ []types.SimulatedBundle, _ []types.UsedSBundle) {
executableData := engine.BlockToExecutableData(block, blockValue)
require.Equal(t, common.Address{0x05, 0x11}, executableData.ExecutionPayload.FeeRecipient)
require.Equal(t, common.Hash{0x05, 0x10}, executableData.ExecutionPayload.Random)
Expand Down
6 changes: 6 additions & 0 deletions common/big.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ var (
Big3 = big.NewInt(3)
Big0 = big.NewInt(0)
Big32 = big.NewInt(32)
Big100 = big.NewInt(100)
Big256 = big.NewInt(256)
Big257 = big.NewInt(257)
)

func PercentOf(val *big.Int, percent int) *big.Int {
res := new(big.Int).Mul(val, big.NewInt(int64(percent)))
return new(big.Int).Div(res, Big100)
}
140 changes: 140 additions & 0 deletions core/sbundle_sim.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package core

import (
"errors"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)

var (
ErrInvalidInclusion = errors.New("invalid inclusion")

ErrTxFailed = errors.New("tx failed")
ErrNegativeProfit = errors.New("negative profit")
ErrInvalidBundle = errors.New("invalid bundle")

SbundlePayoutMaxCostInt uint64 = 30_000
SbundlePayoutMaxCost = big.NewInt(30_000)
)

type SimBundleResult struct {
TotalProfit *big.Int
RefundableValue *big.Int
GasUsed uint64
MevGasPrice *big.Int
BodyLogs []SimBundleBodyLogs
}

type SimBundleBodyLogs struct {
TxLogs []*types.Log `json:"txLogs,omitempty"`
BundleLogs []SimBundleBodyLogs `json:"bundleLogs,omitempty"`
}

func NewSimBundleResult() SimBundleResult {
return SimBundleResult{
TotalProfit: big.NewInt(0),
RefundableValue: big.NewInt(0),
GasUsed: 0,
MevGasPrice: big.NewInt(0),
BodyLogs: nil,
}
}

func SimBundle(chainConfig *params.ChainConfig, chain *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, b *types.SBundle, logs bool) (SimBundleResult, error) {
res := NewSimBundleResult()

currBlock := header.Number.Uint64()
if currBlock < b.Inclusion.BlockNumber || currBlock > b.Inclusion.MaxBlockNumber {
return res, ErrInvalidInclusion
}

// extract constraints into convenient format
refundIdx := make([]bool, len(b.Body))
refundPercents := make([]int, len(b.Body))
for _, el := range b.Validity.Refund {
refundIdx[el.BodyIdx] = true
refundPercents[el.BodyIdx] = el.Percent
}

var (
coinbaseDelta = new(big.Int)
coinbaseBefore *big.Int
)
for i, el := range b.Body {
coinbaseDelta.Set(common.Big0)
coinbaseBefore = statedb.GetBalance(header.Coinbase)

if el.Tx != nil {
vmconfig := vm.Config{}
receipt, err := ApplyTransaction(chainConfig, chain, &header.Coinbase, gp, statedb, header, el.Tx, &header.GasUsed, vmconfig, nil)
if err != nil {
return res, err
}
if receipt.Status != types.ReceiptStatusSuccessful && !el.CanRevert {
return res, ErrTxFailed
}
res.GasUsed += receipt.GasUsed
if logs {
res.BodyLogs = append(res.BodyLogs, SimBundleBodyLogs{TxLogs: receipt.Logs})
}
} else if el.Bundle != nil {
innerRes, err := SimBundle(chainConfig, chain, gp, statedb, header, el.Bundle, logs)
if err != nil {
return res, err
}
res.GasUsed += innerRes.GasUsed
if logs {
res.BodyLogs = append(res.BodyLogs, SimBundleBodyLogs{BundleLogs: innerRes.BodyLogs})
}
} else {
return res, ErrInvalidBundle
}

coinbaseDelta.Set(statedb.GetBalance(header.Coinbase))
coinbaseDelta.Sub(coinbaseDelta, coinbaseBefore)

res.TotalProfit.Add(res.TotalProfit, coinbaseDelta)
if !refundIdx[i] {
res.RefundableValue.Add(res.RefundableValue, coinbaseDelta)
}
}

// estimate payout value and subtract from total profit
signer := types.MakeSigner(chainConfig, header.Number)
for i, el := range refundPercents {
if !refundIdx[i] {
continue
}
// we pay tx cost out of the refundable value

// cost
refundConfig, err := types.GetRefundConfig(&b.Body[i], signer)
if err != nil {
return res, err
}
payoutTxFee := new(big.Int).Mul(header.BaseFee, SbundlePayoutMaxCost)
payoutTxFee.Mul(payoutTxFee, new(big.Int).SetInt64(int64(len(refundConfig))))
res.GasUsed += SbundlePayoutMaxCost.Uint64() * uint64(len(refundConfig))

// allocated refundable value
payoutValue := common.PercentOf(res.RefundableValue, el)

if payoutTxFee.Cmp(payoutValue) > 0 {
return res, ErrNegativeProfit
}

res.TotalProfit.Sub(res.TotalProfit, payoutValue)
}

if res.TotalProfit.Sign() < 0 {
res.TotalProfit.Set(common.Big0)
return res, ErrNegativeProfit
}
res.MevGasPrice.Div(res.TotalProfit, new(big.Int).SetUint64(res.GasUsed))
return res, nil
}
Loading

0 comments on commit a491310

Please sign in to comment.