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
5 changes: 5 additions & 0 deletions console/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,12 @@ pub trait Network:
/// The expected time per block in seconds.
const BLOCK_TIME: u16 = 10;
/// The number of blocks per epoch.
#[cfg(not(feature = "test"))]
const NUM_BLOCKS_PER_EPOCH: u32 = 3600 / Self::BLOCK_TIME as u32; // 360 blocks == ~1 hour
/// The number of blocks per epoch.
/// This is deliberately set to a low value for testing purposes only.
#[cfg(feature = "test")]
const NUM_BLOCKS_PER_EPOCH: u32 = 10;

/// The maximum number of entries in data.
const MAX_DATA_ENTRIES: usize = 32;
Expand Down
5 changes: 4 additions & 1 deletion ledger/src/advance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
error!("Failed to update the current epoch hash at block {}", block.height());
}
}
// Clear the epoch provers cache.
// Clear the epoch provers cache. This is done because once the ledger enters a new epoch,
// all solutions from the previous epoch are no longer relevant.
// Note: solutions that land on exactly the epoch boundary are still considered part of the previous epoch,
// because they were created prior to the advancement to the new epoch (using the previous epoch's puzzle).
self.epoch_provers_cache.write().clear();
} else {
// If the block is not part of a new epoch, add the new provers to the epoch prover cache.
Expand Down
16 changes: 13 additions & 3 deletions ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,22 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {

/// Loads the provers and the number of solutions they have submitted for the current epoch.
pub fn load_epoch_provers(&self) -> IndexMap<Address<N>, u32> {
// Fetch the block heights that belong to the current epoch.
// Fetch the current block height.
let current_block_height = self.vm().block_store().current_block_height();
let start_of_epoch = current_block_height.saturating_sub(current_block_height % N::NUM_BLOCKS_PER_EPOCH);
let existing_epoch_blocks: Vec<_> = (start_of_epoch..=current_block_height).collect();

// Determine the first block to start checking.
// Note that the epoch boundary (where current_block_height % N::NUM_BLOCKS_PER_EPOCH == 0) can contain solutions
// for the previous epoch X. The subsequent block is the first block to contain solutions for the current epoch X+1.
let next_block_height = current_block_height.saturating_add(1);
let start = next_block_height.saturating_sub(current_block_height % N::NUM_BLOCKS_PER_EPOCH);

// If the epoch contains no blocks that have solutions for the epoch.
if start > current_block_height {
return IndexMap::new();
}

// Collect the addresses of the solutions submitted in the current epoch.
let existing_epoch_blocks: Vec<_> = (start..=current_block_height).collect();
let solution_addresses = cfg_iter!(existing_epoch_blocks)
.flat_map(|height| match self.get_solutions(*height).as_deref() {
Ok(Some(solutions)) => solutions.iter().map(|(_, s)| s.address()).collect::<Vec<_>>(),
Expand Down
163 changes: 132 additions & 31 deletions ledger/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2648,12 +2648,12 @@ mod valid_solutions {

#[test]
fn test_cumulative_proof_target_correctness() {
// The number of blocks to test.
const NUM_BLOCKS: u32 = 20;

// Initialize an RNG.
let rng = &mut TestRng::default();

// The number of blocks to test.
let num_blocks = CurrentNetwork::NUM_BLOCKS_PER_EPOCH * 2;

// Initialize the test environment.
let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng);

Expand All @@ -2674,8 +2674,8 @@ mod valid_solutions {
// Track the total number of solutions in the current epoch.
let mut total_epoch_solutions = 0;

// Run through 25 blocks of target adjustment.
while block_height < NUM_BLOCKS {
// Run through `num_blocks` blocks of target adjustment.
while block_height < num_blocks {
// Get coinbase puzzle data from the latest block.
let block = ledger.latest_block();
let coinbase_target = block.coinbase_target();
Expand Down Expand Up @@ -2713,23 +2713,9 @@ mod valid_solutions {
combined_targets = 0;
}

// Get a transfer transaction to ensure solutions can be included in the block.
let inputs = [Value::from_str(&format!("{prover_address}")).unwrap(), Value::from_str("10u64").unwrap()];
let transfer_transaction = ledger
.vm
.execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng)
.unwrap();

// Generate the next prospective block.
let next_block = ledger
.prepare_advance_to_next_beacon_block(
&private_key,
vec![],
solutions,
vec![transfer_transaction.clone()],
rng,
)
.unwrap();
let next_block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], solutions, vec![], rng).unwrap();

// Ensure the combined target matches the expected value.
assert_eq!(combined_targets as u128, next_block.cumulative_proof_target());
Expand All @@ -2743,23 +2729,139 @@ mod valid_solutions {
// Set the latest block height.
block_height = ledger.latest_height();

// Update the epoch solutions count.
if block_height.is_multiple_of(CurrentNetwork::NUM_BLOCKS_PER_EPOCH) {
// Reset the epoch solutions count at the epoch boundary.
total_epoch_solutions = 0;
} else {
total_epoch_solutions += num_solutions;
}

// Fetch the epoch provers cache.
let epoch_provers = ledger.epoch_provers_cache.read();
// Load the epoch provers from the blocks in the current epoch.
let expected_epoch_provers = ledger.load_epoch_provers();
// Check that the epoch solutions are correct
assert_eq!(epoch_provers.values().sum::<u32>(), u32::try_from(total_epoch_solutions).unwrap());
assert_eq!(epoch_provers.len(), expected_epoch_provers.len());
for ((expected_address, expected_count), (address, count)) in
expected_epoch_provers.iter().zip(epoch_provers.iter())
{
assert_eq!(expected_address, address);
assert_eq!(expected_count, count);
}
}
}

#[test]
fn test_epoch_provers_cache_cleared_at_epoch_boundary() {
// Initialize an RNG.
let rng = &mut TestRng::default();

// Initialize the test environment.
let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng);

// Set up the prover account with sufficient balance to generate solutions.
let prover_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let prover_address = Address::try_from(&prover_private_key).unwrap();
setup_prover_account(&ledger, &private_key, &prover_private_key, rng);

// Retrieve the puzzle parameters.
let puzzle = ledger.puzzle();

// Initialize block height.
let mut block_height = ledger.latest_height();

// Start a local counter of proof targets.
let mut combined_targets = 0;

// Track the total number of solutions in the current epoch.
let mut total_epoch_solutions = 0;

// Run through blocks until the end of the epoch.
while block_height < CurrentNetwork::NUM_BLOCKS_PER_EPOCH {
// Check the epoch provers cache.
{
// Fetch the epoch provers cache.
let epoch_provers = ledger.epoch_provers_cache.read();
// Load the epoch provers from the blocks in the current epoch.
let expected_epoch_provers = ledger.load_epoch_provers();
// Check that the epoch solutions are correct
assert_eq!(epoch_provers.values().sum::<u32>(), u32::try_from(total_epoch_solutions).unwrap());
assert_eq!(epoch_provers.len(), expected_epoch_provers.len());
for ((expected_address, expected_count), (address, count)) in
expected_epoch_provers.iter().zip(epoch_provers.iter())
{
assert_eq!(expected_address, address);
assert_eq!(expected_count, count);
assert_eq!(*expected_count, u32::try_from(total_epoch_solutions).unwrap());
}
}

// Get coinbase puzzle data from the latest block.
let block = ledger.latest_block();
let coinbase_target = block.coinbase_target();
let coinbase_threshold = coinbase_target.saturating_div(2);
let latest_epoch_hash = ledger.latest_epoch_hash().unwrap();
let latest_proof_target = ledger.latest_proof_target();

// Sample the number of solutions to generate.
let num_solutions = rng.gen_range(1..=CurrentNetwork::MAX_SOLUTIONS);

// Initialize a vector for valid solutions for this block.
let mut solutions = Vec::with_capacity(num_solutions);

// Loop through proofs until two that meet the threshold are found.
loop {
if let Ok(solution) =
puzzle.prove(latest_epoch_hash, prover_address, rng.r#gen(), Some(latest_proof_target))
{
// Get the proof target.
let proof_target = puzzle.get_proof_target(&solution).unwrap();

// Update the local combined target counter and store the solution.
combined_targets += proof_target;
solutions.push(solution);

// If two have been found, exit the solver loop.
if solutions.len() >= num_solutions {
break;
}
}
}

// If the combined target exceeds the coinbase threshold reset it.
if combined_targets >= coinbase_threshold {
combined_targets = 0;
}

// Generate the next prospective block.
let next_block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], solutions, vec![], rng).unwrap();

// Ensure the next block is correct.
ledger.check_next_block(&next_block, rng).unwrap();

// Advanced to the next block.
ledger.advance_to_next_block(&next_block).unwrap();

// Set the latest block height.
block_height = ledger.latest_height();

// Update the epoch solutions count.
total_epoch_solutions += num_solutions;
}

// Ensure that we are at the end of the epoch.
assert!(block_height.is_multiple_of(CurrentNetwork::NUM_BLOCKS_PER_EPOCH));

// Fetch the epoch provers cache.
let epoch_provers = ledger.epoch_provers_cache.read();
// Load the epoch provers from the blocks in the current epoch.
let expected_epoch_provers = ledger.load_epoch_provers();
// Check that the epoch solutions are correct
assert_eq!(epoch_provers.values().sum::<u32>(), u32::try_from(total_epoch_solutions).unwrap());
assert_eq!(epoch_provers.len(), expected_epoch_provers.len());
for ((expected_address, expected_count), (address, count)) in
expected_epoch_provers.iter().zip(epoch_provers.iter())
{
assert_eq!(expected_address, address);
assert_eq!(expected_count, count);
}
// Check that the epoch solutions are both empty.
assert_eq!(epoch_provers.len(), 0);
assert_eq!(expected_epoch_provers.len(), 0);
}

#[test]
Expand Down Expand Up @@ -3007,7 +3109,6 @@ mod valid_solutions {
assert_eq!(block_aborted_solution_ids, invalid_solutions, "Invalid solutions do not match");
}

// TODO (raychu86): Fix this test
#[test]
fn test_excess_valid_solution_ids() {
// Note that this should be greater than the maximum number of solutions.
Expand Down