-
Notifications
You must be signed in to change notification settings - Fork 11.2k
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
[fastx dist sys] Connect vm adapter to handle-confirmation logic #62
Conversation
fastpay_core/src/authority.rs
Outdated
type Error = FastPayError; | ||
|
||
fn get_resource( | ||
&self, | ||
address: &AccountAddress, | ||
struct_tag: &StructTag, | ||
) -> Result<Option<Vec<u8>>, Self::Error> { | ||
match self.objects.get(address) { | ||
match self.authority_state.objects.lock().unwrap().get(address) { |
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.
Help! @sblackshear
Am I correct to think here either (1) a resource is listed in the input objects or (2) the resource is read-only. If this is the case, I will modify the read here to read from the 'AuthorityTemporaryStore' objects first, and then the global store (but check the read_only).
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 for the module lookup, but modules are always read-only so there this is invariant is trivial.
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 question. Actually, get_resource
is only called when the Move VM touches Move global state, which we don't allow in fastX (https://github.com/MystenLabs/fastnft/blob/main/fastx_programmability/verifier/src/global_storage_access_verifier.rs is the mechanism for preventing this). So I think raising an Err
corresponding to an internal invariant violation is probably the thing to do for now?
In the future, if we allow dynamic accesses of read-only objects, I think we would want to support it exactly as you suggest.
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.
Excited for this!
} | ||
|
||
// If it exists remove it | ||
if let Some(removed) = self.objects.remove(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.
I think raising an internal invariant violation if we try to remove something nonexistent would make sense
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.
Right now I panic if it does not exist.
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 the panic? I think this will silently fall through to 467 if id
does not exist in objects
(which is the case I'm concerned about).
} | ||
|
||
// If it exists remove it | ||
if let Some(removed) = self.objects.remove(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.
Where is the panic? I think this will silently fall through to 467 if id
does not exist in objects
(which is the case I'm concerned about).
// mutate objects if they are &mut so they cannot be read-only. | ||
panic!("Internal invariant violation: Mutating a read-only object.") | ||
} | ||
self.written.push(object.to_object_reference()); |
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 checking no duplicates (either here or at into_inner
) is a useful sanity check.
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 will stick this behind a test conditional config.
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.
Done: check for duplicate write, duplicate delete, duplicates across write & delete, and the number of writes+delete is equal to the mutable inputs.
fastpay_core/src/authority.rs
Outdated
@@ -460,26 +481,102 @@ impl AuthorityState { | |||
} | |||
} | |||
|
|||
impl Storage for AuthorityState { | |||
pub struct AuthorityTemporaryStore<'a> { | |||
pub authority_state: &'a AuthorityState, |
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 would be in favor of removing the pub
from each of these fields + exposing getters for any that we want to allow folks to read. There are invariants about the relationship between fields like objects
and deleted
that are enforced by the functions exposed, but could be violated by someone accessing these fields 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.
Please have a look at #[readonly::make]
: https://github.com/dtolnay/readonly
George: None of them have to be pub, so all good.
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.
Totally, done. When I build a struct i start with pub members, otherwise the type system complains they are not used, and as a result I forget them as pub. My bad.
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 main Q on read-only violations as a non-recoverable error.
fastpay_core/src/authority.rs
Outdated
@@ -460,26 +481,102 @@ impl AuthorityState { | |||
} | |||
} | |||
|
|||
impl Storage for AuthorityState { | |||
pub struct AuthorityTemporaryStore<'a> { | |||
pub authority_state: &'a AuthorityState, |
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.
Please have a look at #[readonly::make]
: https://github.com/dtolnay/readonly
George: None of them have to be pub, so all good.
"); | ||
Ok(Some(m.contents.clone())) | ||
} | ||
other => unimplemented!( |
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 it possible that a #[non_exhaustive]
attribute is missing somewhere in Data
?
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.
Here @sblackshear may have a view, as he designed the 'Data' abstraction?
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 not know about this feature, very cool! Yes, would make sense to add this on Data
fastpay_core/src/authority.rs
Outdated
if object.is_read_only() { | ||
// This is an internal invariant violation. Move only allows us to | ||
// mutate objects if they are &mut so they cannot be read-only. | ||
panic!("Internal invariant violation: Mutating a read-only 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.
What's the value in making this (and other places) an irrecoverable panic versus returning a Result
, or some other indication of success / failure of the operation? It seems that writing to a read-only object might be a common mistake?
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.
Right now we are restricted in terms of interface to what the movevm expects from a Storage I believe, so I have to follow the signatures that return no Result. At the same time I would rather check some of these invariants.
Note these are internal invariants, if the move adaptor is implemented correctly they should never trigger. Let me stick them behind a test comp flag to only use when refactoring, but not in prod -- another reason to not change the signatures.
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 concern for an object being or not being read-only is everywhere in the code at this stage. I'd be comfortable with a debug_assert, which tells me this should never fire outside egregious misuse, or asking the move VM to change its APIs, whichever suits you best.
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.
- Although this is an internal invariant violation, I think should eventually be a
Result
so we can propagate it up to someone that can log it. - This code is inside
Storage::write_object(&self, Object)
, which is a FastNFT trait under our control. I think that API should indeed be changed to useResult
everywhere, but perhaps in a separate PR. This won't cause any problems for Move--it will just need to handle theResult
inside the adapter.
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 ok, I was confused about the provenance of Storage. Lets indeed make it fire results, but I also think we should not be spending runtime resources checking for errors in our code, again and again.
15eb9f9
to
a818221
Compare
fn delete_object(&mut self, id: &ObjectID) { | ||
self.objects.remove(id); | ||
// Check it is not read-only | ||
#[cfg(test)] // Movevm should ensure this |
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.
@huitseeker: thoughts about cfg(test)
vs debug_assert
vs other options for assertions we want to fire everywhere except in release builds?
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.
assertions we want to fire everywhere except in release builds?
Not #[cfg(test)]
, that only fires in testing.
debug_assert
is made for this.
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.
Ok, thanks @sblackshear & @huitseeker for all the feedback:
- I have now moved the checks to a separate function.
- I have used the HashSet pattern to keep code short.
- I have improved the check to ensure all mutable objects are either written or deleted.
- As a result I caught a bug (we were not updating gas object in module upload) and fixed 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.
Hopefully I've left comments that can improve at least the quadratic duplicate checks.
fastpay_core/src/authority.rs
Outdated
for (i, write_ref) in self.written.iter().enumerate() { | ||
for (j, write_ref2) in self.written.iter().enumerate() { | ||
if i != j && write_ref == write_ref2 { | ||
panic!("Invariant violation: duplicate writing."); | ||
} | ||
} |
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 :
debug_assert!({
let mut used = HashSet::new();
self.written.all(move |elt| used.insert(elt))
})
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.
Fixed, see above
fastpay_core/src/authority.rs
Outdated
for (k, del_ref) in self.deleted.iter().enumerate() { | ||
for (l, del_ref2) in self.deleted.iter().enumerate() { | ||
if k != l && del_ref == del_ref2 { | ||
panic!("Invariant violation: duplicate deletion."); | ||
} | ||
} | ||
} |
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.
See above for refactoring a uniqueness check to linear time with a HashSet
.
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.
Fixed, see above
fastpay_core/src/authority.rs
Outdated
for del_ref in &self.deleted { | ||
if write_ref == del_ref { | ||
panic!("Invariant violation: both writing and deleting same 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.
You could just have a list of distinct object refs and a bitmap or compressed bitmap for the deleted ones, with the rest being written.
But if you happen to like this two-vectors approach, ideally, you'd be able to reuse the HashSet
s you're building above and below to make sure that before inserting something in the second HashSet
, you're checking for non-presence in the prior 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.
I kept the two vector approach but refactored the test using HashSets. Fixed, see above.
fastpay_core/src/authority.rs
Outdated
// Check a number of invariants. A correct vm calling the struct should ensure these | ||
// but to help with testing and refactoring we check again 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.
Please export this invariant-checking to a function.
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.
Yep, now in a separate function. Fixed, see above
fastpay_core/src/authority.rs
Outdated
Vec<ObjectRef>, | ||
Vec<ObjectRef>, | ||
) { | ||
#[cfg(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.
This only fires in testing, I suspect you mean debug_assert
.
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.
Thanks for this -- now all behind debug_assert
54e633b
to
bec75c2
Compare
Hey @huitseeker - do a check and then we can land this? |
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: Ah, ok! I see better why you were looking at a config flag now.
The PR is OK in its current state, but here's what I think you were trying to do: using #[cfg(debug_assertions)]
You could make your checking function check_invariants
:
- not use
debug_assert
itself, - but return a boolean and an optional message instead
- then in the main function you would:
#[cfg(debug_assertions)]
{
let (ok, msg) = check_invariants();
debug_assert!(ok, "{}", msg);
}
The advantage being that absolutely nothing runs if you're not in debug mode, not even the function call.
Nice one. I used a simpler variant of the above to avoid the call. (I suspect the compiler already avoided the call, but to be safe). |
This PR finishes issue #8 to support transactions with multiple inputs and outputs, and a connection to the real vm adapter.
Specifically: