Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance bid comparison and reply bidding results && detail logs #2538

Merged
merged 14 commits into from
Jul 11, 2024
Merged
169 changes: 116 additions & 53 deletions miner/bid_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"net"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -68,6 +69,12 @@ type simBidReq struct {
interruptCh chan int32
}

// newBidPackage is the warp of a new bid and a feedback channel
type newBidPackage struct {
bid *types.Bid
feedback chan error
}

// bidSimulator is in charge of receiving bid from builders, reporting issue to builders.
// And take care of bid simulation, rewards computing, best bid maintaining.
type bidSimulator struct {
Expand Down Expand Up @@ -95,7 +102,7 @@ type bidSimulator struct {

// channels
simBidCh chan *simBidReq
newBidCh chan *types.Bid
newBidCh chan newBidPackage

pendingMu sync.RWMutex
pending map[uint64]map[common.Address]map[common.Hash]struct{} // blockNumber -> builder -> bidHash -> struct{}
Expand Down Expand Up @@ -128,7 +135,7 @@ func newBidSimulator(
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
builders: make(map[common.Address]*builderclient.Client),
simBidCh: make(chan *simBidReq),
newBidCh: make(chan *types.Bid, 100),
newBidCh: make(chan newBidPackage, 100),
pending: make(map[uint64]map[common.Address]map[common.Hash]struct{}),
bestBid: make(map[common.Hash]*BidRuntime),
simulatingBid: make(map[common.Hash]*BidRuntime),
Expand Down Expand Up @@ -327,67 +334,58 @@ func (b *bidSimulator) newBidLoop() {
}
}

genDiscardedReply := func(betterBid *BidRuntime) error {
return fmt.Errorf("bid is discarded, current bestBid is [blockReward: %s, validatorReward: %s]", betterBid.expectedBlockReward, betterBid.expectedValidatorReward)
}

for {
select {
case newBid := <-b.newBidCh:
if !b.isRunning() {
continue
}

// check the block reward and validator reward of the newBid
expectedBlockReward := newBid.GasFee
expectedValidatorReward := new(big.Int).Mul(expectedBlockReward, big.NewInt(int64(b.config.ValidatorCommission)))
expectedValidatorReward.Div(expectedValidatorReward, big.NewInt(10000))
expectedValidatorReward.Sub(expectedValidatorReward, newBid.BuilderFee)

if expectedValidatorReward.Cmp(big.NewInt(0)) < 0 {
// damage self profit, ignore
log.Debug("BidSimulator: invalid bid, validator reward is less than 0, ignore",
"builder", newBid.Builder, "bidHash", newBid.Hash().Hex())
bidRuntime, err := newBidRuntime(newBid.bid, b.config.ValidatorCommission)
if err != nil {
Jolly23 marked this conversation as resolved.
Show resolved Hide resolved
if newBid.feedback != nil {
newBid.feedback <- err
Jolly23 marked this conversation as resolved.
Show resolved Hide resolved
}
continue
}

bidRuntime := &BidRuntime{
bid: newBid,
expectedBlockReward: expectedBlockReward,
expectedValidatorReward: expectedValidatorReward,
packedBlockReward: big.NewInt(0),
packedValidatorReward: big.NewInt(0),
finished: make(chan struct{}),
}

simulatingBid := b.GetSimulatingBid(newBid.ParentHash)
// simulatingBid is nil means there is no bid in simulation
if simulatingBid == nil {
// bestBid is nil means bid is the first bid
bestBid := b.GetBestBid(newBid.ParentHash)
if bestBid == nil {
var replyErr error
// simulatingBid will be nil if there is no bid in simulation, compare with the bestBid instead
if simulatingBid := b.GetSimulatingBid(newBid.bid.ParentHash); simulatingBid != nil {
// simulatingBid always better than bestBid, so only compare with simulatingBid if a simulatingBid exists
if bidRuntime.isExpectedBetterThan(simulatingBid) {
commit(commitInterruptBetterBid, bidRuntime)
continue
} else {
replyErr = genDiscardedReply(simulatingBid)
}

// if bestBid is not nil, check if newBid is better than bestBid
if bidRuntime.expectedBlockReward.Cmp(bestBid.expectedBlockReward) >= 0 &&
bidRuntime.expectedValidatorReward.Cmp(bestBid.expectedValidatorReward) >= 0 {
// if both reward are better than last simulating newBid, commit for simulation
} else {
// bestBid is nil means the bid is the first bid, otherwise the bid should compare with the bestBid
if bestBid := b.GetBestBid(newBid.bid.ParentHash); bestBid == nil ||
bidRuntime.isExpectedBetterThan(bestBid) {
commit(commitInterruptBetterBid, bidRuntime)
continue
} else {
replyErr = genDiscardedReply(bestBid)
}

log.Debug("BidSimulator: lower reward, ignore",
"builder", bidRuntime.bid.Builder, "bidHash", newBid.Hash().Hex())
continue
}

// simulatingBid must be better than bestBid, if newBid is better than simulatingBid, commit for simulation
if bidRuntime.expectedBlockReward.Cmp(simulatingBid.expectedBlockReward) >= 0 &&
bidRuntime.expectedValidatorReward.Cmp(simulatingBid.expectedValidatorReward) >= 0 {
// if both reward are better than last simulating newBid, commit for simulation
commit(commitInterruptBetterBid, bidRuntime)
continue
if newBid.feedback != nil {
newBid.feedback <- replyErr

log.Info("[BID ARRIVED]",
"block", newBid.bid.BlockNumber,
"builder", newBid.bid.Builder,
"accepted", replyErr == nil,
"blockReward", weiToEtherStringF6(bidRuntime.expectedBlockReward),
"validatorReward", weiToEtherStringF6(bidRuntime.expectedValidatorReward),
"tx", len(newBid.bid.Txs),
"hash", newBid.bid.Hash().TerminalString(),
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is suggested to send a nil error back to newBid.feedback if no error happen, so that sendBid can return ASAP

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if no error happen, it's nil.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check this note?

}

log.Debug("BidSimulator: lower reward, ignore", "builder", newBid.Builder, "bidHash", newBid.Hash().Hex())
case <-b.exitCh:
return
}
Expand Down Expand Up @@ -442,10 +440,19 @@ func (b *bidSimulator) clearLoop() {
func (b *bidSimulator) sendBid(_ context.Context, bid *types.Bid) error {
timer := time.NewTimer(1 * time.Second)
defer timer.Stop()

replyCh := make(chan error, 1)

select {
case b.newBidCh <- bid:
case b.newBidCh <- newBidPackage{bid: bid, feedback: replyCh}:
b.AddPending(bid.BlockNumber, bid.Builder, bid.Hash())
return nil
case <-timer.C:
return types.ErrMevBusy
}

select {
case reply := <-replyCh:
return reply
Jolly23 marked this conversation as resolved.
Show resolved Hide resolved
case <-timer.C:
return types.ErrMevBusy
}
Expand Down Expand Up @@ -491,6 +498,8 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}

var (
startTS = time.Now()

blockNumber = bidRuntime.bid.BlockNumber
parentHash = bidRuntime.bid.ParentHash
builder = bidRuntime.bid.Builder
Expand Down Expand Up @@ -535,19 +544,20 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
if success {
bidRuntime.duration = time.Since(simStart)
bidSimTimer.UpdateSince(simStart)
metrics.GetOrRegisterCounter(fmt.Sprintf("bid/sim/count/%d", bidRuntime.bid.BlockNumber), nil).Inc(1)
irrun marked this conversation as resolved.
Show resolved Hide resolved

// only recommit self bid when newBidCh is empty
if len(b.newBidCh) > 0 {
return
}

select {
case b.newBidCh <- bidRuntime.bid:
case b.newBidCh <- newBidPackage{bid: bidRuntime.bid}:
log.Debug("BidSimulator: recommit", "builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
default:
}
}
}(time.Now())
}(startTS)

// prepareWork will configure header with a suitable time according to consensus
// prepareWork will start trie prefetching
Expand Down Expand Up @@ -633,7 +643,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
if b.config.GreedyMergeTx {
delay := b.engine.Delay(b.chain, bidRuntime.env.header, &b.delayLeftOver)
if delay != nil && *delay > 0 {
bidTxsSet := mapset.NewSet[common.Hash]()
bidTxsSet := mapset.NewThreadUnsafeSetWithSize[common.Hash](len(bidRuntime.bid.Txs))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the motivation to change the hash set type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for no concurrent operation on bidTxsSet

for _, tx := range bidRuntime.bid.Txs {
bidTxsSet.Add(tx.Hash())
}
Expand All @@ -658,13 +668,30 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}

bestBid := b.GetBestBid(parentHash)

if bestBid == nil {
log.Info("[BID RESULT]", "win", "true[first]", "builder", bidRuntime.bid.Builder, "hash", bidRuntime.bid.Hash().TerminalString())
b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime)
success = true
return
}

if bidRuntime.bid.Hash() != bestBid.bid.Hash() {
log.Info("[BID RESULT]",
"win", bidRuntime.packedBlockReward.Cmp(bestBid.packedBlockReward) >= 0,

"bidHash", bidRuntime.bid.Hash().TerminalString(),
"bestHash", bestBid.bid.Hash().TerminalString(),

"bidGasFee", weiToEtherStringF6(bidRuntime.packedBlockReward),
"bestGasFee", weiToEtherStringF6(bestBid.packedBlockReward),

"bidBlockTx", bidRuntime.env.tcount,
"bestBlockTx", bestBid.env.tcount,

"simElapsed", time.Since(startTS),
)
}

// this is the simplest strategy: best for all the delegators.
if bidRuntime.packedBlockReward.Cmp(bestBid.packedBlockReward) >= 0 {
b.SetBestBid(bidRuntime.bid.ParentHash, bidRuntime)
Expand All @@ -678,7 +705,7 @@ func (b *bidSimulator) simBid(interruptCh chan int32, bidRuntime *BidRuntime) {
}

select {
case b.newBidCh <- bestBid.bid:
case b.newBidCh <- newBidPackage{bid: bestBid.bid}:
log.Debug("BidSimulator: recommit last bid", "builder", bidRuntime.bid.Builder, "bidHash", bidRuntime.bid.Hash().Hex())
default:
}
Expand All @@ -698,7 +725,7 @@ func (b *bidSimulator) reportIssue(bidRuntime *BidRuntime, err error) {
})

if err != nil {
log.Error("BidSimulator: failed to report issue", "builder", bidRuntime.bid.Builder, "err", err)
log.Warn("BidSimulator: failed to report issue", "builder", bidRuntime.bid.Builder, "err", err)
}
}
}
Expand All @@ -718,11 +745,42 @@ type BidRuntime struct {
duration time.Duration
}

func newBidRuntime(newBid *types.Bid, validatorCommission uint64) (*BidRuntime, error) {
// check the block reward and validator reward of the newBid
expectedBlockReward := newBid.GasFee
expectedValidatorReward := new(big.Int).Mul(expectedBlockReward, big.NewInt(int64(validatorCommission)))
expectedValidatorReward.Div(expectedValidatorReward, big.NewInt(10000))
expectedValidatorReward.Sub(expectedValidatorReward, newBid.BuilderFee)

if expectedValidatorReward.Cmp(big.NewInt(0)) < 0 {
// damage self profit, ignore
log.Debug("BidSimulator: invalid bid, validator reward is less than 0, ignore",
"builder", newBid.Builder, "bidHash", newBid.Hash().Hex())
return nil, fmt.Errorf("validator reward is less than 0, value: %s, commissionConfig: %d", expectedValidatorReward, validatorCommission)
}

bidRuntime := &BidRuntime{
bid: newBid,
expectedBlockReward: expectedBlockReward,
expectedValidatorReward: expectedValidatorReward,
packedBlockReward: big.NewInt(0),
packedValidatorReward: big.NewInt(0),
finished: make(chan struct{}),
}

return bidRuntime, nil
}

func (r *BidRuntime) validReward() bool {
return r.packedBlockReward.Cmp(r.expectedBlockReward) >= 0 &&
r.packedValidatorReward.Cmp(r.expectedValidatorReward) >= 0
}

func (r *BidRuntime) isExpectedBetterThan(other *BidRuntime) bool {
return r.expectedBlockReward.Cmp(other.expectedBlockReward) >= 0 &&
r.expectedValidatorReward.Cmp(other.expectedValidatorReward) >= 0
}

// packReward calculates packedBlockReward and packedValidatorReward
func (r *BidRuntime) packReward(validatorCommission uint64) {
r.packedBlockReward = r.env.state.GetBalance(consensus.SystemAddress).ToBig()
Expand Down Expand Up @@ -778,3 +836,8 @@ func (r *BidRuntime) commitTransaction(chain *core.BlockChain, chainConfig *para

return nil
}

func weiToEtherStringF6(wei *big.Int) string {
f, _ := new(big.Float).Quo(new(big.Float).SetInt(wei), big.NewFloat(params.Ether)).Float64()
return strconv.FormatFloat(f, 'f', 6, 64)
}
8 changes: 7 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,13 @@ LOOP:
bestWork = bestBid.env
from = bestBid.bid.Builder

log.Debug("BidSimulator: bid win", "block", bestWork.header.Number.Uint64(), "bid", bestBid.bid.Hash())
log.Info("[BUILDER BLOCK]",
"block", bestWork.header.Number.Uint64(),
"builder", from,
"blockReward", weiToEtherStringF6(bestBid.packedBlockReward),
"validatorReward", weiToEtherStringF6(bestBid.packedValidatorReward),
"bid", bestBid.bid.Hash().TerminalString(),
)
}
}
}
Expand Down
Loading