2018-05-30 23:57:13 +03:00
|
|
|
// 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.
|
|
|
|
|
2018-10-24 19:57:31 +03:00
|
|
|
use std::collections::VecDeque;
|
2018-10-20 03:13:07 +03:00
|
|
|
use std::sync::Arc;
|
|
|
|
use util::RwLock;
|
2018-08-30 17:44:34 +03:00
|
|
|
|
2018-08-16 00:14:48 +03:00
|
|
|
use chrono::prelude::Utc;
|
2018-05-30 23:57:13 +03:00
|
|
|
|
2018-09-18 17:25:26 +03:00
|
|
|
use core::core::hash::{Hash, Hashed};
|
|
|
|
use core::core::id::ShortId;
|
2018-08-30 17:44:34 +03:00
|
|
|
use core::core::verifier_cache::VerifierCache;
|
2018-09-24 11:24:10 +03:00
|
|
|
use core::core::{transaction, Block, BlockHeader, Transaction};
|
2018-05-30 23:57:13 +03:00
|
|
|
use pool::Pool;
|
2018-06-14 15:16:14 +03:00
|
|
|
use types::{BlockChain, PoolAdapter, PoolConfig, PoolEntry, PoolEntryState, PoolError, TxSource};
|
2018-05-30 23:57:13 +03:00
|
|
|
|
2018-10-24 19:57:31 +03:00
|
|
|
// Cache this many txs to handle a potential fork and re-org.
|
|
|
|
const REORG_CACHE_SIZE: usize = 100;
|
|
|
|
|
2018-05-30 23:57:13 +03:00
|
|
|
/// Transaction pool implementation.
|
2018-08-28 00:22:48 +03:00
|
|
|
pub struct TransactionPool {
|
2018-05-30 23:57:13 +03:00
|
|
|
/// Pool Config
|
|
|
|
pub config: PoolConfig,
|
|
|
|
/// Our transaction pool.
|
2018-08-28 00:22:48 +03:00
|
|
|
pub txpool: Pool,
|
2018-05-30 23:57:13 +03:00
|
|
|
/// Our Dandelion "stempool".
|
2018-08-28 00:22:48 +03:00
|
|
|
pub stempool: Pool,
|
2018-10-24 19:57:31 +03:00
|
|
|
/// Cache of previous txs in case of a re-org.
|
|
|
|
pub reorg_cache: Arc<RwLock<VecDeque<PoolEntry>>>,
|
2018-05-30 23:57:13 +03:00
|
|
|
/// The blockchain
|
2018-08-28 00:22:48 +03:00
|
|
|
pub blockchain: Arc<BlockChain>,
|
2018-08-30 17:44:34 +03:00
|
|
|
pub verifier_cache: Arc<RwLock<VerifierCache>>,
|
2018-05-30 23:57:13 +03:00
|
|
|
/// The pool adapter
|
|
|
|
pub adapter: Arc<PoolAdapter>,
|
|
|
|
}
|
|
|
|
|
2018-08-28 00:22:48 +03:00
|
|
|
impl TransactionPool {
|
2018-05-30 23:57:13 +03:00
|
|
|
/// Create a new transaction pool
|
2018-08-28 00:22:48 +03:00
|
|
|
pub fn new(
|
|
|
|
config: PoolConfig,
|
|
|
|
chain: Arc<BlockChain>,
|
2018-08-30 17:44:34 +03:00
|
|
|
verifier_cache: Arc<RwLock<VerifierCache>>,
|
2018-08-28 00:22:48 +03:00
|
|
|
adapter: Arc<PoolAdapter>,
|
|
|
|
) -> TransactionPool {
|
2018-05-30 23:57:13 +03:00
|
|
|
TransactionPool {
|
2018-08-30 17:44:34 +03:00
|
|
|
config,
|
|
|
|
txpool: Pool::new(chain.clone(), verifier_cache.clone(), format!("txpool")),
|
|
|
|
stempool: Pool::new(chain.clone(), verifier_cache.clone(), format!("stempool")),
|
2018-10-24 19:57:31 +03:00
|
|
|
reorg_cache: Arc::new(RwLock::new(VecDeque::new())),
|
2018-05-30 23:57:13 +03:00
|
|
|
blockchain: chain,
|
2018-08-30 17:44:34 +03:00
|
|
|
verifier_cache,
|
|
|
|
adapter,
|
2018-05-30 23:57:13 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 18:53:57 +03:00
|
|
|
pub fn chain_head(&self) -> Result<BlockHeader, PoolError> {
|
|
|
|
self.blockchain.chain_head()
|
|
|
|
}
|
|
|
|
|
2018-09-24 11:24:10 +03:00
|
|
|
fn add_to_stempool(&mut self, entry: PoolEntry, header: &BlockHeader) -> Result<(), PoolError> {
|
2018-05-30 23:57:13 +03:00
|
|
|
// Add tx to stempool (passing in all txs from txpool to validate against).
|
|
|
|
self.stempool
|
2018-09-24 11:24:10 +03:00
|
|
|
.add_to_pool(entry.clone(), self.txpool.all_transactions(), header)?;
|
2018-05-30 23:57:13 +03:00
|
|
|
|
|
|
|
// Note: we do not notify the adapter here,
|
|
|
|
// we let the dandelion monitor handle this.
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-10-24 19:57:31 +03:00
|
|
|
fn add_to_reorg_cache(&mut self, entry: PoolEntry) -> Result<(), PoolError> {
|
|
|
|
let mut cache = self.reorg_cache.write();
|
|
|
|
cache.push_back(entry);
|
|
|
|
if cache.len() > REORG_CACHE_SIZE {
|
|
|
|
cache.pop_front();
|
|
|
|
}
|
|
|
|
debug!("added tx to reorg_cache: size now {}", cache.len());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-09-24 11:24:10 +03:00
|
|
|
fn add_to_txpool(
|
|
|
|
&mut self,
|
|
|
|
mut entry: PoolEntry,
|
|
|
|
header: &BlockHeader,
|
|
|
|
) -> Result<(), PoolError> {
|
2018-05-30 23:57:13 +03:00
|
|
|
// First deaggregate the tx based on current txpool txs.
|
2018-08-16 00:14:48 +03:00
|
|
|
if entry.tx.kernels().len() > 1 {
|
|
|
|
let txs = self
|
|
|
|
.txpool
|
|
|
|
.find_matching_transactions(entry.tx.kernels().clone());
|
2018-05-30 23:57:13 +03:00
|
|
|
if !txs.is_empty() {
|
2018-09-24 11:24:10 +03:00
|
|
|
let tx = transaction::deaggregate(entry.tx, txs)?;
|
|
|
|
tx.validate(self.verifier_cache.clone())?;
|
|
|
|
entry.tx = tx;
|
2018-05-30 23:57:13 +03:00
|
|
|
entry.src.debug_name = "deagg".to_string();
|
|
|
|
}
|
|
|
|
}
|
2018-09-24 11:24:10 +03:00
|
|
|
self.txpool.add_to_pool(entry.clone(), vec![], header)?;
|
2018-05-30 23:57:13 +03:00
|
|
|
|
|
|
|
// 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.
|
2018-10-24 19:57:31 +03:00
|
|
|
{
|
|
|
|
let txpool_tx = self.txpool.aggregate_transaction()?;
|
|
|
|
self.stempool.reconcile(txpool_tx, header)?;
|
|
|
|
}
|
2018-05-30 23:57:13 +03:00
|
|
|
|
|
|
|
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,
|
2018-09-24 11:24:10 +03:00
|
|
|
header: &BlockHeader,
|
2018-05-30 23:57:13 +03:00
|
|
|
) -> Result<(), PoolError> {
|
2018-09-03 14:35:37 +03:00
|
|
|
// Quick check to deal with common case of seeing the *same* tx
|
|
|
|
// broadcast from multiple peers simultaneously.
|
2018-09-18 17:25:26 +03:00
|
|
|
if !stem && self.txpool.contains_tx(tx.hash()) {
|
2018-09-03 14:35:37 +03:00
|
|
|
return Err(PoolError::DuplicateTx);
|
|
|
|
}
|
|
|
|
|
2018-05-30 23:57:13 +03:00
|
|
|
// Do we have the capacity to accept this transaction?
|
|
|
|
self.is_acceptable(&tx)?;
|
|
|
|
|
|
|
|
// Make sure the transaction is valid before anything else.
|
2018-08-30 17:44:34 +03:00
|
|
|
tx.validate(self.verifier_cache.clone())
|
|
|
|
.map_err(|e| PoolError::InvalidTx(e))?;
|
2018-05-30 23:57:13 +03:00
|
|
|
|
|
|
|
// 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,
|
2018-07-30 11:33:28 +03:00
|
|
|
tx_at: Utc::now(),
|
2018-05-30 23:57:13 +03:00
|
|
|
tx: tx.clone(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if stem {
|
2018-10-24 19:57:31 +03:00
|
|
|
// TODO - what happens to txs in the stempool in a re-org scenario?
|
2018-09-24 11:24:10 +03:00
|
|
|
self.add_to_stempool(entry, header)?;
|
2018-05-30 23:57:13 +03:00
|
|
|
} else {
|
2018-10-24 19:57:31 +03:00
|
|
|
self.add_to_txpool(entry.clone(), header)?;
|
|
|
|
self.add_to_reorg_cache(entry)?;
|
2018-05-30 23:57:13 +03:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-10-24 19:57:31 +03:00
|
|
|
fn reconcile_reorg_cache(&mut self, header: &BlockHeader) -> Result<(), PoolError> {
|
|
|
|
let entries = self.reorg_cache.read().iter().cloned().collect::<Vec<_>>();
|
|
|
|
debug!("reconcile_reorg_cache: size: {} ...", entries.len());
|
|
|
|
for entry in entries {
|
|
|
|
let _ = &self.add_to_txpool(entry.clone(), header);
|
|
|
|
}
|
|
|
|
debug!("reconcile_reorg_cache: ... done.");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-05-30 23:57:13 +03:00
|
|
|
/// 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)?;
|
2018-09-24 11:24:10 +03:00
|
|
|
self.txpool.reconcile(None, &block.header)?;
|
2018-05-30 23:57:13 +03:00
|
|
|
|
2018-10-24 19:57:31 +03:00
|
|
|
// Take our "reorg_cache" and see if this block means
|
|
|
|
// we need to (re)add old txs due to a fork and re-org.
|
|
|
|
self.reconcile_reorg_cache(&block.header)?;
|
|
|
|
|
|
|
|
// Now reconcile our stempool, accounting for the updated txpool txs.
|
2018-05-30 23:57:13 +03:00
|
|
|
self.stempool.reconcile_block(block)?;
|
2018-10-24 19:57:31 +03:00
|
|
|
{
|
|
|
|
let txpool_tx = self.txpool.aggregate_transaction()?;
|
|
|
|
self.stempool.reconcile(txpool_tx, &block.header)?;
|
|
|
|
}
|
2018-05-30 23:57:13 +03:00
|
|
|
|
|
|
|
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).
|
2018-09-18 17:25:26 +03:00
|
|
|
pub fn retrieve_transactions(
|
|
|
|
&self,
|
|
|
|
hash: Hash,
|
|
|
|
nonce: u64,
|
|
|
|
kern_ids: &Vec<ShortId>,
|
|
|
|
) -> (Vec<Transaction>, Vec<ShortId>) {
|
|
|
|
self.txpool.retrieve_transactions(hash, nonce, kern_ids)
|
2018-05-30 23:57:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2018-09-24 11:24:10 +03:00
|
|
|
pub fn prepare_mineable_transactions(&self) -> Result<Vec<Transaction>, PoolError> {
|
2018-08-20 01:50:43 +03:00
|
|
|
self.txpool.prepare_mineable_transactions()
|
2018-05-30 23:57:13 +03:00
|
|
|
}
|
|
|
|
}
|