Component: ghost-node @ 0162da4604d9931c16d12a0cec40dd85951368c5 — pallets/weaver/src/lib.rs:642 (validate_attestation_signature).
Description
validate_unsigned for extend_warp / weave_weft calls validate_attestation_signature, which contains:
// pallets/weaver/src/lib.rs:642-646
if T::BlockNumberProvider::current_block_number() <= attestation_block {
return Err(InvalidTransaction::BadProof);
}
The off-chain worker builds attestations stamped with the block it is processing (AttestationContext::default().with_current_block(current_block), lib.rs:776-780, where current_block is the now passed from offchain_worker(now)). When the transaction enters the pool, the pool validates it against the current best block, which is that same now. So now <= now is always true and the pool discards the transaction with BadProof.
extend_warp and weave_weft are the only two ways oracle data (external block numbers, Merkle roots, block hashes) gets on chain. Through the normal unsigned-transaction path, neither can ever enter the pool.
The existing test suite masks this: every validate_unsigned test advances the block first (set_block_number(N+1)) before validating, so the <= branch never triggers. No test validates an attestation at the block it was created, which is the production scenario.
Impact
Total loss of bridge liveness. Without extend_warp, no external block-number consensus ever starts; without weave_weft, no external Merkle root or block hash is ever committed to LoomStates. Every bridge function (weaving plus claims via pull_thread) is dead. The only workaround is a block author manually inserting the transactions past its own pool.
Reproduction
Local test poc_w1_ocw_attestation_rejected_at_submission_block in pallets/weaver/src/poc.rs:
- Set the mock chain block to 5.
- Build a
BlockAttestationwithwith_current_block(5)(exactly what the OCW does at block 5). - Sign it with an authority key, wrap it in
Call::extend_warp. - Call
GhostWeaver::validate_unsigned(TransactionSource::External, &call)→Err(InvalidTransaction::BadProof). - Control: set block to 6, repeat →
Ok.
A second test proves the same rejection for weave_weft. Ran cargo test -p ghost-weaver --lib poc_w1: passed.
Severity: High
Reason: the pallet’s core bridge function is 100% non-functional through the normal path in production (a permanent denial-of-service against all bridge features). Not Critical because it enables no theft or unauthorized minting; it blocks everything instead.
Recommendation
Change <= to < at lib.rs:642 so only attestations for future blocks are rejected. Signature re-verification at dispatch keeps the security properties unchanged.
Verification status
VERIFIED. Mechanism confirmed line-by-line in source; PoC executed locally and passed.