1pub mod deny;
6
7pub use checked::*;
8
9#[iota_macros::with_checked_arithmetic]
10mod checked {
11 use std::{
12 collections::{BTreeMap, HashSet},
13 sync::Arc,
14 };
15
16 use iota_config::verifier_signing_config::VerifierSigningConfig;
17 use iota_protocol_config::ProtocolConfig;
18 use iota_types::{
19 IOTA_AUTHENTICATOR_STATE_OBJECT_ID, IOTA_CLOCK_OBJECT_ID, IOTA_CLOCK_OBJECT_SHARED_VERSION,
20 IOTA_RANDOMNESS_STATE_OBJECT_ID,
21 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
22 error::{IotaError, IotaResult, UserInputError, UserInputResult},
23 executable_transaction::VerifiedExecutableTransaction,
24 fp_bail, fp_ensure,
25 gas::IotaGasStatus,
26 metrics::BytecodeVerifierMetrics,
27 object::{Object, Owner},
28 transaction::{
29 CheckedInputObjects, InputObjectKind, InputObjects, ObjectReadResult,
30 ObjectReadResultKind, ReceivingObjectReadResult, ReceivingObjects, TransactionData,
31 TransactionDataAPI, TransactionKind,
32 },
33 };
34 use tracing::{error, instrument};
35
36 trait IntoChecked {
37 fn into_checked(self) -> CheckedInputObjects;
38 }
39
40 impl IntoChecked for InputObjects {
41 fn into_checked(self) -> CheckedInputObjects {
42 CheckedInputObjects::new_with_checked_transaction_inputs(self)
43 }
44 }
45
46 pub fn get_gas_status(
51 objects: &InputObjects,
52 gas: &[ObjectRef],
53 protocol_config: &ProtocolConfig,
54 reference_gas_price: u64,
55 transaction: &TransactionData,
56 ) -> IotaResult<IotaGasStatus> {
57 check_gas(
58 objects,
59 protocol_config,
60 reference_gas_price,
61 gas,
62 transaction.gas_budget(),
63 transaction.gas_price(),
64 transaction.kind(),
65 )
66 }
67
68 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
69 pub fn check_transaction_input(
70 protocol_config: &ProtocolConfig,
71 reference_gas_price: u64,
72 transaction: &TransactionData,
73 input_objects: InputObjects,
74 receiving_objects: &ReceivingObjects,
75 metrics: &Arc<BytecodeVerifierMetrics>,
76 verifier_signing_config: &VerifierSigningConfig,
77 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
78 let gas_status = check_transaction_input_inner(
79 protocol_config,
80 reference_gas_price,
81 transaction,
82 &input_objects,
83 &[],
84 )?;
85 check_receiving_objects(&input_objects, receiving_objects)?;
86 check_non_system_packages_to_be_published(
88 transaction,
89 protocol_config,
90 metrics,
91 verifier_signing_config,
92 )?;
93
94 Ok((gas_status, input_objects.into_checked()))
95 }
96
97 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
98 pub fn check_transaction_input_with_given_gas(
99 protocol_config: &ProtocolConfig,
100 reference_gas_price: u64,
101 transaction: &TransactionData,
102 mut input_objects: InputObjects,
103 receiving_objects: ReceivingObjects,
104 gas_object: Object,
105 metrics: &Arc<BytecodeVerifierMetrics>,
106 verifier_signing_config: &VerifierSigningConfig,
107 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
108 let gas_object_ref = gas_object.compute_object_reference();
109 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
110
111 let gas_status = check_transaction_input_inner(
112 protocol_config,
113 reference_gas_price,
114 transaction,
115 &input_objects,
116 &[gas_object_ref],
117 )?;
118 check_receiving_objects(&input_objects, &receiving_objects)?;
119 check_non_system_packages_to_be_published(
121 transaction,
122 protocol_config,
123 metrics,
124 verifier_signing_config,
125 )?;
126
127 Ok((gas_status, input_objects.into_checked()))
128 }
129
130 #[instrument(level = "trace", skip_all)]
136 pub fn check_certificate_input(
137 cert: &VerifiedExecutableTransaction,
138 input_objects: InputObjects,
139 protocol_config: &ProtocolConfig,
140 reference_gas_price: u64,
141 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
142 let transaction = cert.data().transaction_data();
143 let gas_status = check_transaction_input_inner(
144 protocol_config,
145 reference_gas_price,
146 transaction,
147 &input_objects,
148 &[],
149 )?;
150 Ok((gas_status, input_objects.into_checked()))
155 }
156
157 #[instrument(level = "trace", skip_all)]
160 pub fn check_dev_inspect_input(
161 config: &ProtocolConfig,
162 kind: &TransactionKind,
163 input_objects: InputObjects,
164 _receiving_objects: ReceivingObjects,
166 ) -> IotaResult<CheckedInputObjects> {
167 kind.validity_check(config)?;
168 if kind.is_system_tx() {
169 return Err(UserInputError::Unsupported(format!(
170 "Transaction kind {kind} is not supported in dev-inspect"
171 ))
172 .into());
173 }
174 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
175 for input_object in input_objects.iter() {
176 let Some(object) = input_object.as_object() else {
177 continue;
179 };
180
181 if !object.is_immutable() {
182 fp_ensure!(
183 used_objects.insert(object.id().into()),
184 UserInputError::MutableObjectUsedMoreThanOnce {
185 object_id: object.id()
186 }
187 .into()
188 );
189 }
190 }
191
192 Ok(input_objects.into_checked())
193 }
194
195 #[instrument(level = "trace", skip_all)]
197 fn check_transaction_input_inner(
198 protocol_config: &ProtocolConfig,
199 reference_gas_price: u64,
200 transaction: &TransactionData,
201 input_objects: &InputObjects,
202 gas_override: &[ObjectRef],
204 ) -> IotaResult<IotaGasStatus> {
205 let gas = if gas_override.is_empty() {
207 transaction.gas()
208 } else {
209 gas_override
210 };
211
212 let gas_status = get_gas_status(
213 input_objects,
214 gas,
215 protocol_config,
216 reference_gas_price,
217 transaction,
218 )?;
219 check_objects(transaction, input_objects)?;
220
221 Ok(gas_status)
222 }
223
224 #[instrument(level = "trace", skip_all)]
225 fn check_receiving_objects(
226 input_objects: &InputObjects,
227 receiving_objects: &ReceivingObjects,
228 ) -> Result<(), IotaError> {
229 let mut objects_in_txn: HashSet<_> = input_objects
230 .object_kinds()
231 .map(|x| x.object_id())
232 .collect();
233
234 for ReceivingObjectReadResult {
243 object_ref: (object_id, version, object_digest),
244 object,
245 } in receiving_objects.iter()
246 {
247 fp_ensure!(
248 *version < SequenceNumber::MAX_VALID_EXCL,
249 UserInputError::InvalidSequenceNumber.into()
250 );
251
252 let Some(object) = object.as_object() else {
253 continue;
255 };
256
257 if !(object.owner.is_address_owned()
258 && object.version() == *version
259 && object.digest() == *object_digest)
260 {
261 fp_ensure!(
263 object.version() == *version,
264 UserInputError::ObjectVersionUnavailableForConsumption {
265 provided_obj_ref: (*object_id, *version, *object_digest),
266 current_version: object.version(),
267 }
268 .into()
269 );
270
271 fp_ensure!(
273 !object.is_package(),
274 UserInputError::MovePackageAsObject {
275 object_id: *object_id
276 }
277 .into()
278 );
279
280 let expected_digest = object.digest();
282 fp_ensure!(
283 expected_digest == *object_digest,
284 UserInputError::InvalidObjectDigest {
285 object_id: *object_id,
286 expected_digest
287 }
288 .into()
289 );
290
291 match object.owner {
292 Owner::AddressOwner(_) => {
293 debug_assert!(
294 false,
295 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
296 (*object_id, *version, *object_id),
297 object
298 );
299 error!(
300 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
301 (*object_id, *version, *object_id),
302 object
303 );
304 fp_bail!(
307 UserInputError::ObjectNotFound {
308 object_id: *object_id,
309 version: Some(*version),
310 }
311 .into()
312 )
313 }
314 Owner::ObjectOwner(owner) => {
315 fp_bail!(
316 UserInputError::InvalidChildObjectArgument {
317 child_id: object.id(),
318 parent_id: owner.into(),
319 }
320 .into()
321 )
322 }
323 Owner::Shared { .. } => fp_bail!(UserInputError::NotSharedObject.into()),
324 Owner::Immutable => fp_bail!(
325 UserInputError::MutableParameterExpected {
326 object_id: *object_id
327 }
328 .into()
329 ),
330 };
331 }
332
333 fp_ensure!(
334 !objects_in_txn.contains(object_id),
335 UserInputError::DuplicateObjectRefInput.into()
336 );
337
338 objects_in_txn.insert(*object_id);
339 }
340 Ok(())
341 }
342
343 #[instrument(level = "trace", skip_all)]
346 fn check_gas(
347 objects: &InputObjects,
348 protocol_config: &ProtocolConfig,
349 reference_gas_price: u64,
350 gas: &[ObjectRef],
351 gas_budget: u64,
352 gas_price: u64,
353 tx_kind: &TransactionKind,
354 ) -> IotaResult<IotaGasStatus> {
355 if tx_kind.is_system_tx() {
356 Ok(IotaGasStatus::new_unmetered())
357 } else {
358 let gas_status =
359 IotaGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
360
361 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
364 let mut gas_objects = vec![];
365 for obj_ref in gas {
366 let obj = objects.get(&obj_ref.0);
367 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
368 object_id: obj_ref.0,
369 version: Some(obj_ref.1),
370 })?;
371 gas_objects.push(obj);
372 }
373 gas_status.check_gas_balance(&gas_objects, gas_budget)?;
374 Ok(gas_status)
375 }
376 }
377
378 #[instrument(level = "trace", skip_all)]
381 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
382 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
384 for object in objects.iter() {
385 if object.is_mutable() {
386 fp_ensure!(
387 used_objects.insert(object.id().into()),
388 UserInputError::MutableObjectUsedMoreThanOnce {
389 object_id: object.id()
390 }
391 );
392 }
393 }
394
395 if !transaction.is_genesis_tx() && objects.is_empty() {
396 return Err(UserInputError::ObjectInputArityViolation);
397 }
398
399 let gas_coins: HashSet<ObjectID> =
400 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
401 for object in objects.iter() {
402 let input_object_kind = object.input_object_kind;
403
404 match &object.object {
405 ObjectReadResultKind::Object(object) => {
406 let owner_address = if gas_coins.contains(&object.id()) {
408 transaction.gas_owner()
409 } else {
410 transaction.sender()
411 };
412 let system_transaction = transaction.is_system_tx();
415 check_one_object(
416 &owner_address,
417 input_object_kind,
418 object,
419 system_transaction,
420 )?;
421 }
422 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
424 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
427 }
428 }
429
430 Ok(())
431 }
432
433 fn check_one_object(
435 owner: &IotaAddress,
436 object_kind: InputObjectKind,
437 object: &Object,
438 system_transaction: bool,
439 ) -> UserInputResult {
440 match object_kind {
441 InputObjectKind::MovePackage(package_id) => {
442 fp_ensure!(
443 object.data.try_as_package().is_some(),
444 UserInputError::MoveObjectAsPackage {
445 object_id: package_id
446 }
447 );
448 }
449 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
450 fp_ensure!(
451 !object.is_package(),
452 UserInputError::MovePackageAsObject { object_id }
453 );
454 fp_ensure!(
455 sequence_number < SequenceNumber::MAX_VALID_EXCL,
456 UserInputError::InvalidSequenceNumber
457 );
458
459 assert_eq!(
461 object.version(),
462 sequence_number,
463 "The fetched object version {} does not match the requested version {}, object id: {}",
464 object.version(),
465 sequence_number,
466 object.id(),
467 );
468
469 let expected_digest = object.digest();
471 fp_ensure!(
472 expected_digest == object_digest,
473 UserInputError::InvalidObjectDigest {
474 object_id,
475 expected_digest
476 }
477 );
478
479 match object.owner {
480 Owner::Immutable => {
481 }
483 Owner::AddressOwner(actual_owner) => {
484 fp_ensure!(
486 owner == &actual_owner,
487 UserInputError::IncorrectUserSignature {
488 error: format!(
489 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
490 ),
491 }
492 );
493 }
494 Owner::ObjectOwner(owner) => {
495 return Err(UserInputError::InvalidChildObjectArgument {
496 child_id: object.id(),
497 parent_id: owner.into(),
498 });
499 }
500 Owner::Shared { .. } => {
501 return Err(UserInputError::NotSharedObject);
504 }
505 };
506 }
507 InputObjectKind::SharedMoveObject {
508 id: IOTA_CLOCK_OBJECT_ID,
509 initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
510 mutable: true,
511 } => {
512 if system_transaction {
515 return Ok(());
516 } else {
517 return Err(UserInputError::ImmutableParameterExpected {
518 object_id: IOTA_CLOCK_OBJECT_ID,
519 });
520 }
521 }
522 InputObjectKind::SharedMoveObject {
523 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
524 ..
525 } => {
526 if system_transaction {
527 return Ok(());
528 } else {
529 return Err(UserInputError::InaccessibleSystemObject {
530 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
531 });
532 }
533 }
534 InputObjectKind::SharedMoveObject {
535 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
536 mutable: true,
537 ..
538 } => {
539 if system_transaction {
542 return Ok(());
543 } else {
544 return Err(UserInputError::ImmutableParameterExpected {
545 object_id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
546 });
547 }
548 }
549 InputObjectKind::SharedMoveObject {
550 initial_shared_version: input_initial_shared_version,
551 ..
552 } => {
553 fp_ensure!(
554 object.version() < SequenceNumber::MAX_VALID_EXCL,
555 UserInputError::InvalidSequenceNumber
556 );
557
558 match object.owner {
559 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
560 return Err(UserInputError::NotSharedObject);
562 }
563 Owner::Shared {
564 initial_shared_version: actual_initial_shared_version,
565 } => {
566 fp_ensure!(
567 input_initial_shared_version == actual_initial_shared_version,
568 UserInputError::SharedObjectStartingVersionMismatch
569 )
570 }
571 }
572 }
573 };
574 Ok(())
575 }
576
577 #[instrument(level = "trace", skip_all)]
579 pub fn check_non_system_packages_to_be_published(
580 transaction: &TransactionData,
581 protocol_config: &ProtocolConfig,
582 metrics: &Arc<BytecodeVerifierMetrics>,
583 verifier_signing_config: &VerifierSigningConfig,
584 ) -> UserInputResult<()> {
585 if transaction.is_system_tx() {
587 return Ok(());
588 }
589
590 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
591 return Ok(());
592 };
593
594 let signing_limits = Some(verifier_signing_config.limits_for_signing());
597 let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
598 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
599
600 let shared_meter_verifier_timer = metrics
602 .verifier_runtime_per_ptb_success_latency
603 .start_timer();
604
605 let verifier_status = pt
606 .non_system_packages_to_be_published()
607 .try_for_each(|module_bytes| {
608 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
609 })
610 .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
611
612 match verifier_status {
613 Ok(_) => {
614 shared_meter_verifier_timer.stop_and_record();
616 }
617 Err(err) => {
618 metrics
621 .verifier_runtime_per_ptb_timeout_latency
622 .observe(shared_meter_verifier_timer.stop_and_discard());
623 return Err(err);
624 }
625 };
626
627 Ok(())
628 }
629}