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 pub fn check_transaction_input_with_given_gas(
98 protocol_config: &ProtocolConfig,
99 reference_gas_price: u64,
100 transaction: &TransactionData,
101 mut input_objects: InputObjects,
102 receiving_objects: ReceivingObjects,
103 gas_object: Object,
104 metrics: &Arc<BytecodeVerifierMetrics>,
105 verifier_signing_config: &VerifierSigningConfig,
106 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
107 let gas_object_ref = gas_object.compute_object_reference();
108 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
109
110 let gas_status = check_transaction_input_inner(
111 protocol_config,
112 reference_gas_price,
113 transaction,
114 &input_objects,
115 &[gas_object_ref],
116 )?;
117 check_receiving_objects(&input_objects, &receiving_objects)?;
118 check_non_system_packages_to_be_published(
120 transaction,
121 protocol_config,
122 metrics,
123 verifier_signing_config,
124 )?;
125
126 Ok((gas_status, input_objects.into_checked()))
127 }
128
129 #[instrument(level = "trace", skip_all)]
135 pub fn check_certificate_input(
136 cert: &VerifiedExecutableTransaction,
137 input_objects: InputObjects,
138 protocol_config: &ProtocolConfig,
139 reference_gas_price: u64,
140 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
141 let transaction = cert.data().transaction_data();
142 let gas_status = check_transaction_input_inner(
143 protocol_config,
144 reference_gas_price,
145 transaction,
146 &input_objects,
147 &[],
148 )?;
149 Ok((gas_status, input_objects.into_checked()))
154 }
155
156 pub fn check_dev_inspect_input(
159 config: &ProtocolConfig,
160 kind: &TransactionKind,
161 input_objects: InputObjects,
162 _receiving_objects: ReceivingObjects,
164 ) -> IotaResult<CheckedInputObjects> {
165 kind.validity_check(config)?;
166 if kind.is_system_tx() {
167 return Err(UserInputError::Unsupported(format!(
168 "Transaction kind {kind} is not supported in dev-inspect"
169 ))
170 .into());
171 }
172 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
173 for input_object in input_objects.iter() {
174 let Some(object) = input_object.as_object() else {
175 continue;
177 };
178
179 if !object.is_immutable() {
180 fp_ensure!(
181 used_objects.insert(object.id().into()),
182 UserInputError::MutableObjectUsedMoreThanOnce {
183 object_id: object.id()
184 }
185 .into()
186 );
187 }
188 }
189
190 Ok(input_objects.into_checked())
191 }
192
193 #[instrument(level = "trace", skip_all)]
195 fn check_transaction_input_inner(
196 protocol_config: &ProtocolConfig,
197 reference_gas_price: u64,
198 transaction: &TransactionData,
199 input_objects: &InputObjects,
200 gas_override: &[ObjectRef],
202 ) -> IotaResult<IotaGasStatus> {
203 let gas = if gas_override.is_empty() {
205 transaction.gas()
206 } else {
207 gas_override
208 };
209
210 let gas_status = get_gas_status(
211 input_objects,
212 gas,
213 protocol_config,
214 reference_gas_price,
215 transaction,
216 )?;
217 check_objects(transaction, input_objects)?;
218
219 Ok(gas_status)
220 }
221
222 #[instrument(level = "trace", skip_all)]
223 fn check_receiving_objects(
224 input_objects: &InputObjects,
225 receiving_objects: &ReceivingObjects,
226 ) -> Result<(), IotaError> {
227 let mut objects_in_txn: HashSet<_> = input_objects
228 .object_kinds()
229 .map(|x| x.object_id())
230 .collect();
231
232 for ReceivingObjectReadResult {
241 object_ref: (object_id, version, object_digest),
242 object,
243 } in receiving_objects.iter()
244 {
245 fp_ensure!(
246 *version < SequenceNumber::MAX_VALID_EXCL,
247 UserInputError::InvalidSequenceNumber.into()
248 );
249
250 let Some(object) = object.as_object() else {
251 continue;
253 };
254
255 if !(object.owner.is_address_owned()
256 && object.version() == *version
257 && object.digest() == *object_digest)
258 {
259 fp_ensure!(
261 object.version() == *version,
262 UserInputError::ObjectVersionUnavailableForConsumption {
263 provided_obj_ref: (*object_id, *version, *object_digest),
264 current_version: object.version(),
265 }
266 .into()
267 );
268
269 fp_ensure!(
271 !object.is_package(),
272 UserInputError::MovePackageAsObject {
273 object_id: *object_id
274 }
275 .into()
276 );
277
278 let expected_digest = object.digest();
280 fp_ensure!(
281 expected_digest == *object_digest,
282 UserInputError::InvalidObjectDigest {
283 object_id: *object_id,
284 expected_digest
285 }
286 .into()
287 );
288
289 match object.owner {
290 Owner::AddressOwner(_) => {
291 debug_assert!(
292 false,
293 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
294 (*object_id, *version, *object_id),
295 object
296 );
297 error!(
298 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
299 (*object_id, *version, *object_id),
300 object
301 );
302 fp_bail!(
305 UserInputError::ObjectNotFound {
306 object_id: *object_id,
307 version: Some(*version),
308 }
309 .into()
310 )
311 }
312 Owner::ObjectOwner(owner) => {
313 fp_bail!(
314 UserInputError::InvalidChildObjectArgument {
315 child_id: object.id(),
316 parent_id: owner.into(),
317 }
318 .into()
319 )
320 }
321 Owner::Shared { .. } => fp_bail!(UserInputError::NotSharedObject.into()),
322 Owner::Immutable => fp_bail!(
323 UserInputError::MutableParameterExpected {
324 object_id: *object_id
325 }
326 .into()
327 ),
328 };
329 }
330
331 fp_ensure!(
332 !objects_in_txn.contains(object_id),
333 UserInputError::DuplicateObjectRefInput.into()
334 );
335
336 objects_in_txn.insert(*object_id);
337 }
338 Ok(())
339 }
340
341 #[instrument(level = "trace", skip_all)]
344 fn check_gas(
345 objects: &InputObjects,
346 protocol_config: &ProtocolConfig,
347 reference_gas_price: u64,
348 gas: &[ObjectRef],
349 gas_budget: u64,
350 gas_price: u64,
351 tx_kind: &TransactionKind,
352 ) -> IotaResult<IotaGasStatus> {
353 if tx_kind.is_system_tx() {
354 Ok(IotaGasStatus::new_unmetered())
355 } else {
356 let gas_status =
357 IotaGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
358
359 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
362 let mut gas_objects = vec![];
363 for obj_ref in gas {
364 let obj = objects.get(&obj_ref.0);
365 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
366 object_id: obj_ref.0,
367 version: Some(obj_ref.1),
368 })?;
369 gas_objects.push(obj);
370 }
371 gas_status.check_gas_balance(&gas_objects, gas_budget)?;
372 Ok(gas_status)
373 }
374 }
375
376 #[instrument(level = "trace", skip_all)]
379 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
380 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
382 for object in objects.iter() {
383 if object.is_mutable() {
384 fp_ensure!(
385 used_objects.insert(object.id().into()),
386 UserInputError::MutableObjectUsedMoreThanOnce {
387 object_id: object.id()
388 }
389 );
390 }
391 }
392
393 if !transaction.is_genesis_tx() && objects.is_empty() {
394 return Err(UserInputError::ObjectInputArityViolation);
395 }
396
397 let gas_coins: HashSet<ObjectID> =
398 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
399 for object in objects.iter() {
400 let input_object_kind = object.input_object_kind;
401
402 match &object.object {
403 ObjectReadResultKind::Object(object) => {
404 let owner_address = if gas_coins.contains(&object.id()) {
406 transaction.gas_owner()
407 } else {
408 transaction.sender()
409 };
410 let system_transaction = transaction.is_system_tx();
413 check_one_object(
414 &owner_address,
415 input_object_kind,
416 object,
417 system_transaction,
418 )?;
419 }
420 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
422 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
425 }
426 }
427
428 Ok(())
429 }
430
431 fn check_one_object(
433 owner: &IotaAddress,
434 object_kind: InputObjectKind,
435 object: &Object,
436 system_transaction: bool,
437 ) -> UserInputResult {
438 match object_kind {
439 InputObjectKind::MovePackage(package_id) => {
440 fp_ensure!(
441 object.data.try_as_package().is_some(),
442 UserInputError::MoveObjectAsPackage {
443 object_id: package_id
444 }
445 );
446 }
447 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
448 fp_ensure!(
449 !object.is_package(),
450 UserInputError::MovePackageAsObject { object_id }
451 );
452 fp_ensure!(
453 sequence_number < SequenceNumber::MAX_VALID_EXCL,
454 UserInputError::InvalidSequenceNumber
455 );
456
457 assert_eq!(
459 object.version(),
460 sequence_number,
461 "The fetched object version {} does not match the requested version {}, object id: {}",
462 object.version(),
463 sequence_number,
464 object.id(),
465 );
466
467 let expected_digest = object.digest();
469 fp_ensure!(
470 expected_digest == object_digest,
471 UserInputError::InvalidObjectDigest {
472 object_id,
473 expected_digest
474 }
475 );
476
477 match object.owner {
478 Owner::Immutable => {
479 }
481 Owner::AddressOwner(actual_owner) => {
482 fp_ensure!(
484 owner == &actual_owner,
485 UserInputError::IncorrectUserSignature {
486 error: format!(
487 "Object {object_id:?} is owned by account address {actual_owner:?}, but given owner/signer address is {owner:?}"
488 ),
489 }
490 );
491 }
492 Owner::ObjectOwner(owner) => {
493 return Err(UserInputError::InvalidChildObjectArgument {
494 child_id: object.id(),
495 parent_id: owner.into(),
496 });
497 }
498 Owner::Shared { .. } => {
499 return Err(UserInputError::NotSharedObject);
502 }
503 };
504 }
505 InputObjectKind::SharedMoveObject {
506 id: IOTA_CLOCK_OBJECT_ID,
507 initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
508 mutable: true,
509 } => {
510 if system_transaction {
513 return Ok(());
514 } else {
515 return Err(UserInputError::ImmutableParameterExpected {
516 object_id: IOTA_CLOCK_OBJECT_ID,
517 });
518 }
519 }
520 InputObjectKind::SharedMoveObject {
521 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
522 ..
523 } => {
524 if system_transaction {
525 return Ok(());
526 } else {
527 return Err(UserInputError::InaccessibleSystemObject {
528 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
529 });
530 }
531 }
532 InputObjectKind::SharedMoveObject {
533 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
534 mutable: true,
535 ..
536 } => {
537 if system_transaction {
540 return Ok(());
541 } else {
542 return Err(UserInputError::ImmutableParameterExpected {
543 object_id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
544 });
545 }
546 }
547 InputObjectKind::SharedMoveObject {
548 initial_shared_version: input_initial_shared_version,
549 ..
550 } => {
551 fp_ensure!(
552 object.version() < SequenceNumber::MAX_VALID_EXCL,
553 UserInputError::InvalidSequenceNumber
554 );
555
556 match object.owner {
557 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
558 return Err(UserInputError::NotSharedObject);
560 }
561 Owner::Shared {
562 initial_shared_version: actual_initial_shared_version,
563 } => {
564 fp_ensure!(
565 input_initial_shared_version == actual_initial_shared_version,
566 UserInputError::SharedObjectStartingVersionMismatch
567 )
568 }
569 }
570 }
571 };
572 Ok(())
573 }
574
575 #[instrument(level = "trace", skip_all)]
577 pub fn check_non_system_packages_to_be_published(
578 transaction: &TransactionData,
579 protocol_config: &ProtocolConfig,
580 metrics: &Arc<BytecodeVerifierMetrics>,
581 verifier_signing_config: &VerifierSigningConfig,
582 ) -> UserInputResult<()> {
583 if transaction.is_system_tx() {
585 return Ok(());
586 }
587
588 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
589 return Ok(());
590 };
591
592 let signing_limits = Some(verifier_signing_config.limits_for_signing());
595 let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
596 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
597
598 let shared_meter_verifier_timer = metrics
600 .verifier_runtime_per_ptb_success_latency
601 .start_timer();
602
603 let verifier_status = pt
604 .non_system_packages_to_be_published()
605 .try_for_each(|module_bytes| {
606 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
607 })
608 .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
609
610 match verifier_status {
611 Ok(_) => {
612 shared_meter_verifier_timer.stop_and_record();
614 }
615 Err(err) => {
616 metrics
619 .verifier_runtime_per_ptb_timeout_latency
620 .observe(shared_meter_verifier_timer.stop_and_discard());
621 return Err(err);
622 }
623 };
624
625 Ok(())
626 }
627}