-
Notifications
You must be signed in to change notification settings - Fork 287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TransactionBatch - hold RuntimeTransaction #3041
TransactionBatch - hold RuntimeTransaction #3041
Conversation
fb11a17
to
3fddfe9
Compare
b766751
to
7c93e54
Compare
@@ -24,7 +24,7 @@ use { | |||
/// for example: message hash, simple-vote-tx flag, limits set by instructions | |||
pub trait StaticMeta { | |||
fn message_hash(&self) -> &Hash; | |||
fn is_simple_vote_tx(&self) -> bool; | |||
fn is_simple_vote_transaction(&self) -> bool; |
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.
@tao-stones changed the name here to be consistent with SanitizedTransaction
, meaning no impl changes when we start to use the transaction traits.
Prefer the longer/explicit name anyway instead of abbreviation.
core/src/banking_stage/committer.rs
Outdated
let txs = batch | ||
.sanitized_transactions() | ||
.iter() | ||
.map(|tx| tx.deref().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.
will be replaced by use of upcoming trait SVMTransactionAdapter
in a future PR
@@ -204,13 +209,11 @@ impl PrioritizationFeeCache { | |||
continue; | |||
} | |||
|
|||
let compute_budget_limits = process_compute_budget_instructions( |
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.
nice!
I'm in favor of introducing |
Introducing it now only gets rid of one deref. think for the comparison on |
address_loader, | ||
reserved_account_keys, | ||
) | ||
}) |
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 it has two steps here: creating the "static" version of RuntimeTransaction::<SanitizedVersionedTransaction>
, then creating the "dynamic" version of RuntimeTransaction<SanitizedTransaction>
; this illustrates the ideal use of RuntimeTransaction
, which is: the "static" version can advance to "dynamic" version; and "dynamic" version is the superset of "static" version.
How do you think to create "static" version in ImmutableDeserializedPacket::new()
, then consume "static" by advance it into "dynamic" when needed? I know you are expecting IDP to be retired soon, but I think it's worth to update IDP to properly use RuntimeTransaction
to shake out its usages, but also save one clone
of SanitizedVersionedTransaction
at ln 128.
I'm thinking something like this:
- change
ImmutableDeserializePacket
def to:
enum RuntimeTransactionVersion {
Static (RuntimeTransaction::<SanitizedVersionedTransaction>),
Dynamic (RuntimeTransaction::<SanitizedTransaction>),
}
pub struct ImmutableDeserializedPacket {
original_packet: Packet,
runtime_transaction: RuntimeTransactionVersion,
// ^^ it caches the rest of stuff IDP used to have
}
- change
new()
to:
pub fn new(packet: Packet) -> Result<Self, DeserializedPacketError> {
let versioned_transaction: VersionedTransaction = packet.deserialize_slice(..)?;
let sanitized_transaction = SanitizedVersionedTransaction::try_from(versioned_transaction)?;
Ok(Self {
original_packet: packet,
runtime_transaction: Static(RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
sanitized_transaction, None, None)),
})
}
- So the
build_sanitized_transaction()
would be renamed to:
pub fn get_sanitized_transaction(
&mut self,
votes_only: bool,
address_loader: impl AddressLoader,
reserved_account_keys: &HashSet<Pubkey>,
) -> Result<()> {
if votes_only && !self.is_simple_vote() {
return None;
}
if self.runtime_transaction.is_static() {
// consume static, advance to dynamic
self.runtime_transaction = Dynamic(
RuntimeTransaction::<SanitizedTransaction>::try_from(
self.runtime_transaction, address_loader, reserved_account_keys)
)?;
}
Ok(())
}
(sorry for pseudo code may not be practical)
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 do something like that. enum should be relatively light overhead. i'll test it out tomorrow.
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.
Thinking through it this morning. I'm not sure it's worth the added complexity.
If we stick to not implementing Clone
for RuntimeTransaction
(could practice imo), then to process the batch we need to take owership of the RuntimeTransaction
; we'd then need to put the RuntimeTransaction
back into the ImmutableDeserializedPacket
on retry.
This adds a ton of complexity and possible bugs.
I think also transitionining from static->dynamic runtime also breaks the naming of Immutable
.
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.
one option is to only implement Clone
for RuntimeTransaction<SanitizedTransaction>
; but that feels a bit dirty, given how much effort I just spent to remove all the SanitizedTransaction::clone
from replay stage.
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.
Chatted offline, we believed IDP will be refactored out soon, so we'll leave it as is for now.
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.
Sounds fine to leave as is but was also wondering about if we create and store a RuntimeTransaction<SanitizedVersionedTransaction>
inside ImmutableDeserializedPacket::new
and then add a new RuntimeTransaction<SanitizedTransaction>::try_from_ref
function which takes a &RuntimeTransaction::<SanitizedVersionedTransaction>
and does the clone internally?
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 brings back some concern we had about adding Clone
to the TransactionMeta
which means we must be careful about the Meta
size. Adding Clone
now may it difficult for us to remove in the future.
See above comment, the IDP is short-lived and will be removed soon.
7c93e54
to
33de7d8
Compare
@@ -139,7 +147,6 @@ pub struct UsageCostDetails<'a, Tx: SVMMessage> { | |||
pub programs_execution_cost: u64, | |||
pub loaded_accounts_data_size_cost: u64, | |||
pub allocated_accounts_data_size: u64, | |||
pub signature_details: TransactionSignatureDetails, |
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 stored by RuntimeTransaction
so we can get rid of it here
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.
looks great!
address_loader, | ||
reserved_account_keys, | ||
) | ||
}) |
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.
Chatted offline, we believed IDP will be refactored out soon, so we'll leave it as is for now.
ledger/src/blockstore_processor.rs
Outdated
let transactions: Vec<SanitizedTransaction> = batch | ||
.sanitized_transactions() | ||
.iter() | ||
.map(|tx| tx.deref().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.
Thought we touched on this before, just to be clear, the deref().clone()
doesn't have meaningful impact on perf, as this function is on hot path.
f7d6794
to
2c50748
Compare
@jstarry @tao-stones I rebased on master just to make sure CI still passes - make sure no SanitizedTransactions snuck in in new tests anywhere. I also pushed the |
cost-model/src/cost_model.rs
Outdated
@@ -624,45 +616,6 @@ mod tests { | |||
assert_eq!(1, data_bytes_cost); | |||
} | |||
|
|||
#[test] | |||
fn test_cost_model_with_failed_compute_budget_transaction() { |
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 think we can keep this test but change it so that there is a compute budget ix that fails in sanitize_and_convert_to_compute_budget_limits
rather than in ComputeBudgetInstructionDetails::try_from
since any errors in try_from
would be caught earlier when RuntimeTransaction
is created. Also, shouldn't get_transaction_cost
be updated to use a runtime tx to avoid reprocessing compute budget ixs?
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.
Added this test back as you described - ended up just updating cost-model code to use RuntimeTransaction
as well to make it so I don't need to update the test again next PR
@@ -24,7 +24,10 @@ use { | |||
transaction_batch::TransactionBatch, | |||
verify_precompiles::verify_precompiles, | |||
}, | |||
solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, | |||
solana_runtime_transaction::{ |
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.
Is there a reason that execute_and_commit_transactions_locked
is recomputing the compute budget details still rather than using them from runtime tx?
solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, | ||
solana_runtime_transaction::{ | ||
instructions_processor::process_compute_budget_instructions, | ||
runtime_transaction::RuntimeTransaction, |
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.
Also how come check_fee_payer_unlocked
isn't using runtime tx now?
address_loader, | ||
reserved_account_keys, | ||
) | ||
}) |
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.
Sounds fine to leave as is but was also wondering about if we create and store a RuntimeTransaction<SanitizedVersionedTransaction>
inside ImmutableDeserializedPacket::new
and then add a new RuntimeTransaction<SanitizedTransaction>::try_from_ref
function which takes a &RuntimeTransaction::<SanitizedVersionedTransaction>
and does the clone internally?
@@ -28,7 +28,10 @@ use { | |||
solana_cost_model::cost_model::CostModel, | |||
solana_measure::measure_us, | |||
solana_runtime::{bank::Bank, bank_forks::BankForks}, | |||
solana_runtime_transaction::instructions_processor::process_compute_budget_instructions, | |||
solana_runtime_transaction::{ |
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 could eliminate one compute budget calculation by using the compute budget details inside runtime tx inside buffer_packets
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 don't think it's very ergonomic to not implement clone for runtime tx, especially for tests. I would be in favor of adding a clone impl generally but can we at the very least add a dcou impl?
#[cfg(feature = "dev-context-only-utils")]
impl<T> Clone for RuntimeTransaction<T>
where
T: Clone,
{
fn clone(&self) -> Self {
Self {
transaction: self.transaction.clone(),
meta: self.meta.clone(),
}
}
}
/// that are required by some outside interfaces. | ||
/// Eventually this trait should go away, but for now it is necessary. | ||
pub trait SVMTransactionAdapter: SVMTransaction { | ||
fn as_sanitized_transaction(&self) -> impl Borrow<SanitizedTransaction>; |
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 think it makes more sense to have a to_sanitized_transaction
method which does the clone internally since we only really need this adapter method for clones. The commit_transactions
call-site for geyser can use deref
directly
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.
commit_transactions
is where we actually need/want this as borrow.
Because we need to have a SanitizedTransaction
and if we don't we need to convert to one - it can't just be deref once we are generic.
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.
but doing the borrow there also let's us not clone the the SanitizedTransaction if that's what we already have
Problem
RuntimeTransaction
helps us in multiple ways:Summary of Changes
RuntimeTransaction
wherever required by forcing it inTransactionBatch
RuntimeTransaction
implementsDeref
for the wrapped transaction type, so in many places this does not require any changes in function implementationTransactionBatch
to useRuntimeTransaction
. This makes the type available wherever we may need it in our processing pipeline, and we can optimize some of the processing to use the meta in the pipeline in subsequent PRs.Fixes #