// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.

// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// OpenEthereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with OpenEthereum.  If not, see <http://www.gnu.org/licenses/>.

//! General error types for use in ethcore.

// Silence: `use of deprecated item 'std::error::Error::cause': replaced by Error::source, which can support downcasting`
// https://github.com/openethereum/openethereum/issues/10302
#![allow(deprecated)]

use std::{error, fmt, time::SystemTime};

use crypto::publickey::Error as EthkeyError;
use ethereum_types::{Address, Bloom, H256, U256};
use ethtrie::TrieError;
use rlp;
use snappy::InvalidInput;
use snapshot::Error as SnapshotError;
use types::{transaction::Error as TransactionError, BlockNumber};
use unexpected::{Mismatch, OutOfBounds};

use engines::EngineError;

pub use executed::{CallError, ExecutionError};

#[derive(Debug, PartialEq, Clone, Eq)]
/// Errors concerning block processing.
pub enum BlockError {
    /// Block has too many uncles.
    TooManyUncles(OutOfBounds<usize>),
    /// Extra data is of an invalid length.
    ExtraDataOutOfBounds(OutOfBounds<usize>),
    /// Seal is incorrect format.
    InvalidSealArity(Mismatch<usize>),
    /// Block has too much gas used.
    TooMuchGasUsed(OutOfBounds<U256>),
    /// Gas target increased too much from previous block
    GasTargetTooBig(OutOfBounds<U256>),
    /// Gas target decreased too much from previous block
    GasTargetTooSmall(OutOfBounds<U256>),
    /// Uncles hash in header is invalid.
    InvalidUnclesHash(Mismatch<H256>),
    /// An uncle is from a generation too old.
    UncleTooOld(OutOfBounds<BlockNumber>),
    /// An uncle is from the same generation as the block.
    UncleIsBrother(OutOfBounds<BlockNumber>),
    /// An uncle is already in the chain.
    UncleInChain(H256),
    /// An uncle is included twice.
    DuplicateUncle(H256),
    /// An uncle has a parent not in the chain.
    UncleParentNotInChain(H256),
    /// State root header field is invalid.
    InvalidStateRoot(Mismatch<H256>),
    /// Gas used header field is invalid.
    InvalidGasUsed(Mismatch<U256>),
    /// Transactions root header field is invalid.
    InvalidTransactionsRoot(Mismatch<H256>),
    /// Difficulty is out of range; this can be used as an looser error prior to getting a definitive
    /// value for difficulty. This error needs only provide bounds of which it is out.
    DifficultyOutOfBounds(OutOfBounds<U256>),
    /// Difficulty header field is invalid; this is a strong error used after getting a definitive
    /// value for difficulty (which is provided).
    InvalidDifficulty(Mismatch<U256>),
    /// Seal element of type H256 (max_hash for Ethash, but could be something else for
    /// other seal engines) is out of bounds.
    MismatchedH256SealElement(Mismatch<H256>),
    /// Proof-of-work aspect of seal, which we assume is a 256-bit value, is invalid.
    InvalidProofOfWork(OutOfBounds<U256>),
    /// Some low-level aspect of the seal is incorrect.
    InvalidSeal,
    /// Gas limit header field is invalid.
    InvalidGasLimit(OutOfBounds<U256>),
    /// Base fee is incorrect; base fee is different from the expected calculated value.
    IncorrectBaseFee(Mismatch<U256>),
    /// Receipts trie root header field is invalid.
    InvalidReceiptsRoot(Mismatch<H256>),
    /// Timestamp header field is invalid.
    InvalidTimestamp(OutOfBounds<SystemTime>),
    /// Timestamp header field is too far in future.
    TemporarilyInvalid(OutOfBounds<SystemTime>),
    /// Log bloom header field is invalid.
    InvalidLogBloom(Box<Mismatch<Bloom>>),
    /// Number field of header is invalid.
    InvalidNumber(Mismatch<BlockNumber>),
    /// Block number isn't sensible.
    RidiculousNumber(OutOfBounds<BlockNumber>),
    /// Timestamp header overflowed
    TimestampOverflow,
    /// Too many transactions from a particular address.
    TooManyTransactions(Address),
    /// Parent given is unknown.
    UnknownParent(H256),
    /// Uncle parent given is unknown.
    UnknownUncleParent(H256),
    /// No transition to epoch number.
    UnknownEpochTransition(u64),
}

impl fmt::Display for BlockError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::BlockError::*;

        let msg = match *self {
            TooManyUncles(ref oob) => format!("Block has too many uncles. {}", oob),
            ExtraDataOutOfBounds(ref oob) => format!("Extra block data too long. {}", oob),
            InvalidSealArity(ref mis) => format!("Block seal in incorrect format: {}", mis),
            TooMuchGasUsed(ref oob) => format!("Block has too much gas used. {}", oob),
            GasTargetTooBig(ref oob) => format!("Gas target is bigger then expected. {}", oob),
            GasTargetTooSmall(ref oob) => format!("Gas target is smaller then expected. {}", oob),
            InvalidUnclesHash(ref mis) => format!("Block has invalid uncles hash: {}", mis),
            UncleTooOld(ref oob) => format!("Uncle block is too old. {}", oob),
            UncleIsBrother(ref oob) => format!("Uncle from same generation as block. {}", oob),
            UncleInChain(ref hash) => format!("Uncle {} already in chain", hash),
            DuplicateUncle(ref hash) => format!("Uncle {} already in the header", hash),
            UncleParentNotInChain(ref hash) => {
                format!("Uncle {} has a parent not in the chain", hash)
            }
            InvalidStateRoot(ref mis) => format!("Invalid state root in header: {}", mis),
            InvalidGasUsed(ref mis) => format!("Invalid gas used in header: {}", mis),
            InvalidTransactionsRoot(ref mis) => {
                format!("Invalid transactions root in header: {}", mis)
            }
            DifficultyOutOfBounds(ref oob) => format!("Invalid block difficulty: {}", oob),
            InvalidDifficulty(ref mis) => format!("Invalid block difficulty: {}", mis),
            MismatchedH256SealElement(ref mis) => format!("Seal element out of bounds: {}", mis),
            InvalidProofOfWork(ref oob) => format!("Block has invalid PoW: {}", oob),
            InvalidSeal => "Block has invalid seal.".into(),
            InvalidGasLimit(ref oob) => format!("Invalid gas limit: {}", oob),
            IncorrectBaseFee(ref mis) => format!("Incorrect base fee: {}", mis),
            InvalidReceiptsRoot(ref mis) => {
                format!("Invalid receipts trie root in header: {}", mis)
            }
            InvalidTimestamp(ref oob) => {
                let oob = oob.map(|st| st.elapsed().unwrap_or_default().as_secs());
                format!("Invalid timestamp in header: {}", oob)
            }
            TemporarilyInvalid(ref oob) => {
                let oob = oob.map(|st| st.elapsed().unwrap_or_default().as_secs());
                format!("Future timestamp in header: {}", oob)
            }
            InvalidLogBloom(ref oob) => format!("Invalid log bloom in header: {}", oob),
            InvalidNumber(ref mis) => format!("Invalid number in header: {}", mis),
            RidiculousNumber(ref oob) => format!("Implausible block number. {}", oob),
            UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
            UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
            UnknownEpochTransition(ref num) => {
                format!("Unknown transition to epoch number: {}", num)
            }
            TimestampOverflow => format!("Timestamp overflow"),
            TooManyTransactions(ref address) => format!("Too many transactions from: {}", address),
        };

        f.write_fmt(format_args!("Block error ({})", msg))
    }
}

impl error::Error for BlockError {
    fn description(&self) -> &str {
        "Block error"
    }
}

error_chain! {
    types {
        QueueError, QueueErrorKind, QueueErrorResultExt, QueueErrorResult;
    }

    errors {
        #[doc = "Queue is full"]
        Full(limit: usize) {
            description("Queue is full")
            display("The queue is full ({})", limit)
        }
    }

    foreign_links {
        Channel(::io::IoError) #[doc = "Io channel error"];
    }
}

error_chain! {
    types {
        ImportError, ImportErrorKind, ImportErrorResultExt, ImportErrorResult;
    }

    errors {
        #[doc = "Already in the block chain."]
        AlreadyInChain {
            description("Block already in chain")
            display("Block already in chain")
        }

        #[doc = "Already in the block queue"]
        AlreadyQueued {
            description("block already in the block queue")
            display("block already in the block queue")
        }

        #[doc = "Already marked as bad from a previous import (could mean parent is bad)."]
        KnownBad {
            description("block known to be bad")
            display("block known to be bad")
        }
    }
}

/// Api-level error for transaction import
#[derive(Debug, Clone)]
pub enum TransactionImportError {
    /// Transaction error
    Transaction(TransactionError),
    /// Other error
    Other(String),
}

impl From<Error> for TransactionImportError {
    fn from(e: Error) -> Self {
        match e {
            Error(ErrorKind::Transaction(transaction_error), _) => {
                TransactionImportError::Transaction(transaction_error)
            }
            _ => TransactionImportError::Other(format!("other block import error: {:?}", e)),
        }
    }
}

error_chain! {
    types {
        Error, ErrorKind, ErrorResultExt, EthcoreResult;
    }

    links {
        Import(ImportError, ImportErrorKind) #[doc = "Error concerning block import." ];
        Queue(QueueError, QueueErrorKind) #[doc = "Io channel queue error"];
    }

    foreign_links {
        Io(::io::IoError) #[doc = "Io create error"];
        StdIo(::std::io::Error) #[doc = "Error concerning the Rust standard library's IO subsystem."];
        Trie(TrieError) #[doc = "Error concerning TrieDBs."];
        Execution(ExecutionError) #[doc = "Error concerning EVM code execution."];
        Block(BlockError) #[doc = "Error concerning block processing."];
        Transaction(TransactionError) #[doc = "Error concerning transaction processing."];
        Snappy(InvalidInput) #[doc = "Snappy error."];
        Engine(EngineError) #[doc = "Consensus vote error."];
        Ethkey(EthkeyError) #[doc = "Ethkey error."];
        Decoder(rlp::DecoderError) #[doc = "RLP decoding errors"];
    }

    errors {
        #[doc = "Snapshot error."]
        Snapshot(err: SnapshotError) {
            description("Snapshot error.")
            display("Snapshot error {}", err)
        }

        #[doc = "PoW hash is invalid or out of date."]
        PowHashInvalid {
            description("PoW hash is invalid or out of date.")
            display("PoW hash is invalid or out of date.")
        }

        #[doc = "The value of the nonce or mishash is invalid."]
        PowInvalid {
            description("The value of the nonce or mishash is invalid.")
            display("The value of the nonce or mishash is invalid.")
        }

        #[doc = "Unknown engine given"]
        UnknownEngineName(name: String) {
            description("Unknown engine name")
            display("Unknown engine name ({})", name)
        }
    }
}

impl From<SnapshotError> for Error {
    fn from(err: SnapshotError) -> Error {
        match err {
            SnapshotError::Io(err) => ErrorKind::StdIo(err).into(),
            SnapshotError::Trie(err) => ErrorKind::Trie(err).into(),
            SnapshotError::Decoder(err) => err.into(),
            other => ErrorKind::Snapshot(other).into(),
        }
    }
}

impl<E> From<Box<E>> for Error
where
    Error: From<E>,
{
    fn from(err: Box<E>) -> Error {
        Error::from(*err)
    }
}
