Skip to content

feat(maru): support custom validator signers - #3700

Open
nadeemb53 wants to merge 11 commits into
mainfrom
feat/maru-custom-signer-integration
Open

feat(maru): support custom validator signers#3700
nadeemb53 wants to merge 11 commits into
mainfrom
feat/maru-custom-signer-integration

Conversation

@nadeemb53

@nadeemb53 nadeemb53 commented Aug 3, 2026

Copy link
Copy Markdown
Member
  • add local/custom validator signer configuration and TOML parsing
  • inject custom signer factories without exposing the P2P private key
  • adapt asynchronous signers to the Besu NodeKey used by QBFT
  • validate custom signer addresses against configured validator sets
  • preserve signer lifecycle ownership and surface synchronous signing failures as failed futures

Stacked on #3666

@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from e9f67d5 to 35e5dc7 Compare August 3, 2026 20:04
@nadeemb53
nadeemb53 requested a lite review from Copilot August 3, 2026 20:19
@nadeemb53 nadeemb53 self-assigned this Aug 3, 2026

Copilot AI left a comment

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.

Pull request overview

Adds an opt-in “custom” QBFT validator signing backend to Maru, allowing embedders to supply validator signer implementations without exposing the node’s P2P private key, while adapting async signing into Besu’s synchronous NodeKey interface and enforcing validator-identity validation against configured validator sets.

Changes:

  • Introduces ValidatorSignerConfig TOML parsing (signer-type, signer-name) and surfaces it through Maru’s config domain model.
  • Adds crypto adapter types to expose Signer<Secp256k1Signature> as a Besu NodeKey for QBFT (including error wrapping and interruption preservation).
  • Wires validator NodeKey creation through the app factory, validates custom signer address membership in configured validator sets, and documents the new configuration.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
maru/README.md Documents local vs custom QBFT validator signing configuration and behavior.
maru/crypto/src/test/kotlin/maru/crypto/ValidatorSignerTest.kt Adds unit tests for NodeKey adaptation, digest forwarding, and error/interrupt handling.
maru/crypto/src/main/kotlin/maru/crypto/ValidatorSigner.kt Implements local validator signer + Besu SecurityModule adapter and toNodeKey() helper.
maru/crypto/build.gradle Adds dependencies needed for Besu plugin SecurityModule types and signer interfaces.
maru/consensus/src/test/kotlin/maru/consensus/qbft/FollowerBeaconBlockImporterTest.kt Adds coverage ensuring signing failures propagate as failed futures.
maru/consensus/src/main/kotlin/maru/consensus/qbft/QbftValidatorFactory.kt Switches QBFT validator identity/signing to use an injected NodeKey instead of raw private key bytes.
maru/consensus/src/main/kotlin/maru/consensus/blockimport/BeaconBlockImporter.kt Wraps prevRandao/signing failures so block import returns a failed future.
maru/config/src/test/kotlin/maru/config/ValidatorSignerConfigTest.kt Adds unit tests for signer config defaults/validation and TOML parsing.
maru/config/src/main/kotlin/maru/config/HopliteTomlFriendly.kt Extends QBFT TOML DTO with signer fields and maps to domain config.
maru/config/src/main/kotlin/maru/config/Config.kt Introduces ValidatorSignerConfig / ValidatorSignerType and attaches it to QbftConfig.
maru/app/src/test/kotlin/maru/app/ValidatorSignerFactoryTest.kt Adds tests for default factory behavior and managed-signer lifecycle semantics.
maru/app/src/main/kotlin/maru/app/ValidatorSignerFactory.kt Introduces ValidatorSignerFactory, ManagedValidatorSigner, and default factory behavior.
maru/app/src/main/kotlin/maru/app/QbftProtocolValidatorFactory.kt Updates factory wiring to pass NodeKey into QBFT validator factory.
maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt Creates validator NodeKey via local/custom signer selection and threads it into MaruApp.
maru/app/src/main/kotlin/maru/app/MaruApp.kt Derives validator address from the validator NodeKey, validates identity, and closes managed signer on shutdown.
maru/app/src/main/kotlin/maru/app/CliEntrypoint.kt Refactors CLI entrypoint to support testable execution and factory injection.
maru/app/build.gradle Exposes config + signer interfaces via api for embedders and adds Besu crypto-service deps.

Comment thread maru/crypto/src/main/kotlin/maru/crypto/ValidatorSigner.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/MaruApp.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt
Copilot AI review requested due to automatic review settings August 3, 2026 20:34
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from 35e5dc7 to 8ee733c Compare August 3, 2026 20:34

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (3)

maru/app/src/main/kotlin/maru/app/MaruApp.kt:330

  • validateValidatorIdentity throws IllegalArgumentException("") for unsupported fork configurations, which produces an empty error message and makes startup/debugging harder. Include the configuration type (or a clearer message) in the exception.
          when (val configuration = it.configuration) {
            is DifficultyAwareQbftConfig -> configuration.postTtdConfig.validatorSet
            is QbftConsensusConfig -> configuration.validatorSet
            else -> throw IllegalArgumentException("")
          }

maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt:356

  • If MaruApp construction fails after creating managedValidatorSigner (e.g., custom signer address not present in any validator set), the signer resource is never closed. Wrap the MaruApp(...) creation in a try/catch and close the signer on failure.
    return MaruApp(
      config = config,
      beaconGenesisConfig = beaconGenesisConfig,
      clock = clock,
      p2pNetwork = p2pNetwork,

maru/app/src/main/kotlin/maru/app/MaruApp.kt:226

  • managedValidatorSigner?.close() will be skipped if protocolStarter.close() throws, which can leak external signer resources on shutdown. Use a try/finally to ensure the signer is closed even when protocol shutdown fails.
    vertx.close()
    protocolStarter.close()
    managedValidatorSigner?.close()
    // close db last, otherwise other components may fail trying to save data
    beaconChain.close()

Copilot AI review requested due to automatic review settings August 3, 2026 20:45
@nadeemb53
nadeemb53 marked this pull request as ready for review August 3, 2026 20:47

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

maru/app/src/main/kotlin/maru/app/MaruApp.kt:329

  • validateValidatorIdentity throws IllegalArgumentException("") for unexpected fork configurations, which results in an empty error message and makes diagnosing misconfigured genesis/forks difficult. Include the unexpected configuration type (or fork) in the exception message.
            else -> throw IllegalArgumentException("")

maru/crypto/src/main/kotlin/maru/crypto/ValidatorSigner.kt:63

  • secpPublicKey is stored as a class property but is only needed inside the init block; keeping it as a field is unnecessary and may trip unused-property checks. Make it a local val inside init instead.
  private val secpPublicKey: SECPPublicKey
  private val publicKey: PublicKey

Copilot AI review requested due to automatic review settings August 4, 2026 08:20

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 4, 2026 08:53

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 4, 2026 08:58
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (3)

maru/crypto/build.gradle:8

  • maru:crypto exposes Signer/Secp256k1Signature in its public API (e.g., LocalValidatorSigner), but signer-interfaces is declared as an implementation dependency. With the java-library plugin this can break compilation for downstream modules that use these public types; it should be an api dependency (or the public API should avoid referencing those types).
  implementation(project(':jvm-libs:linea:core:signer-interfaces'))

maru/crypto/build.gradle:13

  • ValidatorSigner.kt publicly exposes Besu's NodeKey via Signer<Secp256k1Signature>.toNodeKey(), but besu-crypto-services is an implementation dependency. Because maru:crypto applies java-library, downstream modules may not see NodeKey on their compile classpath unless they add the dependency themselves; consider making besu-crypto-services an api dependency (or hide NodeKey behind a Maru-owned type).
  implementation("org.hyperledger.besu:besu-plugin-api")
  implementation("org.hyperledger.besu.internal:besu-crypto-algorithms")
  implementation("org.hyperledger.besu.internal:besu-crypto-services")

maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt:488

  • The warning message contains awkward/incorrect wording ("validatorSet-s"), which makes logs harder to read and search.
      log.warn(
        "localValidator={} isn't found in any of validatorSet-s in any of the Forks in the Genesis file!",
        validator,
      )

Comment thread maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/MaruApp.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt
Comment thread maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/QbftProtocolValidatorFactory.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/ValidatorSignerFactory.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/ValidatorSignerFactory.kt Outdated
}
}

internal class SignerSecurityModule(

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.

We are making an adapter to adapt Signer to a third party interface, which we use instead of our interface. I believe QbftFinalStateAdapter is the only place where it's necessary. In the rest of the places we can use Signer and CloseableSigner

@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from aa02b68 to bae8a40 Compare August 10, 2026 10:45
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch 2 times, most recently from b07ccd3 to 029ebc9 Compare August 11, 2026 08:31
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from 9cab88c to 4d0058e Compare August 11, 2026 15:07
Base automatically changed from feat/besu-custom-signer-integration to main August 11, 2026 16:20
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from 4d0058e to 2d8414d Compare August 11, 2026 16:21
Comment thread maru/app/src/main/kotlin/maru/app/ValidatorSetMembership.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/ValidatorIdentityValidator.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/QbftProtocolValidatorFactory.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/MaruAppFactory.kt Outdated
Comment thread maru/app/src/test/kotlin/maru/app/ValidatorSignerFactoryTest.kt Outdated
Comment thread maru/app/src/main/kotlin/maru/app/ValidatorIdentityValidator.kt Outdated
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch 2 times, most recently from 443cf95 to 4e9336c Compare August 11, 2026 16:56
@nadeemb53
nadeemb53 requested a review from Filter94 August 12, 2026 09:55
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from 4e9336c to eedb450 Compare August 12, 2026 09:55
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from eedb450 to e6b2752 Compare August 12, 2026 12:48
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
@nadeemb53
nadeemb53 force-pushed the feat/maru-custom-signer-integration branch from c399af6 to 3f6568a Compare August 12, 2026 14:59
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
}
}

internal class ValidatorSignerFactory(

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.

Sorry for nitpicking, but I feel like it's important to nail it here.

I think we're missing the distinction between the ValidatorSignerFactory and CustomValidatorSignerFactory. At the first glance it looks like delegation, but these factories have slightly different focus. Can we reflect this difference in the naming?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

rename ValidatorSignerFactory to ValidatorSignerInitializer then?

Comment thread maru/app/src/main/kotlin/maru/app/ValidatorSignerIdentity.kt Outdated
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants