Skip to content

Conversation

mystenmark
Copy link
Contributor

  • Changes to support non-exclusive mutable shared object access.
  • Restrict user transactions from using SharedObjectV2 (for now)
  • Changes to allow &mut inputs that do not get an automatic version bump
  • Build multiple settlement transactions
  • Scheduler support for non-exclusive-write + barrier transactions

- A new ObjectArg variant is added which has an enum to express
  mutability rather than a boolean.
- Initially this will only be allowed for system transactions,
  specifically settlement transactions. Settlement transactions are
  guaranteed not to have overlapping DF writes.
- In the future, we could expose this to users, but we would have to
  abort transactions that do overlapping writes, or transactions that
  directly mutate the root.

The next steps for this feature are:
- Changes to execution to avoid bumping versions of non-exclusive write
  inputs.
- Consensus handler will schedule a system transaction to advance the
  object's version after all non-exclusive writes have completed. This
  ensures that all DF writes that happen at a given owner version become
  visible atomically.
Copy link

vercel bot commented Sep 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
sui-docs Ready Ready Preview Comment Sep 24, 2025 9:15pm
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
multisig-toolkit Ignored Ignored Sep 24, 2025 9:15pm
sui-kiosk Ignored Ignored Sep 24, 2025 9:15pm

mutable: match mutability {
SharedObjectMutability::Mutable => true,
SharedObjectMutability::Immutable => false,
SharedObjectMutability::NonExclusiveWrite => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, we shall have // TODO(address-balances)

UnchangedConsensusKind::ReadConsensusStreamEnded(version),
))
)),
SharedObjectMutability::NonExclusiveWrite => Some((
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line reachable at all? Might be better to annotate unreachable if this is for the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is unreachable for now but it seems easy enough to do the correct thing here right now? Added a comment

} => {
message.object_id = Some(id.to_canonical_string(true));
message.version = Some(initial_shared_version.value());
// TODO: add enum to schema
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO(address-balances)

mutable: match mutability {
crate::transaction::SharedObjectMutability::Mutable => true,
crate::transaction::SharedObjectMutability::Immutable => false,
crate::transaction::SharedObjectMutability::NonExclusiveWrite => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Self::SharedMoveObject { mutability, .. } => match mutability {
SharedObjectMutability::Mutable => true,
SharedObjectMutability::Immutable => false,
SharedObjectMutability::NonExclusiveWrite => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it's safe to make this a todo. Are we certain this function can never be called before we verify a transaction? Might need find a better way to mark this as todo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does not even appear to be used anywhere, removing

(InputObjectKind::SharedMoveObject { mutability, .. }, _) => match mutability {
SharedObjectMutability::Mutable => true,
SharedObjectMutability::Immutable => false,
SharedObjectMutability::NonExclusiveWrite => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar concern here

}
}
SharedObjectMutability::Immutable => None,
SharedObjectMutability::NonExclusiveWrite => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor

@lxfind lxfind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need @tnowacki review the execution related changes


/// All inputs that will be directly mutated by the transaction. This does
/// not include SharedObjectMutability::NonExclusiveWrite inputs.
pub fn mutated_inputs(&self) -> BTreeMap<ObjectID, (VersionDigest, Owner)> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the two names look too similar to use correctly. It's also confusing to call it "mutated" on a transaction instead of effects. I would certainly mis-use if I didn't review this PR. Maybe explicitly name with exclusive_mutable_inputs and all_mutable_inputs? This would then be consistent with the following function non_exclusive_mutable_inputs

self.objects.iter().filter_map(|o| o.as_object())
}

pub fn non_exclusive_writes(&self) -> impl Iterator<Item = (ObjectID, SequenceNumber)> + '_ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be mutable_inputs instead of writes

input_metadata_opt,
Some(InputObjectMetadata::InputObject {
is_mutable_input: false,
mutability: Mutability::Immutable | Mutability::NonExclusiveWrite,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not allow taking NonExclusiveWrite by value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what this is doing - if its immutable or non-exclusive, it returns an error

Some(InputObjectMetadata::InputObject {
// We are only interested in mutable inputs.
is_mutable_input: true,
mutability: Mutability::Mutable,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand whether this should include NonExlusiveMutable

Copy link
Contributor Author

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 should - this causes mutable inputs to be written even if the transaction did not mutate them

settlements.push(build_one_settlement_txn(
settlements.len() as u64,
// pending_updates will be drained and can be re-used
&mut pending_updates,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels cleaner to drain the updates here and pass it by value

Comment on lines +228 to +229
input_sui: u64,
output_sui: u64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some comments on why these belong to the Updates

_ => false,
})
.expect(
"barrier transaction must have a version for SUI_ACCUMULATOR_ROOT_OBJECT_ID",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong message here?

)
.filter_map(
|(txn, tx_digest, expected_fx_digest, effects, executed_fx_digest)| {
let mut barrier_counts = BTreeMap::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be problematic since it is still possible that we split a large checkpoint into multiple checkpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good point. this problem will go away once i move checkpoint splitting into consensus handler

}

impl Default for ObjectWriteBarrier {
fn default() -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we introduce the default constructor? Seems non-intuitive to transition from 0 count to a non-zero count

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its so that we can use or_default() when inserting into the map. We can't start out with the correct count because we may receive a notification before we receive a wait(u32) call.

};
}

async fn wait_barrier(&self, key: (ObjectID, SequenceNumber), count: u32) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain how this deals with races between checkpoint builder and checkpoint executor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, i suppose that the same transaction can be enqueued twice at the same time. will have to support multiple waiters :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants