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