Skip to content

Commit e3b9cd8

Browse files
rjl493456442shekhirin
authored andcommitted
miner, eth: implement recommit mechanism for payload building (ethereum#25836)
* miner, eth: implement recommit for payload building * miner: address comments from marius
1 parent 80d6f44 commit e3b9cd8

File tree

9 files changed

+338
-131
lines changed

9 files changed

+338
-131
lines changed

eth/catalyst/api.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/ethereum/go-ethereum/eth"
3535
"github.com/ethereum/go-ethereum/eth/downloader"
3636
"github.com/ethereum/go-ethereum/log"
37+
"github.com/ethereum/go-ethereum/miner"
3738
"github.com/ethereum/go-ethereum/node"
3839
"github.com/ethereum/go-ethereum/rpc"
3940
)
@@ -279,23 +280,21 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
279280
}
280281
// If payload generation was requested, create a new block to be potentially
281282
// sealed by the beacon client. The payload will be requested later, and we
282-
// might replace it arbitrarily many times in between.
283+
// will replace it arbitrarily many times in between.
283284
if payloadAttributes != nil {
284-
// Create an empty block first which can be used as a fallback
285-
empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
286-
if err != nil {
287-
log.Error("Failed to create empty sealing payload", "err", err)
288-
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
285+
args := &miner.BuildPayloadArgs{
286+
Parent: update.HeadBlockHash,
287+
Timestamp: payloadAttributes.Timestamp,
288+
FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
289+
Random: payloadAttributes.Random,
289290
}
290-
// Send a request to generate a full block in the background.
291-
// The result can be obtained via the returned channel.
292-
resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
291+
payload, err := api.eth.Miner().BuildPayload(args)
293292
if err != nil {
294-
log.Error("Failed to create async sealing payload", "err", err)
293+
log.Error("Failed to build payload", "err", err)
295294
return valid(nil), beacon.InvalidPayloadAttributes.With(err)
296295
}
297296
id := computePayloadId(update.HeadBlockHash, payloadAttributes)
298-
api.localBlocks.put(id, &payload{empty: empty, result: resCh})
297+
api.localBlocks.put(id, payload)
299298
return valid(&id), nil
300299
}
301300
return valid(nil), nil

eth/catalyst/api_test.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/ethereum/go-ethereum/eth"
3535
"github.com/ethereum/go-ethereum/eth/downloader"
3636
"github.com/ethereum/go-ethereum/eth/ethconfig"
37+
"github.com/ethereum/go-ethereum/miner"
3738
"github.com/ethereum/go-ethereum/node"
3839
"github.com/ethereum/go-ethereum/p2p"
3940
"github.com/ethereum/go-ethereum/params"
@@ -181,6 +182,8 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
181182
if err != nil {
182183
t.Fatalf("error preparing payload, err=%v", err)
183184
}
185+
// give the payload some time to be built
186+
time.Sleep(100 * time.Millisecond)
184187
payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
185188
execData, err := api.GetPayloadV1(payloadID)
186189
if err != nil {
@@ -586,12 +589,12 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
586589
if resp.PayloadStatus.Status != beacon.VALID {
587590
t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
588591
}
592+
// give the payload some time to be built
593+
time.Sleep(100 * time.Millisecond)
589594
payload, err := api.GetPayloadV1(*resp.PayloadID)
590595
if err != nil {
591596
t.Fatalf("can't get payload: %v", err)
592597
}
593-
// TODO(493456442, marius) this test can be flaky since we rely on a 100ms
594-
// allowance for block generation internally.
595598
if len(payload.Transactions) == 0 {
596599
t.Fatalf("payload should not be empty")
597600
}
@@ -618,11 +621,17 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
618621
}
619622

620623
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
621-
block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random, false)
624+
args := &miner.BuildPayloadArgs{
625+
Parent: parentHash,
626+
Timestamp: params.Timestamp,
627+
FeeRecipient: params.SuggestedFeeRecipient,
628+
Random: params.Random,
629+
}
630+
payload, err := api.eth.Miner().BuildPayload(args)
622631
if err != nil {
623632
return nil, err
624633
}
625-
return beacon.BlockToExecutableData(block), nil
634+
return payload.ResolveFull(), nil
626635
}
627636

628637
func TestEmptyBlocks(t *testing.T) {
@@ -854,16 +863,17 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
854863
}
855864

856865
// Test parent already post TTD in NewPayload
857-
params := beacon.PayloadAttributesV1{
858-
Timestamp: parent.Time() + 1,
859-
Random: crypto.Keccak256Hash([]byte{byte(1)}),
860-
SuggestedFeeRecipient: parent.Coinbase(),
866+
args := &miner.BuildPayloadArgs{
867+
Parent: parent.Hash(),
868+
Timestamp: parent.Time() + 1,
869+
Random: crypto.Keccak256Hash([]byte{byte(1)}),
870+
FeeRecipient: parent.Coinbase(),
861871
}
862-
empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, params.Random, true)
872+
payload, err := api.eth.Miner().BuildPayload(args)
863873
if err != nil {
864874
t.Fatalf("error preparing payload, err=%v", err)
865875
}
866-
data := *beacon.BlockToExecutableData(empty)
876+
data := *payload.Resolve()
867877
resp2, err := api.NewPayloadV1(data)
868878
if err != nil {
869879
t.Fatalf("error sending NewPayload, err=%v", err)

eth/catalyst/queue.go

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ package catalyst
1818

1919
import (
2020
"sync"
21-
"time"
2221

2322
"github.com/ethereum/go-ethereum/common"
2423
"github.com/ethereum/go-ethereum/core/beacon"
2524
"github.com/ethereum/go-ethereum/core/types"
25+
"github.com/ethereum/go-ethereum/miner"
2626
)
2727

2828
// maxTrackedPayloads is the maximum number of prepared payloads the execution
@@ -35,52 +35,11 @@ const maxTrackedPayloads = 10
3535
// latest one; but have a slight wiggle room for non-ideal conditions.
3636
const maxTrackedHeaders = 10
3737

38-
// payload wraps the miner's block production channel, allowing the mined block
39-
// to be retrieved later upon the GetPayload engine API call.
40-
type payload struct {
41-
lock sync.Mutex
42-
done bool
43-
empty *types.Block
44-
block *types.Block
45-
result chan *types.Block
46-
}
47-
48-
// resolve extracts the generated full block from the given channel if possible
49-
// or fallback to empty block as an alternative.
50-
func (req *payload) resolve() *beacon.ExecutableDataV1 {
51-
// this function can be called concurrently, prevent any
52-
// concurrency issue in the first place.
53-
req.lock.Lock()
54-
defer req.lock.Unlock()
55-
56-
// Try to resolve the full block first if it's not obtained
57-
// yet. The returned block can be nil if the generation fails.
58-
59-
if !req.done {
60-
timeout := time.NewTimer(500 * time.Millisecond)
61-
defer timeout.Stop()
62-
63-
select {
64-
case req.block = <-req.result:
65-
req.done = true
66-
case <-timeout.C:
67-
// TODO(rjl49345642, Marius), should we keep this
68-
// 100ms timeout allowance? Why not just use the
69-
// default and then fallback to empty directly?
70-
}
71-
}
72-
73-
if req.block != nil {
74-
return beacon.BlockToExecutableData(req.block)
75-
}
76-
return beacon.BlockToExecutableData(req.empty)
77-
}
78-
7938
// payloadQueueItem represents an id->payload tuple to store until it's retrieved
8039
// or evicted.
8140
type payloadQueueItem struct {
82-
id beacon.PayloadID
83-
data *payload
41+
id beacon.PayloadID
42+
payload *miner.Payload
8443
}
8544

8645
// payloadQueue tracks the latest handful of constructed payloads to be retrieved
@@ -99,14 +58,14 @@ func newPayloadQueue() *payloadQueue {
9958
}
10059

10160
// put inserts a new payload into the queue at the given id.
102-
func (q *payloadQueue) put(id beacon.PayloadID, data *payload) {
61+
func (q *payloadQueue) put(id beacon.PayloadID, payload *miner.Payload) {
10362
q.lock.Lock()
10463
defer q.lock.Unlock()
10564

10665
copy(q.payloads[1:], q.payloads)
10766
q.payloads[0] = &payloadQueueItem{
108-
id: id,
109-
data: data,
67+
id: id,
68+
payload: payload,
11069
}
11170
}
11271

@@ -120,7 +79,7 @@ func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 {
12079
return nil // no more items
12180
}
12281
if item.id == id {
123-
return item.data.resolve()
82+
return item.payload.Resolve()
12483
}
12584
}
12685
return nil

miner/miner.go

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -251,26 +251,7 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
251251
return miner.worker.pendingLogsFeed.Subscribe(ch)
252252
}
253253

254-
// GetSealingBlockAsync requests to generate a sealing block according to the
255-
// given parameters. Regardless of whether the generation is successful or not,
256-
// there is always a result that will be returned through the result channel.
257-
// The difference is that if the execution fails, the returned result is nil
258-
// and the concrete error is dropped silently.
259-
func (miner *Miner) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (chan *types.Block, error) {
260-
resCh, _, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, random, noTxs)
261-
if err != nil {
262-
return nil, err
263-
}
264-
return resCh, nil
265-
}
266-
267-
// GetSealingBlockSync creates a sealing block according to the given parameters.
268-
// If the generation is failed or the underlying work is already closed, an error
269-
// will be returned.
270-
func (miner *Miner) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (*types.Block, error) {
271-
resCh, errCh, err := miner.worker.getSealingBlock(parent, timestamp, coinbase, random, noTxs)
272-
if err != nil {
273-
return nil, err
274-
}
275-
return <-resCh, <-errCh
254+
// BuildPayload builds the payload according to the provided parameters.
255+
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
256+
return miner.worker.buildPayload(args)
276257
}

0 commit comments

Comments
 (0)