Skip to content

Commit a301d3a

Browse files
authored
fix(package-resolver): make pure_input_layouts more lenient (#6976)
# Description of change This patch handles gracefully the cases where we try to resolve input layouts for arguments of a Move function that does not exist in any published package. Currently the resolution yields an error leading to errors while to create the response for transactions that have failed due to functions not found in the Indexer JSON-RPC. This is observed with the following request: ```sh curl --request POST https://indexer.mainnet.iota.cafe --header 'Content-Type: application/json' --data-raw '{"jsonrpc":"2.0","id":3,"method":"iota_getTransactionBlock","params":["Gdbwf3TeMuwoZbFtDJR1VogRoZJUhEJX9co3mgpr4aSc",{"showInput":true,"showEffects":true,"showEvents":true,"showBalanceChanges":true,"showObjectChanges":true}]}' | jq . ``` which leads to the error that is reported in #6861 ## Links to any relevant issues Fixes #6861 ## Type of change - Bug fix (a non-breaking change which fixes an issue) ## How the change has been tested Describe the tests that you ran to verify your changes. Make sure to provide instructions for the maintainer as well as any relevant configurations. - [x] Basic tests (linting, compilation, formatting, unit/integration tests) - [x] Patch-specific tests (correctness, functionality coverage) Added an integration test in `iota-indexer` that tries to create the transaction response from the serialized transaction that has been observed failing in mainnet. ### Infrastructure QA (only required for crates that are maintained by @iotaledger/infrastructure) The problem is limited to the response parsing, and does not affect ingestion or deployment - [x] Verification of API backward compatibility.
1 parent 2f51c3e commit a301d3a

File tree

8 files changed

+171
-36
lines changed

8 files changed

+171
-36
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/iota-indexer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ shared_test_runtime = []
6565

6666
[dev-dependencies]
6767
# external dependencies
68+
hex.workspace = true
6869
serde_json.workspace = true
6970

7071
# internal dependencies

crates/iota-indexer/src/models/transactions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use move_core_types::{
2121
annotated_value::{MoveDatatypeLayout, MoveTypeLayout},
2222
language_storage::TypeTag,
2323
};
24+
use serde::Deserialize;
2425

2526
use crate::{
2627
errors::IndexerError,
@@ -44,6 +45,7 @@ pub struct TxInsertionOrder {
4445

4546
#[derive(Clone, Debug, Queryable, Insertable, QueryableByName, Selectable)]
4647
#[diesel(table_name = transactions)]
48+
#[cfg_attr(feature = "shared_test_runtime", derive(Deserialize))]
4749
pub struct StoredTransaction {
4850
/// The index of the transaction in the global ordering that starts
4951
/// from genesis.

crates/iota-indexer/src/test_utils.rs

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tracing::info;
1414

1515
use crate::{
1616
IndexerConfig, IndexerMetrics,
17-
db::{ConnectionPoolConfig, new_connection_pool_with_config},
17+
db::{ConnectionPool, ConnectionPoolConfig, PoolConnection, new_connection_pool_with_config},
1818
errors::IndexerError,
1919
handlers::objects_snapshot_handler::SnapshotLagConfig,
2020
indexer::Indexer,
@@ -191,49 +191,84 @@ pub async fn start_test_indexer_impl(
191191
(store, handle)
192192
}
193193

194+
/// Manage a test database for integration tests.
195+
pub struct TestDatabase {
196+
pub url: Secret<String>,
197+
db_name: String,
198+
connection: PoolConnection,
199+
pool_config: ConnectionPoolConfig,
200+
}
201+
202+
impl TestDatabase {
203+
pub fn new(db_url: Secret<String>) -> Self {
204+
let pool_config = ConnectionPoolConfig::default();
205+
let db_name = db_url
206+
.expose_secret()
207+
.split('/')
208+
.next_back()
209+
.unwrap()
210+
.into();
211+
let (default_url, _) = replace_db_name(db_url.expose_secret(), "postgres");
212+
let blocking_pool =
213+
new_connection_pool_with_config(&default_url, Some(5), pool_config).unwrap();
214+
let connection = blocking_pool.get().unwrap();
215+
Self {
216+
url: db_url,
217+
db_name,
218+
connection,
219+
pool_config,
220+
}
221+
}
222+
223+
/// Drop the database in the server if it exists.
224+
pub fn drop_if_exists(&mut self) {
225+
self.connection
226+
.batch_execute(&format!("DROP DATABASE IF EXISTS {}", self.db_name))
227+
.unwrap();
228+
}
229+
230+
/// Create the database in the server.
231+
pub fn create(&mut self) {
232+
self.connection
233+
.batch_execute(&format!("CREATE DATABASE {}", self.db_name))
234+
.unwrap();
235+
}
236+
237+
/// Drop and recreate the database in the server.
238+
pub fn recreate(&mut self) {
239+
self.drop_if_exists();
240+
self.create();
241+
}
242+
243+
/// Create a new connection pool to the database.
244+
pub fn to_connection_pool(&self) -> ConnectionPool {
245+
new_connection_pool_with_config(self.url.expose_secret(), Some(5), self.pool_config)
246+
.unwrap()
247+
}
248+
249+
pub fn reset_db(&mut self) {
250+
crate::db::reset_database(&mut self.to_connection_pool().get().unwrap()).unwrap();
251+
}
252+
}
253+
194254
pub fn create_pg_store(db_url: Secret<String>, reset_database: bool) -> PgIndexerStore {
195255
// Reduce the connection pool size to 10 for testing
196256
// to prevent maxing out
197257
info!("Setting DB_POOL_SIZE to 10");
198258
std::env::set_var("DB_POOL_SIZE", "10");
199259

200-
// Set connection timeout for tests to 1 second
201-
let pool_config = ConnectionPoolConfig::default();
202-
203260
let registry = prometheus::Registry::default();
204261

205262
init_metrics(&registry);
206263

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

209-
let mut parsed_url = db_url.clone();
266+
let mut test_db = TestDatabase::new(db_url);
210267
if reset_database {
211-
let db_name = parsed_url.expose_secret().split('/').next_back().unwrap();
212-
// Switch to default to create a new database
213-
let (default_db_url, _) = replace_db_name(parsed_url.expose_secret(), "postgres");
214-
215-
// Open in default mode
216-
let blocking_pool =
217-
new_connection_pool_with_config(&default_db_url, Some(5), pool_config).unwrap();
218-
let mut default_conn = blocking_pool.get().unwrap();
219-
220-
// Delete the old db if it exists
221-
default_conn
222-
.batch_execute(&format!("DROP DATABASE IF EXISTS {}", db_name))
223-
.unwrap();
224-
225-
// Create the new db
226-
default_conn
227-
.batch_execute(&format!("CREATE DATABASE {}", db_name))
228-
.unwrap();
229-
parsed_url = replace_db_name(parsed_url.expose_secret(), db_name)
230-
.0
231-
.into();
268+
test_db.recreate();
232269
}
233270

234-
let blocking_pool =
235-
new_connection_pool_with_config(parsed_url.expose_secret(), Some(5), pool_config).unwrap();
236-
PgIndexerStore::new(blocking_pool.clone(), indexer_metrics.clone())
271+
PgIndexerStore::new(test_db.to_connection_pool(), indexer_metrics.clone())
237272
}
238273

239274
fn replace_db_name(db_url: &str, new_db_name: &str) -> (String, String) {

crates/iota-indexer/tests/common/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const DEFAULT_DB: &str = "iota_indexer";
4040
const DEFAULT_INDEXER_IP: &str = "127.0.0.1";
4141
const DEFAULT_INDEXER_PORT: u16 = 9005;
4242
const DEFAULT_SERVER_PORT: u16 = 3000;
43+
pub const FIXTURES_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data");
4344

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

@@ -151,7 +152,7 @@ pub async fn start_test_cluster_with_read_write_indexer(
151152
(cluster, pg_store, rpc_client)
152153
}
153154

154-
fn get_indexer_db_url(database_name: Option<&str>) -> String {
155+
pub fn get_indexer_db_url(database_name: Option<&str>) -> String {
155156
database_name.map_or_else(
156157
|| format!("{POSTGRES_URL}/{DEFAULT_DB}"),
157158
|db_name| format!("{POSTGRES_URL}/{db_name}"),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"tx_sequence_number": 14215430,
3+
"transaction_digest": "\\\\xa23422eb74ff16f44575c90f4f9d2bbb3eda47cec893082b6477bff8f8f7cf7a",
4+
"raw_transaction": "\\\\x01000000000004000800ca9a3b00000000010100000000000000000000000000000000000000000000000000000000000000050100000000000000010020ace17c42759a30ce8ea7108fff19a532e637cf6349b895d4df18a6906e2c76f70020ace17c42759a30ce8ea7108fff19a532e637cf6349b895d4df18a6906e2c76f7030200010100000000000000000000000000000000000000000000000000000000000000000000030d76616c696461746f725f7365741272656769737465725f76616c696461746f7200020101000102000000000000000000000000000000000000000000000000000000000000000000030b696f74615f73797374656d11726571756573745f6164645f7374616b650003010100020000010300a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa011d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b96bceb4000000000020fb1c96eddd8587ddee99a2f9bf9415529f4960634413345f1d66feed26c2902da0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caae80300000000000000e1f5050000000000016100f3b6c8806ab54e5e3a0f51828f2c8ab848387176df08d5bb41275969971c9d8606dbd2106d3727d9edc7c2a24f13c556059b28e6a54c347ffcf4e06dd329e10d166081f716111eef930777f69955c24d3364a34bca2c7a2752efb67d2d8375ec",
5+
"raw_effects": "\\\\x00010f010100000000000000080000000000000040420f000000000040420f000000000050fe24000000000050fe240000000000000000000000000020a23422eb74ff16f44575c90f4f9d2bbb3eda47cec893082b6477bff8f8f7cf7a01010000000003208398c885bbde285a80f6b61ec802d68104a120ff5014dbf389d9e44c3747139520a9d4ffc7cf6dea0b3fb8a628e3bc98fae16a40596329b030abc29c38add1eb0620da6b142369d88e983f7bcd89235df5d0b3965e62fed4d5b6ffcc3b5c9a143ccd71ceb400000000000200000000000000000000000000000000000000000000000000000000000000050170ceb400000000002085cca9fd69ae9e80bb6d8c2398aa55a1b3ce18b78a5efb03202638b1055f1bab0201000000000000000120a1278cf198a746e522054961df7c8079d7781b317ef76e908e4ce03aca285a7b020100000000000000001d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b9016bceb4000000000020fb1c96eddd8587ddee99a2f9bf9415529f4960634413345f1d66feed26c2902d00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa01209f5d0043f92c3e16753381b6d12bcc7c23086faad40c901a0aabe434cd9405f900a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa000000",
6+
"checkpoint_sequence_number": 3258168,
7+
"timestamp_ms": 1747169243848,
8+
"object_changes": [
9+
"\\\\x02a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa020100000000000000213078333a3a696f74615f73797374656d3a3a496f746153797374656d5374617465000000000000000000000000000000000000000000000000000000000000000571ceb4000000000070ceb4000000000020a1278cf198a746e522054961df7c8079d7781b317ef76e908e4ce03aca285a7b",
10+
"\\\\x02a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa203078323a3a636f696e3a3a436f696e3c3078323a3a696f74613a3a494f54413e1d7b62cb34fd4d64e5a5768b9afa92c860ebacc7f5381fc2e2405451336621b971ceb400000000006bceb40000000000209f5d0043f92c3e16753381b6d12bcc7c23086faad40c901a0aabe434cd9405f9"
11+
],
12+
"balance_changes": [
13+
"\\\\x00a0a48920e06273329c543149ab25c1e56bde63ff32f5f0d427d07401453b4caa0f3078323a3a696f74613a3a494f5441082d31303030303030"
14+
],
15+
"events": [],
16+
"transaction_kind": 1,
17+
"success_command_count": 0
18+
}

crates/iota-indexer/tests/rpc-tests/read_api.rs

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
// Copyright (c) 2024 IOTA Stiftung
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::str::FromStr;
4+
use std::{fs::File, path::Path, str::FromStr, sync::Arc};
55

6+
use hex::FromHex;
7+
use iota_indexer::{
8+
models::transactions::StoredTransaction, store::package_resolver::IndexerStorePackageResolver,
9+
test_utils::TestDatabase,
10+
};
611
use iota_json_rpc_api::{IndexerApiClient, ReadApiClient, TransactionBuilderClient};
712
use iota_json_rpc_types::{
813
CheckpointId, IotaGetPastObjectRequest, IotaObjectDataOptions, IotaObjectRef,
914
IotaObjectResponse, IotaObjectResponseQuery, IotaPastObjectResponse,
1015
IotaTransactionBlockResponse, IotaTransactionBlockResponseOptions,
1116
};
17+
use iota_package_resolver::Resolver;
1218
use iota_protocol_config::ProtocolVersion;
1319
use iota_test_transaction_builder::{create_nft, delete_nft, publish_nfts_package};
1420
use iota_types::{
@@ -17,13 +23,37 @@ use iota_types::{
1723
digests::{ChainIdentifier, ObjectDigest, TransactionDigest},
1824
error::IotaObjectResponseError,
1925
};
26+
use serde_json::Value;
2027

2128
use crate::common::{
22-
ApiTestSetup, execute_tx_and_wait_for_indexer, indexer_wait_for_checkpoint,
23-
indexer_wait_for_checkpoint_pruned, indexer_wait_for_object, indexer_wait_for_transaction,
24-
rpc_call_error_msg_matches, start_test_cluster_with_read_write_indexer,
29+
ApiTestSetup, FIXTURES_DIR, execute_tx_and_wait_for_indexer, get_indexer_db_url,
30+
indexer_wait_for_checkpoint, indexer_wait_for_checkpoint_pruned, indexer_wait_for_object,
31+
indexer_wait_for_transaction, rpc_call_error_msg_matches,
32+
start_test_cluster_with_read_write_indexer,
2533
};
2634

35+
/// Utility function to convert hex strings in JSON values to byte arrays.
36+
fn convert_hex_in_json(value: &mut Value) {
37+
match value {
38+
Value::String(s) => {
39+
if let Ok(bytes) = Vec::from_hex(s.strip_prefix("\\\\x").unwrap_or(s)) {
40+
*value = Value::Array(bytes.into_iter().map(Into::into).collect());
41+
}
42+
}
43+
Value::Array(arr) => {
44+
for v in arr.iter_mut() {
45+
convert_hex_in_json(v);
46+
}
47+
}
48+
Value::Object(obj) => {
49+
for (_key, val) in obj.iter_mut() {
50+
convert_hex_in_json(val);
51+
}
52+
}
53+
_ => {}
54+
}
55+
}
56+
2757
fn is_ascending(vec: &[u64]) -> bool {
2858
vec.windows(2).all(|window| window[0] <= window[1])
2959
}
@@ -1685,6 +1715,50 @@ fn try_get_object_before_version() {
16851715
});
16861716
}
16871717

1718+
#[tokio::test]
1719+
async fn failed_stored_tx_into_transaction_block() {
1720+
let db_url = get_indexer_db_url(Some("test_failed_stored_tx_into_transaction_block"));
1721+
let mut test_db = TestDatabase::new(db_url.into());
1722+
test_db.recreate();
1723+
test_db.reset_db();
1724+
let pool = test_db.to_connection_pool();
1725+
1726+
let mut failed_tx: serde_json::Value = serde_json::from_reader(
1727+
File::open(
1728+
Path::new(FIXTURES_DIR).join("failed_transaction_unpublished_function_call.json"),
1729+
)
1730+
.unwrap(),
1731+
)
1732+
.unwrap();
1733+
1734+
let json = failed_tx.as_object_mut().unwrap();
1735+
1736+
// Convert hex strings to Vec<u8>
1737+
for key in [
1738+
"raw_transaction",
1739+
"raw_effects",
1740+
"transaction_digest",
1741+
"object_changes",
1742+
"balance_changes",
1743+
] {
1744+
json.entry(key).and_modify(convert_hex_in_json);
1745+
}
1746+
1747+
let failed_tx: StoredTransaction = serde_json::from_value(failed_tx).unwrap();
1748+
1749+
let package_resolver = Arc::new(Resolver::new(IndexerStorePackageResolver::new(pool)));
1750+
assert!(
1751+
failed_tx
1752+
.try_into_iota_transaction_block_response(
1753+
IotaTransactionBlockResponseOptions::full_content(),
1754+
package_resolver
1755+
)
1756+
.await
1757+
.is_ok()
1758+
);
1759+
test_db.drop_if_exists();
1760+
}
1761+
16881762
#[test]
16891763
fn get_chain_identifier_with_pruning_enabled() {
16901764
let ApiTestSetup { runtime, .. } = ApiTestSetup::get_or_init();

crates/iota-package-resolver/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,16 @@ impl<S: PackageStore> Resolver<S> {
507507
for cmd in &tx.commands {
508508
match cmd {
509509
Command::MoveCall(call) => {
510-
let params = self
510+
let Ok(params) = self
511511
.function_parameters(
512512
call.package.into(),
513513
call.module.as_str(),
514514
call.function.as_str(),
515515
)
516-
.await?;
516+
.await
517+
else {
518+
continue;
519+
};
517520

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

0 commit comments

Comments
 (0)