-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathmoney.rs
245 lines (216 loc) · 8.68 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! Wallet features related to spending money and checking balances.
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,
};
/// Create and send a transaction that mints the coins on the network
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(())
}
/// Create and send a transaction that spends coins on the network
pub async fn spend_coins(
parachain: bool,
db: &Db,
client: &HttpClient,
keystore: &LocalKeystore,
args: SpendArgs,
) -> anyhow::Result<()> {
// Depending how the parachain and metadata support shapes up, it may make sense to have a
// macro that writes all of these helpers and ifs.
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);
// Construct a template Transaction to push coins into later
let mut transaction: Transaction<OuterVerifier, Checker> = Transaction {
inputs: Vec::new(),
peeks: Vec::new(),
outputs: Vec::new(),
checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend).into(),
};
// 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: Default::default(), // 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);
let signature =
crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)?;
OuterVerifierRedeemer::Sr25519Signature(signature)
}
OuterVerifier::UpForGrabs(_) => OuterVerifierRedeemer::UpForGrabs(()),
OuterVerifier::ThresholdMultiSignature(_) => todo!(),
};
// insert the proof
let encoded_redeemer = redeemer.encode();
log::debug!("encoded redeemer is: {:?}", encoded_redeemer);
input.redeemer = RedemptionStrategy::Redemption(encoded_redeemer);
}
log::debug!("signed transactions is: {:#?}", transaction);
// 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))
}
/// Apply a transaction to the local database, storing the new coins.
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 }) => {
// Add it to the global unspent_outputs table
crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount)
}
_ => Err(anyhow!("{:?}", ())),
}
}