1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
use std::collections::{BTreeMap, BTreeSet, HashSet};
use move_core_types::language_storage::TypeTag;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use crate::{
base_types::{ObjectID, ObjectRef, SequenceNumber},
digests::{ObjectDigest, TransactionDigest},
event::Event,
is_system_package,
object::{Data, Object, Owner},
storage::BackingPackageStore,
transaction::Argument,
};
/// A type containing all of the information needed to work with a deleted
/// shared object in execution and when committing the execution effects of the
/// transaction. This holds:
/// 0. The object ID of the deleted shared object.
/// 1. The version of the shared object.
/// 2. Whether the object appeared as mutable (or owned) in the transaction, or
/// as a read-only shared object.
/// 3. The transaction digest of the previous transaction that used this shared
/// object mutably or took it by value.
pub type DeletedSharedObjectInfo = (ObjectID, SequenceNumber, bool, TransactionDigest);
/// A sequence of information about deleted shared objects in the transaction's
/// inputs.
pub type DeletedSharedObjects = Vec<DeletedSharedObjectInfo>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SharedInput {
Existing(ObjectRef),
Deleted(DeletedSharedObjectInfo),
Cancelled((ObjectID, SequenceNumber)),
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct DynamicallyLoadedObjectMetadata {
pub version: SequenceNumber,
pub digest: ObjectDigest,
pub owner: Owner,
pub storage_rebate: u64,
pub previous_transaction: TransactionDigest,
}
/// View of the store necessary to produce the layouts of types.
pub trait TypeLayoutStore: BackingPackageStore {}
impl<T> TypeLayoutStore for T where T: BackingPackageStore {}
#[derive(Debug)]
pub enum ExecutionResults {
V1(ExecutionResultsV1),
}
/// Used by iota-execution v1 and above, to capture the execution results from
/// Move. The results represent the primitive information that can then be used
/// to construct both transaction effect V1.
#[derive(Debug, Default)]
pub struct ExecutionResultsV1 {
/// All objects written regardless of whether they were mutated, created, or
/// unwrapped.
pub written_objects: BTreeMap<ObjectID, Object>,
/// All objects that existed prior to this transaction, and are modified in
/// this transaction. This includes any type of modification, including
/// mutated, wrapped and deleted objects.
pub modified_objects: BTreeSet<ObjectID>,
/// All object IDs created in this transaction.
pub created_object_ids: BTreeSet<ObjectID>,
/// All object IDs deleted in this transaction.
/// No object ID should be in both created_object_ids and
/// deleted_object_ids.
pub deleted_object_ids: BTreeSet<ObjectID>,
/// All Move events emitted in this transaction.
pub user_events: Vec<Event>,
}
pub type ExecutionResult = (
// mutable_reference_outputs
Vec<(Argument, Vec<u8>, TypeTag)>,
// return_values
Vec<(Vec<u8>, TypeTag)>,
);
impl ExecutionResultsV1 {
pub fn drop_writes(&mut self) {
self.written_objects.clear();
self.modified_objects.clear();
self.created_object_ids.clear();
self.deleted_object_ids.clear();
self.user_events.clear();
}
pub fn merge_results(&mut self, new_results: Self) {
self.written_objects.extend(new_results.written_objects);
self.modified_objects.extend(new_results.modified_objects);
self.created_object_ids
.extend(new_results.created_object_ids);
self.deleted_object_ids
.extend(new_results.deleted_object_ids);
self.user_events.extend(new_results.user_events);
}
pub fn update_version_and_previous_tx(
&mut self,
lamport_version: SequenceNumber,
prev_tx: TransactionDigest,
input_objects: &BTreeMap<ObjectID, Object>,
) {
for (id, obj) in self.written_objects.iter_mut() {
// TODO: We can now get rid of the following logic by passing in lamport version
// into the execution layer, and create new objects using the lamport version
// directly.
// Update the version for the written object.
match &mut obj.data {
Data::Move(obj) => {
// Move objects all get the transaction's lamport timestamp
obj.increment_version_to(lamport_version);
}
Data::Package(pkg) => {
// Modified packages get their version incremented (this is a special case that
// only applies to system packages). All other packages can only be created,
// and they are left alone.
if self.modified_objects.contains(id) {
debug_assert!(is_system_package(*id));
pkg.increment_version();
}
}
}
// Record the version that the shared object was created at in its owner field.
// Note, this only works because shared objects must be created as
// shared (not created as owned in one transaction and later
// converted to shared in another).
if let Owner::Shared {
initial_shared_version,
} = &mut obj.owner
{
if self.created_object_ids.contains(id) {
assert_eq!(
*initial_shared_version,
SequenceNumber::new(),
"Initial version should be blank before this point for {id:?}",
);
*initial_shared_version = lamport_version;
}
// Update initial_shared_version for reshared objects
if let Some(Owner::Shared {
initial_shared_version: previous_initial_shared_version,
}) = input_objects.get(id).map(|obj| &obj.owner)
{
debug_assert!(!self.created_object_ids.contains(id));
debug_assert!(!self.deleted_object_ids.contains(id));
debug_assert!(
*initial_shared_version == SequenceNumber::new()
|| *initial_shared_version == *previous_initial_shared_version
);
*initial_shared_version = *previous_initial_shared_version;
}
}
obj.previous_transaction = prev_tx;
}
}
}
/// If a transaction digest shows up in this list, when executing such
/// transaction, we will always return `ExecutionError::CertificateDenied`
/// without executing it (but still do gas smashing). Because this list is not
/// gated by protocol version, there are a few important criteria for adding a
/// digest to this list:
/// 1. The certificate must be causing all validators to either panic or hang
/// forever deterministically.
/// 2. If we ever ship a fix to make it no longer panic or hang when executing
/// such transaction, we must make sure the transaction is already in this
/// list. Otherwise nodes running the newer version without these
/// transactions in the list will generate forked result.
///
/// Below is a scenario of when we need to use this list:
/// 1. We detect that a specific transaction is causing all validators to either
/// panic or hang forever deterministically.
/// 2. We push a CertificateDenyConfig to deny such transaction to all
/// validators asap.
/// 3. To make sure that all fullnodes are able to sync to the latest version,
/// we need to add the transaction digest to this list as well asap, and ship
/// this binary to all fullnodes, so that they can sync past this
/// transaction.
/// 4. We then can start fixing the issue, and ship the fix to all nodes.
/// 5. Unfortunately, we can't remove the transaction digest from this list,
/// because if we do so, any future node that sync from genesis will fork on
/// this transaction. We may be able to remove it once we have stable
/// snapshots and the binary has a minimum supported protocol version past
/// the epoch.
pub fn get_denied_certificates() -> &'static HashSet<TransactionDigest> {
static DENIED_CERTIFICATES: Lazy<HashSet<TransactionDigest>> = Lazy::new(|| HashSet::from([]));
Lazy::force(&DENIED_CERTIFICATES)
}
pub fn is_certificate_denied(
transaction_digest: &TransactionDigest,
certificate_deny_set: &HashSet<TransactionDigest>,
) -> bool {
certificate_deny_set.contains(transaction_digest)
|| get_denied_certificates().contains(transaction_digest)
}