Consolidate and cleanup tx aggregation (#1332)

* Include commitments non-duplicate checks in aggregate
* Remove said check from the pool
* Block building now uses tx aggregation to reduce duplication
This commit is contained in:
Ignotus Peverell
2018-08-12 19:08:08 -07:00
committed by GitHub
parent 4be97abbbb
commit e9c987c075
12 changed files with 149 additions and 191 deletions
+26 -54
View File
@@ -523,7 +523,8 @@ impl Block {
let header = self.header.clone();
let nonce = thread_rng().next_u64();
let mut out_full = self.outputs
let mut out_full = self
.outputs
.iter()
.filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT))
.cloned()
@@ -564,55 +565,19 @@ impl Block {
reward_kern: TxKernel,
difficulty: Difficulty,
) -> Result<Block, Error> {
let mut kernels = vec![];
let mut inputs = vec![];
let mut outputs = vec![];
// A block is just a big transaction, aggregate as such. Note that
// aggregate also runs validation and duplicate commitment checks.
let agg_tx = transaction::aggregate(txs, Some((reward_out, reward_kern)))?;
// we will sum these together at the end
// to give us the overall offset for the block
let mut kernel_offsets = vec![];
// iterate over the all the txs
// build the kernel for each
// and collect all the kernels, inputs and outputs
// to build the block (which we can sort of think of as one big tx?)
for tx in txs {
// validate each transaction and gather their kernels
// tx has an offset k2 where k = k1 + k2
// and the tx is signed using k1
// the kernel excess is k1G
// we will sum all the offsets later and store the total offset
// on the block_header
tx.validate()?;
// we will sum these later to give a single aggregate offset
kernel_offsets.push(tx.offset);
// add all tx inputs/outputs/kernels to the block
kernels.extend(tx.kernels.into_iter());
inputs.extend(tx.inputs.into_iter());
outputs.extend(tx.outputs.into_iter());
}
// include the reward kernel and output
kernels.push(reward_kern);
outputs.push(reward_out);
// now sort everything so the block is built deterministically
inputs.sort();
outputs.sort();
kernels.sort();
// now sum the kernel_offsets up to give us
// an aggregate offset for the entire block
kernel_offsets.push(prev.total_kernel_offset);
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
// Now add the kernel offset of the previous block for a total
let total_kernel_offset =
committed::sum_kernel_offsets(vec![agg_tx.offset, prev.total_kernel_offset], vec![])?;
let total_kernel_sum = {
let zero_commit = secp_static::commit_to_zero_value();
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let mut excesses = map_vec!(kernels, |x| x.excess());
let mut excesses = map_vec!(agg_tx.kernels, |x| x.excess());
excesses.push(prev.total_kernel_sum);
excesses.retain(|x| *x != zero_commit);
secp.commit_sum(excesses, vec![])?
@@ -628,9 +593,9 @@ impl Block {
total_kernel_sum,
..Default::default()
},
inputs,
outputs,
kernels,
inputs: agg_tx.inputs,
outputs: agg_tx.outputs,
kernels: agg_tx.kernels,
}.cut_through())
}
@@ -655,12 +620,14 @@ impl Block {
/// we do not want to cut-through (all coinbase must be preserved)
///
pub fn cut_through(self) -> Block {
let in_set = self.inputs
let in_set = self
.inputs
.iter()
.map(|inp| inp.commitment())
.collect::<HashSet<_>>();
let out_set = self.outputs
let out_set = self
.outputs
.iter()
.filter(|out| !out.features.contains(OutputFeatures::COINBASE_OUTPUT))
.map(|out| out.commitment())
@@ -668,12 +635,14 @@ impl Block {
let to_cut_through = in_set.intersection(&out_set).collect::<HashSet<_>>();
let new_inputs = self.inputs
let new_inputs = self
.inputs
.into_iter()
.filter(|inp| !to_cut_through.contains(&inp.commitment()))
.collect::<Vec<_>>();
let new_outputs = self.outputs
let new_outputs = self
.outputs
.into_iter()
.filter(|out| !to_cut_through.contains(&out.commitment()))
.collect::<Vec<_>>();
@@ -757,7 +726,8 @@ impl Block {
// Verify that no input is spending an output from the same block.
fn verify_cut_through(&self) -> Result<(), Error> {
for inp in &self.inputs {
if self.outputs
if self
.outputs
.iter()
.any(|out| out.commitment() == inp.commitment())
{
@@ -800,12 +770,14 @@ impl Block {
/// Check the sum of coinbase-marked outputs match
/// the sum of coinbase-marked kernels accounting for fees.
pub fn verify_coinbase(&self) -> Result<(), Error> {
let cb_outs = self.outputs
let cb_outs = self
.outputs
.iter()
.filter(|out| out.features.contains(OutputFeatures::COINBASE_OUTPUT))
.collect::<Vec<&Output>>();
let cb_kerns = self.kernels
let cb_kerns = self
.kernels
.iter()
.filter(|kernel| kernel.features.contains(KernelFeatures::COINBASE_KERNEL))
.collect::<Vec<&TxKernel>>();
+37 -37
View File
@@ -314,7 +314,7 @@ impl Readable for Transaction {
// Treat any validation issues as data corruption.
// An example of this would be reading a tx
// that exceeded the allowed number of inputs.
tx.validate().map_err(|_| ser::Error::CorruptedData)?;
tx.validate(false).map_err(|_| ser::Error::CorruptedData)?;
Ok(tx)
}
@@ -446,12 +446,14 @@ impl Transaction {
/// Validates all relevant parts of a fully built transaction. Checks the
/// excess value against the signature as well as range proofs for each
/// output.
pub fn validate(&self) -> Result<(), Error> {
self.verify_features()?;
self.verify_weight()?;
pub fn validate(&self, as_block: bool) -> Result<(), Error> {
if !as_block {
self.verify_features()?;
self.verify_weight()?;
self.verify_kernel_sums(self.overage(), self.offset)?;
}
self.verify_sorted()?;
self.verify_cut_through()?;
self.verify_kernel_sums(self.overage(), self.offset)?;
self.verify_rangeproofs()?;
self.verify_kernel_signatures()?;
Ok(())
@@ -482,7 +484,8 @@ impl Transaction {
// Verify that no input is spending an output from the same block.
fn verify_cut_through(&self) -> Result<(), Error> {
for inp in &self.inputs {
if self.outputs
if self
.outputs
.iter()
.any(|out| out.commitment() == inp.commitment())
{
@@ -503,7 +506,8 @@ impl Transaction {
// Verify we have no outputs tagged as COINBASE_OUTPUT.
fn verify_output_features(&self) -> Result<(), Error> {
if self.outputs
if self
.outputs
.iter()
.any(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT))
{
@@ -514,7 +518,8 @@ impl Transaction {
// Verify we have no kernels tagged as COINBASE_KERNEL.
fn verify_kernel_features(&self) -> Result<(), Error> {
if self.kernels
if self
.kernels
.iter()
.any(|x| x.features.contains(KernelFeatures::COINBASE_KERNEL))
{
@@ -525,8 +530,12 @@ impl Transaction {
}
/// Aggregate a vec of transactions into a multi-kernel transaction with
/// cut_through
pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
/// cut_through. Optionally allows passing a reward output and kernel for
/// block building.
pub fn aggregate(
transactions: Vec<Transaction>,
reward: Option<(Output, TxKernel)>,
) -> Result<Transaction, Error> {
let mut inputs: Vec<Input> = vec![];
let mut outputs: Vec<Output> = vec![];
let mut kernels: Vec<TxKernel> = vec![];
@@ -543,37 +552,24 @@ pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
outputs.append(&mut transaction.outputs);
kernels.append(&mut transaction.kernels);
}
let as_block = reward.is_some();
if let Some((out, kernel)) = reward {
outputs.push(out);
kernels.push(kernel);
}
// 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
.into_iter()
.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)
}
};
// assemble output commitments set, checking they're all unique
let mut out_set = HashSet::new();
let all_uniq = { outputs.iter().all(|o| out_set.insert(o.commitment())) };
if !all_uniq {
return Err(Error::AggregationError);
}
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
@@ -591,6 +587,10 @@ pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
new_outputs.sort();
kernels.sort();
// now sum the kernel_offsets up to give us an aggregate offset for the
// transaction
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
// build a new aggregate tx from the following -
// * cut-through inputs
// * cut-through outputs
@@ -602,7 +602,7 @@ pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
// The resulting tx could be invalid for a variety of reasons -
// * tx too large (too many inputs|outputs|kernels)
// * cut-through may have invalidated the sums
tx.validate()?;
tx.validate(as_block)?;
Ok(tx)
}
@@ -618,7 +618,7 @@ pub fn deaggregate(mk_tx: Transaction, txs: Vec<Transaction>) -> Result<Transact
// transaction
let mut kernel_offsets = vec![];
let tx = aggregate(txs)?;
let tx = aggregate(txs, None)?;
for mk_input in mk_tx.inputs {
if !tx.inputs.contains(&mk_input) && !inputs.contains(&mk_input) {
@@ -670,7 +670,7 @@ pub fn deaggregate(mk_tx: Transaction, txs: Vec<Transaction>) -> Result<Transact
let tx = Transaction::new(inputs, outputs, kernels).with_offset(total_kernel_offset);
// Now validate the resulting tx to ensure we have not built something invalid.
tx.validate()?;
tx.validate(false)?;
Ok(tx)
}