-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Implemented update of balance_root
and state_root
for inputs and outputs
#789
Conversation
return Err(Error::InvalidTransactionOutcome { | ||
transaction_id: tx.id(), | ||
}) | ||
// TODO: Maybe we need move it to `fuel-vm`? O_o Because other `Outputs` are processed there |
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'm unsure about this part. fuel-vm
already updates outputs for all utxos. Maybe better to move it there or extract all updates 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.
fuel-vm could set the balance and state roots, but it doesn't manage utxo id's or utxo state directly. This is mainly to enable the client to do bulk optimization for utxo lookups and validation across many txs at once in parallel.
edit: however in the case of contract output utxo ids, the vm should be able to calculate those as well without interacting with any db state, so moving this to the VM may be the right call (and would probably make fraud proof setup easier).
.outputs_mut() | ||
.last_mut() | ||
{ | ||
*to = Address::try_from(contract_id.as_ref()).unwrap(); |
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.
@Voxelot I added this test but not sure what our expectation is. Maybe we want to reject transactions that try sending tokens to the contract via outputs(not the script body).
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.
Hmm yeah, we could do this but it would add extra overhead to every transfer to check if the output address is a contract id.
cc @adlerjohn
/// The wrapper around `contract_id` to simplify work with `Contract` in the database. | ||
pub struct ContractRef<'a, Database> { | ||
database: Database, | ||
contract_id: &'a ContractId, |
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.
do we really need to lifetime reference this? the ContractId implements copy and should be fairly cheap to own 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.
I added this type to simplify usage without affecting performance. Because the type is public and used inside of another crate, the compiler will not optimize usage of this struct, and it will add a smaaaaaaaaaaaaaall overhead(to copy 32 bytes)=)
If this lifetime causes pain to your eyes, I will remove it=D
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.
While copying does incur a small overhead, dereferencing is also not free from a cache standpoint. I also think the simpler struct definition we get may be worth the cost of copying the Bytes32 .
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 know why we are afraid of lifetimes=) But if two people don't like it, I will replace it with four copies.
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 know it's more efficient to use the lifetimes, but my preference is to try and keep the code as simple as possible until it's proven to be a bottleneck.
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.
Rather than a question of lifetimes, I think (if I'm understanding Brandon's original comment correctly) it's more of a question about when to use references vs. when to use copies. Personally, I think IDs (similar to ints) are something you should be able to copy without much fear of overhead. But I can also understand that in a performance-critical application, avoiding copies is a good general goal. (But there is the potential cost of accumulating cache misses if this is in any sort of loop!)
c69eee9
to
d2a9dd0
Compare
bac9d86
to
17b3214
Compare
fuel-core/src/executor.rs
Outdated
let expected_utxo_id = |contract: &ContractRef<&mut Database>| { | ||
let maybe_utxo_id = contract.utxo()?.map(|utxo| utxo.into_owned()); | ||
let expected_utxo_id = if self.config.utxo_validation { | ||
maybe_utxo_id | ||
.ok_or(Error::ContractUtxoMissing(*contract_id))? | ||
.into_owned() | ||
.ok_or_else(|| Error::ContractUtxoMissing(*contract.contract_id()))? | ||
} else { | ||
maybe_utxo_id.unwrap_or_default().into_owned() | ||
maybe_utxo_id.unwrap_or_default() | ||
}; | ||
Result::<_, Error>::Ok(expected_utxo_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.
Instead of a lambda, can expected_utxo_id
be replaced by a getter on the ContractRef
interface? If that is practical, I think it would make the readability a bit better and consistent:
let mut contract = ContractRef::new(&mut *db, contract_id);
*utxo_id = contract.utxo_id()?;
*balance_root = contract.balance_root()?;
*state_root = contract.state_root()?;
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 tried to avoid any business logic inside of those wrappers=)
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.
Good point. I am not strongly opinionated either way. As usual with software, there are pros and cons to either approach!
@@ -2449,6 +2574,281 @@ mod tests { | |||
assert_eq!(coin.status, CoinStatus::Spent); | |||
} | |||
|
|||
#[test] |
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.
Just an aside comment: now that we have so many lines of tests in this file, it may be worth extracting the test suite to its own file.
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 will, but in a separate PR. executor.rs
should live in the fuel-executor
, and during this refactoring, we will definitely split the logic across several files and the same with tests.
fuel-core/src/executor.rs
Outdated
// Needed to convince the compiler that tx is taken by ref here | ||
#[allow(clippy::needless_borrow)] |
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.
Does this clippy annotation still apply?
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 remove
fuel-core/src/executor.rs
Outdated
*header.height(), | ||
&tx_id, | ||
tx_db_transaction.deref_mut(), | ||
vm_result.tx().inputs(), | ||
vm_result.tx().outputs(), | ||
)?; | ||
self.compute_outputs( |
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.
It looks like we're persisting the outputs before we compute their state/balance roots etc. It seems like this should've been caught in tests if we're checking that the balance / state roots on contract outputs are set and persisted correctly 🤔 .
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 have a test but without storage check=( persist_output_utxos
persist only outputs related to utxo=) It is the expected ordering of methods. Added a comment to make it clear
} = executor.execute(ExecutionBlock::Production(block)).unwrap(); | ||
|
||
let empty_state = Bytes32::from(*empty_sum()); | ||
let executed_tx = block.transactions()[2].as_script().unwrap(); |
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 should also be testing:
- that the tx in the db matches the tx in the block
- that utxos in the db match the expected state, since those are distinct from the output types on the tx
related: https://github.com/FuelLabs/fuel-core/pull/789/files#r1040374277
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.
blocking the previous approval due to bug in utxo persistence
Added check of the storage into unit test. Added comment to highlight the ordering of operations.
Ahh that's right, these output calculations aren't persisted to the utxo state set. They are just metadata on the tx itself. Thanks for clarifying. |
@@ -50,6 +51,14 @@ pub struct ExecutionResult { | |||
/// The list of skipped transactions with corresponding errors. Those transactions were | |||
/// not included in the block and didn't affect the state of the blockchain. | |||
pub skipped_transactions: Vec<(Transaction, Error)>, | |||
/// The status of the transactions execution included into the block. |
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.
[nit] I think this sentence might need to be adjusted along the lines of "The status of executed transactions included in the block"
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
The change actualizes the
balance_root
andstate_root
for inputs before execution and outputs after execution. It only preparation of the code to do those updates with the current dummy algorithm. The correct algorithm for state calculation should be implemented in #627 and #626.Also, it is preparation for updating
tx_pointer
. Because it requires an additional field in the database, it should be done in a separate PR.Minor changes:
refs
folder in thefuel-executor
with a wrapper to minimize the amount of code.tx_status
to theExecutionResult
, preparation for the Move writing transaction status db writing into txpool and add full status to subscription #772 and more advanced testing.tx_pool::TransactionStatus
tofuel-core-interfaces
.CC @bvrooman This change should answer your question in the related issues.