-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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] Refactor authority to introduce an order lock and cetificates store #12
Conversation
/// States of fastnft objects | ||
objects: BTreeMap<ObjectID, ObjectState>, | ||
/// Order lock map maps object versions to the first next transaction seen | ||
order_lock: BTreeMap<(ObjectID, SequenceNumber), Option<SignedTransferOrder>>, |
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 use do I have for two pointers the transactions bringing about two different versions of a same object?
accounts, now named objects store. - Renamed accounts field to objects - Rename account_state to object_state - Defined order_lock structure and helper functions - Added order lock logic to authority handlers - Fixed authoirty tests
- Add certificated field and helper functions - Rename confirmation_log to legacy, and added add_cert call.
- Removed legacy pending_order and replaced it with order_locks - Clean up authority helpers and handlers, and document
be6014d
to
99689c2
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.
Good progress, but I had a few questions / suggestions.
fastpay_core/src/authority.rs
Outdated
object_id: ObjectID, | ||
seq: SequenceNumber, |
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.
If you're going to do this, you're inviting multiple clones of the ObjectID
, SequenceNumber
objects to pair them up before passing a reference to the pair (as you do here with contains_key
and get
.
There's two options :
- live with that and make
ObjectID
,SequenceNumber
implementCopy
- require the pair in the API of your functions (or, more to the point of parcimonious copy, a reference to it) wherever you need one. That way the caller has a chance of creating the pair once and reusing it many times, rather than having every callee act as a pair-builder.
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, I am now using a type ObjectRef
and &ObjectRef
to refer to objects, and we already have a to_object_reference
on object state to get these.
fastpay_core/src/authority.rs
Outdated
pub fn archive_order_lock(&mut self, object_id: ObjectID, seq: SequenceNumber) { | ||
// Note: for the moment just delete the local lock, | ||
// here we can log or write to longer term store. | ||
self.order_lock.remove(&(object_id, seq)); | ||
} |
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 would be great to have a basic unit test for the lock set / get / archive lifecycle, or at least an issue on writing 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.
For the moment I am including a check in the existing tests.
// Check locks are set and archived correctly
assert!(authority_state.get_order_lock(&(object_id, 0.into())).is_err());
assert!(authority_state.get_order_lock(&(object_id, 1.into())).expect("Exists").is_none());
// The lock is None, so we replace it with the given order. | ||
lock.replace(signed_order); |
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 use case for this (objID, seq)
lock to be populated with a None
? Are you looking for a Map
with default, i.e. this (replacing FooMap
with your current favorite):
use std::{borrow::Borrow, collections::FooMap, hash::Hash};
pub struct DefaultFooMap<K, V> {
map: FooMap<K, V>,
}
impl<K, V> DefaultFooMap<K, V>
where
V: Default,
{
pub fn new() -> Self {
DefaultFooMap { map: FooMap::new() }
}
pub fn get_mut<Q>(&mut self, v: &Q) -> &mut V
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.map.entry(v).or_default()
}
pub fn get<Q>(&self, v: &Q) -> &V
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.map.get(v).unwrap_or_default()
}
}
I'll leave the customizable default (not reliant on the Default
trait) as an exercise to the reader, depends on what you actually need.
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, I am leaving an extensive comment about the state of order_locks
, which ascribes meaning to the key not existing, the key existing and the value being None, and the key existing and the value being some.
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.
/* Order lock states and invariants
Each object in `objects` needs to have an entry in `order_locks`
that is initially initialized to None. This indicates we have not
seen any valid transactions mutating this object. Once we see the
first valid transaction the lock is set to Some(SignedOrder). We
will never change this lock to a different value of back to None.
Eventually, a certificate may be seen with a transaction that consumes
an object. In that case we can delete the key-value of the lock and
archive them. This reduces the amount of memory consumed by locks. It
also means that if an object has a lock entry it is 'active' ie transaction
may use it as an input. If not, a transaction can be rejected.
*/
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 nuance is that the map with default above never needs to materialize the (key, None)
record in the map, it just assumes there's a valid None
return for any key
(or equivalently that any material "key" is "in" the map from the caller's PoV). I was wondering if you could get away with this assumption.
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 do not know whether we could use this. But the previous code (which I am slowly refactoring) did not: by placing the pending_confirmation
within the accounts it ensured that the lock only exists if the object at this version exists. So I am replicating this 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.
by placing the pending_confirmation within the accounts it ensured that the lock only exists if the object at this version exists.
Fair enough.
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 rename handle_account_info_request, AccountInfoRequest and AccountInfoResponse.
We may also want to be more restrictive regarding the lock operations to begin with, and relax them latter if we find valid cases. For example, not even allowing setting the order_lock it the same signed order, not allowing initiating an order_lock that already exists and etc.
I am still trying to understand a few high-level questions, but we could discuss this in Slack:
- Why do we prefer to index objects by <id, seq> instead of just (this is essentially the same question as why we prefer to keep objects "immutable").
- Why do we prefer to keep an order_lock map in AuthorityState instead of keeping the lock within ObjectState.
} | ||
|
||
pub struct AuthorityState { | ||
// Fixed size, static, identity of the authority and shard |
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 committee be moved to the dynamic part? Could it ever change?
that is initially initialized to None. This indicates we have not | ||
seen any valid transactions mutating this object. Once we see the | ||
first valid transaction the lock is set to Some(SignedOrder). We | ||
will never change this lock to a different value of back to 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.
will never change this lock to a different value of back to None. | |
will never change this lock to a different value or back to None. |
|
||
*/ | ||
/// Order lock map maps object versions to the first next transaction seen | ||
order_lock: BTreeMap<(ObjectID, SequenceNumber), Option<SignedTransferOrder>>, |
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.
order_lock: BTreeMap<(ObjectID, SequenceNumber), Option<SignedTransferOrder>>, | |
order_lock: BTreeMap<TransactionDigest, Option<SignedTransferOrder>>, |
#### Summary of Changes - migrate some llvm ci pre land tests
This starts to address issue #11. Specifically I replaces the
pending_confirmation
andconfirmed_log
fields within accounts, with aorder_locks
andcertificates
field within an authority. This aligns the operation of fastx with the spec.order_lock
field in authority and helper functions to init, update and archive lockscertificates
andparent_sync
fields in authority, and helper functions to register certificates and maintain indexes.pending_confirmation
andconfirmed_log
.