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
19 changes: 6 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,12 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Checkout leanSpec
uses: actions/checkout@v4
with:
repository: leanEthereum/leanSpec
ref: bf0f606a75095cf1853529bc770516b1464d9716
path: ./leanSpec

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Generate test fixtures
working-directory: ./leanSpec
run: uv run fill --clean --fork=devnet
- name: Download test fixtures
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p leanSpec/fixtures
gh run download --repo leanEthereum/leanSpec --name fixtures-prod-scheme --dir leanSpec/fixtures

- name: Setup Rust
uses: dtolnay/rust-toolchain@master
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ leanSpec:
git clone https://github.com/leanEthereum/leanSpec.git --single-branch
cd leanSpec && git checkout $(LEAN_SPEC_COMMIT_HASH)

leanSpec/fixtures: leanSpec
cd leanSpec && uv run fill --fork devnet --scheme=prod -o fixtures

# lean-quickstart:
# git clone https://github.com/blockblaz/lean-quickstart.git --depth 1 --single-branch

Expand Down
20 changes: 10 additions & 10 deletions bin/ethlambda/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,22 +187,22 @@ fn read_validator_keys(
for validator in validator_vec {
let validator_index = validator.index;

// Resolve the private key file path relative to the validators config directory
let privkey_path = if validator.privkey_file.is_absolute() {
// Resolve the secret key file path relative to the validators config directory
let secret_key_path = if validator.privkey_file.is_absolute() {
validator.privkey_file.clone()
} else {
validator_keys_dir.join(&validator.privkey_file)
};

info!(node_id=%node_id, index=validator_index, privkey_file=?privkey_path, "Loading validator private key");
info!(node_id=%node_id, index=validator_index, secret_key_file=?secret_key_path, "Loading validator secret key");

// Read the hex-encoded private key file
let privkey_bytes =
std::fs::read(&privkey_path).expect("Failed to read validator secret key file");
// Read the hex-encoded secret key file
let secret_key_bytes =
std::fs::read(&secret_key_path).expect("Failed to read validator secret key file");

// Parse the private key
let secret_key = ValidatorSecretKey::from_bytes(&privkey_bytes).unwrap_or_else(|err| {
error!(node_id=%node_id, index=validator_index, privkey_file=?privkey_path, ?err, "Failed to parse validator secret key");
// Parse the secret key
let secret_key = ValidatorSecretKey::from_bytes(&secret_key_bytes).unwrap_or_else(|err| {
error!(node_id=%node_id, index=validator_index, secret_key_file=?secret_key_path, ?err, "Failed to parse validator secret key");
std::process::exit(1);
});

Expand All @@ -212,7 +212,7 @@ fn read_validator_keys(
info!(
node_id = %node_id,
count = validator_keys.len(),
"Loaded validator private keys"
"Loaded validator secret keys"
);

validator_keys
Expand Down
11 changes: 11 additions & 0 deletions crates/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,26 @@ thiserror.workspace = true
tracing.workspace = true

prometheus.workspace = true
hex.workspace = true

[dev-dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
hex = { workspace = true }
datatest-stable = "0.3.3"
leansig.workspace = true
tree_hash = "0.12.0"
ethereum_ssz = "0.10.0"
ethereum_ssz_derive = "0.10.0"
ssz_types = "0.14.0"

[[test]]
name = "forkchoice_spectests"
path = "tests/forkchoice_spectests.rs"
harness = false
required-features = ["skip-signature-verification"]

[[test]]
name = "signature_spectests"
path = "tests/signature_spectests.rs"
harness = false
7 changes: 6 additions & 1 deletion crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ impl Store {
#[cfg(not(feature = "skip-signature-verification"))]
{
use ethlambda_types::signature::ValidatorSignature;
let epoch = target.slot.try_into().expect("slot exceeds u32");
// Use attestation.data.slot as epoch (matching what Zeam and ethlambda use for signing)
let epoch: u32 = attestation.data.slot.try_into().expect("slot exceeds u32");
let signature = ValidatorSignature::from_bytes(&signed_attestation.signature)
.map_err(|_| StoreError::SignatureDecodingFailed)?;
if !validator_pubkey.is_valid(epoch, &message, &signature) {
Expand Down Expand Up @@ -949,22 +950,26 @@ fn verify_signatures(
}

let proposer_attestation = &signed_block.message.proposer_attestation;

let proposer_signature =
ValidatorSignature::from_bytes(&signed_block.signature.proposer_signature)
.map_err(|_| StoreError::ProposerSignatureDecodingFailed)?;

let proposer = validators
.get(block.proposer_index as usize)
.ok_or(StoreError::InvalidValidatorIndex)?;

let proposer_pubkey = proposer
.get_pubkey()
.map_err(|_| StoreError::PubkeyDecodingFailed(proposer.index))?;

let epoch = proposer_attestation
.data
.slot
.try_into()
.expect("slot exceeds u32");
let message = proposer_attestation.data.tree_hash_root();

if !proposer_pubkey.is_valid(epoch, &message, &proposer_signature) {
return Err(StoreError::ProposerSignatureVerificationFailed);
}
Expand Down
88 changes: 88 additions & 0 deletions crates/blockchain/tests/signature_spectests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::path::Path;

use ethlambda_blockchain::{SECONDS_PER_SLOT, store::Store};
use ethlambda_types::{
block::{Block, SignedBlockWithAttestation},
primitives::TreeHash,
state::State,
};

mod signature_types;
use signature_types::VerifySignaturesTestVector;

const SUPPORTED_FIXTURE_FORMAT: &str = "verify_signatures_test";

fn run(path: &Path) -> datatest_stable::Result<()> {
let tests = VerifySignaturesTestVector::from_file(path)?;

for (name, test) in tests.tests {
if test.info.fixture_format != SUPPORTED_FIXTURE_FORMAT {
return Err(format!(
"Unsupported fixture format: {} (expected {})",
test.info.fixture_format, SUPPORTED_FIXTURE_FORMAT
)
.into());
}

println!("Running test: {}", name);

// Step 1: Populate the pre-state with the test fixture
let anchor_state: State = test.anchor_state.into();

// Create anchor block from the state's latest block header
let anchor_block = Block {
slot: anchor_state.latest_block_header.slot,
proposer_index: anchor_state.latest_block_header.proposer_index,
parent_root: anchor_state.latest_block_header.parent_root,
state_root: anchor_state.tree_hash_root(),
body: Default::default(),
};

// Initialize the store with the anchor state and block
let genesis_time = anchor_state.config.genesis_time;
let mut store = Store::get_forkchoice_store(anchor_state, anchor_block);

// Step 2: Run the state transition function with the block fixture
let signed_block: SignedBlockWithAttestation = test.signed_block_with_attestation.into();

// Advance time to the block's slot
let block_time = signed_block.message.block.slot * SECONDS_PER_SLOT + genesis_time;
store.on_tick(block_time, true);

// Process the block (this includes signature verification)
let result = store.on_block(signed_block);

// Step 3: Check that it succeeded or failed as expected
match (result.is_ok(), test.expect_exception.as_ref()) {
(true, None) => {
// Expected success, got success
}
(true, Some(expected_err)) => {
return Err(format!(
"Test '{}' failed: expected exception '{}' but got success",
name, expected_err
)
.into());
}
(false, None) => {
return Err(format!(
"Test '{}' failed: expected success but got failure: {:?}",
name,
result.err()
)
.into());
}
(false, Some(_)) => {
// Expected failure, got failure
}
}
}

Ok(())
}

datatest_stable::harness!({
test = run,
root = "../../../ethlambda/leanSpec/fixtures/consensus/verify_signatures",
pattern = r".*\.json"
});
Loading
Loading