contract/consensus: introduce stake timelock

With this check added, a staker is only able to propose after grace(lock) period has passed
This commit is contained in:
aggstam
2023-06-06 17:45:28 +03:00
parent 9c2f9630ef
commit e2ad94dfff
6 changed files with 105 additions and 20 deletions

View File

@@ -27,7 +27,7 @@ use darkfi_sdk::{
error::{ContractError, ContractResult},
merkle_add, msg,
pasta::{group::ff::FromUniformBytes, pallas},
util::get_slot_checkpoint,
util::{get_slot_checkpoint, get_verifying_slot_epoch},
ContractCall,
};
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
@@ -35,8 +35,8 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
use crate::{
error::ConsensusError,
model::{
ConsensusProposalParamsV1, ConsensusProposalUpdateV1, SlotCheckpoint, HEADSTART,
MU_RHO_PREFIX, MU_Y_PREFIX,
calculate_grace_period, ConsensusProposalParamsV1, ConsensusProposalUpdateV1,
SlotCheckpoint, HEADSTART, MU_RHO_PREFIX, MU_Y_PREFIX,
},
ConsensusFunction,
};
@@ -174,6 +174,14 @@ pub(crate) fn consensus_proposal_process_instruction_v1(
let input = &params.input;
let output = &params.output;
// The coin has passed through the grace period and is allowed to propose.
if params.input.epoch != 0 &&
get_verifying_slot_epoch() - params.input.epoch <= calculate_grace_period()
{
msg!("[ConsensusProposalV1] Error: Coin is not allowed to make proposals yet");
return Err(ConsensusError::CoinStillInGracePeriod.into())
}
// The Merkle root is used to know whether this is a coin that
// existed in a previous state.
if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {

View File

@@ -25,6 +25,9 @@ pub enum ConsensusError {
#[error("Eta VRF proof couldn't be verified")]
ProposalErroneousVrfProof,
#[error("Coin is still in grace period")]
CoinStillInGracePeriod,
}
impl From<ConsensusError> for ContractError {
@@ -32,6 +35,7 @@ impl From<ConsensusError> for ContractError {
match e {
ConsensusError::ProposalMissingSlotCheckpoint => Self::Custom(1),
ConsensusError::ProposalErroneousVrfProof => Self::Custom(2),
ConsensusError::CoinStillInGracePeriod => Self::Custom(3),
}
}
}

View File

@@ -115,22 +115,28 @@ pub struct ConsensusProposalUpdateV1 {
pub coin: Coin,
}
// Consensus parameters configuration.
// Note: Always verify `pallas::Base` are correct, in case of changes,
// using pallas_constants tool.
// Configured reward
/// Consensus parameters configuration.
/// Note: Always verify `pallas::Base` are correct, in case of changes,
/// using pallas_constants tool.
/// Number of slots in one epoch
pub const EPOCH_LENGTH: u64 = 10;
/// Slot time in seconds
pub const SLOT_TIME: u64 = 90;
/// Grace period days target
pub const GRACE_PERIOD_DAYS: u64 = 2;
/// Configured reward
pub const REWARD: u64 = 1;
// Reward `pallas::Base`, calculated by: pallas::Base::from(REWARD)
/// Reward `pallas::Base`, calculated by: pallas::Base::from(REWARD)
pub const REWARD_PALLAS: pallas::Base = pallas::Base::from_raw([1, 0, 0, 0]);
// Serial prefix, calculated by: pallas::Base::from(2)
/// Serial prefix, calculated by: pallas::Base::from(2)
pub const SERIAL_PREFIX: pallas::Base = pallas::Base::from_raw([2, 0, 0, 0]);
// Seed prefix, calculated by: pallas::Base::from(3)
/// Seed prefix, calculated by: pallas::Base::from(3)
pub const SEED_PREFIX: pallas::Base = pallas::Base::from_raw([3, 0, 0, 0]);
// Election seed y prefix, calculated by: pallas::Base::from(22)
/// Election seed y prefix, calculated by: pallas::Base::from(22)
pub const MU_Y_PREFIX: pallas::Base = pallas::Base::from_raw([22, 0, 0, 0]);
// Election seed rho prefix, calculated by: pallas::Base::from(5)
/// Election seed rho prefix, calculated by: pallas::Base::from(5)
pub const MU_RHO_PREFIX: pallas::Base = pallas::Base::from_raw([5, 0, 0, 0]);
// Lottery headstart, calculated by: darkfi::consensus::LeadCoin::headstart()
/// Lottery headstart, calculated by: darkfi::consensus::LeadCoin::headstart()
pub const HEADSTART: pallas::Base = pallas::Base::from_raw([
11731824086999220879,
11830614503713258191,
@@ -151,3 +157,10 @@ pub struct SlotCheckpoint {
/// Slot sigma2
pub sigma2: pallas::Base,
}
/// Auxiliary function to calculate the grace(locked) period, denominated
/// in epochs.
pub fn calculate_grace_period() -> u64 {
// 86400 seconds in a day
(86400 * GRACE_PERIOD_DAYS) / (SLOT_TIME * EPOCH_LENGTH)
}

View File

@@ -112,7 +112,7 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
assert!(ALICE_INITIAL == alice_staked_oc.note.value);
// We simulate the proposal of genesis slot
let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?;
let slot_checkpoint = th.get_slot_checkpoint_by_slot(current_slot).await?;
// With alice's current coin value she can become the slot proposer,
// so she creates a proposal transaction to burn her staked coin,

View File

@@ -576,6 +576,25 @@ impl ConsensusTestHarness {
Ok(())
}
pub async fn execute_erroneous_proposal_txs(
&mut self,
holder: Holder,
txs: Vec<Transaction>,
slot: u64,
erroneous: usize,
) -> Result<()> {
let wallet = self.holders.get_mut(&holder).unwrap();
let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Proposal).unwrap();
let timer = Instant::now();
let erroneous_txs =
wallet.state.read().await.verify_transactions(&txs, slot, false).await?;
assert_eq!(erroneous_txs.len(), erroneous);
tx_action_benchmark.verify_times.push(timer.elapsed());
Ok(())
}
pub fn unstake_native(
&mut self,
holder: Holder,
@@ -718,7 +737,7 @@ impl ConsensusTestHarness {
Ok(oc)
}
pub async fn get_slot_checkpoints_by_slot(&self, slot: u64) -> Result<SlotCheckpoint> {
pub async fn get_slot_checkpoint_by_slot(&self, slot: u64) -> Result<SlotCheckpoint> {
let faucet = self.holders.get(&Holder::Faucet).unwrap();
let slot_checkpoint =
faucet.state.read().await.blockchain.get_slot_checkpoints_by_slot(&[slot])?[0]
@@ -728,6 +747,25 @@ impl ConsensusTestHarness {
Ok(slot_checkpoint)
}
pub async fn generate_slot_checkpoint(&self, slot: u64) -> Result<SlotCheckpoint> {
// We grab the genesis slot to generate slot checkpoint
// using same consensus parameters
let genesis_slot = self.get_slot_checkpoint_by_slot(0).await?;
let slot_checkpoint = SlotCheckpoint {
slot,
eta: genesis_slot.eta,
sigma1: genesis_slot.sigma1,
sigma2: genesis_slot.sigma2,
};
// Store generated slot checkpoint
for wallet in self.holders.values() {
wallet.state.write().await.receive_slot_checkpoints(&[slot_checkpoint.clone()]).await?;
}
Ok(slot_checkpoint)
}
pub fn assert_trees(&self) {
let faucet = self.holders.get(&Holder::Faucet).unwrap();
let money_root = faucet.merkle_tree.root(0).unwrap();

View File

@@ -29,7 +29,7 @@
use darkfi::Result;
use log::info;
use darkfi_consensus_contract::model::REWARD;
use darkfi_consensus_contract::model::{calculate_grace_period, EPOCH_LENGTH, REWARD};
mod harness;
use harness::{init_logger, ConsensusTestHarness, Holder};
@@ -42,8 +42,8 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
const ALICE_AIRDROP: u64 = 1000;
// Slot to verify against
let current_slot = 0;
let current_epoch = 0;
let mut current_slot = 11;
let mut current_epoch = 1;
// Initialize harness
let mut th = ConsensusTestHarness::new().await?;
@@ -95,8 +95,30 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
// Verify values match
assert!(alice_oc.note.value == alice_staked_oc.note.value);
// We simulate the proposal of genesis slot
let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?;
// We progress one slot
current_slot += 1;
// We generate current slot checkpoint to simulate its proposal
let slot_checkpoint = th.generate_slot_checkpoint(current_slot).await?;
// Since alice didn't wait for the grace period to pass, her proposal should fail
info!(target: "consensus", "[Alice] ====================");
info!(target: "consensus", "[Alice] Building proposal tx");
info!(target: "consensus", "[Alice] ====================");
let (proposal_tx, proposal_params, proposal_secret_key) =
th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?;
info!(target: "consensus", "[Malicious] =====================================");
info!(target: "consensus", "[Malicious] Checking proposal before grace period");
info!(target: "consensus", "[Malicious] =====================================");
th.execute_erroneous_proposal_txs(Holder::Alice, vec![proposal_tx], current_slot, 1).await?;
// We progress after grace period
current_epoch += calculate_grace_period();
current_slot += current_epoch * EPOCH_LENGTH;
// We generate current slot checkpoint to simulate its proposal
let slot_checkpoint = th.generate_slot_checkpoint(current_slot).await?;
// With alice's current coin value she can become the slot proposer,
// so she creates a proposal transaction to burn her staked coin,