@@ -2,7 +2,7 @@ import {fromHexString, toHexString} from "@chainsafe/ssz";
2
2
import { routes , ServerApi , isSignedBlockContents , isSignedBlindedBlockContents } from "@lodestar/api" ;
3
3
import { computeTimeAtSlot } from "@lodestar/state-transition" ;
4
4
import { SLOTS_PER_HISTORICAL_ROOT } from "@lodestar/params" ;
5
- import { sleep } from "@lodestar/utils" ;
5
+ import { sleep , toHex } from "@lodestar/utils" ;
6
6
import { allForks , deneb } from "@lodestar/types" ;
7
7
import {
8
8
BlockSource ,
@@ -19,6 +19,8 @@ import {NetworkEvent} from "../../../../network/index.js";
19
19
import { ApiModules } from "../../types.js" ;
20
20
import { resolveBlockId , toBeaconHeaderResponse } from "./utils.js" ;
21
21
22
+ type PublishBlockOpts = ImportBlockOpts & { broadcastValidation ?: routes . beacon . BroadcastValidation } ;
23
+
22
24
/**
23
25
* Validator clock may be advanced from beacon's clock. If the validator requests a resource in a
24
26
* future slot, wait some time instead of rejecting the request because it's in the future
@@ -37,6 +39,127 @@ export function getBeaconBlockApi({
37
39
network,
38
40
db,
39
41
} : Pick < ApiModules , "chain" | "config" | "metrics" | "network" | "db" > ) : ServerApi < routes . beacon . block . Api > {
42
+ const publishBlock : ServerApi < routes . beacon . block . Api > [ "publishBlock" ] = async (
43
+ signedBlockOrContents ,
44
+ opts : PublishBlockOpts = { }
45
+ ) => {
46
+ const seenTimestampSec = Date . now ( ) / 1000 ;
47
+ let blockForImport : BlockInput , signedBlock : allForks . SignedBeaconBlock , signedBlobs : deneb . SignedBlobSidecars ;
48
+
49
+ if ( isSignedBlockContents ( signedBlockOrContents ) ) {
50
+ ( { signedBlock, signedBlobSidecars : signedBlobs } = signedBlockOrContents ) ;
51
+ blockForImport = getBlockInput . postDeneb (
52
+ config ,
53
+ signedBlock ,
54
+ BlockSource . api ,
55
+ // The blobsSidecar will be replaced in the followup PRs with just blobs
56
+ blobSidecarsToBlobsSidecar (
57
+ config ,
58
+ signedBlock ,
59
+ signedBlobs . map ( ( sblob ) => sblob . message )
60
+ ) ,
61
+ null
62
+ ) ;
63
+ } else {
64
+ signedBlock = signedBlockOrContents ;
65
+ signedBlobs = [ ] ;
66
+ // TODO: Once API supports submitting data as SSZ, replace null with blockBytes
67
+ blockForImport = getBlockInput . preDeneb ( config , signedBlock , BlockSource . api , null ) ;
68
+ }
69
+
70
+ // check what validations have been requested before broadcasting and publishing the block
71
+ // TODO: add validation time to metrics
72
+ const broadcastValidation = opts . broadcastValidation ?? routes . beacon . BroadcastValidation . none ;
73
+ // if block is locally produced, full or blinded, it already is 'consensus' validated as it went through
74
+ // state transition to produce the stateRoot
75
+ const slot = signedBlock . message . slot ;
76
+ const blockRoot = toHex ( chain . config . getForkTypes ( slot ) . BeaconBlock . hashTreeRoot ( signedBlock . message ) ) ;
77
+ const blockLocallyProduced =
78
+ chain . producedBlockRoot . has ( blockRoot ) || chain . producedBlindedBlockRoot . has ( blockRoot ) ;
79
+ const valLogMeta = { broadcastValidation, blockRoot, blockLocallyProduced, slot} ;
80
+
81
+ switch ( broadcastValidation ) {
82
+ case routes . beacon . BroadcastValidation . none : {
83
+ if ( blockLocallyProduced ) {
84
+ chain . logger . debug ( "No broadcast validation requested for the block" , valLogMeta ) ;
85
+ } else {
86
+ chain . logger . warn ( "No broadcast validation requested for the block" , valLogMeta ) ;
87
+ }
88
+ break ;
89
+ }
90
+ case routes . beacon . BroadcastValidation . consensus : {
91
+ // check if this beacon node produced the block else run validations
92
+ if ( ! blockLocallyProduced ) {
93
+ // error or log warning that we support consensus val on blocks produced via this beacon node
94
+ const message = "Consensus validation not implemented yet for block not produced by this beacon node" ;
95
+ if ( chain . opts . broadcastValidationStrictness === "error" ) {
96
+ throw Error ( message ) ;
97
+ } else {
98
+ chain . logger . warn ( message , valLogMeta ) ;
99
+ }
100
+ }
101
+ break ;
102
+ }
103
+
104
+ default : {
105
+ // error or log warning we do not support this validation
106
+ const message = `Broadcast validation of ${ broadcastValidation } type not implemented yet` ;
107
+ if ( chain . opts . broadcastValidationStrictness === "error" ) {
108
+ throw Error ( message ) ;
109
+ } else {
110
+ chain . logger . warn ( message ) ;
111
+ }
112
+ }
113
+ }
114
+
115
+ // Simple implementation of a pending block queue. Keeping the block here recycles the API logic, and keeps the
116
+ // REST request promise without any extra infrastructure.
117
+ const msToBlockSlot =
118
+ computeTimeAtSlot ( config , blockForImport . block . message . slot , chain . genesisTime ) * 1000 - Date . now ( ) ;
119
+ if ( msToBlockSlot <= MAX_API_CLOCK_DISPARITY_MS && msToBlockSlot > 0 ) {
120
+ // If block is a bit early, hold it in a promise. Equivalent to a pending queue.
121
+ await sleep ( msToBlockSlot ) ;
122
+ }
123
+
124
+ // TODO: Validate block
125
+ metrics ?. registerBeaconBlock ( OpSource . api , seenTimestampSec , blockForImport . block . message ) ;
126
+ const publishPromises = [
127
+ // Send the block, regardless of whether or not it is valid. The API
128
+ // specification is very clear that this is the desired behaviour.
129
+ ( ) => network . publishBeaconBlock ( signedBlock ) as Promise < unknown > ,
130
+ ( ) =>
131
+ // there is no rush to persist block since we published it to gossip anyway
132
+ chain . processBlock ( blockForImport , { ...opts , eagerPersistBlock : false } ) . catch ( ( e ) => {
133
+ if ( e instanceof BlockError && e . type . code === BlockErrorCode . PARENT_UNKNOWN ) {
134
+ network . events . emit ( NetworkEvent . unknownBlockParent , {
135
+ blockInput : blockForImport ,
136
+ peer : IDENTITY_PEER_ID ,
137
+ } ) ;
138
+ }
139
+ throw e ;
140
+ } ) ,
141
+ ...signedBlobs . map ( ( signedBlob ) => ( ) => network . publishBlobSidecar ( signedBlob ) ) ,
142
+ ] ;
143
+ await promiseAllMaybeAsync ( publishPromises ) ;
144
+ } ;
145
+
146
+ const publishBlindedBlock : ServerApi < routes . beacon . block . Api > [ "publishBlindedBlock" ] = async (
147
+ signedBlindedBlockOrContents ,
148
+ opts : PublishBlockOpts = { }
149
+ ) => {
150
+ const executionBuilder = chain . executionBuilder ;
151
+ if ( ! executionBuilder ) throw Error ( "exeutionBuilder required to publish SignedBlindedBeaconBlock" ) ;
152
+ // Mechanism for blobs & blocks on builder is not yet finalized
153
+ if ( isSignedBlindedBlockContents ( signedBlindedBlockOrContents ) ) {
154
+ throw Error ( "exeutionBuilder not yet implemented for deneb+ forks" ) ;
155
+ } else {
156
+ const signedBlockOrContents = await executionBuilder . submitBlindedBlock ( signedBlindedBlockOrContents ) ;
157
+ // the full block is published by relay and it's possible that the block is already known to us by gossip
158
+ // see https://github.com/ChainSafe/lodestar/issues/5404
159
+ return publishBlock ( signedBlockOrContents , { ...opts , ignoreIfKnown : true } ) ;
160
+ }
161
+ } ;
162
+
40
163
return {
41
164
async getBlockHeaders ( filters ) {
42
165
// TODO - SLOW CODE: This code seems like it could be improved
@@ -189,74 +312,15 @@ export function getBeaconBlockApi({
189
312
} ;
190
313
} ,
191
314
192
- async publishBlindedBlock ( signedBlindedBlockOrContents ) {
193
- const executionBuilder = chain . executionBuilder ;
194
- if ( ! executionBuilder ) throw Error ( "exeutionBuilder required to publish SignedBlindedBeaconBlock" ) ;
195
- // Mechanism for blobs & blocks on builder is not yet finalized
196
- if ( isSignedBlindedBlockContents ( signedBlindedBlockOrContents ) ) {
197
- throw Error ( "exeutionBuilder not yet implemented for deneb+ forks" ) ;
198
- } else {
199
- const signedBlockOrContents = await executionBuilder . submitBlindedBlock ( signedBlindedBlockOrContents ) ;
200
- // the full block is published by relay and it's possible that the block is already known to us by gossip
201
- // see https://github.com/ChainSafe/lodestar/issues/5404
202
- return this . publishBlock ( signedBlockOrContents , { ignoreIfKnown : true } ) ;
203
- }
204
- } ,
205
-
206
- async publishBlock ( signedBlockOrContents , opts : ImportBlockOpts = { } ) {
207
- const seenTimestampSec = Date . now ( ) / 1000 ;
208
- let blockForImport : BlockInput , signedBlock : allForks . SignedBeaconBlock , signedBlobs : deneb . SignedBlobSidecars ;
315
+ publishBlock,
316
+ publishBlindedBlock,
209
317
210
- if ( isSignedBlockContents ( signedBlockOrContents ) ) {
211
- ( { signedBlock, signedBlobSidecars : signedBlobs } = signedBlockOrContents ) ;
212
- blockForImport = getBlockInput . postDeneb (
213
- config ,
214
- signedBlock ,
215
- BlockSource . api ,
216
- // The blobsSidecar will be replaced in the followup PRs with just blobs
217
- blobSidecarsToBlobsSidecar (
218
- config ,
219
- signedBlock ,
220
- signedBlobs . map ( ( sblob ) => sblob . message )
221
- ) ,
222
- null
223
- ) ;
224
- } else {
225
- signedBlock = signedBlockOrContents ;
226
- signedBlobs = [ ] ;
227
- // TODO: Once API supports submitting data as SSZ, replace null with blockBytes
228
- blockForImport = getBlockInput . preDeneb ( config , signedBlock , BlockSource . api , null ) ;
229
- }
230
-
231
- // Simple implementation of a pending block queue. Keeping the block here recycles the API logic, and keeps the
232
- // REST request promise without any extra infrastructure.
233
- const msToBlockSlot =
234
- computeTimeAtSlot ( config , blockForImport . block . message . slot , chain . genesisTime ) * 1000 - Date . now ( ) ;
235
- if ( msToBlockSlot <= MAX_API_CLOCK_DISPARITY_MS && msToBlockSlot > 0 ) {
236
- // If block is a bit early, hold it in a promise. Equivalent to a pending queue.
237
- await sleep ( msToBlockSlot ) ;
238
- }
318
+ async publishBlindedBlockV2 ( signedBlindedBlockOrContents , opts ) {
319
+ await publishBlindedBlock ( signedBlindedBlockOrContents , opts ) ;
320
+ } ,
239
321
240
- // TODO: Validate block
241
- metrics ?. registerBeaconBlock ( OpSource . api , seenTimestampSec , blockForImport . block . message ) ;
242
- const publishPromises = [
243
- // Send the block, regardless of whether or not it is valid. The API
244
- // specification is very clear that this is the desired behaviour.
245
- ( ) => network . publishBeaconBlock ( signedBlock ) as Promise < unknown > ,
246
- ( ) =>
247
- // there is no rush to persist block since we published it to gossip anyway
248
- chain . processBlock ( blockForImport , { ...opts , eagerPersistBlock : false } ) . catch ( ( e ) => {
249
- if ( e instanceof BlockError && e . type . code === BlockErrorCode . PARENT_UNKNOWN ) {
250
- network . events . emit ( NetworkEvent . unknownBlockParent , {
251
- blockInput : blockForImport ,
252
- peer : IDENTITY_PEER_ID ,
253
- } ) ;
254
- }
255
- throw e ;
256
- } ) ,
257
- ...signedBlobs . map ( ( signedBlob ) => ( ) => network . publishBlobSidecar ( signedBlob ) ) ,
258
- ] ;
259
- await promiseAllMaybeAsync ( publishPromises ) ;
322
+ async publishBlockV2 ( signedBlockOrContents , opts ) {
323
+ await publishBlock ( signedBlockOrContents , opts ) ;
260
324
} ,
261
325
262
326
async getBlobSidecars ( blockId ) {
0 commit comments