Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions docs/content/developers/explore/provers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,29 @@ Provers are rewarded with `$BRIDGE` tokens from the Hyperbridge treasury for eac
- **Rotation proofs** — Proofs that carry a new BEEFY authority set transition.
- **Messaging proofs** — Proofs that advance the proven parachain height past blocks containing new cross-chain message dispatches.

The reward amount is configurable via governance through `pallet-beefy-consensus-proofs::set_proof_reward`.
The base reward amount is configurable via governance through `pallet-beefy-consensus-proofs::set_proof_reward` (currently **100 `$BRIDGE`** per proof).

### The Reward Mechanism

The process for earning rewards is deterministic and transparent:

1. **The Triggering Action**: A prover monitors the relay chain for BEEFY finality and Hyperbridge for new cross-chain message dispatches.
2. **Proof Generation**: When new work is detected, the prover generates a consensus proof (either ECDSA or SP1) and submits it to the Hyperbridge runtime.
2. **Proof Generation**: When new work is detected, the prover generates a consensus proof (either ECDSA or SP1) and submits it as a **signed** extrinsic — the signer is the reward payee.
3. **Verification and State Update**: The runtime verifies the proof. If it is valid and advances the proven state (either by rotating the authority set or proving new message dispatches), it is accepted.
4. **Automated Reward Issuance**: Upon acceptance, the protocol transfers a fixed `$BRIDGE` reward from the treasury to the prover's account.
4. **Automated Reward Issuance**: Upon acceptance, the protocol transfers a `$BRIDGE` reward from the treasury to the prover's account (the amount depends on the prover's position — see below — and an equal amount of non-transferable Reputation Asset is minted).

### Uncle Proofs

To reward redundancy and keep proving live, Hyperbridge rewards more than just the single fastest prover. The **first** prover to land a valid proof for a given finality target advances the chain's state and earns the **full** reward. Additional **independent** provers that generate their own valid proof for the **same** target are accepted as **uncles** and paid on a *decreasing reward curve*, up to a governance-configured limit (`MaxUncleProvers`).

The curve (set via `set_reward_curve`) pays each position a fraction of the base reward. The current curve is **100% / 50% / 30% / 20% / 10% / 5%** — the first prover receives the full reward (100%), and the next five uncles receive 50%, 30%, 20%, 10%, and 5% respectively. Uncle rewards apply to SP1 proofs only.

### Prover-Bound Proofs

Each SP1 proof commits a nonce into its public values, and the runtime requires that nonce to equal the extrinsic signer. This binds a proof to the prover that produced it:

- A proof **cannot be stolen** — copying the proof bytes from the mempool and submitting them under a different account is rejected, because the committed account no longer matches the signer.
- Reward **deduplication is per-account** — an account is rewarded at most once per finality target, so a single prover cannot mint many byte-distinct variants of one proof (e.g. via Groth16 re-randomization) to sweep multiple uncle slots.

## Reputation and the Path to Becoming a Collator

Expand All @@ -51,7 +64,7 @@ This means that running a prover is a direct path to becoming a Collator and ear

## A Competitive, Permissionless Environment

The incentive structure fosters a healthy, competitive market. Only the **first prover** to successfully submit a proof for a given state advance receives the reward. This encourages provers to optimize their operations for low latency and efficiency, ensuring that Hyperbridge's cross-chain message pipeline remains fast and reliable.
The incentive structure fosters a healthy, competitive market. The **first prover** to submit a proof for a given state advance earns the full reward and advances the chain, which rewards low-latency, efficient operations. Independent provers for the same target still earn a smaller, decreasing reward as uncles, so redundant provers are compensated for the liveness they provide rather than racing winner-take-all.

This model is inherently permissionless — no staking or whitelisting is required to participate. Any operator can run a prover and begin competing to submit proofs and earn rewards.

Expand Down
2 changes: 1 addition & 1 deletion evm/rust/abi/SP1Beefy.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions evm/rust/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ mod beefy {
mmr_leaf: value.mmrLeaf.into(),
headers: value.headers.into_iter().map(Into::into).collect(),
proof: value.proof.to_vec(),
nonce: H256(value.nonce.0),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion evm/script/DeployIsmp.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "forge-std/Script.sol";
import "stringutils/strings.sol";


import {SP1Verifier} from "@sp1-contracts/v5.0.0/SP1VerifierGroth16.sol";
import {SP1Verifier} from "@sp1-contracts/v6.1.0/SP1VerifierGroth16.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {UniV4UniswapV2Wrapper} from "../src/utils/uniswapv2/UniV4UniswapV2Wrapper.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";
Expand Down
17 changes: 12 additions & 5 deletions evm/src/consensus/SP1Beefy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,16 @@ contract SP1Beefy is IConsensusV2, ERC165 {
MiniCommitment memory commitment,
PartialBeefyMmrLeaf memory leaf,
ParachainHeader[] memory headers,
bytes memory proofBytes
) = abi.decode(proof, (MiniCommitment, PartialBeefyMmrLeaf, ParachainHeader[], bytes));
SP1BeefyProof memory sp1Proof =
SP1BeefyProof({commitment: commitment, mmrLeaf: leaf, headers: headers, proof: proofBytes});
bytes memory proofBytes,
bytes32 nonce
) = abi.decode(proof, (MiniCommitment, PartialBeefyMmrLeaf, ParachainHeader[], bytes, bytes32));
SP1BeefyProof memory sp1Proof = SP1BeefyProof({
commitment: commitment,
mmrLeaf: leaf,
headers: headers,
proof: proofBytes,
nonce: nonce
});

(BeefyConsensusState memory newState, IntermediateState[] memory intermediates) =
verifyConsensus(consensusState, sp1Proof);
Expand Down Expand Up @@ -135,7 +141,8 @@ contract SP1Beefy is IConsensusV2, ERC165 {
authorities_root: authority.root,
headers: headers,
block_number: commitment.blockNumber,
leaf_hash: keccak256(Codec.Encode(proof.mmrLeaf))
leaf_hash: keccak256(Codec.Encode(proof.mmrLeaf)),
nonce: proof.nonce
})
);
verifier.verifyProof(verificationKey, publicInputs, proof.proof);
Expand Down
6 changes: 6 additions & 0 deletions evm/src/consensus/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ struct SP1BeefyProof {
ParachainHeader[] headers;
// SP1 plonk proof for BEEFY consensus
bytes proof;
// Prover-chosen nonce committed into the proof's public values. Carried verbatim so the
// verifier can reconstruct the committed public inputs; rewarding verifiers bind it to the
// submission account to make a proof non-transferable.
bytes32 nonce;
}

struct MiniCommitment {
Expand Down Expand Up @@ -66,6 +70,8 @@ struct PublicInputs {
uint256 block_number;
// Parachain header hashes
ParachainHeaderHash[] headers;
// Prover-chosen nonce, committed verbatim by the SP1 program
bytes32 nonce;
}

struct Payload {
Expand Down
20 changes: 16 additions & 4 deletions evm/tests/foundry/SP1BeefyForkTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ import {IntermediateState} from "@hyperbridge/core/interfaces/IConsensusV2.sol";

import {SP1Beefy} from "../../src/consensus/SP1Beefy.sol";
import {BeefyConsensusState} from "../../src/consensus/Types.sol";
import {ISP1Verifier} from "@sp1-contracts/ISP1Verifier.sol";

/// @notice Verifies an SP1Beefy proof generated by tesseract-prover (zk-beefy)
/// against the production SP1Beefy contract deployed on ethereum mainnet.
/// @notice Verifies an SP1Beefy proof generated by tesseract-prover (zk-beefy) on an ethereum
/// mainnet fork. This change rotates the SP1 program vkey, so the proof can no longer be verified
/// by the production SP1Beefy (which is pinned to the previous vkey as an immutable) until it is
/// redeployed. We therefore reuse the *live* SP1 verifier but point a fresh SP1Beefy at the new
/// vkey — still exercising the real on-chain verifier against the regenerated proof.
///
contract SP1BeefyForkTest is Test {
/// SP1Beefy deployed by DeployHostUpdates.s.sol — same CREATE2 address on all chains.
/// Used only to read the live SP1 verifier address; redeploy carries the new vkey.
address internal constant SP1_BEEFY = 0x82582f85cf370adCB61D97dab3068c0C4102Ccb6;

/// sp1-beefy v1.1.0 program vkey (public values now include the committed prover nonce).
bytes32 internal constant VKEY = bytes32(0x007d1720c695842ed647a1a72e981751f9b5e26fc5ca038523b23430a1292f08);

/// Fixture committed under tests/foundry/fixtures/. Regenerate with the zk-beefy test:
/// cargo test --release -p zk-beefy --lib tests::test_sp1_beefy -- --ignored --nocapture
/// (See FIXTURE_OUT env var in tesseract/consensus/beefy/zk/src/tests.rs.)
Expand All @@ -32,7 +40,7 @@ contract SP1BeefyForkTest is Test {
vm.selectFork(mainnetFork);
}

function testVerifyMainnetProof() public view {
function testVerifyMainnetProof() public {
string memory json = vm.readFile(FIXTURE_PATH);

bytes memory previousState = vm.parseJsonBytes(json, ".previous_state");
Expand All @@ -47,8 +55,12 @@ contract SP1BeefyForkTest is Test {
console.log("prev.latestHeight:", prev.latestHeight);
console.log("prev.currentAuthoritySet.id:", prev.currentAuthoritySet.id);

// Reuse the live SP1 verifier from the production deployment, but with the new vkey.
ISP1Verifier verifier = SP1Beefy(SP1_BEEFY).verifier();
SP1Beefy beefy = new SP1Beefy(verifier, VKEY);

(bytes memory newStateEnc, IntermediateState[] memory intermediates, uint256 nextAuthId) =
SP1Beefy(SP1_BEEFY).verify(previousState, proof);
beefy.verify(previousState, proof);

BeefyConsensusState memory newState = abi.decode(newStateEnc, (BeefyConsensusState));
console.log("new.latestHeight:", newState.latestHeight);
Expand Down
Loading
Loading