// Copyright 2020 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, Serialize, Deserialize)]
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, Serialize, Deserialize)]
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(())
	}
}
