drk: added fee call to transfer

This commit is contained in:
skoupidi
2024-05-02 16:07:30 +03:00
parent cd4655bb62
commit 07606d27e4
9 changed files with 444 additions and 161 deletions

View File

@@ -23,7 +23,7 @@ use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
pub enum RpcError {
// Transaction-related errors
TxSimulationFail = -32110,
TxBroadcastFail = -32111,
TxGasCalculationFail = -32111,
// State-related errors,
NotSynced = -32120,
@@ -43,7 +43,7 @@ fn to_tuple(e: RpcError) -> (i32, String) {
let msg = match e {
// Transaction-related errors
RpcError::TxSimulationFail => "Failed simulating transaction state change",
RpcError::TxBroadcastFail => "Failed broadcasting transaction",
RpcError::TxGasCalculationFail => "Failed to calculate transaction's gas",
// State-related errors
RpcError::NotSynced => "Blockchain is not synced",
RpcError::UnknownBlockHeight => "Did not find block height",

View File

@@ -73,6 +73,7 @@ impl RequestHandler for Darkfid {
"tx.broadcast" => self.tx_broadcast(req.id, req.params).await,
"tx.pending" => self.tx_pending(req.id, req.params).await,
"tx.clean_pending" => self.tx_pending(req.id, req.params).await,
"tx.calculate_gas" => self.tx_calculate_gas(req.id, req.params).await,
// ==============
// Invalid method

View File

@@ -17,7 +17,7 @@
*/
use darkfi_serial::deserialize_async;
use log::error;
use log::{error, warn};
use tinyjson::JsonValue;
use darkfi::{
@@ -135,8 +135,7 @@ impl Darkfid {
self.p2p.broadcast(&tx).await;
if self.p2p.hosts().channels().await.is_empty() {
error!(target: "darkfid::rpc::tx_broadcast", "Failed broadcasting tx, no connected channels");
return server_error(RpcError::TxBroadcastFail, id, None)
warn!(target: "darkfid::rpc::tx_broadcast", "No connected channels to broadcast tx");
}
let tx_hash = tx.hash().to_string();
@@ -209,4 +208,56 @@ impl Darkfid {
JsonResponse::new(JsonValue::Array(pending_txs), id).into()
}
// RPCAPI:
// Compute provided transaction's total gas, against current best fork.
// Returns the gas value if the transaction is valid, otherwise, a corresponding
// error.
//
// --> {"jsonrpc": "2.0", "method": "tx.calculate_gas", "params": ["base64encodedTX", "include_fee"], "id": 1}
// <-- {"jsonrpc": "2.0", "result": true, "id": 1}
pub async fn tx_calculate_gas(&self, id: u16, params: JsonValue) -> JsonResult {
let params = params.get::<Vec<JsonValue>>().unwrap();
if params.len() != 2 || !params[0].is_string() || !params[1].is_bool() {
return JsonError::new(InvalidParams, None, id).into()
}
if !*self.validator.synced.read().await {
error!(target: "darkfid::rpc::tx_calculate_gas", "Blockchain is not synced");
return server_error(RpcError::NotSynced, id, None)
}
// Try to deserialize the transaction
let tx_enc = params[0].get::<String>().unwrap().trim();
let tx_bytes = match base64::decode(tx_enc) {
Some(v) => v,
None => {
error!(target: "darkfid::rpc::tx_calculate_gas", "Failed decoding base64 transaction");
return server_error(RpcError::ParseError, id, None)
}
};
let tx: Transaction = match deserialize_async(&tx_bytes).await {
Ok(v) => v,
Err(e) => {
error!(target: "darkfid::rpc::tx_calculate_gas", "Failed deserializing bytes into Transaction: {}", e);
return server_error(RpcError::ParseError, id, None)
}
};
// Parse the include fee flag
let include_fee = params[1].get::<bool>().unwrap();
// Simulate state transition
let result = self.validator.calculate_gas(&tx, *include_fee).await;
if result.is_err() {
error!(
target: "darkfid::rpc::tx_calculate_gas", "Failed to validate state transition: {}",
result.err().unwrap()
);
return server_error(RpcError::TxGasCalculationFail, id, None)
};
JsonResponse::new(JsonValue::Number(result.unwrap() as f64), id).into()
}
}

View File

@@ -1,6 +1,5 @@
-- Wallet definitions for this contract.
-- We store data that is needed to be able to receive and send tokens.
-- TODO: The tables should be prefixed with ContractId to prevent collision
-- Arbitrary info that is potentially useful
CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_info (

View File

@@ -22,12 +22,22 @@ use lazy_static::lazy_static;
use rand::rngs::OsRng;
use rusqlite::types::Value;
use darkfi::{zk::halo2::Field, Error, Result};
use darkfi::{
tx::Transaction,
zk::{halo2::Field, proof::ProvingKey, Proof},
zkas::ZkBinary,
Error, Result,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
client::{
compute_remainder_blind,
fee_v1::{create_fee_proof, FeeCallInput, FeeCallOutput, FEE_CALL_GAS},
MoneyNote, OwnCoin,
},
model::{
Coin, MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1, MoneyTokenFreezeParamsV1,
MoneyTokenMintParamsV1, MoneyTransferParamsV1, Nullifier, TokenId, DARK_TOKEN_ID,
Coin, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1,
MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1, MoneyTransferParamsV1, Nullifier, Output,
TokenId, DARK_TOKEN_ID,
},
MoneyFunction,
};
@@ -38,8 +48,9 @@ use darkfi_sdk::{
ScalarBlind, SecretKey, MONEY_CONTRACT_ID,
},
pasta::pallas,
ContractCall,
};
use darkfi_serial::{deserialize_async, serialize_async};
use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
use crate::{
convert_named_params,
@@ -401,95 +412,124 @@ impl Drk {
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
let Value::Blob(ref coin_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_coins] Coin bytes parsing failed"))
};
let coin: Coin = deserialize_async(coin_bytes).await?;
let Value::Integer(is_spent) = row[1] else {
return Err(Error::ParseFailed("[get_coins] Is spent parsing failed"))
};
let Ok(is_spent) = u64::try_from(is_spent) else {
return Err(Error::ParseFailed("[get_coins] Is spent parsing failed"))
};
let is_spent = is_spent > 0;
let Value::Blob(ref value_bytes) = row[2] else {
return Err(Error::ParseFailed("[get_coins] Value bytes parsing failed"))
};
let value: u64 = deserialize_async(value_bytes).await?;
let Value::Blob(ref token_id_bytes) = row[3] else {
return Err(Error::ParseFailed("[get_coins] Token ID bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
let Value::Blob(ref spend_hook_bytes) = row[4] else {
return Err(Error::ParseFailed("[get_coins] Spend hook bytes parsing failed"))
};
let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
let Value::Blob(ref user_data_bytes) = row[5] else {
return Err(Error::ParseFailed("[get_coins] User data bytes parsing failed"))
};
let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
let Value::Blob(ref coin_blind_bytes) = row[6] else {
return Err(Error::ParseFailed("[get_coins] Coin blind bytes parsing failed"))
};
let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
let Value::Blob(ref value_blind_bytes) = row[7] else {
return Err(Error::ParseFailed("[get_coins] Value blind bytes parsing failed"))
};
let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
let Value::Blob(ref token_blind_bytes) = row[8] else {
return Err(Error::ParseFailed("[get_coins] Token blind bytes parsing failed"))
};
let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
let Value::Blob(ref secret_bytes) = row[9] else {
return Err(Error::ParseFailed("[get_coins] Secret bytes parsing failed"))
};
let secret: SecretKey = deserialize_async(secret_bytes).await?;
// TODO: Remove from SQL store, can be derived ondemand
let Value::Blob(ref nullifier_bytes) = row[10] else {
return Err(Error::ParseFailed("[get_coins] Nullifier bytes parsing failed"))
};
let _nullifier: Nullifier = deserialize_async(nullifier_bytes).await?;
let Value::Blob(ref leaf_position_bytes) = row[11] else {
return Err(Error::ParseFailed("[get_coins] Leaf position bytes parsing failed"))
};
let leaf_position: bridgetree::Position =
deserialize_async(leaf_position_bytes).await?;
let Value::Blob(ref memo) = row[12] else {
return Err(Error::ParseFailed("[get_coins] Memo parsing failed"))
};
let note = MoneyNote {
value,
token_id,
spend_hook: spend_hook.into(),
user_data,
coin_blind,
value_blind,
token_blind,
memo: memo.clone(),
};
let owncoin = OwnCoin { coin, note, secret, leaf_position };
owncoins.push((owncoin, is_spent))
owncoins.push(self.parse_coin_record(&row).await?)
}
Ok(owncoins)
}
/// Fetch provided token unspend balances from the wallet.
pub async fn get_token_coins(&self, token_id: &TokenId) -> Result<Vec<OwnCoin>> {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
)
.await;
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::RusqliteError(format!(
"[get_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?.0)
}
Ok(owncoins)
}
/// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
/// The boolean in the returned tuple notes if the coin was marked as spent.
async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool)> {
let Value::Blob(ref coin_bytes) = row[0] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin bytes parsing failed"))
};
let coin: Coin = deserialize_async(coin_bytes).await?;
let Value::Integer(is_spent) = row[1] else {
return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
};
let Ok(is_spent) = u64::try_from(is_spent) else {
return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
};
let is_spent = is_spent > 0;
let Value::Blob(ref value_bytes) = row[2] else {
return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
};
let value: u64 = deserialize_async(value_bytes).await?;
let Value::Blob(ref token_id_bytes) = row[3] else {
return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
let Value::Blob(ref spend_hook_bytes) = row[4] else {
return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
};
let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
let Value::Blob(ref user_data_bytes) = row[5] else {
return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
};
let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
let Value::Blob(ref coin_blind_bytes) = row[6] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
};
let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
let Value::Blob(ref value_blind_bytes) = row[7] else {
return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
};
let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
let Value::Blob(ref token_blind_bytes) = row[8] else {
return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
};
let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
let Value::Blob(ref secret_bytes) = row[9] else {
return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
};
let secret: SecretKey = deserialize_async(secret_bytes).await?;
// TODO: Remove from SQL store, can be derived ondemand
let Value::Blob(ref nullifier_bytes) = row[10] else {
return Err(Error::ParseFailed("[parse_coin_record] Nullifier bytes parsing failed"))
};
let _nullifier: Nullifier = deserialize_async(nullifier_bytes).await?;
let Value::Blob(ref leaf_position_bytes) = row[11] else {
return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
};
let leaf_position: bridgetree::Position = deserialize_async(leaf_position_bytes).await?;
let Value::Blob(ref memo) = row[12] else {
return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
};
let note = MoneyNote {
value,
token_id,
spend_hook: spend_hook.into(),
user_data,
coin_blind,
value_blind,
token_blind,
memo: memo.clone(),
};
Ok((OwnCoin { coin, note, secret, leaf_position }, is_spent))
}
/// Create an alias record for provided Token ID.
pub async fn add_alias(&self, alias: String, token_id: TokenId) -> WalletDbResult<()> {
println!("Generating alias {alias} for Token: {token_id}");
@@ -642,7 +682,10 @@ impl Drk {
match MoneyFunction::try_from(data[0])? {
MoneyFunction::FeeV1 => {
println!("[apply_tx_money_data] Found Money::FeeV1 call");
// TODO: implement
let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
nullifiers.push(params.input.nullifier);
coins.push(params.output.coin);
notes.push(params.output.note);
}
MoneyFunction::GenesisMintV1 => {
println!("[apply_tx_money_data] Found Money::GenesisMintV1 call");
@@ -871,4 +914,118 @@ impl Drk {
// Else parse input
Ok(TokenId::from_str(input.as_str())?)
}
/// Create and append a `Money::Fee` call to a given [`Transaction`].
///
/// Optionally takes a set of spent coins in order not to reuse them here.
///
/// Returns the `Fee` call, and all necessary data and parameters related.
pub async fn append_fee_call(
&self,
tx: &Transaction,
public_key: PublicKey,
money_merkle_tree: &MerkleTree,
fee_pk: &ProvingKey,
fee_zkbin: &ZkBinary,
spent_coins: Option<&[OwnCoin]>,
) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
// First we verify the fee-less transaction to see how much gas it uses for execution
// and verification.
let gas_used = FEE_CALL_GAS + self.get_tx_gas(tx, false).await?;
// Knowing the total gas, we can now find an OwnCoin of enough value
// so that we can create a valid Money::Fee call.
let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
available_coins.retain(|x| x.note.value > gas_used);
if let Some(spent_coins) = spent_coins {
available_coins.retain(|x| !spent_coins.contains(x));
}
if available_coins.is_empty() {
return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
}
let coin = &available_coins[0];
let change_value = coin.note.value - gas_used;
// Input and output setup
let input = FeeCallInput {
coin: coin.clone(),
merkle_path: money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
user_data_blind: BaseBlind::random(&mut OsRng),
};
let output = FeeCallOutput {
public_key,
value: change_value,
token_id: coin.note.token_id,
blind: BaseBlind::random(&mut OsRng),
spend_hook: FuncId::none(),
user_data: pallas::Base::ZERO,
};
// Create blinding factors
let token_blind = BaseBlind::random(&mut OsRng);
let input_value_blind = ScalarBlind::random(&mut OsRng);
let fee_value_blind = ScalarBlind::random(&mut OsRng);
let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
// Create an ephemeral signing key
let signature_secret = SecretKey::random(&mut OsRng);
// Create the actual fee proof
let (proof, public_inputs) = create_fee_proof(
fee_zkbin,
fee_pk,
&input,
input_value_blind,
&output,
output_value_blind,
output.spend_hook,
output.user_data,
output.blind,
token_blind,
signature_secret,
)?;
// Encrypted note for the output
let note = MoneyNote {
coin_blind: output.blind,
value: output.value,
token_id: output.token_id,
spend_hook: output.spend_hook,
user_data: output.user_data,
value_blind: output_value_blind,
token_blind,
memo: vec![],
};
let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;
let params = MoneyFeeParamsV1 {
input: Input {
value_commit: public_inputs.input_value_commit,
token_commit: public_inputs.token_commit,
nullifier: public_inputs.nullifier,
merkle_root: public_inputs.merkle_root,
user_data_enc: public_inputs.input_user_data_enc,
signature_public: public_inputs.signature_public,
},
output: Output {
value_commit: public_inputs.output_value_commit,
token_commit: public_inputs.token_commit,
coin: public_inputs.output_coin,
note: encrypted_note,
},
fee_value_blind,
token_blind,
};
// Encode the contract call
let mut data = vec![MoneyFunction::FeeV1 as u8];
gas_used.encode_async(&mut data).await?;
params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
Ok((call, vec![proof], vec![signature_secret]))
}
}

View File

@@ -278,7 +278,7 @@ impl Drk {
}
}
// Queries darkfid for a block with given height
// Queries darkfid for a block with given height.
async fn get_block_by_height(&self, height: u32) -> Result<BlockInfo> {
let req = JsonRequest::new(
"blockchain.get_block",
@@ -293,7 +293,7 @@ impl Drk {
}
/// Broadcast a given transaction to darkfid and forward onto the network.
/// Returns the transaction ID upon success
/// Returns the transaction ID upon success.
pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
println!("Broadcasting transaction...");
@@ -314,7 +314,7 @@ impl Drk {
Ok(txid)
}
/// Queries darkfid for a tx with given hash
/// Queries darkfid for a tx with given hash.
pub async fn get_tx(&self, tx_hash: &TransactionHash) -> Result<Option<Transaction>> {
let tx_hash_str = tx_hash.to_string();
let req = JsonRequest::new(
@@ -333,7 +333,7 @@ impl Drk {
}
}
/// Simulate the transaction with the state machine
/// Simulate the transaction with the state machine.
pub async fn simulate_tx(&self, tx: &Transaction) -> Result<bool> {
let tx_str = base64::encode(&serialize_async(tx).await);
let req =
@@ -361,4 +361,18 @@ impl Drk {
Ok(ret)
}
/// Queries darkfid for given transaction's gas.
pub async fn get_tx_gas(&self, tx: &Transaction, include_fee: bool) -> Result<u64> {
let params = JsonValue::Array(vec![
JsonValue::String(base64::encode(&serialize_async(tx).await)),
JsonValue::Boolean(include_fee),
]);
let req = JsonRequest::new("tx.calculate_gas", params);
let rep = self.rpc_client.as_ref().unwrap().request(req).await?;
let gas = *rep.get::<f64>().unwrap() as u64;
Ok(gas)
}
}

View File

@@ -24,12 +24,11 @@ use darkfi::{
Error, Result,
};
use darkfi_money_contract::{
client::{transfer_v1::make_transfer_call, OwnCoin},
model::TokenId,
MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
client::transfer_v1::make_transfer_call, model::TokenId, MoneyFunction,
MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, Keypair, PublicKey},
crypto::{contract_id::MONEY_CONTRACT_ID, Keypair, PublicKey},
tx::ContractCall,
};
use darkfi_serial::AsyncEncodable;
@@ -44,13 +43,8 @@ impl Drk {
token_id: TokenId,
recipient: PublicKey,
) -> Result<Transaction> {
// First get all unspent OwnCoins to see what our balance is.
let owncoins = self.get_coins(false).await?;
let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
// We're only interested in the ones for the token_id we're sending
// And the ones not owned by some protocol (meaning spend-hook should be 0)
owncoins.retain(|x| x.note.token_id == token_id);
owncoins.retain(|x| x.note.spend_hook == FuncId::none());
// First get all unspent OwnCoins to see what our balance is
let owncoins = self.get_token_coins(&token_id).await?;
if owncoins.is_empty() {
return Err(Error::Custom(format!("Did not find any coins with token ID: {token_id}")))
}
@@ -74,12 +68,10 @@ impl Drk {
let secret = self.default_secret().await?;
let keypair = Keypair::new(secret);
let contract_id = *MONEY_CONTRACT_ID;
// Now we need to do a lookup for the zkas proof bincodes, and create
// the circuit objects and proving keys so we can build the transaction.
// We also do this through the RPC.
let zkas_bins = self.lookup_zkas(&contract_id).await?;
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
else {
@@ -91,38 +83,71 @@ impl Drk {
return Err(Error::Custom("Burn circuit not found".to_string()))
};
let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
else {
return Err(Error::Custom("Fee circuit not found".to_string()))
};
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
// Creating Mint and Burn circuit proving keys
// Creating Mint, Burn and Fee circuits proving keys
let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
// Building transaction parameters
let (params, secrets, spent_coins) = make_transfer_call(
keypair, recipient, amount, token_id, owncoins, tree, mint_zkbin, mint_pk, burn_zkbin,
keypair,
recipient,
amount,
token_id,
owncoins,
tree.clone(),
mint_zkbin,
mint_pk,
burn_zkbin,
burn_pk,
)?;
// Encode and sign the transaction
// Encode the call
let mut data = vec![MoneyFunction::TransferV1 as u8];
params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
// Create the TransactionBuilder containing the `Transfer` call
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
// We first have to execute the fee-less tx to gather its used gas, and then we feed
// it into the fee-creating function.
// We also tell it about any spent coins so we don't accidentally reuse them in the
// fee call.
// TODO: We have to build a proper coin selection algorithm so that we can utilize
// the Money::Transfer to merge any coins which would give us a coin with enough
// value for paying the transaction fee.
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&secrets.signature_secrets)?;
tx.signatures = vec![sigs];
tx.signatures.push(sigs);
// We need to mark the coins we've spent in our wallet
for spent_coin in spent_coins {
if let Err(e) = self.mark_spent_coin(&spent_coin.coin).await {
return Err(Error::Custom(format!("Mark spent coin {spent_coin:?} failed: {e:?}")))
};
}
let (fee_call, fee_proofs, fee_secrets) = self
.append_fee_call(&tx, keypair.public, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins))
.await?;
// Append the fee call to the transaction
tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
// Now build the actual transaction and sign it with all necessary keys.
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&secrets.signature_secrets)?;
tx.signatures.push(sigs);
let sigs = tx.create_sigs(&fee_secrets)?;
tx.signatures.push(sigs);
Ok(tx)
}

View File

@@ -40,39 +40,39 @@ testnet user guide.
Note: List is not exhaustive. Missing functionalities that are not part
of the guide can be added for future regressions.
| # | Description | Command | Status |
|----|---------------------------|--------------------------------------------------|-------------------------|
| 0 | Initialization | wallet --initialize | Pass |
| 1 | Key generation | wallet --keygen | Pass |
| 2 | Set default wallet | wallet --default-address {ADDR_ID} | Pass |
| 3 | Default address retrieval | wallet --address | Pass |
| 4 | Block scanning | scan | Pass |
| 5 | Block subscribing | subscribe | Pass |
| 6 | Balance retrieval | wallet --balance | Pass |
| 7 | Aliases retrieval | alias show | Pass |
| 8 | Mint auth generation | token generate-mint | Pass |
| 9 | Mint auths retrieval | token list | Pass |
| 10 | Alias add | alias add {ALIAS} {TOKEN} | Pass |
| 11 | Aliases retrieval | alias show | Pass |
| 12 | Mint generation | token mint {ALIAS} {AMOUNT} {ADDR} | Failure: disabled |
| 13 | Transfer | transfer {AMOUNT} {ALIAS} {ADDR} | Failure: fee is missing |
| 14 | Coins retrieval | wallet --coins | Pass |
| 15 | OTC initialization | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Failure: needs #12 |
| 16 | OTC join | otc join | Failure: needs #15 |
| 17 | OTC sign | otc sign | Failure: needs #16 |
| 18 | DAO create | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN} | Failure: needs #12 |
| 19 | DAO view | dao view | Failure: needs #18 |
| 20 | DAO import | dao import | Failure: needs #18 |
| 21 | DAO list | dao sign | Failure: needs #18 |
| 22 | DAO mint | dao mint {DAO} | Failure: needs #18 |
| 23 | DAO balance | dao balance {DAO} | Failure: needs #18 |
| 24 | DAO propose | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN} | Failure: needs #18 |
| 25 | DAO proposals retrieval | dao proposals {DAO} | Failure: needs #24 |
| 26 | DAO proposal retrieval | dao proposal {DAO} {PROPOSAL_ID} | Failure: needs #24 |
| 27 | DAO vote | dao vote {DAO} {PROPOSAL_ID} {VOTE} {WEIGHT} | Failure: needs #24 |
| 28 | DAO proposal execution | dao exec {DAO} {PROPOSAL_ID} | Failure: needs #27 |
| 29 | Coins unspend | unspend {COIN} | Pass |
| 30 | Transaction inspect | inspect | Pass |
| 31 | Transaction simulate | explorer simulate-tx | Pass |
| 31 | Transaction broadcast | broadcast | Pass |
| # | Description | Command | Status |
|----|---------------------------|--------------------------------------------------|--------------------|
| 0 | Initialization | wallet --initialize | Pass |
| 1 | Key generation | wallet --keygen | Pass |
| 2 | Set default wallet | wallet --default-address {ADDR_ID} | Pass |
| 3 | Default address retrieval | wallet --address | Pass |
| 4 | Block scanning | scan | Pass |
| 5 | Block subscribing | subscribe | Pass |
| 6 | Balance retrieval | wallet --balance | Pass |
| 7 | Aliases retrieval | alias show | Pass |
| 8 | Mint auth generation | token generate-mint | Pass |
| 9 | Mint auths retrieval | token list | Pass |
| 10 | Alias add | alias add {ALIAS} {TOKEN} | Pass |
| 11 | Aliases retrieval | alias show | Pass |
| 12 | Mint generation | token mint {ALIAS} {AMOUNT} {ADDR} | Failure: disabled |
| 13 | Transfer | transfer {AMOUNT} {ALIAS} {ADDR} | Pass |
| 14 | Coins retrieval | wallet --coins | Pass |
| 15 | OTC initialization | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Failure: needs #12 |
| 16 | OTC join | otc join | Failure: needs #15 |
| 17 | OTC sign | otc sign | Failure: needs #16 |
| 18 | DAO create | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN} | Failure: needs #12 |
| 19 | DAO view | dao view | Failure: needs #18 |
| 20 | DAO import | dao import | Failure: needs #18 |
| 21 | DAO list | dao sign | Failure: needs #18 |
| 22 | DAO mint | dao mint {DAO} | Failure: needs #18 |
| 23 | DAO balance | dao balance {DAO} | Failure: needs #18 |
| 24 | DAO propose | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN} | Failure: needs #18 |
| 25 | DAO proposals retrieval | dao proposals {DAO} | Failure: needs #24 |
| 26 | DAO proposal retrieval | dao proposal {DAO} {PROPOSAL_ID} | Failure: needs #24 |
| 27 | DAO vote | dao vote {DAO} {PROPOSAL_ID} {VOTE} {WEIGHT} | Failure: needs #24 |
| 28 | DAO proposal execution | dao exec {DAO} {PROPOSAL_ID} | Failure: needs #27 |
| 29 | Coins unspend | unspend {COIN} | Pass |
| 30 | Transaction inspect | inspect | Pass |
| 31 | Transaction simulate | explorer simulate-tx | Pass |
| 31 | Transaction broadcast | broadcast | Pass |

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use darkfi_sdk::crypto::MerkleTree;
use log::{debug, error, info, warn};
@@ -30,6 +30,7 @@ use crate::{
},
error::TxVerifyFailed,
tx::Transaction,
zk::VerifyingKey,
Error, Result,
};
@@ -45,7 +46,7 @@ use pow::PoWModule;
pub mod verification;
use verification::{
verify_block, verify_checkpoint_block, verify_genesis_block, verify_producer_transaction,
verify_transactions,
verify_transaction, verify_transactions,
};
/// Fee calculation helpers
@@ -53,7 +54,7 @@ pub mod fees;
/// Helper utilities
pub mod utils;
use utils::{block_rank, deploy_native_contracts};
use utils::{best_fork_index, block_rank, deploy_native_contracts};
/// Configuration for initializing [`Validator`]
#[derive(Clone)]
@@ -127,6 +128,41 @@ impl Validator {
Ok(state)
}
/// Auxiliary function to compute provided transaction's total gas,
/// against current best fork.
/// The function takes a boolean called `verify_fee` to overwrite
/// the nodes configured `verify_fees` flag.
pub async fn calculate_gas(&self, tx: &Transaction, verify_fee: bool) -> Result<u64> {
// Grab the best fork to verify against
let forks = self.consensus.forks.read().await;
let fork = &forks[best_fork_index(&forks)?];
let overlay = fork.overlay.lock().unwrap().full_clone()?;
let next_block_height = fork.get_next_block_height()?;
drop(forks);
// Map of ZK proof verifying keys for the transaction
let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
for call in &tx.calls {
vks.insert(call.data.contract_id.to_bytes(), HashMap::new());
}
// Verify transaction to grab the gas used
let verify_result = verify_transaction(
&overlay,
next_block_height,
tx,
&mut MerkleTree::new(1),
&mut vks,
verify_fee,
)
.await;
// Purge new trees
overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
verify_result
}
/// The node retrieves a transaction, validates its state transition,
/// and appends it to the pending txs store.
pub async fn append_tx(&self, tx: &Transaction, write: bool) -> Result<()> {