-
Notifications
You must be signed in to change notification settings - Fork 11.4k
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
[0/n][transfer-to-object] Transfer to Object Implementation #12611
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
7 Ignored Deployments
|
crates/sui-types/src/execution.rs
Outdated
@@ -205,6 +218,8 @@ impl Value { | |||
}, | |||
_, | |||
) => *used_in_non_entry_move_call, | |||
// TODO(tzakian): Need to figure this out. |
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 only thing you can do with a Receiver<T>
is consume it to receive the object, right? So it doesn't seem possible to use the one in a non-entry move call and then have the value live to be queried about it, which implies false
is the correct return value here.
sui-execution/latest/sui-adapter/src/programmable_transactions/context.rs
Outdated
Show resolved
Hide resolved
sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
fn receiving_objects(&self) -> Vec<ObjectRef> { |
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.
What's the reason to not include the Receiving in the input objects with a new InputObjectKind, and handle this new kind differently when needed? Asking because I believe there are much more similarities between input objects and receiving objects, than differences.
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.
My first crack at implementing this is exactly as you describe (received objects are returned as input objects with a new InputObjectKind
). That direction is definitely workable I believe, however what I encountered when doing that was there were a lot of places where InputObjectKinds
flow where Receiving
objects make no sense (e.g., in checking or loading object arguments to a transaction). So this led to a bunch of unreachable!
/invariant_violation
code. @tnowacki had some opinions on this I believe as well (or maybe I'm misremembering).
Moving to separating them out seemed like a cleaner option since it led to significantly less unreachable/invariant_violation code paths and (IMO) seemed to fit more. I actually view Receiving
objects as more akin to dynamic fields than input objects other than the addition of dependencies, but I'm more than happy to also prototype treating them as input objects some more and we can take a look at both options and decide if you'd like :)
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 the main thing with not treating Receiving
objects as input objects is there are also a number of places where they need to be treated differently from other input objects, especially around the fact that we (1) don't lock them like we do with other objects, and (2) we don't check their existence prior to execution -- in this way they're more akin to dynamic fields with a specified version than an input object. Additionally at the execution layer, Receiving
objects and other input objects differ significantly in terms of how they should be treated and checked.
Because of this the worry is that if we treat Receiving
objects as input objects, they could accidentally leak to someplace they should and we might, e.g., accidentally grab a lock on them (or try to load them etc.) when we shouldn't. Treating them separately makes this much less likely IMO.
@@ -68,9 +68,11 @@ pub async fn check_transaction_input( | |||
transaction.check_version_supported(epoch_store.protocol_config())?; | |||
transaction.validity_check(epoch_store.protocol_config())?; | |||
let input_objects = transaction.input_objects()?; | |||
let receiving_objects = transaction.receiving_objects()?; |
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.
There are several similar functions in this file that checks input for different kinds of transactions. It's an unfortunate duplication of code but it does mean we have to handle receiving objects in all those places. This further backs my question regarding whether we should include receiving objects in input objects, otherwise you would have to chase down all calls to input_objects().
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.
My understanding is that receiving objects can only be used for PTBs (outside of those they don't make any sense I believe). So I don't believe we need to handle these separate apart from possibly checking that the set of receiving_objects
is empty in that case.
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.
Not sure if I understand, but check_transaction_input_with_given_gas
and check_dev_inspect_input
could be called on PTBs as well?
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.
ahh correct, these will need to be added there as well.
c9eb4ee
to
bbb4c95
Compare
InboxRef
transaction argument type…n and transfer-to-object (#12852) ## Description This adds a new table to the ``PerpetualTables` that holds the set of received and shared deleted objects during the epoch. This table is not currently used anywhere and is unused. However, both shared-object deletion #12623 and transfer-to-object #12611 PRs rely on this table. This table can be pruned at epoch boundaries so the table key contains the epoch ID. @laura-makdah and I have discussed and we're happy to either land this soon, or to wait until one or the other above PRs is ready to land and land this change at the same time. Either way this change on its own should be fine to land any time as the new table is not used anywhere yet. ## Test Plan Not tested yet -- functionality will be tested in the two PRs being built on top of this. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [ ] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes
84c9e1f
to
b889c94
Compare
…n and transfer-to-object (#12852) ## Description This adds a new table to the ``PerpetualTables` that holds the set of received and shared deleted objects during the epoch. This table is not currently used anywhere and is unused. However, both shared-object deletion #12623 and transfer-to-object #12611 PRs rely on this table. This table can be pruned at epoch boundaries so the table key contains the epoch ID. @laura-makdah and I have discussed and we're happy to either land this soon, or to wait until one or the other above PRs is ready to land and land this change at the same time. Either way this change on its own should be fine to land any time as the new table is not used anywhere yet. ## Test Plan Not tested yet -- functionality will be tested in the two PRs being built on top of this. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [ ] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes
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.
Continuing to review, but giving feedback as immediately as I can. So expect some more comments over the afternoon here. But the receiving value should not need to use global value, because we are protecting the loading by the uniqueness of the Receiving value
@@ -1216,6 +1222,7 @@ fn load_object_arg<'vm, 'state>( | |||
/* imm override */ !mutable, | |||
id, | |||
), | |||
ObjectArg::Receiving(_) => invariant_violation!("Impossible to hit Receiving in v0"), |
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.
How do these things work? Should we just panic as we will otherwise fork? Not sure I have personally made such a decision yet, so mostly wondering what is standard for myself :)
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.
Same! I think panicking might be the best call here since that would be better. @amnn any thoughts on this?
let Some((tag, layout, annotated_layout)) = get_tag_and_layouts(context, &child_ty)? else { | ||
return Ok(NativeResult::err( | ||
context.gas_used(), | ||
E_BCS_SERIALIZATION_FAILURE, |
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 this not an invariant violation?
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 was going off of and replicating similar error handling as in the get_or_fetch_object
macro. But happy to make it an invariant violation (storage error).
sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs
Outdated
Show resolved
Hide resolved
sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs
Show resolved
Hide resolved
sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs
Show resolved
Hide resolved
// We don't count receiving object arguments as something that needs to be written | ||
// unless it has actually been received -- this is tracked in the `received` field of | ||
// the object store. | ||
if let InputObjectMetadata::Receiving { .. } = &object_metadata { return 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.
Shouldn't we early return above if we are in this case? Then we shouldn't need to 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.
(you might need to copy out the owner/id fields again but should be easy enough
let old_value = value_opt.replace(Value::Receiving(*id, *version)); | ||
// If the value is receiving and it's being restored it must have been taken since | ||
// the `Receiving` struct is not copy. | ||
assert_invariant!( |
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.
restore is used to implement borrow_mut, so I think we will be able to hit this case
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 we're able to hit this case since it doesn't have copy
-- if we mutably borrow the value will be taken and then restored so old_value
should always be none
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.
Ah, I see. Makes sense (just be sure to test it ;))
.../sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.mvir
Outdated
Show resolved
Hide resolved
CallArg::Object(o) => match o { | ||
ObjectArg::ImmOrOwnedObject(_) => vec![], | ||
ObjectArg::SharedObject { .. } => vec![], | ||
ObjectArg::Receiving(obj_ref) => vec![*obj_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.
Why is this a vec instead of just an option?
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.
Don't have a super string opinion here, but I used a vec instead of an option here to keep consistency with the other interfaces around receiving objects, and input objects on PTB/PTB components. In particular both input_objects
and with other implementations of receiving_objects
on other parts of the PTB.
860849c
to
ae242b4
Compare
ae242b4
to
a7c90d9
Compare
a7c90d9
to
7128bab
Compare
public entry fun pass_through(x: Receiving<B>): Receiving<B> { x } | ||
|
||
public entry fun pass_through_a(x: Receiving<A>): Receiving<A> { x } | ||
|
||
public entry fun pass_through_ref_a(_x: &Receiving<A>) { } | ||
|
||
public entry fun pass_through_mut_ref_a(_x: &mut Receiving<A>) { } |
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.
Did we end up fixing the type on first usage? Or first mutable usage?
If the latter, I have some more tests for you :)
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 did windup fixing the type on first usage!
task 10 'run'. lines 61-61: | ||
Error: Error checking transaction input objects: IncorrectUserSignature { error: "Object 0xd38c9db0c1935b185ec59bacb9e6ce2e736579cedabb791fce9a8165cef6c319 is owned by account address 0xa0027b0c13d66e2958feb9c56af104ea20ddfcb46a3ea93b9ba5d3c2d9b94d42, but given owner/signer address is 0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" } |
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.
Why do we get this error for this one?
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 an object-owned object that I am trying to pass-in as a normal object to the transaction, so the transaction input checks are going to complain about this (with this error).
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.
Question: when a transaction receives an object, and deletes (or wraps) it in the same transaction, which code ensures that it's marked as modified?
@@ -207,6 +209,14 @@ pub mod checked { | |||
self.gas_status.charge_publish_package(size) | |||
} | |||
|
|||
pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> { |
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.
Seems unrelated code?
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.
Bad rebase from @dariorussi's fix maybe?
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 vm_rework
branch was cut before Dario's fix, so when I rebased the changes from latest
into vm_rework
it pulled in the changes to latest
with it. So I think this is fine/to be expected?
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.
Happy to not replicate it over, but it feels kinda weird to not just do a full execution_layer.py rebase
to replicate the changes over.
Good question, this is done by marking every received object as mutated when we finish off the Edit: apparently github doesn't support sending lines to a PR, even though it seems like it should... here's a link to the line in question that should work:
|
@@ -531,8 +575,10 @@ impl ObjectRuntimeState { | |||
(id, (owner, type_, value)) | |||
}) | |||
.collect(); | |||
for deleted_id in deleted_ids.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.
Woooo I don't think we can delete this yet. This is required at least prior to @tnowacki's fix on the recent child object 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.
Good catch! Must have happened in a bad rebase...I'll add this back in!
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.
There were no tests that caught this? would it be possible to add one?
7128bab
to
f225f58
Compare
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.
Overall LGTM on the core changes
@@ -537,6 +587,13 @@ impl ObjectRuntimeState { | |||
} | |||
} | |||
|
|||
// Any received objects are viewed as modified. | |||
for (received_object, _) in received.into_iter() { | |||
if let Some(loaded_child) = loaded_child_objects.get_mut(&received_object) { |
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.
Should it be an invariant violation if it's not loaded?
if has_key { | ||
versioned_results.push((*idx, true)) | ||
} else if receiving_objects.contains(input_key) { | ||
// If there's a more recent version of this object, then it was mutated by someone |
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 understand this - the comment here implies that this branch only enters if there was a more recent version of the object. But I don't see why has_key
can't also be true when a more recent version exists.
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 call out -- I'll update the comments to clarify.
You're correct that has_key
could also be true if a more recent version exists (and that's fine).
What I really should say here (and I'll correct to this) is that has_key
can also return false if a more recent version exists in the case where the object at the specified version has been pruned. In that case we need to "look up" to see if a more recent version of that object has been exists or has been deleted.
@@ -1884,6 +2046,35 @@ impl ChildObjectResolver for AuthorityStore { | |||
} | |||
Ok(Some(child_object)) | |||
} | |||
|
|||
fn get_object_received_at_version( |
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.
where is this actually called from?
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.
All of the calls from the object runtime to receive an object call this method. E.g.,
.get_object_received_at_version(&owner, &child, version, self.current_epoch_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.
omg I was grepping in /crates
so I didn't see those.
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've been there...more times than I'd care to admit
// If the receiving object is in the loaded runtime objects, then that means that it | ||
// was actually successfully loaded (so existed, and there was authenticated mutable | ||
// access to it). So we insert the previous transaction as a dependency. |
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.
Why? This just seems correct?
if let Some(obj_meta) = self.loaded_runtime_objects.get(id) { | ||
// Check that the expected version, digest, and owner match the loaded version, | ||
// digest, and owner. If they don't then don't register a dependency. | ||
// This is because this could be "spoofed" by loading a dynamic object field. |
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.
can you explain this spoofing? shouldn't an attempt to receive a child object fail because the object will be object owned?
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.
Basically, due to the way dynamic object fields work, you can have the following sequence of certificates and executions:
- Object
O
at versionn
with object idoid
is sent to a shared objectS
- Tx1 creates a cert to receive
O
at versionn
but never actually tries to receiveO
. BUT Tx1 loads a dynamic object fieldF
that will reference this object in the future. (Tx1 could not be executed successfully right now). - Tx2 creates a cert, and executes a transaction moving
O
to the dynamic object fieldF
.O
now has anObjectOwner()
ownership, but importantly the same object idoid
. - Tx1 executes. This will load
O
via the dynamic object field at a different versionn + k
(k > 0
). Because of thisoid
will be in theloaded_runtime_objects
but at a different version. - If we don't have this check that the object in the loaded runtime object metadata was actually received at the specified version (and I added the other checks just to be doubly sure) then we could register a transaction dependency that actually didn't exist. Not a massive deal, but also not quite correct either.
A test that demonstrates this is here:
async fn receive_and_dof_interleave() { |
…13896) ## Description This adds support for receiving transaction argument types to json-rpc ## Test Plan Tested by PRs above this. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [X] protocol change - [X] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Release notes included in base PR.
Description
This PR implements the core transfer-to-object functionality. In particular it implements the ability to "receive" an object that was sent to the address (object ID) of another object using one of the
transfer
ortransfer_public
functions in thetransfer
module.More detail is given on the programming model in the attached issue so I will not go into that.
SDK support for receiving objects has been added in the two PRs stacked on this one:
Receiving
type to the json-rpc types, and adds support receiving objects in the Typescript SDK.per_epoch_object_marker
table at epoch boundariesTest Plan
I've written a number of tests for this that I believe cover things:
transfer_to_object.rs
tests (e.g., receive-then-unwrap, receive-unwrap-wrap, etc).Receiving
argument is not locked at signing) in the sui-coretransfer_to_object.rs
testsReceiving
arguments to transactions (see new tests in thetransaction_manager_tests.rs
file).A more detailed listing of the tests:
receive_return_object_dont_touch.move
]receive_return_object_then_transfer.move
]basic_receive.move
]duplicate_receive_argument.move
]Receiving
argument, then later use it in PTB.pass_receiver_immut_then_reuse.move
]pass_receiver_mut_then_reuse.move
]pass_through_then_receive.move
]receive_by_ref.move
](checking borrow/borrow_mut, and restore rules for PTB execution)
receive_invalid_type.move
]receive_object_owner.move
]take_receiver_then_try_to_reuse.move
]receive_invalid_param_ty.move
]receive_dof_and_mutate.move
]receive_add_dof_and_mutate.move
]receive_add_dof_and_mutate.move
]receive_and_send_back.move
]receive_and_deleted.move
]receive_and_wrap.move
]receive_and_abort.move
]receive_by_value_flow_through.move
]receive_multiple_times_in_row.move
]shared_parent/basic_receive.move
]shared_parent/transfer_then_share.move
]shared_parent/drop_receiving.move
]shared_parent/receive_dof_and_mutate.move
]shared_parent/receive_multiple_times_in_row.move
]object. This is tested in [
mvcc/receive_object_dof.move
] and[
mvcc/receive_object_split_changes_dof.move
]transfer_to_object_tests.rs/test_tto_unwrap_transfer
]transfer_to_object_tests.rs/test_tto_unwrap_delete
]transfer_to_object_tests.rs/test_tto_unwrap_add_as_dynamic_field
]transfer_to_object_tests/test_tto_transfer
]transfer_to_object_tests/test_tto_unused_receiver
]transfer_to_object_tests/test_tto_pass_receiving_by_refs
]transfer_to_object_tests/test_tto_delete
]transfer_to_object_tests/test_tto_wrap
]orders of execution for two certs that want to receive the same argument
(but only one is valid) can both be run in either order, and both return
the same execution effects in either order
[
transfer_to_object_tests/test_tt_not_locked
]successfully and receive the object
[
transfer_to_object_tests/test_tto_valid_dependencies
][
transfer_to_object_tests/test_tto_valid_dependencies_delete_on_receive
]the object
[
transfer_to_object_tests/test_tto_dependencies_dont_receive
]the object and we abort
[
transfer_to_object_tests/test_tto_dependencies_dont_receive_but_abort
]then went on to abort in the transaction
[
transfer_to_object_tests/test_tto_dependencies_receive_and_abort
]dynamic object field load of an object that we want to receive at a
different version as a receivership of that object (i.e., don't register
the transaction dependendency)
[
transfer_to_object_tests/receive_and_dof_interleave
]Additional tests
PTBs
MakeMoveVec
:Type mismatches:
TransferObjects
SplitCoins
MergeCoins
MVCC [
receive_object_access_through_parent[dof/df].move
]Transaction input checks (in sui-core tests)
test_tto_not_locked
in the sui-core testsinput_objects \intersect receiving_object != {}
[test_tto_intersection_input_and_receiving_objects
]Type-fixing for receiving arguments [pt_receive_type_fixing.move]
If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process.
Type of Change (Check all that apply)
Release notes
Added the ability to receive objects off of another object. This is currently only turned on in devnet. More information on transfer-to-object, and receiving objects off of other objects can be found in the GitHub issue on it which can be found here: #12658