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:
+26
-54
@@ -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>>();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user