diff --git a/src/chainstate/stacks/db/blocks.rs b/src/chainstate/stacks/db/blocks.rs index 0f779cec5..5a0ed5cc1 100644 --- a/src/chainstate/stacks/db/blocks.rs +++ b/src/chainstate/stacks/db/blocks.rs @@ -2676,7 +2676,6 @@ impl StacksChainState { let miner_participant_principal = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_PARTICIPANT.to_string()).unwrap(); let miner_available_name = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_AVAILABLE.to_string()).unwrap(); - let miner_authorized_name = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_AUTHORIZED.to_string()).unwrap(); let miner_principal = Value::Tuple(TupleData::from_data(vec![ (miner_participant_principal, Value::Principal(PrincipalData::Standard(StandardPrincipalData::from(miner_reward.address.clone()))))]) @@ -2685,7 +2684,7 @@ impl StacksChainState { let miner_reward_total = miner_reward.total(); clarity_tx.connection().as_transaction(|x| { x.with_clarity_db(|ref mut db| { - // (+ reward (get available (default-to {available: 0, authorized: false} (map-get rewards ((miner)))))) + // (+ reward (get available (default-to {available: 0} (map-get rewards ((miner)))))) let miner_status_opt = db.fetch_entry(&miner_contract_id, BOOT_CODE_MINER_REWARDS_MAP, &miner_principal)?; let new_miner_status = match miner_status_opt { @@ -2695,7 +2694,6 @@ impl StacksChainState { // this miner doesn't have an entry in the contract yet Value::Tuple(TupleData::from_data(vec![ (miner_available_name, Value::UInt(miner_reward_total)), - (miner_authorized_name, Value::Bool(false)), ]).expect("FATAL: failed to construct miner reward tuple")) }, Some(ref miner_status) => { @@ -2730,6 +2728,14 @@ impl StacksChainState { debug!("Grant miner {} {} STX. Updated to: {}", miner_reward.address.to_string(), miner_reward_total, &new_miner_status); db.set_entry(&miner_contract_id, BOOT_CODE_MINER_REWARDS_MAP, miner_principal, new_miner_status)?; + + // credit contract so it can disburse when miner withdraws + let miner_contract_principal = PrincipalData::Contract(QualifiedContractIdentifier::new(StandardPrincipalData::from(boot_code_address.clone()), ContractName::from(BOOT_CODE_MINER_CONTRACT_NAME))); + let cur_balance = db.get_account_stx_balance(&miner_contract_principal); + let final_balance = cur_balance.checked_add(miner_reward_total).expect("FATAL: STX reward overflow"); + debug!("Balance available in {} {} STX", BOOT_CODE_MINER_CONTRACT_NAME, final_balance); + db.set_account_stx_balance(&miner_contract_principal, final_balance); + Ok(()) })}).map_err(Error::ClarityError)?; Ok(()) diff --git a/src/chainstate/stacks/db/mod.rs b/src/chainstate/stacks/db/mod.rs index 58bebd5e7..f54dd47c4 100644 --- a/src/chainstate/stacks/db/mod.rs +++ b/src/chainstate/stacks/db/mod.rs @@ -464,26 +464,26 @@ const STACKS_MINER_AUTH_KEY : &'static str = "a5879925788dcb3fe1f2737453e371ba04 const STACKS_BOOT_CODE : &'static [&'static str] = &[ r#" - (define-constant ERR-NO-PRINCIPAL 1) - (define-constant ERR-NOT-AUTHORIZED 2) - - (define-constant AUTHORIZER 'ST3REJ5WQ42JGJZ6W77CX79JYMCVTKD73D6R6Z4R3) ;; addr of STACKS_MINER_AUTH_KEY + (define-constant ERR-NO-BALANCE u5) (define-map rewards ((participant principal)) - ((available uint) (authorized bool)) + ((available uint)) ) (define-private (get-participant-info (participant principal)) - (default-to {available: u0, authorized: false} (map-get? rewards {participant: participant}))) + (default-to {available: u0} (map-get? rewards {participant: participant}))) - (define-public (get-participant-reward (participant principal)) + (define-read-only (get-participant-reward (participant principal)) (ok (get available (get-participant-info participant)))) - (define-public (is-participant-authorized? (participant principal)) - (ok (get authorized (get-participant-info participant)))) - - ;; TODO: authorize STX withdrawals - ;; TODO: withdraw STX + (define-public (withdraw-reward) + (let ((caller-address tx-sender) (contract-address (as-contract tx-sender)) (available (get available (get-participant-info tx-sender)))) + (if (> available u0) + (match + (as-contract (stx-transfer? available contract-address caller-address)) + value (begin (map-set rewards {participant: caller-address} {available: u0}) (ok available)) + err-value (err err-value)) + (err ERR-NO-BALANCE)))) "# ]; @@ -497,7 +497,6 @@ pub const BOOT_CODE_MINER_CONTRACT_NAME : &'static str = "miner-rewards"; pub const BOOT_CODE_MINER_REWARDS_MAP : &'static str = "rewards"; pub const BOOT_CODE_MINER_REWARDS_PARTICIPANT : &'static str = "participant"; pub const BOOT_CODE_MINER_REWARDS_AVAILABLE : &'static str = "available"; -pub const BOOT_CODE_MINER_REWARDS_AUTHORIZED : &'static str = "authorized"; #[cfg(test)] pub const MINER_REWARD_MATURITY : u64 = 2; // small for testing purposes diff --git a/src/chainstate/stacks/miner.rs b/src/chainstate/stacks/miner.rs index 18e301f4d..dda02e617 100644 --- a/src/chainstate/stacks/miner.rs +++ b/src/chainstate/stacks/miner.rs @@ -1167,13 +1167,12 @@ pub mod test { return None; } - pub fn get_miner_status<'a>(clarity_tx: &mut ClarityTx<'a>, addr: &StacksAddress) -> Option<(bool, u128)> { + pub fn get_miner_status<'a>(clarity_tx: &mut ClarityTx<'a>, addr: &StacksAddress) -> Option { let boot_code_address = StacksAddress::from_string(&STACKS_BOOT_CODE_CONTRACT_ADDRESS.to_string()).unwrap(); let miner_contract_id = QualifiedContractIdentifier::new(StandardPrincipalData::from(boot_code_address.clone()), ContractName::try_from(BOOT_CODE_MINER_CONTRACT_NAME.to_string()).unwrap()); let miner_participant_principal = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_PARTICIPANT.to_string()).unwrap(); let miner_available_name = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_AVAILABLE.to_string()).unwrap(); - let miner_authorized_name = ClarityName::try_from(BOOT_CODE_MINER_REWARDS_AUTHORIZED.to_string()).unwrap(); let miner_principal = Value::Tuple(TupleData::from_data(vec![ (miner_participant_principal, Value::Principal(PrincipalData::Standard(StandardPrincipalData::from(addr.clone()))))]) @@ -1189,13 +1188,6 @@ pub mod test { Some(ref miner_status) => { match **miner_status { Value::Tuple(ref tuple) => { - let authorized = match tuple.get(&miner_authorized_name).expect("FATAL: no miner authorized in tuple") { - Value::Bool(ref authorized) => *authorized, - _ => { - panic!("FATAL: miner reward data map is malformed"); - } - }; - let available = match tuple.get(&miner_available_name).expect("FATAL: no miner available in tuple") { Value::UInt(ref available) => *available, _ => { @@ -1203,7 +1195,7 @@ pub mod test { } }; - Some((authorized, available)) + Some(available) }, ref x => { panic!("FATAL: miner status is not a tuple: {:?}", &x); @@ -1384,8 +1376,8 @@ pub mod test { test_debug!("Miner {} '{}' has no mature funds in this fork", miner.id, miner.origin_address().unwrap().to_string()); return total == 0; } - Some((authorized, amount)) => { - test_debug!("Miner {} '{}' is authorized: {}, with amount: {} in this fork", miner.id, miner.origin_address().unwrap().to_string(), authorized, amount); + Some(amount) => { + test_debug!("Miner {} '{}' with amount: {} in this fork", miner.id, miner.origin_address().unwrap().to_string(), amount); if amount != total { test_debug!("Amount {} != {}", amount, total); return false; @@ -4414,6 +4406,7 @@ pub mod test { // assert_eq!(stacks_block.txs.len(), 1); } } + // TODO: invalid block with duplicate microblock public key hash (okay between forks, but not // within the same fork) // TODO: (BLOCKED) build off of different points in the same microblock stream