-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Don't remove invalid transactions when skipping. #5121
Changes from 3 commits
990bfb7
bd14cd2
6c4afe5
f4475bb
f0f213e
3ca7cce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,15 +26,15 @@ use sp_inherents::InherentData; | |
use log::{error, info, debug, trace}; | ||
use sp_core::ExecutionContext; | ||
use sp_runtime::{ | ||
traits::{Block as BlockT, Hash as HashT, Header as HeaderT, DigestFor, BlakeTwo256}, | ||
generic::BlockId, | ||
traits::{Block as BlockT, Hash as HashT, Header as HeaderT, DigestFor, BlakeTwo256}, | ||
}; | ||
use sp_transaction_pool::{TransactionPool, InPoolTransaction}; | ||
use sc_telemetry::{telemetry, CONSENSUS_INFO}; | ||
use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; | ||
use sp_api::{ProvideRuntimeApi, ApiExt}; | ||
use futures::prelude::*; | ||
use sp_blockchain::HeaderBackend; | ||
use sp_blockchain::{HeaderBackend, ApplyExtrinsicFailed}; | ||
use std::marker::PhantomData; | ||
|
||
/// Proposer factory. | ||
|
@@ -230,7 +230,7 @@ impl<A, B, Block, C> ProposerInner<B, Block, C, A> | |
Ok(()) => { | ||
debug!("[{:?}] Pushed to the block.", pending_tx_hash); | ||
} | ||
Err(sp_blockchain::Error::ApplyExtrinsicFailed(sp_blockchain::ApplyExtrinsicFailed::Validity(e))) | ||
Err(sp_blockchain::Error::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity(e))) | ||
if e.exhausted_resources() => { | ||
if is_first { | ||
debug!("[{:?}] Invalid transaction: FullBlock on empty block", pending_tx_hash); | ||
|
@@ -246,6 +246,13 @@ impl<A, B, Block, C> ProposerInner<B, Block, C, A> | |
break; | ||
} | ||
} | ||
Err(e) if skipped > 0 => { | ||
trace!( | ||
"[{:?}] Ignoring invalid transaction when skipping: {}", | ||
pending_tx_hash, | ||
e | ||
); | ||
} | ||
Err(e) => { | ||
debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e); | ||
unqueue_invalid.push(pending_tx_hash); | ||
|
@@ -395,4 +402,47 @@ mod tests { | |
storage_changes.transaction_storage_root, | ||
); | ||
} | ||
|
||
#[test] | ||
fn should_not_remove_invalid_transactions_when_skipping() { | ||
// given | ||
let client = Arc::new(substrate_test_runtime_client::new()); | ||
let txpool = Arc::new( | ||
BasicPool::new(Default::default(), Arc::new(FullChainApi::new(client.clone()))).0 | ||
); | ||
|
||
futures::executor::block_on( | ||
txpool.submit_at(&BlockId::number(0), vec![ | ||
extrinsic(0), | ||
extrinsic(1), | ||
Transfer { | ||
amount: Default::default(), | ||
nonce: 2, | ||
from: AccountKeyring::Alice.into(), | ||
to: Default::default(), | ||
}.into_exhaust_tx(), | ||
extrinsic(3), | ||
extrinsic(4), | ||
extrinsic(5), | ||
]) | ||
).unwrap(); | ||
|
||
let mut proposer_factory = ProposerFactory::new(client.clone(), txpool.clone()); | ||
let mut proposer = proposer_factory.init_with_now( | ||
&client.header(&BlockId::number(0)).unwrap().unwrap(), | ||
Box::new(move || time::Instant::now()), | ||
); | ||
|
||
// when | ||
let deadline = time::Duration::from_secs(9); | ||
let block = futures::executor::block_on( | ||
proposer.propose(Default::default(), Default::default(), deadline, RecordProof::No) | ||
).map(|r| r.block).unwrap(); | ||
|
||
// then | ||
// block should have some extrinsics although we have some more in the pool. | ||
assert_eq!(block.extrinsics().len(), 2); | ||
assert_eq!(txpool.ready().count(), 6); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still would like to see that we build a second block here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then it means that the transaction cannot always exhaust resources. I guess it's fine, I'll change it so that it exhaust resources if it's not the ONLY/FIRST transaction in the block. Then keeping that variant as |
||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,15 +101,36 @@ impl Transfer { | |
pub fn into_signed_tx(self) -> Extrinsic { | ||
let signature = sp_keyring::AccountKeyring::from_public(&self.from) | ||
.expect("Creates keyring from public key.").sign(&self.encode()).into(); | ||
Extrinsic::Transfer(self, signature) | ||
Extrinsic::Transfer { | ||
transfer: self, | ||
signature, | ||
exhaust_resources: false, | ||
} | ||
} | ||
|
||
/// Convert into a signed extrinsic, which will not end up included | ||
/// in block due to resource exhaustion. | ||
#[cfg(feature = "std")] | ||
pub fn into_exhaust_tx(self) -> Extrinsic { | ||
let signature = sp_keyring::AccountKeyring::from_public(&self.from) | ||
.expect("Creates keyring from public key.").sign(&self.encode()).into(); | ||
Extrinsic::Transfer { | ||
transfer: self, | ||
signature, | ||
exhaust_resources: true, | ||
} | ||
} | ||
} | ||
|
||
/// Extrinsic for test-runtime. | ||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] | ||
pub enum Extrinsic { | ||
AuthoritiesChange(Vec<AuthorityId>), | ||
Transfer(Transfer, AccountSignature), | ||
Transfer { | ||
transfer: Transfer, | ||
signature: AccountSignature, | ||
exhaust_resources: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why isn't this just a distinct variant in this enum? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has to share most of the logic with
but I felt it's too arbitrary and it made more sense to just extend transfer. |
||
}, | ||
IncludeData(Vec<u8>), | ||
StorageChange(Vec<u8>, Option<Vec<u8>>), | ||
ChangesTrieConfigUpdate(Option<ChangesTrieConfiguration>), | ||
|
@@ -130,9 +151,9 @@ impl BlindCheckable for Extrinsic { | |
fn check(self, _signature: CheckSignature) -> Result<Self, TransactionValidityError> { | ||
match self { | ||
Extrinsic::AuthoritiesChange(new_auth) => Ok(Extrinsic::AuthoritiesChange(new_auth)), | ||
Extrinsic::Transfer(transfer, signature) => { | ||
Extrinsic::Transfer { transfer, signature, exhaust_resources } => { | ||
if sp_runtime::verify_encoded_lazy(&signature, &transfer, &transfer.from) { | ||
Ok(Extrinsic::Transfer(transfer, signature)) | ||
Ok(Extrinsic::Transfer { transfer, signature, exhaust_resources }) | ||
} else { | ||
Err(InvalidTransaction::BadProof.into()) | ||
} | ||
|
@@ -165,7 +186,7 @@ impl ExtrinsicT for Extrinsic { | |
impl Extrinsic { | ||
pub fn transfer(&self) -> &Transfer { | ||
match self { | ||
Extrinsic::Transfer(ref transfer, _) => transfer, | ||
Extrinsic::Transfer { ref transfer, .. } => transfer, | ||
_ => panic!("cannot convert to transfer ref"), | ||
} | ||
} | ||
|
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.
We really need to factor this out and make it reusable for Polkadot: https://github.com/paritytech/polkadot/blob/master/validation/src/block_production.rs#L299
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.
Agree, I think it deserves a separate issue though. I'll make a companion PR for now.
EDIT: Seems the companion PR is not necessary, cause the skipping logic is not present there.