Add orphan leader proofs import (#600)
* Add orphan leader proofs import * add additional test case covering double import
This commit is contained in:
parent
a036b8adc3
commit
31c5f69121
@ -84,6 +84,17 @@ impl Ledger {
|
|||||||
.ok_or(LedgerError::ParentNotFound(parent_id))?;
|
.ok_or(LedgerError::ParentNotFound(parent_id))?;
|
||||||
let config = self.config.clone();
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
// Oprhan proofs need to be:
|
||||||
|
// * locally valid for the block they were originally in
|
||||||
|
// * not in conflict with the current ledger state
|
||||||
|
// This first condition is checked here, the second one is checked in the state update
|
||||||
|
// (in particular, we do not check the imported leader proof is for an earlier slot)
|
||||||
|
for orphan in header.orphaned_proofs() {
|
||||||
|
if !self.states.contains_key(&orphan.id()) {
|
||||||
|
return Err(LedgerError::OrphanMissing(orphan.id()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let new_state = parent_state
|
let new_state = parent_state
|
||||||
.clone()
|
.clone()
|
||||||
.try_apply_header(header, &self.config)?;
|
.try_apply_header(header, &self.config)?;
|
||||||
@ -140,7 +151,6 @@ impl LedgerState {
|
|||||||
// use the next epoch state as the current epoch state and reset next epoch state
|
// use the next epoch state as the current epoch state and reset next epoch state
|
||||||
// 3. we are in the next-next or later epoch:
|
// 3. we are in the next-next or later epoch:
|
||||||
// use the parent state as the epoch state and reset next epoch state
|
// use the parent state as the epoch state and reset next epoch state
|
||||||
|
|
||||||
if current_epoch == new_epoch {
|
if current_epoch == new_epoch {
|
||||||
// case 1)
|
// case 1)
|
||||||
let next_epoch_state = self
|
let next_epoch_state = self
|
||||||
@ -222,6 +232,10 @@ impl LedgerState {
|
|||||||
header: &Header,
|
header: &Header,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Self, LedgerError> {
|
) -> Result<Self, LedgerError> {
|
||||||
|
for proof in header.orphaned_proofs() {
|
||||||
|
self = self.try_apply_proof(proof.leader_proof(), config)?;
|
||||||
|
}
|
||||||
|
|
||||||
self = self
|
self = self
|
||||||
.try_apply_proof(header.leader_proof(), config)?
|
.try_apply_proof(header.leader_proof(), config)?
|
||||||
.update_nonce(header.leader_proof());
|
.update_nonce(header.leader_proof());
|
||||||
|
@ -181,13 +181,6 @@ impl Cryptarchia {
|
|||||||
// Classic longest chain rule with parameter k
|
// Classic longest chain rule with parameter k
|
||||||
if cmax.length < chain.length {
|
if cmax.length < chain.length {
|
||||||
cmax = chain;
|
cmax = chain;
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"shorter {:?} {} {}",
|
|
||||||
chain.header.id(),
|
|
||||||
cmax.length,
|
|
||||||
chain.length
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The chain is forking too much, we need to pay a bit more attention
|
// The chain is forking too much, we need to pay a bit more attention
|
||||||
@ -197,13 +190,6 @@ impl Cryptarchia {
|
|||||||
let candidate_density = branches.walk_back_before(&chain, density_slot).length;
|
let candidate_density = branches.walk_back_before(&chain, density_slot).length;
|
||||||
if cmax_density < candidate_density {
|
if cmax_density < candidate_density {
|
||||||
cmax = chain;
|
cmax = chain;
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"less dense {:?} {} {}",
|
|
||||||
chain.header.id(),
|
|
||||||
cmax_density,
|
|
||||||
candidate_density
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +206,10 @@ pub mod tests {
|
|||||||
use blake2::Digest;
|
use blake2::Digest;
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
|
||||||
use super::{ledger::tests::genesis_state, Cryptarchia};
|
use super::{
|
||||||
|
ledger::{tests::genesis_state, LedgerError},
|
||||||
|
Cryptarchia, Error,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn header(slot: impl Into<Slot>, parent: HeaderId, coin: Coin) -> Header {
|
pub fn header(slot: impl Into<Slot>, parent: HeaderId, coin: Coin) -> Header {
|
||||||
let slot = slot.into();
|
let slot = slot.into();
|
||||||
@ -231,6 +220,15 @@ pub mod tests {
|
|||||||
Block::new(header(slot, parent, coin))
|
Block::new(header(slot, parent, coin))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn block_with_orphans(
|
||||||
|
slot: impl Into<Slot>,
|
||||||
|
parent: HeaderId,
|
||||||
|
coin: Coin,
|
||||||
|
orphans: Vec<Header>,
|
||||||
|
) -> Block {
|
||||||
|
Block::new(header(slot, parent, coin).with_orphaned_proofs(orphans))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn propose_and_evolve(
|
pub fn propose_and_evolve(
|
||||||
slot: impl Into<Slot>,
|
slot: impl Into<Slot>,
|
||||||
parent: HeaderId,
|
parent: HeaderId,
|
||||||
@ -390,4 +388,94 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(engine.tip_id(), parent);
|
assert_eq!(engine.tip_id(), parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_orphan_proof_import() {
|
||||||
|
let coin = Coin::new(0);
|
||||||
|
let mut engine = engine(&[coin.commitment()]);
|
||||||
|
|
||||||
|
let coin_new = coin.evolve();
|
||||||
|
let coin_new_new = coin_new.evolve();
|
||||||
|
|
||||||
|
// produce a fork where the coin has been spent twice
|
||||||
|
let fork_1 = block(1, *engine.genesis(), coin);
|
||||||
|
let fork_2 = block(2, fork_1.header().id(), coin_new);
|
||||||
|
|
||||||
|
// neither of the evolved coins should be usable right away in another branch
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block(1, *engine.genesis(), coin_new)),
|
||||||
|
Err(Error::LedgerError(LedgerError::CommitmentNotFound))
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block(1, *engine.genesis(), coin_new_new)),
|
||||||
|
Err(Error::LedgerError(LedgerError::CommitmentNotFound))
|
||||||
|
));
|
||||||
|
|
||||||
|
// they also should not be accepted if the fork from where they have been imported has not been seen already
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block_with_orphans(
|
||||||
|
1,
|
||||||
|
*engine.genesis(),
|
||||||
|
coin_new,
|
||||||
|
vec![fork_1.header().clone()]
|
||||||
|
)),
|
||||||
|
Err(Error::LedgerError(LedgerError::OrphanMissing(_)))
|
||||||
|
));
|
||||||
|
|
||||||
|
// now the first block of the fork is seen (and accepted)
|
||||||
|
engine = engine.receive_block(fork_1.clone()).unwrap();
|
||||||
|
// and it can now be imported in another branch (note this does not validate it's for an earlier slot)
|
||||||
|
engine
|
||||||
|
.receive_block(block_with_orphans(
|
||||||
|
1,
|
||||||
|
*engine.genesis(),
|
||||||
|
coin_new,
|
||||||
|
vec![fork_1.header().clone()],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
// but the next coin is still not accepted since the second block using the evolved coin has not been seen yet
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block_with_orphans(
|
||||||
|
1,
|
||||||
|
*engine.genesis(),
|
||||||
|
coin_new_new,
|
||||||
|
vec![fork_1.header().clone(), fork_2.header().clone()]
|
||||||
|
)),
|
||||||
|
Err(Error::LedgerError(LedgerError::OrphanMissing(_)))
|
||||||
|
));
|
||||||
|
|
||||||
|
// now the second block of the fork is seen as well and the coin evolved twice can be used in another branch
|
||||||
|
engine = engine.receive_block(fork_2.clone()).unwrap();
|
||||||
|
engine
|
||||||
|
.receive_block(block_with_orphans(
|
||||||
|
1,
|
||||||
|
*engine.genesis(),
|
||||||
|
coin_new_new,
|
||||||
|
vec![fork_1.header().clone(), fork_2.header().clone()],
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
// but we can't import just the second proof because it's using an evolved coin that has not been seen yet
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block_with_orphans(
|
||||||
|
1,
|
||||||
|
*engine.genesis(),
|
||||||
|
coin_new_new,
|
||||||
|
vec![fork_2.header().clone()]
|
||||||
|
)),
|
||||||
|
Err(Error::LedgerError(LedgerError::CommitmentNotFound))
|
||||||
|
));
|
||||||
|
|
||||||
|
// an imported proof that uses a coin that was already used in the base branch should not be allowed
|
||||||
|
let block_1 = block(1, *engine.genesis(), coin);
|
||||||
|
engine = engine.receive_block(block_1.clone()).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
engine.receive_block(block_with_orphans(
|
||||||
|
2,
|
||||||
|
block_1.header().id(),
|
||||||
|
coin_new_new,
|
||||||
|
vec![fork_1.header().clone(), fork_2.header().clone()]
|
||||||
|
)),
|
||||||
|
Err(Error::LedgerError(LedgerError::NullifierExists))
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user