Skip to content

feat(builder): add readiness gating and builder identity resolution - #9781

Draft
markolazic01 wants to merge 35 commits into
ChainSafe:unstablefrom
markolazic01:feat/builder-api-01
Draft

feat(builder): add readiness gating and builder identity resolution#9781
markolazic01 wants to merge 35 commits into
ChainSafe:unstablefrom
markolazic01:feat/builder-api-01

Conversation

@markolazic01

Copy link
Copy Markdown
Contributor

Motivation

Further development of ePBS builder entity.

Description

  • Extracts builder identity resolution to identity.ts and expands basic functionality into resolveBuilderIdentity and getBuilderStatus.
  • Introduces readiness.ts, containing waitForNodeReady function which polls until the BN is synced and its EL is online + logNodeVersion.
  • BuilderStatusTracker service for tracking builder status and balance.
  • --executionFeeRecipient cli option
  • Wiring for all features.

Lacks:

  • Metrics
  • Tests

Comment thread packages/builder/src/services/builderStatusTracker.ts
@markolazic01 markolazic01 changed the title feat(builder): beacon node readiness, builder identity resolution and execution fee recipient feat(builder): beacon node readiness, builder identity resolution and cli flags for execution fee recipient and request timeout Aug 7, 2026
@markolazic01
markolazic01 marked this pull request as ready for review August 9, 2026 12:19
@markolazic01
markolazic01 requested a review from a team as a code owner August 9, 2026 12:19
@markolazic01

Copy link
Copy Markdown
Contributor Author

Marking this ready. Leaving metrics and readiness tests as a follow-up.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 250ae7bff1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/builder/test/unit/utils/apiStub.ts

@nflaig nflaig left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a quick pass, generally looks pretty good

Comment thread packages/builder/src/services/builderStatusTracker.ts Outdated
Comment thread packages/builder/src/genesis.ts Outdated
Comment thread packages/builder/src/identity.ts Outdated

if (builderEntry.status !== "active") {
throw Error(`Builder not active: ${builderEntry.status}`);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if builder is deposited later, should this wait instead of throwing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can add the wait, probably on the fetchBuilder()'s length check:
https://github.com/markolazic01/lodestar/blob/51c3493b2b7cfd788d665b99611de9856d6307b1/packages/builder/src/identity.ts#L61
because that would indicate that the builder is not yet known, while status other than "active" indicates that builder exited.
How does this sound?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep that sounds good, the reason I brought this up is because it's quite common for operators today to import bls keys in the validator client even if those are completely unknown to the chain, but it's good so you can prepare your setup, then later, once those keys are deposited and become known to the chain, the validator clients picks them up automatically with no interaction from the operator. Similar ux would be great to have for the builder

Comment thread packages/builder/src/identity.ts Outdated

if (!builderRes.ok) {
throw Error(`Failed to get builder state from beacon node: ${builderRes.status}`);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be useful to also print out the error response from the beacon node

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applied, noting this requires await res.errorBody() first, as error() reads the error body synchronously.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noting this requires await res.errorBody()

I don't like this pattern, see #9781 (comment)

Comment thread packages/builder/src/readiness.ts
Comment thread packages/builder/src/readiness.ts Outdated
Comment thread packages/cli/src/cmds/builder/handler.ts Outdated
Comment thread packages/cli/src/cmds/builder/handler.ts
Comment thread packages/builder/src/identity.ts Outdated
Comment on lines +54 to +55
if (!builderRes.ok) {
await builderRes.errorBody();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this manual checking for ok and then handling the error like this, the api client is designed in a way that most of the time it's better to use .value() or .assertOk()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fixed now, there is another check in here:
https://github.com/markolazic01/lodestar/blob/7386db08081ce635662d6898c925036cd2015b71/packages/builder/src/readiness.ts#L17-L24
I left this one in because I haven't found a cleaner way to log the error message otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine but keep in mind that network errors will still throw (eg. host not reachable or any tcp error)

regarding http errors (which ok is related to) there shouldn't be any scenario where getSyncingStatus returns 500 or similar

so need to make sure to handle network error gracefully which usually requires a try/catch or .catch

Comment thread packages/builder/src/readiness.ts

export async function logNodeVersion(api: ApiClient, logger: Logger): Promise<void> {
try {
const versionRes = await api.node.getNodeVersionV2();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some clients may not have this api yet, but that's fine for now, we might need to bully them a bit to implement it, but since it's gloas related, it's fine to assume it's implemented by clients

Comment thread packages/cli/src/cmds/builder/handler.ts Outdated
Comment thread packages/builder/src/builder.ts Outdated
Comment thread packages/builder/src/readiness.ts
@nflaig

nflaig commented Aug 10, 2026

Copy link
Copy Markdown
Member

the title is a bit too long, would be good to make it more succinct, details are in the commit body afterwards based on the PR description

markolazic01 and others added 2 commits August 10, 2026 16:01
Co-authored-by: Nico Flaig <nflaig@protonmail.com>
Co-authored-by: Nico Flaig <nflaig@protonmail.com>
@markolazic01 markolazic01 changed the title feat(builder): beacon node readiness, builder identity resolution and cli flags for execution fee recipient and request timeout feat(builder): add readiness gating and builder identity resolution Aug 10, 2026
@markolazic01

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing, applied the suggestions and updated the description.

return apiResponse;
}

export async function mockApiErrorResponse<E extends Endpoint>(status: HttpStatusCode): Promise<ApiResponse<E>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe could dedupe these in packages/test-utils if it's 1:1 what the validator uses, would be rather a follow-up PR though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference between implementation here and in the builder would be the functions listed here:

export function getApiClientStub(): ApiClientStub {

but I think it's alright to have all the functions that consumers need in one place.
I also removed some unused parts of it here, but these two can be put in sync based on what is used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we tell lodekeeper to make an issue for it? So we don't forget about this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lodekeeper can you create an issue on this?
It's about adapting apiStub helper and moving it into a common place (packages/test-utils) after this PR is merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — opened #9819 to track this.

Captured the reconciliation points from the current files: mockApiResponse is already identical, the getApiClientStub function lists differ (builder is a minimal subset + getStateBuilders), and — one extra beyond the function list — the builder's mockApiErrorResponse is now async + await res.errorBody() vs the validator's sync version, so the dedup will need to reconcile that too.


if (syncingStatus.isOptimistic) {
logger.warn("Beacon node head is optimistic, execution payloads are not yet verified");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so for the operator it should be clear that in case the EL is syncing we won't be able to submit any bids

throw Error(`Builder not known to the beacon node: ${id}`);
}

return builders[0];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably sanity check the response from the beacon node to make sure it resolved "our" builder

Comment thread packages/builder/src/builder.ts Outdated
if (builderStatus.status !== "active") {
throw Error(`Builder not active: ${builderStatus.status}`);
}
const index = await resolveBuilderIdentity(api, logger, builderSigner.getPubkeyHex());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this still assumes the builder is active before starting up the builder client? see #9781 (comment)

I do think this is fine for now, we don't need to address it in this PR, it's more a nice to have to improve ux for operators

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants