fix(fees): review fixes — clamp actual fee, bind cross-zone producer, doc contracts

This commit is contained in:
erhant
2026-08-24 21:43:23 +03:00
parent 5363d5d0b4
commit 5b83eb4ee3
4 changed files with 65 additions and 23 deletions
+5
View File
@@ -84,6 +84,11 @@ impl Block {
/// Whether the header signature verifies against the embedded producer
/// key. Every valid block must satisfy this.
///
/// This attests only that the producer signed the *declared* `header.hash`,
/// not that the hash matches the block contents — so it is not an
/// authenticity check on its own. Pair it with a `recompute_hash` check (as
/// `validate_against_tip` does) before trusting it.
#[must_use]
pub fn has_valid_producer_signature(&self) -> bool {
self.header
+37 -18
View File
@@ -414,15 +414,24 @@ impl CrossZoneVerifier {
.await?;
// Equivocation defense: the source block must be signed by the peer's
// pinned block-signing key, not merely inscribed on the channel.
if let Some(expected) = self.peer_pubkeys.get(&msg.src_zone)
&& !peer_block.is_signed_by(expected)
{
return Err(CrossZoneVerifyError::Forged(anyhow!(
"forged cross-zone dispatch: peer zone {} block {} is not signed by the pinned block-signing key",
hex::encode(msg.src_zone),
msg.src_block_id
)));
// pinned block-signing key and declare it as the producer, not merely be
// inscribed on the channel — so it is a block the peer's home zone would
// also accept.
if let Some(expected) = self.peer_pubkeys.get(&msg.src_zone) {
if !peer_block.is_signed_by(expected) {
return Err(CrossZoneVerifyError::Forged(anyhow!(
"forged cross-zone dispatch: peer zone {} block {} is not signed by the pinned block-signing key",
hex::encode(msg.src_zone),
msg.src_block_id
)));
}
if peer_block.header.producer != *expected {
return Err(CrossZoneVerifyError::Forged(anyhow!(
"forged cross-zone dispatch: peer zone {} block {} declares a producer other than the pinned block-signing key",
hex::encode(msg.src_zone),
msg.src_block_id
)));
}
}
// Everything below is a property of the peer block just read, so a
@@ -565,15 +574,25 @@ fn accept_peer_block(
return false;
}
if let Some(expected) = expected_pubkey
&& !block.is_signed_by(expected)
{
warn!(
"Peer reader dropping block {} from {}: not signed by the pinned block-signing key",
block.header.block_id,
hex::encode(peer_zone)
);
return false;
if let Some(expected) = expected_pubkey {
if !block.is_signed_by(expected) {
warn!(
"Peer reader dropping block {} from {}: not signed by the pinned block-signing key",
block.header.block_id,
hex::encode(peer_zone)
);
return false;
}
// The producer must be the pinned key itself, not merely signed by it,
// so the block is one the peer's home zone would also accept.
if block.header.producer != *expected {
warn!(
"Peer reader dropping block {} from {}: producer is not the pinned block-signing key",
block.header.block_id,
hex::encode(peer_zone)
);
return false;
}
}
true
+16 -5
View File
@@ -65,6 +65,12 @@ impl FeeTxView {
/// The amount held from the payer before execution, at the block's opening
/// base fees: `gas_limit·base_fee_exec + gas_stor·base_fee_stor + tip`.
///
/// The caller must pass a cap-validated view (gas within `MAX_GAS_*`) and a
/// well-formed `FeeState` whose base fees stay in `[MIN, BASE_FEE_*_MAX]` — the
/// bounds `next_base_fee` maintains. Under those preconditions every product
/// fits u64 and the u128 sum cannot overflow; on an unchecked view against a
/// corrupt state it could wrap silently in release.
#[must_use]
pub fn fee_reserve(view: &FeeTxView, fee_state: &FeeState) -> u128 {
u128::from(view.gas_limit()) * u128::from(fee_state.base_fee_exec)
@@ -75,12 +81,15 @@ pub fn fee_reserve(view: &FeeTxView, fee_state: &FeeState) -> u128 {
/// The base fee actually owed after execution.
///
/// `gas_exec·base_fee_exec + gas_stor·base_fee_stor`, where `gas_exec` is the
/// charged (clamped) cycle count for public transactions and the fixed
/// verification cost for private.
/// executed cycle count clamped to the transaction's `gas_limit` for public
/// transactions (a session may overshoot its budget by up to one instruction,
/// but a transaction is never billed past the gas it declared) and the fixed
/// verification cost for private. Clamping here keeps `actual + tip ≤ reserve`
/// regardless of what the caller passes.
#[must_use]
pub fn fee_actual_base(charged_cycles: u64, view: &FeeTxView, fee_state: &FeeState) -> u128 {
let gas_exec = match view {
FeeTxView::Public { .. } => charged_cycles,
FeeTxView::Public { gas_limit, .. } => charged_cycles.min(*gas_limit),
FeeTxView::Private { .. } => market::PRIVATE_VERIFY_GAS,
};
u128::from(gas_exec) * u128::from(fee_state.base_fee_exec)
@@ -131,7 +140,7 @@ mod tests {
}
#[test]
fn reserve_dominates_actual_within_the_limit() {
fn reserve_dominates_actual_even_past_the_limit() {
let view = FeeTxView::Public {
payer: payer(),
gas_limit: 10_000,
@@ -141,7 +150,9 @@ mod tests {
};
let state = genesis();
let reserve = fee_reserve(&view, &state);
for cycles in [0, 1, 5_000, 10_000] {
// Including cycle counts above the limit: the clamp keeps the actual fee
// bounded by the reserve, so the payer is never billed past gas_limit.
for cycles in [0, 1, 5_000, 10_000, 10_001, 1_000_000, u64::MAX] {
assert!(fee_actual_base(cycles, &view, &state) + u128::from(view.tip()) <= reserve);
}
}
@@ -172,6 +172,13 @@ fn link_against(
return Link::OffChain("block-signing key does not match the pinned key".to_owned());
}
// The producer must also *be* the pinned key, not merely signed by it:
// otherwise the block declares a producer its home zone would reject
// (`has_valid_producer_signature` fails there) yet passes here.
if expected_pubkey.is_some_and(|key| block.header.producer != *key) {
return Link::OffChain("block producer does not match the pinned key".to_owned());
}
let recomputed = block.recompute_hash();
if recomputed != block.header.hash {
// The signature does not cover this field, so a correctly signed block