Transaction timestamp condition (#4419)

* Transaction timestamp condtiion

* Updated docs

* Updated docs

* Check agains last block timestamp
This commit is contained in:
Arkadiy Paronyan 2017-02-03 19:32:10 +01:00 committed by GitHub
parent 85e9091b29
commit 312aa72747
28 changed files with 224 additions and 110 deletions

View File

@ -206,6 +206,7 @@ impl HeaderChain {
pub fn get_header(&self, id: BlockId) -> Option<Bytes> { pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
match id { match id {
BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()), BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()),
BlockId::Latest if self.headers.read().is_empty() => Some(self.genesis_header.clone()),
BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()), BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()),
BlockId::Number(num) => { BlockId::Number(num) => {
if self.best_block.read().number < num { return None } if self.best_block.read().number < num { return None }

View File

@ -21,8 +21,9 @@ use ethcore::block_status::BlockStatus;
use ethcore::client::ClientReport; use ethcore::client::ClientReport;
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::header::Header; use ethcore::header::Header;
use ethcore::views::HeaderView;
use ethcore::verification::queue::{self, HeaderQueue}; use ethcore::verification::queue::{self, HeaderQueue};
use ethcore::transaction::PendingTransaction; use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition};
use ethcore::blockchain_info::BlockChainInfo; use ethcore::blockchain_info::BlockChainInfo;
use ethcore::spec::Spec; use ethcore::spec::Spec;
use ethcore::service::ClientIoMessage; use ethcore::service::ClientIoMessage;
@ -34,6 +35,7 @@ use util::{Bytes, Mutex, RwLock};
use provider::Provider; use provider::Provider;
use request; use request;
use time;
use self::header_chain::HeaderChain; use self::header_chain::HeaderChain;
@ -110,7 +112,11 @@ impl Client {
let best_num = self.chain.best_block().number; let best_num = self.chain.best_block().number;
self.tx_pool.lock() self.tx_pool.lock()
.values() .values()
.filter(|t| t.min_block.as_ref().map_or(true, |x| x <= &best_num)) .filter(|t| match t.condition {
Some(TransactionCondition::Number(ref x)) => x <= &best_num,
Some(TransactionCondition::Timestamp(ref x)) => *x <= time::get_time().sec as u64,
None => true,
})
.cloned() .cloned()
.collect() .collect()
} }
@ -135,6 +141,7 @@ impl Client {
genesis_hash: genesis_hash, genesis_hash: genesis_hash,
best_block_hash: best_block.hash, best_block_hash: best_block.hash,
best_block_number: best_block.number, best_block_number: best_block.number,
best_block_timestamp: HeaderView::new(&self.chain.get_header(BlockId::Latest).expect("Latest hash is always in the chain")).timestamp(),
ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None }, ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None },
ancient_block_number: if first_block.is_some() { Some(0) } else { None }, ancient_block_number: if first_block.is_some() { Some(0) } else { None },
first_block_hash: first_block.as_ref().map(|first| first.hash), first_block_hash: first_block.as_ref().map(|first| first.hash),

View File

@ -24,6 +24,8 @@ pub struct BestBlock {
pub hash: H256, pub hash: H256,
/// Best block number. /// Best block number.
pub number: BlockNumber, pub number: BlockNumber,
/// Best block timestamp.
pub timestamp: u64,
/// Best block total difficulty. /// Best block total difficulty.
pub total_difficulty: U256, pub total_difficulty: U256,
/// Best block uncompressed bytes /// Best block uncompressed bytes

View File

@ -485,6 +485,7 @@ impl BlockChain {
let best_block_number = bc.block_number(&best_block_hash).unwrap(); let best_block_number = bc.block_number(&best_block_hash).unwrap();
let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty; let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty;
let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner(); let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner();
let best_block_timestamp = BlockView::new(&best_block_rlp).header().timestamp();
let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec()); let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec());
let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h)); let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h));
@ -533,6 +534,7 @@ impl BlockChain {
number: best_block_number, number: best_block_number,
total_difficulty: best_block_total_difficulty, total_difficulty: best_block_total_difficulty,
hash: best_block_hash, hash: best_block_hash,
timestamp: best_block_timestamp,
block: best_block_rlp, block: best_block_rlp,
}; };
@ -585,6 +587,7 @@ impl BlockChain {
number: extras.number - 1, number: extras.number - 1,
total_difficulty: best_block_total_difficulty, total_difficulty: best_block_total_difficulty,
hash: hash, hash: hash,
timestamp: BlockView::new(&best_block_rlp).header().timestamp(),
block: best_block_rlp, block: best_block_rlp,
}; };
// update parent extras // update parent extras
@ -738,6 +741,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info, info: info,
timestamp: header.timestamp(),
block: bytes block: bytes
}, is_best); }, is_best);
@ -786,6 +790,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info, info: info,
timestamp: header.timestamp(),
block: bytes, block: bytes,
}, is_best); }, is_best);
true true
@ -850,6 +855,7 @@ impl BlockChain {
blocks_blooms: self.prepare_block_blooms_update(bytes, &info), blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
info: info.clone(), info: info.clone(),
timestamp: header.timestamp(),
block: bytes, block: bytes,
}, true); }, true);
@ -921,6 +927,7 @@ impl BlockChain {
hash: update.info.hash, hash: update.info.hash,
number: update.info.number, number: update.info.number,
total_difficulty: update.info.total_difficulty, total_difficulty: update.info.total_difficulty,
timestamp: update.timestamp,
block: update.block.to_vec(), block: update.block.to_vec(),
}); });
}, },
@ -1206,6 +1213,11 @@ impl BlockChain {
self.best_block.read().number self.best_block.read().number
} }
/// Get best block timestamp.
pub fn best_block_timestamp(&self) -> u64 {
self.best_block.read().timestamp
}
/// Get best block total difficulty. /// Get best block total difficulty.
pub fn best_block_total_difficulty(&self) -> U256 { pub fn best_block_total_difficulty(&self) -> U256 {
self.best_block.read().total_difficulty self.best_block.read().total_difficulty
@ -1293,6 +1305,7 @@ impl BlockChain {
genesis_hash: self.genesis_hash(), genesis_hash: self.genesis_hash(),
best_block_hash: best_block.hash.clone(), best_block_hash: best_block.hash.clone(),
best_block_number: best_block.number, best_block_number: best_block.number,
best_block_timestamp: best_block.timestamp,
first_block_hash: self.first_block(), first_block_hash: self.first_block(),
first_block_number: From::from(self.first_block_number()), first_block_number: From::from(self.first_block_number()),
ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()), ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()),

View File

@ -9,6 +9,8 @@ use super::extras::{BlockDetails, BlockReceipts, TransactionAddress, LogGroupPos
pub struct ExtrasUpdate<'a> { pub struct ExtrasUpdate<'a> {
/// Block info. /// Block info.
pub info: BlockInfo, pub info: BlockInfo,
/// Block timestamp.
pub timestamp: u64,
/// Current block uncompressed rlp bytes /// Current block uncompressed rlp bytes
pub block: &'a [u8], pub block: &'a [u8],
/// Modified block hashes. /// Modified block hashes.

View File

@ -1406,7 +1406,11 @@ impl BlockChainClient for Client {
} }
fn ready_transactions(&self) -> Vec<PendingTransaction> { fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain.read().best_block_number()) let (number, timestamp) = {
let chain = self.chain.read();
(chain.best_block_number(), chain.best_block_timestamp())
};
self.miner.ready_transactions(number, timestamp)
} }
fn queue_consensus_message(&self, message: Bytes) { fn queue_consensus_message(&self, message: Bytes) {

View File

@ -669,12 +669,14 @@ impl BlockChainClient for TestBlockChainClient {
} }
fn chain_info(&self) -> BlockChainInfo { fn chain_info(&self) -> BlockChainInfo {
let number = self.blocks.read().len() as BlockNumber - 1;
BlockChainInfo { BlockChainInfo {
total_difficulty: *self.difficulty.read(), total_difficulty: *self.difficulty.read(),
pending_total_difficulty: *self.difficulty.read(), pending_total_difficulty: *self.difficulty.read(),
genesis_hash: self.genesis_hash.clone(), genesis_hash: self.genesis_hash.clone(),
best_block_hash: self.last_hash.read().clone(), best_block_hash: self.last_hash.read().clone(),
best_block_number: self.blocks.read().len() as BlockNumber - 1, best_block_number: number,
best_block_timestamp: number,
first_block_hash: self.first_block.read().as_ref().map(|x| x.0), first_block_hash: self.first_block.read().as_ref().map(|x| x.0),
first_block_number: self.first_block.read().as_ref().map(|x| x.1), first_block_number: self.first_block.read().as_ref().map(|x| x.1),
ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0), ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0),
@ -709,7 +711,8 @@ impl BlockChainClient for TestBlockChainClient {
} }
fn ready_transactions(&self) -> Vec<PendingTransaction> { fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain_info().best_block_number) let info = self.chain_info();
self.miner.ready_transactions(info.best_block_number, info.best_block_timestamp)
} }
fn signing_network_id(&self) -> Option<u64> { None } fn signing_network_id(&self) -> Option<u64> { None }

View File

@ -25,7 +25,7 @@ use client::TransactionImportResult;
use executive::contract_address; use executive::contract_address;
use block::{ClosedBlock, IsBlock, Block}; use block::{ClosedBlock, IsBlock, Block};
use error::*; use error::*;
use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction}; use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction, Condition as TransactionCondition};
use receipt::{Receipt, RichReceipt}; use receipt::{Receipt, RichReceipt};
use spec::Spec; use spec::Spec;
use engines::{Engine, Seal}; use engines::{Engine, Seal};
@ -325,7 +325,7 @@ impl Miner {
let _timer = PerfTimer::new("prepare_block"); let _timer = PerfTimer::new("prepare_block");
let chain_info = chain.chain_info(); let chain_info = chain.chain_info();
let (transactions, mut open_block, original_work_hash) = { let (transactions, mut open_block, original_work_hash) = {
let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number)}; let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp)};
let mut sealing_work = self.sealing_work.lock(); let mut sealing_work = self.sealing_work.lock();
let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash());
let best_hash = chain_info.best_block_hash; let best_hash = chain_info.best_block_hash;
@ -597,7 +597,7 @@ impl Miner {
client: &MiningBlockChainClient, client: &MiningBlockChainClient,
transactions: Vec<UnverifiedTransaction>, transactions: Vec<UnverifiedTransaction>,
default_origin: TransactionOrigin, default_origin: TransactionOrigin,
min_block: Option<BlockNumber>, condition: Option<TransactionCondition>,
transaction_queue: &mut BanningTransactionQueue, transaction_queue: &mut BanningTransactionQueue,
) -> Vec<Result<TransactionImportResult, Error>> { ) -> Vec<Result<TransactionImportResult, Error>> {
let accounts = self.accounts.as_ref() let accounts = self.accounts.as_ref()
@ -635,7 +635,7 @@ impl Miner {
let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action); let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action);
match origin { match origin {
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
transaction_queue.add(transaction, origin, insertion_time, min_block, &details_provider) transaction_queue.add(transaction, origin, insertion_time, condition.clone(), &details_provider)
}, },
TransactionOrigin::External => { TransactionOrigin::External => {
transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider) transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider)
@ -892,7 +892,7 @@ impl MinerService for Miner {
let mut transaction_queue = self.transaction_queue.lock(); let mut transaction_queue = self.transaction_queue.lock();
// We need to re-validate transactions // We need to re-validate transactions
let import = self.add_transactions_to_queue( let import = self.add_transactions_to_queue(
chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.min_block, &mut transaction_queue chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.condition, &mut transaction_queue
).pop().expect("one result returned per added transaction; one added => one result; qed"); ).pop().expect("one result returned per added transaction; one added => one result; qed");
match import { match import {
@ -927,7 +927,7 @@ impl MinerService for Miner {
fn pending_transactions(&self) -> Vec<PendingTransaction> { fn pending_transactions(&self) -> Vec<PendingTransaction> {
let queue = self.transaction_queue.lock(); let queue = self.transaction_queue.lock();
queue.pending_transactions(BlockNumber::max_value()) queue.pending_transactions(BlockNumber::max_value(), u64::max_value())
} }
fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> { fn local_transactions(&self) -> BTreeMap<H256, LocalTransactionStatus> {
@ -942,14 +942,14 @@ impl MinerService for Miner {
self.transaction_queue.lock().future_transactions() self.transaction_queue.lock().future_transactions()
} }
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction> {
let queue = self.transaction_queue.lock(); let queue = self.transaction_queue.lock();
match self.options.pending_set { match self.options.pending_set {
PendingSet::AlwaysQueue => queue.pending_transactions(best_block), PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp),
PendingSet::SealingOrElseQueue => { PendingSet::SealingOrElseQueue => {
self.from_pending_block( self.from_pending_block(
best_block, best_block,
|| queue.pending_transactions(best_block), || queue.pending_transactions(best_block, best_block_timestamp),
|sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect()
) )
}, },
@ -1325,7 +1325,7 @@ mod tests {
// then // then
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.ready_transactions(best_block).len(), 1); assert_eq!(miner.ready_transactions(best_block, 0).len(), 1);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1);
assert_eq!(miner.pending_receipts(best_block).len(), 1); assert_eq!(miner.pending_receipts(best_block).len(), 1);
// This method will let us know if pending block was created (before calling that method) // This method will let us know if pending block was created (before calling that method)
@ -1345,7 +1345,7 @@ mod tests {
// then // then
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0);
assert_eq!(miner.pending_receipts(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0);
} }
@ -1364,7 +1364,7 @@ mod tests {
assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(res.unwrap(), TransactionImportResult::Current);
assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions().len(), 1);
assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0);
assert_eq!(miner.ready_transactions(best_block).len(), 0); assert_eq!(miner.ready_transactions(best_block, 0).len(), 0);
assert_eq!(miner.pending_receipts(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0);
// This method will let us know if pending block was created (before calling that method) // This method will let us know if pending block was created (before calling that method)
assert!(miner.prepare_work_sealing(&client)); assert!(miner.prepare_work_sealing(&client));

View File

@ -154,7 +154,7 @@ pub trait MinerService : Send + Sync {
fn pending_transactions(&self) -> Vec<PendingTransaction>; fn pending_transactions(&self) -> Vec<PendingTransaction>;
/// Get a list of all transactions that can go into the given block. /// Get a list of all transactions that can go into the given block.
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>; fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction>;
/// Get a list of all future transactions. /// Get a list of all future transactions.
fn future_transactions(&self) -> Vec<PendingTransaction>; fn future_transactions(&self) -> Vec<PendingTransaction>;

View File

@ -276,17 +276,17 @@ struct VerifiedTransaction {
origin: TransactionOrigin, origin: TransactionOrigin,
/// Insertion time /// Insertion time
insertion_time: QueuingInstant, insertion_time: QueuingInstant,
/// Delay until specifid block. /// Delay until specified condition is met.
min_block: Option<BlockNumber>, condition: Option<Condition>,
} }
impl VerifiedTransaction { impl VerifiedTransaction {
fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option<BlockNumber>) -> Self { fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, condition: Option<Condition>) -> Self {
VerifiedTransaction { VerifiedTransaction {
transaction: transaction, transaction: transaction,
origin: origin, origin: origin,
insertion_time: time, insertion_time: time,
min_block: min_block, condition: condition,
} }
} }
@ -666,14 +666,14 @@ impl TransactionQueue {
tx: SignedTransaction, tx: SignedTransaction,
origin: TransactionOrigin, origin: TransactionOrigin,
time: QueuingInstant, time: QueuingInstant,
min_block: Option<BlockNumber>, condition: Option<Condition>,
details_provider: &TransactionDetailsProvider, details_provider: &TransactionDetailsProvider,
) -> Result<TransactionImportResult, Error> { ) -> Result<TransactionImportResult, Error> {
if origin == TransactionOrigin::Local { if origin == TransactionOrigin::Local {
let hash = tx.hash(); let hash = tx.hash();
let cloned_tx = tx.clone(); let cloned_tx = tx.clone();
let result = self.add_internal(tx, origin, time, min_block, details_provider); let result = self.add_internal(tx, origin, time, condition, details_provider);
match result { match result {
Ok(TransactionImportResult::Current) => { Ok(TransactionImportResult::Current) => {
self.local_transactions.mark_pending(hash); self.local_transactions.mark_pending(hash);
@ -694,7 +694,7 @@ impl TransactionQueue {
} }
result result
} else { } else {
self.add_internal(tx, origin, time, min_block, details_provider) self.add_internal(tx, origin, time, condition, details_provider)
} }
} }
@ -704,7 +704,7 @@ impl TransactionQueue {
tx: SignedTransaction, tx: SignedTransaction,
origin: TransactionOrigin, origin: TransactionOrigin,
time: QueuingInstant, time: QueuingInstant,
min_block: Option<BlockNumber>, condition: Option<Condition>,
details_provider: &TransactionDetailsProvider, details_provider: &TransactionDetailsProvider,
) -> Result<TransactionImportResult, Error> { ) -> Result<TransactionImportResult, Error> {
if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price { if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price {
@ -815,7 +815,7 @@ impl TransactionQueue {
} }
tx.check_low_s()?; tx.check_low_s()?;
// No invalid transactions beyond this point. // No invalid transactions beyond this point.
let vtx = VerifiedTransaction::new(tx, origin, time, min_block); let vtx = VerifiedTransaction::new(tx, origin, time, condition);
let r = self.import_tx(vtx, client_account.nonce).map_err(Error::Transaction); let r = self.import_tx(vtx, client_account.nonce).map_err(Error::Transaction);
assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len());
r r
@ -1068,11 +1068,11 @@ impl TransactionQueue {
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions(&self) -> Vec<SignedTransaction> { pub fn top_transactions(&self) -> Vec<SignedTransaction> {
self.top_transactions_at(BlockNumber::max_value()) self.top_transactions_at(BlockNumber::max_value(), u64::max_value())
} }
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F) fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F)
where F: FnMut(&VerifiedTransaction) { where F: FnMut(&VerifiedTransaction) {
let mut delayed = HashSet::new(); let mut delayed = HashSet::new();
@ -1082,7 +1082,12 @@ impl TransactionQueue {
if delayed.contains(&sender) { if delayed.contains(&sender) {
continue; continue;
} }
if tx.min_block.unwrap_or(0) > best_block { let delay = match tx.condition {
Some(Condition::Number(n)) => n > best_block,
Some(Condition::Timestamp(t)) => t > best_timestamp,
None => false,
};
if delay {
delayed.insert(sender); delayed.insert(sender);
continue; continue;
} }
@ -1091,16 +1096,16 @@ impl TransactionQueue {
} }
/// Returns top transactions from the queue ordered by priority. /// Returns top transactions from the queue ordered by priority.
pub fn top_transactions_at(&self, best_block: BlockNumber) -> Vec<SignedTransaction> { pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<SignedTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(tx.transaction.clone()));
r r
} }
/// Return all ready transactions. /// Return all ready transactions.
pub fn pending_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> { pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
let mut r = Vec::new(); let mut r = Vec::new();
self.filter_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone())));
r r
} }
@ -1109,7 +1114,7 @@ impl TransactionQueue {
self.future.by_priority self.future.by_priority
.iter() .iter()
.map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`"))
.map(|t| PendingTransaction { transaction: t.transaction.clone(), min_block: t.min_block }) .map(|t| PendingTransaction { transaction: t.transaction.clone(), condition: t.condition.clone() })
.collect() .collect()
} }
@ -1382,7 +1387,7 @@ pub mod test {
use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; use super::{TransactionSet, TransactionOrder, VerifiedTransaction};
use miner::local_transactions::LocalTransactionsList; use miner::local_transactions::LocalTransactionsList;
use client::TransactionImportResult; use client::TransactionImportResult;
use transaction::{SignedTransaction, Transaction, Action}; use transaction::{SignedTransaction, Transaction, Action, Condition};
pub struct DummyTransactionDetailsProvider { pub struct DummyTransactionDetailsProvider {
account_details: AccountDetails, account_details: AccountDetails,
@ -2178,15 +2183,15 @@ pub mod test {
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
// when // when
let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(1), &default_tx_provider()).unwrap(); let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(Condition::Number(1)), &default_tx_provider()).unwrap();
let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap();
// then // then
assert_eq!(res1, TransactionImportResult::Current); assert_eq!(res1, TransactionImportResult::Current);
assert_eq!(res2, TransactionImportResult::Current); assert_eq!(res2, TransactionImportResult::Current);
let top = txq.top_transactions_at(0); let top = txq.top_transactions_at(0, 0);
assert_eq!(top.len(), 0); assert_eq!(top.len(), 0);
let top = txq.top_transactions_at(1); let top = txq.top_transactions_at(1, 0);
assert_eq!(top.len(), 2); assert_eq!(top.len(), 2);
} }

View File

@ -29,7 +29,7 @@ use spec::Spec;
use views::BlockView; use views::BlockView;
use util::stats::Histogram; use util::stats::Histogram;
use ethkey::{KeyPair, Secret}; use ethkey::{KeyPair, Secret};
use transaction::{PendingTransaction, Transaction, Action}; use transaction::{PendingTransaction, Transaction, Action, Condition};
use miner::MinerService; use miner::MinerService;
#[test] #[test]
@ -299,7 +299,7 @@ fn does_not_propagate_delayed_transactions() {
action: Action::Call(Address::default()), action: Action::Call(Address::default()),
value: 0.into(), value: 0.into(),
data: Vec::new(), data: Vec::new(),
}.sign(secret, None), Some(2)); }.sign(secret, None), Some(Condition::Number(2)));
let tx1 = PendingTransaction::new(Transaction { let tx1 = PendingTransaction::new(Transaction {
nonce: 1.into(), nonce: 1.into(),
gas_price: 0.into(), gas_price: 0.into(),

View File

@ -34,6 +34,8 @@ pub struct BlockChainInfo {
pub best_block_hash: H256, pub best_block_hash: H256,
/// Best blockchain block number. /// Best blockchain block number.
pub best_block_number: BlockNumber, pub best_block_number: BlockNumber,
/// Best blockchain block timestamp.
pub best_block_timestamp: u64,
/// Best ancient block hash. /// Best ancient block hash.
pub ancient_block_hash: Option<H256>, pub ancient_block_hash: Option<H256>,
/// Best ancient block number. /// Best ancient block number.

View File

@ -52,6 +52,16 @@ impl Decodable for Action {
} }
} }
/// Transaction activation condition.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "ipc", binary)]
pub enum Condition {
/// Valid at this block number or later.
Number(BlockNumber),
/// Valid at this unix time or later.
Timestamp(u64),
}
/// A set of information describing an externally-originating message call /// A set of information describing an externally-originating message call
/// or contract creation operation. /// or contract creation operation.
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
@ -448,16 +458,16 @@ impl Deref for LocalizedTransaction {
pub struct PendingTransaction { pub struct PendingTransaction {
/// Signed transaction data. /// Signed transaction data.
pub transaction: SignedTransaction, pub transaction: SignedTransaction,
/// To be activated at this block. `None` for immediately. /// To be activated at this condition. `None` for immediately.
pub min_block: Option<BlockNumber>, pub condition: Option<Condition>,
} }
impl PendingTransaction { impl PendingTransaction {
/// Create a new pending transaction from signed transaction. /// Create a new pending transaction from signed transaction.
pub fn new(signed: SignedTransaction, min_block: Option<BlockNumber>) -> Self { pub fn new(signed: SignedTransaction, condition: Option<Condition>) -> Self {
PendingTransaction { PendingTransaction {
transaction: signed, transaction: signed,
min_block: min_block, condition: condition,
} }
} }
} }
@ -466,7 +476,7 @@ impl From<SignedTransaction> for PendingTransaction {
fn from(t: SignedTransaction) -> Self { fn from(t: SignedTransaction) -> Self {
PendingTransaction { PendingTransaction {
transaction: t, transaction: t,
min_block: None, condition: None,
} }
} }
} }

View File

@ -192,7 +192,7 @@ pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider
{ {
let network_id = client.signing_network_id(); let network_id = client.signing_network_id();
let min_block = filled.min_block.clone(); let condition = filled.condition.clone();
let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?; let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?;
let (signed_transaction, token) = match signed_transaction { let (signed_transaction, token) = match signed_transaction {
@ -201,7 +201,7 @@ pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider
}; };
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id);
let pending_transaction = PendingTransaction::new(signed_transaction, min_block); let pending_transaction = PendingTransaction::new(signed_transaction, condition.map(Into::into));
dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| { dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| {
match token { match token {
Some(ref token) => WithToken::Yes(hash, token.clone()), Some(ref token) => WithToken::Yes(hash, token.clone()),
@ -222,7 +222,7 @@ pub fn fill_optional_fields<C, M>(request: TransactionRequest, default_sender: A
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()), value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new), data: request.data.unwrap_or_else(Vec::new),
min_block: request.min_block, condition: request.condition,
} }
} }

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{Address, U256, Bytes}; use util::{Address, U256, Bytes};
use v1::types::TransactionCondition;
/// Transaction request coming from RPC /// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
@ -33,8 +34,8 @@ pub struct TransactionRequest {
pub data: Option<Bytes>, pub data: Option<Bytes>,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this condition is met.
pub min_block: Option<u64>, pub condition: Option<TransactionCondition>,
} }
/// Transaction request coming from RPC with default values filled in. /// Transaction request coming from RPC with default values filled in.
@ -56,8 +57,8 @@ pub struct FilledTransactionRequest {
pub data: Bytes, pub data: Bytes,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this condition is met.
pub min_block: Option<u64>, pub condition: Option<TransactionCondition>,
} }
impl From<FilledTransactionRequest> for TransactionRequest { impl From<FilledTransactionRequest> for TransactionRequest {
@ -70,7 +71,7 @@ impl From<FilledTransactionRequest> for TransactionRequest {
value: Some(r.value), value: Some(r.value),
data: Some(r.data), data: Some(r.data),
nonce: r.nonce, nonce: r.nonce,
min_block: r.min_block, condition: r.condition,
} }
} }
} }

View File

@ -349,7 +349,7 @@ mod test {
value: 10_000_000.into(), value: 10_000_000.into(),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
}) })
} }

View File

@ -87,8 +87,8 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient,
if let Some(gas) = modification.gas { if let Some(gas) = modification.gas {
request.gas = gas.into(); request.gas = gas.into();
} }
if let Some(ref min_block) = modification.min_block { if let Some(ref condition) = modification.condition {
request.min_block = min_block.as_ref().and_then(|b| b.to_min_block_num()); request.condition = condition.clone().map(Into::into);
} }
} }
let result = f(&*client, &*miner, &*accounts, payload); let result = f(&*client, &*miner, &*accounts, payload);
@ -160,7 +160,7 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
// Dispatch if everything is ok // Dispatch if everything is ok
if sender_matches && data_matches && value_matches && nonce_matches { if sender_matches && data_matches && value_matches && nonce_matches {
let pending_transaction = PendingTransaction::new(signed_transaction, request.min_block); let pending_transaction = PendingTransaction::new(signed_transaction, request.condition.map(Into::into));
dispatch_transaction(&*client, &*miner, pending_transaction) dispatch_transaction(&*client, &*miner, pending_transaction)
.map(Into::into) .map(Into::into)
.map(ConfirmationResponse::SendTransaction) .map(ConfirmationResponse::SendTransaction)

View File

@ -212,7 +212,7 @@ impl MinerService for TestMinerService {
self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect()
} }
fn ready_transactions(&self, _best_block: BlockNumber) -> Vec<PendingTransaction> { fn ready_transactions(&self, _best_block: BlockNumber, _best_timestamp: u64) -> Vec<PendingTransaction> {
self.pending_transactions.lock().values().cloned().map(Into::into).collect() self.pending_transactions.lock().values().cloned().map(Into::into).collect()
} }

View File

@ -516,7 +516,7 @@ fn rpc_eth_pending_transaction_by_hash() {
tester.miner.pending_transactions.lock().insert(H256::zero(), tx); tester.miner.pending_transactions.lock().insert(H256::zero(), tx);
} }
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","minBlock":null,"networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#;
let request = r#"{ let request = r#"{
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "eth_getTransactionByHash", "method": "eth_getTransactionByHash",
@ -832,12 +832,11 @@ fn rpc_eth_sign_transaction() {
let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() +
r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""raw":"0x"# + &rlp.to_hex() + r#"","# +
r#""tx":{"# + r#""tx":{"# +
r#""blockHash":null,"blockNumber":null,"creates":null,"# + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# +
&format!("\"from\":\"0x{:?}\",", &address) + &format!("\"from\":\"0x{:?}\",", &address) +
r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# +
&format!("\"hash\":\"0x{:?}\",", t.hash()) + &format!("\"hash\":\"0x{:?}\",", t.hash()) +
r#""input":"0x","# + r#""input":"0x","# +
r#""minBlock":null,"# +
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
r#""nonce":"0x1","# + r#""nonce":"0x1","# +
&format!("\"publicKey\":\"0x{:?}\",", t.recover_public().unwrap()) + &format!("\"publicKey\":\"0x{:?}\",", t.recover_public().unwrap()) +

View File

@ -85,7 +85,7 @@ fn should_return_list_of_items_to_confirm() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap();
@ -93,7 +93,7 @@ fn should_return_list_of_items_to_confirm() {
let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#;
let response = concat!( let response = concat!(
r#"{"jsonrpc":"2.0","result":["#, r#"{"jsonrpc":"2.0","result":["#,
r#"{"id":"0x1","payload":{"sendTransaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","minBlock":null,"nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, r#"{"id":"0x1","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#,
r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#,
r#"],"id":1}"# r#"],"id":1}"#
); );
@ -116,7 +116,7 @@ fn should_reject_transaction_from_queue_without_dispatching() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
@ -143,7 +143,7 @@ fn should_not_remove_transaction_if_password_is_invalid() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);
@ -187,7 +187,7 @@ fn should_confirm_transaction_and_dispatch() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -233,7 +233,7 @@ fn should_alter_the_sender_and_nonce() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: Some(10.into()), nonce: Some(10.into()),
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -283,7 +283,7 @@ fn should_confirm_transaction_with_token() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -332,7 +332,7 @@ fn should_confirm_transaction_with_rlp() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {
@ -380,7 +380,7 @@ fn should_return_error_when_sender_does_not_match() {
value: U256::from(1), value: U256::from(1),
data: vec![], data: vec![],
nonce: None, nonce: None,
min_block: None, condition: None,
})).unwrap(); })).unwrap();
let t = Transaction { let t = Transaction {

View File

@ -277,12 +277,11 @@ fn should_add_sign_transaction_to_the_queue() {
let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() +
r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""raw":"0x"# + &rlp.to_hex() + r#"","# +
r#""tx":{"# + r#""tx":{"# +
r#""blockHash":null,"blockNumber":null,"creates":null,"# + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# +
&format!("\"from\":\"0x{:?}\",", &address) + &format!("\"from\":\"0x{:?}\",", &address) +
r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# +
&format!("\"hash\":\"0x{:?}\",", t.hash()) + &format!("\"hash\":\"0x{:?}\",", t.hash()) +
r#""input":"0x","# + r#""input":"0x","# +
r#""minBlock":null,"# +
&format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) +
r#""nonce":"0x1","# + r#""nonce":"0x1","# +
&format!("\"publicKey\":\"0x{:?}\",", t.public_key()) + &format!("\"publicKey\":\"0x{:?}\",", t.public_key()) +

View File

@ -139,7 +139,7 @@ mod tests {
fn test_serialize_block_transactions() { fn test_serialize_block_transactions() {
let t = BlockTransactions::Full(vec![Transaction::default()]); let t = BlockTransactions::Full(vec![Transaction::default()]);
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}]"#); assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}]"#);
let t = BlockTransactions::Hashes(vec![H256::default().into()]); let t = BlockTransactions::Hashes(vec![H256::default().into()]);
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();

View File

@ -21,7 +21,7 @@ use serde::{Serialize, Serializer};
use util::log::Colour; use util::log::Colour;
use util::bytes::ToPretty; use util::bytes::ToPretty;
use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, BlockNumber}; use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition};
use v1::helpers; use v1::helpers;
/// Confirmation waiting in a queue /// Confirmation waiting in a queue
@ -196,9 +196,8 @@ pub struct TransactionModification {
pub gas_price: Option<U256>, pub gas_price: Option<U256>,
/// Modified gas /// Modified gas
pub gas: Option<U256>, pub gas: Option<U256>,
/// Modified min block /// Modified transaction condition.
#[serde(rename="minBlock")] pub condition: Option<Option<TransactionCondition>>,
pub min_block: Option<Option<BlockNumber>>,
} }
/// Represents two possible return values. /// Represents two possible return values.
@ -240,7 +239,7 @@ impl<A, B> Serialize for Either<A, B> where
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;
use serde_json; use serde_json;
use v1::types::{U256, H256, BlockNumber}; use v1::types::{U256, H256, TransactionCondition};
use v1::helpers; use v1::helpers;
use super::*; use super::*;
@ -274,13 +273,13 @@ mod tests {
value: 100_000.into(), value: 100_000.into(),
data: vec![1, 2, 3], data: vec![1, 2, 3],
nonce: Some(1.into()), nonce: Some(1.into()),
min_block: None, condition: None,
}), }),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#;
// then // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -300,13 +299,13 @@ mod tests {
value: 100_000.into(), value: 100_000.into(),
data: vec![1, 2, 3], data: vec![1, 2, 3],
nonce: Some(1.into()), nonce: Some(1.into()),
min_block: None, condition: None,
}), }),
}; };
// when // when
let res = serde_json::to_string(&ConfirmationRequest::from(request)); let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#;
// then // then
assert_eq!(res.unwrap(), expected.to_owned()); assert_eq!(res.unwrap(), expected.to_owned());
@ -336,7 +335,7 @@ mod tests {
let s1 = r#"{ let s1 = r#"{
"sender": "0x000000000000000000000000000000000000000a", "sender": "0x000000000000000000000000000000000000000a",
"gasPrice":"0xba43b7400", "gasPrice":"0xba43b7400",
"minBlock":"0x42" "condition": { "block": 66 }
}"#; }"#;
let s2 = r#"{"gas": "0x1233"}"#; let s2 = r#"{"gas": "0x1233"}"#;
let s3 = r#"{}"#; let s3 = r#"{}"#;
@ -351,19 +350,19 @@ mod tests {
sender: Some(10.into()), sender: Some(10.into()),
gas_price: Some(U256::from_str("0ba43b7400").unwrap()), gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
gas: None, gas: None,
min_block: Some(Some(BlockNumber::Num(0x42))), condition: Some(Some(TransactionCondition::Number(0x42))),
}); });
assert_eq!(res2, TransactionModification { assert_eq!(res2, TransactionModification {
sender: None, sender: None,
gas_price: None, gas_price: None,
gas: Some(U256::from_str("1233").unwrap()), gas: Some(U256::from_str("1233").unwrap()),
min_block: None, condition: None,
}); });
assert_eq!(res3, TransactionModification { assert_eq!(res3, TransactionModification {
sender: None, sender: None,
gas_price: None, gas_price: None,
gas: None, gas: None,
min_block: None, condition: None,
}); });
} }

View File

@ -27,6 +27,7 @@ mod log;
mod sync; mod sync;
mod transaction; mod transaction;
mod transaction_request; mod transaction_request;
mod transaction_condition;
mod receipt; mod receipt;
mod rpc_settings; mod rpc_settings;
mod trace; mod trace;
@ -55,6 +56,7 @@ pub use self::sync::{
}; };
pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus};
pub use self::transaction_request::TransactionRequest; pub use self::transaction_request::TransactionRequest;
pub use self::transaction_condition::TransactionCondition;
pub use self::receipt::Receipt; pub use self::receipt::Receipt;
pub use self::rpc_settings::RpcSettings; pub use self::rpc_settings::RpcSettings;
pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace::{LocalizedTrace, TraceResults};

View File

@ -19,7 +19,7 @@ use ethcore::miner;
use ethcore::contract_address; use ethcore::contract_address;
use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction};
use v1::helpers::errors; use v1::helpers::errors;
use v1::types::{Bytes, H160, H256, U256, H512, BlockNumber}; use v1::types::{Bytes, H160, H256, U256, H512, TransactionCondition};
/// Transaction /// Transaction
#[derive(Debug, Default, Clone, PartialEq, Serialize)] #[derive(Debug, Default, Clone, PartialEq, Serialize)]
@ -70,8 +70,7 @@ pub struct Transaction {
/// The S field of the signature. /// The S field of the signature.
pub s: U256, pub s: U256,
/// Transaction activates at specified block. /// Transaction activates at specified block.
#[serde(rename="minBlock")] pub condition: Option<TransactionCondition>,
pub min_block: Option<BlockNumber>,
} }
/// Local Transaction Status /// Local Transaction Status
@ -190,7 +189,7 @@ impl From<LocalizedTransaction> for Transaction {
v: t.original_v().into(), v: t.original_v().into(),
r: signature.r().into(), r: signature.r().into(),
s: signature.s().into(), s: signature.s().into(),
min_block: None, condition: None,
} }
} }
} }
@ -224,7 +223,7 @@ impl From<SignedTransaction> for Transaction {
v: t.original_v().into(), v: t.original_v().into(),
r: signature.r().into(), r: signature.r().into(),
s: signature.s().into(), s: signature.s().into(),
min_block: None, condition: None,
} }
} }
} }
@ -232,7 +231,7 @@ impl From<SignedTransaction> for Transaction {
impl From<PendingTransaction> for Transaction { impl From<PendingTransaction> for Transaction {
fn from(t: PendingTransaction) -> Transaction { fn from(t: PendingTransaction) -> Transaction {
let mut r = Transaction::from(t.transaction); let mut r = Transaction::from(t.transaction);
r.min_block = t.min_block.map(|b| BlockNumber::Num(b)); r.condition = t.condition.map(|b| b.into());
r r
} }
} }
@ -261,7 +260,7 @@ mod tests {
fn test_transaction_serialize() { fn test_transaction_serialize() {
let t = Transaction::default(); let t = Transaction::default();
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();
assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}"#); assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}"#);
} }
#[test] #[test]

View File

@ -0,0 +1,67 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity 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.
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
use ethcore;
/// Represents condition on minimum block number or block timestamp.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum TransactionCondition {
/// Valid at this minimum block number.
#[serde(rename="block")]
Number(u64),
/// Valid at given unix time.
#[serde(rename="time")]
Timestamp(u64),
}
impl Into<ethcore::transaction::Condition> for TransactionCondition {
fn into(self) -> ethcore::transaction::Condition {
match self {
TransactionCondition::Number(n) => ethcore::transaction::Condition::Number(n),
TransactionCondition::Timestamp(n) => ethcore::transaction::Condition::Timestamp(n),
}
}
}
impl From<ethcore::transaction::Condition> for TransactionCondition {
fn from(condition: ethcore::transaction::Condition) -> Self {
match condition {
ethcore::transaction::Condition::Number(n) => TransactionCondition::Number(n),
ethcore::transaction::Condition::Timestamp(n) => TransactionCondition::Timestamp(n),
}
}
}
#[cfg(test)]
mod tests {
use ethcore;
use super::*;
use serde_json;
#[test]
fn condition_deserialization() {
let s = r#"[{ "block": 51 }, { "time": 10 }]"#;
let deserialized: Vec<TransactionCondition> = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, vec![TransactionCondition::Number(51), TransactionCondition::Timestamp(10)])
}
#[test]
fn condition_into() {
assert_eq!(ethcore::transaction::Condition::Number(100), TransactionCondition::Number(100).into());
assert_eq!(ethcore::transaction::Condition::Timestamp(100), TransactionCondition::Timestamp(100).into());
}
}

View File

@ -16,7 +16,7 @@
//! `TransactionRequest` type //! `TransactionRequest` type
use v1::types::{Bytes, H160, U256, BlockNumber}; use v1::types::{Bytes, H160, U256, TransactionCondition};
use v1::helpers; use v1::helpers;
use util::log::Colour; use util::log::Colour;
@ -41,9 +41,8 @@ pub struct TransactionRequest {
pub data: Option<Bytes>, pub data: Option<Bytes>,
/// Transaction's nonce /// Transaction's nonce
pub nonce: Option<U256>, pub nonce: Option<U256>,
/// Delay until this block if specified. /// Delay until this block condition.
#[serde(rename="minBlock")] pub condition: Option<TransactionCondition>,
pub min_block: Option<BlockNumber>,
} }
pub fn format_ether(i: U256) -> String { pub fn format_ether(i: U256) -> String {
@ -93,7 +92,7 @@ impl From<helpers::TransactionRequest> for TransactionRequest {
value: r.value.map(Into::into), value: r.value.map(Into::into),
data: r.data.map(Into::into), data: r.data.map(Into::into),
nonce: r.nonce.map(Into::into), nonce: r.nonce.map(Into::into),
min_block: r.min_block.map(|b| BlockNumber::Num(b)), condition: r.condition.map(Into::into),
} }
} }
} }
@ -108,7 +107,7 @@ impl From<helpers::FilledTransactionRequest> for TransactionRequest {
value: Some(r.value.into()), value: Some(r.value.into()),
data: Some(r.data.into()), data: Some(r.data.into()),
nonce: r.nonce.map(Into::into), nonce: r.nonce.map(Into::into),
min_block: r.min_block.map(|b| BlockNumber::Num(b)), condition: r.condition.map(Into::into),
} }
} }
} }
@ -123,7 +122,7 @@ impl Into<helpers::TransactionRequest> for TransactionRequest {
value: self.value.map(Into::into), value: self.value.map(Into::into),
data: self.data.map(Into::into), data: self.data.map(Into::into),
nonce: self.nonce.map(Into::into), nonce: self.nonce.map(Into::into),
min_block: self.min_block.and_then(|b| b.to_min_block_num()), condition: self.condition.map(Into::into),
} }
} }
} }
@ -134,7 +133,7 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
use rustc_serialize::hex::FromHex; use rustc_serialize::hex::FromHex;
use serde_json; use serde_json;
use v1::types::{U256, H160, BlockNumber}; use v1::types::{U256, H160, TransactionCondition};
use super::*; use super::*;
#[test] #[test]
@ -147,7 +146,7 @@ mod tests {
"value":"0x3", "value":"0x3",
"data":"0x123456", "data":"0x123456",
"nonce":"0x4", "nonce":"0x4",
"minBlock":"0x13" "condition": { "block": 19 }
}"#; }"#;
let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); let deserialized: TransactionRequest = serde_json::from_str(s).unwrap();
@ -159,7 +158,7 @@ mod tests {
value: Some(U256::from(3)), value: Some(U256::from(3)),
data: Some(vec![0x12, 0x34, 0x56].into()), data: Some(vec![0x12, 0x34, 0x56].into()),
nonce: Some(U256::from(4)), nonce: Some(U256::from(4)),
min_block: Some(BlockNumber::Num(0x13)), condition: Some(TransactionCondition::Number(0x13)),
}); });
} }
@ -183,7 +182,7 @@ mod tests {
value: Some(U256::from_str("9184e72a").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()),
data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()),
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }
@ -200,7 +199,7 @@ mod tests {
value: None, value: None,
data: None, data: None,
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }
@ -224,7 +223,7 @@ mod tests {
value: None, value: None,
data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()),
nonce: None, nonce: None,
min_block: None, condition: None,
}); });
} }

View File

@ -1,5 +1,5 @@
use client::{Rpc, RpcError}; use client::{Rpc, RpcError};
use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, BlockNumber}; use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition};
use serde_json::{Value as JsonValue, to_value}; use serde_json::{Value as JsonValue, to_value};
use std::path::PathBuf; use std::path::PathBuf;
use futures::{BoxFuture, Canceled}; use futures::{BoxFuture, Canceled};
@ -22,13 +22,13 @@ impl SignerRpc {
id: U256, id: U256,
new_gas: Option<U256>, new_gas: Option<U256>,
new_gas_price: Option<U256>, new_gas_price: Option<U256>,
new_min_block: Option<Option<BlockNumber>>, new_condition: Option<Option<TransactionCondition>>,
pwd: &str pwd: &str
) -> BoxFuture<Result<U256, RpcError>, Canceled> ) -> BoxFuture<Result<U256, RpcError>, Canceled>
{ {
self.rpc.request("signer_confirmRequest", vec![ self.rpc.request("signer_confirmRequest", vec![
to_value(&format!("{:#x}", id)), to_value(&format!("{:#x}", id)),
to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, min_block: new_min_block }), to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }),
to_value(&pwd), to_value(&pwd),
]) ])
} }