use crate::{cli::MintCoinArgs, cli::SpendArgs, rpc::fetch_storage, sync};
use anyhow::anyhow;
use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params};
use parity_scale_codec::Encode;
use runtime::{
money::{Coin, MoneyConstraintChecker},
OuterConstraintChecker, OuterVerifier, OuterVerifierRedeemer,
};
use sc_keystore::LocalKeystore;
use sled::Db;
use sp_core::sr25519::Public;
use sp_runtime::traits::{BlakeTwo256, Hash};
use tuxedo_core::{
types::{Input, Output, OutputRef, RedemptionStrategy, Transaction},
verifier::Sr25519Signature,
ConstraintChecker,
};
pub async fn mint_coins(
parachain: bool,
client: &HttpClient,
args: MintCoinArgs,
) -> anyhow::Result<()> {
if parachain {
mint_coins_helper::<crate::ParachainConstraintChecker>(client, args).await
} else {
mint_coins_helper::<crate::OuterConstraintChecker>(client, args).await
}
}
pub async fn mint_coins_helper<Checker: ConstraintChecker + From<OuterConstraintChecker>>(
client: &HttpClient,
args: MintCoinArgs,
) -> anyhow::Result<()> {
log::debug!("The args are:: {:?}", args);
let transaction: tuxedo_core::types::Transaction<OuterVerifier, Checker> = Transaction {
inputs: Vec::new(),
peeks: Vec::new(),
outputs: vec![(
Coin::<0>::new(args.amount),
OuterVerifier::Sr25519Signature(Sr25519Signature {
owner_pubkey: args.owner,
}),
)
.into()],
checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Mint).into(),
};
let encoded_tx = hex::encode(transaction.encode());
let params = rpc_params![encoded_tx];
let _spawn_response: Result<String, _> = client.request("author_submitExtrinsic", params).await;
log::info!(
"Node's response to mint-coin transaction: {:?}",
_spawn_response
);
let minted_coin_ref = OutputRef {
tx_hash: <BlakeTwo256 as Hash>::hash_of(&transaction.encode()),
index: 0,
};
let output = &transaction.outputs[0];
let amount = output.payload.extract::<Coin<0>>()?.0;
print!(
"Minted {:?} worth {amount}. ",
hex::encode(minted_coin_ref.encode())
);
crate::pretty_print_verifier(&output.verifier);
Ok(())
}
pub async fn spend_coins(
parachain: bool,
db: &Db,
client: &HttpClient,
keystore: &LocalKeystore,
args: SpendArgs,
) -> anyhow::Result<()> {
if parachain {
spend_coins_helper::<crate::ParachainConstraintChecker>(db, client, keystore, args).await
} else {
spend_coins_helper::<crate::OuterConstraintChecker>(db, client, keystore, args).await
}
}
pub async fn spend_coins_helper<Checker: ConstraintChecker + From<OuterConstraintChecker>>(
db: &Db,
client: &HttpClient,
keystore: &LocalKeystore,
args: SpendArgs,
) -> anyhow::Result<()> {
log::debug!("The args are:: {:?}", args);
let mut transaction: Transaction<OuterVerifier, Checker> = Transaction {
inputs: Vec::new(),
peeks: Vec::new(),
outputs: Vec::new(),
checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend).into(),
};
let mut total_output_amount = 0;
for amount in &args.output_amount {
let output = Output {
payload: Coin::<0>::new(*amount).into(),
verifier: OuterVerifier::Sr25519Signature(Sr25519Signature {
owner_pubkey: args.recipient,
}),
};
total_output_amount += amount;
transaction.outputs.push(output);
}
let mut total_input_amount = 0;
let mut all_input_refs = args.input;
for output_ref in &all_input_refs {
let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!(
"user-specified output ref not found in local database"
))?;
total_input_amount += amount;
}
if total_input_amount < total_output_amount {
match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? {
Some(more_inputs) => {
all_input_refs.extend(more_inputs);
}
None => Err(anyhow!(
"Not enough value in database to construct transaction"
))?,
}
}
for output_ref in &all_input_refs {
get_coin_from_storage(output_ref, client).await?;
transaction.inputs.push(Input {
output_ref: output_ref.clone(),
redeemer: Default::default(), });
}
let stripped_encoded_transaction = transaction.clone().encode();
for input in &mut transaction.inputs {
let utxo = fetch_storage::<OuterVerifier>(&input.output_ref, client).await?;
let redeemer = match utxo.verifier {
OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => {
let public = Public::from_h256(owner_pubkey);
let signature =
crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)?;
OuterVerifierRedeemer::Sr25519Signature(signature)
}
OuterVerifier::UpForGrabs(_) => OuterVerifierRedeemer::UpForGrabs(()),
OuterVerifier::ThresholdMultiSignature(_) => todo!(),
};
let encoded_redeemer = redeemer.encode();
log::debug!("encoded redeemer is: {:?}", encoded_redeemer);
input.redeemer = RedemptionStrategy::Redemption(encoded_redeemer);
}
log::debug!("signed transactions is: {:#?}", transaction);
let genesis_spend_hex = hex::encode(transaction.encode());
let params = rpc_params![genesis_spend_hex];
let genesis_spend_response: Result<String, _> =
client.request("author_submitExtrinsic", params).await;
log::info!(
"Node's response to spend transaction: {:?}",
genesis_spend_response
);
let tx_hash = <BlakeTwo256 as Hash>::hash_of(&transaction.encode());
for (i, output) in transaction.outputs.iter().enumerate() {
let new_coin_ref = OutputRef {
tx_hash,
index: i as u32,
};
let amount = output.payload.extract::<Coin<0>>()?.0;
print!(
"Created {:?} worth {amount}. ",
hex::encode(new_coin_ref.encode())
);
crate::pretty_print_verifier(&output.verifier);
}
Ok(())
}
pub async fn get_coin_from_storage(
output_ref: &OutputRef,
client: &HttpClient,
) -> anyhow::Result<(Coin<0>, OuterVerifier)> {
let utxo = fetch_storage::<OuterVerifier>(output_ref, client).await?;
let coin_in_storage: Coin<0> = utxo.payload.extract()?;
Ok((coin_in_storage, utxo.verifier))
}
pub(crate) fn apply_transaction(
db: &Db,
tx_hash: <BlakeTwo256 as Hash>::Output,
index: u32,
output: &Output<OuterVerifier>,
) -> anyhow::Result<()> {
let amount = output.payload.extract::<Coin<0>>()?.0;
let output_ref = OutputRef { tx_hash, index };
match output.verifier {
OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => {
crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount)
}
_ => Err(anyhow!("{:?}", ())),
}
}