Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Private packets verification and queue refactoring #8715

Merged
merged 22 commits into from
Aug 29, 2018

Conversation

grbIzl
Copy link
Collaborator

@grbIzl grbIzl commented May 25, 2018

This PR addresses two major TODOs from private transactions PR

@grbIzl grbIzl added A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. M4-core ⛓ Core client code / Rust. labels May 25, 2018
#[derive(Debug)]
pub struct PrivateScorying;

impl txpool::Scoring<VerifiedPrivateTransaction> for PrivateScorying {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomusdrw I reworked my queue's implementation in order to use txpool directly for storing transaction in it. The main problem, that I'm facing, is scoring implementation. Actually I'd like to fully re-use miner's scoring, but I don't know, how to do it, because it's bound with miner's VerifiedTransaction trait.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as result I had to copy-past scoring code from miner.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the two (Scoring and Ready) generic then.

@Tbaut
Copy link
Contributor

Tbaut commented May 28, 2018

@grbIzl we should update the documentation for that one right?

@grbIzl
Copy link
Collaborator Author

grbIzl commented May 28, 2018

@Tbaut i don't think so. It's mostly the internal improvements and refactorings

@5chdn 5chdn added this to the 1.12 milestone May 31, 2018
@grbIzl grbIzl force-pushed the PrivatePacketsVerification branch from 63f4402 to 6436745 Compare June 19, 2018 10:05
@grbIzl grbIzl added A0-pleasereview 🤓 Pull request needs code review. and removed A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. labels Jun 20, 2018
@grbIzl grbIzl requested a review from tomusdrw June 20, 2018 09:49
Copy link
Collaborator

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of grumbles and suggestions.

@@ -167,6 +168,12 @@ error_chain! {
description("General ethcore error."),
display("General ethcore error {}", err),
}

#[doc = "Tx pool error."]
Txpool(err: TxPoolError) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be just foreign_link (and From implementation wouldn't be needed in such case)

}
Ok(())
}

/// Add signed private transaction into the store /// Creates corresponding public transaction if last required signature collected and sends it to the chain
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split into two lines

}
let account = desc.validator_account;
if let Action::Call(contract) = transaction.signed().action {
let signed_tx = transaction.transaction.clone();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to clone the entire transaction? It seems that we only need a reference in :283

None => {
trace!("Propagating transaction further");
self.broadcast_private_transaction(private_hash, transaction.private_transaction.rlp_bytes().into_vec());
return Ok(())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that error handling in this block might be incorrect:

  1. We drain all transactions for verification
  2. But we stop at first incorrect transaction

That means that every other transaction will just be ignored. Most likely we should run all the transactions and only return error after that (or even return a Vec<Result<>).

Please review all return, bail! and ? statements in that loop.

let mut signed_transactions = self.imported_signed_transactions.lock();
for tx in signed_transactions.drain(..) {
let private_hash = tx.private_transaction_hash();
let desc = match self.transactions_for_signing.lock().get(&private_hash) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We acquire a second lock here (after acquiring imported_signed_transactions).

Couple of issues with this:

  1. Seems that RwLock would do (you sometimes only need read access, not write)
  2. Multiple, dependent locks are always error-prone, is it really necessary to have the two? Can't we put it into separate structs?
  3. Calling private functions self.* after acquiring a lock is always super error-prone, we have to be sure that the lock is not re-acquired in that private function.
  4. Last, but not least: the locking happens in opposite order than the declaration - to avoid deadlocks we should always acquire locks in the same order as the fields are declared in the struct.

// Use pool's verifying pipeline for original transaction's verification
let verifier = pool::verifier::Verifier::new(client, options, Default::default());
let _verified_tx = verifier.verify_transaction(pool::verifier::Transaction::Unverified(transaction.clone()))?;
let signed_tx = SignedTransaction::new(transaction)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should take SignedTransaction from _verified_tx, otherwise you are recovering the signature twice

pub fn drain<C: pool::client::NonceClient>(&self, client: C) -> Vec<Arc<VerifiedPrivateTransaction>> {
let ready = PrivateReadyState::new(client);
let mut hashes: Vec<H256> = Vec::new();
let res: Vec<Arc<VerifiedPrivateTransaction>> = self.verification_pool.read().pending(ready).collect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let transactions: Vec<_> = self.verification_pool.read().pending(ready).collect()
let mut pool = self.verification_pool.write();
for tx in &transactions {
  pool.remove(tx.hash(), true);
}
transactions

doesn't make sense to allocate the vec twice.

@@ -329,6 +329,10 @@ pub struct PeerInfo {
ask_time: Instant,
/// Holds a set of transactions recently sent to this peer to avoid spamming.
last_sent_transactions: HashSet<H256>,
/// Holds a set of private transactions recently sent to this peer to avoid spamming.
last_sent_private_transactions: HashSet<H256>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to separate the two? The collision is not possibly anyway, for simplicity let's have a single set.

@@ -1083,6 +1117,9 @@ impl ChainSync {
peer_info.last_sent_transactions.clear()
);
}

// reset stats for private transaction packets
self.clear_private_stats();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it seems that we always re-broadcast after the the block, right? Why can't we use the same logic as for regular transactions?

Also this statements should be guarded by similar ifs as the above code - we don't need to clear it when we import uncle block or we are syncing.

@@ -1121,13 +1158,13 @@ impl ChainSync {
}

/// Broadcast private transaction message to peers.
pub fn propagate_private_transaction(&mut self, io: &mut SyncIo, packet: Bytes) {
SyncPropagator::propagate_private_transaction(self, io, packet);
pub fn propagate_private_transaction(&mut self, io: &mut SyncIo, transaction_hash: H256, packet: Bytes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth merging the two methods (and the methods they are calling into one) and just discriminate using enum to avoid duplication.

@5chdn 5chdn added A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. A3-stale 🍃 Pull request did not receive any updates in a long time. No review needed at this stage. Close it. and removed A0-pleasereview 🤓 Pull request needs code review. labels Jul 2, 2018
@5chdn
Copy link
Contributor

5chdn commented Jul 2, 2018

Marking as stale @grbIzl. 🐥

@5chdn
Copy link
Contributor

5chdn commented Jul 9, 2018

Will reopen once you continue working on it. 👍

@5chdn 5chdn closed this Jul 9, 2018
@grbIzl grbIzl reopened this Jul 18, 2018
@5chdn 5chdn added A0-pleasereview 🤓 Pull request needs code review. and removed A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. labels Aug 20, 2018
}
Ok(())
}

/// Add signed private transaction into the store
/// Creates corresponding public transaction if last required signature collected and sends it to the chain
pub fn process_signature(&self, signed_tx: SignedPrivateTransaction) -> Result<(), Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
verification_queue.remove_private_transaction(&transaction_hash);
return Ok(());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needless return i.e, enough with Ok(())

warn!("Failed to remove transaction from signing store, error: {:?}", err);
bail!(err);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace single-pattern match with:

if let Err(err) = self.transactions_for_signing.write().remove(&private_hash) { ..... }

fn broadcast_private_transaction(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::PrivateTransaction(message.clone())));
fn broadcast_private_transaction(&self, transaction_hash: H256, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::PrivateTransaction(transaction_hash, message.clone())));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks suspect to cloning the vector here instead of moving into the closure! Any particular reason why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible to move captured outer variable (message)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: in the future we may consider to change type ChainMessageType to only take reference of [u8]. In that case this should solve this message clone issue.

use error::{Error, ErrorKind};

type Pool = txpool::Pool<VerifiedPrivateTransaction, pool::scoring::NonceAndGasPrice>;

/// Maximum length for private transactions queues.
const MAX_QUEUE_LEN: usize = 8312;

/// Desriptor for private transaction stored in queue for verification
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in Descriptor

Rephrase this, something like Private transaction type!

/// Address that should be used for verification
pub validator_account: Address,
pub validator_account: Option<Address>,
/// Resulted verified
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified result

pub validator_account: Option<Address>,
/// Resulted verified
pub transaction: SignedTransaction,
/// Original transaction's hash
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original transaction hash

pub transaction: SignedTransaction,
/// Original transaction's hash
pub transaction_hash: H256,
/// Original transaction's sender
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orginal transaction sender or Transaction origin address

let options = self.verification_options.clone();
// Use pool's verifying pipeline for original transaction's verification
let verifier = pool::verifier::Verifier::new(client, options, Default::default(), None);
let verified_tx = verifier.verify_transaction(pool::verifier::Transaction::Unverified(transaction.clone()))?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needless clone() call

You can either take the transaction (UnverifiedTransaction) by reference and clone it or take it by value and move it!

}
}
}

impl VerificationStore {
/// Adds private transaction for verification into the store
pub fn add_transaction<C: pool::client::Client>(
&mut self,
&self,
transaction: UnverifiedTransaction,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below!


/// Trait which should be implemented by a private transaction handler.
pub trait PrivateTxHandler: Send + Sync + 'static {
/// Function called on new private transaction received.
fn import_private_transaction(&self, rlp: &[u8]) -> Result<(), String>;
/// Returns hash of the imported transaction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hash


/// Function called on new signed private transaction received.
fn import_signed_private_transaction(&self, rlp: &[u8]) -> Result<(), String>;
/// Returns hash of the imported transaction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hash

insertion_id: i as u64,
transaction: Arc::new(verified),
}
let txs = vec![tx1, tx2, tx3, tx4].into_iter().enumerate().map(|(_, tx)| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove enumerate() because it is not used!

Copy link
Collaborator

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,

Looks overall good, I have left a few comments that can improve that code a bit!

I have not commented on the logging but ideally, all these logs should have a target i.e., trace!(target: "foo", "some very important message. Not required for this PR

But, this PR needs to rebased/merged to master before merging (that's why I used the Request changes option!

@5chdn 5chdn modified the milestones: 2.0 Ethereum, 2.1 Aug 23, 2018
Copy link
Collaborator

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@niklasad1 niklasad1 added A8-looksgood 🦄 Pull request is reviewed well. and removed A0-pleasereview 🤓 Pull request needs code review. labels Aug 28, 2018
Copy link
Collaborator

@sorpaas sorpaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

fn broadcast_private_transaction(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::PrivateTransaction(message.clone())));
fn broadcast_private_transaction(&self, transaction_hash: H256, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::PrivateTransaction(transaction_hash, message.clone())));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: in the future we may consider to change type ChainMessageType to only take reference of [u8]. In that case this should solve this message clone issue.

@sorpaas sorpaas merged commit 1073d56 into master Aug 29, 2018
@sorpaas sorpaas deleted the PrivatePacketsVerification branch August 29, 2018 12:31
dvdplm added a commit that referenced this pull request Aug 30, 2018
* master:
  evmbin: Fix gas_used issue in state root mismatch and handle output better (#9418)
  Update hardcoded sync (#9421)
  Add block reward contract config to ethash and allow off-chain contracts (#9312)
  Private packets verification and queue refactoring (#8715)
  Update tobalaba.json (#9419)
  docs: add parity ethereum logo to readme (#9415)
  build: update rocksdb crate (#9414)
  Updating the CI system  (#8765)
  Better support for eth_getLogs in light mode (#9186)
  Add update docs script to CI (#9219)
  `gasleft` extern implemented for WASM runtime (kip-6) (#9357)
  block view! removal in progress (#9397)
  Prevent sync restart if import queue full (#9381)
  nonroot CentOS Docker image (#9280)
  ethcore: kovan: delay activation of strict score validation (#9406)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A8-looksgood 🦄 Pull request is reviewed well. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add private transactions validation before propagating
7 participants