Minimal Transaction Pool (#1067)
* verify a tx like we verify a block (experimental) * first minimal_pool test up and running but not testing what we need to * rework tx_pool validation to use txhashset extension * minimal tx pool wired up but rough * works locally (rough statew though) delete "legacy" pool and graph code * rework the new pool into TransactionPool and Pool impls * rework pool to store pool entries with associated timer and source etc. * all_transactions * extra_txs so we can validate stempool against existing txpool * rework reconcile_block * txhashset apply_raw_tx can now rewind to a checkpoint (prev raw tx) * wip - txhashset tx tests * more flexible rewind on MMRs * add tests to cover apply_raw_txs on txhashset extension * add_to_stempool and add_to_txpool * deaggregate multi kernel tx when adding to txpoool * handle freshness in stempool handle propagation of stempool txs via dandelion monitor * patience timer and fluff if we cannot propagate to next relay * aggregate and fluff stempool is we have no relay * refactor coinbase maturity * rewrote basic tx pool tests to use a real txhashset via chain adapter * rework dandelion monitor to reflect recent discussion works locally but needs a cleanup * refactor dandelion_monitor - split out phases * more pool test coverage * remove old test code from pool (still wip) * block_building and block_reconciliation tests * tracked down chain test failure... * fix test_coinbase_maturity * dandelion_monitor now runs... * refactor dandelion config, shared across p2p and pool components * fix pool tests with new config * fix p2p tests * rework tx pool to deal with duplicate commitments (testnet2 limitation) * cleanup and address some PR feedback * add big comment about pre_tx...
This commit is contained in:
@@ -19,3 +19,4 @@ grin_util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
grin_wallet = { path = "../wallet" }
|
||||
grin_chain = { path = "../chain" }
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! This file is (hopefully) temporary.
|
||||
//!
|
||||
//! It contains a trait based on (but not exactly equal to) the trait defined
|
||||
//! for the blockchain Output set, discussed at
|
||||
//! https://github.com/ignopeverell/grin/issues/29, and a dummy implementation
|
||||
//! of said trait.
|
||||
//! Notably, OutputDiff has been left off, and the question of how to handle
|
||||
//! abstract return types has been deferred.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::clone::Clone;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use core::core::{block, hash, transaction};
|
||||
use core::core::{Input, OutputFeatures, OutputIdentifier};
|
||||
use core::global;
|
||||
use core::core::hash::Hashed;
|
||||
use types::{BlockChain, PoolError};
|
||||
use util::secp::pedersen::Commitment;
|
||||
|
||||
/// A DummyOutputSet for mocking up the chain
|
||||
pub struct DummyOutputSet {
|
||||
outputs: HashMap<Commitment, transaction::Output>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DummyOutputSet {
|
||||
/// Empty output set
|
||||
pub fn empty() -> DummyOutputSet {
|
||||
DummyOutputSet {
|
||||
outputs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// roots
|
||||
pub fn root(&self) -> hash::Hash {
|
||||
hash::ZERO_HASH
|
||||
}
|
||||
|
||||
/// apply a block
|
||||
pub fn apply(&self, b: &block::Block) -> DummyOutputSet {
|
||||
let mut new_outputs = self.outputs.clone();
|
||||
|
||||
for input in &b.inputs {
|
||||
new_outputs.remove(&input.commitment());
|
||||
}
|
||||
for output in &b.outputs {
|
||||
new_outputs.insert(output.commitment(), output.clone());
|
||||
}
|
||||
DummyOutputSet {
|
||||
outputs: new_outputs,
|
||||
}
|
||||
}
|
||||
|
||||
/// create with block
|
||||
pub fn with_block(&mut self, b: &block::Block) {
|
||||
for input in &b.inputs {
|
||||
self.outputs.remove(&input.commitment());
|
||||
}
|
||||
for output in &b.outputs {
|
||||
self.outputs.insert(output.commitment(), output.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// rewind
|
||||
pub fn rewind(&self, _: &block::Block) -> DummyOutputSet {
|
||||
DummyOutputSet {
|
||||
outputs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// get an output
|
||||
pub fn get_output(&self, output_ref: &Commitment) -> Option<&transaction::Output> {
|
||||
self.outputs.get(output_ref)
|
||||
}
|
||||
|
||||
fn clone(&self) -> DummyOutputSet {
|
||||
DummyOutputSet {
|
||||
outputs: self.outputs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// only for testing: add an output to the map
|
||||
pub fn with_output(&self, output: transaction::Output) -> DummyOutputSet {
|
||||
let mut new_outputs = self.outputs.clone();
|
||||
new_outputs.insert(output.commitment(), output);
|
||||
DummyOutputSet {
|
||||
outputs: new_outputs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A DummyChain is the mocked chain for playing with what methods we would
|
||||
/// need
|
||||
#[allow(dead_code)]
|
||||
pub struct DummyChainImpl {
|
||||
output: RwLock<DummyOutputSet>,
|
||||
block_headers: RwLock<Vec<block::BlockHeader>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DummyChainImpl {
|
||||
/// new dummy chain
|
||||
pub fn new() -> DummyChainImpl {
|
||||
DummyChainImpl {
|
||||
output: RwLock::new(DummyOutputSet {
|
||||
outputs: HashMap::new(),
|
||||
}),
|
||||
block_headers: RwLock::new(vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockChain for DummyChainImpl {
|
||||
fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<hash::Hash, PoolError> {
|
||||
match self.output.read().unwrap().get_output(&output_ref.commit) {
|
||||
Some(_) => Ok(hash::Hash::default()),
|
||||
None => Err(PoolError::GenericPoolError),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_matured(&self, input: &Input, height: u64) -> Result<(), PoolError> {
|
||||
if !input.features.contains(OutputFeatures::COINBASE_OUTPUT) {
|
||||
return Ok(());
|
||||
}
|
||||
let block_hash = input.block_hash.expect("requires a block hash");
|
||||
let headers = self.block_headers.read().unwrap();
|
||||
if let Some(h) = headers.iter().find(|x| x.hash() == block_hash) {
|
||||
if h.height + global::coinbase_maturity() < height {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(PoolError::InvalidTx(transaction::Error::ImmatureCoinbase))
|
||||
}
|
||||
|
||||
fn head_header(&self) -> Result<block::BlockHeader, PoolError> {
|
||||
let headers = self.block_headers.read().unwrap();
|
||||
if headers.len() > 0 {
|
||||
Ok(headers[0].clone())
|
||||
} else {
|
||||
Err(PoolError::GenericPoolError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DummyChain for DummyChainImpl {
|
||||
fn update_output_set(&mut self, new_output: DummyOutputSet) {
|
||||
self.output = RwLock::new(new_output);
|
||||
}
|
||||
|
||||
fn apply_block(&self, b: &block::Block) {
|
||||
self.output.write().unwrap().with_block(b);
|
||||
self.store_head_header(&b.header)
|
||||
}
|
||||
|
||||
fn store_head_header(&self, block_header: &block::BlockHeader) {
|
||||
let mut headers = self.block_headers.write().unwrap();
|
||||
headers.insert(0, block_header.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy chain trait
|
||||
pub trait DummyChain: BlockChain {
|
||||
/// update output set
|
||||
fn update_output_set(&mut self, new_output: DummyOutputSet);
|
||||
/// apply a block
|
||||
fn apply_block(&self, b: &block::Block);
|
||||
/// store header
|
||||
fn store_head_header(&self, block_header: &block::BlockHeader);
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! Base types for the transaction pool's Directed Acyclic Graphs
|
||||
|
||||
use std::vec::Vec;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use util::secp::pedersen::Commitment;
|
||||
|
||||
use time;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use core::core;
|
||||
use core::core::hash::Hashed;
|
||||
use core::core::OutputIdentifier;
|
||||
|
||||
/// An entry in the transaction pool.
|
||||
/// These are the vertices of both of the graph structures
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PoolEntry {
|
||||
// Core data
|
||||
/// Unique identifier of this pool entry and the corresponding transaction
|
||||
pub transaction_hash: core::hash::Hash,
|
||||
|
||||
// Metadata
|
||||
/// Size estimate
|
||||
pub size_estimate: u64,
|
||||
/// Receive timestamp
|
||||
pub receive_ts: time::Tm,
|
||||
}
|
||||
|
||||
impl PoolEntry {
|
||||
/// Create new transaction pool entry
|
||||
pub fn new(tx: &core::transaction::Transaction) -> PoolEntry {
|
||||
PoolEntry {
|
||||
transaction_hash: transaction_identifier(tx),
|
||||
size_estimate: estimate_transaction_size(tx),
|
||||
receive_ts: time::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO guessing this needs implementing
|
||||
fn estimate_transaction_size(_tx: &core::transaction::Transaction) -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// An edge connecting graph vertices.
|
||||
/// For various use cases, one of either the source or destination may be
|
||||
/// unpopulated
|
||||
#[derive(Clone)]
|
||||
pub struct Edge {
|
||||
// Source and Destination are the vertex id's, the transaction (kernel)
|
||||
// hash.
|
||||
source: Option<core::hash::Hash>,
|
||||
destination: Option<core::hash::Hash>,
|
||||
|
||||
// Output is the output hash which this input/output pairing corresponds
|
||||
// to.
|
||||
output: OutputIdentifier,
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
/// Create new edge
|
||||
pub fn new(
|
||||
source: Option<core::hash::Hash>,
|
||||
destination: Option<core::hash::Hash>,
|
||||
output: OutputIdentifier,
|
||||
) -> Edge {
|
||||
Edge {
|
||||
source: source,
|
||||
destination: destination,
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new edge with a source
|
||||
pub fn with_source(&self, src: Option<core::hash::Hash>) -> Edge {
|
||||
Edge {
|
||||
source: src,
|
||||
destination: self.destination,
|
||||
output: self.output.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new edge with destination
|
||||
pub fn with_destination(&self, dst: Option<core::hash::Hash>) -> Edge {
|
||||
Edge {
|
||||
source: self.source,
|
||||
destination: dst,
|
||||
output: self.output.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The output_identifier of the edge.
|
||||
pub fn output(&self) -> OutputIdentifier {
|
||||
self.output.clone()
|
||||
}
|
||||
|
||||
/// The output commitment of the edge
|
||||
pub fn output_commitment(&self) -> Commitment {
|
||||
self.output.commit
|
||||
}
|
||||
|
||||
/// The destination hash of the edge
|
||||
pub fn destination_hash(&self) -> Option<core::hash::Hash> {
|
||||
self.destination
|
||||
}
|
||||
|
||||
/// The source hash of the edge
|
||||
pub fn source_hash(&self) -> Option<core::hash::Hash> {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Edge {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Edge {{source: {:?}, destination: {:?}, commitment: {:?}}}",
|
||||
self.source, self.destination, self.output
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The generic graph container. Both graphs, the pool and orphans, embed this
|
||||
/// structure and add additional capability on top of it.
|
||||
pub struct DirectedGraph {
|
||||
/// Edges
|
||||
pub edges: HashMap<Commitment, Edge>,
|
||||
/// Vertices
|
||||
pub vertices: Vec<PoolEntry>,
|
||||
/// A small optimization: keeping roots (vertices with in-degree 0) in a
|
||||
/// separate list makes topological sort a bit faster. (This is true for
|
||||
/// Kahn's, not sure about other implementations)
|
||||
pub roots: Vec<PoolEntry>,
|
||||
}
|
||||
|
||||
impl DirectedGraph {
|
||||
/// Create an empty directed graph
|
||||
pub fn empty() -> DirectedGraph {
|
||||
DirectedGraph {
|
||||
edges: HashMap::new(),
|
||||
vertices: Vec::new(),
|
||||
roots: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an edge by its commitment
|
||||
pub fn get_edge_by_commitment(&self, output_commitment: &Commitment) -> Option<&Edge> {
|
||||
self.edges.get(output_commitment)
|
||||
}
|
||||
|
||||
/// Remove an edge by its commitment
|
||||
pub fn remove_edge_by_commitment(&mut self, output_commitment: &Commitment) -> Option<Edge> {
|
||||
self.edges.remove(output_commitment)
|
||||
}
|
||||
|
||||
/// Remove a vertex by its hash
|
||||
pub fn remove_vertex(&mut self, tx_hash: core::hash::Hash) -> Option<PoolEntry> {
|
||||
match self.roots
|
||||
.iter()
|
||||
.position(|x| x.transaction_hash == tx_hash)
|
||||
{
|
||||
Some(i) => Some(self.roots.swap_remove(i)),
|
||||
None => match self.vertices
|
||||
.iter()
|
||||
.position(|x| x.transaction_hash == tx_hash)
|
||||
{
|
||||
Some(i) => Some(self.vertices.swap_remove(i)),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Promote any non-root vertices to roots based on current edges.
|
||||
/// For a given tx, if there are no edges with that tx as destination then
|
||||
/// it is a root.
|
||||
pub fn update_roots(&mut self) {
|
||||
let mut new_vertices: Vec<PoolEntry> = vec![];
|
||||
|
||||
// first find the set of all destinations from the edges in the graph
|
||||
// a root is a vertex that is not a destination of any edge
|
||||
let destinations = self.edges
|
||||
.values()
|
||||
.filter_map(|edge| edge.destination)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
// now iterate over the current non-root vertices
|
||||
// and check if it is now a root based on the set of edge destinations
|
||||
for x in &self.vertices {
|
||||
if destinations.contains(&x.transaction_hash) {
|
||||
new_vertices.push(x.clone());
|
||||
} else {
|
||||
self.roots.push(x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// now update our vertices to reflect the updated list
|
||||
self.vertices = new_vertices;
|
||||
}
|
||||
|
||||
/// Adds a vertex and a set of incoming edges to the graph.
|
||||
///
|
||||
/// The PoolEntry at vertex is added to the graph; depending on the
|
||||
/// number of incoming edges, the vertex is either added to the vertices
|
||||
/// or to the roots.
|
||||
///
|
||||
/// Outgoing edges must not be included in edges; this method is designed
|
||||
/// for adding vertices one at a time and only accepts incoming edges as
|
||||
/// internal edges.
|
||||
pub fn add_entry(&mut self, vertex: PoolEntry, mut edges: Vec<Edge>) {
|
||||
if edges.len() == 0 {
|
||||
self.roots.push(vertex);
|
||||
} else {
|
||||
self.vertices.push(vertex);
|
||||
for edge in edges.drain(..) {
|
||||
self.edges.insert(edge.output_commitment(), edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// add_vertex_only adds a vertex, meant to be complemented by add_edge_only
|
||||
/// in cases where delivering a vector of edges is not feasible or efficient
|
||||
pub fn add_vertex_only(&mut self, vertex: PoolEntry, is_root: bool) {
|
||||
if is_root {
|
||||
self.roots.push(vertex);
|
||||
} else {
|
||||
self.vertices.push(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
/// add_edge_only adds an edge
|
||||
pub fn add_edge_only(&mut self, edge: Edge) {
|
||||
self.edges.insert(edge.output_commitment(), edge);
|
||||
}
|
||||
|
||||
/// Number of vertices (root + internal)
|
||||
pub fn len_vertices(&self) -> usize {
|
||||
self.vertices.len() + self.roots.len()
|
||||
}
|
||||
|
||||
/// Number of root vertices only
|
||||
pub fn len_roots(&self) -> usize {
|
||||
self.roots.len()
|
||||
}
|
||||
|
||||
/// Number of edges
|
||||
pub fn len_edges(&self) -> usize {
|
||||
self.edges.len()
|
||||
}
|
||||
|
||||
/// Get the current list of roots
|
||||
pub fn get_roots(&self) -> Vec<core::hash::Hash> {
|
||||
self.roots.iter().map(|x| x.transaction_hash).collect()
|
||||
}
|
||||
|
||||
/// Get list of all vertices in this graph including the roots
|
||||
pub fn get_vertices(&self) -> Vec<core::hash::Hash> {
|
||||
let mut hashes = self.roots
|
||||
.iter()
|
||||
.map(|x| x.transaction_hash)
|
||||
.collect::<Vec<_>>();
|
||||
let non_root_hashes = self.vertices
|
||||
.iter()
|
||||
.map(|x| x.transaction_hash)
|
||||
.collect::<Vec<_>>();
|
||||
hashes.extend(&non_root_hashes);
|
||||
return hashes;
|
||||
}
|
||||
}
|
||||
|
||||
/// Using transaction merkle_inputs_outputs to calculate a deterministic hash;
|
||||
/// this hashing mechanism has some ambiguity issues especially around range
|
||||
/// proofs and any extra data the kernel may cover, but it is used initially
|
||||
/// for testing purposes.
|
||||
pub fn transaction_identifier(tx: &core::transaction::Transaction) -> core::hash::Hash {
|
||||
// core::transaction::merkle_inputs_outputs(&tx.inputs, &tx.outputs)
|
||||
tx.hash()
|
||||
}
|
||||
+8
-9
@@ -12,19 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The transaction pool, keeping a view of currently-valid transactions that
|
||||
//! The transaction pool, keeping a view of currently valid transactions that
|
||||
//! may be confirmed soon.
|
||||
|
||||
#![deny(non_upper_case_globals)]
|
||||
#![deny(non_camel_case_types)]
|
||||
#![deny(non_snake_case)]
|
||||
#![deny(unused_mut)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod graph;
|
||||
pub mod types;
|
||||
pub mod blockchain;
|
||||
pub mod pool;
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_core as core;
|
||||
@@ -39,5 +33,10 @@ extern crate serde_derive;
|
||||
extern crate slog;
|
||||
extern crate time;
|
||||
|
||||
pub use pool::TransactionPool;
|
||||
pub use types::{BlockChain, PoolAdapter, PoolConfig, PoolError, TxSource};
|
||||
mod pool;
|
||||
pub mod transaction_pool;
|
||||
pub mod types;
|
||||
|
||||
pub use transaction_pool::TransactionPool;
|
||||
pub use types::{BlockChain, DandelionConfig, PoolAdapter, PoolConfig, PoolEntryState, PoolError,
|
||||
TxSource};
|
||||
|
||||
+169
-860
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,178 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! Transaction pool implementation leveraging txhashset for chain state
|
||||
//! validation. It is a valid operation to add a tx to the tx pool if the
|
||||
//! resulting tx pool can be added to the current chain state to produce a
|
||||
//! valid chain state.
|
||||
|
||||
use std::sync::Arc;
|
||||
use time;
|
||||
|
||||
use core::core::transaction;
|
||||
use core::core::{Block, CompactBlock, Transaction};
|
||||
use pool::Pool;
|
||||
use types::*;
|
||||
|
||||
/// Transaction pool implementation.
|
||||
pub struct TransactionPool<T> {
|
||||
/// Pool Config
|
||||
pub config: PoolConfig,
|
||||
|
||||
/// Our transaction pool.
|
||||
pub txpool: Pool<T>,
|
||||
/// Our Dandelion "stempool".
|
||||
pub stempool: Pool<T>,
|
||||
|
||||
/// The blockchain
|
||||
pub blockchain: Arc<T>,
|
||||
/// The pool adapter
|
||||
pub adapter: Arc<PoolAdapter>,
|
||||
}
|
||||
|
||||
impl<T> TransactionPool<T>
|
||||
where
|
||||
T: BlockChain,
|
||||
{
|
||||
/// Create a new transaction pool
|
||||
pub fn new(config: PoolConfig, chain: Arc<T>, adapter: Arc<PoolAdapter>) -> TransactionPool<T> {
|
||||
TransactionPool {
|
||||
config: config,
|
||||
txpool: Pool::new(chain.clone(), format!("txpool")),
|
||||
stempool: Pool::new(chain.clone(), format!("stempool")),
|
||||
blockchain: chain,
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_stempool(&mut self, entry: PoolEntry) -> Result<(), PoolError> {
|
||||
// Add tx to stempool (passing in all txs from txpool to validate against).
|
||||
self.stempool
|
||||
.add_to_pool(entry.clone(), self.txpool.all_transactions())?;
|
||||
|
||||
// Note: we do not notify the adapter here,
|
||||
// we let the dandelion monitor handle this.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_to_txpool(&mut self, mut entry: PoolEntry) -> Result<(), PoolError> {
|
||||
// First deaggregate the tx based on current txpool txs.
|
||||
if entry.tx.kernels.len() > 1 {
|
||||
let txs = self.txpool
|
||||
.find_matching_transactions(entry.tx.kernels.clone());
|
||||
if !txs.is_empty() {
|
||||
entry.tx = transaction::deaggregate(entry.tx, txs)?;
|
||||
entry.src.debug_name = "deagg".to_string();
|
||||
}
|
||||
}
|
||||
self.txpool.add_to_pool(entry.clone(), vec![])?;
|
||||
|
||||
// We now need to reconcile the stempool based on the new state of the txpool.
|
||||
// Some stempool txs may no longer be valid and we need to evict them.
|
||||
let txpool_tx = self.txpool.aggregate_transaction()?;
|
||||
self.stempool.reconcile(txpool_tx)?;
|
||||
|
||||
self.adapter.tx_accepted(&entry.tx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the given tx to the pool, directing it to either the stempool or
|
||||
/// txpool based on stem flag provided.
|
||||
pub fn add_to_pool(
|
||||
&mut self,
|
||||
src: TxSource,
|
||||
tx: Transaction,
|
||||
stem: bool,
|
||||
) -> Result<(), PoolError> {
|
||||
// Do we have the capacity to accept this transaction?
|
||||
self.is_acceptable(&tx)?;
|
||||
|
||||
// Make sure the transaction is valid before anything else.
|
||||
tx.validate().map_err(|e| PoolError::InvalidTx(e))?;
|
||||
|
||||
// Check the tx lock_time is valid based on current chain state.
|
||||
self.blockchain.verify_tx_lock_height(&tx)?;
|
||||
|
||||
// Check coinbase maturity before we go any further.
|
||||
self.blockchain.verify_coinbase_maturity(&tx)?;
|
||||
|
||||
let entry = PoolEntry {
|
||||
state: PoolEntryState::Fresh,
|
||||
src,
|
||||
tx_at: time::now_utc().to_timespec(),
|
||||
tx: tx.clone(),
|
||||
};
|
||||
|
||||
if stem {
|
||||
self.add_to_stempool(entry)?;
|
||||
} else {
|
||||
self.add_to_txpool(entry)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reconcile the transaction pool (both txpool and stempool) against the
|
||||
/// provided block.
|
||||
pub fn reconcile_block(&mut self, block: &Block) -> Result<(), PoolError> {
|
||||
// First reconcile the txpool.
|
||||
self.txpool.reconcile_block(block)?;
|
||||
self.txpool.reconcile(None)?;
|
||||
|
||||
// Then reconcile the stempool, accounting for the txpool txs.
|
||||
let txpool_tx = self.txpool.aggregate_transaction()?;
|
||||
self.stempool.reconcile_block(block)?;
|
||||
self.stempool.reconcile(txpool_tx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve all transactions matching the provided "compact block"
|
||||
/// based on the kernel set.
|
||||
/// Note: we only look in the txpool for this (stempool is under embargo).
|
||||
pub fn retrieve_transactions(&self, cb: &CompactBlock) -> Vec<Transaction> {
|
||||
self.txpool.retrieve_transactions(cb)
|
||||
}
|
||||
|
||||
/// Whether the transaction is acceptable to the pool, given both how
|
||||
/// full the pool is and the transaction weight.
|
||||
fn is_acceptable(&self, tx: &Transaction) -> Result<(), PoolError> {
|
||||
if self.total_size() > self.config.max_pool_size {
|
||||
// TODO evict old/large transactions instead
|
||||
return Err(PoolError::OverCapacity);
|
||||
}
|
||||
|
||||
// for a basic transaction (1 input, 2 outputs) -
|
||||
// (-1 * 1) + (4 * 2) + 1 = 8
|
||||
// 8 * 10 = 80
|
||||
if self.config.accept_fee_base > 0 {
|
||||
let threshold = (tx.tx_weight() as u64) * self.config.accept_fee_base;
|
||||
if tx.fee() < threshold {
|
||||
return Err(PoolError::LowFeeTransaction(threshold));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the total size of the pool.
|
||||
/// Note: we only consider the txpool here as stempool is under embargo.
|
||||
pub fn total_size(&self) -> usize {
|
||||
self.txpool.size()
|
||||
}
|
||||
|
||||
/// Returns a vector of transactions from the txpool so we can build a
|
||||
/// block from them.
|
||||
pub fn prepare_mineable_transactions(&self, num_to_fetch: u32) -> Vec<Transaction> {
|
||||
self.txpool.prepare_mineable_transactions(num_to_fetch)
|
||||
}
|
||||
}
|
||||
+83
-491
@@ -15,18 +15,29 @@
|
||||
//! The primary module containing the implementations of the transaction pool
|
||||
//! and its top-level members.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::Iterator;
|
||||
use std::vec::Vec;
|
||||
use std::{error, fmt};
|
||||
|
||||
use util::secp::pedersen::Commitment;
|
||||
|
||||
pub use graph;
|
||||
use time::Timespec;
|
||||
|
||||
use core::consensus;
|
||||
use core::core::transaction::{Input, OutputIdentifier};
|
||||
use core::core::{block, hash, transaction};
|
||||
use core::core::transaction;
|
||||
use core::core::transaction::Transaction;
|
||||
|
||||
/// Configuration for "Dandelion".
|
||||
/// Note: shared between p2p and pool.
|
||||
/// Look in top-level server config for info on configuring these parameters.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DandelionConfig {
|
||||
/// Choose new Dandelion relay peer every n secs.
|
||||
pub relay_secs: u64,
|
||||
/// Dandelion embargo, fluff and broadcast tx if not seen on network before
|
||||
/// embargo expires.
|
||||
pub embargo_secs: u64,
|
||||
/// Dandelion patience timer, fluff/stem processing runs every n secs.
|
||||
/// Tx aggregation happens on stem txs received within this window.
|
||||
pub patience_secs: u64,
|
||||
/// Dandelion stem probability (stem 90% of the time, fluff 10% etc.)
|
||||
pub stem_probability: usize,
|
||||
}
|
||||
|
||||
/// Transaction pool configuration
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -40,14 +51,6 @@ pub struct PoolConfig {
|
||||
/// Maximum capacity of the pool in number of transactions
|
||||
#[serde = "default_max_pool_size"]
|
||||
pub max_pool_size: usize,
|
||||
|
||||
/// Maximum capacity of the pool in number of transactions
|
||||
#[serde = "default_dandelion_probability"]
|
||||
pub dandelion_probability: usize,
|
||||
|
||||
/// Default embargo for Dandelion transaction
|
||||
#[serde = "default_dandelion_embargo"]
|
||||
pub dandelion_embargo: i64,
|
||||
}
|
||||
|
||||
impl Default for PoolConfig {
|
||||
@@ -55,8 +58,6 @@ impl Default for PoolConfig {
|
||||
PoolConfig {
|
||||
accept_fee_base: default_accept_fee_base(),
|
||||
max_pool_size: default_max_pool_size(),
|
||||
dandelion_probability: default_dandelion_probability(),
|
||||
dandelion_embargo: default_dandelion_embargo(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,11 +68,34 @@ fn default_accept_fee_base() -> u64 {
|
||||
fn default_max_pool_size() -> usize {
|
||||
50_000
|
||||
}
|
||||
fn default_dandelion_probability() -> usize {
|
||||
90
|
||||
|
||||
/// Represents a single entry in the pool.
|
||||
/// A single (possibly aggregated) transaction.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PoolEntry {
|
||||
/// The state of the pool entry.
|
||||
pub state: PoolEntryState,
|
||||
/// Info on where this tx originated from.
|
||||
pub src: TxSource,
|
||||
/// Timestamp of when this tx was originally added to the pool.
|
||||
pub tx_at: Timespec,
|
||||
/// The transaction itself.
|
||||
pub tx: Transaction,
|
||||
}
|
||||
fn default_dandelion_embargo() -> i64 {
|
||||
30
|
||||
|
||||
/// The possible states a pool entry can be in.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PoolEntryState {
|
||||
/// A new entry, not yet processed.
|
||||
Fresh,
|
||||
/// Tx to be included in the next "stem" run.
|
||||
ToStem,
|
||||
/// Tx previously "stemmed" and propagated.
|
||||
Stemmed,
|
||||
/// Tx to be included in the next "fluff" run.
|
||||
ToFluff,
|
||||
/// Tx previously "fluffed" and broadcast.
|
||||
Fluffed,
|
||||
}
|
||||
|
||||
/// Placeholder: the data representing where we heard about a tx from.
|
||||
@@ -82,6 +106,7 @@ fn default_dandelion_embargo() -> i64 {
|
||||
///
|
||||
/// Most likely this will evolve to contain some sort of network identifier,
|
||||
/// once we get a better sense of what transaction building might look like.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TxSource {
|
||||
/// Human-readable name used for logging and errors.
|
||||
pub debug_name: String,
|
||||
@@ -89,93 +114,32 @@ pub struct TxSource {
|
||||
pub identifier: String,
|
||||
}
|
||||
|
||||
/// This enum describes the parent for a given input of a transaction.
|
||||
#[derive(Clone)]
|
||||
pub enum Parent {
|
||||
/// Unknown
|
||||
Unknown,
|
||||
/// Block Transaction
|
||||
BlockTransaction,
|
||||
/// Pool Transaction
|
||||
PoolTransaction {
|
||||
/// Transaction reference
|
||||
tx_ref: hash::Hash,
|
||||
},
|
||||
/// StemPool Transaction
|
||||
StemPoolTransaction {
|
||||
/// Transaction reference
|
||||
tx_ref: hash::Hash,
|
||||
},
|
||||
/// AlreadySpent
|
||||
AlreadySpent {
|
||||
/// Other transaction reference
|
||||
other_tx: hash::Hash,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for Parent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&Parent::Unknown => write!(f, "Parent: Unknown"),
|
||||
&Parent::BlockTransaction => write!(f, "Parent: Block Transaction"),
|
||||
&Parent::PoolTransaction { tx_ref: x } => {
|
||||
write!(f, "Parent: Pool Transaction ({:?})", x)
|
||||
}
|
||||
&Parent::StemPoolTransaction { tx_ref: x } => {
|
||||
write!(f, "Parent: Stempool Transaction ({:?})", x)
|
||||
}
|
||||
&Parent::AlreadySpent { other_tx: x } => write!(f, "Parent: Already Spent By {:?}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO document this enum more accurately
|
||||
/// Enum of errors
|
||||
/// Possible errors when interacting with the transaction pool.
|
||||
#[derive(Debug)]
|
||||
pub enum PoolError {
|
||||
/// An invalid pool entry caused by underlying tx validation error
|
||||
InvalidTx(transaction::Error),
|
||||
/// An entry already in the pool
|
||||
AlreadyInPool,
|
||||
/// An entry already in the stempool
|
||||
AlreadyInStempool,
|
||||
/// A duplicate output
|
||||
DuplicateOutput {
|
||||
/// The other transaction
|
||||
other_tx: Option<hash::Hash>,
|
||||
/// Is in chain?
|
||||
in_chain: bool,
|
||||
/// The output
|
||||
output: Commitment,
|
||||
},
|
||||
/// A double spend
|
||||
DoubleSpend {
|
||||
/// The other transaction
|
||||
other_tx: hash::Hash,
|
||||
/// The spent output
|
||||
spent_output: Commitment,
|
||||
},
|
||||
/// A failed deaggregation error
|
||||
FailedDeaggregation,
|
||||
/// Attempt to add a transaction to the pool with lock_height
|
||||
/// greater than height of current block
|
||||
ImmatureTransaction {
|
||||
/// The lock height of the invalid transaction
|
||||
lock_height: u64,
|
||||
},
|
||||
/// An orphan successfully added to the orphans set
|
||||
OrphanTransaction,
|
||||
/// TODO - wip, just getting imports working, remove this and use more
|
||||
/// specific errors
|
||||
GenericPoolError,
|
||||
/// TODO - is this the right level of abstraction for pool errors?
|
||||
OutputNotFound,
|
||||
/// TODO - is this the right level of abstraction for pool errors?
|
||||
OutputSpent,
|
||||
ImmatureTransaction,
|
||||
/// Attempt to spend a coinbase output before it has sufficiently matured.
|
||||
ImmatureCoinbase,
|
||||
/// Problem propagating a stem tx to the next Dandelion relay node.
|
||||
DandelionError,
|
||||
/// Transaction pool is over capacity, can't accept more transactions
|
||||
OverCapacity,
|
||||
/// Transaction fee is too low given its weight
|
||||
LowFeeTransaction(u64),
|
||||
/// Attempt to add a duplicate output to the pool.
|
||||
DuplicateCommitment,
|
||||
/// Other kinds of error (not yet pulled out into meaningful errors).
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<transaction::Error> for PoolError {
|
||||
fn from(e: transaction::Error) -> PoolError {
|
||||
PoolError::InvalidTx(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for PoolError {
|
||||
@@ -196,21 +160,21 @@ impl fmt::Display for PoolError {
|
||||
|
||||
/// Interface that the pool requires from a blockchain implementation.
|
||||
pub trait BlockChain {
|
||||
/// Get an unspent output by its commitment. Will return an error if the
|
||||
/// output is spent or if it doesn't exist. The blockchain is expected to
|
||||
/// produce a result with its current view of the most worked chain,
|
||||
/// ignoring orphans, etc.
|
||||
/// We do not maintain outputs themselves. The only information we have is
|
||||
/// the hash from the output MMR.
|
||||
fn is_unspent(&self, output_ref: &OutputIdentifier) -> Result<hash::Hash, PoolError>;
|
||||
/// Validate a vec of txs against the current chain state after applying the
|
||||
/// pre_tx to the chain state.
|
||||
fn validate_raw_txs(
|
||||
&self,
|
||||
txs: Vec<transaction::Transaction>,
|
||||
pre_tx: Option<transaction::Transaction>,
|
||||
) -> Result<Vec<transaction::Transaction>, PoolError>;
|
||||
|
||||
/// Check if an output being spent by the input has sufficiently matured.
|
||||
/// This is only applicable for coinbase outputs (1,000 blocks).
|
||||
/// Non-coinbase outputs will always pass this check.
|
||||
fn is_matured(&self, input: &Input, height: u64) -> Result<(), PoolError>;
|
||||
/// Verify any coinbase outputs being spent
|
||||
/// have matured sufficiently.
|
||||
fn verify_coinbase_maturity(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;
|
||||
|
||||
/// Get the block header at the head
|
||||
fn head_header(&self) -> Result<block::BlockHeader, PoolError>;
|
||||
/// Verify any coinbase outputs being spent
|
||||
/// have matured sufficiently.
|
||||
fn verify_tx_lock_height(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;
|
||||
}
|
||||
|
||||
/// Bridge between the transaction pool and the rest of the system. Handles
|
||||
@@ -221,391 +185,19 @@ pub trait PoolAdapter: Send + Sync {
|
||||
/// it to its internal cache.
|
||||
fn tx_accepted(&self, tx: &transaction::Transaction);
|
||||
/// The stem transaction pool has accepted this transactions as valid and
|
||||
/// added it to its internal cache.
|
||||
fn stem_tx_accepted(&self, tx: &transaction::Transaction);
|
||||
/// added it to its internal cache, we have waited for the "patience" timer
|
||||
/// to fire and we now want to propagate the tx to the next Dandelion relay.
|
||||
fn stem_tx_accepted(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;
|
||||
}
|
||||
|
||||
/// Dummy adapter used as a placeholder for real implementations
|
||||
// TODO: do we need this dummy, if it's never used?
|
||||
#[allow(dead_code)]
|
||||
pub struct NoopAdapter {}
|
||||
|
||||
impl PoolAdapter for NoopAdapter {
|
||||
fn tx_accepted(&self, _: &transaction::Transaction) {}
|
||||
fn stem_tx_accepted(&self, _: &transaction::Transaction) {}
|
||||
}
|
||||
|
||||
/// Pool contains the elements of the graph that are connected, in full, to
|
||||
/// the blockchain.
|
||||
/// Reservations of outputs by orphan transactions (not fully connected) are
|
||||
/// not respected.
|
||||
/// Spending references (input -> output) exist in two structures: internal
|
||||
/// graph references are contained in the pool edge sets, while references
|
||||
/// sourced from the blockchain's Output set are contained in the
|
||||
/// blockchain_connections set.
|
||||
/// Spent by references (output-> input) exist in two structures: pool-pool
|
||||
/// connections are in the pool edge set, while unspent (dangling) references
|
||||
/// exist in the available_outputs set.
|
||||
pub struct Pool {
|
||||
graph: graph::DirectedGraph,
|
||||
|
||||
// available_outputs are unspent outputs of the current pool set,
|
||||
// maintained as edges with empty destinations, keyed by the
|
||||
// output's hash.
|
||||
available_outputs: HashMap<Commitment, graph::Edge>,
|
||||
|
||||
// Consumed blockchain output's are kept in a separate map.
|
||||
consumed_blockchain_outputs: HashMap<Commitment, graph::Edge>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
/// Return an empty pool
|
||||
pub fn empty() -> Pool {
|
||||
Pool {
|
||||
graph: graph::DirectedGraph::empty(),
|
||||
available_outputs: HashMap::new(),
|
||||
consumed_blockchain_outputs: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an output, check if a spending reference (input -> output)
|
||||
/// already exists in the pool.
|
||||
/// Returns the transaction (kernel) hash corresponding to the conflicting
|
||||
/// transaction
|
||||
pub fn check_double_spend(&self, o: &transaction::Output) -> Option<hash::Hash> {
|
||||
self.graph
|
||||
.get_edge_by_commitment(&o.commitment())
|
||||
.or(self.consumed_blockchain_outputs.get(&o.commitment()))
|
||||
.map(|x| x.destination_hash().unwrap())
|
||||
}
|
||||
|
||||
/// Length of roots
|
||||
pub fn len_roots(&self) -> usize {
|
||||
self.graph.len_roots()
|
||||
}
|
||||
|
||||
/// Length of vertices
|
||||
pub fn len_vertices(&self) -> usize {
|
||||
self.graph.len_vertices()
|
||||
}
|
||||
|
||||
/// Consumed outputs
|
||||
pub fn get_blockchain_spent(&self, c: &Commitment) -> Option<&graph::Edge> {
|
||||
self.consumed_blockchain_outputs.get(c)
|
||||
}
|
||||
|
||||
/// Add transaction
|
||||
pub fn add_pool_transaction(
|
||||
&mut self,
|
||||
pool_entry: graph::PoolEntry,
|
||||
mut blockchain_refs: Vec<graph::Edge>,
|
||||
pool_refs: Vec<graph::Edge>,
|
||||
mut new_unspents: Vec<graph::Edge>,
|
||||
) {
|
||||
// Removing consumed available_outputs
|
||||
for new_edge in &pool_refs {
|
||||
// All of these should correspond to an existing unspent
|
||||
assert!(
|
||||
self.available_outputs
|
||||
.remove(&new_edge.output_commitment())
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
// Accounting for consumed blockchain outputs
|
||||
for new_blockchain_edge in blockchain_refs.drain(..) {
|
||||
self.consumed_blockchain_outputs
|
||||
.insert(new_blockchain_edge.output_commitment(), new_blockchain_edge);
|
||||
}
|
||||
|
||||
// Adding the transaction to the vertices list along with internal
|
||||
// pool edges
|
||||
self.graph.add_entry(pool_entry, pool_refs);
|
||||
|
||||
// Adding the new unspents to the unspent map
|
||||
for unspent_output in new_unspents.drain(..) {
|
||||
self.available_outputs
|
||||
.insert(unspent_output.output_commitment(), unspent_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// More relax way for stempool transaction in order to accept scenario such as:
|
||||
/// Parent is in mempool, child is allowed in stempool
|
||||
///
|
||||
pub fn add_stempool_transaction(
|
||||
&mut self,
|
||||
pool_entry: graph::PoolEntry,
|
||||
mut blockchain_refs: Vec<graph::Edge>,
|
||||
pool_refs: Vec<graph::Edge>,
|
||||
mut new_unspents: Vec<graph::Edge>,
|
||||
) {
|
||||
// Removing consumed available_outputs
|
||||
for new_edge in &pool_refs {
|
||||
// All of these *can* correspond to an existing unspent
|
||||
self.available_outputs.remove(&new_edge.output_commitment());
|
||||
}
|
||||
|
||||
// Accounting for consumed blockchain outputs
|
||||
for new_blockchain_edge in blockchain_refs.drain(..) {
|
||||
self.consumed_blockchain_outputs
|
||||
.insert(new_blockchain_edge.output_commitment(), new_blockchain_edge);
|
||||
}
|
||||
|
||||
// Adding the transaction to the vertices list along with internal
|
||||
// pool edges
|
||||
self.graph.add_entry(pool_entry, pool_refs);
|
||||
|
||||
// Adding the new unspents to the unspent map
|
||||
for unspent_output in new_unspents.drain(..) {
|
||||
self.available_outputs
|
||||
.insert(unspent_output.output_commitment(), unspent_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update roots
|
||||
pub fn update_roots(&mut self) {
|
||||
self.graph.update_roots()
|
||||
}
|
||||
|
||||
/// Remove transaction
|
||||
pub fn remove_pool_transaction(
|
||||
&mut self,
|
||||
tx: &transaction::Transaction,
|
||||
marked_txs: &HashSet<hash::Hash>,
|
||||
) {
|
||||
self.graph.remove_vertex(graph::transaction_identifier(tx));
|
||||
|
||||
for input in tx.inputs.iter().map(|x| x.commitment()) {
|
||||
match self.graph.remove_edge_by_commitment(&input) {
|
||||
Some(x) => if !marked_txs.contains(&x.source_hash().unwrap()) {
|
||||
self.available_outputs
|
||||
.insert(x.output_commitment(), x.with_destination(None));
|
||||
},
|
||||
None => {
|
||||
self.consumed_blockchain_outputs.remove(&input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for output in tx.outputs.iter().map(|x| x.commitment()) {
|
||||
match self.graph.remove_edge_by_commitment(&output) {
|
||||
Some(x) => if !marked_txs.contains(&x.destination_hash().unwrap()) {
|
||||
self.consumed_blockchain_outputs
|
||||
.insert(x.output_commitment(), x.with_source(None));
|
||||
},
|
||||
None => {
|
||||
self.available_outputs.remove(&output);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Currently a single rule for miner preference -
|
||||
/// return all txs if less than num_to_fetch txs in the entire pool
|
||||
/// otherwise return num_to_fetch of just the roots
|
||||
pub fn get_mineable_transactions(&self, num_to_fetch: u32) -> Vec<hash::Hash> {
|
||||
if self.graph.len_vertices() <= num_to_fetch as usize {
|
||||
self.graph.get_vertices()
|
||||
} else {
|
||||
let mut roots = self.graph.get_roots();
|
||||
roots.truncate(num_to_fetch as usize);
|
||||
roots
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionGraphContainer for Pool {
|
||||
fn get_graph(&self) -> &graph::DirectedGraph {
|
||||
&self.graph
|
||||
}
|
||||
fn get_available_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.available_outputs.get(output)
|
||||
}
|
||||
fn get_external_spent_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.consumed_blockchain_outputs.get(output)
|
||||
}
|
||||
fn get_internal_spent_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.graph.get_edge_by_commitment(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Orphans contains the elements of the transaction graph that have not been
|
||||
/// connected in full to the blockchain.
|
||||
pub struct Orphans {
|
||||
graph: graph::DirectedGraph,
|
||||
|
||||
// available_outputs are unspent outputs of the current orphan set,
|
||||
// maintained as edges with empty destinations.
|
||||
available_outputs: HashMap<Commitment, graph::Edge>,
|
||||
|
||||
// missing_outputs are spending references (inputs) with missing
|
||||
// corresponding outputs, maintained as edges with empty sources.
|
||||
missing_outputs: HashMap<Commitment, graph::Edge>,
|
||||
|
||||
// pool_connections are bidirectional edges which connect to the pool
|
||||
// graph. They should map one-to-one to pool graph available_outputs.
|
||||
// pool_connections should not be viewed authoritatively, they are
|
||||
// merely informational until the transaction is officially connected to
|
||||
// the pool.
|
||||
pool_connections: HashMap<Commitment, graph::Edge>,
|
||||
}
|
||||
|
||||
impl Orphans {
|
||||
/// empty set
|
||||
pub fn empty() -> Orphans {
|
||||
Orphans {
|
||||
graph: graph::DirectedGraph::empty(),
|
||||
available_outputs: HashMap::new(),
|
||||
missing_outputs: HashMap::new(),
|
||||
pool_connections: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for a double spent output, given the hash of the output,
|
||||
/// ONLY in the data maintained by the orphans set. This includes links
|
||||
/// to the pool as well as links internal to orphan transactions.
|
||||
/// Returns the transaction hash corresponding to the conflicting
|
||||
/// transaction.
|
||||
pub fn check_double_spend(&self, o: transaction::Output) -> Option<hash::Hash> {
|
||||
self.graph
|
||||
.get_edge_by_commitment(&o.commitment())
|
||||
.or(self.pool_connections.get(&o.commitment()))
|
||||
.map(|x| x.destination_hash().unwrap())
|
||||
}
|
||||
|
||||
/// unknown output
|
||||
pub fn get_unknown_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.missing_outputs.get(output)
|
||||
}
|
||||
|
||||
/// Add an orphan transaction to the orphans set.
|
||||
///
|
||||
/// This method adds a given transaction (represented by the PoolEntry at
|
||||
/// orphan_entry) to the orphans set.
|
||||
///
|
||||
/// This method has no failure modes. All checks should be passed before
|
||||
/// entry.
|
||||
///
|
||||
/// Expects a HashMap at is_missing describing the indices of orphan_refs
|
||||
/// which correspond to missing (vs orphan-to-orphan) links.
|
||||
pub fn add_orphan_transaction(
|
||||
&mut self,
|
||||
orphan_entry: graph::PoolEntry,
|
||||
mut pool_refs: Vec<graph::Edge>,
|
||||
mut orphan_refs: Vec<graph::Edge>,
|
||||
is_missing: HashMap<usize, ()>,
|
||||
mut new_unspents: Vec<graph::Edge>,
|
||||
) {
|
||||
// Removing consumed available_outputs
|
||||
for (i, new_edge) in orphan_refs.drain(..).enumerate() {
|
||||
if is_missing.contains_key(&i) {
|
||||
self.missing_outputs
|
||||
.insert(new_edge.output_commitment(), new_edge);
|
||||
} else {
|
||||
assert!(
|
||||
self.available_outputs
|
||||
.remove(&new_edge.output_commitment())
|
||||
.is_some()
|
||||
);
|
||||
self.graph.add_edge_only(new_edge);
|
||||
}
|
||||
}
|
||||
|
||||
// Accounting for consumed blockchain and pool outputs
|
||||
for external_edge in pool_refs.drain(..) {
|
||||
self.pool_connections
|
||||
.insert(external_edge.output_commitment(), external_edge);
|
||||
}
|
||||
|
||||
// if missing_refs is the same length as orphan_refs, we have
|
||||
// no orphan-orphan links for this transaction and it is a
|
||||
// root transaction of the orphans set
|
||||
self.graph
|
||||
.add_vertex_only(orphan_entry, is_missing.len() == orphan_refs.len());
|
||||
|
||||
// Adding the new unspents to the unspent map
|
||||
for unspent_output in new_unspents.drain(..) {
|
||||
self.available_outputs
|
||||
.insert(unspent_output.output_commitment(), unspent_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionGraphContainer for Orphans {
|
||||
fn get_graph(&self) -> &graph::DirectedGraph {
|
||||
&self.graph
|
||||
}
|
||||
fn get_available_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.available_outputs.get(output)
|
||||
}
|
||||
fn get_external_spent_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.pool_connections.get(output)
|
||||
}
|
||||
fn get_internal_spent_output(&self, output: &Commitment) -> Option<&graph::Edge> {
|
||||
self.graph.get_edge_by_commitment(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that embed a graph and connect to external state.
|
||||
///
|
||||
/// The types implementing this trait consist of a graph with nodes and edges
|
||||
/// representing transactions and outputs, respectively. Outputs fall into one
|
||||
/// of three categories:
|
||||
/// 1) External spent: An output sourced externally consumed by a transaction
|
||||
/// in this graph,
|
||||
/// 2) Internal spent: An output produced by a transaction in this graph and
|
||||
/// consumed by another transaction in this graph,
|
||||
/// 3) [External] Unspent: An output produced by a transaction in this graph
|
||||
/// that is not yet spent.
|
||||
///
|
||||
/// There is no concept of an external "spent by" reference (output produced by
|
||||
/// a transaction in the graph spent by a transaction in another source), as
|
||||
/// these references are expected to be maintained by descendent graph. Outputs
|
||||
/// follow a heirarchy (Blockchain -> Pool -> Orphans) where each descendent
|
||||
/// exists at a lower priority than their parent. An output consumed by a
|
||||
/// child graph is marked as unspent in the parent graph and an external spent
|
||||
/// in the child. This ensures that no descendent set must modify state in a
|
||||
/// set of higher priority.
|
||||
pub trait TransactionGraphContainer {
|
||||
/// Accessor for graph object
|
||||
fn get_graph(&self) -> &graph::DirectedGraph;
|
||||
/// Accessor for internal spents
|
||||
fn get_internal_spent_output(&self, output: &Commitment) -> Option<&graph::Edge>;
|
||||
/// Accessor for external unspents
|
||||
fn get_available_output(&self, output: &Commitment) -> Option<&graph::Edge>;
|
||||
/// Accessor for external spents
|
||||
fn get_external_spent_output(&self, output: &Commitment) -> Option<&graph::Edge>;
|
||||
|
||||
/// Checks if the available_output set has the output at the given
|
||||
/// commitment
|
||||
fn has_available_output(&self, c: &Commitment) -> bool {
|
||||
self.get_available_output(c).is_some()
|
||||
}
|
||||
|
||||
/// Checks if the pool has anything by this output already, between
|
||||
/// available outputs and internal ones.
|
||||
fn find_output(&self, c: &Commitment) -> Option<hash::Hash> {
|
||||
self.get_available_output(c)
|
||||
.or(self.get_internal_spent_output(c))
|
||||
.map(|x| x.source_hash().unwrap())
|
||||
}
|
||||
|
||||
/// Search for a spent reference internal to the graph
|
||||
fn get_internal_spent(&self, c: &Commitment) -> Option<&graph::Edge> {
|
||||
self.get_internal_spent_output(c)
|
||||
}
|
||||
|
||||
/// number of root transactions
|
||||
fn num_root_transactions(&self) -> usize {
|
||||
self.get_graph().len_roots()
|
||||
}
|
||||
|
||||
/// number of transactions
|
||||
fn num_transactions(&self) -> usize {
|
||||
self.get_graph().len_vertices()
|
||||
}
|
||||
|
||||
/// number of output edges
|
||||
fn num_output_edges(&self) -> usize {
|
||||
self.get_graph().len_edges()
|
||||
fn stem_tx_accepted(&self, _: &transaction::Transaction) -> Result<(), PoolError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
|
||||
pub mod common;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use core::core::{Block, BlockHeader};
|
||||
|
||||
use chain::txhashset;
|
||||
use chain::types::Tip;
|
||||
use chain::ChainStore;
|
||||
use core::core::target::Difficulty;
|
||||
|
||||
use keychain::Keychain;
|
||||
use wallet::libtx;
|
||||
|
||||
use common::*;
|
||||
|
||||
#[test]
|
||||
fn test_transaction_pool_block_building() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
|
||||
let db_root = ".grin_block_building".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
let chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
|
||||
// Initialize the chain/txhashset with an initial block
|
||||
// so we have a non-empty UTXO set.
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = keychain.derive_key_id(height as u32).unwrap();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, height).unwrap();
|
||||
let block = Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
|
||||
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| extension.apply_block(&block)).unwrap();
|
||||
|
||||
let tip = Tip::from_block(&block.header);
|
||||
chain.store.save_block_header(&block.header).unwrap();
|
||||
chain.store.save_head(&tip).unwrap();
|
||||
|
||||
block.header
|
||||
};
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(&Arc::new(chain.clone())));
|
||||
|
||||
// Now create tx to spend that first coinbase (now matured).
|
||||
// Provides us with some useful outputs to test with.
|
||||
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
|
||||
|
||||
// Add this tx to the pool (stem=false, direct to txpool).
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), initial_tx, false)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
}
|
||||
|
||||
let root_tx_1 = test_transaction(&keychain, vec![10, 20], vec![24]);
|
||||
let root_tx_2 = test_transaction(&keychain, vec![30], vec![28]);
|
||||
let root_tx_3 = test_transaction(&keychain, vec![40], vec![38]);
|
||||
|
||||
let child_tx_1 = test_transaction(&keychain, vec![24], vec![22]);
|
||||
let child_tx_2 = test_transaction(&keychain, vec![38], vec![32]);
|
||||
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
|
||||
// Add the three root txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_1, false)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_2, false)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_3, false)
|
||||
.unwrap();
|
||||
|
||||
// Now add the two child txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_1.clone(), false)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_2.clone(), false)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 6);
|
||||
}
|
||||
|
||||
let txs = {
|
||||
let read_pool = pool.read().unwrap();
|
||||
read_pool.prepare_mineable_transactions(4)
|
||||
};
|
||||
assert_eq!(txs.len(), 4);
|
||||
|
||||
let block = {
|
||||
let key_id = keychain.derive_key_id(2).unwrap();
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
|
||||
Block::new(&header, txs, Difficulty::one(), reward)
|
||||
}.unwrap();
|
||||
|
||||
{
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| {
|
||||
extension.apply_block(&block)?;
|
||||
Ok(())
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
// Now reconcile the transaction pool with the new block
|
||||
// and check the resulting contents of the pool are what we expect.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 2);
|
||||
assert_eq!(write_pool.txpool.entries[0].tx, child_tx_1);
|
||||
assert_eq!(write_pool.txpool.entries[1].tx, child_tx_2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
|
||||
pub mod common;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use core::core::{Block, BlockHeader};
|
||||
|
||||
use chain::txhashset;
|
||||
use chain::types::Tip;
|
||||
use chain::ChainStore;
|
||||
use core::core::target::Difficulty;
|
||||
|
||||
use keychain::Keychain;
|
||||
use wallet::libtx;
|
||||
|
||||
use common::*;
|
||||
|
||||
#[test]
|
||||
fn test_transaction_pool_block_reconciliation() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
|
||||
let db_root = ".grin_block_reconcilliation".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
let chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
|
||||
// Initialize the chain/txhashset with an initial block
|
||||
// so we have a non-empty UTXO set.
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = keychain.derive_key_id(height as u32).unwrap();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, height).unwrap();
|
||||
let block = Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
|
||||
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| extension.apply_block(&block)).unwrap();
|
||||
|
||||
let tip = Tip::from_block(&block.header);
|
||||
chain.store.save_block_header(&block.header).unwrap();
|
||||
chain.store.save_head(&tip).unwrap();
|
||||
|
||||
block.header
|
||||
};
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(&Arc::new(chain.clone())));
|
||||
|
||||
// Now create tx to spend that first coinbase (now matured).
|
||||
// Provides us with some useful outputs to test with.
|
||||
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
|
||||
|
||||
let header = {
|
||||
let key_id = keychain.derive_key_id(2).unwrap();
|
||||
let fees = initial_tx.fee();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
|
||||
let block = Block::new(&header, vec![initial_tx], Difficulty::one(), reward).unwrap();
|
||||
|
||||
{
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| {
|
||||
extension.apply_block(&block)?;
|
||||
Ok(())
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
let tip = Tip::from_block(&block.header);
|
||||
chain.store.save_block_header(&block.header).unwrap();
|
||||
chain.store.save_head(&tip).unwrap();
|
||||
|
||||
block.header
|
||||
};
|
||||
|
||||
// Preparation: We will introduce three root pool transactions.
|
||||
// 1. A transaction that should be invalidated because it is exactly
|
||||
// contained in the block.
|
||||
// 2. A transaction that should be invalidated because the input is
|
||||
// consumed in the block, although it is not exactly consumed.
|
||||
// 3. A transaction that should remain after block reconciliation.
|
||||
let block_transaction = test_transaction(&keychain, vec![10], vec![8]);
|
||||
let conflict_transaction = test_transaction(&keychain, vec![20], vec![12, 6]);
|
||||
let valid_transaction = test_transaction(&keychain, vec![30], vec![13, 15]);
|
||||
|
||||
// We will also introduce a few children:
|
||||
// 4. A transaction that descends from transaction 1, that is in
|
||||
// turn exactly contained in the block.
|
||||
let block_child = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// 5. A transaction that descends from transaction 4, that is not
|
||||
// contained in the block at all and should be valid after
|
||||
// reconciliation.
|
||||
let pool_child = test_transaction(&keychain, vec![5], vec![3]);
|
||||
// 6. A transaction that descends from transaction 2 that does not
|
||||
// conflict with anything in the block in any way, but should be
|
||||
// invalidated (orphaned).
|
||||
let conflict_child = test_transaction(&keychain, vec![12], vec![2]);
|
||||
// 7. A transaction that descends from transaction 2 that should be
|
||||
// valid due to its inputs being satisfied by the block.
|
||||
let conflict_valid_child = test_transaction(&keychain, vec![6], vec![4]);
|
||||
// 8. A transaction that descends from transaction 3 that should be
|
||||
// invalidated due to an output conflict.
|
||||
let valid_child_conflict = test_transaction(&keychain, vec![13], vec![9]);
|
||||
// 9. A transaction that descends from transaction 3 that should remain
|
||||
// valid after reconciliation.
|
||||
let valid_child_valid = test_transaction(&keychain, vec![15], vec![11]);
|
||||
// 10. A transaction that descends from both transaction 6 and
|
||||
// transaction 9
|
||||
let mixed_child = test_transaction(&keychain, vec![2, 11], vec![7]);
|
||||
|
||||
let txs_to_add = vec![
|
||||
block_transaction,
|
||||
conflict_transaction,
|
||||
valid_transaction.clone(),
|
||||
block_child,
|
||||
pool_child.clone(),
|
||||
conflict_child,
|
||||
conflict_valid_child.clone(),
|
||||
valid_child_conflict.clone(),
|
||||
valid_child_valid.clone(),
|
||||
mixed_child,
|
||||
];
|
||||
|
||||
// First we add the above transactions to the pool.
|
||||
// All should be accepted.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
for tx in &txs_to_add {
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx.clone(), false)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(write_pool.total_size(), txs_to_add.len());
|
||||
}
|
||||
|
||||
// Now we prepare the block that will cause the above conditions to be met.
|
||||
// First, the transactions we want in the block:
|
||||
// - Copy of 1
|
||||
let block_tx_1 = test_transaction(&keychain, vec![10], vec![8]);
|
||||
// - Conflict w/ 2, satisfies 7
|
||||
let block_tx_2 = test_transaction(&keychain, vec![20], vec![6]);
|
||||
// - Copy of 4
|
||||
let block_tx_3 = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// - Output conflict w/ 8
|
||||
let block_tx_4 = test_transaction(&keychain, vec![40], vec![9, 31]);
|
||||
let block_txs = vec![block_tx_1, block_tx_2, block_tx_3, block_tx_4];
|
||||
|
||||
// Now apply this block.
|
||||
let block = {
|
||||
let key_id = keychain.derive_key_id(3).unwrap();
|
||||
let fees = block_txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, 0).unwrap();
|
||||
let block = Block::new(&header, block_txs, Difficulty::one(), reward).unwrap();
|
||||
|
||||
{
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| {
|
||||
extension.apply_block(&block)?;
|
||||
Ok(())
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
block
|
||||
};
|
||||
|
||||
// And reconcile the pool with this latest block.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 4);
|
||||
assert_eq!(write_pool.txpool.entries[0].tx, valid_transaction);
|
||||
// TODO - this is the "correct" behavior (see below)
|
||||
// assert_eq!(write_pool.txpool.entries[1].tx, pool_child);
|
||||
// assert_eq!(write_pool.txpool.entries[2].tx, conflict_valid_child);
|
||||
// assert_eq!(write_pool.txpool.entries[3].tx, valid_child_valid);
|
||||
|
||||
//
|
||||
// TODO - once the hash() vs hash_with_index(pos - 1) change is made in
|
||||
// txhashset.apply_output() TODO - and we no longer incorrectly allow
|
||||
// duplicate outputs in the MMR TODO - then this test will fail
|
||||
//
|
||||
// TODO - wtf is with these name permutations...
|
||||
//
|
||||
assert_eq!(write_pool.txpool.entries[1].tx, conflict_valid_child);
|
||||
assert_eq!(write_pool.txpool.entries[2].tx, valid_child_conflict);
|
||||
assert_eq!(write_pool.txpool.entries[3].tx, valid_child_valid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
|
||||
pub mod common;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use core::core::Transaction;
|
||||
|
||||
use keychain::Keychain;
|
||||
use pool::TransactionPool;
|
||||
use pool::types::*;
|
||||
|
||||
use common::*;
|
||||
|
||||
pub fn test_setup(
|
||||
chain: &Arc<CoinbaseMaturityErrorChainAdapter>,
|
||||
) -> TransactionPool<CoinbaseMaturityErrorChainAdapter> {
|
||||
TransactionPool::new(
|
||||
PoolConfig {
|
||||
accept_fee_base: 0,
|
||||
max_pool_size: 50,
|
||||
},
|
||||
chain.clone(),
|
||||
Arc::new(NoopAdapter {}),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CoinbaseMaturityErrorChainAdapter {}
|
||||
|
||||
impl CoinbaseMaturityErrorChainAdapter {
|
||||
pub fn new() -> CoinbaseMaturityErrorChainAdapter {
|
||||
CoinbaseMaturityErrorChainAdapter {}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockChain for CoinbaseMaturityErrorChainAdapter {
|
||||
fn validate_raw_txs(
|
||||
&self,
|
||||
_txs: Vec<Transaction>,
|
||||
_pre_tx: Option<Transaction>,
|
||||
) -> Result<Vec<Transaction>, PoolError> {
|
||||
Err(PoolError::Other(
|
||||
"not implemented, not a real chain adapter...".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// Returns an ImmatureCoinbase for every tx we pass in.
|
||||
fn verify_coinbase_maturity(&self, _tx: &Transaction) -> Result<(), PoolError> {
|
||||
Err(PoolError::ImmatureCoinbase)
|
||||
}
|
||||
|
||||
// Mocking this out for these tests.
|
||||
fn verify_tx_lock_height(&self, _tx: &Transaction) -> Result<(), PoolError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Test we correctly verify coinbase maturity when adding txs to the pool.
|
||||
#[test]
|
||||
fn test_coinbase_maturity() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
|
||||
// Mocking this up with an adapter that will raise an error for coinbase
|
||||
// maturity.
|
||||
let chain = CoinbaseMaturityErrorChainAdapter::new();
|
||||
let pool = RwLock::new(test_setup(&Arc::new(chain.clone())));
|
||||
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
let tx = test_transaction(&keychain, vec![50], vec![49]);
|
||||
match write_pool.add_to_pool(test_source(), tx.clone(), true) {
|
||||
Err(PoolError::ImmatureCoinbase) => {}
|
||||
_ => panic!("Expected an immature coinbase error here."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! Common test functions
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
|
||||
use std::fs;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use core::core::{BlockHeader, Transaction};
|
||||
|
||||
use chain::store::ChainKVStore;
|
||||
use chain::txhashset;
|
||||
use chain::txhashset::TxHashSet;
|
||||
use chain::ChainStore;
|
||||
use core::core::hash::Hashed;
|
||||
use core::core::pmmr::MerkleProof;
|
||||
use pool::*;
|
||||
|
||||
use keychain::Keychain;
|
||||
use wallet::libtx;
|
||||
|
||||
use pool::types::*;
|
||||
use pool::TransactionPool;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ChainAdapter {
|
||||
pub txhashset: Arc<RwLock<TxHashSet>>,
|
||||
pub store: Arc<ChainKVStore>,
|
||||
}
|
||||
|
||||
impl ChainAdapter {
|
||||
pub fn init(db_root: String) -> Result<ChainAdapter, String> {
|
||||
let target_dir = format!("target/{}", db_root);
|
||||
let chain_store = ChainKVStore::new(target_dir.clone())
|
||||
.map_err(|e| format!("failed to init chain_store, {}", e))?;
|
||||
let store = Arc::new(chain_store);
|
||||
let txhashset = TxHashSet::open(target_dir.clone(), store.clone())
|
||||
.map_err(|e| format!("failed to init txhashset, {}", e))?;
|
||||
|
||||
Ok(ChainAdapter {
|
||||
txhashset: Arc::new(RwLock::new(txhashset)),
|
||||
store: store.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockChain for ChainAdapter {
|
||||
fn validate_raw_txs(
|
||||
&self,
|
||||
txs: Vec<Transaction>,
|
||||
pre_tx: Option<Transaction>,
|
||||
) -> Result<Vec<Transaction>, PoolError> {
|
||||
let header = self.store.head_header().unwrap();
|
||||
let mut txhashset = self.txhashset.write().unwrap();
|
||||
let res = txhashset::extending_readonly(&mut txhashset, |extension| {
|
||||
let valid_txs = extension.validate_raw_txs(txs, pre_tx, header.height)?;
|
||||
Ok(valid_txs)
|
||||
}).map_err(|e| PoolError::Other(format!("Error: test chain adapter: {:?}", e)))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Mocking this check out for these tests.
|
||||
// We will test the Merkle proof verification logic elsewhere.
|
||||
fn verify_coinbase_maturity(&self, _tx: &Transaction) -> Result<(), PoolError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Mocking this out for these tests.
|
||||
fn verify_tx_lock_height(&self, _tx: &Transaction) -> Result<(), PoolError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_setup(chain: &Arc<ChainAdapter>) -> TransactionPool<ChainAdapter> {
|
||||
TransactionPool::new(
|
||||
PoolConfig {
|
||||
accept_fee_base: 0,
|
||||
max_pool_size: 50,
|
||||
},
|
||||
chain.clone(),
|
||||
Arc::new(NoopAdapter {}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_transaction_spending_coinbase(
|
||||
keychain: &Keychain,
|
||||
header: &BlockHeader,
|
||||
output_values: Vec<u64>,
|
||||
) -> Transaction {
|
||||
let output_sum = output_values.iter().sum::<u64>() as i64;
|
||||
|
||||
let coinbase_reward: u64 = 60_000_000_000;
|
||||
|
||||
let fees: i64 = coinbase_reward as i64 - output_sum;
|
||||
assert!(fees >= 0);
|
||||
|
||||
let mut tx_elements = Vec::new();
|
||||
|
||||
// single input spending a single coinbase (deterministic key_id aka height)
|
||||
{
|
||||
let key_id = keychain.derive_key_id(header.height as u32).unwrap();
|
||||
tx_elements.push(libtx::build::coinbase_input(
|
||||
coinbase_reward,
|
||||
header.hash(),
|
||||
MerkleProof::default(),
|
||||
key_id,
|
||||
));
|
||||
}
|
||||
|
||||
for output_value in output_values {
|
||||
let key_id = keychain.derive_key_id(output_value as u32).unwrap();
|
||||
tx_elements.push(libtx::build::output(output_value, key_id));
|
||||
}
|
||||
|
||||
tx_elements.push(libtx::build::with_fee(fees as u64));
|
||||
|
||||
libtx::build::transaction(tx_elements, &keychain).unwrap()
|
||||
}
|
||||
|
||||
pub fn test_transaction(
|
||||
keychain: &Keychain,
|
||||
input_values: Vec<u64>,
|
||||
output_values: Vec<u64>,
|
||||
) -> Transaction {
|
||||
let input_sum = input_values.iter().sum::<u64>() as i64;
|
||||
let output_sum = output_values.iter().sum::<u64>() as i64;
|
||||
|
||||
let fees: i64 = input_sum - output_sum;
|
||||
assert!(fees >= 0);
|
||||
|
||||
let mut tx_elements = Vec::new();
|
||||
|
||||
for input_value in input_values {
|
||||
let key_id = keychain.derive_key_id(input_value as u32).unwrap();
|
||||
tx_elements.push(libtx::build::input(input_value, key_id));
|
||||
}
|
||||
|
||||
for output_value in output_values {
|
||||
let key_id = keychain.derive_key_id(output_value as u32).unwrap();
|
||||
tx_elements.push(libtx::build::output(output_value, key_id));
|
||||
}
|
||||
tx_elements.push(libtx::build::with_fee(fees as u64));
|
||||
|
||||
libtx::build::transaction(tx_elements, &keychain).unwrap()
|
||||
}
|
||||
|
||||
pub fn test_source() -> TxSource {
|
||||
TxSource {
|
||||
debug_name: format!("test"),
|
||||
identifier: format!("127.0.0.1"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_output_dir(db_root: String) {
|
||||
if let Err(e) = fs::remove_dir_all(format!("target/{}", db_root)) {
|
||||
println!("cleaning output dir failed - {:?}", e)
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! Top-level Graph tests
|
||||
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
|
||||
use core::core::OutputFeatures;
|
||||
use core::core::transaction::ProofMessageElements;
|
||||
use keychain::Keychain;
|
||||
use wallet::libtx::proof;
|
||||
|
||||
#[test]
|
||||
fn test_add_entry() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
let key_id1 = keychain.derive_key_id(1).unwrap();
|
||||
let key_id2 = keychain.derive_key_id(2).unwrap();
|
||||
let key_id3 = keychain.derive_key_id(3).unwrap();
|
||||
|
||||
let output_commit = keychain.commit(70, &key_id1).unwrap();
|
||||
|
||||
let inputs = vec![
|
||||
core::core::transaction::Input::new(
|
||||
OutputFeatures::DEFAULT_OUTPUT,
|
||||
keychain.commit(50, &key_id2).unwrap(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
core::core::transaction::Input::new(
|
||||
OutputFeatures::DEFAULT_OUTPUT,
|
||||
keychain.commit(25, &key_id3).unwrap(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
];
|
||||
|
||||
let msg = ProofMessageElements::new(100, &key_id1);
|
||||
|
||||
let output = core::core::transaction::Output {
|
||||
features: OutputFeatures::DEFAULT_OUTPUT,
|
||||
commit: output_commit,
|
||||
proof: proof::create(
|
||||
&keychain,
|
||||
100,
|
||||
&key_id1,
|
||||
output_commit,
|
||||
None,
|
||||
msg.to_proof_message(),
|
||||
).unwrap(),
|
||||
};
|
||||
|
||||
let kernel = core::core::transaction::TxKernel::empty()
|
||||
.with_fee(5)
|
||||
.with_lock_height(0);
|
||||
|
||||
let test_transaction =
|
||||
core::core::transaction::Transaction::new(inputs, vec![output], vec![kernel]);
|
||||
|
||||
let test_pool_entry = pool::graph::PoolEntry::new(&test_transaction);
|
||||
|
||||
let incoming_edge_1 = pool::graph::Edge::new(
|
||||
Some(random_hash()),
|
||||
Some(core::core::hash::ZERO_HASH),
|
||||
core::core::OutputIdentifier::from_output(&output),
|
||||
);
|
||||
|
||||
let mut test_graph = pool::graph::DirectedGraph::empty();
|
||||
|
||||
test_graph.add_entry(test_pool_entry, vec![incoming_edge_1]);
|
||||
|
||||
assert_eq!(test_graph.vertices.len(), 1);
|
||||
assert_eq!(test_graph.roots.len(), 0);
|
||||
assert_eq!(test_graph.edges.len(), 1);
|
||||
}
|
||||
|
||||
/// For testing/debugging: a random tx hash
|
||||
fn random_hash() -> core::core::hash::Hash {
|
||||
let hash_bytes: [u8; 32] = rand::random();
|
||||
core::core::hash::Hash(hash_bytes)
|
||||
}
|
||||
-1151
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,223 @@
|
||||
// Copyright 2018 The Grin Developers
|
||||
//
|
||||
// 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.
|
||||
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate grin_chain as chain;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_keychain as keychain;
|
||||
extern crate grin_pool as pool;
|
||||
extern crate grin_util as util;
|
||||
extern crate grin_wallet as wallet;
|
||||
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
|
||||
pub mod common;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use core::core::{Block, BlockHeader};
|
||||
|
||||
use chain::txhashset;
|
||||
use chain::types::Tip;
|
||||
use chain::ChainStore;
|
||||
use core::core::target::Difficulty;
|
||||
use core::core::transaction;
|
||||
|
||||
use keychain::Keychain;
|
||||
use wallet::libtx;
|
||||
|
||||
use common::*;
|
||||
|
||||
/// Test we can add some txs to the pool (both stempool and txpool).
|
||||
#[test]
|
||||
fn test_the_transaction_pool() {
|
||||
let keychain = Keychain::from_random_seed().unwrap();
|
||||
|
||||
let db_root = ".grin_transaction_pool".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
let chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
|
||||
// Initialize the chain/txhashset with a few blocks,
|
||||
// so we have a non-empty UTXO set.
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = keychain.derive_key_id(height as u32).unwrap();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, height).unwrap();
|
||||
let block = Block::new(&BlockHeader::default(), vec![], Difficulty::one(), reward).unwrap();
|
||||
|
||||
let mut txhashset = chain.txhashset.write().unwrap();
|
||||
txhashset::extending(&mut txhashset, |extension| extension.apply_block(&block)).unwrap();
|
||||
|
||||
let tip = Tip::from_block(&block.header);
|
||||
chain.store.save_block_header(&block.header).unwrap();
|
||||
chain.store.save_head(&tip).unwrap();
|
||||
|
||||
block.header
|
||||
};
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(&Arc::new(chain.clone())));
|
||||
|
||||
// Now create tx to spend a coinbase, giving us some useful outputs for testing
|
||||
// with.
|
||||
let initial_tx = {
|
||||
test_transaction_spending_coinbase(
|
||||
&keychain,
|
||||
&header,
|
||||
vec![500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400],
|
||||
)
|
||||
};
|
||||
|
||||
// Add this tx to the pool (stem=false, direct to txpool).
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), initial_tx, false)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
}
|
||||
|
||||
// tx1 spends some outputs from the initial test tx.
|
||||
let tx1 = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
||||
// tx2 spends some outputs from both tx1 and the initial test tx.
|
||||
let tx2 = test_transaction(&keychain, vec![499, 700], vec![498]);
|
||||
|
||||
// Take a write lock and add a couple of tx entries to the pool.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
|
||||
// Check we have a single initial tx in the pool.
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
|
||||
// First, add a simple tx to the pool in "stem" mode.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx1.clone(), true)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
assert_eq!(write_pool.stempool.size(), 1);
|
||||
|
||||
// Add another tx spending outputs from the previous tx.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx2.clone(), true)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
assert_eq!(write_pool.stempool.size(), 2);
|
||||
}
|
||||
|
||||
// Test adding the exact same tx multiple times (same kernel signature).
|
||||
// This will fail during tx aggregation due to duplicate outputs and duplicate
|
||||
// kernels.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert!(
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx1.clone(), true)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
// Test adding a duplicate tx with the same input and outputs (not the *same*
|
||||
// tx).
|
||||
{
|
||||
let tx1a = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert!(write_pool.add_to_pool(test_source(), tx1a, true).is_err());
|
||||
}
|
||||
|
||||
// Test adding a tx attempting to spend a non-existent output.
|
||||
{
|
||||
let bad_tx = test_transaction(&keychain, vec![10_001], vec![10_000]);
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert!(write_pool.add_to_pool(test_source(), bad_tx, true).is_err());
|
||||
}
|
||||
|
||||
// Test adding a tx that would result in a duplicate output (conflicts with
|
||||
// output from tx2). For reasons of security all outputs in the UTXO set must
|
||||
// be unique. Otherwise spending one will almost certainly cause the other
|
||||
// to be immediately stolen via a "replay" tx.
|
||||
{
|
||||
let tx = test_transaction(&keychain, vec![900], vec![498]);
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
assert!(write_pool.add_to_pool(test_source(), tx, true).is_err());
|
||||
}
|
||||
|
||||
// Confirm the tx pool correctly identifies an invalid tx (already spent).
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
let tx3 = test_transaction(&keychain, vec![500], vec![497]);
|
||||
assert!(write_pool.add_to_pool(test_source(), tx3, true).is_err());
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
assert_eq!(write_pool.stempool.size(), 2);
|
||||
}
|
||||
|
||||
// Check we can take some entries from the stempool and "fluff" them into the
|
||||
// txpool. This also exercises multi-kernel txs.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
let agg_tx = write_pool
|
||||
.stempool
|
||||
.aggregate_transaction()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(agg_tx.kernels.len(), 2);
|
||||
write_pool
|
||||
.add_to_pool(test_source(), agg_tx, false)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 2);
|
||||
}
|
||||
|
||||
// Now check we can correctly deaggregate a multi-kernel tx based on current
|
||||
// contents of the txpool.
|
||||
// We will do this be adding a new tx to the pool
|
||||
// that is a superset of a tx already in the pool.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
|
||||
let tx4 = test_transaction(&keychain, vec![800], vec![799]);
|
||||
// tx1 and tx2 are already in the txpool (in aggregated form)
|
||||
// tx4 is the "new" part of this aggregated tx that we care about
|
||||
let agg_tx = transaction::aggregate(vec![tx1.clone(), tx2.clone(), tx4]).unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), agg_tx, false)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 3);
|
||||
let entry = write_pool.txpool.entries.last().unwrap();
|
||||
assert_eq!(entry.tx.kernels.len(), 1);
|
||||
assert_eq!(entry.src.debug_name, "deagg");
|
||||
}
|
||||
|
||||
// Check we cannot "double spend" an output spent in a previous block.
|
||||
// We use the initial coinbase output here for convenience.
|
||||
{
|
||||
let mut write_pool = pool.write().unwrap();
|
||||
|
||||
let double_spend_tx =
|
||||
{ test_transaction_spending_coinbase(&keychain, &header, vec![1000]) };
|
||||
|
||||
// check we cannot add a double spend to the stempool
|
||||
assert!(
|
||||
write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), true)
|
||||
.is_err()
|
||||
);
|
||||
|
||||
// check we cannot add a double spend to the txpool
|
||||
assert!(
|
||||
write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), false)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user