Skip to content

fix(package-resolver): make pure_input_layouts more lenient #6976

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

Merged
merged 5 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/iota-indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ shared_test_runtime = []

[dev-dependencies]
# external dependencies
hex.workspace = true
serde_json.workspace = true

# internal dependencies
Expand Down
2 changes: 2 additions & 0 deletions crates/iota-indexer/src/models/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use move_core_types::{
annotated_value::{MoveDatatypeLayout, MoveTypeLayout},
language_storage::TypeTag,
};
use serde::Deserialize;

use crate::{
errors::IndexerError,
Expand All @@ -44,6 +45,7 @@ pub struct TxInsertionOrder {

#[derive(Clone, Debug, Queryable, Insertable, QueryableByName, Selectable)]
#[diesel(table_name = transactions)]
#[cfg_attr(feature = "shared_test_runtime", derive(Deserialize))]
pub struct StoredTransaction {
/// The index of the transaction in the global ordering that starts
/// from genesis.
Expand Down
93 changes: 64 additions & 29 deletions crates/iota-indexer/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing::info;

use crate::{
IndexerConfig, IndexerMetrics,
db::{ConnectionPoolConfig, new_connection_pool_with_config},
db::{ConnectionPool, ConnectionPoolConfig, PoolConnection, new_connection_pool_with_config},
errors::IndexerError,
handlers::objects_snapshot_handler::SnapshotLagConfig,
indexer::Indexer,
Expand Down Expand Up @@ -191,49 +191,84 @@ pub async fn start_test_indexer_impl(
(store, handle)
}

/// Manage a test database for integration tests.
pub struct TestDatabase {
pub url: Secret<String>,
db_name: String,
connection: PoolConnection,
pool_config: ConnectionPoolConfig,
}

impl TestDatabase {
pub fn new(db_url: Secret<String>) -> Self {
let pool_config = ConnectionPoolConfig::default();
let db_name = db_url
.expose_secret()
.split('/')
.next_back()
.unwrap()
.into();
let (default_url, _) = replace_db_name(db_url.expose_secret(), "postgres");
let blocking_pool =
new_connection_pool_with_config(&default_url, Some(5), pool_config).unwrap();
let connection = blocking_pool.get().unwrap();
Self {
url: db_url,
db_name,
connection,
pool_config,
}
}

/// Drop the database in the server if it exists.
pub fn drop_if_exists(&mut self) {
self.connection
.batch_execute(&format!("DROP DATABASE IF EXISTS {}", self.db_name))
.unwrap();
}

/// Create the database in the server.
pub fn create(&mut self) {
self.connection
.batch_execute(&format!("CREATE DATABASE {}", self.db_name))
.unwrap();
}

/// Drop and recreate the database in the server.
pub fn recreate(&mut self) {
self.drop_if_exists();
self.create();
}

/// Create a new connection pool to the database.
pub fn to_connection_pool(&self) -> ConnectionPool {
new_connection_pool_with_config(self.url.expose_secret(), Some(5), self.pool_config)
.unwrap()
}

pub fn reset_db(&mut self) {
crate::db::reset_database(&mut self.to_connection_pool().get().unwrap()).unwrap();
}
}

pub fn create_pg_store(db_url: Secret<String>, reset_database: bool) -> PgIndexerStore {
// Reduce the connection pool size to 10 for testing
// to prevent maxing out
info!("Setting DB_POOL_SIZE to 10");
std::env::set_var("DB_POOL_SIZE", "10");

// Set connection timeout for tests to 1 second
let pool_config = ConnectionPoolConfig::default();

let registry = prometheus::Registry::default();

init_metrics(&registry);

let indexer_metrics = IndexerMetrics::new(&registry);

let mut parsed_url = db_url.clone();
let mut test_db = TestDatabase::new(db_url);
if reset_database {
let db_name = parsed_url.expose_secret().split('/').next_back().unwrap();
// Switch to default to create a new database
let (default_db_url, _) = replace_db_name(parsed_url.expose_secret(), "postgres");

// Open in default mode
let blocking_pool =
new_connection_pool_with_config(&default_db_url, Some(5), pool_config).unwrap();
let mut default_conn = blocking_pool.get().unwrap();

// Delete the old db if it exists
default_conn
.batch_execute(&format!("DROP DATABASE IF EXISTS {}", db_name))
.unwrap();

// Create the new db
default_conn
.batch_execute(&format!("CREATE DATABASE {}", db_name))
.unwrap();
parsed_url = replace_db_name(parsed_url.expose_secret(), db_name)
.0
.into();
test_db.recreate();
}

let blocking_pool =
new_connection_pool_with_config(parsed_url.expose_secret(), Some(5), pool_config).unwrap();
PgIndexerStore::new(blocking_pool.clone(), indexer_metrics.clone())
PgIndexerStore::new(test_db.to_connection_pool(), indexer_metrics.clone())
}

fn replace_db_name(db_url: &str, new_db_name: &str) -> (String, String) {
Expand Down
3 changes: 2 additions & 1 deletion crates/iota-indexer/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const DEFAULT_DB: &str = "iota_indexer";
const DEFAULT_INDEXER_IP: &str = "127.0.0.1";
const DEFAULT_INDEXER_PORT: u16 = 9005;
const DEFAULT_SERVER_PORT: u16 = 3000;
pub const FIXTURES_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data");

static GLOBAL_API_TEST_SETUP: OnceLock<ApiTestSetup> = OnceLock::new();

Expand Down Expand Up @@ -151,7 +152,7 @@ pub async fn start_test_cluster_with_read_write_indexer(
(cluster, pg_store, rpc_client)
}

fn get_indexer_db_url(database_name: Option<&str>) -> String {
pub fn get_indexer_db_url(database_name: Option<&str>) -> String {
database_name.map_or_else(
|| format!("{POSTGRES_URL}/{DEFAULT_DB}"),
|db_name| format!("{POSTGRES_URL}/{db_name}"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"tx_sequence_number": 14215430,
"transaction_digest": "\\\\xa23422eb74ff16f44575c90f4f9d2bbb3eda47cec893082b6477bff8f8f7cf7a",
"raw_transaction": "\\\\x01000000000004000800ca9a3b00000000010100000000000000000000000000000000000000000000000000000000000000050100000000000000010020ace17c42759a30ce8ea7108fff19a532e637cf6349b895d4df18a6906e2c76f70020ace17c42759a30ce8ea7108fff19a532e637cf6349b895d4df18a6906e2c76f7030200010100000000000000000000000000000000000000000000000000000000000000000000030d76616c696461746f725f7365741272656769737465725f76616c696461746f7200020101000102000000000000000000000000000000000000000000000000000000000000000000030b696f74615f73797374656d11726571756573745f6164645f7374616b650003010100020000010300a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa011d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b96bceb4000000000020fb1c96eddd8587ddee99a2f9bf9415529f4960634413345f1d66feed26c2902da0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caae80300000000000000e1f5050000000000016100f3b6c8806ab54e5e3a0f51828f2c8ab848387176df08d5bb41275969971c9d8606dbd2106d3727d9edc7c2a24f13c556059b28e6a54c347ffcf4e06dd329e10d166081f716111eef930777f69955c24d3364a34bca2c7a2752efb67d2d8375ec",
"raw_effects": "\\\\x00010f010100000000000000080000000000000040420f000000000040420f000000000050fe24000000000050fe240000000000000000000000000020a23422eb74ff16f44575c90f4f9d2bbb3eda47cec893082b6477bff8f8f7cf7a01010000000003208398c885bbde285a80f6b61ec802d68104a120ff5014dbf389d9e44c3747139520a9d4ffc7cf6dea0b3fb8a628e3bc98fae16a40596329b030abc29c38add1eb0620da6b142369d88e983f7bcd89235df5d0b3965e62fed4d5b6ffcc3b5c9a143ccd71ceb400000000000200000000000000000000000000000000000000000000000000000000000000050170ceb400000000002085cca9fd69ae9e80bb6d8c2398aa55a1b3ce18b78a5efb03202638b1055f1bab0201000000000000000120a1278cf198a746e522054961df7c8079d7781b317ef76e908e4ce03aca285a7b020100000000000000001d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b9016bceb4000000000020fb1c96eddd8587ddee99a2f9bf9415529f4960634413345f1d66feed26c2902d00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa01209f5d0043f92c3e16753381b6d12bcc7c23086faad40c901a0aabe434cd9405f900a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa000000",
"checkpoint_sequence_number": 3258168,
"timestamp_ms": 1747169243848,
"object_changes": [
"\\\\x02a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa020100000000000000213078333a3a696f74615f73797374656d3a3a496f746153797374656d5374617465000000000000000000000000000000000000000000000000000000000000000571ceb4000000000070ceb4000000000020a1278cf198a746e522054961df7c8079d7781b317ef76e908e4ce03aca285a7b",
"\\\\x02a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa203078323a3a636f696e3a3a436f696e3c3078323a3a696f74613a3a494f54413e1d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b971ceb400000000006bceb40000000000209f5d0043f92c3e16753381b6d12bcc7c23086faad40c901a0aabe434cd9405f9"
],
"balance_changes": [
"\\\\x00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa0f3078323a3a696f74613a3a494f5441082d31303030303030"
],
"events": [],
"transaction_kind": 1,
"success_command_count": 0
}
82 changes: 78 additions & 4 deletions crates/iota-indexer/tests/rpc-tests/read_api.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;
use std::{fs::File, path::Path, str::FromStr, sync::Arc};

use hex::FromHex;
use iota_indexer::{
models::transactions::StoredTransaction, store::package_resolver::IndexerStorePackageResolver,
test_utils::TestDatabase,
};
use iota_json_rpc_api::{IndexerApiClient, ReadApiClient, TransactionBuilderClient};
use iota_json_rpc_types::{
CheckpointId, IotaGetPastObjectRequest, IotaObjectDataOptions, IotaObjectRef,
IotaObjectResponse, IotaObjectResponseQuery, IotaPastObjectResponse,
IotaTransactionBlockResponse, IotaTransactionBlockResponseOptions,
};
use iota_package_resolver::Resolver;
use iota_protocol_config::ProtocolVersion;
use iota_test_transaction_builder::{create_nft, delete_nft, publish_nfts_package};
use iota_types::{
Expand All @@ -17,13 +23,37 @@ use iota_types::{
digests::{ChainIdentifier, ObjectDigest, TransactionDigest},
error::IotaObjectResponseError,
};
use serde_json::Value;

use crate::common::{
ApiTestSetup, execute_tx_and_wait_for_indexer, indexer_wait_for_checkpoint,
indexer_wait_for_checkpoint_pruned, indexer_wait_for_object, indexer_wait_for_transaction,
rpc_call_error_msg_matches, start_test_cluster_with_read_write_indexer,
ApiTestSetup, FIXTURES_DIR, execute_tx_and_wait_for_indexer, get_indexer_db_url,
indexer_wait_for_checkpoint, indexer_wait_for_checkpoint_pruned, indexer_wait_for_object,
indexer_wait_for_transaction, rpc_call_error_msg_matches,
start_test_cluster_with_read_write_indexer,
};

/// Utility function to convert hex strings in JSON values to byte arrays.
fn convert_hex_in_json(value: &mut Value) {
match value {
Value::String(s) => {
if let Ok(bytes) = Vec::from_hex(s.strip_prefix("\\\\x").unwrap_or(s)) {
*value = Value::Array(bytes.into_iter().map(Into::into).collect());
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
convert_hex_in_json(v);
}
}
Value::Object(obj) => {
for (_key, val) in obj.iter_mut() {
convert_hex_in_json(val);
}
}
_ => {}
}
}

fn is_ascending(vec: &[u64]) -> bool {
vec.windows(2).all(|window| window[0] <= window[1])
}
Expand Down Expand Up @@ -1685,6 +1715,50 @@ fn try_get_object_before_version() {
});
}

#[tokio::test]
async fn failed_stored_tx_into_transaction_block() {
let db_url = get_indexer_db_url(Some("test_failed_stored_tx_into_transaction_block"));
let mut test_db = TestDatabase::new(db_url.into());
test_db.recreate();
test_db.reset_db();
let pool = test_db.to_connection_pool();

let mut failed_tx: serde_json::Value = serde_json::from_reader(
File::open(
Path::new(FIXTURES_DIR).join("failed_transaction_unpublished_function_call.json"),
)
.unwrap(),
)
.unwrap();

let json = failed_tx.as_object_mut().unwrap();

// Convert hex strings to Vec<u8>
for key in [
"raw_transaction",
"raw_effects",
"transaction_digest",
"object_changes",
"balance_changes",
] {
json.entry(key).and_modify(convert_hex_in_json);
}

let failed_tx: StoredTransaction = serde_json::from_value(failed_tx).unwrap();

let package_resolver = Arc::new(Resolver::new(IndexerStorePackageResolver::new(pool)));
assert!(
failed_tx
.try_into_iota_transaction_block_response(
IotaTransactionBlockResponseOptions::full_content(),
package_resolver
)
.await
.is_ok()
);
test_db.drop_if_exists();
}

#[test]
fn get_chain_identifier_with_pruning_enabled() {
let ApiTestSetup { runtime, .. } = ApiTestSetup::get_or_init();
Expand Down
7 changes: 5 additions & 2 deletions crates/iota-package-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,16 @@ impl<S: PackageStore> Resolver<S> {
for cmd in &tx.commands {
match cmd {
Command::MoveCall(call) => {
let params = self
let Ok(params) = self
.function_parameters(
call.package.into(),
call.module.as_str(),
call.function.as_str(),
)
.await?;
.await
else {
continue;
};

for (open_sig, arg) in params.iter().zip(call.arguments.iter()) {
let sig = open_sig.instantiate(&call.type_arguments)?;
Expand Down
Loading