2020-01-20 14:40:58 +03:00
|
|
|
// Copyright 2020 The Grin Developers
|
2017-03-21 00:07:00 +03:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
//! Utility functions to build Grin transactions. Handles the blinding of
|
|
|
|
//! inputs and outputs, maintaining the sum of blinding factors, producing
|
|
|
|
//! the excess signature, etc.
|
|
|
|
//!
|
|
|
|
//! Each building function is a combinator that produces a function taking
|
|
|
|
//! a transaction a sum of blinding factors, to return another transaction
|
|
|
|
//! and sum. Combinators can then be chained and executed using the
|
|
|
|
//! _transaction_ function.
|
|
|
|
//!
|
|
|
|
//! Example:
|
2019-11-01 13:56:58 +03:00
|
|
|
//! build::transaction(
|
|
|
|
//! KernelFeatures::Plain{ fee: 2 },
|
|
|
|
//! vec![
|
|
|
|
//! input_rand(75),
|
|
|
|
//! output_rand(42),
|
|
|
|
//! output_rand(32),
|
|
|
|
//! ]
|
|
|
|
//! )
|
|
|
|
|
|
|
|
use crate::core::{Input, KernelFeatures, Output, OutputFeatures, Transaction, TxKernel};
|
2019-06-27 11:19:17 +03:00
|
|
|
use crate::libtx::proof::{self, ProofBuild};
|
|
|
|
use crate::libtx::{aggsig, Error};
|
2019-11-14 18:27:30 +03:00
|
|
|
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
|
2017-03-21 00:07:00 +03:00
|
|
|
|
|
|
|
/// Context information available to transaction combinators.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub struct Context<'a, K, B>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2019-03-19 19:13:49 +03:00
|
|
|
/// The keychain used for key derivation
|
|
|
|
pub keychain: &'a K,
|
2019-06-27 11:19:17 +03:00
|
|
|
/// The bulletproof builder
|
|
|
|
pub builder: &'a B,
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Function type returned by the transaction combinators. Transforms a
|
2019-11-01 13:56:58 +03:00
|
|
|
/// (Transaction, BlindSum) tuple into another, given the provided context.
|
|
|
|
/// Will return an Err if seomthing went wrong at any point during transaction building.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub type Append<K, B> = dyn for<'a> Fn(
|
|
|
|
&'a mut Context<'_, K, B>,
|
2019-11-01 13:56:58 +03:00
|
|
|
Result<(Transaction, BlindSum), Error>,
|
|
|
|
) -> Result<(Transaction, BlindSum), Error>;
|
2017-03-21 00:07:00 +03:00
|
|
|
|
|
|
|
/// Adds an input with the provided value and blinding key to the transaction
|
|
|
|
/// being built.
|
2019-06-27 11:19:17 +03:00
|
|
|
fn build_input<K, B>(value: u64, features: OutputFeatures, key_id: Identifier) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-03-04 03:19:54 +03:00
|
|
|
Box::new(
|
2019-11-01 13:56:58 +03:00
|
|
|
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
|
|
|
|
if let Ok((tx, sum)) = acc {
|
|
|
|
let commit =
|
|
|
|
build
|
|
|
|
.keychain
|
2020-02-04 16:52:00 +03:00
|
|
|
.commit(value, &key_id, SwitchCommitmentType::Regular)?;
|
2019-11-01 13:56:58 +03:00
|
|
|
// TODO: proper support for different switch commitment schemes
|
|
|
|
let input = Input::new(features, commit);
|
|
|
|
Ok((
|
|
|
|
tx.with_input(input),
|
|
|
|
sum.sub_key_id(key_id.to_value_path(value)),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
acc
|
|
|
|
}
|
2018-03-04 03:19:54 +03:00
|
|
|
},
|
|
|
|
)
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|
|
|
|
|
2018-01-17 06:03:40 +03:00
|
|
|
/// Adds an input with the provided value and blinding key to the transaction
|
|
|
|
/// being built.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn input<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-03-04 03:19:54 +03:00
|
|
|
debug!(
|
2018-10-21 23:30:56 +03:00
|
|
|
"Building input (spending regular output): {}, {}",
|
|
|
|
value, key_id
|
2018-03-04 03:19:54 +03:00
|
|
|
);
|
2019-01-08 19:07:38 +03:00
|
|
|
build_input(value, OutputFeatures::Plain, key_id)
|
2018-01-17 06:03:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds a coinbase input spending a coinbase output.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn coinbase_input<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-10-21 23:30:56 +03:00
|
|
|
debug!("Building input (spending coinbase): {}, {}", value, key_id);
|
2019-01-08 19:07:38 +03:00
|
|
|
build_input(value, OutputFeatures::Coinbase, key_id)
|
2018-01-17 06:03:40 +03:00
|
|
|
}
|
|
|
|
|
2017-10-12 06:35:40 +03:00
|
|
|
/// Adds an output with the provided value and key identifier from the
|
|
|
|
/// keychain.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn output<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-03-04 03:19:54 +03:00
|
|
|
Box::new(
|
2019-11-01 13:56:58 +03:00
|
|
|
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
|
|
|
|
let (tx, sum) = acc?;
|
|
|
|
|
2019-06-27 11:19:17 +03:00
|
|
|
// TODO: proper support for different switch commitment schemes
|
2020-02-04 16:52:00 +03:00
|
|
|
let switch = SwitchCommitmentType::Regular;
|
2019-06-27 11:19:17 +03:00
|
|
|
|
2019-11-01 13:56:58 +03:00
|
|
|
let commit = build.keychain.commit(value, &key_id, switch)?;
|
2018-08-19 00:38:48 +03:00
|
|
|
|
2018-10-21 23:30:56 +03:00
|
|
|
debug!("Building output: {}, {:?}", value, commit);
|
2018-03-23 13:13:57 +03:00
|
|
|
|
2019-06-27 11:19:17 +03:00
|
|
|
let rproof = proof::create(
|
|
|
|
build.keychain,
|
|
|
|
build.builder,
|
|
|
|
value,
|
|
|
|
&key_id,
|
|
|
|
switch,
|
|
|
|
commit,
|
|
|
|
None,
|
2019-11-01 13:56:58 +03:00
|
|
|
)?;
|
2018-03-04 03:19:54 +03:00
|
|
|
|
2019-11-01 13:56:58 +03:00
|
|
|
Ok((
|
2018-03-04 03:19:54 +03:00
|
|
|
tx.with_output(Output {
|
2019-01-08 19:07:38 +03:00
|
|
|
features: OutputFeatures::Plain,
|
2019-09-04 16:53:05 +03:00
|
|
|
commit,
|
2018-03-04 03:19:54 +03:00
|
|
|
proof: rproof,
|
|
|
|
}),
|
2018-12-18 14:51:44 +03:00
|
|
|
sum.add_key_id(key_id.to_value_path(value)),
|
2019-11-01 13:56:58 +03:00
|
|
|
))
|
2018-03-04 03:19:54 +03:00
|
|
|
},
|
|
|
|
)
|
2017-10-11 21:12:01 +03:00
|
|
|
}
|
|
|
|
|
2018-02-13 18:35:30 +03:00
|
|
|
/// Adds a known excess value on the transaction being built. Usually used in
|
2017-03-21 00:07:00 +03:00
|
|
|
/// combination with the initial_tx function when a new transaction is built
|
|
|
|
/// by adding to a pre-existing one.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn with_excess<K, B>(excess: BlindingFactor) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-03-04 03:19:54 +03:00
|
|
|
Box::new(
|
2019-11-01 13:56:58 +03:00
|
|
|
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
|
|
|
|
acc.map(|(tx, sum)| (tx, sum.add_blinding_factor(excess.clone())))
|
2018-03-04 03:19:54 +03:00
|
|
|
},
|
|
|
|
)
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets an initial transaction to add to when building a new transaction.
|
2019-11-01 13:56:58 +03:00
|
|
|
pub fn initial_tx<K, B>(tx: Transaction) -> Box<Append<K, B>>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2018-03-04 03:19:54 +03:00
|
|
|
Box::new(
|
2019-11-01 13:56:58 +03:00
|
|
|
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
|
|
|
|
acc.map(|(_, sum)| (tx.clone(), sum))
|
2018-03-04 03:19:54 +03:00
|
|
|
},
|
|
|
|
)
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|
|
|
|
|
2019-11-01 13:56:58 +03:00
|
|
|
/// Takes an existing transaction and partially builds on it.
|
2017-03-21 00:07:00 +03:00
|
|
|
///
|
|
|
|
/// Example:
|
2019-11-01 13:56:58 +03:00
|
|
|
/// let (tx, sum) = build::transaction(tx, vec![input_rand(4), output_rand(1))], keychain)?;
|
2017-03-21 00:07:00 +03:00
|
|
|
///
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn partial_transaction<K, B>(
|
2019-11-01 13:56:58 +03:00
|
|
|
tx: Transaction,
|
2019-06-27 11:19:17 +03:00
|
|
|
elems: Vec<Box<Append<K, B>>>,
|
2018-06-08 08:21:54 +03:00
|
|
|
keychain: &K,
|
2019-06-27 11:19:17 +03:00
|
|
|
builder: &B,
|
2018-11-10 18:24:11 +03:00
|
|
|
) -> Result<(Transaction, BlindingFactor), Error>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2019-06-27 11:19:17 +03:00
|
|
|
let mut ctx = Context { keychain, builder };
|
2019-11-01 13:56:58 +03:00
|
|
|
let (tx, sum) = elems
|
|
|
|
.iter()
|
|
|
|
.fold(Ok((tx, BlindSum::new())), |acc, elem| elem(&mut ctx, acc))?;
|
2017-10-03 03:02:31 +03:00
|
|
|
let blind_sum = ctx.keychain.blind_sum(&sum)?;
|
2017-03-21 00:07:00 +03:00
|
|
|
Ok((tx, blind_sum))
|
|
|
|
}
|
|
|
|
|
2018-02-13 18:35:30 +03:00
|
|
|
/// Builds a complete transaction.
|
2020-05-28 17:26:18 +03:00
|
|
|
/// NOTE: We only use this in tests (for convenience).
|
|
|
|
/// In the real world we use signature aggregation across multiple participants.
|
2019-06-27 11:19:17 +03:00
|
|
|
pub fn transaction<K, B>(
|
2019-11-01 13:56:58 +03:00
|
|
|
features: KernelFeatures,
|
2019-06-27 11:19:17 +03:00
|
|
|
elems: Vec<Box<Append<K, B>>>,
|
|
|
|
keychain: &K,
|
|
|
|
builder: &B,
|
|
|
|
) -> Result<Transaction, Error>
|
2020-05-28 17:26:18 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
|
|
|
B: ProofBuild,
|
|
|
|
{
|
|
|
|
let mut kernel = TxKernel::with_features(features);
|
|
|
|
|
|
|
|
// Construct the message to be signed.
|
|
|
|
let msg = kernel.msg_to_sign()?;
|
|
|
|
|
|
|
|
// Generate kernel public excess and associated signature.
|
|
|
|
let excess = BlindingFactor::rand(&keychain.secp());
|
|
|
|
let skey = excess.secret_key(&keychain.secp())?;
|
|
|
|
kernel.excess = keychain.secp().commit(0, skey)?;
|
|
|
|
let pubkey = &kernel.excess.to_pubkey(&keychain.secp())?;
|
|
|
|
kernel.excess_sig = aggsig::sign_with_blinding(&keychain.secp(), &msg, &excess, Some(&pubkey))?;
|
|
|
|
kernel.verify()?;
|
|
|
|
transaction_with_kernel(elems, kernel, excess, keychain, builder)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Build a complete transaction with the provided kernel and corresponding private excess.
|
|
|
|
/// NOTE: Only used in tests (for convenience).
|
|
|
|
/// Cannot recommend passing private excess around like this in the real world.
|
|
|
|
pub fn transaction_with_kernel<K, B>(
|
|
|
|
elems: Vec<Box<Append<K, B>>>,
|
|
|
|
kernel: TxKernel,
|
|
|
|
excess: BlindingFactor,
|
|
|
|
keychain: &K,
|
|
|
|
builder: &B,
|
|
|
|
) -> Result<Transaction, Error>
|
2018-06-08 08:21:54 +03:00
|
|
|
where
|
|
|
|
K: Keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
B: ProofBuild,
|
2018-06-08 08:21:54 +03:00
|
|
|
{
|
2019-06-27 11:19:17 +03:00
|
|
|
let mut ctx = Context { keychain, builder };
|
2020-05-28 17:26:18 +03:00
|
|
|
let (tx, sum) = elems
|
2019-11-01 13:56:58 +03:00
|
|
|
.iter()
|
|
|
|
.fold(Ok((Transaction::empty(), BlindSum::new())), |acc, elem| {
|
|
|
|
elem(&mut ctx, acc)
|
|
|
|
})?;
|
2018-02-13 18:35:30 +03:00
|
|
|
let blind_sum = ctx.keychain.blind_sum(&sum)?;
|
|
|
|
|
2020-05-28 17:26:18 +03:00
|
|
|
// Update tx with new kernel and offset.
|
|
|
|
let mut tx = tx.replace_kernel(kernel);
|
|
|
|
tx.offset = blind_sum.split(&excess, &keychain.secp())?;
|
2018-02-13 18:35:30 +03:00
|
|
|
Ok(tx)
|
|
|
|
}
|
|
|
|
|
2018-12-08 02:59:40 +03:00
|
|
|
// Just a simple test, most exhaustive tests in the core.
|
2017-03-21 00:07:00 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2018-10-20 03:13:07 +03:00
|
|
|
use std::sync::Arc;
|
2019-11-14 18:27:30 +03:00
|
|
|
use util::RwLock;
|
2018-08-30 17:44:34 +03:00
|
|
|
|
2017-03-21 00:07:00 +03:00
|
|
|
use super::*;
|
2019-01-25 23:48:15 +03:00
|
|
|
use crate::core::transaction::Weighting;
|
2018-12-08 02:59:40 +03:00
|
|
|
use crate::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
2020-05-22 14:51:58 +03:00
|
|
|
use crate::global;
|
2019-06-27 11:19:17 +03:00
|
|
|
use crate::libtx::ProofBuilder;
|
2019-11-14 18:27:30 +03:00
|
|
|
use keychain::{ExtKeychain, ExtKeychainPath};
|
2017-03-21 00:07:00 +03:00
|
|
|
|
2018-12-08 02:59:40 +03:00
|
|
|
fn verifier_cache() -> Arc<RwLock<dyn VerifierCache>> {
|
2018-08-30 17:44:34 +03:00
|
|
|
Arc::new(RwLock::new(LruVerifierCache::new()))
|
|
|
|
}
|
|
|
|
|
2017-03-21 00:07:00 +03:00
|
|
|
#[test]
|
|
|
|
fn blind_simple_tx() {
|
2020-05-22 14:51:58 +03:00
|
|
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
2018-12-29 01:46:21 +03:00
|
|
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
2019-06-27 11:19:17 +03:00
|
|
|
let builder = ProofBuilder::new(&keychain);
|
2018-10-10 12:11:01 +03:00
|
|
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
|
|
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
|
|
|
let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
|
2017-10-03 03:02:31 +03:00
|
|
|
|
2018-08-30 17:44:34 +03:00
|
|
|
let vc = verifier_cache();
|
|
|
|
|
2018-02-13 18:35:30 +03:00
|
|
|
let tx = transaction(
|
2019-11-01 13:56:58 +03:00
|
|
|
KernelFeatures::Plain { fee: 2 },
|
|
|
|
vec![input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
2018-02-13 18:35:30 +03:00
|
|
|
&keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
&builder,
|
2018-12-08 02:59:40 +03:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-02-13 18:35:30 +03:00
|
|
|
|
2019-01-25 23:48:15 +03:00
|
|
|
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
2018-02-13 18:35:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn blind_simple_tx_with_offset() {
|
2020-05-22 14:51:58 +03:00
|
|
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
2018-12-29 01:46:21 +03:00
|
|
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
2019-06-27 11:19:17 +03:00
|
|
|
let builder = ProofBuilder::new(&keychain);
|
2018-10-10 12:11:01 +03:00
|
|
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
|
|
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
|
|
|
let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
|
2018-02-13 18:35:30 +03:00
|
|
|
|
2018-08-30 17:44:34 +03:00
|
|
|
let vc = verifier_cache();
|
|
|
|
|
2018-09-12 14:17:36 +03:00
|
|
|
let tx = transaction(
|
2019-11-01 13:56:58 +03:00
|
|
|
KernelFeatures::Plain { fee: 2 },
|
|
|
|
vec![input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
2017-10-03 03:02:31 +03:00
|
|
|
&keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
&builder,
|
2018-12-08 02:59:40 +03:00
|
|
|
)
|
|
|
|
.unwrap();
|
2017-10-03 03:02:31 +03:00
|
|
|
|
2019-01-25 23:48:15 +03:00
|
|
|
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|
2017-10-03 03:02:31 +03:00
|
|
|
|
2017-05-19 18:22:08 +03:00
|
|
|
#[test]
|
|
|
|
fn blind_simpler_tx() {
|
2020-05-22 14:51:58 +03:00
|
|
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
2018-12-29 01:46:21 +03:00
|
|
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
2019-06-27 11:19:17 +03:00
|
|
|
let builder = ProofBuilder::new(&keychain);
|
2018-10-10 12:11:01 +03:00
|
|
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
|
|
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
2017-10-03 03:02:31 +03:00
|
|
|
|
2018-08-30 17:44:34 +03:00
|
|
|
let vc = verifier_cache();
|
|
|
|
|
2018-02-13 18:35:30 +03:00
|
|
|
let tx = transaction(
|
2019-11-01 13:56:58 +03:00
|
|
|
KernelFeatures::Plain { fee: 4 },
|
|
|
|
vec![input(6, key_id1), output(2, key_id2)],
|
2017-10-17 00:23:10 +03:00
|
|
|
&keychain,
|
2019-06-27 11:19:17 +03:00
|
|
|
&builder,
|
2018-12-08 02:59:40 +03:00
|
|
|
)
|
|
|
|
.unwrap();
|
2017-10-03 03:02:31 +03:00
|
|
|
|
2019-01-25 23:48:15 +03:00
|
|
|
tx.validate(Weighting::AsTransaction, vc.clone()).unwrap();
|
2017-05-19 18:22:08 +03:00
|
|
|
}
|
2017-03-21 00:07:00 +03:00
|
|
|
}
|