use darkfi::{
blockchain::{BlockInfo, Header},
rpc::{jsonrpc::JsonNotification, util::JsonValue},
system::{ExecutorPtr, StoppableTask, Subscription},
tx::{ContractCallLeaf, Transaction, TransactionBuilder},
util::{encoding::base64, time::Timestamp},
validator::{
consensus::{Fork, Proposal},
utils::best_fork_index,
},
zk::{empty_witnesses, ProvingKey, ZkCircuit},
zkas::ZkBinary,
Error, Result,
};
use darkfi_money_contract::{
client::pow_reward_v1::PoWRewardCallBuilder, MoneyFunction, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{poseidon_hash, FuncId, PublicKey, SecretKey, MONEY_CONTRACT_ID},
pasta::pallas,
ContractCall,
};
use darkfi_serial::{serialize_async, Encodable};
use log::{error, info};
use num_bigint::BigUint;
use rand::rngs::OsRng;
use smol::channel::{Receiver, Sender};
use crate::{proto::ProposalMessage, task::garbage_collect_task, DarkfiNodePtr};
pub struct MinerRewardsRecipientConfig {
pub recipient: PublicKey,
pub spend_hook: Option<FuncId>,
pub user_data: Option<pallas::Base>,
}
pub async fn miner_task(
node: &DarkfiNodePtr,
recipient_config: &MinerRewardsRecipientConfig,
skip_sync: bool,
ex: &ExecutorPtr,
) -> Result<()> {
info!(target: "darkfid::task::miner_task", "Starting miner task...");
info!(target: "darkfid::task::miner_task", "Generating zkas bin and proving keys...");
let (zkbin, _) = node.validator.blockchain.contracts.get_zkas(
&node.validator.blockchain.sled_db,
&MONEY_CONTRACT_ID,
MONEY_CONTRACT_ZKAS_MINT_NS_V1,
)?;
let circuit = ZkCircuit::new(empty_witnesses(&zkbin)?, &zkbin);
let pk = ProvingKey::build(zkbin.k, &circuit);
info!(target: "darkfid::task::miner_task", "Generating signing key...");
let mut secret = SecretKey::random(&mut OsRng);
let block_sub = node.subscribers.get("blocks").unwrap();
let proposals_sub = node.subscribers.get("proposals").unwrap();
let subscription = proposals_sub.publisher.clone().subscribe().await;
if !skip_sync {
info!(target: "darkfid::task::miner_task", "Waiting for next finalization...");
loop {
subscription.receive().await;
let finalized = node.validator.finalization().await?;
if finalized.is_empty() {
continue
}
let mut notif_blocks = Vec::with_capacity(finalized.len());
for block in finalized {
notif_blocks
.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
}
block_sub.notify(JsonValue::Array(notif_blocks)).await;
break;
}
}
let (sender, stop_signal) = smol::channel::bounded(1);
let gc_task = StoppableTask::new();
gc_task.clone().start(
async { Ok(()) },
|_| async { },
Error::GarbageCollectionTaskStopped,
ex.clone(),
);
info!(target: "darkfid::task::miner_task", "Miner initialized successfully!");
loop {
let forks = node.validator.consensus.forks.read().await;
let index = match best_fork_index(&forks) {
Ok(i) => i,
Err(e) => {
error!(
target: "darkfid::task::miner_task",
"Finding best fork index failed: {e}"
);
continue
}
};
let extended_fork = match forks[index].full_clone() {
Ok(f) => f,
Err(e) => {
error!(
target: "darkfid::task::miner_task",
"Fork full clone creation failed: {e}"
);
continue
}
};
drop(forks);
match smol::future::or(
listen_to_network(node, &extended_fork, &subscription, &sender),
mine(
node,
&extended_fork,
&mut secret,
recipient_config,
&zkbin,
&pk,
&stop_signal,
skip_sync,
),
)
.await
{
Ok(_) => { }
Err(Error::NetworkNotConnected) => {
error!(target: "darkfid::task::miner_task", "Node disconnected from the network");
subscription.unsubscribe().await;
return Err(Error::NetworkNotConnected)
}
Err(e) => {
error!(
target: "darkfid::task::miner_task",
"Error during listen_to_network() or mine(): {e}"
);
continue
}
}
let finalized = match node.validator.finalization().await {
Ok(f) => f,
Err(e) => {
error!(
target: "darkfid::task::miner_task",
"Finalization failed: {e}"
);
continue
}
};
if finalized.is_empty() {
continue
}
let mut notif_blocks = Vec::with_capacity(finalized.len());
for block in finalized {
notif_blocks.push(JsonValue::String(base64::encode(&serialize_async(&block).await)));
}
block_sub.notify(JsonValue::Array(notif_blocks)).await;
gc_task.clone().stop().await;
gc_task.clone().start(
garbage_collect_task(node.clone()),
|res| async {
match res {
Ok(()) | Err(Error::GarbageCollectionTaskStopped) => { }
Err(e) => {
error!(target: "darkfid", "Failed starting garbage collection task: {}", e)
}
}
},
Error::GarbageCollectionTaskStopped,
ex.clone(),
);
}
}
async fn listen_to_network(
node: &DarkfiNodePtr,
extended_fork: &Fork,
subscription: &Subscription<JsonNotification>,
sender: &Sender<()>,
) -> Result<()> {
let last_proposal_hash = extended_fork.last_proposal()?.hash;
loop {
subscription.receive().await;
let forks = node.validator.consensus.forks.read().await;
let index = best_fork_index(&forks)?;
if forks[index].last_proposal()?.hash != last_proposal_hash {
drop(forks);
break
}
drop(forks);
}
sender.send(()).await?;
if let Err(e) = node.miner_daemon_request("abort", &JsonValue::Array(vec![])).await {
error!(target: "darkfid::task::miner::listen_to_network", "Failed to execute miner daemon abort request: {}", e);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn mine(
node: &DarkfiNodePtr,
extended_fork: &Fork,
secret: &mut SecretKey,
recipient_config: &MinerRewardsRecipientConfig,
zkbin: &ZkBinary,
pk: &ProvingKey,
stop_signal: &Receiver<()>,
skip_sync: bool,
) -> Result<()> {
smol::future::or(
wait_stop_signal(stop_signal),
mine_next_block(node, extended_fork, secret, recipient_config, zkbin, pk, skip_sync),
)
.await
}
pub async fn wait_stop_signal(stop_signal: &Receiver<()>) -> Result<()> {
if stop_signal.is_full() {
stop_signal.recv().await?;
}
stop_signal.recv().await?;
Ok(())
}
async fn mine_next_block(
node: &DarkfiNodePtr,
extended_fork: &Fork,
secret: &mut SecretKey,
recipient_config: &MinerRewardsRecipientConfig,
zkbin: &ZkBinary,
pk: &ProvingKey,
skip_sync: bool,
) -> Result<()> {
let (next_target, mut next_block) = generate_next_block(
extended_fork,
secret,
recipient_config,
zkbin,
pk,
node.validator.consensus.module.read().await.target,
node.validator.verify_fees,
)
.await?;
let target = JsonValue::String(next_target.to_string());
let block = JsonValue::String(base64::encode(&serialize_async(&next_block).await));
let response =
node.miner_daemon_request_with_retry("mine", &JsonValue::Array(vec![target, block])).await;
next_block.header.nonce = *response.get::<f64>().unwrap() as u64;
next_block.sign(secret);
extended_fork.module.verify_current_block(&next_block)?;
if !skip_sync && !node.p2p_handler.p2p.is_connected() {
return Err(Error::NetworkNotConnected)
}
let proposal = Proposal::new(next_block);
node.validator.append_proposal(&proposal).await?;
let message = ProposalMessage(proposal);
node.p2p_handler.p2p.broadcast(&message).await;
Ok(())
}
async fn generate_next_block(
extended_fork: &Fork,
secret: &mut SecretKey,
recipient_config: &MinerRewardsRecipientConfig,
zkbin: &ZkBinary,
pk: &ProvingKey,
block_target: u32,
verify_fees: bool,
) -> Result<(BigUint, BlockInfo)> {
let last_proposal = extended_fork.last_proposal()?;
let next_block_height = last_proposal.block.header.height + 1;
let (mut txs, _, fees) = extended_fork
.unproposed_txs(&extended_fork.blockchain, next_block_height, block_target, verify_fees)
.await?;
let prefix = pallas::Base::from_raw([4, 0, 0, 0]);
let next_secret = poseidon_hash([prefix, secret.inner(), (next_block_height as u64).into()]);
*secret = SecretKey::from(next_secret);
let tx = generate_transaction(next_block_height, fees, secret, recipient_config, zkbin, pk)?;
txs.push(tx);
let header = Header::new(last_proposal.hash, next_block_height, Timestamp::current_time(), 0);
let mut next_block = BlockInfo::new_empty(header);
next_block.append_txs(txs);
let target = extended_fork.module.next_mine_target()?;
Ok((target, next_block))
}
fn generate_transaction(
block_height: u32,
fees: u64,
secret: &SecretKey,
recipient_config: &MinerRewardsRecipientConfig,
zkbin: &ZkBinary,
pk: &ProvingKey,
) -> Result<Transaction> {
let debris = PoWRewardCallBuilder {
signature_public: PublicKey::from_secret(*secret),
block_height,
fees,
recipient: Some(recipient_config.recipient),
spend_hook: recipient_config.spend_hook,
user_data: recipient_config.user_data,
mint_zkbin: zkbin.clone(),
mint_pk: pk.clone(),
}
.build()?;
let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
debris.params.encode(&mut data)?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&[*secret])?;
tx.signatures = vec![sigs];
Ok(tx)
}