diff --git a/api/src/context.rs b/api/src/context.rs index 1a77f5d5ee1f4..89cfaac290156 100644 --- a/api/src/context.rs +++ b/api/src/context.rs @@ -30,7 +30,7 @@ use aptos_types::{ account_address::AccountAddress, account_config::{AccountResource, NewBlockEvent}, chain_id::ChainId, - contract_event::EventWithVersion, + contract_event::{ContractEvent, ContractEventV1, EventWithVersion}, event::EventKey, indexer::indexer_db_reader::IndexerReader, ledger_info::LedgerInfoWithSignatures, @@ -818,12 +818,21 @@ impl Context { .into_iter() .zip(infos) .enumerate() - .map(|(i, ((txn, txn_output), info))| { - let version = start_version + i as u64; - let (write_set, events, _, _, _) = txn_output.unpack(); - self.get_accumulator_root_hash(version) - .map(|h| (version, txn, info, events, h, write_set).into()) - }) + .map( + |(i, ((txn, txn_output), info))| -> Result { + let version = start_version + i as u64; + let (write_set, mut events, _, _, _) = txn_output.unpack(); + if self + .node_config + .indexer_db_config + .enable_event_v2_translation + { + let _ = self.translate_v2_to_v1_events_for_version(version, &mut events); + } + let h = self.get_accumulator_root_hash(version)?; + Ok((version, txn, info, events, h, write_set).into()) + }, + ) .collect() } @@ -878,7 +887,18 @@ impl Context { })?; txns.into_inner() .into_iter() - .map(|t| self.convert_into_transaction_on_chain_data(t)) + .map(|t| -> Result { + let mut txn = self.convert_into_transaction_on_chain_data(t)?; + if self + .node_config + .indexer_db_config + .enable_event_v2_translation + { + let _ = + self.translate_v2_to_v1_events_for_version(txn.version, &mut txn.events); + } + Ok(txn) + }) .collect::>>() .context("Failed to parse account transactions") .map_err(|err| E::internal_with_code(err, AptosErrorCode::InternalError, ledger_info)) @@ -889,10 +909,22 @@ impl Context { hash: HashValue, ledger_version: u64, ) -> Result> { - self.db + if let Some(t) = self + .db .get_transaction_by_hash(hash, ledger_version, true)? - .map(|t| self.convert_into_transaction_on_chain_data(t)) - .transpose() + { + let mut txn: TransactionOnChainData = self.convert_into_transaction_on_chain_data(t)?; + if self + .node_config + .indexer_db_config + .enable_event_v2_translation + { + let _ = self.translate_v2_to_v1_events_for_version(txn.version, &mut txn.events); + } + Ok(Some(txn)) + } else { + Ok(None) + } } pub async fn get_pending_transaction_by_hash( @@ -915,11 +947,64 @@ impl Context { version: u64, ledger_version: u64, ) -> Result { - self.convert_into_transaction_on_chain_data(self.db.get_transaction_by_version( - version, - ledger_version, - true, - )?) + let mut txn = self.convert_into_transaction_on_chain_data( + self.db + .get_transaction_by_version(version, ledger_version, true)?, + )?; + if self + .node_config + .indexer_db_config + .enable_event_v2_translation + { + let _ = self.translate_v2_to_v1_events_for_version(version, &mut txn.events); + } + Ok(txn) + } + + fn translate_v2_to_v1_events_for_version( + &self, + version: u64, + events: &mut [ContractEvent], + ) -> Result<()> { + for (idx, event) in events.iter_mut().enumerate() { + let translated_event = self + .indexer_reader + .as_ref() + .ok_or(anyhow!("Internal indexer reader doesn't exist"))? + .get_translated_v1_event_by_version_and_index(version, idx as u64); + if let Ok(translated_event) = translated_event { + *event = ContractEvent::V1(translated_event); + } + } + Ok(()) + } + + pub fn translate_v2_to_v1_events_for_simulation( + &self, + events: &mut [ContractEvent], + ) -> Result<()> { + let mut count_map: HashMap = HashMap::new(); + for event in events.iter_mut() { + if let ContractEvent::V2(v2) = event { + let translated_event = self + .indexer_reader + .as_ref() + .ok_or(anyhow!("Internal indexer reader doesn't exist"))? + .translate_event_v2_to_v1(v2)?; + if let Some(v1) = translated_event { + let count = count_map.get(v1.key()).unwrap_or(&0); + let v1_adjusted = ContractEventV1::new( + *v1.key(), + v1.sequence_number() + count, + v1.type_tag().clone(), + v1.event_data().to_vec(), + ); + *event = ContractEvent::V1(v1_adjusted); + count_map.insert(*v1.key(), count + 1); + } + } + } + Ok(()) } pub fn get_accumulator_root_hash(&self, version: u64) -> Result { diff --git a/api/src/tests/event_v2_translation_test.rs b/api/src/tests/event_v2_translation_test.rs new file mode 100644 index 0000000000000..baf67753e3080 --- /dev/null +++ b/api/src/tests/event_v2_translation_test.rs @@ -0,0 +1,551 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::{new_test_context, new_test_context_with_db_sharding_and_internal_indexer}; +use aptos_api_test_context::current_function_name; +use aptos_crypto::{ed25519::Ed25519PrivateKey, SigningKey, ValidCryptoMaterial}; +use aptos_sdk::types::LocalAccount; +use aptos_types::account_config::RotationProofChallenge; +use move_core_types::{account_address::AccountAddress, language_storage::CORE_CODE_ADDRESS}; +use serde_json::{json, Value}; + +static ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION: u64 = 81; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_feature_enable_disable() { + let mut context = new_test_context(current_function_name!()); + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + assert!( + context + .is_feature_enabled(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await + ); + context + .disable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + assert!( + !context + .is_feature_enabled(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await + ); + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + assert!( + context + .is_feature_enabled(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await + ); +} + +fn matches_event_details( + event: &Value, + event_type: &str, + creation_number: u64, + account_address: AccountAddress, + sequence_number: u64, +) -> bool { + event["type"] == event_type + && event["guid"]["creation_number"] == creation_number + && event["guid"]["account_address"] == account_address.to_hex_literal() + && event["sequence_number"] == sequence_number +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_event_v2_translation_coin_deposit_event() { + let context = + &mut new_test_context_with_db_sharding_and_internal_indexer(current_function_name!()); + + // Start with the MODULE_EVENT_MIGRATION feature disabled + context + .disable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Create two accounts + let account1 = &mut context.api_create_account().await; + let account2 = &mut context.api_create_account().await; + + // Transfer coins from account1 to account2, emitting V1 events as the feature is disabled + context + .api_execute_aptos_account_transfer(account2, account1.address(), 101) + .await; + + // Enable the MODULE_EVENT_MIGRATION feature + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Check the simulation API outputs the translated V1 event rather than the V2 event as it is + let payload = json!({ + "type": "entry_function_payload", + "function": "0x1::coin::transfer", + "type_arguments": ["0x1::aptos_coin::AptosCoin"], + "arguments": [ + account1.address().to_hex_literal(), "102" + ] + }); + let resp = context.simulate_transaction(account2, payload, 200).await; + + let is_expected_event = |e: &Value| { + matches_event_details(e, "0x1::coin::DepositEvent", 2, account1.address(), 2) + && e["data"]["amount"] == "102" + }; + + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Transfer coins from account2 to account1, emitting V2 events as the feature is enabled + context + .api_execute_aptos_account_transfer(account2, account1.address(), 102) + .await; + + // Check the event_by_creation_number API outputs the translated V1 event + let resp = context + .gen_events_by_creation_num(&account1.address(), 2) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the event_by_handle API outputs the translated V1 event + let resp = context + .gen_events_by_handle( + &account1.address(), + "0x1::coin::CoinStore%3C0x1::aptos_coin::AptosCoin%3E", + "deposit_events", + ) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the accounts-transactions API outputs the translated V1 event + let resp = context + .get( + format!( + "/accounts/{}/transactions?limit=1", + account2.address().to_hex_literal() + ) + .as_str(), + ) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + let hash = resp[0]["hash"].as_str().unwrap(); + let version = resp[0]["version"].as_str().unwrap(); + + // Check the transactions API outputs the translated V1 event + let resp = context + .get(format!("/transactions?start={}&limit=1", version).as_str()) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_hash API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_hash/{}", hash).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_version API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_version/{}", version).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_event_v2_translation_coin_withdraw_event() { + let context = + &mut new_test_context_with_db_sharding_and_internal_indexer(current_function_name!()); + + // Start with the MODULE_EVENT_MIGRATION feature disabled + context + .disable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Create two accounts + let account1 = &mut context.api_create_account().await; + let account2 = &mut context.api_create_account().await; + + // Transfer coins from account1 to account2, emitting V1 events as the feature is disabled + context + .api_execute_aptos_account_transfer(account2, account1.address(), 101) + .await; + + // Enable the MODULE_EVENT_MIGRATION feature + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Check the simulation API outputs the translated V1 event rather than the V2 event as it is + let payload = json!({ + "type": "entry_function_payload", + "function": "0x1::coin::transfer", + "type_arguments": ["0x1::aptos_coin::AptosCoin"], + "arguments": [ + account1.address().to_hex_literal(), "102" + ] + }); + let resp = context.simulate_transaction(account2, payload, 200).await; + let address2_address = account2.address(); + let is_expected_event = |e: &Value| { + matches_event_details(e, "0x1::coin::WithdrawEvent", 3, address2_address, 1) + && e["data"]["amount"] == "102" + }; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Transfer coins from account2 to account1, emitting V2 events as the feature is enabled + context + .api_execute_aptos_account_transfer(account2, account1.address(), 102) + .await; + + // Check the event_by_creation_number API outputs the translated V1 event + let resp = context + .gen_events_by_creation_num(&account2.address(), 3) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the event_by_handle API outputs the translated V1 event + let resp = context + .gen_events_by_handle( + &account2.address(), + "0x1::coin::CoinStore%3C0x1::aptos_coin::AptosCoin%3E", + "withdraw_events", + ) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the accounts-transactions API outputs the translated V1 event + let resp = context + .get( + format!( + "/accounts/{}/transactions?limit=1", + account2.address().to_hex_literal() + ) + .as_str(), + ) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + let hash = resp[0]["hash"].as_str().unwrap(); + let version = resp[0]["version"].as_str().unwrap(); + + // Check the transactions API outputs the translated V1 event + let resp = context + .get(format!("/transactions?start={}&limit=1", version).as_str()) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_hash API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_hash/{}", hash).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_version API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_version/{}", version).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_event_v2_translation_account_coin_register_event() { + let context = + &mut new_test_context_with_db_sharding_and_internal_indexer(current_function_name!()); + + // Make sure that the MODULE_EVENT_MIGRATION feature is enabled + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Create two accounts + let account1 = &mut context.api_create_account().await; + let account2 = &mut context.gen_account(); + + // Check the simulation API outputs the translated V1 event rather than the V2 event as it is + let payload = json!({ + "type": "entry_function_payload", + "function": "0x1::aptos_account::transfer", + "type_arguments": [], + "arguments": [ + account2.address().to_hex_literal(), "102" + ] + }); + let resp = context.simulate_transaction(account1, payload, 200).await; + + let is_expected_event = |e: &Value| { + matches_event_details( + e, + "0x1::account::CoinRegisterEvent", + 0, + account2.address(), + 0, + ) && e["data"]["type_info"]["struct_name"] + == format!("0x{}", hex::encode("AptosCoin".to_string().as_bytes())) + }; + + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Transfer coins from account2 to account1, emitting V2 events as the feature is enabled + context + .api_execute_aptos_account_transfer(account1, account2.address(), 102) + .await; + + // Check the event_by_creation_number API outputs the translated V1 event + let resp = context + .gen_events_by_creation_num(&account2.address(), 0) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the event_by_handle API outputs the translated V1 event + let resp = context + .gen_events_by_handle( + &account2.address(), + "0x1::account::Account", + "coin_register_events", + ) + .await; + assert!(is_expected_event(resp.as_array().unwrap().last().unwrap())); + + // Check the accounts-transactions API outputs the translated V1 event + let resp = context + .get( + format!( + "/accounts/{}/transactions?limit=1", + account1.address().to_hex_literal() + ) + .as_str(), + ) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + let hash = resp[0]["hash"].as_str().unwrap(); + let version = resp[0]["version"].as_str().unwrap(); + + // Check the transactions API outputs the translated V1 event + let resp = context + .get(format!("/transactions?start={}&limit=1", version).as_str()) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_hash API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_hash/{}", hash).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_version API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_version/{}", version).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); +} + +fn rotate_authentication_key_payload( + account: &LocalAccount, + new_private_key: &Ed25519PrivateKey, + new_public_key_bytes: Vec, +) -> Value { + let from_scheme = 0; + let to_scheme = 0; + + // Construct a proof challenge struct that proves that + // the user intends to rotate their auth key. + let rotation_proof = RotationProofChallenge { + account_address: CORE_CODE_ADDRESS, + module_name: String::from("account"), + struct_name: String::from("RotationProofChallenge"), + sequence_number: account.sequence_number(), + originator: account.address(), + current_auth_key: AccountAddress::from_bytes(account.authentication_key()).unwrap(), + new_public_key: new_public_key_bytes.clone(), + }; + + let rotation_msg = bcs::to_bytes(&rotation_proof).unwrap(); + + // Sign the rotation message by the current private key and the new private key. + let signature_by_curr_privkey = account.private_key().sign_arbitrary_message(&rotation_msg); + let signature_by_new_privkey = new_private_key.sign_arbitrary_message(&rotation_msg); + + json!({ + "type": "entry_function_payload", + "function": "0x1::account::rotate_authentication_key", + "type_arguments": [], + "arguments": [ + from_scheme, + hex::encode(account.public_key().to_bytes()), + to_scheme, + hex::encode(new_public_key_bytes), + hex::encode(signature_by_curr_privkey.to_bytes()), + hex::encode(signature_by_new_privkey.to_bytes()), + ] + }) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_event_v2_translation_account_key_rotation_event() { + let context = + &mut new_test_context_with_db_sharding_and_internal_indexer(current_function_name!()); + + // Make sure that the MODULE_EVENT_MIGRATION feature is enabled + context + .enable_feature(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + .await; + + // Create two accounts + let account1 = &mut context.api_create_account().await; + let account2 = &mut context.gen_account(); + + // Check the simulation API outputs the translated V1 event rather than the V2 event as it is + let payload = rotate_authentication_key_payload( + account1, + account2.private_key(), + account2.public_key().to_bytes().to_vec(), + ); + let resp = context + .simulate_transaction(account1, payload.clone(), 200) + .await; + + let account1_address = account1.address(); + let account1_authentication_key = account1.authentication_key(); + let is_expected_event = |e: &Value| { + matches_event_details(e, "0x1::account::KeyRotationEvent", 1, account1_address, 0) + && e["data"]["old_authentication_key"] + == format!("0x{}", hex::encode(account1_authentication_key.to_bytes())) + && e["data"]["new_authentication_key"] + == format!( + "0x{}", + hex::encode(account2.authentication_key().to_bytes()) + ) + }; + + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Rotate authentication key, emitting V2 events as the feature is enabled + context.api_execute_txn(account1, payload).await; + context.wait_for_internal_indexer_caught_up().await; + + // Check the event_by_creation_number API outputs the translated V1 event + let resp = context + .gen_events_by_creation_num(&account1.address(), 1) + .await; + assert!(resp.as_array().unwrap().iter().any(is_expected_event)); + + // Check the event_by_handle API outputs the translated V1 event + let resp = context + .gen_events_by_handle( + &account1.address(), + "0x1::account::Account", + "key_rotation_events", + ) + .await; + assert!(resp.as_array().unwrap().iter().any(is_expected_event)); + + // Check the accounts-transactions API outputs the translated V1 event + let resp = context + .get( + format!( + "/accounts/{}/transactions?limit=1", + account1.address().to_hex_literal() + ) + .as_str(), + ) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + let hash = resp[0]["hash"].as_str().unwrap(); + let version = resp[0]["version"].as_str().unwrap(); + + // Check the transactions API outputs the translated V1 event + let resp = context + .get(format!("/transactions?start={}&limit=1", version).as_str()) + .await; + assert!(resp[0]["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_hash API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_hash/{}", hash).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); + + // Check the transactions_by_version API outputs the translated V1 event + let resp = context + .get(format!("/transactions/by_version/{}", version).as_str()) + .await; + assert!(resp["events"] + .as_array() + .unwrap() + .iter() + .any(is_expected_event)); +} diff --git a/api/src/tests/mod.rs b/api/src/tests/mod.rs index 340721b1ce200..8c2bb45be9e8b 100644 --- a/api/src/tests/mod.rs +++ b/api/src/tests/mod.rs @@ -5,6 +5,7 @@ mod accounts_test; mod blocks_test; mod converter_test; +mod event_v2_translation_test; mod events_test; mod index_test; mod invalid_post_request_test; @@ -36,7 +37,7 @@ fn new_test_context_with_config(test_name: String, node_config: NodeConfig) -> T fn new_test_context_with_db_sharding_and_internal_indexer(test_name: String) -> TestContext { let mut node_config = NodeConfig::default(); node_config.storage.rocksdb_configs.enable_storage_sharding = true; - node_config.indexer_db_config = InternalIndexerDBConfig::new(true, true, true, 10); + node_config.indexer_db_config = InternalIndexerDBConfig::new(true, true, true, true, 10); let test_context = super_new_test_context(test_name, node_config, false, None); let _ = test_context .get_indexer_reader() @@ -51,6 +52,6 @@ fn new_test_context_with_sharding_and_delayed_internal_indexer( ) -> TestContext { let mut node_config = NodeConfig::default(); node_config.storage.rocksdb_configs.enable_storage_sharding = true; - node_config.indexer_db_config = InternalIndexerDBConfig::new(true, true, true, 1); + node_config.indexer_db_config = InternalIndexerDBConfig::new(true, true, true, true, 1); super_new_test_context(test_name, node_config, false, end_version) } diff --git a/api/src/transactions.rs b/api/src/transactions.rs index 56495de0ad2c6..03f0f73e73ced 100644 --- a/api/src/transactions.rs +++ b/api/src/transactions.rs @@ -1439,11 +1439,16 @@ impl TransactionsApi { output.gas_used(), exe_status, ); + let mut events = output.events().to_vec(); + let _ = self + .context + .translate_v2_to_v1_events_for_simulation(&mut events); + let simulated_txn = TransactionOnChainData { version, transaction: txn, info, - events: output.events().to_vec(), + events, accumulator_root_hash: zero_hash, changes: output.write_set().clone(), }; diff --git a/api/test-context/src/test_context.rs b/api/test-context/src/test_context.rs index aa17398519ebb..86509463ebe21 100644 --- a/api/test-context/src/test_context.rs +++ b/api/test-context/src/test_context.rs @@ -367,6 +367,54 @@ impl TestContext { ) } + pub async fn enable_feature(&mut self, feature: u64) { + // script { + // fun main(root: &signer, feature: u64) { + // let aptos_framework = aptos_framework::aptos_governance::get_signer_testnet_only(root, @0x1); + // std::features::change_feature_flags_for_next_epoch(&aptos_framework, vector[feature], vector[]); + // aptos_framework::aptos_governance::reconfigure(&aptos_framework); + // std::features::on_new_epoch(&aptos_framework); + // } + // } + let mut root = self.root_account().await; + self.api_execute_script( + &mut root, + "a11ceb0b0700000a06010004030418051c1707336f08a2012006c201260000000100020301000101030502000100040602000101050602000102060c03010c0002060c05010303060c0a030a0301060c106170746f735f676f7665726e616e6365086665617475726573176765745f7369676e65725f746573746e65745f6f6e6c79236368616e67655f666561747572655f666c6167735f666f725f6e6578745f65706f63680b7265636f6e6669677572650c6f6e5f6e65775f65706f63680000000000000000000000000000000000000000000000000000000000000001052000000000000000000000000000000000000000000000000000000000000000010a0301000000010e0b00070011000c020e020b0140040100000000000000070111010e0211020e02110302", + json!([]), + json!([feature.to_string()]), + ).await; + self.wait_for_internal_indexer_caught_up().await; + } + + pub async fn disable_feature(&mut self, feature: u64) { + // script { + // fun main(root: &signer, feature: u64) { + // let aptos_framework = aptos_framework::aptos_governance::get_signer_testnet_only(root, @0x1); + // std::features::change_feature_flags_for_next_epoch(&aptos_framework, vector[], vector[feature]); + // aptos_framework::aptos_governance::reconfigure(&aptos_framework); + // std::features::on_new_epoch(&aptos_framework); + // } + // } + let mut root = self.root_account().await; + self.api_execute_script( + &mut root, + "a11ceb0b0700000a06010004030418051c1707336f08a2012006c201260000000100020301000101030502000100040602000101050602000102060c03010c0002060c05010303060c0a030a0301060c106170746f735f676f7665726e616e6365086665617475726573176765745f7369676e65725f746573746e65745f6f6e6c79236368616e67655f666561747572655f666c6167735f666f725f6e6578745f65706f63680b7265636f6e6669677572650c6f6e5f6e65775f65706f63680000000000000000000000000000000000000000000000000000000000000001052000000000000000000000000000000000000000000000000000000000000000010a0301000000010e0b00070011000c020e0207010b014004010000000000000011010e0211020e02110302", + json!([]), + json!([feature.to_string()]), + ).await; + self.wait_for_internal_indexer_caught_up().await; + } + + pub async fn is_feature_enabled(&self, feature: u64) -> bool { + let request = json!({ + "function":"0x1::features::is_enabled", + "arguments": vec![feature.to_string()], + "type_arguments": Vec::::new(), + }); + let resp = self.post("/view", request).await; + resp[0].as_bool().unwrap() + } + pub fn latest_state_view(&self) -> DbStateView { self.context .state_view_at_version(self.get_latest_ledger_info().version()) @@ -395,6 +443,46 @@ impl TestContext { account } + pub async fn api_create_account(&mut self) -> LocalAccount { + let root = &mut self.root_account().await; + let account = self.gen_account(); + self.api_execute_aptos_account_transfer(root, account.address(), TRANSFER_AMOUNT) + .await; + account + } + + pub async fn api_execute_aptos_account_transfer( + &mut self, + sender: &mut LocalAccount, + receiver: AccountAddress, + amount: u64, + ) { + self.api_execute_entry_function( + sender, + "0x1::aptos_account::transfer", + json!([]), + json!([receiver.to_hex_literal(), amount.to_string()]), + ) + .await; + self.wait_for_internal_indexer_caught_up().await; + } + + pub async fn wait_for_internal_indexer_caught_up(&self) { + let (internal_indexer_ledger_info_opt, storage_ledger_info) = self + .context + .get_latest_internal_and_storage_ledger_info::() + .expect("cannot get ledger info"); + if let Some(mut internal_indexer_ledger_info) = internal_indexer_ledger_info_opt { + while internal_indexer_ledger_info.version() < storage_ledger_info.version() { + tokio::time::sleep(Duration::from_millis(10)).await; + internal_indexer_ledger_info = self + .context + .get_latest_internal_indexer_ledger_info::() + .expect("cannot get internal indexer version"); + } + } + } + pub async fn create_user_account(&self, account: &LocalAccount) -> SignedTransaction { let mut tc = self.root_account().await; self.create_user_account_by(&mut tc, account) @@ -861,6 +949,27 @@ impl TestContext { .await; } + pub async fn api_execute_script( + &mut self, + account: &mut LocalAccount, + bytecode: &str, + type_args: serde_json::Value, + args: serde_json::Value, + ) { + self.api_execute_txn( + account, + json!({ + "type": "script_payload", + "code": { + "bytecode": bytecode, + }, + "type_arguments": type_args, + "arguments": args + }), + ) + .await; + } + pub async fn api_execute_txn(&mut self, account: &mut LocalAccount, payload: Value) { self.api_execute_txn_expecting(account, payload, 202).await; } diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 4cd1b2a9db29d..abd45752be592 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -132,6 +132,7 @@ pub enum FeatureFlag { TransactionSimulationEnhancement, CollectionOwner, EnableLoaderV2, + AccountAndCoinModuleEventMigration, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -349,6 +350,9 @@ impl From for AptosFeatureFlag { }, FeatureFlag::CollectionOwner => AptosFeatureFlag::COLLECTION_OWNER, FeatureFlag::EnableLoaderV2 => AptosFeatureFlag::ENABLE_LOADER_V2, + FeatureFlag::AccountAndCoinModuleEventMigration => { + AptosFeatureFlag::ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION + }, } } } @@ -493,6 +497,9 @@ impl From for FeatureFlag { }, AptosFeatureFlag::COLLECTION_OWNER => FeatureFlag::CollectionOwner, AptosFeatureFlag::ENABLE_LOADER_V2 => FeatureFlag::EnableLoaderV2, + AptosFeatureFlag::ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION => { + FeatureFlag::AccountAndCoinModuleEventMigration + }, } } } diff --git a/aptos-move/framework/aptos-framework/doc/account.md b/aptos-move/framework/aptos-framework/doc/account.md index 383d4918dd700..a2f3a491b6a95 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -2033,7 +2033,7 @@ in the event of key recovery. ); table::add(address_map, new_auth_key, originating_addr); - if (std::features::module_event_migration_enabled()) { + if (std::features::account_and_coin_module_event_migration_enabled()) { event::emit(KeyRotation { account: originating_addr, old_authentication_key: account_resource.authentication_key, @@ -2260,7 +2260,7 @@ Coin management methods.
public(friend) fun register_coin<CoinType>(account_addr: address) acquires Account {
     let account = borrow_global_mut<Account>(account_addr);
-    if (std::features::module_event_migration_enabled()) {
+    if (std::features::account_and_coin_module_event_migration_enabled()) {
         event::emit(
             CoinRegister {
                 account: account_addr,
diff --git a/aptos-move/framework/aptos-framework/doc/coin.md b/aptos-move/framework/aptos-framework/doc/coin.md
index 273f2a2f3e1f0..b58c6bacbf4d8 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -2577,7 +2577,7 @@ Deposit the coin balance into the recipient's account and emit an event.
             !coin_store.frozen,
             error::permission_denied(EFROZEN),
         );
-        if (std::features::module_event_migration_enabled()) {
+        if (std::features::account_and_coin_module_event_migration_enabled()) {
             event::emit(
                 CoinDeposit { coin_type: type_name<CoinType>(), account: account_addr, amount: coin.value }
             );
@@ -3178,7 +3178,7 @@ Withdraw specified amount of coin CoinType from the si
             !coin_store.frozen,
             error::permission_denied(EFROZEN),
         );
-        if (std::features::module_event_migration_enabled()) {
+        if (std::features::account_and_coin_module_event_migration_enabled()) {
             event::emit(
                 CoinWithdraw {
                     coin_type: type_name<CoinType>(), account: account_addr, amount: coin_amount_to_withdraw
diff --git a/aptos-move/framework/aptos-framework/sources/account.move b/aptos-move/framework/aptos-framework/sources/account.move
index 0487ed630c4e2..8e49c2b3ef56b 100644
--- a/aptos-move/framework/aptos-framework/sources/account.move
+++ b/aptos-move/framework/aptos-framework/sources/account.move
@@ -741,7 +741,7 @@ module aptos_framework::account {
         );
         table::add(address_map, new_auth_key, originating_addr);
 
-        if (std::features::module_event_migration_enabled()) {
+        if (std::features::account_and_coin_module_event_migration_enabled()) {
             event::emit(KeyRotation {
                 account: originating_addr,
                 old_authentication_key: account_resource.authentication_key,
@@ -860,7 +860,7 @@ module aptos_framework::account {
 
     public(friend) fun register_coin(account_addr: address) acquires Account {
         let account = borrow_global_mut(account_addr);
-        if (std::features::module_event_migration_enabled()) {
+        if (std::features::account_and_coin_module_event_migration_enabled()) {
             event::emit(
                 CoinRegister {
                     account: account_addr,
diff --git a/aptos-move/framework/aptos-framework/sources/coin.move b/aptos-move/framework/aptos-framework/sources/coin.move
index f1d9a81962785..fa4e3d1d4b8db 100644
--- a/aptos-move/framework/aptos-framework/sources/coin.move
+++ b/aptos-move/framework/aptos-framework/sources/coin.move
@@ -798,7 +798,7 @@ module aptos_framework::coin {
                 !coin_store.frozen,
                 error::permission_denied(EFROZEN),
             );
-            if (std::features::module_event_migration_enabled()) {
+            if (std::features::account_and_coin_module_event_migration_enabled()) {
                 event::emit(
                     CoinDeposit { coin_type: type_name(), account: account_addr, amount: coin.value }
                 );
@@ -1059,7 +1059,7 @@ module aptos_framework::coin {
                 !coin_store.frozen,
                 error::permission_denied(EFROZEN),
             );
-            if (std::features::module_event_migration_enabled()) {
+            if (std::features::account_and_coin_module_event_migration_enabled()) {
                 event::emit(
                     CoinWithdraw {
                         coin_type: type_name(), account: account_addr, amount: coin_amount_to_withdraw
diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md
index 3919625f4ff9c..58f8fb0bcec76 100644
--- a/aptos-move/framework/move-stdlib/doc/features.md
+++ b/aptos-move/framework/move-stdlib/doc/features.md
@@ -133,6 +133,8 @@ return true.
 -  [Function `transaction_simulation_enhancement_enabled`](#0x1_features_transaction_simulation_enhancement_enabled)
 -  [Function `get_collection_owner_feature`](#0x1_features_get_collection_owner_feature)
 -  [Function `is_collection_owner_enabled`](#0x1_features_is_collection_owner_enabled)
+-  [Function `get_account_and_coin_module_event_migration_feature`](#0x1_features_get_account_and_coin_module_event_migration_feature)
+-  [Function `account_and_coin_module_event_migration_enabled`](#0x1_features_account_and_coin_module_event_migration_enabled)
 -  [Function `change_feature_flags`](#0x1_features_change_feature_flags)
 -  [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal)
 -  [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch)
@@ -241,6 +243,18 @@ Lifetime: transient
 
 
 
+
+
+Whether aptos_framwork enables the behavior of module event migration for the account and coin modules.
+
+Lifetime: transient
+
+
+
const ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION: u64 = 82;
+
+ + + @@ -3274,6 +3288,52 @@ Deprecated feature + + + + +## Function `get_account_and_coin_module_event_migration_feature` + + + +
public fun get_account_and_coin_module_event_migration_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_account_and_coin_module_event_migration_feature(): u64 { ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION }
+
+ + + +
+ + + +## Function `account_and_coin_module_event_migration_enabled` + + + +
public fun account_and_coin_module_event_migration_enabled(): bool
+
+ + + +
+Implementation + + +
public fun account_and_coin_module_event_migration_enabled(): bool acquires Features {
+    is_enabled(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 2bdba4056eaae..00c6591243eb6 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -607,6 +607,17 @@ module std::features { is_enabled(COLLECTION_OWNER) } + /// Whether aptos_framwork enables the behavior of module event migration for the account and coin modules. + /// + /// Lifetime: transient + const ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION: u64 = 82; + + public fun get_account_and_coin_module_event_migration_feature(): u64 { ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION } + + public fun account_and_coin_module_event_migration_enabled(): bool acquires Features { + is_enabled(ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/config/src/config/internal_indexer_db_config.rs b/config/src/config/internal_indexer_db_config.rs index 323d02c090b45..eb2cc34897d72 100644 --- a/config/src/config/internal_indexer_db_config.rs +++ b/config/src/config/internal_indexer_db_config.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; pub struct InternalIndexerDBConfig { pub enable_transaction: bool, pub enable_event: bool, + pub enable_event_v2_translation: bool, pub enable_statekeys: bool, pub batch_size: usize, } @@ -20,12 +21,14 @@ impl InternalIndexerDBConfig { pub fn new( enable_transaction: bool, enable_event: bool, + enable_event_v2_translation: bool, enable_statekeys: bool, batch_size: usize, ) -> Self { Self { enable_transaction, enable_event, + enable_event_v2_translation, enable_statekeys, batch_size, } @@ -39,6 +42,10 @@ impl InternalIndexerDBConfig { self.enable_event } + pub fn enable_event_translation(&self) -> bool { + self.enable_event_v2_translation + } + pub fn enable_statekeys(&self) -> bool { self.enable_statekeys } @@ -57,6 +64,7 @@ impl Default for InternalIndexerDBConfig { Self { enable_transaction: false, enable_event: false, + enable_event_v2_translation: false, enable_statekeys: false, batch_size: 10_000, } diff --git a/crates/aptos-rosetta/src/types/objects.rs b/crates/aptos-rosetta/src/types/objects.rs index e1764b6b3b309..2a4cdc575cb21 100644 --- a/crates/aptos-rosetta/src/types/objects.rs +++ b/crates/aptos-rosetta/src/types/objects.rs @@ -2175,8 +2175,8 @@ fn get_amount_from_event_v2( ) -> Vec { filter_v2_events(type_tag, events, |event| { if let Ok(event) = bcs::from_bytes::(event.event_data()) { - if event.account == account_address && &event.coin_type == coin_type { - Some(event.amount) + if event.account() == &account_address && event.coin_type() == coin_type { + Some(event.amount()) } else { None } diff --git a/ecosystem/indexer-grpc/indexer-grpc-table-info/src/internal_indexer_db_service.rs b/ecosystem/indexer-grpc/indexer-grpc-table-info/src/internal_indexer_db_service.rs index c795a724e9324..5e30c42440966 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-table-info/src/internal_indexer_db_service.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-table-info/src/internal_indexer_db_service.rs @@ -50,7 +50,9 @@ impl InternalIndexerDBService { open_internal_indexer_db(db_path_buf.as_path(), &rocksdb_config) .expect("Failed to open internal indexer db"), ); - let internal_indexer_db_config = InternalIndexerDBConfig::new(true, true, true, 10_000); + + let internal_indexer_db_config = + InternalIndexerDBConfig::new(true, true, true, true, 10_000); Some(InternalIndexerDB::new(arc_db, internal_indexer_db_config)) } @@ -136,6 +138,19 @@ impl InternalIndexerDBService { } } + if node_config.indexer_db_config.enable_event_v2_translation { + let event_v2_translation_start_version = self + .db_indexer + .indexer_db + .get_event_v2_translation_version()? + .map_or(0, |v| v + 1); + if start_version != event_v2_translation_start_version { + panic!( + "Cannot start event v2 translation indexer because the progress doesn't match." + ); + } + } + Ok(start_version) } diff --git a/storage/aptosdb/src/event_store/mod.rs b/storage/aptosdb/src/event_store/mod.rs index 1ff4d31c330d4..e909ab782a4f7 100644 --- a/storage/aptosdb/src/event_store/mod.rs +++ b/storage/aptosdb/src/event_store/mod.rs @@ -19,13 +19,14 @@ use aptos_crypto::{ }; use aptos_db_indexer_schemas::schema::{ event_by_key::EventByKeySchema, event_by_version::EventByVersionSchema, + translated_v1_event::TranslatedV1EventSchema, }; use aptos_schemadb::{iterator::SchemaIterator, schema::ValueCodec, ReadOptions, SchemaBatch, DB}; use aptos_storage_interface::{db_ensure as ensure, db_other_bail, AptosDbError, Result}; use aptos_types::{ account_address::AccountAddress, account_config::{new_block_event_key, NewBlockEvent}, - contract_event::ContractEvent, + contract_event::{ContractEvent, ContractEventV1}, event::EventKey, proof::position::Position, transaction::Version, diff --git a/storage/aptosdb/src/state_store/mod.rs b/storage/aptosdb/src/state_store/mod.rs index 38ab16199c188..4f26b4aeb6941 100644 --- a/storage/aptosdb/src/state_store/mod.rs +++ b/storage/aptosdb/src/state_store/mod.rs @@ -110,7 +110,7 @@ pub(crate) struct StateStore { buffered_state: Mutex, buffered_state_target_items: usize, smt_ancestors: Mutex>, - internal_indexer_db: Option, + pub internal_indexer_db: Option, } impl Deref for StateStore { diff --git a/storage/indexer/src/db_indexer.rs b/storage/indexer/src/db_indexer.rs index ac3d18709a068..17f0f7b2818ea 100644 --- a/storage/indexer/src/db_indexer.rs +++ b/storage/indexer/src/db_indexer.rs @@ -1,14 +1,19 @@ // Copyright (c) Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{metrics::TIMER, utils::PrefixedStateValueIterator}; +use crate::{ + event_v2_translator::EventV2TranslationEngine, metrics::TIMER, + utils::PrefixedStateValueIterator, +}; use aptos_config::config::internal_indexer_db_config::InternalIndexerDBConfig; use aptos_db_indexer_schemas::{ metadata::{MetadataKey, MetadataValue, StateSnapshotProgress}, schema::{ event_by_key::EventByKeySchema, event_by_version::EventByVersionSchema, + event_sequence_number::EventSequenceNumberSchema, indexer_metadata::InternalIndexerMetadataSchema, state_keys::StateKeysSchema, transaction_by_account::TransactionByAccountSchema, + translated_v1_event::TranslatedV1EventSchema, }, utils::{ error_if_too_many_requested, get_first_seq_num_and_limit, AccountTransactionVersionIter, @@ -17,25 +22,29 @@ use aptos_db_indexer_schemas::{ }; use aptos_schemadb::{SchemaBatch, DB}; use aptos_storage_interface::{ - db_ensure as ensure, db_other_bail as bail, AptosDbError, DbReader, Result, + db_ensure as ensure, db_other_bail as bail, state_view::LatestDbStateCheckpointView, + AptosDbError, DbReader, Result, }; use aptos_types::{ account_address::AccountAddress, - contract_event::{ContractEvent, EventWithVersion}, + contract_event::{ContractEvent, ContractEventV1, ContractEventV2, EventWithVersion}, event::EventKey, indexer::indexer_db_reader::Order, state_store::{ state_key::{prefix::StateKeyPrefix, StateKey}, state_value::StateValue, + TStateView, }, transaction::{AccountTransactionsWithProof, Transaction, Version}, write_set::{TransactionWrite, WriteSet}, }; +use move_core_types::language_storage::StructTag; use std::{ cmp::min, + collections::{HashMap, HashSet}, sync::{ mpsc::{self, Receiver, Sender}, - Arc, + Arc, Mutex, }, thread, }; @@ -114,10 +123,18 @@ impl InternalIndexerDB { self.get_version(&MetadataKey::TransactionVersion) } + pub fn get_event_v2_translation_version(&self) -> Result> { + self.get_version(&MetadataKey::EventV2TranslationVersion) + } + pub fn event_enabled(&self) -> bool { self.config.enable_event } + pub fn event_v2_translation_enabled(&self) -> bool { + self.config.enable_event_v2_translation + } + pub fn transaction_enabled(&self) -> bool { self.config.enable_transaction } @@ -273,6 +290,16 @@ impl InternalIndexerDB { .get::(key)? .map(|v| v.expect_version())) } + + pub fn get_translated_v1_event_by_version_and_index( + &self, + version: Version, + index: u64, + ) -> Result { + self.db + .get::(&(version, index))? + .ok_or_else(|| AptosDbError::NotFound(format!("Event {} of Txn {}", index, version))) + } } pub struct DBIndexer { @@ -280,6 +307,8 @@ pub struct DBIndexer { pub main_db_reader: Arc, sender: Sender>, committer_handle: Option>, + event_sequence_number_cache: Mutex>, + event_v2_translation_engine: EventV2TranslationEngine, } impl Drop for DBIndexer { @@ -310,6 +339,8 @@ impl DBIndexer { main_db_reader: db_reader, sender, committer_handle: Some(committer_handle), + event_sequence_number_cache: Mutex::new(HashMap::new()), + event_v2_translation_engine: EventV2TranslationEngine::new(), } } @@ -367,6 +398,7 @@ impl DBIndexer { // This promises num_transactions should be readable from main db let mut db_iter = self.get_main_db_iter(version, num_transactions)?; let batch = SchemaBatch::new(); + let mut event_keys: HashSet = HashSet::new(); db_iter.try_for_each(|res| { let (txn, events, writeset) = res?; if let Some(txn) = txn.try_as_signed_user_txn() { @@ -379,7 +411,7 @@ impl DBIndexer { } if self.indexer_db.event_enabled() { - events.iter().enumerate().for_each(|(idx, event)| { + events.iter().enumerate().try_for_each(|(idx, event)| { if let ContractEvent::V1(v1) = event { batch .put::( @@ -394,7 +426,44 @@ impl DBIndexer { ) .expect("Failed to put events by version to a batch"); } - }); + if self.indexer_db.event_v2_translation_enabled() { + if let ContractEvent::V2(v2) = event { + if let Some(translated_v1_event) = + self.translate_event_v2_to_v1(v2).map_err(|e| { + anyhow::anyhow!( + "Failed to translate event: {:?}. Error: {}", + v2, + e + ) + })? + { + let key = *translated_v1_event.key(); + let sequence_number = translated_v1_event.sequence_number(); + self.cache_sequence_number(&key, sequence_number); + event_keys.insert(key); + batch + .put::( + &(key, sequence_number), + &(version, idx as u64), + ) + .expect("Failed to put events by key to a batch"); + batch + .put::( + &(key, version, sequence_number), + &(idx as u64), + ) + .expect("Failed to put events by version to a batch"); + batch + .put::( + &(version, idx as u64), + &translated_v1_event, + ) + .expect("Failed to put translated v1 events to a batch"); + } + } + } + Ok::<(), AptosDbError>(()) + })?; } if self.indexer_db.statekeys_enabled() { @@ -413,6 +482,22 @@ impl DBIndexer { assert_eq!(num_transactions, version - start_version); + if self.indexer_db.event_v2_translation_enabled() { + batch.put::( + &MetadataKey::EventV2TranslationVersion, + &MetadataValue::Version(version - 1), + )?; + + for event_key in event_keys { + batch + .put::( + &event_key, + &self.get_cached_sequence_number(&event_key).unwrap_or(0), + ) + .expect("Failed to put events by key to a batch"); + } + } + if self.indexer_db.transaction_enabled() { batch.put::( &MetadataKey::TransactionVersion, @@ -441,6 +526,60 @@ impl DBIndexer { Ok(version) } + pub fn get_state_value_for_resource( + &self, + address: &AccountAddress, + struct_tag: &StructTag, + ) -> Result> { + let state_view = self + .main_db_reader + .latest_state_checkpoint_view() + .expect("Failed to get state view"); + + //let struct_tag = StructTag::from_str(struct_tag_str)?; + let state_key = StateKey::resource(address, struct_tag)?; + let maybe_state_value = state_view.get_state_value(&state_key)?; + Ok(maybe_state_value) + } + + pub fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + ) -> Result> { + if let Some(translator) = self + .event_v2_translation_engine + .translators + .get(v2.type_tag()) + { + return Ok(Some(translator.translate_event_v2_to_v1(v2, self)?)); + } + Ok(None) + } + + fn cache_sequence_number(&self, event_key: &EventKey, sequence_number: u64) { + let mut cache = self.event_sequence_number_cache.lock().unwrap(); + cache.insert(*event_key, sequence_number); + } + + fn get_cached_sequence_number(&self, event_key: &EventKey) -> Option { + let cache = self.event_sequence_number_cache.lock().unwrap(); + cache.get(event_key).copied() + } + + pub fn get_next_sequence_number(&self, event_key: &EventKey, default: u64) -> Result { + let mut cache = self.event_sequence_number_cache.lock().unwrap(); + if let Some(seq) = cache.get_mut(event_key) { + Ok(*seq + 1) + } else { + let seq = self + .indexer_db + .db + .get::(event_key)? + .map_or(default, |seq| seq + 1); + Ok(seq) + } + } + pub fn get_account_transactions( &self, address: AccountAddress, @@ -550,9 +689,16 @@ impl DBIndexer { let mut events_with_version = event_indices .into_iter() .map(|(seq, ver, idx)| { - let event = self + let event = match self .main_db_reader - .get_event_by_version_and_index(ver, idx)?; + .get_event_by_version_and_index(ver, idx)? + { + event @ ContractEvent::V1(_) => event, + ContractEvent::V2(_) => ContractEvent::V1( + self.indexer_db + .get_translated_v1_event_by_version_and_index(ver, idx)?, + ), + }; let v0 = match &event { ContractEvent::V1(event) => event, ContractEvent::V2(_) => bail!("Unexpected module event"), @@ -563,6 +709,7 @@ impl DBIndexer { seq, v0.sequence_number() ); + Ok(EventWithVersion::new(ver, event)) }) .collect::>>()?; diff --git a/storage/indexer/src/event_v2_translator.rs b/storage/indexer/src/event_v2_translator.rs new file mode 100644 index 0000000000000..31fc075644e41 --- /dev/null +++ b/storage/indexer/src/event_v2_translator.rs @@ -0,0 +1,180 @@ +// Copyright (c) Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::db_indexer::DBIndexer; +use aptos_storage_interface::Result; +use aptos_types::{ + account_config::{ + AccountResource, CoinDeposit, CoinRegister, CoinRegisterEvent, CoinStoreResource, + CoinWithdraw, DepositEvent, KeyRotation, KeyRotationEvent, WithdrawEvent, + COIN_DEPOSIT_TYPE, COIN_REGISTER_EVENT_TYPE, COIN_REGISTER_TYPE, COIN_WITHDRAW_TYPE, + DEPOSIT_EVENT_TYPE, KEY_ROTATION_EVENT_TYPE, KEY_ROTATION_TYPE, WITHDRAW_EVENT_TYPE, + }, + contract_event::{ContractEventV1, ContractEventV2}, + event::EventKey, + DummyCoinType, +}; +use move_core_types::language_storage::{StructTag, TypeTag}; +use std::{collections::HashMap, str::FromStr}; + +pub trait EventV2Translator: Send + Sync { + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + db_indexer: &DBIndexer, + ) -> Result; +} + +pub struct EventV2TranslationEngine { + // Map from event type to translator + pub translators: HashMap>, +} + +impl EventV2TranslationEngine { + pub fn new() -> Self { + let mut translators: HashMap> = + HashMap::new(); + translators.insert(COIN_DEPOSIT_TYPE.clone(), Box::new(CoinDepositTranslator)); + translators.insert(COIN_WITHDRAW_TYPE.clone(), Box::new(CoinWithdrawTranslator)); + translators.insert(COIN_REGISTER_TYPE.clone(), Box::new(CoinRegisterTranslator)); + translators.insert(KEY_ROTATION_TYPE.clone(), Box::new(KeyRotationTranslator)); + Self { translators } + } +} + +impl Default for EventV2TranslationEngine { + fn default() -> Self { + Self::new() + } +} + +struct CoinDepositTranslator; +impl EventV2Translator for CoinDepositTranslator { + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + db_indexer: &DBIndexer, + ) -> Result { + let coin_deposit = CoinDeposit::try_from_bytes(v2.event_data())?; + let struct_tag_str = format!("0x1::coin::CoinStore<{}>", coin_deposit.coin_type()); + let struct_tag = StructTag::from_str(&struct_tag_str)?; + let (key, sequence_number) = if let Some(state_value) = + db_indexer.get_state_value_for_resource(coin_deposit.account(), &struct_tag)? + { + // We can use `DummyCoinType` as it does not affect the correctness of deserialization. + let coin_store_resource: CoinStoreResource = + bcs::from_bytes(state_value.bytes())?; + let key = *coin_store_resource.deposit_events().key(); + let sequence_number = db_indexer + .get_next_sequence_number(&key, coin_store_resource.deposit_events().count())?; + (key, sequence_number) + } else { + (EventKey::new(2, *coin_deposit.account()), 0) + }; + let deposit_event = DepositEvent::new(coin_deposit.amount()); + Ok(ContractEventV1::new( + key, + sequence_number, + DEPOSIT_EVENT_TYPE.clone(), + bcs::to_bytes(&deposit_event)?, + )) + } +} + +struct CoinWithdrawTranslator; +impl EventV2Translator for CoinWithdrawTranslator { + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + db_indexer: &DBIndexer, + ) -> Result { + let coin_withdraw = CoinWithdraw::try_from_bytes(v2.event_data())?; + let struct_tag_str = format!("0x1::coin::CoinStore<{}>", coin_withdraw.coin_type()); + let struct_tag = StructTag::from_str(&struct_tag_str)?; + let (key, sequence_number) = if let Some(state_value) = + db_indexer.get_state_value_for_resource(coin_withdraw.account(), &struct_tag)? + { + // We can use `DummyCoinType` as it does not affect the correctness of deserialization. + let coin_store_resource: CoinStoreResource = + bcs::from_bytes(state_value.bytes())?; + let key = *coin_store_resource.withdraw_events().key(); + let sequence_number = db_indexer + .get_next_sequence_number(&key, coin_store_resource.withdraw_events().count())?; + (key, sequence_number) + } else { + (EventKey::new(2, *coin_withdraw.account()), 0) + }; + let withdraw_event = WithdrawEvent::new(coin_withdraw.amount()); + Ok(ContractEventV1::new( + key, + sequence_number, + WITHDRAW_EVENT_TYPE.clone(), + bcs::to_bytes(&withdraw_event)?, + )) + } +} + +struct CoinRegisterTranslator; +impl EventV2Translator for CoinRegisterTranslator { + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + db_indexer: &DBIndexer, + ) -> Result { + let coin_register = CoinRegister::try_from_bytes(v2.event_data())?; + let struct_tag_str = "0x1::account::Account".to_string(); + let struct_tag = StructTag::from_str(&struct_tag_str)?; + let (key, sequence_number) = if let Some(state_value) = + db_indexer.get_state_value_for_resource(coin_register.account(), &struct_tag)? + { + let account_resource: AccountResource = bcs::from_bytes(state_value.bytes())?; + let key = *account_resource.coin_register_events().key(); + let sequence_number = db_indexer + .get_next_sequence_number(&key, account_resource.coin_register_events().count())?; + (key, sequence_number) + } else { + (EventKey::new(0, *coin_register.account()), 0) + }; + let coin_register_event = CoinRegisterEvent::new(coin_register.type_info().clone()); + Ok(ContractEventV1::new( + key, + sequence_number, + COIN_REGISTER_EVENT_TYPE.clone(), + bcs::to_bytes(&coin_register_event)?, + )) + } +} + +struct KeyRotationTranslator; +impl EventV2Translator for KeyRotationTranslator { + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + db_indexer: &DBIndexer, + ) -> Result { + let key_rotation = KeyRotation::try_from_bytes(v2.event_data())?; + let struct_tag_str = "0x1::account::Account".to_string(); + let struct_tag = StructTag::from_str(&struct_tag_str)?; + let (key, sequence_number) = if let Some(state_value) = + db_indexer.get_state_value_for_resource(key_rotation.account(), &struct_tag)? + { + let account_resource: AccountResource = bcs::from_bytes(state_value.bytes())?; + let key = *account_resource.key_rotation_events().key(); + let sequence_number = db_indexer + .get_next_sequence_number(&key, account_resource.key_rotation_events().count())?; + (key, sequence_number) + } else { + (EventKey::new(1, *key_rotation.account()), 0) + }; + let key_rotation_event = KeyRotationEvent::new( + key_rotation.old_authentication_key().clone(), + key_rotation.new_authentication_key().clone(), + ); + Ok(ContractEventV1::new( + key, + sequence_number, + KEY_ROTATION_EVENT_TYPE.clone(), + bcs::to_bytes(&key_rotation_event)?, + )) + } +} diff --git a/storage/indexer/src/indexer_reader.rs b/storage/indexer/src/indexer_reader.rs index 535d043d50119..0a249a0fed4c2 100644 --- a/storage/indexer/src/indexer_reader.rs +++ b/storage/indexer/src/indexer_reader.rs @@ -5,7 +5,7 @@ use crate::{db_indexer::DBIndexer, db_v2::IndexerAsyncV2}; use anyhow::anyhow; use aptos_types::{ account_address::AccountAddress, - contract_event::EventWithVersion, + contract_event::{ContractEventV1, ContractEventV2, EventWithVersion}, event::EventKey, indexer::indexer_db_reader::{IndexerReader, Order}, state_store::{ @@ -157,4 +157,35 @@ impl IndexerReader for IndexerReaders { } anyhow::bail!("DB indexer reader is not available") } + + fn get_translated_v1_event_by_version_and_index( + &self, + version: Version, + index: u64, + ) -> anyhow::Result { + if let Some(db_indexer_reader) = &self.db_indexer_reader { + if db_indexer_reader.indexer_db.event_v2_translation_enabled() { + return Ok(db_indexer_reader + .indexer_db + .get_translated_v1_event_by_version_and_index(version, index)?); + } else { + anyhow::bail!("Event translation is not enabled") + } + } + anyhow::bail!("DB indexer reader is not available") + } + + fn translate_event_v2_to_v1( + &self, + v2: &ContractEventV2, + ) -> anyhow::Result> { + if let Some(db_indexer_reader) = &self.db_indexer_reader { + if db_indexer_reader.indexer_db.event_v2_translation_enabled() { + return Ok(db_indexer_reader.translate_event_v2_to_v1(v2)?); + } else { + anyhow::bail!("Event translation is not enabled") + } + } + anyhow::bail!("DB indexer reader is not available") + } } diff --git a/storage/indexer/src/lib.rs b/storage/indexer/src/lib.rs index 7a041c5dbbaca..bf9e00d1a0365 100644 --- a/storage/indexer/src/lib.rs +++ b/storage/indexer/src/lib.rs @@ -6,6 +6,7 @@ mod db; pub mod db_indexer; pub mod db_ops; pub mod db_v2; +pub mod event_v2_translator; pub mod indexer_reader; mod metrics; mod utils; diff --git a/storage/indexer_schemas/src/metadata.rs b/storage/indexer_schemas/src/metadata.rs index 940f724da79d0..0168eb34470b8 100644 --- a/storage/indexer_schemas/src/metadata.rs +++ b/storage/indexer_schemas/src/metadata.rs @@ -38,6 +38,7 @@ pub enum MetadataKey { EventVersion, StateVersion, TransactionVersion, + EventV2TranslationVersion, } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/storage/indexer_schemas/src/schema/event_sequence_number/mod.rs b/storage/indexer_schemas/src/schema/event_sequence_number/mod.rs new file mode 100644 index 0000000000000..5f50a75c46aed --- /dev/null +++ b/storage/indexer_schemas/src/schema/event_sequence_number/mod.rs @@ -0,0 +1,53 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! This module defines physical storage schema for event sequence numbers for associated event keys, +//! specifically for translated v1 events. +//! +//! ```text +//! |<--key---->|<-value->| +//! | event_key | seq_num | +//! ``` + +use crate::schema::EVENT_SEQUENCE_NUMBER_CF_NAME; +use anyhow::Result; +use aptos_schemadb::{ + define_pub_schema, + schema::{KeyCodec, ValueCodec}, +}; +use aptos_types::event::EventKey; + +define_pub_schema!( + EventSequenceNumberSchema, + Key, + Value, + EVENT_SEQUENCE_NUMBER_CF_NAME +); + +type SeqNum = u64; +type Key = EventKey; +type Value = SeqNum; + +impl KeyCodec for Key { + fn encode_key(&self) -> Result> { + Ok(bcs::to_bytes(self)?) + } + + fn decode_key(data: &[u8]) -> Result { + Ok(bcs::from_bytes(data)?) + } +} + +impl ValueCodec for Value { + fn encode_value(&self) -> Result> { + Ok(bcs::to_bytes(self)?) + } + + fn decode_value(data: &[u8]) -> Result { + Ok(bcs::from_bytes(data)?) + } +} + +#[cfg(test)] +mod test; diff --git a/storage/indexer_schemas/src/schema/event_sequence_number/test.rs b/storage/indexer_schemas/src/schema/event_sequence_number/test.rs new file mode 100644 index 0000000000000..dc078e160ff87 --- /dev/null +++ b/storage/indexer_schemas/src/schema/event_sequence_number/test.rs @@ -0,0 +1,19 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::*; +use aptos_schemadb::{schema::fuzzing::assert_encode_decode, test_no_panic_decoding}; +use proptest::prelude::*; + +proptest! { + #[test] + fn test_encode_decode( + event_key in any::(), + seq_num in any::(), + ) { + assert_encode_decode::(&event_key, &seq_num); + } +} + +test_no_panic_decoding!(EventSequenceNumberSchema); diff --git a/storage/indexer_schemas/src/schema/mod.rs b/storage/indexer_schemas/src/schema/mod.rs index 0f4dfd4c7bcd6..31c7e6c25267e 100644 --- a/storage/indexer_schemas/src/schema/mod.rs +++ b/storage/indexer_schemas/src/schema/mod.rs @@ -8,10 +8,14 @@ pub mod event_by_key; pub mod event_by_version; +pub mod event_sequence_number; pub mod indexer_metadata; pub mod state_keys; pub mod table_info; pub mod transaction_by_account; +pub mod translated_v1_event; + +use anyhow::ensure; use aptos_schemadb::ColumnFamilyName; pub const DEFAULT_COLUMN_FAMILY_NAME: ColumnFamilyName = "default"; @@ -22,6 +26,8 @@ pub const EVENT_BY_KEY_CF_NAME: ColumnFamilyName = "event_by_key"; pub const EVENT_BY_VERSION_CF_NAME: ColumnFamilyName = "event_by_version"; pub const TRANSACTION_BY_ACCOUNT_CF_NAME: ColumnFamilyName = "transaction_by_account"; pub const STATE_KEYS_CF_NAME: ColumnFamilyName = "state_keys"; +pub const TRANSLATED_V1_EVENT_CF_NAME: ColumnFamilyName = "translated_v1_event"; +pub const EVENT_SEQUENCE_NUMBER_CF_NAME: ColumnFamilyName = "event_sequence_number"; pub fn column_families() -> Vec { vec![ @@ -39,5 +45,17 @@ pub fn internal_indexer_column_families() -> Vec { EVENT_BY_VERSION_CF_NAME, TRANSACTION_BY_ACCOUNT_CF_NAME, STATE_KEYS_CF_NAME, + TRANSLATED_V1_EVENT_CF_NAME, + EVENT_SEQUENCE_NUMBER_CF_NAME, ] } + +fn ensure_slice_len_eq(data: &[u8], len: usize) -> anyhow::Result<()> { + ensure!( + data.len() == len, + "Unexpected data len {}, expected {}.", + data.len(), + len, + ); + Ok(()) +} diff --git a/storage/indexer_schemas/src/schema/translated_v1_event/mod.rs b/storage/indexer_schemas/src/schema/translated_v1_event/mod.rs new file mode 100644 index 0000000000000..9f196482389d8 --- /dev/null +++ b/storage/indexer_schemas/src/schema/translated_v1_event/mod.rs @@ -0,0 +1,66 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! This module defines physical storage schema for the contract events. +//! +//! A translated v1 event is keyed by the version of the transaction it belongs to and the index of +//! the original v2 event among all events yielded by the same transaction. +//! ```text +//! |<-------key----->|<---value--->| +//! | version | index | event bytes | +//! ``` + +use crate::schema::{ensure_slice_len_eq, TRANSLATED_V1_EVENT_CF_NAME}; +use anyhow::Result; +use aptos_schemadb::{ + define_pub_schema, + schema::{KeyCodec, ValueCodec}, +}; +use aptos_types::{contract_event::ContractEventV1, transaction::Version}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::mem::size_of; + +define_pub_schema!( + TranslatedV1EventSchema, + Key, + ContractEventV1, + TRANSLATED_V1_EVENT_CF_NAME +); + +type Index = u64; +type Key = (Version, Index); + +impl KeyCodec for Key { + fn encode_key(&self) -> Result> { + let (version, index) = *self; + + let mut encoded_key = Vec::with_capacity(size_of::() + size_of::()); + encoded_key.write_u64::(version)?; + encoded_key.write_u64::(index)?; + Ok(encoded_key) + } + + fn decode_key(data: &[u8]) -> Result { + ensure_slice_len_eq(data, size_of::())?; + + let version_size = size_of::(); + + let version = (&data[..version_size]).read_u64::()?; + let index = (&data[version_size..]).read_u64::()?; + Ok((version, index)) + } +} + +impl ValueCodec for ContractEventV1 { + fn encode_value(&self) -> Result> { + bcs::to_bytes(self).map_err(Into::into) + } + + fn decode_value(data: &[u8]) -> Result { + bcs::from_bytes(data).map_err(Into::into) + } +} + +#[cfg(test)] +mod test; diff --git a/storage/indexer_schemas/src/schema/translated_v1_event/test.rs b/storage/indexer_schemas/src/schema/translated_v1_event/test.rs new file mode 100644 index 0000000000000..d28c191121c04 --- /dev/null +++ b/storage/indexer_schemas/src/schema/translated_v1_event/test.rs @@ -0,0 +1,20 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::*; +use aptos_schemadb::{schema::fuzzing::assert_encode_decode, test_no_panic_decoding}; +use proptest::prelude::*; + +proptest! { + #[test] + fn test_encode_decode( + version in any::(), + index in any::(), + event in any::(), + ) { + assert_encode_decode::(&(version, index), &event); + } +} + +test_no_panic_decoding!(TranslatedV1EventSchema); diff --git a/types/src/account_config/events/coin.rs b/types/src/account_config/events/coin.rs deleted file mode 100644 index 1a0cf5844e7e5..0000000000000 --- a/types/src/account_config/events/coin.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - account_config::TypeInfoResource, - move_utils::{move_event_v1::MoveEventV1Type, move_event_v2::MoveEventV2Type}, -}; -use anyhow::Result; -use move_core_types::{ - account_address::AccountAddress, ident_str, identifier::IdentStr, move_resource::MoveStructType, -}; -use serde::{Deserialize, Serialize}; - -/// Struct that represents a SentPaymentEvent. -#[derive(Debug, Serialize, Deserialize)] -pub struct WithdrawEvent { - pub amount: u64, -} - -impl WithdrawEvent { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - bcs::from_bytes(bytes).map_err(Into::into) - } - - /// Get the amount sent or received - pub fn amount(&self) -> u64 { - self.amount - } -} - -impl MoveStructType for WithdrawEvent { - const MODULE_NAME: &'static IdentStr = ident_str!("coin"); - const STRUCT_NAME: &'static IdentStr = ident_str!("WithdrawEvent"); -} - -impl MoveEventV1Type for WithdrawEvent {} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CoinWithdraw { - pub coin_type: String, - pub account: AccountAddress, - pub amount: u64, -} - -impl CoinWithdraw { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - bcs::from_bytes(bytes).map_err(Into::into) - } -} - -impl MoveStructType for CoinWithdraw { - const MODULE_NAME: &'static IdentStr = ident_str!("coin"); - const STRUCT_NAME: &'static IdentStr = ident_str!("CoinWithdraw"); -} - -impl MoveEventV2Type for CoinWithdraw {} - -/// Struct that represents a DepositPaymentEvent. -#[derive(Debug, Serialize, Deserialize)] -pub struct DepositEvent { - pub amount: u64, -} - -impl DepositEvent { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - bcs::from_bytes(bytes).map_err(Into::into) - } - - /// Get the amount sent or received - pub fn amount(&self) -> u64 { - self.amount - } -} - -impl MoveStructType for DepositEvent { - const MODULE_NAME: &'static IdentStr = ident_str!("coin"); - const STRUCT_NAME: &'static IdentStr = ident_str!("DepositEvent"); -} - -impl MoveEventV1Type for DepositEvent {} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CoinDeposit { - pub coin_type: String, - pub account: AccountAddress, - pub amount: u64, -} - -impl CoinDeposit { - pub fn try_from_bytes(bytes: &[u8]) -> Result { - bcs::from_bytes(bytes).map_err(Into::into) - } -} - -impl MoveStructType for CoinDeposit { - const MODULE_NAME: &'static IdentStr = ident_str!("coin"); - const STRUCT_NAME: &'static IdentStr = ident_str!("CoinDeposit"); -} - -impl MoveEventV2Type for CoinDeposit {} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CoinRegister { - pub account: AccountAddress, - pub type_info: TypeInfoResource, -} - -impl MoveStructType for CoinRegister { - const MODULE_NAME: &'static IdentStr = ident_str!("account"); - const STRUCT_NAME: &'static IdentStr = ident_str!("CoinRegister"); -} - -impl MoveEventV2Type for CoinRegister {} diff --git a/types/src/account_config/events/coin_deposit.rs b/types/src/account_config/events/coin_deposit.rs new file mode 100644 index 0000000000000..1d11ee01ee0c5 --- /dev/null +++ b/types/src/account_config/events/coin_deposit.rs @@ -0,0 +1,65 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::move_utils::move_event_v2::MoveEventV2Type; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct CoinDeposit { + coin_type: String, + account: AccountAddress, + amount: u64, +} + +impl CoinDeposit { + pub fn new(coin_type: String, account: AccountAddress, amount: u64) -> Self { + Self { + coin_type, + account, + amount, + } + } + + pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn coin_type(&self) -> &str { + &self.coin_type + } + + pub fn account(&self) -> &AccountAddress { + &self.account + } + + pub fn amount(&self) -> u64 { + self.amount + } +} + +impl MoveStructType for CoinDeposit { + const MODULE_NAME: &'static IdentStr = ident_str!("coin"); + const STRUCT_NAME: &'static IdentStr = ident_str!("CoinDeposit"); +} + +impl MoveEventV2Type for CoinDeposit {} + +pub const COIN_DEPOSIT_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::coin::CoinDeposit"; + +pub static COIN_DEPOSIT_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("coin").to_owned(), + name: ident_str!("CoinDeposit").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/coin_register.rs b/types/src/account_config/events/coin_register.rs new file mode 100644 index 0000000000000..001e36d9b6fa4 --- /dev/null +++ b/types/src/account_config/events/coin_register.rs @@ -0,0 +1,53 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::{account_config::TypeInfo, move_utils::move_event_v2::MoveEventV2Type}; +use anyhow::Result; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CoinRegister { + account: AccountAddress, + type_info: TypeInfo, +} + +impl CoinRegister { + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn account(&self) -> &AccountAddress { + &self.account + } + + pub fn type_info(&self) -> &TypeInfo { + &self.type_info + } +} + +impl MoveStructType for CoinRegister { + const MODULE_NAME: &'static IdentStr = ident_str!("account"); + const STRUCT_NAME: &'static IdentStr = ident_str!("CoinRegister"); +} + +impl MoveEventV2Type for CoinRegister {} + +pub const COIN_REGISTER_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::account::CoinRegister"; + +pub static COIN_REGISTER_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("account").to_owned(), + name: ident_str!("CoinRegister").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/coin_register_event.rs b/types/src/account_config/events/coin_register_event.rs new file mode 100644 index 0000000000000..c8d1cca885a47 --- /dev/null +++ b/types/src/account_config/events/coin_register_event.rs @@ -0,0 +1,45 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::account_config::TypeInfo; +use anyhow::Result; +use move_core_types::{ + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CoinRegisterEvent { + type_info: TypeInfo, +} + +impl CoinRegisterEvent { + pub fn new(type_info: TypeInfo) -> Self { + Self { type_info } + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } +} + +impl MoveStructType for CoinRegisterEvent { + const MODULE_NAME: &'static IdentStr = ident_str!("account"); + const STRUCT_NAME: &'static IdentStr = ident_str!("CoinRegisterEvent"); +} + +pub const COIN_REGISTER_EVENT_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::account::CoinRegisterEvent"; + +pub static COIN_REGISTER_EVENT_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("account").to_owned(), + name: ident_str!("CoinRegisterEvent").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/coin_withdraw.rs b/types/src/account_config/events/coin_withdraw.rs new file mode 100644 index 0000000000000..5e553da8b32f3 --- /dev/null +++ b/types/src/account_config/events/coin_withdraw.rs @@ -0,0 +1,65 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::move_utils::move_event_v2::MoveEventV2Type; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct CoinWithdraw { + coin_type: String, + account: AccountAddress, + amount: u64, +} + +impl CoinWithdraw { + pub fn new(coin_type: String, account: AccountAddress, amount: u64) -> Self { + Self { + coin_type, + account, + amount, + } + } + + pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn coin_type(&self) -> &str { + &self.coin_type + } + + pub fn account(&self) -> &AccountAddress { + &self.account + } + + pub fn amount(&self) -> u64 { + self.amount + } +} + +impl MoveStructType for CoinWithdraw { + const MODULE_NAME: &'static IdentStr = ident_str!("coin"); + const STRUCT_NAME: &'static IdentStr = ident_str!("CoinWithdraw"); +} + +impl MoveEventV2Type for CoinWithdraw {} + +pub const COIN_WITHDRAW_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::coin::CoinWithdraw"; + +pub static COIN_WITHDRAW_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("coin").to_owned(), + name: ident_str!("CoinWithdraw").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/deposit_event.rs b/types/src/account_config/events/deposit_event.rs new file mode 100644 index 0000000000000..925a534fc9c3e --- /dev/null +++ b/types/src/account_config/events/deposit_event.rs @@ -0,0 +1,49 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::move_utils::move_event_v1::MoveEventV1Type; +use anyhow::Result; +use move_core_types::{ + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DepositEvent { + amount: u64, +} + +impl DepositEvent { + pub fn new(amount: u64) -> Self { + Self { amount } + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + /// Get the amount sent or received + pub fn amount(&self) -> u64 { + self.amount + } +} + +impl MoveStructType for DepositEvent { + const MODULE_NAME: &'static IdentStr = ident_str!("coin"); + const STRUCT_NAME: &'static IdentStr = ident_str!("DepositEvent"); +} + +impl MoveEventV1Type for DepositEvent {} + +pub static DEPOSIT_EVENT_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("coin").to_owned(), + name: ident_str!("DepositEvent").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/key_rotation.rs b/types/src/account_config/events/key_rotation.rs new file mode 100644 index 0000000000000..3da64bbcc9c44 --- /dev/null +++ b/types/src/account_config/events/key_rotation.rs @@ -0,0 +1,55 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyRotation { + account: AccountAddress, + old_authentication_key: Vec, + new_authentication_key: Vec, +} + +impl KeyRotation { + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn account(&self) -> &AccountAddress { + &self.account + } + + pub fn old_authentication_key(&self) -> &Vec { + &self.old_authentication_key + } + + pub fn new_authentication_key(&self) -> &Vec { + &self.new_authentication_key + } +} + +impl MoveStructType for KeyRotation { + const MODULE_NAME: &'static IdentStr = ident_str!("account"); + const STRUCT_NAME: &'static IdentStr = ident_str!("KeyRotation"); +} + +pub const KEY_ROTATION_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::account::KeyRotation"; + +pub static KEY_ROTATION_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("account").to_owned(), + name: ident_str!("KeyRotation").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/key_rotation_event.rs b/types/src/account_config/events/key_rotation_event.rs new file mode 100644 index 0000000000000..14ff80c6346e2 --- /dev/null +++ b/types/src/account_config/events/key_rotation_event.rs @@ -0,0 +1,56 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use move_core_types::{ + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyRotationEvent { + old_authentication_key: Vec, + new_authentication_key: Vec, +} + +impl KeyRotationEvent { + pub fn new(old_authentication_key: Vec, new_authentication_key: Vec) -> Self { + Self { + old_authentication_key, + new_authentication_key, + } + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn old_authentication_key(&self) -> &Vec { + &self.old_authentication_key + } + + pub fn new_authentication_key(&self) -> &Vec { + &self.new_authentication_key + } +} + +impl MoveStructType for KeyRotationEvent { + const MODULE_NAME: &'static IdentStr = ident_str!("account"); + const STRUCT_NAME: &'static IdentStr = ident_str!("KeyRotationEvent"); +} + +pub const KEY_ROTATION_EVENT_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::account::KeyRotationEvent"; + +pub static KEY_ROTATION_EVENT_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("account").to_owned(), + name: ident_str!("KeyRotationEvent").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/events/mod.rs b/types/src/account_config/events/mod.rs index 83950b07a8774..45433ae63b5b2 100644 --- a/types/src/account_config/events/mod.rs +++ b/types/src/account_config/events/mod.rs @@ -2,15 +2,29 @@ // Parts of the project are originally copyright © Meta Platforms, Inc. // SPDX-License-Identifier: Apache-2.0 -pub mod coin; +pub mod coin_deposit; +pub mod coin_register; +pub mod coin_register_event; +pub mod coin_withdraw; +pub mod deposit_event; pub mod fungible_asset; +pub mod key_rotation; +pub mod key_rotation_event; pub mod new_block; pub mod new_epoch; +pub mod withdraw_event; -pub use coin::*; +pub use coin_deposit::*; +pub use coin_register::*; +pub use coin_register_event::*; +pub use coin_withdraw::*; +pub use deposit_event::*; pub use fungible_asset::*; +pub use key_rotation::*; +pub use key_rotation_event::*; pub use new_block::*; pub use new_epoch::*; +pub use withdraw_event::*; pub fn is_aptos_governance_create_proposal_event(event_type: &str) -> bool { event_type == "0x1::aptos_governance::CreateProposal" diff --git a/types/src/account_config/events/withdraw_event.rs b/types/src/account_config/events/withdraw_event.rs new file mode 100644 index 0000000000000..ff386b8f97d68 --- /dev/null +++ b/types/src/account_config/events/withdraw_event.rs @@ -0,0 +1,49 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::move_utils::move_event_v1::MoveEventV1Type; +use anyhow::Result; +use move_core_types::{ + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +/// Struct that represents a SentPaymentEvent. +#[derive(Debug, Serialize, Deserialize)] +pub struct WithdrawEvent { + amount: u64, +} + +impl WithdrawEvent { + pub fn new(amount: u64) -> Self { + Self { amount } + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } + + pub fn amount(&self) -> u64 { + self.amount + } +} + +impl MoveStructType for WithdrawEvent { + const MODULE_NAME: &'static IdentStr = ident_str!("coin"); + const STRUCT_NAME: &'static IdentStr = ident_str!("WithdrawEvent"); +} + +impl MoveEventV1Type for WithdrawEvent {} + +pub static WITHDRAW_EVENT_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("coin").to_owned(), + name: ident_str!("WithdrawEvent").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/account_config/mod.rs b/types/src/account_config/mod.rs index 16301637a6934..9a1e97e6e4964 100644 --- a/types/src/account_config/mod.rs +++ b/types/src/account_config/mod.rs @@ -5,7 +5,9 @@ pub mod constants; pub mod events; pub mod resources; +pub mod structs; pub use constants::*; pub use events::*; pub use resources::*; +pub use structs::*; diff --git a/types/src/account_config/resources/mod.rs b/types/src/account_config/resources/mod.rs index cbe90b7599639..82bee857de31a 100644 --- a/types/src/account_config/resources/mod.rs +++ b/types/src/account_config/resources/mod.rs @@ -11,7 +11,6 @@ pub mod core_account; pub mod fungible_asset_metadata; pub mod fungible_store; pub mod object; -pub mod type_info; pub use chain_id::*; pub use challenge::*; @@ -21,4 +20,3 @@ pub use core_account::*; pub use fungible_asset_metadata::*; pub use fungible_store::*; pub use object::*; -pub use type_info::*; diff --git a/types/src/account_config/resources/type_info.rs b/types/src/account_config/resources/type_info.rs deleted file mode 100644 index aa4328e17cb06..0000000000000 --- a/types/src/account_config/resources/type_info.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -use move_core_types::{ - account_address::AccountAddress, - ident_str, - identifier::IdentStr, - move_resource::{MoveResource, MoveStructType}, -}; -use serde::{Deserialize, Serialize}; - -/// A Rust representation of TypeInfo. -#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct TypeInfoResource { - pub account_address: AccountAddress, - pub module_name: Vec, - pub struct_name: Vec, -} - -impl MoveStructType for TypeInfoResource { - const MODULE_NAME: &'static IdentStr = ident_str!("type_info"); - const STRUCT_NAME: &'static IdentStr = ident_str!("TypeInfo"); -} - -impl MoveResource for TypeInfoResource {} diff --git a/types/src/account_config/structs/mod.rs b/types/src/account_config/structs/mod.rs new file mode 100644 index 0000000000000..ff10ab7e61efa --- /dev/null +++ b/types/src/account_config/structs/mod.rs @@ -0,0 +1,7 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod type_info; + +pub use type_info::*; diff --git a/types/src/account_config/structs/type_info.rs b/types/src/account_config/structs/type_info.rs new file mode 100644 index 0000000000000..b0f9d2f3efc96 --- /dev/null +++ b/types/src/account_config/structs/type_info.rs @@ -0,0 +1,42 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag, CORE_CODE_ADDRESS}, + move_resource::MoveStructType, +}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TypeInfo { + account_address: AccountAddress, + module_name: Vec, + struct_name: Vec, +} + +impl TypeInfo { + pub fn try_from_bytes(bytes: &[u8]) -> Result { + bcs::from_bytes(bytes).map_err(Into::into) + } +} + +impl MoveStructType for TypeInfo { + const MODULE_NAME: &'static IdentStr = ident_str!("type_info"); + const STRUCT_NAME: &'static IdentStr = ident_str!("TypeInfo"); +} + +pub const TYPE_INFO_TYPE_STR: &str = + "0000000000000000000000000000000000000000000000000000000000000001::type_info::TypeInfo"; + +pub static TYPE_INFO_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("type_info").to_owned(), + name: ident_str!("TypeInfo").to_owned(), + type_args: vec![], + })) +}); diff --git a/types/src/contract_event.rs b/types/src/contract_event.rs index a89e3d88a9f26..52647747eb6a7 100644 --- a/types/src/contract_event.rs +++ b/types/src/contract_event.rs @@ -169,6 +169,7 @@ impl ContractEvent { /// Entry produced via a call to the `emit_event` builtin. #[derive(Hash, Clone, Eq, PartialEq, Serialize, Deserialize, CryptoHasher)] +#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))] pub struct ContractEventV1 { /// The unique key that the event was emitted to key: EventKey, diff --git a/types/src/indexer/indexer_db_reader.rs b/types/src/indexer/indexer_db_reader.rs index 164b927de37dc..ad84c58c8f762 100644 --- a/types/src/indexer/indexer_db_reader.rs +++ b/types/src/indexer/indexer_db_reader.rs @@ -3,7 +3,7 @@ use crate::{ account_address::AccountAddress, - contract_event::EventWithVersion, + contract_event::{ContractEventV1, ContractEventV2, EventWithVersion}, event::EventKey, state_store::{ state_key::{prefix::StateKeyPrefix, StateKey}, @@ -72,4 +72,11 @@ pub trait IndexerReader: Send + Sync { Ok(()) } + fn get_translated_v1_event_by_version_and_index( + &self, + version: Version, + index: u64, + ) -> Result; + + fn translate_event_v2_to_v1(&self, v2: &ContractEventV2) -> Result>; } diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index d1297014ad21f..646586716a04c 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -97,6 +97,7 @@ pub enum FeatureFlag { TRANSACTION_SIMULATION_ENHANCEMENT = 78, COLLECTION_OWNER = 79, ENABLE_LOADER_V2 = 81, + ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION = 82, } impl FeatureFlag { @@ -176,6 +177,7 @@ impl FeatureFlag { FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT, // TODO(loader_v2): Enable V2 loader. // FeatureFlag::ENABLE_LOADER_V2, + FeatureFlag::ACCOUNT_AND_COIN_MODULE_EVENT_MIGRATION, ] } } diff --git a/types/src/utility_coin.rs b/types/src/utility_coin.rs index 625f63e823109..13e674fcb2465 100644 --- a/types/src/utility_coin.rs +++ b/types/src/utility_coin.rs @@ -43,3 +43,23 @@ impl MoveStructType for AptosCoinType { const MODULE_NAME: &'static IdentStr = ident_str!("aptos_coin"); const STRUCT_NAME: &'static IdentStr = ident_str!("AptosCoin"); } + +pub static DUMMY_COIN_TYPE: Lazy = Lazy::new(|| { + TypeTag::Struct(Box::new(StructTag { + address: AccountAddress::ONE, + module: ident_str!("dummy_coin").to_owned(), + name: ident_str!("DummyCoin").to_owned(), + type_args: vec![], + })) +}); + +pub struct DummyCoinType; +impl CoinType for DummyCoinType { + fn type_tag() -> TypeTag { + DUMMY_COIN_TYPE.clone() + } + + fn coin_info_address() -> AccountAddress { + AccountAddress::ONE + } +}