mirror of https://github.com/ethereum/go-ethereum
miner, eth: implement recommit mechanism for payload building (#25836)
* miner, eth: implement recommit for payload building * miner: address comments from mariuspull/26095/head
parent
2415911f53
commit
a2a144c593
@ -0,0 +1,168 @@ |
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package miner |
||||
|
||||
import ( |
||||
"math/big" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/beacon" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// BuildPayloadArgs contains the provided parameters for building payload.
|
||||
// Check engine-api specification for more details.
|
||||
// https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1
|
||||
type BuildPayloadArgs struct { |
||||
Parent common.Hash // The parent block to build payload on top
|
||||
Timestamp uint64 // The provided timestamp of generated payload
|
||||
FeeRecipient common.Address // The provided recipient address for collecting transaction fee
|
||||
Random common.Hash // The provided randomness value
|
||||
} |
||||
|
||||
// Payload wraps the built payload(block waiting for sealing). According to the
|
||||
// engine-api specification, EL should build the initial version of the payload
|
||||
// which has an empty transaction set and then keep update it in order to maximize
|
||||
// the revenue. Therefore, the empty-block here is always available and full-block
|
||||
// will be set/updated afterwards.
|
||||
type Payload struct { |
||||
empty *types.Block |
||||
full *types.Block |
||||
fullFees *big.Int |
||||
stop chan struct{} |
||||
lock *sync.Mutex |
||||
cond *sync.Cond |
||||
} |
||||
|
||||
// newPayload initializes the payload object.
|
||||
func newPayload(empty *types.Block) *Payload { |
||||
lock := new(sync.Mutex) |
||||
return &Payload{ |
||||
empty: empty, |
||||
stop: make(chan struct{}), |
||||
lock: lock, |
||||
cond: sync.NewCond(lock), |
||||
} |
||||
} |
||||
|
||||
// update updates the full-block with latest built version.
|
||||
func (payload *Payload) update(block *types.Block, fees *big.Int) { |
||||
payload.lock.Lock() |
||||
defer payload.lock.Unlock() |
||||
|
||||
select { |
||||
case <-payload.stop: |
||||
return // reject stale update
|
||||
default: |
||||
} |
||||
// Ensure the newly provided full block has a higher transaction fee.
|
||||
// In post-merge stage, there is no uncle reward anymore and transaction
|
||||
// fee(apart from the mev revenue) is the only indicator for comparison.
|
||||
if payload.full == nil || fees.Cmp(payload.fullFees) > 0 { |
||||
payload.full = block |
||||
payload.fullFees = fees |
||||
} |
||||
payload.cond.Broadcast() // fire signal for notifying full block
|
||||
} |
||||
|
||||
// Resolve returns the latest built payload and also terminates the background
|
||||
// thread for updating payload. It's safe to be called multiple times.
|
||||
func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { |
||||
payload.lock.Lock() |
||||
defer payload.lock.Unlock() |
||||
|
||||
select { |
||||
case <-payload.stop: |
||||
default: |
||||
close(payload.stop) |
||||
} |
||||
if payload.full != nil { |
||||
return beacon.BlockToExecutableData(payload.full) |
||||
} |
||||
return beacon.BlockToExecutableData(payload.empty) |
||||
} |
||||
|
||||
// ResolveEmpty is basically identical to Resolve, but it expects empty block only.
|
||||
// It's only used in tests.
|
||||
func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV1 { |
||||
payload.lock.Lock() |
||||
defer payload.lock.Unlock() |
||||
|
||||
return beacon.BlockToExecutableData(payload.empty) |
||||
} |
||||
|
||||
// ResolveFull is basically identical to Resolve, but it expects full block only.
|
||||
// It's only used in tests.
|
||||
func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 { |
||||
payload.lock.Lock() |
||||
defer payload.lock.Unlock() |
||||
|
||||
if payload.full == nil { |
||||
select { |
||||
case <-payload.stop: |
||||
return nil |
||||
default: |
||||
} |
||||
payload.cond.Wait() |
||||
} |
||||
return beacon.BlockToExecutableData(payload.full) |
||||
} |
||||
|
||||
// buildPayload builds the payload according to the provided parameters.
|
||||
func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { |
||||
// Build the initial version with no transaction included. It should be fast
|
||||
// enough to run. The empty payload can at least make sure there is something
|
||||
// to deliver for not missing slot.
|
||||
empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, true) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// Construct a payload object for return.
|
||||
payload := newPayload(empty) |
||||
|
||||
// Spin up a routine for updating the payload in background. This strategy
|
||||
// can maximum the revenue for including transactions with highest fee.
|
||||
go func() { |
||||
// Setup the timer for re-building the payload. The initial clock is kept
|
||||
// for triggering process immediately.
|
||||
timer := time.NewTimer(0) |
||||
defer timer.Stop() |
||||
|
||||
// Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in
|
||||
// the Mainnet configuration) have passed since the point in time identified
|
||||
// by the timestamp parameter.
|
||||
endTimer := time.NewTimer(time.Second * 12) |
||||
|
||||
for { |
||||
select { |
||||
case <-timer.C: |
||||
block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, false) |
||||
if err == nil { |
||||
payload.update(block, fees) |
||||
} |
||||
timer.Reset(w.recommit) |
||||
case <-payload.stop: |
||||
return |
||||
case <-endTimer.C: |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
return payload, nil |
||||
} |
@ -0,0 +1,80 @@ |
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package miner |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/consensus/ethash" |
||||
"github.com/ethereum/go-ethereum/core/beacon" |
||||
"github.com/ethereum/go-ethereum/core/rawdb" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
func TestBuildPayload(t *testing.T) { |
||||
var ( |
||||
db = rawdb.NewMemoryDatabase() |
||||
recipient = common.HexToAddress("0xdeadbeef") |
||||
) |
||||
w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0) |
||||
defer w.close() |
||||
|
||||
timestamp := uint64(time.Now().Unix()) |
||||
args := &BuildPayloadArgs{ |
||||
Parent: b.chain.CurrentBlock().Hash(), |
||||
Timestamp: timestamp, |
||||
Random: common.Hash{}, |
||||
FeeRecipient: recipient, |
||||
} |
||||
payload, err := w.buildPayload(args) |
||||
if err != nil { |
||||
t.Fatalf("Failed to build payload %v", err) |
||||
} |
||||
verify := func(data *beacon.ExecutableDataV1, txs int) { |
||||
if data.ParentHash != b.chain.CurrentBlock().Hash() { |
||||
t.Fatal("Unexpect parent hash") |
||||
} |
||||
if data.Random != (common.Hash{}) { |
||||
t.Fatal("Unexpect random value") |
||||
} |
||||
if data.Timestamp != timestamp { |
||||
t.Fatal("Unexpect timestamp") |
||||
} |
||||
if data.FeeRecipient != recipient { |
||||
t.Fatal("Unexpect fee recipient") |
||||
} |
||||
if len(data.Transactions) != txs { |
||||
t.Fatal("Unexpect transaction set") |
||||
} |
||||
} |
||||
empty := payload.ResolveEmpty() |
||||
verify(empty, 0) |
||||
|
||||
full := payload.ResolveFull() |
||||
verify(full, len(pendingTxs)) |
||||
|
||||
// Ensure resolve can be called multiple times and the
|
||||
// result should be unchanged
|
||||
dataOne := payload.Resolve() |
||||
dataTwo := payload.Resolve() |
||||
if !reflect.DeepEqual(dataOne, dataTwo) { |
||||
t.Fatal("Unexpected payload data") |
||||
} |
||||
} |
Loading…
Reference in new issue