mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 08:51:08 +03:00
Add aggregate function for multi-kernel transaction (#966)
* Add aggregate function * Rustfmt * Add multikernel tx test * Rustfmt * Add test bad multi-kernel transaction * Add sorting * Modified multikernel test
This commit is contained in:
parent
7bad33d249
commit
bd64c6099f
3 changed files with 177 additions and 21 deletions
|
@ -353,26 +353,7 @@ mod test {
|
|||
assert!(tx2.validate().is_ok());
|
||||
|
||||
// now build a "cut_through" tx from tx1 and tx2
|
||||
let mut tx3 = tx1.clone();
|
||||
tx3.inputs.extend(tx2.inputs.iter().cloned());
|
||||
tx3.outputs.extend(tx2.outputs.iter().cloned());
|
||||
tx3.kernels.extend(tx2.kernels.iter().cloned());
|
||||
|
||||
// make sure everything is sorted
|
||||
tx3.inputs.sort();
|
||||
tx3.outputs.sort();
|
||||
tx3.kernels.sort();
|
||||
|
||||
// finally sum the offsets up
|
||||
// TODO - hide this in a convenience function somewhere
|
||||
tx3.offset = {
|
||||
let secp = static_secp_instance();
|
||||
let secp = secp.lock().unwrap();
|
||||
let skey1 = tx1.offset.secret_key(&secp).unwrap();
|
||||
let skey2 = tx2.offset.secret_key(&secp).unwrap();
|
||||
let skey3 = secp.blind_sum(vec![skey1, skey2], vec![]).unwrap();
|
||||
BlindingFactor::from_secret_key(skey3)
|
||||
};
|
||||
let tx3 = aggregate(vec![tx1, tx2]).unwrap();
|
||||
|
||||
assert!(tx3.validate().is_ok());
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
use util::secp::{self, Message, Signature};
|
||||
use util::{kernel_sig_msg, static_secp_instance};
|
||||
use util::secp::pedersen::{Commitment, ProofMessage, RangeProof};
|
||||
use std::collections::HashSet;
|
||||
use std::cmp::max;
|
||||
use std::cmp::Ordering;
|
||||
use std::{error, fmt};
|
||||
|
@ -488,6 +489,80 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Aggregate a vec of transactions into a multi-kernel transaction
|
||||
pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
|
||||
let mut inputs: Vec<Input> = vec![];
|
||||
let mut outputs: Vec<Output> = vec![];
|
||||
let mut kernels: Vec<TxKernel> = vec![];
|
||||
|
||||
// we will sum these together at the end to give us the overall offset for the
|
||||
// transaction
|
||||
let mut kernel_offsets = vec![];
|
||||
|
||||
for mut transaction in transactions {
|
||||
// we will summ these later to give a single aggregate offset
|
||||
kernel_offsets.push(transaction.offset);
|
||||
|
||||
inputs.append(&mut transaction.inputs);
|
||||
outputs.append(&mut transaction.outputs);
|
||||
kernels.append(&mut transaction.kernels);
|
||||
}
|
||||
|
||||
// now sum the kernel_offsets up to give us an aggregate offset for the
|
||||
// transaction
|
||||
let total_kernel_offset = {
|
||||
let secp = static_secp_instance();
|
||||
let secp = secp.lock().unwrap();
|
||||
let mut keys = kernel_offsets
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|x| *x != BlindingFactor::zero())
|
||||
.filter_map(|x| x.secret_key(&secp).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if keys.is_empty() {
|
||||
BlindingFactor::zero()
|
||||
} else {
|
||||
let sum = secp.blind_sum(keys, vec![])?;
|
||||
BlindingFactor::from_secret_key(sum)
|
||||
}
|
||||
};
|
||||
|
||||
let in_set = inputs
|
||||
.iter()
|
||||
.map(|inp| inp.commitment())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let out_set = outputs
|
||||
.iter()
|
||||
.filter(|out| !out.features.contains(OutputFeatures::COINBASE_OUTPUT))
|
||||
.map(|out| out.commitment())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let to_cut_through = in_set.intersection(&out_set).collect::<HashSet<_>>();
|
||||
|
||||
let mut new_inputs = inputs
|
||||
.iter()
|
||||
.filter(|inp| !to_cut_through.contains(&inp.commitment()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut new_outputs = outputs
|
||||
.iter()
|
||||
.filter(|out| !to_cut_through.contains(&out.commitment()))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// sort them lexicographically
|
||||
new_inputs.sort();
|
||||
new_outputs.sort();
|
||||
kernels.sort();
|
||||
|
||||
let tx = Transaction::new(new_inputs, new_outputs, kernels);
|
||||
|
||||
Ok(tx.with_offset(total_kernel_offset))
|
||||
}
|
||||
|
||||
/// A transaction input.
|
||||
///
|
||||
/// Primarily a reference to an output being spent by the transaction.
|
||||
|
|
102
pool/src/pool.rs
102
pool/src/pool.rs
|
@ -860,7 +860,7 @@ mod tests {
|
|||
use core::core::hash::{Hash, Hashed};
|
||||
use core::core::pmmr::MerkleProof;
|
||||
use core::core::target::Difficulty;
|
||||
use core::core::transaction::ProofMessageElements;
|
||||
use core::core::transaction::{self, ProofMessageElements};
|
||||
use types::PoolError::InvalidTx;
|
||||
|
||||
macro_rules! expect_output_parent {
|
||||
|
@ -942,6 +942,106 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Attempt to add a multi kernel transaction to the mempool
|
||||
fn test_multikernel_pool_add() {
|
||||
let mut dummy_chain = DummyChainImpl::new();
|
||||
let head_header = block::BlockHeader {
|
||||
height: 1,
|
||||
..block::BlockHeader::default()
|
||||
};
|
||||
dummy_chain.store_head_header(&head_header);
|
||||
|
||||
let parent_transaction = test_transaction(vec![5, 6, 7], vec![11, 3]);
|
||||
// We want this transaction to be rooted in the blockchain.
|
||||
let new_output = DummyOutputSet::empty()
|
||||
.with_output(test_output(5))
|
||||
.with_output(test_output(6))
|
||||
.with_output(test_output(7))
|
||||
.with_output(test_output(8));
|
||||
|
||||
// Prepare a second transaction, connected to the first.
|
||||
let child_transaction = test_transaction(vec![11, 3], vec![12]);
|
||||
|
||||
let txs = vec![parent_transaction, child_transaction];
|
||||
let multi_kernel_transaction = transaction::aggregate(txs).unwrap();
|
||||
|
||||
dummy_chain.update_output_set(new_output);
|
||||
|
||||
// To mirror how this construction is intended to be used, the pool
|
||||
// is placed inside a RwLock.
|
||||
let pool = RwLock::new(test_setup(&Arc::new(dummy_chain)));
|
||||
|
||||
// Take the write lock and add a pool entry
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
// First, add the transaction rooted in the blockchain
|
||||
let result =
|
||||
write_pool.add_to_memory_pool(test_source(), multi_kernel_transaction, false);
|
||||
if result.is_err() {
|
||||
panic!(
|
||||
"got an error adding multi-kernel tx: {:?}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Now take the read lock and use a few exposed methods to check consistency
|
||||
{
|
||||
let read_pool = pool.read().unwrap();
|
||||
assert_eq!(read_pool.total_size(), 1);
|
||||
expect_output_parent!(read_pool, Parent::PoolTransaction{tx_ref: _}, 12);
|
||||
expect_output_parent!(read_pool, Parent::AlreadySpent{other_tx: _}, 5);
|
||||
expect_output_parent!(read_pool, Parent::BlockTransaction, 8);
|
||||
expect_output_parent!(read_pool, Parent::Unknown, 11, 3, 20);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Attempt to add a bad multi kernel transaction to the mempool should get rejected
|
||||
fn test_bad_multikernel_pool_add() {
|
||||
let mut dummy_chain = DummyChainImpl::new();
|
||||
let head_header = block::BlockHeader {
|
||||
height: 1,
|
||||
..block::BlockHeader::default()
|
||||
};
|
||||
dummy_chain.store_head_header(&head_header);
|
||||
|
||||
let parent_transaction = test_transaction(vec![5, 6, 7], vec![11, 3]);
|
||||
// We want this transaction to be rooted in the blockchain.
|
||||
let new_output = DummyOutputSet::empty()
|
||||
.with_output(test_output(5))
|
||||
.with_output(test_output(6))
|
||||
.with_output(test_output(7))
|
||||
.with_output(test_output(8));
|
||||
|
||||
// Prepare a second transaction, connected to the first.
|
||||
let child_transaction1 = test_transaction(vec![11, 3], vec![12]);
|
||||
let child_transaction2 = test_transaction(vec![11, 3], vec![10]);
|
||||
|
||||
let txs = vec![parent_transaction, child_transaction1, child_transaction2];
|
||||
let bad_multi_kernel_transaction = transaction::aggregate(txs).unwrap();
|
||||
|
||||
dummy_chain.update_output_set(new_output);
|
||||
|
||||
// To mirror how this construction is intended to be used, the pool
|
||||
// is placed inside a RwLock.
|
||||
let pool = RwLock::new(test_setup(&Arc::new(dummy_chain)));
|
||||
|
||||
// Take the write lock and add a pool entry
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
// First, add the transaction rooted in the blockchain
|
||||
let result =
|
||||
write_pool.add_to_memory_pool(test_source(), bad_multi_kernel_transaction, false);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// A basic test; add a transaction to the pool and add the child to the
|
||||
/// stempool
|
||||
|
|
Loading…
Reference in a new issue