Add a min fee to accept transactions in the pool

Configuration for a minum accept fee base for the transaction
pool. The base is multipled by a weight computed from the
transaction number of inputs, outputs and kernels. The transaction
fee is required to always be larger than the weight times the
base.

    min_fee = base * (-1*input_len + 4*output_len + kernel_len)

The weight is set to never be less than one.

Also added a configurable (and fairly naive for now) max pool
capacity in number of transactions.
This commit is contained in:
Ignotus Peverell 2017-10-07 18:24:11 +00:00
parent 3dd1dde00b
commit 1e73e3aefc
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
6 changed files with 70 additions and 4 deletions

View file

@ -80,7 +80,7 @@ impl Server {
let pool_adapter = Arc::new(PoolToChainAdapter::new());
let tx_pool = Arc::new(RwLock::new(
pool::TransactionPool::new(pool_adapter.clone()),
pool::TransactionPool::new(config.pool_config.clone(), pool_adapter.clone()),
));
let chain_adapter = Arc::new(ChainToPoolAndNetAdapter::new(tx_pool.clone()));

View file

@ -17,6 +17,7 @@ use std::convert::From;
use api;
use chain;
use p2p;
use pool;
use store;
use pow;
use core::global::MiningParameterMode;
@ -97,6 +98,8 @@ pub struct ServerConfig {
/// Configuration for the mining daemon
pub mining_config: Option<pow::types::MinerConfig>,
pub pool_config: pool::PoolConfig,
}
impl Default for ServerConfig {
@ -110,6 +113,7 @@ impl Default for ServerConfig {
p2p_config: Some(p2p::P2PConfig::default()),
mining_config: Some(pow::types::MinerConfig::default()),
mining_parameter_mode: Some(MiningParameterMode::Production),
pool_config: pool::PoolConfig::default(),
}
}
}

View file

@ -10,6 +10,8 @@ grin_keychain = { path = "../keychain" }
grin_store = { path = "../store" }
grin_p2p = { path = "../p2p" }
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp" }
serde = "~1.0.8"
serde_derive = "~1.0.8"
time = "^0.1"
rand = "0.3"
log = "0.3"

View file

@ -28,6 +28,9 @@ mod pool;
extern crate time;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate log;
extern crate blake2_rfc as blake2;
extern crate grin_core as core;
@ -35,4 +38,4 @@ extern crate grin_keychain as keychain;
extern crate secp256k1zkp as secp;
pub use pool::TransactionPool;
pub use types::{BlockChain, TxSource, PoolError};
pub use types::{BlockChain, TxSource, PoolError, PoolConfig};

View file

@ -14,7 +14,7 @@
//! Top-level Pool type, methods, and tests
use types::{Pool, BlockChain, Orphans, Parent, PoolError, TxSource, TransactionGraphContainer};
use types::*;
pub use graph;
use core::core::transaction;
@ -32,6 +32,7 @@ use std::collections::HashMap;
/// The transactions HashMap holds ownership of all transactions in the pool,
/// keyed by their transaction hash.
pub struct TransactionPool<T> {
config: PoolConfig,
/// All transactions in the pool
pub transactions: HashMap<hash::Hash, Box<transaction::Transaction>>,
/// The pool itself
@ -49,8 +50,9 @@ where
T: BlockChain,
{
/// Create a new transaction pool
pub fn new(chain: Arc<T>) -> TransactionPool<T> {
pub fn new(config: PoolConfig, chain: Arc<T>) -> TransactionPool<T> {
TransactionPool {
config: config,
transactions: HashMap::new(),
pool: Pool::empty(),
orphans: Orphans::empty(),
@ -127,6 +129,12 @@ where
_: TxSource,
tx: transaction::Transaction,
) -> Result<(), PoolError> {
// Do we have the capacity to accept this transaction?
if let Err(e) = self.is_acceptable(&tx) {
return Err(e);
}
// Making sure the transaction is valid before anything else.
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
tx.validate(&secp).map_err(|_e| PoolError::Invalid)?;
@ -551,6 +559,26 @@ where
.map(|x| self.transactions.get(x).unwrap().clone())
.collect()
}
/// 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::Transaction) -> Result<(), PoolError> {
if self.total_size() > self.config.max_pool_size {
// TODO evict old/large transactions instead
return Err(PoolError::OverCapacity);
}
if self.config.accept_fee_base > 0 {
let mut tx_weight = -1 * (tx.inputs.len() as i32) + (4 * tx.outputs.len() as i32) + 1;
if tx_weight < 1 {
tx_weight = 1;
}
let threshold = (tx_weight as u64) * self.config.accept_fee_base;
if tx.fee < threshold {
return Err(PoolError::LowFeeTransaction(threshold));
}
}
Ok(())
}
}
#[cfg(test)]
@ -1027,6 +1055,10 @@ mod tests {
fn test_setup(dummy_chain: &Arc<DummyChainImpl>) -> TransactionPool<DummyChainImpl> {
TransactionPool {
config: PoolConfig{
accept_fee_base: 0,
max_pool_size: 10_000,
},
transactions: HashMap::new(),
pool: Pool::empty(),
orphans: Orphans::empty(),

View file

@ -28,6 +28,27 @@ use core::core::block;
use core::core::transaction;
use core::core::hash;
/// Tranasction pool configuration
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PoolConfig {
/// Base fee for a transaction to be accepted by the pool. The transaction
/// weight is computed from its number of inputs, outputs and kernels and
/// multipled by the base fee to compare to the actual transaction fee.
pub accept_fee_base: u64,
/// Maximum capacity of the pool in number of transactions
pub max_pool_size: usize,
}
impl Default for PoolConfig {
fn default() -> PoolConfig {
PoolConfig {
accept_fee_base: 10,
max_pool_size: 50_000,
}
}
}
/// Placeholder: the data representing where we heard about a tx from.
///
/// Used to make decisions based on transaction acceptance priority from
@ -105,6 +126,10 @@ pub enum PoolError {
OutputNotFound,
/// TODO - is this the right level of abstraction for pool errors?
OutputSpent,
/// Transaction pool is over capacity, can't accept more transactions
OverCapacity,
/// Transaction fee is too low given its weight
LowFeeTransaction(u64),
}
/// Interface that the pool requires from a blockchain implementation.