-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fn queue_transactions accepts UnverifiedTransactions #10887
Conversation
|
||
let txs: Vec<UnverifiedTransaction> = transactions | ||
.iter() | ||
.filter_map(|bytes| client.engine.decode_transaction(bytes).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this verification is performed by a miner in import_external_transactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we previous ignored invalid rlps and now we return an err in on_peer_transactions
? This seems like an improvement to me. But maybe it was done on purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we previous ignored invalid rlps and now we return an err in on_peer_transactions?
Yes
This seems like an improvement to me. But maybe it was done on purpose?
No, it was a regression introduced very recently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This closure is invoked in a IoWorker
thread pool and the whole thing was done to prevent blocking networking task for useless stuff like decoding transactions.
client.notify(|notify| { | ||
notify.transactions_received(&txs, peer_id); | ||
notify.transactions_received(&hashes, peer_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we should move this notification after miner imports the transactions? cause those transactions may be invalid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIR this notification only updates last_sent_transactions
of a peer, so that we don't propagate the same transaction that the peer sent us
self.machine().decode_transaction(transaction) | ||
/// Verifies len of transaction RLP | ||
fn verify_transaction_len(&self, transaction: &[u8]) -> Result<(), transaction::Error> { | ||
if transaction.len() > self.params().max_transaction_size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_transaction_size
is configurable since #8417 but I don't know if this is really needed... maybe we could just remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we can't just remove it. We need a way to tell users that their transaction is not being accepted to the pool and will never be propagated. We can obviously hardcode that into a miner, but it feels like a regression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
}); | ||
|
||
client.importer.miner.import_external_transactions(client, txs); | ||
// TODO: queue_transactions.queue, should accept FnOnce, so it can capture outer variables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you open an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to do it initially, but it requires reworking IoWorkers
. Currently every IoMessage
is passed to every handler. We just have one handler for ClientIoMessage
, I guess we could maybe hack around it with some internal mutability though.
|
||
let txs: Vec<UnverifiedTransaction> = transactions | ||
.iter() | ||
.filter_map(|bytes| client.engine.decode_transaction(bytes).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we previous ignored invalid rlps and now we return an err in on_peer_transactions
? This seems like an improvement to me. But maybe it was done on purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't accept this PR in it's current form it basically reverts a lot of work that has been done to optimize networking and transaction importing pipeline.
@@ -246,7 +246,7 @@ pub trait Handler: Send + Sync { | |||
/// Called when a peer makes an announcement. | |||
fn on_announcement(&self, _ctx: &dyn EventContext, _announcement: &Announcement) { } | |||
/// Called when a peer requests relay of some transactions. | |||
fn on_transactions(&self, _ctx: &dyn EventContext, _relay: &[UnverifiedTransaction]) { } | |||
fn on_transactions(&self, _ctx: &dyn EventContext, _relay: Vec<UnverifiedTransaction>) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a regression. Luckily it's only a light client, so we shouldn't have many transactions but you may end up cloning a lot of data.
}); | ||
|
||
client.importer.miner.import_external_transactions(client, txs); | ||
// TODO: queue_transactions.queue, should accept FnOnce, so it can capture outer variables | ||
for res in client.importer.miner.import_external_transactions(client, transactions.clone()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unacceptable, it means you clone ~4MB a second.
}); | ||
|
||
client.importer.miner.import_external_transactions(client, txs); | ||
// TODO: queue_transactions.queue, should accept FnOnce, so it can capture outer variables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to do it initially, but it requires reworking IoWorkers
. Currently every IoMessage
is passed to every handler. We just have one handler for ClientIoMessage
, I guess we could maybe hack around it with some internal mutability though.
self.machine().decode_transaction(transaction) | ||
/// Verifies len of transaction RLP | ||
fn verify_transaction_len(&self, transaction: &[u8]) -> Result<(), transaction::Error> { | ||
if transaction.len() > self.params().max_transaction_size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we can't just remove it. We need a way to tell users that their transaction is not being accepted to the pool and will never be propagated. We can obviously hardcode that into a miner, but it feels like a regression.
@@ -673,8 +673,7 @@ impl SyncHandler { | |||
trace!(target: "sync", "{:02} -> Transactions ({} entries)", peer_id, item_count); | |||
let mut transactions = Vec::with_capacity(item_count); | |||
for i in 0 .. item_count { | |||
let rlp = r.at(i)?; | |||
let tx = rlp.as_raw().to_vec(); | |||
let tx = r.val_at(i)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole point of passing Bytes
was to avoid doing any intensive work (like hashing transactions) synchronously in the network IoHandlers
.
The whole refactoring was done after a careful profiling of issues that happens on the mainnet where network
lock could be acquired for a very long time.
|
||
let txs: Vec<UnverifiedTransaction> = transactions | ||
.iter() | ||
.filter_map(|bytes| client.engine.decode_transaction(bytes).ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This closure is invoked in a IoWorker
thread pool and the whole thing was done to prevent blocking networking task for useless stuff like decoding transactions.
Regarding light client: note that all transactions always come from the RPC and we expect there is only few of them. Additional decoding-encoding is way lower priority than full node pipeline on the mainnet where you can have 16MBs packets of transactions received many times a second. |
I'll rework this code and open a new pr |
importing transactions on a client layer is a mess, this pr tries too simplify it a bit and reduce the number of redundant transaction encoding and decoding. example scenario before this pr and after this pr
before
light client, relay:
ethcore/light/src/net/mod.rs
decodes transactionethcore/sync/src/api.rs
encodes transactionethcore/src/client/client.rs
decodes transactionminer/src/pool/verifier.rs
encodes transactionethcore/src/machine.rs
decodes transactionnow
light client, relay
ethcore/light/src/net/mod.rs
decodes transactionminer/src/pool/verifier.rs
encodes transactionit's still not perfect, but definitely better than it was before