-
Notifications
You must be signed in to change notification settings - Fork 110
/
api_forkchoice.nim
210 lines (185 loc) · 8.07 KB
/
api_forkchoice.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/[typetraits],
eth/common,
results,
../web3_eth_conv,
../beacon_engine,
web3/execution_types,
./api_utils,
chronicles
{.push gcsafe, raises:[CatchableError].}
template validateVersion(attr, com, apiVersion) =
let
version = attr.version
timestamp = ethTime attr.timestamp
if apiVersion == Version.V3:
if version != apiVersion:
raise invalidAttr("forkChoiceUpdatedV3 expect PayloadAttributesV3" &
" but got PayloadAttributes" & $version)
if not com.isCancunOrLater(timestamp):
raise unsupportedFork(
"forkchoiceUpdatedV3 get invalid payloadAttributes timestamp")
else:
if com.isCancunOrLater(timestamp):
if version < Version.V3:
raise unsupportedFork("forkChoiceUpdated" & $apiVersion &
" doesn't support payloadAttributes" & $version)
if version >= Version.V3:
raise invalidAttr("forkChoiceUpdated" & $apiVersion &
" doesn't support PayloadAttributes" & $version)
elif com.isShanghaiOrLater(timestamp):
if version < Version.V2:
raise invalidParams("forkChoiceUpdated" & $apiVersion &
" doesn't support payloadAttributesV1")
if version > Version.V2:
raise invalidAttr("if timestamp is Shanghai or later," &
" payloadAttributes must be PayloadAttributesV2")
else:
if version != Version.V1:
raise invalidParams("if timestamp is earlier than Shanghai," &
" payloadAttributes must be PayloadAttributesV1")
proc forkchoiceUpdated*(ben: BeaconEngineRef,
apiVersion: Version,
update: ForkchoiceStateV1,
attrsOpt: Option[PayloadAttributes]):
ForkchoiceUpdatedResponse =
let
com = ben.com
db = com.db
chain = ben.chain
blockHash = ethHash update.headBlockHash
if blockHash == common.Hash256():
warn "Forkchoice requested update to zero hash"
return simpleFCU(PayloadExecutionStatus.invalid)
# Check whether we have the block yet in our database or not. If not, we'll
# need to either trigger a sync, or to reject this forkchoice update for a
# reason.
var header: common.BlockHeader
if not db.getBlockHeader(blockHash, header):
# If this block was previously invalidated, keep rejecting it here too
let res = ben.checkInvalidAncestor(blockHash, blockHash)
if res.isSome:
return simpleFCU(res.get)
# If the head hash is unknown (was not given to us in a newPayload request),
# we cannot resolve the header, so not much to do. This could be extended in
# the future to resolve from the `eth` network, but it's an unexpected case
# that should be fixed, not papered over.
if not ben.get(blockHash, header):
warn "Forkchoice requested unknown head",
hash = blockHash.short
return simpleFCU(PayloadExecutionStatus.syncing)
# Header advertised via a past newPayload request. Start syncing to it.
# Before we do however, make sure any legacy sync in switched off so we
# don't accidentally have 2 cycles running.
if not ben.ttdReached():
ben.reachTTD()
# TODO: cancel downloader
info "Forkchoice requested sync to new head",
number = header.blockNumber,
hash = blockHash.short
# Update sync header (if any)
com.syncReqNewHead(header)
return simpleFCU(PayloadExecutionStatus.syncing)
# Block is known locally, just sanity check that the beacon client does not
# attempt to push us back to before the merge.
let blockNumber = header.blockNumber.truncate(uint64)
if header.difficulty > 0.u256 or blockNumber == 0'u64:
var
td, ptd: DifficultyInt
ttd = com.ttd.get(high(common.BlockNumber))
if not db.getTd(blockHash, td) or (blockNumber > 0'u64 and not db.getTd(header.parentHash, ptd)):
error "TDs unavailable for TTD check",
number = blockNumber,
hash = blockHash.short,
td = td,
parent = header.parentHash.short,
ptd = ptd
return simpleFCU(PayloadExecutionStatus.invalid, "TDs unavailable for TDD check")
if td < ttd or (blockNumber > 0'u64 and ptd > ttd):
error "Refusing beacon update to pre-merge",
number = blockNumber,
hash = blockHash.short,
diff = header.difficulty,
ptd = ptd,
ttd = ttd
return invalidFCU()
# If the head block is already in our canonical chain, the beacon client is
# probably resyncing. Ignore the update.
var canonHash: common.Hash256
if db.getBlockHash(header.blockNumber, canonHash) and canonHash == blockHash:
# TODO should this be possible?
# If we allow these types of reorgs, we will do lots and lots of reorgs during sync
debug "Reorg to previous block"
if chain.setCanonical(header) != ValidationResult.OK:
return invalidFCU(com, header)
elif chain.setCanonical(header) != ValidationResult.OK:
return invalidFCU(com, header)
# If the beacon client also advertised a finalized block, mark the local
# chain final and completely in PoS mode.
let finalizedBlockHash = ethHash update.finalizedBlockHash
if finalizedBlockHash != common.Hash256():
if not ben.posFinalized:
ben.finalizePoS()
# TODO: If the finalized block is not in our canonical tree, somethings wrong
var finalBlock: common.BlockHeader
if not db.getBlockHeader(finalizedBlockHash, finalBlock):
warn "Final block not available in database",
hash=finalizedBlockHash.short
raise invalidForkChoiceState("finalized block header not available")
var finalHash: common.Hash256
if not db.getBlockHash(finalBlock.blockNumber, finalHash):
warn "Final block not in canonical chain",
number=finalBlock.blockNumber,
hash=finalizedBlockHash.short
raise invalidForkChoiceState("finalized block hash not available")
if finalHash != finalizedBlockHash:
warn "Final block not in canonical chain",
number=finalBlock.blockNumber,
expect=finalizedBlockHash.short,
get=finalHash.short
raise invalidForkChoiceState("finalized block not canonical")
db.finalizedHeaderHash(finalizedBlockHash)
let safeBlockHash = ethHash update.safeBlockHash
if safeBlockHash != common.Hash256():
var safeBlock: common.BlockHeader
if not db.getBlockHeader(safeBlockHash, safeBlock):
warn "Safe block not available in database",
hash = safeBlockHash.short
raise invalidForkChoiceState("safe head not available")
var safeHash: common.Hash256
if not db.getBlockHash(safeBlock.blockNumber, safeHash):
warn "Safe block hash not available in database",
hash = safeHash.short
raise invalidForkChoiceState("safe block hash not available")
if safeHash != safeBlockHash:
warn "Safe block not in canonical chain",
blockNumber=safeBlock.blockNumber,
expect=safeBlockHash.short,
get=safeHash.short
raise invalidForkChoiceState("safe head not canonical")
db.safeHeaderHash(safeBlockHash)
# If payload generation was requested, create a new block to be potentially
# sealed by the beacon client. The payload will be requested later, and we
# might replace it arbitrarilly many times in between.
if attrsOpt.isSome:
let attrs = attrsOpt.get()
validateVersion(attrs, com, apiVersion)
let bundle = ben.generatePayload(attrs).valueOr:
error "Failed to create sealing payload", err = error
raise invalidAttr(error)
let id = computePayloadId(blockHash, attrs)
ben.put(id, ben.blockValue, bundle.executionPayload, bundle.blobsBundle)
info "Created payload for sealing",
id = id.toHex,
hash = bundle.executionPayload.blockHash.short,
number = bundle.executionPayload.blockNumber
return validFCU(some(id), blockHash)
return validFCU(none(PayloadID), blockHash)