@lodestar/builder 1.47.0-dev.d9c16ea16f → 1.47.0-dev.e91bc3e859

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Lodestar Builder
2
2
 
3
3
  [![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/aMxzVcr)
4
- [![Eth Consensus Spec v1.7.0-alpha.13](https://img.shields.io/badge/ETH%20consensus--spec-1.7.0_alpha.13-blue)](https://github.com/ethereum/consensus-specs/releases/tag/v1.7.0-alpha.13)
4
+ [![Eth Consensus Spec v1.7.0-alpha.14](https://img.shields.io/badge/ETH%20consensus--spec-1.7.0_alpha.14-blue)](https://github.com/ethereum/consensus-specs/releases/tag/v1.7.0-alpha.14)
5
5
  ![ES Version](https://img.shields.io/badge/ES-2021-yellow)
6
6
  ![Node Version](https://img.shields.io/badge/node-24.x-green)
7
7
 
package/lib/builder.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import { ApiClient } from "@lodestar/api";
2
2
  import { ChainForkConfig } from "@lodestar/config";
3
3
  import { ClockOptions, IClock } from "@lodestar/state-transition";
4
+ import { BuilderIndex, ExecutionAddress } from "@lodestar/types";
4
5
  import { Logger } from "@lodestar/utils";
6
+ import { Metrics } from "./metrics.js";
5
7
  import { BuilderSigner, Keypair } from "./services/builderSigner.js";
8
+ import { BuilderStatusTracker } from "./services/builderStatusTracker.js";
6
9
  export type BuilderModules = {
7
10
  opts: BuilderOptions;
8
11
  builderSigner: BuilderSigner;
12
+ builderStatusTracker: BuilderStatusTracker;
9
13
  clock: IClock;
14
+ index: BuilderIndex;
10
15
  };
11
16
  export type BuilderOptions = {
12
17
  logger: Logger;
@@ -15,15 +20,21 @@ export type BuilderOptions = {
15
20
  abortController: AbortController;
16
21
  api: ApiClient;
17
22
  clock?: ClockOptions;
23
+ executionFeeRecipient: ExecutionAddress;
24
+ metrics: Metrics | null;
18
25
  };
19
26
  /**
20
27
  * Main class for the Builder client.
21
28
  */
22
29
  export declare class Builder {
23
30
  readonly builderSigner: BuilderSigner;
31
+ private readonly builderStatusTracker;
24
32
  private readonly controller;
25
33
  private readonly clock;
26
- constructor({ opts, builderSigner, clock }: BuilderModules);
34
+ private readonly index;
35
+ private readonly logger;
36
+ private readonly executionFeeRecipient;
37
+ constructor({ opts, builderSigner, builderStatusTracker, clock, index }: BuilderModules);
27
38
  static init(opts: BuilderOptions): Promise<Builder>;
28
39
  close(): Promise<void>;
29
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAS,MAAM,eAAe,CAAC;AAChD,OAAO,EAAC,eAAe,EAAwC,MAAM,kBAAkB,CAAC;AAExF,OAAO,EAAQ,YAAY,EAAE,MAAM,EAAC,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAC,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAC,aAAa,EAAE,OAAO,EAAC,MAAM,6BAA6B,CAAC;AAEnE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,cAAc,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,YAAY,EAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAC,EAAE,cAAc,EAMvD;IAED,OAAa,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAyCxD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CACF"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,eAAe,EAAwC,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAQ,YAAY,EAAE,MAAM,EAAC,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAC,MAAM,EAAmB,MAAM,iBAAiB,CAAC;AAGzD,OAAO,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAErC,OAAO,EAAC,aAAa,EAAE,OAAO,EAAC,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAC,oBAAoB,EAAC,MAAM,oCAAoC,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,cAAc,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;IACjC,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,qBAAqB,EAAE,gBAAgB,CAAC;IACxC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAuB;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAmB;IAEzD,YAAY,EAAC,IAAI,EAAE,aAAa,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAC,EAAE,cAAc,EAiBpF;IAED,OAAa,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CACF"}
package/lib/builder.js CHANGED
@@ -1,49 +1,54 @@
1
1
  import { assertEqualParams, createBeaconConfig } from "@lodestar/config";
2
- import { PAYLOAD_BUILDER_VERSION } from "@lodestar/params";
3
2
  import { Clock } from "@lodestar/state-transition";
3
+ import { toHex, toRootHex } from "@lodestar/utils";
4
4
  import { waitForGenesis } from "./genesis.js";
5
+ import { resolveBuilderIdentity } from "./identity.js";
6
+ import { logNodeVersion, waitForNodeReady } from "./readiness.js";
5
7
  import { BuilderSigner } from "./services/builderSigner.js";
8
+ import { BuilderStatusTracker } from "./services/builderStatusTracker.js";
6
9
  /**
7
10
  * Main class for the Builder client.
8
11
  */
9
12
  export class Builder {
10
13
  builderSigner;
14
+ builderStatusTracker;
11
15
  controller;
12
16
  clock;
13
- constructor({ opts, builderSigner, clock }) {
17
+ index;
18
+ logger;
19
+ executionFeeRecipient;
20
+ constructor({ opts, builderSigner, builderStatusTracker, clock, index }) {
14
21
  this.builderSigner = builderSigner;
22
+ this.builderStatusTracker = builderStatusTracker;
15
23
  this.clock = clock;
16
24
  this.controller = opts.abortController;
25
+ this.logger = opts.logger;
26
+ this.index = index;
27
+ this.executionFeeRecipient = opts.executionFeeRecipient;
28
+ this.clock.runEveryEpoch((epoch) => this.builderStatusTracker.poll(epoch));
17
29
  this.clock.start(this.controller.signal);
30
+ this.logger.info("Builder client initialized", {
31
+ index: this.index,
32
+ executionFeeRecipient: toHex(this.executionFeeRecipient),
33
+ });
18
34
  }
19
35
  static async init(opts) {
20
- const genesis = await waitForGenesis(opts.api, opts.logger, opts.abortController.signal);
21
- opts.logger.info("Genesis fetched from the beacon node");
22
- const specRes = await opts.api.config.getSpec();
36
+ const { api, logger } = opts;
37
+ const genesis = await waitForGenesis(api, logger, opts.abortController.signal);
38
+ logger.info("Genesis fetched from the beacon node", {
39
+ genesisValidatorsRoot: toRootHex(genesis.genesisValidatorsRoot),
40
+ });
41
+ const specRes = await api.config.getSpec();
23
42
  assertEqualParams(opts.config, specRes.value());
24
- opts.logger.info("Verified connected beacon node and builder have the same config");
43
+ logger.info("Verified connected beacon node and builder have the same config");
25
44
  const config = createBeaconConfig(opts.config, genesis.genesisValidatorsRoot);
26
45
  const builderSigner = new BuilderSigner(config, opts.keypair);
27
- const builderRes = await opts.api.beacon.getStateBuilders({
28
- stateId: "head",
29
- builderIds: [builderSigner.getPubkeyHex()],
30
- });
31
- if (!builderRes.ok) {
32
- throw Error(`Getting state builders from BN failed: ${builderRes.status}`);
33
- }
34
- const builders = builderRes.value();
35
- if (builders.length === 0) {
36
- throw Error(`Builder not registered: ${builderSigner.getPubkeyHex()}`);
37
- }
38
- const builderStatus = builders[0];
39
- if (builderStatus.status !== "active") {
40
- throw Error(`Builder not active: ${builderStatus.status}`);
41
- }
42
- if (builderStatus.builder.version !== PAYLOAD_BUILDER_VERSION) {
43
- throw Error(`Builder version mismatch: got ${builderStatus.builder.version}, expected ${PAYLOAD_BUILDER_VERSION}`);
44
- }
45
- const clock = new Clock(config, opts.logger, { genesisTime: Number(genesis.genesisTime), ...opts.clock });
46
- return new Builder({ opts, builderSigner, clock });
46
+ await waitForNodeReady(api, logger, opts.abortController.signal);
47
+ await logNodeVersion(api, logger);
48
+ const clock = new Clock(config, logger, { genesisTime: Number(genesis.genesisTime), ...opts.clock });
49
+ const index = await resolveBuilderIdentity(api, logger, builderSigner.getPubkeyHex(), opts.abortController.signal, clock, config);
50
+ const builderStatusTracker = new BuilderStatusTracker(api, logger, index, opts.metrics);
51
+ return new Builder({ opts, builderSigner, builderStatusTracker, clock, index });
47
52
  }
48
53
  async close() {
49
54
  this.controller.abort();
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,iBAAiB,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAC,KAAK,EAAuB,MAAM,4BAA4B,CAAC;AAEvE,OAAO,EAAC,cAAc,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAC,aAAa,EAAU,MAAM,6BAA6B,CAAC;AAiBnE;;GAEG;AACH,MAAM,OAAO,OAAO;IACT,aAAa,CAAgB;IACrB,UAAU,CAAkB;IAC5B,KAAK,CAAS;IAE/B,YAAY,EAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAiB;QACtD,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC;QAEvC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAoB;QACpC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACzF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QAEzD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAChD,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;QAEpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;QAC9E,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAE9D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC;YACxD,OAAO,EAAE,MAAM;YACf,UAAU,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;SAC3C,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC,0CAA0C,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC;QAEpC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,KAAK,CAAC,2BAA2B,aAAa,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,aAAa,GAAkC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEjE,IAAI,aAAa,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,KAAK,CAAC,uBAAuB,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,aAAa,CAAC,OAAO,CAAC,OAAO,KAAK,uBAAuB,EAAE,CAAC;YAC9D,MAAM,KAAK,CACT,iCAAiC,aAAa,CAAC,OAAO,CAAC,OAAO,cAAc,uBAAuB,EAAE,CACtG,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,EAAC,CAAC,CAAC;QAExG,OAAO,IAAI,OAAO,CAAC,EAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAC,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACF"}
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
package/lib/defaults.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare const defaultOptions: {
2
2
  beaconNodeUrl: string;
3
+ requestTimeout: number;
3
4
  };
4
5
  //# sourceMappingURL=defaults.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;IAEzB,aAAa;CACd,CAAC"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;IAEzB,aAAa;IACb,cAAc;CACf,CAAC"}
package/lib/defaults.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export const defaultOptions = {
2
2
  // Source beacon node the builder connects to
3
3
  beaconNodeUrl: "http://127.0.0.1:9596",
4
+ requestTimeout: 10_000,
4
5
  };
5
6
  //# sourceMappingURL=defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,6CAA6C;IAC7C,aAAa,EAAE,uBAAuB;CACvC,CAAC"}
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,6CAA6C;IAC7C,aAAa,EAAE,uBAAuB;IACtC,cAAc,EAAE,MAAM;CACvB,CAAC"}
package/lib/genesis.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ApiClient } from "@lodestar/api";
2
2
  import { Genesis } from "@lodestar/types/phase0";
3
3
  import { Logger } from "@lodestar/utils";
4
- export declare function waitForGenesis(api: ApiClient, logger: Logger, signal?: AbortSignal): Promise<Genesis>;
4
+ export declare function waitForGenesis(api: ApiClient, logger: Logger, signal: AbortSignal): Promise<Genesis>;
5
5
  //# sourceMappingURL=genesis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"genesis.d.ts","sourceRoot":"","sources":["../src/genesis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAA2B,MAAM,eAAe,CAAC;AAClE,OAAO,EAAC,OAAO,EAAC,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAC,MAAM,EAAQ,MAAM,iBAAiB,CAAC;AAK9C,wBAAsB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAa3G"}
1
+ {"version":3,"file":"genesis.d.ts","sourceRoot":"","sources":["../src/genesis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAA2B,MAAM,eAAe,CAAC;AAClE,OAAO,EAAC,OAAO,EAAC,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAe,MAAM,EAAQ,MAAM,iBAAiB,CAAC;AAK5D,wBAAsB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAc1G"}
package/lib/genesis.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ApiError, HttpStatusCode } from "@lodestar/api";
2
- import { sleep } from "@lodestar/utils";
2
+ import { ErrorAborted, sleep } from "@lodestar/utils";
3
3
  /** The time between polls when waiting for genesis */
4
4
  const WAITING_FOR_GENESIS_POLL_MS = 12 * 1000;
5
5
  export async function waitForGenesis(api, logger, signal) {
6
- while (true) {
6
+ while (!signal.aborted) {
7
7
  try {
8
8
  return (await api.beacon.getGenesis()).value();
9
9
  }
@@ -17,5 +17,6 @@ export async function waitForGenesis(api, logger, signal) {
17
17
  await sleep(WAITING_FOR_GENESIS_POLL_MS, signal);
18
18
  }
19
19
  }
20
+ throw new ErrorAborted("waitForGenesis");
20
21
  }
21
22
  //# sourceMappingURL=genesis.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"genesis.js","sourceRoot":"","sources":["../src/genesis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,QAAQ,EAAE,cAAc,EAAC,MAAM,eAAe,CAAC;AAElE,OAAO,EAAS,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAE9C,sDAAsD;AACtD,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAc,EAAE,MAAc,EAAE,MAAoB;IACvF,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACjD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,SAAS,EAAE,CAAC;gBACnE,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAC,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAC,OAAO,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,KAAK,CAAC,2BAA2B,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"genesis.js","sourceRoot":"","sources":["../src/genesis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,QAAQ,EAAE,cAAc,EAAC,MAAM,eAAe,CAAC;AAElE,OAAO,EAAC,YAAY,EAAU,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAE5D,sDAAsD;AACtD,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAc,EAAE,MAAc,EAAE,MAAmB;IACtF,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACjD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,SAAS,EAAE,CAAC;gBACnE,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAC,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAC,OAAO,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,KAAK,CAAC,2BAA2B,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,MAAM,IAAI,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { ApiClient, routes } from "@lodestar/api";
2
+ import { ChainForkConfig } from "@lodestar/config";
3
+ import { IClock } from "@lodestar/state-transition";
4
+ import { BuilderIndex, BuilderStatus } from "@lodestar/types";
5
+ import { Logger } from "@lodestar/utils";
6
+ export declare const WAITING_FOR_BUILDER_POLL_MS: number;
7
+ export declare function resolveBuilderIdentity(api: ApiClient, logger: Logger, id: routes.beacon.BuilderId, signal: AbortSignal, clock: IClock, config: ChainForkConfig): Promise<BuilderIndex>;
8
+ export declare function getBuilderStatus(api: ApiClient, logger: Logger, id: routes.beacon.BuilderId): Promise<{
9
+ status: BuilderStatus;
10
+ balance: number;
11
+ } | null>;
12
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAA4B,MAAM,EAAC,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAe,MAAM,EAAe,MAAM,iBAAiB,CAAC;AAEnE,eAAO,MAAM,2BAA2B,QAAY,CAAC;AAErD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,EAC3B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,YAAY,CAAC,CAgBvB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,GAC1B,OAAO,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,IAAI,CAAC,CAe1D"}
@@ -0,0 +1,104 @@
1
+ import { ApiError, HttpStatusCode } from "@lodestar/api";
2
+ import { PAYLOAD_BUILDER_VERSION } from "@lodestar/params";
3
+ import { ErrorAborted, sleep, toHex } from "@lodestar/utils";
4
+ export const WAITING_FOR_BUILDER_POLL_MS = 10 * 1000;
5
+ export async function resolveBuilderIdentity(api, logger, id, signal, clock, config) {
6
+ const builderEntry = await waitForBuilder(api, logger, id, signal, clock, config);
7
+ if (builderEntry.builder.version !== PAYLOAD_BUILDER_VERSION) {
8
+ throw Error(`Builder version mismatch: got ${builderEntry.builder.version}, expected ${PAYLOAD_BUILDER_VERSION}`);
9
+ }
10
+ logger.info("Builder identity resolved", {
11
+ index: builderEntry.index,
12
+ status: builderEntry.status,
13
+ balanceGwei: builderEntry.builder.balance,
14
+ executionAddress: toHex(builderEntry.builder.executionAddress),
15
+ slot: clock.getCurrentSlot(),
16
+ });
17
+ return builderEntry.index;
18
+ }
19
+ export async function getBuilderStatus(api, logger, id) {
20
+ try {
21
+ const builderEntry = await fetchBuilder(api, id);
22
+ if (builderEntry) {
23
+ return {
24
+ status: builderEntry.status,
25
+ balance: builderEntry.builder.balance,
26
+ };
27
+ }
28
+ logger.warn("Builder status not available in beacon node");
29
+ return null;
30
+ }
31
+ catch (e) {
32
+ logger.warn("Couldn't fetch the builder", {}, e);
33
+ return null;
34
+ }
35
+ }
36
+ async function waitForBuilder(api, logger, id, signal, clock, config) {
37
+ const gloasForkEpoch = config.GLOAS_FORK_EPOCH;
38
+ while (!signal.aborted) {
39
+ const currentEpoch = clock.getCurrentEpoch();
40
+ if (currentEpoch < gloasForkEpoch) {
41
+ // Builders only exist post-gloas, the beacon node returns an error for pre-gloas state
42
+ logger.info("Waiting for Gloas fork before resolving builder identity", {
43
+ gloasForkEpoch,
44
+ currentEpoch,
45
+ slot: clock.getCurrentSlot(),
46
+ });
47
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
48
+ continue;
49
+ }
50
+ let builder = null;
51
+ try {
52
+ builder = await fetchBuilder(api, id);
53
+ }
54
+ catch (e) {
55
+ // At the fork boundary getStateBuilders("head") can still 400: it serves the head block's
56
+ // post-state, which stays pre-gloas until a gloas-epoch block is head. Keep polling on the transient.
57
+ if (e instanceof ApiError && e.status === HttpStatusCode.BAD_REQUEST) {
58
+ logger.info("Waiting for Gloas state to be available at head", {
59
+ gloasForkEpoch,
60
+ currentEpoch,
61
+ slot: clock.getCurrentSlot(),
62
+ });
63
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
64
+ continue;
65
+ }
66
+ throw e;
67
+ }
68
+ if (builder?.status === "active") {
69
+ return builder;
70
+ }
71
+ if (builder?.status === "exited") {
72
+ throw Error(`Builder exited: id=${id}`);
73
+ }
74
+ if (builder?.status === "pending") {
75
+ logger.info("Waiting for builder deposit to be finalized", { id, slot: clock.getCurrentSlot() });
76
+ }
77
+ else {
78
+ logger.info("Waiting for builder to be known to the beacon node", { id, slot: clock.getCurrentSlot() });
79
+ }
80
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
81
+ }
82
+ throw new ErrorAborted("waitForBuilder");
83
+ }
84
+ async function fetchBuilder(api, id) {
85
+ const builderRes = await api.beacon.getStateBuilders({
86
+ stateId: "head",
87
+ builderIds: [id],
88
+ });
89
+ const builders = builderRes.value();
90
+ if (builders.length === 0) {
91
+ return null;
92
+ }
93
+ const builder = builders[0];
94
+ if (typeof id === "number") {
95
+ if (id !== builder.index) {
96
+ throw Error(`Index mismatch: got=${builder.index} expected=${id}`);
97
+ }
98
+ }
99
+ else if (id !== toHex(builder.builder.pubkey)) {
100
+ throw Error(`Pubkey mismatch: got=${toHex(builder.builder.pubkey)} expected=${id}`);
101
+ }
102
+ return builder;
103
+ }
104
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
package/lib/metrics.d.ts CHANGED
@@ -1,4 +1,37 @@
1
+ import { BuilderStatus } from "@lodestar/types";
1
2
  import { MetricsRegisterExtra } from "@lodestar/utils";
2
3
  export type Metrics = ReturnType<typeof getMetrics>;
3
- export declare function getMetrics(_register: MetricsRegisterExtra): {};
4
+ export type LodestarGitData = {
5
+ /** "0.16.0 developer/feature-1 4f816b16" */
6
+ version: string;
7
+ /** "4f816b16dfde718e2d74f95f2c8292596138c248" */
8
+ commit: string;
9
+ /** "hoodi" */
10
+ network: string;
11
+ };
12
+ export declare const builderStatusValue: Record<BuilderStatus, number>;
13
+ export declare function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitData): {
14
+ builderStatus: import("@lodestar/utils").GaugeExtra<import("@lodestar/utils").NoLabels>;
15
+ builderBalance: import("@lodestar/utils").GaugeExtra<import("@lodestar/utils").NoLabels>;
16
+ restApiClient: {
17
+ requestTime: import("@lodestar/utils").Histogram<{
18
+ routeId: string;
19
+ }>;
20
+ streamTime: import("@lodestar/utils").Histogram<{
21
+ routeId: string;
22
+ }>;
23
+ requestErrors: import("@lodestar/utils").GaugeExtra<{
24
+ routeId: string;
25
+ baseUrl: string;
26
+ }>;
27
+ requestToFallbacks: import("@lodestar/utils").GaugeExtra<{
28
+ routeId: string;
29
+ baseUrl: string;
30
+ }>;
31
+ urlsScore: import("@lodestar/utils").GaugeExtra<{
32
+ urlIndex: number;
33
+ baseUrl: string;
34
+ }>;
35
+ };
36
+ };
4
37
  //# sourceMappingURL=metrics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,wBAAgB,UAAU,CAAC,SAAS,EAAE,oBAAoB,MAEzD"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAC,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG;IAC5B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAsC,CAAC;AAEpG,wBAAgB,UAAU,CAAC,QAAQ,EAAE,oBAAoB,EAAE,OAAO,EAAE,eAAe;IAU/E,aAAa;IAKb,cAAc;IAOd,aAAa;QACX,WAAW;qBAA+B,MAAM;;QAQhD,UAAU;qBAA+B,MAAM;;QAQ/C,aAAa;qBAA2B,MAAM;qBAAW,MAAM;;QAM/D,kBAAkB;qBAA2B,MAAM;qBAAW,MAAM;;QAMpE,SAAS;sBAA4B,MAAM;qBAAW,MAAM;;;EAOjE"}
package/lib/metrics.js CHANGED
@@ -1,4 +1,53 @@
1
- export function getMetrics(_register) {
2
- return {};
1
+ export const builderStatusValue = { pending: 0, active: 1, exited: 2 };
2
+ export function getMetrics(register, gitData) {
3
+ register
4
+ .gauge({
5
+ name: "lodestar_version",
6
+ help: "Lodestar version",
7
+ labelNames: Object.keys(gitData),
8
+ })
9
+ .set(gitData, 1);
10
+ return {
11
+ builderStatus: register.gauge({
12
+ name: "bc_builder_status",
13
+ help: "Current builder status: pending=0, active=1, exited=2",
14
+ }),
15
+ builderBalance: register.gauge({
16
+ name: "bc_builder_balance_gwei",
17
+ help: "Current builder balance in gwei",
18
+ }),
19
+ // REST API client
20
+ restApiClient: {
21
+ requestTime: register.histogram({
22
+ name: "bc_rest_api_client_request_time_seconds",
23
+ help: "Histogram of REST API client request time by routeId",
24
+ labelNames: ["routeId"],
25
+ // Expected times are ~ 50-500ms, but in an overload NodeJS they can be greater
26
+ buckets: [0.01, 0.1, 1, 2, 5],
27
+ }),
28
+ streamTime: register.histogram({
29
+ name: "bc_rest_api_client_stream_time_seconds",
30
+ help: "Histogram of REST API client streaming time by routeId",
31
+ labelNames: ["routeId"],
32
+ // Expected times are ~ 50-500ms, but in an overload NodeJS they can be greater
33
+ buckets: [0.01, 0.1, 1, 2, 5],
34
+ }),
35
+ requestErrors: register.gauge({
36
+ name: "bc_rest_api_client_request_errors_total",
37
+ help: "Total count of errors on REST API client requests by routeId",
38
+ labelNames: ["routeId", "baseUrl"],
39
+ }),
40
+ requestToFallbacks: register.gauge({
41
+ name: "bc_rest_api_client_request_to_fallbacks_total",
42
+ help: "Total count of requests to fallback URLs on REST API by routeId",
43
+ labelNames: ["routeId", "baseUrl"],
44
+ }),
45
+ urlsScore: register.gauge({
46
+ name: "bc_rest_api_client_urls_score",
47
+ help: "Current score of REST API URLs by url index",
48
+ labelNames: ["urlIndex", "baseUrl"],
49
+ }),
50
+ },
51
+ };
3
52
  }
4
53
  //# sourceMappingURL=metrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,UAAU,CAAC,SAA+B;IACxD,OAAO,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,kBAAkB,GAAkC,EAAC,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC,CAAC;AAEpG,MAAM,UAAU,UAAU,CAAC,QAA8B,EAAE,OAAwB;IACjF,QAAQ;SACL,KAAK,CAAkB;QACtB,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,kBAAkB;QACxB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAA4B;KAC5D,CAAC;SACD,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAEnB,OAAO;QACL,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC;YAC5B,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,uDAAuD;SAC9D,CAAC;QAEF,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC;YAC7B,IAAI,EAAE,yBAAyB;YAC/B,IAAI,EAAE,iCAAiC;SACxC,CAAC;QAEF,kBAAkB;QAElB,aAAa,EAAE;YACb,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAoB;gBACjD,IAAI,EAAE,yCAAyC;gBAC/C,IAAI,EAAE,sDAAsD;gBAC5D,UAAU,EAAE,CAAC,SAAS,CAAC;gBACvB,+EAA+E;gBAC/E,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;aAC9B,CAAC;YAEF,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAoB;gBAChD,IAAI,EAAE,wCAAwC;gBAC9C,IAAI,EAAE,wDAAwD;gBAC9D,UAAU,EAAE,CAAC,SAAS,CAAC;gBACvB,+EAA+E;gBAC/E,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;aAC9B,CAAC;YAEF,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAqC;gBAChE,IAAI,EAAE,yCAAyC;gBAC/C,IAAI,EAAE,8DAA8D;gBACpE,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;aACnC,CAAC;YAEF,kBAAkB,EAAE,QAAQ,CAAC,KAAK,CAAqC;gBACrE,IAAI,EAAE,+CAA+C;gBACrD,IAAI,EAAE,iEAAiE;gBACvE,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;aACnC,CAAC;YAEF,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAsC;gBAC7D,IAAI,EAAE,+BAA+B;gBACrC,IAAI,EAAE,6CAA6C;gBACnD,UAAU,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;aACpC,CAAC;SACH;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { ApiClient } from "@lodestar/api";
2
+ import { Logger } from "@lodestar/utils";
3
+ export declare function waitForNodeReady(api: ApiClient, logger: Logger, signal: AbortSignal): Promise<void>;
4
+ export declare function logNodeVersion(api: ApiClient, logger: Logger): Promise<void>;
5
+ //# sourceMappingURL=readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,MAAM,EAAQ,MAAM,iBAAiB,CAAC;AAK9C,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzG;AA6CD,wBAAsB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAalF"}
@@ -0,0 +1,58 @@
1
+ import { sleep } from "@lodestar/utils";
2
+ /** The time between polls when waiting for BN to be ready */
3
+ const WAITING_FOR_NODE_READY_POLL_MS = 5 * 1000;
4
+ export async function waitForNodeReady(api, logger, signal) {
5
+ while (!(await isNodeReady(api, logger))) {
6
+ await sleep(WAITING_FOR_NODE_READY_POLL_MS, signal);
7
+ }
8
+ }
9
+ async function isNodeReady(api, logger) {
10
+ try {
11
+ const syncingStatusRes = await api.node.getSyncingStatus();
12
+ if (!syncingStatusRes.ok) {
13
+ logger.warn("Cannot get node sync status", {
14
+ status: syncingStatusRes.status,
15
+ message: syncingStatusRes.error()?.message,
16
+ });
17
+ return false;
18
+ }
19
+ const syncingStatus = syncingStatusRes.value();
20
+ if (syncingStatus.isSyncing || syncingStatus.elOffline) {
21
+ logger.info(syncingStatus.elOffline ? "Beacon node EL is offline, unable to submit bids" : "Beacon node is not ready yet", {
22
+ headSlot: syncingStatus.headSlot,
23
+ syncDistance: syncingStatus.syncDistance,
24
+ elOffline: syncingStatus.elOffline,
25
+ });
26
+ return false;
27
+ }
28
+ if (syncingStatus.isOptimistic) {
29
+ logger.warn("Beacon node head is optimistic, execution payloads are not yet verified - unable to submit bids", {
30
+ headSlot: syncingStatus.headSlot,
31
+ syncDistance: syncingStatus.syncDistance,
32
+ });
33
+ return false;
34
+ }
35
+ logger.info("Beacon node is ready", { headSlot: syncingStatus.headSlot });
36
+ return true;
37
+ }
38
+ catch (e) {
39
+ logger.error("Cannot reach the beacon node", {}, e);
40
+ return false;
41
+ }
42
+ }
43
+ export async function logNodeVersion(api, logger) {
44
+ try {
45
+ const versionRes = await api.node.getNodeVersionV2();
46
+ const version = versionRes.value();
47
+ logger.info("Connected node version", {
48
+ beaconNode: `${version.beaconNode.name}/${version.beaconNode.version}`,
49
+ executionClient: version.executionClient
50
+ ? `${version.executionClient.name}/${version.executionClient.version}`
51
+ : "unknown",
52
+ });
53
+ }
54
+ catch (e) {
55
+ logger.warn("Failed to get node version", {}, e);
56
+ }
57
+ }
58
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAE9C,6DAA6D;AAC7D,MAAM,8BAA8B,GAAG,CAAC,GAAG,IAAI,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAc,EAAE,MAAc,EAAE,MAAmB;IACxF,OAAO,CAAC,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,KAAK,CAAC,8BAA8B,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,GAAc,EAAE,MAAc;IACvD,IAAI,CAAC;QACH,MAAM,gBAAgB,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE3D,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;gBACzC,MAAM,EAAE,gBAAgB,CAAC,MAAM;gBAC/B,OAAO,EAAE,gBAAgB,CAAC,KAAK,EAAE,EAAE,OAAO;aAC3C,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAE/C,IAAI,aAAa,CAAC,SAAS,IAAI,aAAa,CAAC,SAAS,EAAE,CAAC;YACvD,MAAM,CAAC,IAAI,CACT,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,kDAAkD,CAAC,CAAC,CAAC,8BAA8B,EAC7G;gBACE,QAAQ,EAAE,aAAa,CAAC,QAAQ;gBAChC,YAAY,EAAE,aAAa,CAAC,YAAY;gBACxC,SAAS,EAAE,aAAa,CAAC,SAAS;aACnC,CACF,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,aAAa,CAAC,YAAY,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,iGAAiG,EAAE;gBAC7G,QAAQ,EAAE,aAAa,CAAC,QAAQ;gBAChC,YAAY,EAAE,aAAa,CAAC,YAAY;aACzC,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,EAAC,CAAC,CAAC;QAExE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,EAAE,EAAE,CAAU,CAAC,CAAC;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAc,EAAE,MAAc;IACjE,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACpC,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE;YACtE,eAAe,EAAE,OAAO,CAAC,eAAe;gBACtC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC,OAAO,EAAE;gBACtE,CAAC,CAAC,SAAS;SACd,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,EAAE,CAAU,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { ApiClient } from "@lodestar/api";
2
+ import { BuilderIndex, BuilderStatus, Epoch } from "@lodestar/types";
3
+ import { Logger } from "@lodestar/utils";
4
+ import { Metrics } from "../metrics.js";
5
+ /**
6
+ * Service for tracking builder status.
7
+ * Provides regular builder status and balance updates for operator diagnostics.
8
+ */
9
+ export declare class BuilderStatusTracker {
10
+ private readonly api;
11
+ private readonly logger;
12
+ private readonly index;
13
+ private readonly metrics;
14
+ private status?;
15
+ private balanceGwei?;
16
+ constructor(api: ApiClient, logger: Logger, index: BuilderIndex, metrics: Metrics | null);
17
+ poll(currentEpoch: Epoch): Promise<void>;
18
+ getStatus(): {
19
+ status: BuilderStatus | undefined;
20
+ balance: number | undefined;
21
+ };
22
+ }
23
+ //# sourceMappingURL=builderStatusTracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builderStatusTracker.d.ts","sourceRoot":"","sources":["../../src/services/builderStatusTracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,eAAe,CAAC;AACxC,OAAO,EAAC,YAAY,EAAE,aAAa,EAAE,KAAK,EAAC,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAC,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAC,OAAO,EAAqB,MAAM,eAAe,CAAC;AAE1D;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IAEzC,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAC,CAAS;IAE7B,YAAY,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,EAKvF;IAEK,IAAI,CAAC,YAAY,EAAE,KAAK,iBAgB7B;IAED,SAAS,IAAI;QAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAC,CAK5E;CACF"}
@@ -0,0 +1,44 @@
1
+ import { getBuilderStatus } from "../identity.js";
2
+ import { builderStatusValue } from "../metrics.js";
3
+ /**
4
+ * Service for tracking builder status.
5
+ * Provides regular builder status and balance updates for operator diagnostics.
6
+ */
7
+ export class BuilderStatusTracker {
8
+ api;
9
+ logger;
10
+ index;
11
+ metrics;
12
+ status;
13
+ balanceGwei;
14
+ constructor(api, logger, index, metrics) {
15
+ this.api = api;
16
+ this.logger = logger;
17
+ this.index = index;
18
+ this.metrics = metrics;
19
+ }
20
+ async poll(currentEpoch) {
21
+ const builderStatus = await getBuilderStatus(this.api, this.logger, this.index);
22
+ if (builderStatus !== null) {
23
+ if (this.status !== undefined && this.status !== builderStatus.status) {
24
+ this.logger.info("Builder status changed", { from: this.status, to: builderStatus.status, epoch: currentEpoch });
25
+ }
26
+ this.status = builderStatus.status;
27
+ this.balanceGwei = builderStatus.balance;
28
+ this.logger.info("Builder status", {
29
+ status: builderStatus.status,
30
+ balance: builderStatus.balance,
31
+ epoch: currentEpoch,
32
+ });
33
+ this.metrics?.builderStatus.set(builderStatusValue[builderStatus.status]);
34
+ this.metrics?.builderBalance.set(builderStatus.balance);
35
+ }
36
+ }
37
+ getStatus() {
38
+ return {
39
+ status: this.status,
40
+ balance: this.balanceGwei,
41
+ };
42
+ }
43
+ }
44
+ //# sourceMappingURL=builderStatusTracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builderStatusTracker.js","sourceRoot":"","sources":["../../src/services/builderStatusTracker.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,gBAAgB,EAAC,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAU,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAE1D;;;GAGG;AACH,MAAM,OAAO,oBAAoB;IACd,GAAG,CAAY;IACf,MAAM,CAAS;IACf,KAAK,CAAe;IACpB,OAAO,CAAiB;IAEjC,MAAM,CAAiB;IACvB,WAAW,CAAU;IAE7B,YAAY,GAAc,EAAE,MAAc,EAAE,KAAmB,EAAE,OAAuB;QACtF,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,YAAmB;QAC5B,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAChF,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;gBACtE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,EAAC,CAAC,CAAC;YACjH,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;YACnC,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBACjC,MAAM,EAAE,aAAa,CAAC,MAAM;gBAC5B,OAAO,EAAE,aAAa,CAAC,OAAO;gBAC9B,KAAK,EAAE,YAAY;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,kBAAkB,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;YAC1E,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,WAAW;SAC1B,CAAC;IACJ,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodestar/builder",
3
- "version": "1.47.0-dev.d9c16ea16f",
3
+ "version": "1.47.0-dev.e91bc3e859",
4
4
  "description": "A Typescript implementation of the builder client",
5
5
  "author": "ChainSafe Systems",
6
6
  "license": "Apache-2.0",
@@ -48,16 +48,16 @@
48
48
  "dependencies": {
49
49
  "@chainsafe/blst": "^2.2.0",
50
50
  "@chainsafe/ssz": "^1.6.2",
51
- "@lodestar/api": "^1.47.0-dev.d9c16ea16f",
52
- "@lodestar/config": "^1.47.0-dev.d9c16ea16f",
53
- "@lodestar/params": "^1.47.0-dev.d9c16ea16f",
54
- "@lodestar/state-transition": "^1.47.0-dev.d9c16ea16f",
55
- "@lodestar/types": "^1.47.0-dev.d9c16ea16f",
56
- "@lodestar/utils": "^1.47.0-dev.d9c16ea16f"
51
+ "@lodestar/api": "^1.47.0-dev.e91bc3e859",
52
+ "@lodestar/config": "^1.47.0-dev.e91bc3e859",
53
+ "@lodestar/params": "^1.47.0-dev.e91bc3e859",
54
+ "@lodestar/state-transition": "^1.47.0-dev.e91bc3e859",
55
+ "@lodestar/types": "^1.47.0-dev.e91bc3e859",
56
+ "@lodestar/utils": "^1.47.0-dev.e91bc3e859"
57
57
  },
58
58
  "devDependencies": {
59
- "@lodestar/logger": "^1.47.0-dev.d9c16ea16f",
60
- "@lodestar/test-utils": "^1.47.0-dev.d9c16ea16f"
59
+ "@lodestar/logger": "^1.47.0-dev.e91bc3e859",
60
+ "@lodestar/test-utils": "^1.47.0-dev.e91bc3e859"
61
61
  },
62
- "gitHead": "8f55fe85416ebef254c015d9d7bd2cc8735c4d66"
62
+ "gitHead": "1bf64c513fe8c47184808c7fca42bed23ad28277"
63
63
  }
package/src/builder.ts CHANGED
@@ -1,15 +1,21 @@
1
- import {ApiClient, routes} from "@lodestar/api";
1
+ import {ApiClient} from "@lodestar/api";
2
2
  import {ChainForkConfig, assertEqualParams, createBeaconConfig} from "@lodestar/config";
3
- import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
4
3
  import {Clock, ClockOptions, IClock} from "@lodestar/state-transition";
5
- import {Logger} from "@lodestar/utils";
4
+ import {BuilderIndex, ExecutionAddress} from "@lodestar/types";
5
+ import {Logger, toHex, toRootHex} from "@lodestar/utils";
6
6
  import {waitForGenesis} from "./genesis.js";
7
+ import {resolveBuilderIdentity} from "./identity.js";
8
+ import {Metrics} from "./metrics.js";
9
+ import {logNodeVersion, waitForNodeReady} from "./readiness.js";
7
10
  import {BuilderSigner, Keypair} from "./services/builderSigner.js";
11
+ import {BuilderStatusTracker} from "./services/builderStatusTracker.js";
8
12
 
9
13
  export type BuilderModules = {
10
14
  opts: BuilderOptions;
11
15
  builderSigner: BuilderSigner;
16
+ builderStatusTracker: BuilderStatusTracker;
12
17
  clock: IClock;
18
+ index: BuilderIndex;
13
19
  };
14
20
 
15
21
  export type BuilderOptions = {
@@ -19,6 +25,8 @@ export type BuilderOptions = {
19
25
  abortController: AbortController;
20
26
  api: ApiClient;
21
27
  clock?: ClockOptions;
28
+ executionFeeRecipient: ExecutionAddress;
29
+ metrics: Metrics | null;
22
30
  };
23
31
 
24
32
  /**
@@ -26,58 +34,63 @@ export type BuilderOptions = {
26
34
  */
27
35
  export class Builder {
28
36
  readonly builderSigner: BuilderSigner;
37
+ private readonly builderStatusTracker: BuilderStatusTracker;
29
38
  private readonly controller: AbortController;
30
39
  private readonly clock: IClock;
40
+ private readonly index: BuilderIndex;
41
+ private readonly logger: Logger;
42
+ private readonly executionFeeRecipient: ExecutionAddress;
31
43
 
32
- constructor({opts, builderSigner, clock}: BuilderModules) {
44
+ constructor({opts, builderSigner, builderStatusTracker, clock, index}: BuilderModules) {
33
45
  this.builderSigner = builderSigner;
46
+ this.builderStatusTracker = builderStatusTracker;
34
47
  this.clock = clock;
35
48
  this.controller = opts.abortController;
49
+ this.logger = opts.logger;
50
+ this.index = index;
36
51
 
52
+ this.executionFeeRecipient = opts.executionFeeRecipient;
53
+
54
+ this.clock.runEveryEpoch((epoch) => this.builderStatusTracker.poll(epoch));
37
55
  this.clock.start(this.controller.signal);
56
+
57
+ this.logger.info("Builder client initialized", {
58
+ index: this.index,
59
+ executionFeeRecipient: toHex(this.executionFeeRecipient),
60
+ });
38
61
  }
39
62
 
40
63
  static async init(opts: BuilderOptions): Promise<Builder> {
41
- const genesis = await waitForGenesis(opts.api, opts.logger, opts.abortController.signal);
42
- opts.logger.info("Genesis fetched from the beacon node");
64
+ const {api, logger} = opts;
65
+ const genesis = await waitForGenesis(api, logger, opts.abortController.signal);
66
+ logger.info("Genesis fetched from the beacon node", {
67
+ genesisValidatorsRoot: toRootHex(genesis.genesisValidatorsRoot),
68
+ });
43
69
 
44
- const specRes = await opts.api.config.getSpec();
70
+ const specRes = await api.config.getSpec();
45
71
  assertEqualParams(opts.config, specRes.value());
46
- opts.logger.info("Verified connected beacon node and builder have the same config");
72
+ logger.info("Verified connected beacon node and builder have the same config");
47
73
 
48
74
  const config = createBeaconConfig(opts.config, genesis.genesisValidatorsRoot);
49
75
  const builderSigner = new BuilderSigner(config, opts.keypair);
50
76
 
51
- const builderRes = await opts.api.beacon.getStateBuilders({
52
- stateId: "head",
53
- builderIds: [builderSigner.getPubkeyHex()],
54
- });
55
-
56
- if (!builderRes.ok) {
57
- throw Error(`Getting state builders from BN failed: ${builderRes.status}`);
58
- }
59
-
60
- const builders = builderRes.value();
61
-
62
- if (builders.length === 0) {
63
- throw Error(`Builder not registered: ${builderSigner.getPubkeyHex()}`);
64
- }
65
-
66
- const builderStatus: routes.beacon.BuilderResponse = builders[0];
77
+ await waitForNodeReady(api, logger, opts.abortController.signal);
78
+ await logNodeVersion(api, logger);
67
79
 
68
- if (builderStatus.status !== "active") {
69
- throw Error(`Builder not active: ${builderStatus.status}`);
70
- }
80
+ const clock = new Clock(config, logger, {genesisTime: Number(genesis.genesisTime), ...opts.clock});
71
81
 
72
- if (builderStatus.builder.version !== PAYLOAD_BUILDER_VERSION) {
73
- throw Error(
74
- `Builder version mismatch: got ${builderStatus.builder.version}, expected ${PAYLOAD_BUILDER_VERSION}`
75
- );
76
- }
82
+ const index = await resolveBuilderIdentity(
83
+ api,
84
+ logger,
85
+ builderSigner.getPubkeyHex(),
86
+ opts.abortController.signal,
87
+ clock,
88
+ config
89
+ );
77
90
 
78
- const clock = new Clock(config, opts.logger, {genesisTime: Number(genesis.genesisTime), ...opts.clock});
91
+ const builderStatusTracker = new BuilderStatusTracker(api, logger, index, opts.metrics);
79
92
 
80
- return new Builder({opts, builderSigner, clock});
93
+ return new Builder({opts, builderSigner, builderStatusTracker, clock, index});
81
94
  }
82
95
 
83
96
  async close(): Promise<void> {
package/src/defaults.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export const defaultOptions = {
2
2
  // Source beacon node the builder connects to
3
3
  beaconNodeUrl: "http://127.0.0.1:9596",
4
+ requestTimeout: 10_000,
4
5
  };
package/src/genesis.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {ApiClient, ApiError, HttpStatusCode} from "@lodestar/api";
2
2
  import {Genesis} from "@lodestar/types/phase0";
3
- import {Logger, sleep} from "@lodestar/utils";
3
+ import {ErrorAborted, Logger, sleep} from "@lodestar/utils";
4
4
 
5
5
  /** The time between polls when waiting for genesis */
6
6
  const WAITING_FOR_GENESIS_POLL_MS = 12 * 1000;
7
7
 
8
- export async function waitForGenesis(api: ApiClient, logger: Logger, signal?: AbortSignal): Promise<Genesis> {
9
- while (true) {
8
+ export async function waitForGenesis(api: ApiClient, logger: Logger, signal: AbortSignal): Promise<Genesis> {
9
+ while (!signal.aborted) {
10
10
  try {
11
11
  return (await api.beacon.getGenesis()).value();
12
12
  } catch (e) {
@@ -18,4 +18,5 @@ export async function waitForGenesis(api: ApiClient, logger: Logger, signal?: Ab
18
18
  await sleep(WAITING_FOR_GENESIS_POLL_MS, signal);
19
19
  }
20
20
  }
21
+ throw new ErrorAborted("waitForGenesis");
21
22
  }
@@ -0,0 +1,138 @@
1
+ import {ApiClient, ApiError, HttpStatusCode, routes} from "@lodestar/api";
2
+ import {ChainForkConfig} from "@lodestar/config";
3
+ import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
4
+ import {IClock} from "@lodestar/state-transition";
5
+ import {BuilderIndex, BuilderStatus} from "@lodestar/types";
6
+ import {ErrorAborted, Logger, sleep, toHex} from "@lodestar/utils";
7
+
8
+ export const WAITING_FOR_BUILDER_POLL_MS = 10 * 1000;
9
+
10
+ export async function resolveBuilderIdentity(
11
+ api: ApiClient,
12
+ logger: Logger,
13
+ id: routes.beacon.BuilderId,
14
+ signal: AbortSignal,
15
+ clock: IClock,
16
+ config: ChainForkConfig
17
+ ): Promise<BuilderIndex> {
18
+ const builderEntry = await waitForBuilder(api, logger, id, signal, clock, config);
19
+
20
+ if (builderEntry.builder.version !== PAYLOAD_BUILDER_VERSION) {
21
+ throw Error(`Builder version mismatch: got ${builderEntry.builder.version}, expected ${PAYLOAD_BUILDER_VERSION}`);
22
+ }
23
+
24
+ logger.info("Builder identity resolved", {
25
+ index: builderEntry.index,
26
+ status: builderEntry.status,
27
+ balanceGwei: builderEntry.builder.balance,
28
+ executionAddress: toHex(builderEntry.builder.executionAddress),
29
+ slot: clock.getCurrentSlot(),
30
+ });
31
+
32
+ return builderEntry.index;
33
+ }
34
+
35
+ export async function getBuilderStatus(
36
+ api: ApiClient,
37
+ logger: Logger,
38
+ id: routes.beacon.BuilderId
39
+ ): Promise<{status: BuilderStatus; balance: number} | null> {
40
+ try {
41
+ const builderEntry = await fetchBuilder(api, id);
42
+ if (builderEntry) {
43
+ return {
44
+ status: builderEntry.status,
45
+ balance: builderEntry.builder.balance,
46
+ };
47
+ }
48
+ logger.warn("Builder status not available in beacon node");
49
+ return null;
50
+ } catch (e) {
51
+ logger.warn("Couldn't fetch the builder", {}, e as Error);
52
+ return null;
53
+ }
54
+ }
55
+
56
+ async function waitForBuilder(
57
+ api: ApiClient,
58
+ logger: Logger,
59
+ id: routes.beacon.BuilderId,
60
+ signal: AbortSignal,
61
+ clock: IClock,
62
+ config: ChainForkConfig
63
+ ): Promise<routes.beacon.BuilderResponse> {
64
+ const gloasForkEpoch = config.GLOAS_FORK_EPOCH;
65
+ while (!signal.aborted) {
66
+ const currentEpoch = clock.getCurrentEpoch();
67
+ if (currentEpoch < gloasForkEpoch) {
68
+ // Builders only exist post-gloas, the beacon node returns an error for pre-gloas state
69
+ logger.info("Waiting for Gloas fork before resolving builder identity", {
70
+ gloasForkEpoch,
71
+ currentEpoch,
72
+ slot: clock.getCurrentSlot(),
73
+ });
74
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
75
+ continue;
76
+ }
77
+
78
+ let builder: routes.beacon.BuilderResponse | null = null;
79
+ try {
80
+ builder = await fetchBuilder(api, id);
81
+ } catch (e) {
82
+ // At the fork boundary getStateBuilders("head") can still 400: it serves the head block's
83
+ // post-state, which stays pre-gloas until a gloas-epoch block is head. Keep polling on the transient.
84
+ if (e instanceof ApiError && e.status === HttpStatusCode.BAD_REQUEST) {
85
+ logger.info("Waiting for Gloas state to be available at head", {
86
+ gloasForkEpoch,
87
+ currentEpoch,
88
+ slot: clock.getCurrentSlot(),
89
+ });
90
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
91
+ continue;
92
+ }
93
+ throw e;
94
+ }
95
+
96
+ if (builder?.status === "active") {
97
+ return builder;
98
+ }
99
+ if (builder?.status === "exited") {
100
+ throw Error(`Builder exited: id=${id}`);
101
+ }
102
+ if (builder?.status === "pending") {
103
+ logger.info("Waiting for builder deposit to be finalized", {id, slot: clock.getCurrentSlot()});
104
+ } else {
105
+ logger.info("Waiting for builder to be known to the beacon node", {id, slot: clock.getCurrentSlot()});
106
+ }
107
+ await sleep(WAITING_FOR_BUILDER_POLL_MS, signal);
108
+ }
109
+ throw new ErrorAborted("waitForBuilder");
110
+ }
111
+
112
+ async function fetchBuilder(
113
+ api: ApiClient,
114
+ id: routes.beacon.BuilderId
115
+ ): Promise<routes.beacon.BuilderResponse | null> {
116
+ const builderRes = await api.beacon.getStateBuilders({
117
+ stateId: "head",
118
+ builderIds: [id],
119
+ });
120
+
121
+ const builders = builderRes.value();
122
+
123
+ if (builders.length === 0) {
124
+ return null;
125
+ }
126
+
127
+ const builder = builders[0];
128
+
129
+ if (typeof id === "number") {
130
+ if (id !== builder.index) {
131
+ throw Error(`Index mismatch: got=${builder.index} expected=${id}`);
132
+ }
133
+ } else if (id !== toHex(builder.builder.pubkey)) {
134
+ throw Error(`Pubkey mismatch: got=${toHex(builder.builder.pubkey)} expected=${id}`);
135
+ }
136
+
137
+ return builder;
138
+ }
package/src/metrics.ts CHANGED
@@ -1,7 +1,75 @@
1
+ import {BuilderStatus} from "@lodestar/types";
1
2
  import {MetricsRegisterExtra} from "@lodestar/utils";
2
3
 
3
4
  export type Metrics = ReturnType<typeof getMetrics>;
4
5
 
5
- export function getMetrics(_register: MetricsRegisterExtra) {
6
- return {};
6
+ export type LodestarGitData = {
7
+ /** "0.16.0 developer/feature-1 4f816b16" */
8
+ version: string;
9
+ /** "4f816b16dfde718e2d74f95f2c8292596138c248" */
10
+ commit: string;
11
+ /** "hoodi" */
12
+ network: string;
13
+ };
14
+
15
+ export const builderStatusValue: Record<BuilderStatus, number> = {pending: 0, active: 1, exited: 2};
16
+
17
+ export function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitData) {
18
+ register
19
+ .gauge<LodestarGitData>({
20
+ name: "lodestar_version",
21
+ help: "Lodestar version",
22
+ labelNames: Object.keys(gitData) as [keyof LodestarGitData],
23
+ })
24
+ .set(gitData, 1);
25
+
26
+ return {
27
+ builderStatus: register.gauge({
28
+ name: "bc_builder_status",
29
+ help: "Current builder status: pending=0, active=1, exited=2",
30
+ }),
31
+
32
+ builderBalance: register.gauge({
33
+ name: "bc_builder_balance_gwei",
34
+ help: "Current builder balance in gwei",
35
+ }),
36
+
37
+ // REST API client
38
+
39
+ restApiClient: {
40
+ requestTime: register.histogram<{routeId: string}>({
41
+ name: "bc_rest_api_client_request_time_seconds",
42
+ help: "Histogram of REST API client request time by routeId",
43
+ labelNames: ["routeId"],
44
+ // Expected times are ~ 50-500ms, but in an overload NodeJS they can be greater
45
+ buckets: [0.01, 0.1, 1, 2, 5],
46
+ }),
47
+
48
+ streamTime: register.histogram<{routeId: string}>({
49
+ name: "bc_rest_api_client_stream_time_seconds",
50
+ help: "Histogram of REST API client streaming time by routeId",
51
+ labelNames: ["routeId"],
52
+ // Expected times are ~ 50-500ms, but in an overload NodeJS they can be greater
53
+ buckets: [0.01, 0.1, 1, 2, 5],
54
+ }),
55
+
56
+ requestErrors: register.gauge<{routeId: string; baseUrl: string}>({
57
+ name: "bc_rest_api_client_request_errors_total",
58
+ help: "Total count of errors on REST API client requests by routeId",
59
+ labelNames: ["routeId", "baseUrl"],
60
+ }),
61
+
62
+ requestToFallbacks: register.gauge<{routeId: string; baseUrl: string}>({
63
+ name: "bc_rest_api_client_request_to_fallbacks_total",
64
+ help: "Total count of requests to fallback URLs on REST API by routeId",
65
+ labelNames: ["routeId", "baseUrl"],
66
+ }),
67
+
68
+ urlsScore: register.gauge<{urlIndex: number; baseUrl: string}>({
69
+ name: "bc_rest_api_client_urls_score",
70
+ help: "Current score of REST API URLs by url index",
71
+ labelNames: ["urlIndex", "baseUrl"],
72
+ }),
73
+ },
74
+ };
7
75
  }
@@ -0,0 +1,69 @@
1
+ import {ApiClient} from "@lodestar/api";
2
+ import {Logger, sleep} from "@lodestar/utils";
3
+
4
+ /** The time between polls when waiting for BN to be ready */
5
+ const WAITING_FOR_NODE_READY_POLL_MS = 5 * 1000;
6
+
7
+ export async function waitForNodeReady(api: ApiClient, logger: Logger, signal: AbortSignal): Promise<void> {
8
+ while (!(await isNodeReady(api, logger))) {
9
+ await sleep(WAITING_FOR_NODE_READY_POLL_MS, signal);
10
+ }
11
+ }
12
+
13
+ async function isNodeReady(api: ApiClient, logger: Logger): Promise<boolean> {
14
+ try {
15
+ const syncingStatusRes = await api.node.getSyncingStatus();
16
+
17
+ if (!syncingStatusRes.ok) {
18
+ logger.warn("Cannot get node sync status", {
19
+ status: syncingStatusRes.status,
20
+ message: syncingStatusRes.error()?.message,
21
+ });
22
+ return false;
23
+ }
24
+
25
+ const syncingStatus = syncingStatusRes.value();
26
+
27
+ if (syncingStatus.isSyncing || syncingStatus.elOffline) {
28
+ logger.info(
29
+ syncingStatus.elOffline ? "Beacon node EL is offline, unable to submit bids" : "Beacon node is not ready yet",
30
+ {
31
+ headSlot: syncingStatus.headSlot,
32
+ syncDistance: syncingStatus.syncDistance,
33
+ elOffline: syncingStatus.elOffline,
34
+ }
35
+ );
36
+ return false;
37
+ }
38
+
39
+ if (syncingStatus.isOptimistic) {
40
+ logger.warn("Beacon node head is optimistic, execution payloads are not yet verified - unable to submit bids", {
41
+ headSlot: syncingStatus.headSlot,
42
+ syncDistance: syncingStatus.syncDistance,
43
+ });
44
+ return false;
45
+ }
46
+
47
+ logger.info("Beacon node is ready", {headSlot: syncingStatus.headSlot});
48
+
49
+ return true;
50
+ } catch (e) {
51
+ logger.error("Cannot reach the beacon node", {}, e as Error);
52
+ return false;
53
+ }
54
+ }
55
+
56
+ export async function logNodeVersion(api: ApiClient, logger: Logger): Promise<void> {
57
+ try {
58
+ const versionRes = await api.node.getNodeVersionV2();
59
+ const version = versionRes.value();
60
+ logger.info("Connected node version", {
61
+ beaconNode: `${version.beaconNode.name}/${version.beaconNode.version}`,
62
+ executionClient: version.executionClient
63
+ ? `${version.executionClient.name}/${version.executionClient.version}`
64
+ : "unknown",
65
+ });
66
+ } catch (e) {
67
+ logger.warn("Failed to get node version", {}, e as Error);
68
+ }
69
+ }
@@ -0,0 +1,51 @@
1
+ import {ApiClient} from "@lodestar/api";
2
+ import {BuilderIndex, BuilderStatus, Epoch} from "@lodestar/types";
3
+ import {Logger} from "@lodestar/utils";
4
+ import {getBuilderStatus} from "../identity.js";
5
+ import {Metrics, builderStatusValue} from "../metrics.js";
6
+
7
+ /**
8
+ * Service for tracking builder status.
9
+ * Provides regular builder status and balance updates for operator diagnostics.
10
+ */
11
+ export class BuilderStatusTracker {
12
+ private readonly api: ApiClient;
13
+ private readonly logger: Logger;
14
+ private readonly index: BuilderIndex;
15
+ private readonly metrics: Metrics | null;
16
+
17
+ private status?: BuilderStatus;
18
+ private balanceGwei?: number;
19
+
20
+ constructor(api: ApiClient, logger: Logger, index: BuilderIndex, metrics: Metrics | null) {
21
+ this.api = api;
22
+ this.logger = logger;
23
+ this.index = index;
24
+ this.metrics = metrics;
25
+ }
26
+
27
+ async poll(currentEpoch: Epoch) {
28
+ const builderStatus = await getBuilderStatus(this.api, this.logger, this.index);
29
+ if (builderStatus !== null) {
30
+ if (this.status !== undefined && this.status !== builderStatus.status) {
31
+ this.logger.info("Builder status changed", {from: this.status, to: builderStatus.status, epoch: currentEpoch});
32
+ }
33
+ this.status = builderStatus.status;
34
+ this.balanceGwei = builderStatus.balance;
35
+ this.logger.info("Builder status", {
36
+ status: builderStatus.status,
37
+ balance: builderStatus.balance,
38
+ epoch: currentEpoch,
39
+ });
40
+ this.metrics?.builderStatus.set(builderStatusValue[builderStatus.status]);
41
+ this.metrics?.builderBalance.set(builderStatus.balance);
42
+ }
43
+ }
44
+
45
+ getStatus(): {status: BuilderStatus | undefined; balance: number | undefined} {
46
+ return {
47
+ status: this.status,
48
+ balance: this.balanceGwei,
49
+ };
50
+ }
51
+ }