-
Notifications
You must be signed in to change notification settings - Fork 18
/
money.rs
147 lines (129 loc) · 5.22 KB
/
money.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Wallet features related to spending money and checking balances.
use crate::{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, Transaction,
};
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},
verifier::Sr25519Signature,
};
/// Create and send a transaction that spends coins on the network
pub async fn spend_coins(
db: &Db,
client: &HttpClient,
keystore: &LocalKeystore,
args: SpendArgs,
) -> anyhow::Result<()> {
log::debug!("The args are:: {:?}", args);
// Construct a template Transaction to push coins into later
let mut transaction = Transaction {
inputs: Vec::new(),
peeks: Vec::new(),
outputs: Vec::new(),
checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend),
};
// Construct each output and then push to the transactions
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);
}
// The total input set will consist of any manually chosen inputs
// plus any automatically chosen to make the input amount high enough
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;
}
//TODO filtering on a specific sender
// If the supplied inputs are not valuable enough to cover the output amount
// we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.)
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"
))?,
}
}
// Make sure each input decodes and is still present in the node's storage,
// and then push to 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: vec![], // We will sign the total transaction so this should be empty
});
}
// Keep a copy of the stripped encoded transaction for signing purposes
let stripped_encoded_transaction = transaction.clone().encode();
// Iterate back through the inputs, signing, and putting the signatures in place.
for input in &mut transaction.inputs {
// Fetch the output from storage
let utxo = fetch_storage::<OuterVerifier>(&input.output_ref, client).await?;
// Construct the proof that it can be consumed
let redeemer = match utxo.verifier {
OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => {
let public = Public::from_h256(owner_pubkey);
crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)?
}
OuterVerifier::UpForGrabs(_) => Vec::new(),
OuterVerifier::ThresholdMultiSignature(_) => todo!(),
};
// insert the proof
input.redeemer = redeemer;
}
// Send the 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
);
// Print new output refs for user to check later
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(())
}
/// Given an output ref, fetch the details about this coin from the node's
/// storage.
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))
}