1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Copyright 2019 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.

//! The primary module containing the implementations of the transaction pool
//! and its top-level members.

use chrono::prelude::{DateTime, Utc};

use self::core::core::block;
use self::core::core::committed;
use self::core::core::hash::Hash;
use self::core::core::transaction::{self, Transaction};
use self::core::core::{BlockHeader, BlockSums};
use self::core::{consensus, global};
use failure::Fail;
use grin_core as core;
use grin_keychain as keychain;

/// Dandelion "epoch" length.
const DANDELION_EPOCH_SECS: u16 = 600;

/// Dandelion embargo timer.
const DANDELION_EMBARGO_SECS: u16 = 180;

/// Dandelion aggregation timer.
const DANDELION_AGGREGATION_SECS: u16 = 30;

/// Dandelion stem probability (stem 90% of the time, fluff 10%).
const DANDELION_STEM_PROBABILITY: u8 = 90;

/// Always stem our (pushed via api) txs?
/// Defaults to true to match the Dandelion++ paper.
/// But can be overridden to allow a node to fluff our txs if desired.
/// If set to false we will stem/fluff our txs as per current epoch.
const DANDELION_ALWAYS_STEM_OUR_TXS: bool = true;

/// Configuration for "Dandelion".
/// Note: shared between p2p and pool.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DandelionConfig {
	/// Length of each "epoch".
	#[serde(default = "default_dandelion_epoch_secs")]
	pub epoch_secs: u16,
	/// Dandelion embargo timer. Fluff and broadcast individual txs if not seen
	/// on network before embargo expires.
	#[serde(default = "default_dandelion_embargo_secs")]
	pub embargo_secs: u16,
	/// Dandelion aggregation timer.
	#[serde(default = "default_dandelion_aggregation_secs")]
	pub aggregation_secs: u16,
	/// Dandelion stem probability (stem 90% of the time, fluff 10% etc.)
	#[serde(default = "default_dandelion_stem_probability")]
	pub stem_probability: u8,
	/// Default to always stem our txs as described in Dandelion++ paper.
	#[serde(default = "default_dandelion_always_stem_our_txs")]
	pub always_stem_our_txs: bool,
}

impl Default for DandelionConfig {
	fn default() -> DandelionConfig {
		DandelionConfig {
			epoch_secs: default_dandelion_epoch_secs(),
			embargo_secs: default_dandelion_embargo_secs(),
			aggregation_secs: default_dandelion_aggregation_secs(),
			stem_probability: default_dandelion_stem_probability(),
			always_stem_our_txs: default_dandelion_always_stem_our_txs(),
		}
	}
}

fn default_dandelion_epoch_secs() -> u16 {
	DANDELION_EPOCH_SECS
}

fn default_dandelion_embargo_secs() -> u16 {
	DANDELION_EMBARGO_SECS
}

fn default_dandelion_aggregation_secs() -> u16 {
	DANDELION_AGGREGATION_SECS
}

fn default_dandelion_stem_probability() -> u8 {
	DANDELION_STEM_PROBABILITY
}

fn default_dandelion_always_stem_our_txs() -> bool {
	DANDELION_ALWAYS_STEM_OUR_TXS
}

/// Transaction pool configuration
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
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
	/// multiplied by the base fee to compare to the actual transaction fee.
	#[serde(default = "default_accept_fee_base")]
	pub accept_fee_base: u64,

	/// Maximum capacity of the pool in number of transactions
	#[serde(default = "default_max_pool_size")]
	pub max_pool_size: usize,

	/// Maximum capacity of the pool in number of transactions
	#[serde(default = "default_max_stempool_size")]
	pub max_stempool_size: usize,

	/// Maximum total weight of transactions that can get selected to build a
	/// block from. Allows miners to restrict the maximum weight of their
	/// blocks.
	#[serde(default = "default_mineable_max_weight")]
	pub mineable_max_weight: usize,
}

impl Default for PoolConfig {
	fn default() -> PoolConfig {
		PoolConfig {
			accept_fee_base: default_accept_fee_base(),
			max_pool_size: default_max_pool_size(),
			max_stempool_size: default_max_stempool_size(),
			mineable_max_weight: default_mineable_max_weight(),
		}
	}
}

fn default_accept_fee_base() -> u64 {
	consensus::MILLI_GRIN
}
fn default_max_pool_size() -> usize {
	50_000
}
fn default_max_stempool_size() -> usize {
	50_000
}
fn default_mineable_max_weight() -> usize {
	global::max_block_weight()
}

/// Represents a single entry in the pool.
/// A single (possibly aggregated) transaction.
#[derive(Clone, Debug)]
pub struct PoolEntry {
	/// Info on where this tx originated from.
	pub src: TxSource,
	/// Timestamp of when this tx was originally added to the pool.
	pub tx_at: DateTime<Utc>,
	/// The transaction itself.
	pub tx: Transaction,
}

/// Used to make decisions based on transaction acceptance priority from
/// various sources. For example, a node may want to bypass pool size
/// restrictions when accepting a transaction from a local wallet.
///
/// 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, PartialEq)]
pub enum TxSource {
	PushApi,
	Broadcast,
	Fluff,
	EmbargoExpired,
	Deaggregate,
}

impl TxSource {
	/// Convenience fn for checking if this tx was sourced via the push api.
	pub fn is_pushed(&self) -> bool {
		match self {
			TxSource::PushApi => true,
			_ => false,
		}
	}
}

/// Possible errors when interacting with the transaction pool.
#[derive(Debug, Fail, PartialEq)]
pub enum PoolError {
	/// An invalid pool entry caused by underlying tx validation error
	#[fail(display = "Invalid Tx {}", _0)]
	InvalidTx(transaction::Error),
	/// An invalid pool entry caused by underlying block validation error
	#[fail(display = "Invalid Block {}", _0)]
	InvalidBlock(block::Error),
	/// Underlying keychain error.
	#[fail(display = "Keychain error {}", _0)]
	Keychain(keychain::Error),
	/// Underlying "committed" error.
	#[fail(display = "Committed error {}", _0)]
	Committed(committed::Error),
	/// Attempt to add a transaction to the pool with lock_height
	/// greater than height of current block
	#[fail(display = "Immature transaction")]
	ImmatureTransaction,
	/// Attempt to spend a coinbase output before it has sufficiently matured.
	#[fail(display = "Immature coinbase")]
	ImmatureCoinbase,
	/// Problem propagating a stem tx to the next Dandelion relay node.
	#[fail(display = "Dandelion error")]
	DandelionError,
	/// Transaction pool is over capacity, can't accept more transactions
	#[fail(display = "Over capacity")]
	OverCapacity,
	/// Transaction fee is too low given its weight
	#[fail(display = "Low fee transaction {}", _0)]
	LowFeeTransaction(u64),
	/// Attempt to add a duplicate output to the pool.
	#[fail(display = "Duplicate commitment")]
	DuplicateCommitment,
	/// Attempt to add a duplicate tx to the pool.
	#[fail(display = "Duplicate tx")]
	DuplicateTx,
	/// Other kinds of error (not yet pulled out into meaningful errors).
	#[fail(display = "General pool error {}", _0)]
	Other(String),
}

impl From<transaction::Error> for PoolError {
	fn from(e: transaction::Error) -> PoolError {
		PoolError::InvalidTx(e)
	}
}

impl From<block::Error> for PoolError {
	fn from(e: block::Error) -> PoolError {
		PoolError::InvalidBlock(e)
	}
}

impl From<keychain::Error> for PoolError {
	fn from(e: keychain::Error) -> PoolError {
		PoolError::Keychain(e)
	}
}

impl From<committed::Error> for PoolError {
	fn from(e: committed::Error) -> PoolError {
		PoolError::Committed(e)
	}
}

/// Interface that the pool requires from a blockchain implementation.
pub trait BlockChain: Sync + Send {
	/// Verify any coinbase outputs being spent
	/// have matured sufficiently.
	fn verify_coinbase_maturity(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;

	/// Verify any coinbase outputs being spent
	/// have matured sufficiently.
	fn verify_tx_lock_height(&self, tx: &transaction::Transaction) -> Result<(), PoolError>;

	fn validate_tx(&self, tx: &Transaction) -> Result<(), PoolError>;

	fn chain_head(&self) -> Result<BlockHeader, PoolError>;

	fn get_block_header(&self, hash: &Hash) -> Result<BlockHeader, PoolError>;
	fn get_block_sums(&self, hash: &Hash) -> Result<BlockSums, PoolError>;
}

/// Bridge between the transaction pool and the rest of the system. Handles
/// downstream processing of valid transactions by the rest of the system, most
/// importantly the broadcasting of transactions to our peers.
pub trait PoolAdapter: Send + Sync {
	/// The transaction pool has accepted this transaction as valid.
	fn tx_accepted(&self, entry: &PoolEntry);

	/// The stem transaction pool has accepted this transactions as valid.
	fn stem_tx_accepted(&self, entry: &PoolEntry) -> Result<(), PoolError>;
}

/// Dummy adapter used as a placeholder for real implementations
#[allow(dead_code)]
pub struct NoopAdapter {}

impl PoolAdapter for NoopAdapter {
	fn tx_accepted(&self, _entry: &PoolEntry) {}
	fn stem_tx_accepted(&self, _entry: &PoolEntry) -> Result<(), PoolError> {
		Ok(())
	}
}