mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 08:51:08 +03:00
introduce Inputs enum variants for future commit only support (#3406)
This commit is contained in:
parent
b43666af80
commit
83b269961a
8 changed files with 203 additions and 120 deletions
|
@ -629,11 +629,8 @@ impl BlockPrintable {
|
||||||
include_proof: bool,
|
include_proof: bool,
|
||||||
include_merkle_proof: bool,
|
include_merkle_proof: bool,
|
||||||
) -> Result<BlockPrintable, chain::Error> {
|
) -> Result<BlockPrintable, chain::Error> {
|
||||||
let inputs = block
|
let inputs: Vec<_> = block.inputs().into();
|
||||||
.inputs()
|
let inputs = inputs.iter().map(|x| x.commitment().to_hex()).collect();
|
||||||
.iter()
|
|
||||||
.map(|x| x.commitment().to_hex())
|
|
||||||
.collect();
|
|
||||||
let outputs = block
|
let outputs = block
|
||||||
.outputs()
|
.outputs()
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1052,7 +1052,8 @@ impl<'a> Extension<'a> {
|
||||||
// Add spent_pos to affected_pos to update the accumulator later on.
|
// Add spent_pos to affected_pos to update the accumulator later on.
|
||||||
// Remove the spent output from the output_pos index.
|
// Remove the spent output from the output_pos index.
|
||||||
let mut spent = vec![];
|
let mut spent = vec![];
|
||||||
for input in b.inputs() {
|
let inputs: Vec<_> = b.inputs().into();
|
||||||
|
for input in &inputs {
|
||||||
let pos = self.apply_input(input, batch)?;
|
let pos = self.apply_input(input, batch)?;
|
||||||
affected_pos.push(pos.pos);
|
affected_pos.push(pos.pos);
|
||||||
batch.delete_output_pos_height(&input.commitment())?;
|
batch.delete_output_pos_height(&input.commitment())?;
|
||||||
|
@ -1331,7 +1332,8 @@ impl<'a> Extension<'a> {
|
||||||
// reused output commitment. For example an output at pos 1, spent, reused at pos 2.
|
// reused output commitment. For example an output at pos 1, spent, reused at pos 2.
|
||||||
// The output_pos index should be updated to reflect the old pos 1 when unspent.
|
// The output_pos index should be updated to reflect the old pos 1 when unspent.
|
||||||
if let Ok(spent) = spent {
|
if let Ok(spent) = spent {
|
||||||
for (x, y) in block.inputs().iter().zip(spent) {
|
let inputs: Vec<_> = block.inputs().into();
|
||||||
|
for (x, y) in inputs.iter().zip(spent) {
|
||||||
batch.save_output_pos_height(&x.commitment(), y.pos, y.height)?;
|
batch.save_output_pos_height(&x.commitment(), y.pos, y.height)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
use crate::core::core::hash::{Hash, Hashed};
|
use crate::core::core::hash::{Hash, Hashed};
|
||||||
use crate::core::core::pmmr::{self, ReadonlyPMMR};
|
use crate::core::core::pmmr::{self, ReadonlyPMMR};
|
||||||
use crate::core::core::{Block, BlockHeader, Input, Output, OutputIdentifier, Transaction};
|
use crate::core::core::{Block, BlockHeader, Input, Inputs, Output, OutputIdentifier, Transaction};
|
||||||
use crate::core::global;
|
use crate::core::global;
|
||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use crate::store::Batch;
|
use crate::store::Batch;
|
||||||
|
@ -52,7 +52,8 @@ impl<'a> UTXOView<'a> {
|
||||||
self.validate_output(output, batch)?;
|
self.validate_output(output, batch)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for input in block.inputs() {
|
let inputs: Vec<_> = block.inputs().into();
|
||||||
|
for input in &inputs {
|
||||||
self.validate_input(input, batch)?;
|
self.validate_input(input, batch)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -66,7 +67,8 @@ impl<'a> UTXOView<'a> {
|
||||||
self.validate_output(output, batch)?;
|
self.validate_output(output, batch)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for input in tx.inputs() {
|
let inputs: Vec<_> = tx.inputs().into();
|
||||||
|
for input in &inputs {
|
||||||
self.validate_input(input, batch)?;
|
self.validate_input(input, batch)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -113,12 +115,13 @@ impl<'a> UTXOView<'a> {
|
||||||
/// that have not sufficiently matured.
|
/// that have not sufficiently matured.
|
||||||
pub fn verify_coinbase_maturity(
|
pub fn verify_coinbase_maturity(
|
||||||
&self,
|
&self,
|
||||||
inputs: &[Input],
|
inputs: &Inputs,
|
||||||
height: u64,
|
height: u64,
|
||||||
batch: &Batch<'_>,
|
batch: &Batch<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Find the greatest output pos of any coinbase
|
// Find the greatest output pos of any coinbase
|
||||||
// outputs we are attempting to spend.
|
// outputs we are attempting to spend.
|
||||||
|
let inputs: Vec<_> = inputs.into();
|
||||||
let pos = inputs
|
let pos = inputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| x.is_coinbase())
|
.filter(|x| x.is_coinbase())
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
use crate::consensus::{self, reward, REWARD};
|
use crate::consensus::{self, reward, REWARD};
|
||||||
use crate::core::committed::{self, Committed};
|
use crate::core::committed::{self, Committed};
|
||||||
use crate::core::compact_block::{CompactBlock, CompactBlockBody};
|
use crate::core::compact_block::CompactBlock;
|
||||||
use crate::core::hash::{DefaultHashable, Hash, Hashed, ZERO_HASH};
|
use crate::core::hash::{DefaultHashable, Hash, Hashed, ZERO_HASH};
|
||||||
use crate::core::verifier_cache::VerifierCache;
|
use crate::core::verifier_cache::VerifierCache;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
transaction, Commitment, Input, KernelFeatures, Output, Transaction, TransactionBody, TxKernel,
|
transaction, Commitment, Inputs, KernelFeatures, Output, Transaction, TransactionBody,
|
||||||
Weighting,
|
TxKernel, Weighting,
|
||||||
};
|
};
|
||||||
use crate::global;
|
use crate::global;
|
||||||
use crate::pow::{verify_size, Difficulty, Proof, ProofOfWork};
|
use crate::pow::{verify_size, Difficulty, Proof, ProofOfWork};
|
||||||
|
@ -32,10 +32,8 @@ use chrono::naive::{MAX_DATE, MIN_DATE};
|
||||||
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
|
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use keychain::{self, BlindingFactor};
|
use keychain::{self, BlindingFactor};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::from_hex;
|
use util::from_hex;
|
||||||
use util::RwLock;
|
use util::RwLock;
|
||||||
|
@ -562,36 +560,35 @@ impl Block {
|
||||||
|
|
||||||
let header = cb.header.clone();
|
let header = cb.header.clone();
|
||||||
|
|
||||||
let mut all_inputs = HashSet::new();
|
let mut inputs = vec![];
|
||||||
let mut all_outputs = HashSet::new();
|
let mut outputs = vec![];
|
||||||
let mut all_kernels = HashSet::new();
|
let mut kernels = vec![];
|
||||||
|
|
||||||
// collect all the inputs, outputs and kernels from the txs
|
// collect all the inputs, outputs and kernels from the txs
|
||||||
for tx in txs {
|
for tx in txs {
|
||||||
all_inputs.extend(tx.inputs());
|
let tx_inputs: Vec<_> = tx.inputs().into();
|
||||||
all_outputs.extend(tx.outputs());
|
inputs.extend_from_slice(tx_inputs.as_slice());
|
||||||
all_kernels.extend(tx.kernels());
|
outputs.extend_from_slice(tx.outputs());
|
||||||
|
kernels.extend_from_slice(tx.kernels());
|
||||||
}
|
}
|
||||||
|
|
||||||
// include the coinbase output(s) and kernel(s) from the compact_block
|
// apply cut-through to our tx inputs and outputs
|
||||||
{
|
let (inputs, outputs) = transaction::cut_through(&mut inputs, &mut outputs)?;
|
||||||
let body: CompactBlockBody = cb.into();
|
|
||||||
all_outputs.extend(body.out_full);
|
|
||||||
all_kernels.extend(body.kern_full);
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert the sets to vecs
|
let mut outputs = outputs.to_vec();
|
||||||
let all_inputs = Vec::from_iter(all_inputs);
|
let mut kernels = kernels.to_vec();
|
||||||
let all_outputs = Vec::from_iter(all_outputs);
|
|
||||||
let all_kernels = Vec::from_iter(all_kernels);
|
// include the full outputs and kernels from the compact block
|
||||||
|
outputs.extend_from_slice(cb.out_full());
|
||||||
|
kernels.extend_from_slice(cb.kern_full());
|
||||||
|
|
||||||
// Initialize a tx body and sort everything.
|
// Initialize a tx body and sort everything.
|
||||||
let body = TransactionBody::init(&all_inputs, &all_outputs, &all_kernels, false)?;
|
let body = TransactionBody::init(inputs, &outputs, &kernels, false)?;
|
||||||
|
|
||||||
// Finally return the full block.
|
// Finally return the full block.
|
||||||
// Note: we have not actually validated the block here,
|
// Note: we have not actually validated the block here,
|
||||||
// caller must validate the block.
|
// caller must validate the block.
|
||||||
Block { header, body }.cut_through()
|
Ok(Block { header, body })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a new empty block from a specified header
|
/// Build a new empty block from a specified header
|
||||||
|
@ -635,7 +632,7 @@ impl Block {
|
||||||
// Now build the block with all the above information.
|
// Now build the block with all the above information.
|
||||||
// Note: We have not validated the block here.
|
// Note: We have not validated the block here.
|
||||||
// Caller must validate the block as necessary.
|
// Caller must validate the block as necessary.
|
||||||
Block {
|
let block = Block {
|
||||||
header: BlockHeader {
|
header: BlockHeader {
|
||||||
version,
|
version,
|
||||||
height,
|
height,
|
||||||
|
@ -649,8 +646,8 @@ impl Block {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
body: agg_tx.into(),
|
body: agg_tx.into(),
|
||||||
}
|
};
|
||||||
.cut_through()
|
Ok(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes this block and returns a new block with the coinbase output
|
/// Consumes this block and returns a new block with the coinbase output
|
||||||
|
@ -662,18 +659,18 @@ impl Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get inputs
|
/// Get inputs
|
||||||
pub fn inputs(&self) -> &[Input] {
|
pub fn inputs(&self) -> Inputs {
|
||||||
&self.body.inputs
|
self.body.inputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get outputs
|
/// Get outputs
|
||||||
pub fn outputs(&self) -> &[Output] {
|
pub fn outputs(&self) -> &[Output] {
|
||||||
&self.body.outputs
|
&self.body.outputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get kernels
|
/// Get kernels
|
||||||
pub fn kernels(&self) -> &[TxKernel] {
|
pub fn kernels(&self) -> &[TxKernel] {
|
||||||
&self.body.kernels
|
&self.body.kernels()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sum of all fees (inputs less outputs) in the block
|
/// Sum of all fees (inputs less outputs) in the block
|
||||||
|
@ -681,24 +678,6 @@ impl Block {
|
||||||
self.body.fee()
|
self.body.fee()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Matches any output with a potential spending input, eliminating them
|
|
||||||
/// from the block. Provides a simple way to cut-through the block. The
|
|
||||||
/// elimination is stable with respect to the order of inputs and outputs.
|
|
||||||
/// Method consumes the block.
|
|
||||||
pub fn cut_through(self) -> Result<Block, Error> {
|
|
||||||
let mut inputs = self.inputs().to_vec();
|
|
||||||
let mut outputs = self.outputs().to_vec();
|
|
||||||
let (inputs, outputs) = transaction::cut_through(&mut inputs, &mut outputs)?;
|
|
||||||
|
|
||||||
// Initialize tx body and sort everything.
|
|
||||||
let body = TransactionBody::init(inputs, outputs, self.kernels(), false)?;
|
|
||||||
|
|
||||||
Ok(Block {
|
|
||||||
header: self.header,
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "Lightweight" validation that we can perform quickly during read/deserialization.
|
/// "Lightweight" validation that we can perform quickly during read/deserialization.
|
||||||
/// Subset of full validation that skips expensive verification steps, specifically -
|
/// Subset of full validation that skips expensive verification steps, specifically -
|
||||||
/// * rangeproof verification (on the body)
|
/// * rangeproof verification (on the body)
|
||||||
|
|
|
@ -375,9 +375,6 @@ pub enum Error {
|
||||||
InvalidProofMessage,
|
InvalidProofMessage,
|
||||||
/// Error when verifying kernel sums via committed trait.
|
/// Error when verifying kernel sums via committed trait.
|
||||||
Committed(committed::Error),
|
Committed(committed::Error),
|
||||||
/// Error when sums do not verify correctly during tx aggregation.
|
|
||||||
/// Likely a "double spend" across two unconfirmed txs.
|
|
||||||
AggregationError,
|
|
||||||
/// Validation error relating to cut-through (tx is spending its own
|
/// Validation error relating to cut-through (tx is spending its own
|
||||||
/// output).
|
/// output).
|
||||||
CutThrough,
|
CutThrough,
|
||||||
|
@ -650,23 +647,16 @@ pub enum Weighting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TransactionBody is a common abstraction for transaction and block
|
/// TransactionBody is a common abstraction for transaction and block
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct TransactionBody {
|
pub struct TransactionBody {
|
||||||
/// List of inputs spent by the transaction.
|
/// List of inputs spent by the transaction.
|
||||||
pub inputs: Vec<Input>,
|
pub inputs: Inputs,
|
||||||
/// List of outputs the transaction produces.
|
/// List of outputs the transaction produces.
|
||||||
pub outputs: Vec<Output>,
|
pub outputs: Vec<Output>,
|
||||||
/// List of kernels that make up this transaction (usually a single kernel).
|
/// List of kernels that make up this transaction (usually a single kernel).
|
||||||
pub kernels: Vec<TxKernel>,
|
pub kernels: Vec<TxKernel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PartialEq
|
|
||||||
impl PartialEq for TransactionBody {
|
|
||||||
fn eq(&self, l: &TransactionBody) -> bool {
|
|
||||||
self.inputs == l.inputs && self.outputs == l.outputs && self.kernels == l.kernels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of Writeable for a body, defines how to
|
/// Implementation of Writeable for a body, defines how to
|
||||||
/// write the body as binary.
|
/// write the body as binary.
|
||||||
impl Writeable for TransactionBody {
|
impl Writeable for TransactionBody {
|
||||||
|
@ -719,15 +709,16 @@ impl Readable for TransactionBody {
|
||||||
|
|
||||||
impl Committed for TransactionBody {
|
impl Committed for TransactionBody {
|
||||||
fn inputs_committed(&self) -> Vec<Commitment> {
|
fn inputs_committed(&self) -> Vec<Commitment> {
|
||||||
self.inputs.iter().map(|x| x.commitment()).collect()
|
let inputs: Vec<_> = self.inputs().into();
|
||||||
|
inputs.iter().map(|x| x.commitment()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputs_committed(&self) -> Vec<Commitment> {
|
fn outputs_committed(&self) -> Vec<Commitment> {
|
||||||
self.outputs.iter().map(|x| x.commitment()).collect()
|
self.outputs().iter().map(|x| x.commitment()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kernels_committed(&self) -> Vec<Commitment> {
|
fn kernels_committed(&self) -> Vec<Commitment> {
|
||||||
self.kernels.iter().map(|x| x.excess()).collect()
|
self.kernels().iter().map(|x| x.excess()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,7 +738,7 @@ impl TransactionBody {
|
||||||
/// Creates a new empty transaction (no inputs or outputs, zero fee).
|
/// Creates a new empty transaction (no inputs or outputs, zero fee).
|
||||||
pub fn empty() -> TransactionBody {
|
pub fn empty() -> TransactionBody {
|
||||||
TransactionBody {
|
TransactionBody {
|
||||||
inputs: vec![],
|
inputs: Inputs::default(),
|
||||||
outputs: vec![],
|
outputs: vec![],
|
||||||
kernels: vec![],
|
kernels: vec![],
|
||||||
}
|
}
|
||||||
|
@ -770,7 +761,7 @@ impl TransactionBody {
|
||||||
verify_sorted: bool,
|
verify_sorted: bool,
|
||||||
) -> Result<TransactionBody, Error> {
|
) -> Result<TransactionBody, Error> {
|
||||||
let mut body = TransactionBody {
|
let mut body = TransactionBody {
|
||||||
inputs: inputs.to_vec(),
|
inputs: inputs.into(),
|
||||||
outputs: outputs.to_vec(),
|
outputs: outputs.to_vec(),
|
||||||
kernels: kernels.to_vec(),
|
kernels: kernels.to_vec(),
|
||||||
};
|
};
|
||||||
|
@ -786,13 +777,30 @@ impl TransactionBody {
|
||||||
Ok(body)
|
Ok(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transaction inputs.
|
||||||
|
pub fn inputs(&self) -> Inputs {
|
||||||
|
self.inputs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction outputs.
|
||||||
|
pub fn outputs(&self) -> &[Output] {
|
||||||
|
&self.outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction kernels.
|
||||||
|
pub fn kernels(&self) -> &[TxKernel] {
|
||||||
|
&self.kernels
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a new body with the provided inputs added. Existing
|
/// Builds a new body with the provided inputs added. Existing
|
||||||
/// inputs, if any, are kept intact.
|
/// inputs, if any, are kept intact.
|
||||||
/// Sort order is maintained.
|
/// Sort order is maintained.
|
||||||
pub fn with_input(mut self, input: Input) -> TransactionBody {
|
pub fn with_input(mut self, input: Input) -> TransactionBody {
|
||||||
if let Err(e) = self.inputs.binary_search(&input) {
|
let mut inputs: Vec<_> = self.inputs.into();
|
||||||
self.inputs.insert(e, input)
|
if let Err(e) = inputs.binary_search(&input) {
|
||||||
|
inputs.insert(e, input)
|
||||||
};
|
};
|
||||||
|
self.inputs = inputs.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -957,7 +965,8 @@ impl TransactionBody {
|
||||||
// Verify that no input is spending an output from the same block.
|
// Verify that no input is spending an output from the same block.
|
||||||
// Assumes inputs and outputs are sorted
|
// Assumes inputs and outputs are sorted
|
||||||
fn verify_cut_through(&self) -> Result<(), Error> {
|
fn verify_cut_through(&self) -> Result<(), Error> {
|
||||||
let mut inputs = self.inputs.iter().map(|x| x.hash()).peekable();
|
let inputs: Vec<_> = self.inputs().into();
|
||||||
|
let mut inputs = inputs.iter().map(|x| x.hash()).peekable();
|
||||||
let mut outputs = self.outputs.iter().map(|x| x.hash()).peekable();
|
let mut outputs = self.outputs.iter().map(|x| x.hash()).peekable();
|
||||||
while let (Some(ih), Some(oh)) = (inputs.peek(), outputs.peek()) {
|
while let (Some(ih), Some(oh)) = (inputs.peek(), outputs.peek()) {
|
||||||
match ih.cmp(oh) {
|
match ih.cmp(oh) {
|
||||||
|
@ -1195,18 +1204,18 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get inputs
|
/// Get inputs
|
||||||
pub fn inputs(&self) -> &[Input] {
|
pub fn inputs(&self) -> Inputs {
|
||||||
&self.body.inputs
|
self.body.inputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get outputs
|
/// Get outputs
|
||||||
pub fn outputs(&self) -> &[Output] {
|
pub fn outputs(&self) -> &[Output] {
|
||||||
&self.body.outputs
|
&self.body.outputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get kernels
|
/// Get kernels
|
||||||
pub fn kernels(&self) -> &[TxKernel] {
|
pub fn kernels(&self) -> &[TxKernel] {
|
||||||
&self.body.kernels
|
&self.body.kernels()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total fee for a transaction is the sum of fees of all kernels.
|
/// Total fee for a transaction is the sum of fees of all kernels.
|
||||||
|
@ -1273,23 +1282,23 @@ impl Transaction {
|
||||||
|
|
||||||
/// Matches any output with a potential spending input, eliminating them
|
/// Matches any output with a potential spending input, eliminating them
|
||||||
/// from the Vec. Provides a simple way to cut-through a block or aggregated
|
/// from the Vec. Provides a simple way to cut-through a block or aggregated
|
||||||
/// transaction. The elimination is stable with respect to the order of inputs
|
/// transaction.
|
||||||
/// and outputs.
|
|
||||||
pub fn cut_through<'a>(
|
pub fn cut_through<'a>(
|
||||||
inputs: &'a mut [Input],
|
inputs: &'a mut [Input],
|
||||||
outputs: &'a mut [Output],
|
outputs: &'a mut [Output],
|
||||||
) -> Result<(&'a [Input], &'a [Output]), Error> {
|
) -> Result<(&'a [Input], &'a [Output]), Error> {
|
||||||
// assemble output commitments set, checking they're all unique
|
// Make sure inputs and outputs are sorted consistently by commitment.
|
||||||
outputs.sort_unstable();
|
inputs.sort_unstable_by_key(|x| x.commitment());
|
||||||
if outputs.windows(2).any(|pair| pair[0] == pair[1]) {
|
outputs.sort_unstable_by_key(|x| x.commitment());
|
||||||
return Err(Error::AggregationError);
|
|
||||||
}
|
|
||||||
inputs.sort_unstable();
|
|
||||||
let mut inputs_idx = 0;
|
let mut inputs_idx = 0;
|
||||||
let mut outputs_idx = 0;
|
let mut outputs_idx = 0;
|
||||||
let mut ncut = 0;
|
let mut ncut = 0;
|
||||||
while inputs_idx < inputs.len() && outputs_idx < outputs.len() {
|
while inputs_idx < inputs.len() && outputs_idx < outputs.len() {
|
||||||
match inputs[inputs_idx].hash().cmp(&outputs[outputs_idx].hash()) {
|
match inputs[inputs_idx]
|
||||||
|
.partial_cmp(&outputs[outputs_idx])
|
||||||
|
.expect("compare input to output")
|
||||||
|
{
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
inputs.swap(inputs_idx - ncut, inputs_idx);
|
inputs.swap(inputs_idx - ncut, inputs_idx);
|
||||||
inputs_idx += 1;
|
inputs_idx += 1;
|
||||||
|
@ -1318,10 +1327,21 @@ pub fn cut_through<'a>(
|
||||||
outputs_idx += 1;
|
outputs_idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
// Take new shorter slices with cut-through elements removed.
|
||||||
&inputs[..inputs.len() - ncut],
|
let inputs = &inputs[..inputs.len() - ncut];
|
||||||
&outputs[..outputs.len() - ncut],
|
let outputs = &outputs[..outputs.len() - ncut];
|
||||||
))
|
|
||||||
|
// Check we have no duplicate inputs after cut-through.
|
||||||
|
if inputs.windows(2).any(|pair| pair[0] == pair[1]) {
|
||||||
|
return Err(Error::CutThrough);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we have no duplicate outputs after cut-through.
|
||||||
|
if outputs.windows(2).any(|pair| pair[0] == pair[1]) {
|
||||||
|
return Err(Error::CutThrough);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((inputs, outputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aggregate a vec of txs into a multi-kernel tx with cut_through.
|
/// Aggregate a vec of txs into a multi-kernel tx with cut_through.
|
||||||
|
@ -1352,17 +1372,14 @@ pub fn aggregate(txs: &[Transaction]) -> Result<Transaction, Error> {
|
||||||
// we will sum these later to give a single aggregate offset
|
// we will sum these later to give a single aggregate offset
|
||||||
kernel_offsets.push(tx.offset.clone());
|
kernel_offsets.push(tx.offset.clone());
|
||||||
|
|
||||||
inputs.extend_from_slice(tx.inputs());
|
let tx_inputs: Vec<_> = tx.inputs().into();
|
||||||
|
inputs.extend_from_slice(&tx_inputs);
|
||||||
outputs.extend_from_slice(tx.outputs());
|
outputs.extend_from_slice(tx.outputs());
|
||||||
kernels.extend_from_slice(tx.kernels());
|
kernels.extend_from_slice(tx.kernels());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort inputs and outputs during cut_through.
|
|
||||||
let (inputs, outputs) = cut_through(&mut inputs, &mut outputs)?;
|
let (inputs, outputs) = cut_through(&mut inputs, &mut outputs)?;
|
||||||
|
|
||||||
// Now sort kernels.
|
|
||||||
kernels.sort_unstable();
|
|
||||||
|
|
||||||
// now sum the kernel_offsets up to give us an aggregate offset for the
|
// now sum the kernel_offsets up to give us an aggregate offset for the
|
||||||
// transaction
|
// transaction
|
||||||
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
|
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
|
||||||
|
@ -1372,6 +1389,7 @@ pub fn aggregate(txs: &[Transaction]) -> Result<Transaction, Error> {
|
||||||
// * cut-through outputs
|
// * cut-through outputs
|
||||||
// * full set of tx kernels
|
// * full set of tx kernels
|
||||||
// * sum of all kernel offsets
|
// * sum of all kernel offsets
|
||||||
|
// Note: We sort input/outputs/kernels when building the transaction body internally.
|
||||||
let tx = Transaction::new(inputs, outputs, &kernels).with_offset(total_kernel_offset);
|
let tx = Transaction::new(inputs, outputs, &kernels).with_offset(total_kernel_offset);
|
||||||
|
|
||||||
Ok(tx)
|
Ok(tx)
|
||||||
|
@ -1390,9 +1408,11 @@ pub fn deaggregate(mk_tx: Transaction, txs: &[Transaction]) -> Result<Transactio
|
||||||
|
|
||||||
let tx = aggregate(txs)?;
|
let tx = aggregate(txs)?;
|
||||||
|
|
||||||
for mk_input in mk_tx.inputs() {
|
let mk_inputs: Vec<_> = mk_tx.inputs().into();
|
||||||
if !tx.inputs().contains(&mk_input) && !inputs.contains(mk_input) {
|
for mk_input in mk_inputs {
|
||||||
inputs.push(*mk_input);
|
let tx_inputs: Vec<_> = tx.inputs().into();
|
||||||
|
if !tx_inputs.contains(&mk_input) && !inputs.contains(&mk_input) {
|
||||||
|
inputs.push(mk_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for mk_output in mk_tx.outputs() {
|
for mk_output in mk_tx.outputs() {
|
||||||
|
@ -1460,6 +1480,20 @@ pub struct Input {
|
||||||
impl DefaultHashable for Input {}
|
impl DefaultHashable for Input {}
|
||||||
hashable_ord!(Input);
|
hashable_ord!(Input);
|
||||||
|
|
||||||
|
// Inputs can be compared to outputs.
|
||||||
|
impl PartialEq<Output> for Input {
|
||||||
|
fn eq(&self, other: &Output) -> bool {
|
||||||
|
self.commitment() == other.commitment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs can be compared to outputs.
|
||||||
|
impl PartialOrd<Output> for Input {
|
||||||
|
fn partial_cmp(&self, other: &Output) -> Option<Ordering> {
|
||||||
|
Some(self.commitment().cmp(&other.commitment()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ::std::hash::Hash for Input {
|
impl ::std::hash::Hash for Input {
|
||||||
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
|
@ -1517,6 +1551,79 @@ impl Input {
|
||||||
self.features.is_plain()
|
self.features.is_plain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Wrapper around a vec of inputs.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub enum Inputs {
|
||||||
|
/// Vec of inputs.
|
||||||
|
FeaturesAndCommit(Vec<Input>),
|
||||||
|
// Vec of commitments.
|
||||||
|
// CommitOnly(Vec<Commitment>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Inputs> for Vec<Input> {
|
||||||
|
fn from(inputs: Inputs) -> Self {
|
||||||
|
match inputs {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Inputs> for Vec<Input> {
|
||||||
|
fn from(inputs: &Inputs) -> Self {
|
||||||
|
match inputs {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[Input]> for Inputs {
|
||||||
|
fn from(inputs: &[Input]) -> Self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<Input>> for Inputs {
|
||||||
|
fn from(inputs: Vec<Input>) -> Self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Inputs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Inputs::FeaturesAndCommit(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writeable for Inputs {
|
||||||
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
|
match self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs.write(writer)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inputs {
|
||||||
|
/// Number of inputs.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify inputs are sorted and unique.
|
||||||
|
fn verify_sorted_and_unique(&self) -> Result<(), ser::Error> {
|
||||||
|
match self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs.verify_sorted_and_unique(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_unstable(&mut self) {
|
||||||
|
match self {
|
||||||
|
Inputs::FeaturesAndCommit(inputs) => inputs.sort_unstable(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enum of various supported kernel "features".
|
// Enum of various supported kernel "features".
|
||||||
enum_from_primitive! {
|
enum_from_primitive! {
|
||||||
|
|
|
@ -676,15 +676,14 @@ fn same_amount_outputs_copy_range_proof() {
|
||||||
|
|
||||||
// now we reconstruct the transaction, swapping the rangeproofs so they
|
// now we reconstruct the transaction, swapping the rangeproofs so they
|
||||||
// have the wrong privkey
|
// have the wrong privkey
|
||||||
let ins = tx.inputs();
|
let ins: Vec<_> = tx.inputs().into();
|
||||||
let mut outs = tx.outputs().to_vec();
|
let mut outs = tx.outputs().to_vec();
|
||||||
let kernels = tx.kernels();
|
|
||||||
outs[0].proof = outs[1].proof;
|
outs[0].proof = outs[1].proof;
|
||||||
|
|
||||||
let key_id = keychain::ExtKeychain::derive_key_id(1, 4, 0, 0, 0);
|
let key_id = keychain::ExtKeychain::derive_key_id(1, 4, 0, 0, 0);
|
||||||
let prev = BlockHeader::default();
|
let prev = BlockHeader::default();
|
||||||
let b = new_block(
|
let b = new_block(
|
||||||
&[Transaction::new(ins, &outs, kernels)],
|
&[Transaction::new(&ins, &outs, tx.kernels())],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
&prev,
|
&prev,
|
||||||
|
@ -729,16 +728,15 @@ fn wrong_amount_range_proof() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// we take the range proofs from tx2 into tx1 and rebuild the transaction
|
// we take the range proofs from tx2 into tx1 and rebuild the transaction
|
||||||
let ins = tx1.inputs();
|
let ins: Vec<_> = tx1.inputs().into();
|
||||||
let mut outs = tx1.outputs().to_vec();
|
let mut outs = tx1.outputs().to_vec();
|
||||||
let kernels = tx1.kernels();
|
|
||||||
outs[0].proof = tx2.outputs()[0].proof;
|
outs[0].proof = tx2.outputs()[0].proof;
|
||||||
outs[1].proof = tx2.outputs()[1].proof;
|
outs[1].proof = tx2.outputs()[1].proof;
|
||||||
|
|
||||||
let key_id = keychain::ExtKeychain::derive_key_id(1, 4, 0, 0, 0);
|
let key_id = keychain::ExtKeychain::derive_key_id(1, 4, 0, 0, 0);
|
||||||
let prev = BlockHeader::default();
|
let prev = BlockHeader::default();
|
||||||
let b = new_block(
|
let b = new_block(
|
||||||
&[Transaction::new(ins, &outs, kernels)],
|
&[Transaction::new(&ins, &outs, tx1.kernels())],
|
||||||
&keychain,
|
&keychain,
|
||||||
&builder,
|
&builder,
|
||||||
&prev,
|
&prev,
|
||||||
|
|
|
@ -557,9 +557,7 @@ fn reward_empty_block() {
|
||||||
|
|
||||||
let b = new_block(&[], &keychain, &builder, &previous_header, &key_id);
|
let b = new_block(&[], &keychain, &builder, &previous_header, &key_id);
|
||||||
|
|
||||||
b.cut_through()
|
b.validate(&BlindingFactor::zero(), verifier_cache())
|
||||||
.unwrap()
|
|
||||||
.validate(&BlindingFactor::zero(), verifier_cache())
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,11 +576,7 @@ fn reward_with_tx_block() {
|
||||||
let previous_header = BlockHeader::default();
|
let previous_header = BlockHeader::default();
|
||||||
|
|
||||||
let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id);
|
let block = new_block(&[tx1], &keychain, &builder, &previous_header, &key_id);
|
||||||
block
|
block.validate(&BlindingFactor::zero(), vc.clone()).unwrap();
|
||||||
.cut_through()
|
|
||||||
.unwrap()
|
|
||||||
.validate(&BlindingFactor::zero(), vc.clone())
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -354,7 +354,8 @@ where
|
||||||
let mut insert_pos = None;
|
let mut insert_pos = None;
|
||||||
let mut is_rejected = false;
|
let mut is_rejected = false;
|
||||||
|
|
||||||
for input in entry.tx.inputs() {
|
let tx_inputs: Vec<_> = entry.tx.inputs().into();
|
||||||
|
for input in tx_inputs {
|
||||||
if rejected.contains(&input.commitment()) {
|
if rejected.contains(&input.commitment()) {
|
||||||
// Depends on a rejected tx, so reject this one.
|
// Depends on a rejected tx, so reject this one.
|
||||||
is_rejected = true;
|
is_rejected = true;
|
||||||
|
@ -464,9 +465,11 @@ where
|
||||||
// Reject any txs where we see a matching tx kernel in the block.
|
// Reject any txs where we see a matching tx kernel in the block.
|
||||||
// Also reject any txs where we see a conflicting tx,
|
// Also reject any txs where we see a conflicting tx,
|
||||||
// where an input is spent in a different tx.
|
// where an input is spent in a different tx.
|
||||||
|
let block_inputs: Vec<_> = block.inputs().into();
|
||||||
self.entries.retain(|x| {
|
self.entries.retain(|x| {
|
||||||
|
let tx_inputs: Vec<_> = x.tx.inputs().into();
|
||||||
!x.tx.kernels().iter().any(|y| block.kernels().contains(y))
|
!x.tx.kernels().iter().any(|y| block.kernels().contains(y))
|
||||||
&& !x.tx.inputs().iter().any(|y| block.inputs().contains(y))
|
&& !tx_inputs.iter().any(|y| block_inputs.contains(y))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue