diff --git a/core/src/client/client.rs b/core/src/client/client.rs index ba1538effe..9c3fb6824c 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -20,7 +20,7 @@ use std::sync::{Arc, Weak}; use std::time::Instant; use cio::IoChannel; -use ckey::{Address, PlatformAddress, Public}; +use ckey::{Address, NetworkId, PlatformAddress, Public}; use cmerkle::Result as TrieResult; use cnetwork::NodeId; use cstate::{ @@ -892,7 +892,31 @@ impl BlockProducer for Client { } } -impl MiningBlockChainClient for Client {} +impl MiningBlockChainClient for Client { + fn get_malicious_users(&self) -> Vec
{ + self.importer.miner.get_malicious_users() + } + + fn release_malicious_users(&self, prisoner_vec: Vec
) { + self.importer.miner.release_malicious_users(prisoner_vec) + } + + fn imprison_malicious_users(&self, prisoner_vec: Vec
) { + self.importer.miner.imprison_malicious_users(prisoner_vec) + } + + fn get_immune_users(&self) -> Vec
{ + self.importer.miner.get_immune_users() + } + + fn register_immune_users(&self, immune_user_vec: Vec
) { + self.importer.miner.register_immune_users(immune_user_vec) + } + + fn get_network_id(&self) -> NetworkId { + self.common_params(BlockId::Latest).unwrap().network_id() + } +} impl ChainTimeInfo for Client { fn transaction_block_age(&self, tracker: &H256, parent_block_number: BlockNumber) -> Option { diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 33594dcf4a..d80f5b0301 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -32,7 +32,7 @@ pub use self::test_client::TestBlockChainClient; use std::ops::Range; use std::sync::Arc; -use ckey::{Address, PlatformAddress, Public}; +use ckey::{Address, NetworkId, PlatformAddress, Public}; use cmerkle::Result as TrieResult; use cnetwork::NodeId; use cstate::{AssetScheme, FindActionHandler, OwnedAsset, StateResult, Text, TopLevelState, TopStateView}; @@ -271,7 +271,25 @@ pub trait BlockProducer { } /// Extended client interface used for mining -pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + FindActionHandler {} +pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + FindActionHandler { + /// Returns malicious users who sent failing transactions. + fn get_malicious_users(&self) -> Vec
; + + /// Release designated users from the malicious user list. + fn release_malicious_users(&self, prisoner_vec: Vec
); + + /// Append designated users to the malicious user list. + fn imprison_malicious_users(&self, prisoner_vec: Vec
); + + /// Returns users immune from getting banned. + fn get_immune_users(&self) -> Vec
; + + /// Append designated users to the immune user list. + fn register_immune_users(&self, immune_user_vec: Vec
); + + /// Returns network id. + fn get_network_id(&self) -> NetworkId; +} /// Provides methods to access database. pub trait DatabaseClient { diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index f823758c79..af2791e459 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -362,7 +362,31 @@ impl BlockProducer for TestBlockChainClient { } } -impl MiningBlockChainClient for TestBlockChainClient {} +impl MiningBlockChainClient for TestBlockChainClient { + fn get_malicious_users(&self) -> Vec
{ + self.miner.get_malicious_users() + } + + fn release_malicious_users(&self, prisoner_vec: Vec
) { + self.miner.release_malicious_users(prisoner_vec) + } + + fn imprison_malicious_users(&self, prisoner_vec: Vec
) { + self.miner.imprison_malicious_users(prisoner_vec) + } + + fn get_immune_users(&self) -> Vec
{ + self.miner.get_immune_users() + } + + fn register_immune_users(&self, immune_user_vec: Vec
) { + self.miner.register_immune_users(immune_user_vec) + } + + fn get_network_id(&self) -> NetworkId { + NetworkId::default() + } +} impl AccountData for TestBlockChainClient { fn seq(&self, address: &Address, id: BlockId) -> Option { diff --git a/core/src/miner/mem_pool.rs b/core/src/miner/mem_pool.rs index d95627f55a..65e126388a 100644 --- a/core/src/miner/mem_pool.rs +++ b/core/src/miner/mem_pool.rs @@ -958,6 +958,11 @@ impl MemPool { self.current.queue.iter().any(|tx| tx.origin.is_local()) } + /// Returns Some(true) if the given transaction is local and None for not found. + pub fn is_local_transaction(&self, tx_hash: H256) -> Option { + self.by_hash.get(&tx_hash).and_then(|found_item| Some(found_item.origin.is_local())) + } + /// Checks the given timelock with the current time/timestamp. fn should_wait_timelock(timelock: &TxTimelock, best_block_number: BlockNumber, best_block_timestamp: u64) -> bool { if let Some(block_number) = timelock.block { diff --git a/core/src/miner/miner.rs b/core/src/miner/miner.rs index 43f1e00dbf..09a8b5ee63 100644 --- a/core/src/miner/miner.rs +++ b/core/src/miner/miner.rs @@ -16,6 +16,7 @@ use std::collections::HashSet; use std::iter::once; +use std::iter::FromIterator; use std::ops::Range; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -23,7 +24,7 @@ use std::time::{Duration, Instant}; use ckey::{public_to_address, Address, Password, PlatformAddress, Public}; use cstate::{FindActionHandler, TopLevelState}; -use ctypes::errors::HistoryError; +use ctypes::errors::{HistoryError, RuntimeError}; use ctypes::transaction::{Action, IncompleteTransaction, Timelock}; use ctypes::{BlockNumber, Header}; use cvm::ChainTimeInfo; @@ -127,6 +128,8 @@ pub struct Miner { accounts: Option>, notifiers: RwLock>>, + malicious_users: RwLock>, + immune_users: RwLock>, } impl Miner { @@ -184,6 +187,8 @@ impl Miner { sealing_enabled: AtomicBool::new(true), accounts, notifiers: RwLock::new(notifiers), + malicious_users: RwLock::new(HashSet::new()), + immune_users: RwLock::new(HashSet::new()), } } @@ -269,6 +274,27 @@ impl Miner { .into_iter() .map(|tx| { let hash = tx.hash(); + // FIXME: Refactoring is needed. recover_public is calling in verify_transaction_unordered. + let signer_public = tx.recover_public()?; + let signer_address = public_to_address(&signer_public); + if default_origin.is_local() { + self.immune_users.write().insert(signer_address); + } + + let origin = self + .accounts + .as_ref() + .and_then(|accounts| match accounts.has_public(&signer_public) { + Ok(true) => Some(TxOrigin::Local), + Ok(false) => None, + Err(_) => None, + }) + .unwrap_or(default_origin); + + if self.malicious_users.read().contains(&signer_address) { + // FIXME: just to skip, think about another way. + return Ok(()) + } if client.transaction_block(&TransactionId::Hash(hash)).is_some() { cdebug!(MINER, "Rejected transaction {:?}: already in the blockchain", hash); return Err(HistoryError::TransactionAlreadyImported.into()) @@ -276,7 +302,8 @@ impl Miner { if !self.is_allowed_transaction(&tx.action) { cdebug!(MINER, "Rejected transaction {:?}: {:?} is not allowed transaction", hash, tx.action); } - match tx + let immune_users = self.immune_users.read(); + let tx = tx .verify_basic() .map_err(From::from) .and_then(|_| { @@ -284,33 +311,34 @@ impl Miner { self.engine.verify_transaction_with_params(&tx, &common_params) }) .and_then(|_| CodeChainMachine::verify_transaction_seal(tx, &fake_header)) - { - Err(e) => { + .map_err(|e| { + match e { + Error::Syntax(_) if !origin.is_local() && !immune_users.contains(&signer_address) => { + self.malicious_users.write().insert(signer_address); + } + _ => {} + } cdebug!(MINER, "Rejected transaction {:?} with invalid signature: {:?}", hash, e); - Err(e) + e + })?; + + // This check goes here because verify_transaction takes SignedTransaction parameter + self.engine.machine().verify_transaction(&tx, &fake_header, client, false).map_err(|e| { + match e { + Error::Syntax(_) if !origin.is_local() && !immune_users.contains(&signer_address) => { + self.malicious_users.write().insert(signer_address); + } + _ => {} } - Ok(tx) => { - // This check goes here because verify_transaction takes SignedTransaction parameter - self.engine.machine().verify_transaction(&tx, &fake_header, client, false)?; - - let origin = self - .accounts - .as_ref() - .and_then(|accounts| match accounts.has_public(&tx.signer_public()) { - Ok(true) => Some(TxOrigin::Local), - Ok(false) => None, - Err(_) => None, - }) - .unwrap_or(default_origin); + e + })?; - let timelock = self.calculate_timelock(&tx, client)?; - let tx_hash = tx.hash(); + let timelock = self.calculate_timelock(&tx, client)?; + let tx_hash = tx.hash(); - to_insert.push(MemPoolInput::new(tx, origin, timelock)); - tx_hashes.push(tx_hash); - Ok(()) - } - } + to_insert.push(MemPoolInput::new(tx, origin, timelock)); + tx_hashes.push(tx_hash); + Ok(()) }) .collect(); @@ -499,15 +527,22 @@ impl Miner { let mut tx_count: usize = 0; let tx_total = transactions.len(); - let mut invald_tx_users = HashSet::new(); + let mut invalid_tx_users = HashSet::new(); + + let immune_users = self.immune_users.read(); for tx in transactions { let signer_public = tx.signer_public(); - if invald_tx_users.contains(&signer_public) { + let signer_address = public_to_address(&signer_public); + if self.malicious_users.read().contains(&signer_address) { + invalid_transactions.push(tx.hash()); + continue + } + if invalid_tx_users.contains(&signer_public) { // The previous transaction has failed continue } if !self.is_allowed_transaction(&tx.action) { - invald_tx_users.insert(signer_public); + invalid_tx_users.insert(signer_public); invalid_transactions.push(tx.hash()); continue } @@ -524,7 +559,22 @@ impl Miner { // already have transaction - ignore Err(Error::History(HistoryError::TransactionAlreadyImported)) => {} Err(e) => { - invald_tx_users.insert(signer_public); + match e { + Error::Runtime(RuntimeError::AssetSupplyOverflow) + | Error::Runtime(RuntimeError::InvalidScript) => { + if !self + .mem_pool + .read() + .is_local_transaction(hash) + .expect("The tx is clearly fetched from the mempool") + && !immune_users.contains(&signer_address) + { + self.malicious_users.write().insert(signer_address); + } + } + _ => {} + } + invalid_tx_users.insert(signer_public); invalid_transactions.push(hash); cinfo!( MINER, @@ -1074,6 +1124,35 @@ impl MinerService for Miner { cdebug!(MINER, "Stop sealing"); self.sealing_enabled.store(false, Ordering::Relaxed); } + + fn get_malicious_users(&self) -> Vec
{ + Vec::from_iter(self.malicious_users.read().iter().map(Clone::clone)) + } + + fn release_malicious_users(&self, prisoner_vec: Vec
) { + let mut malicious_users = self.malicious_users.write(); + for address in prisoner_vec { + malicious_users.remove(&address); + } + } + + fn imprison_malicious_users(&self, prisoner_vec: Vec
) { + let mut malicious_users = self.malicious_users.write(); + for address in prisoner_vec { + malicious_users.insert(address); + } + } + + fn get_immune_users(&self) -> Vec
{ + Vec::from_iter(self.immune_users.read().iter().map(Clone::clone)) + } + + fn register_immune_users(&self, immune_user_vec: Vec
) { + let mut immune_users = self.immune_users.write(); + for address in immune_user_vec { + immune_users.insert(address); + } + } } fn get_next_seq(transactions: impl IntoIterator, addresses: &[Address]) -> Option { diff --git a/core/src/miner/mod.rs b/core/src/miner/mod.rs index 63f599efe7..1922977013 100644 --- a/core/src/miner/mod.rs +++ b/core/src/miner/mod.rs @@ -150,6 +150,21 @@ pub trait MinerService: Send + Sync { /// Stop sealing. fn stop_sealing(&self); + + /// Get malicious users + fn get_malicious_users(&self) -> Vec
; + + /// Release target malicious users from malicious user set. + fn release_malicious_users(&self, prisoner_vec: Vec
); + + /// Imprison target malicious users to malicious user set. + fn imprison_malicious_users(&self, prisoner_vec: Vec
); + + /// Get ban-immune users. + fn get_immune_users(&self) -> Vec
; + + /// Register users to ban-immune users. + fn register_immune_users(&self, immune_user_vec: Vec
); } /// Mining status diff --git a/rpc/src/v1/impls/mempool.rs b/rpc/src/v1/impls/mempool.rs index d534c6fabb..e61f9d6928 100644 --- a/rpc/src/v1/impls/mempool.rs +++ b/rpc/src/v1/impls/mempool.rs @@ -16,8 +16,9 @@ use std::sync::Arc; -use ccore::{BlockChainClient, SignedTransaction}; +use ccore::{BlockChainClient, MiningBlockChainClient, SignedTransaction}; use cjson::bytes::Bytes; +use ckey::{Address, PlatformAddress}; use primitives::H256; use rlp::UntrustedRlp; @@ -41,7 +42,7 @@ impl MempoolClient { impl Mempool for MempoolClient where - C: BlockChainClient + 'static, + C: BlockChainClient + MiningBlockChainClient + 'static, { fn send_signed_transaction(&self, raw: Bytes) -> Result { UntrustedRlp::new(&raw.into_vec()) @@ -78,4 +79,37 @@ where fn get_pending_transactions_count(&self, from: Option, to: Option) -> Result { Ok(self.client.count_pending_transactions(from.unwrap_or(0)..to.unwrap_or(::std::u64::MAX))) } + + fn get_banned_accounts(&self) -> Result> { + let malicious_user_vec = self.client.get_malicious_users(); + let network_id = self.client.get_network_id(); + Ok(malicious_user_vec.into_iter().map(|address| PlatformAddress::new_v1(network_id, address)).collect()) + } + + fn unban_accounts(&self, prisoner_list: Vec) -> Result<()> { + let prisoner_vec: Vec
= prisoner_list.into_iter().map(PlatformAddress::into_address).collect(); + + self.client.release_malicious_users(prisoner_vec); + Ok(()) + } + + fn ban_accounts(&self, prisoner_list: Vec) -> Result<()> { + let prisoner_vec: Vec
= prisoner_list.into_iter().map(PlatformAddress::into_address).collect(); + + self.client.imprison_malicious_users(prisoner_vec); + Ok(()) + } + + fn get_immune_accounts(&self) -> Result> { + let immune_user_vec = self.client.get_immune_users(); + let network_id = self.client.get_network_id(); + Ok(immune_user_vec.into_iter().map(|address| PlatformAddress::new_v1(network_id, address)).collect()) + } + + fn register_immune_accounts(&self, immune_user_list: Vec) -> Result<()> { + let immune_user_vec: Vec
= immune_user_list.into_iter().map(PlatformAddress::into_address).collect(); + + self.client.register_immune_users(immune_user_vec); + Ok(()) + } } diff --git a/rpc/src/v1/traits/mempool.rs b/rpc/src/v1/traits/mempool.rs index ef482ad89a..571b9fa919 100644 --- a/rpc/src/v1/traits/mempool.rs +++ b/rpc/src/v1/traits/mempool.rs @@ -17,6 +17,7 @@ use cjson::bytes::Bytes; use primitives::H256; +use ckey::PlatformAddress; use jsonrpc_core::Result; use super::super::types::PendingTransactions; @@ -42,5 +43,20 @@ build_rpc_trait! { /// Gets the count of transactions in the current mem pool. # [rpc(name = "mempool_getPendingTransactionsCount")] fn get_pending_transactions_count(&self, Option, Option) -> Result; + + #[rpc(name = "mempool_getBannedAccounts")] + fn get_banned_accounts(&self) -> Result>; + + #[rpc(name = "mempool_unbanAccounts")] + fn unban_accounts(&self, Vec) -> Result<()>; + + #[rpc(name = "mempool_banAccounts")] + fn ban_accounts(&self, Vec) -> Result<()>; + + #[rpc(name = "mempool_getImmuneAccounts")] + fn get_immune_accounts(&self) -> Result>; + + #[rpc(name = "mempool_registerImmuneAccounts")] + fn register_immune_accounts(&self, Vec) -> Result<()>; } } diff --git a/spec/JSON-RPC.md b/spec/JSON-RPC.md index 8127e45591..61e2375710 100644 --- a/spec/JSON-RPC.md +++ b/spec/JSON-RPC.md @@ -323,6 +323,11 @@ When `Transaction` is included in any response, there will be an additional fiel * [mempool_getTransactionResultsByTracker](#mempool_getTransactionResultsByTracker) * [mempool_getPendingTransactions](#mempool_getpendingtransactions) * [mempool_getPendingTransactionsCount](#mempool_getpendingtransactionscount) + * [mempool_getBannedAccounts](#mempool_getbannedaccounts) + * [mempool_unbanAccounts](#mempool_unbanaccounts) + * [mempool_banAccounts](#mempool_banaccounts) + * [mempool_registerImmuneAccounts](#mempool_registerimmuneaccounts) + * [mempool_getRegisteredImmuneAccounts](#mempool_getregisteredimmuneaccounts) *** * [engine_getCoinbase](#engine_getcoinbase) * [engine_getBlockReward](#engine_getblockreward) @@ -1803,6 +1808,160 @@ Returns a count of the transactions that have insertion_timestamps within the gi [Back to **List of methods**](#list-of-methods) +## mempool_banAccounts +Register accounts to the mempool's banned account list. The mempool would not import the transactions from the users on the list. + +### Params + 1. prisoner_list: `PlatformAccount[]` + +### Returns +`null` + +Errors: `Invalid params` + +### Request Example +``` +curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "mempool_getBannedAccounts", "params": [], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc": "2.0", + "result": null, + "id": null +} +``` + +[Back to **List of methods**](#list-of-methods) + +## mempool_unbanAccounts +Release accounts from the mempool's banned account list. + +### Params + 1. trusty_list: `PlatformAccount[]` + +### Returns +`null` + +Errors: `Invalid params` + +### Request Example +``` +curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "mempool_unbanAccounts", "params": [["tccq9h7vnl68frvqapzv3tujrxtxtwqdnxw6yamrrgd"]], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc": "2.0", + "result": null, + "id": null +} +``` + +[Back to **List of methods**](#list-of-methods) + +## mempool_getBannedAccounts +Returns accounts banned for propagating transactions which cause syntax errors or runtime errors. + +### Params +No parameters + +### Returns +`PlatformAddress[]` + +Error: `Invalid params` + +### Request Example +``` +curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "mempool_getBannedAccounts", "params": [], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc": "2.0", + "result": [ + "tccq9h7vnl68frvqapzv3tujrxtxtwqdnxw6yamrrgd" + ], + "id": null +} +``` + +[Back to **List of methods**](#list-of-methods) + +## mempool_registerImmuneAccounts +Register accounts immune from getting banned. The trasactions from these accounts would never be rejected for the reason they are malicious. + +### Params + 1. immune_user_list: `PlatformAccount[]` + +### Returns +`null` + +Error: `Invalid params` + +### Request Example +``` +curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "mempool_registerImmuneAccounts", "params": [["tccq9h7vnl68frvqapzv3tujrxtxtwqdnxw6yamrrgd"]], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc": "2.0", + "result": null, + "id": null +} +``` + +[Back to **List of methods**](#list-of-methods) + +## mempool_getRegisteredImmuneAccounts +Gets immune accounts registered by `mempool_registerImmuneAccounts`. + +### Params +No parameters + +### Returns +`PlatformAccount[]` + +Error: `Invalid params` + +### Request Example +``` +curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "mempool_getImmuneAccounts", "params": [], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc": "2.0", + "result": [ + "tccq9h7vnl68frvqapzv3tujrxtxtwqdnxw6yamrrgd" + ], + "id": null +} +``` + +[Back to **List of methods**](#list-of-methods) + ## engine_getCoinbase Gets coinbase's account id.