-
-
Notifications
You must be signed in to change notification settings - Fork 328
/
Copy pathhttp.ts
134 lines (119 loc) Β· 4.77 KB
/
http.ts
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
import {allForks, bellatrix, Slot, Root, BLSPubkey, deneb, Wei} from "@lodestar/types";
import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
import {Logger} from "@lodestar/logger";
import {getClient, ApiClient as BuilderApi} from "@lodestar/api/builder";
import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params";
import {toSafePrintableUrl} from "@lodestar/utils";
import {Metrics} from "../../metrics/metrics.js";
import {IExecutionBuilder} from "./interface.js";
export type ExecutionBuilderHttpOpts = {
enabled: boolean;
url: string;
timeout?: number;
faultInspectionWindow?: number;
allowedFaults?: number;
// Only required for merge-mock runs, no need to expose it to cli
issueLocalFcUWithFeeRecipient?: string;
// Add User-Agent header to all requests
userAgent?: string;
};
export const defaultExecutionBuilderHttpOpts: ExecutionBuilderHttpOpts = {
enabled: false,
url: "http://localhost:8661",
timeout: 12000,
};
export class ExecutionBuilderHttp implements IExecutionBuilder {
readonly api: BuilderApi;
readonly config: ChainForkConfig;
readonly issueLocalFcUWithFeeRecipient?: string;
// Builder needs to be explicity enabled using updateStatus
status = false;
faultInspectionWindow: number;
allowedFaults: number;
constructor(
opts: ExecutionBuilderHttpOpts,
config: ChainForkConfig,
metrics: Metrics | null = null,
logger?: Logger
) {
const baseUrl = opts.url;
if (!baseUrl) throw Error("No Url provided for executionBuilder");
this.api = getClient(
{
baseUrl,
globalInit: {
timeoutMs: opts.timeout,
headers: opts.userAgent ? {"User-Agent": opts.userAgent} : undefined,
},
},
{config, metrics: metrics?.builderHttpClient}
);
logger?.info("External builder", {url: toSafePrintableUrl(baseUrl)});
this.config = config;
this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient;
/**
* Beacon clients select randomized values from the following ranges when initializing
* the circuit breaker (so at boot time and once for each unique boot).
*
* ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 2
* FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
*
*/
this.faultInspectionWindow = Math.max(
opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
SLOTS_PER_EPOCH
);
// allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/2
this.allowedFaults = Math.min(
opts.allowedFaults ?? Math.floor(this.faultInspectionWindow / 2),
Math.floor(this.faultInspectionWindow / 2)
);
}
updateStatus(shouldEnable: boolean): void {
this.status = shouldEnable;
}
async checkStatus(): Promise<void> {
try {
(await this.api.status()).assertOk();
} catch (e) {
// Disable if the status was enabled
this.status = false;
throw e;
}
}
async registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void> {
(await this.api.registerValidator({registrations})).assertOk();
}
async getHeader(
_fork: ForkExecution,
slot: Slot,
parentHash: Root,
proposerPubkey: BLSPubkey
): Promise<{
header: allForks.ExecutionPayloadHeader;
executionPayloadValue: Wei;
blobKzgCommitments?: deneb.BlobKzgCommitments;
}> {
const signedBuilderBid = (await this.api.getHeader({slot, parentHash, proposerPubkey})).value();
if (!signedBuilderBid) {
throw Error("No bid received");
}
const {header, value: executionPayloadValue} = signedBuilderBid.message;
const {blobKzgCommitments} = signedBuilderBid.message as deneb.BuilderBid;
return {header, executionPayloadValue, blobKzgCommitments};
}
async submitBlindedBlock(
signedBlindedBlock: allForks.SignedBlindedBeaconBlock
): Promise<allForks.SignedBeaconBlockOrContents> {
const data = (await this.api.submitBlindedBlock({signedBlindedBlock}, {retries: 2})).value();
const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data);
// for the sake of timely proposals we can skip matching the payload with payloadHeader
// if the roots (transactions, withdrawals) don't match, this will likely lead to a block with
// invalid signature, but there is no recourse to this anyway so lets just proceed and will
// probably need diagonis if this block turns out to be invalid because of some bug
//
const contents = blobsBundle ? {blobs: blobsBundle.blobs, kzgProofs: blobsBundle.proofs} : null;
return reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents});
}
}