Skip to content

Commit 55d31bb

Browse files
committed
miner, eth: implement recommit for payload building
1 parent 1913b50 commit 55d31bb

File tree

8 files changed

+291
-100
lines changed

8 files changed

+291
-100
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"
@@ -163,6 +164,8 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
163164
if err != nil {
164165
t.Fatalf("error preparing payload, err=%v", err)
165166
}
167+
// give the payload some time to be built
168+
time.Sleep(100 * time.Millisecond)
166169
payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
167170
execData, err := api.GetPayloadV1(payloadID)
168171
if err != nil {
@@ -568,12 +571,12 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
568571
if resp.PayloadStatus.Status != beacon.VALID {
569572
t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
570573
}
574+
// give the payload some time to be built
575+
time.Sleep(100 * time.Millisecond)
571576
payload, err := api.GetPayloadV1(*resp.PayloadID)
572577
if err != nil {
573578
t.Fatalf("can't get payload: %v", err)
574579
}
575-
// TODO(493456442, marius) this test can be flaky since we rely on a 100ms
576-
// allowance for block generation internally.
577580
if len(payload.Transactions) == 0 {
578581
t.Fatalf("payload should not be empty")
579582
}
@@ -600,11 +603,17 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
600603
}
601604

602605
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) {
603-
block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random, false)
606+
args := &miner.BuildPayloadArgs{
607+
Parent: parentHash,
608+
Timestamp: params.Timestamp,
609+
FeeRecipient: params.SuggestedFeeRecipient,
610+
Random: params.Random,
611+
}
612+
payload, err := api.eth.Miner().BuildPayload(args)
604613
if err != nil {
605614
return nil, err
606615
}
607-
return beacon.BlockToExecutableData(block), nil
616+
return payload.ResolveFull(), nil
608617
}
609618

610619
func TestEmptyBlocks(t *testing.T) {
@@ -836,16 +845,17 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
836845
}
837846

838847
// Test parent already post TTD in NewPayload
839-
params := beacon.PayloadAttributesV1{
840-
Timestamp: parent.Time() + 1,
841-
Random: crypto.Keccak256Hash([]byte{byte(1)}),
842-
SuggestedFeeRecipient: parent.Coinbase(),
848+
args := &miner.BuildPayloadArgs{
849+
Parent: parent.Hash(),
850+
Timestamp: parent.Time() + 1,
851+
Random: crypto.Keccak256Hash([]byte{byte(1)}),
852+
FeeRecipient: parent.Coinbase(),
843853
}
844-
empty, err := api.eth.Miner().GetSealingBlockSync(parent.Hash(), params.Timestamp, params.SuggestedFeeRecipient, params.Random, true)
854+
payload, err := api.eth.Miner().BuildPayload(args)
845855
if err != nil {
846856
t.Fatalf("error preparing payload, err=%v", err)
847857
}
848-
data := *beacon.BlockToExecutableData(empty)
858+
data := *payload.Resolve()
849859
resp2, err := api.NewPayloadV1(data)
850860
if err != nil {
851861
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
@@ -250,26 +250,7 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
250250
return miner.worker.pendingLogsFeed.Subscribe(ch)
251251
}
252252

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

0 commit comments

Comments
 (0)