mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
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:
@@ -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 = ¶ms.input;
|
||||
let output = ¶ms.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))? {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user