Skip to content

Commit

Permalink
[AN-Issue-1346] Implemented custom handling of automation transaction (
Browse files Browse the repository at this point in the history
…#134)

* [AN-Issue-1346] Implemented custom handling of automation transactions

- Added separate flow for automation transaction handling. It differes
from user transaction flow only by custom validation step of
inner-payload of automation transaction

- Added validation logic for the automation transaction inner-payload

- Introuced specific StatusCode for failures during automation
transaction inner-payload validation. They are qualified as verification
status type and will lead to have transaction kept and the account will
be charged.

* Updated copyright info

* Added language-e2e-testsuite to ci-flow with fixed tests

* Addressed review comments and added tests

- Introduced a new type PayloadTypeReference which will describe
  user-transaction payload type and reference payload if required.

- Updated TransactionMetadata and UserTransctionContext to utilize
  PayloadTypeReference enabling easy extension of any new payload type
  when needed

- Updated
  transaction_arg_validation::validate_combine_signer_and_txn_args to
  accept GasMeter as input argument.

- Updated automation inner payload validation to charge gas for it

- Added initial set of e2e tests to check Automation transaction flow.

* Fixed test failures after rebase

* Fixed copyright comment

* Introduce means to create and process automation transaction specifying the required arguments for registration

* Addressed review comments

* Renamed 'automation transaction' to 'automation registration'

* Updated automation::registry to be non-entry and made direct call from native layer

* Cosmetic chagnes

* Copyright fix

* Temporarly fixed test failure

* Split and reordered test runs in ci

* Increased nextest timeout from 2100 to 3000

---------

Co-authored-by: Aregnaz Harutyunyan <>
  • Loading branch information
aregng authored Dec 18, 2024
1 parent 4917e1e commit 6e99cf6
Show file tree
Hide file tree
Showing 59 changed files with 5,229 additions and 2,358 deletions.
2 changes: 1 addition & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ terminate-after = 1

[[profile.default.overrides]]
filter = 'test(test_gas) + test(test_compilation_metadata) + test(test_fee_collection_and_distribution_for_burn_percentages) + test(verify_resource_group_upgrades) + test(test_fee_collection_and_distribution_for_burn_percentages) + test(test_multiple_aggregators_and_collocation)'
slow-timeout = { period = "2100s", terminate-after = 1 }
slow-timeout = { period = "3000s", terminate-after = 1 }

[profile.smoke-test]
# like profile.ci
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/aptos-framework-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ jobs:
id: changed-files
uses: tj-actions/changed-files@v42

- name: Run tests
- name: Run supra-framework
run: |
${{ env.CARGO_BIN }} test --release -p aptos-framework -- --skip prover
- name: Run e2e-testsuite
run: |
RUST_MIN_STACK=104857600 ${{ env.CARGO_BIN }} nextest run -p language-e2e-testsuite --no-fail-fast
- name: Run e2e-move-tests
run: |
RUST_MIN_STACK=104857600 ${{ env.CARGO_BIN }} nextest run -p e2e-move-tests --no-fail-fast
- name: Slack Notification
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions api/src/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright (c) 2024 Supra.
// Copyright © Aptos Foundation
// Parts of the project are originally copyright © Meta Platforms, Inc.
// SPDX-License-Identifier: Apache-2.0
Expand Down Expand Up @@ -1031,6 +1032,12 @@ impl TransactionsApi {
})?;
// Verify the signed transaction
match signed_transaction.payload() {
TransactionPayload::AutomationRegistration(params) => {
TransactionsApi::validate_entry_function_payload_format(
ledger_info,
params.automated_function(),
)?;
},
TransactionPayload::EntryFunction(entry_function) => {
TransactionsApi::validate_entry_function_payload_format(
ledger_info,
Expand Down Expand Up @@ -1380,6 +1387,10 @@ impl TransactionsApi {
format!("Script::{}", txn.committed_hash()).to_string()
},
TransactionPayload::ModuleBundle(_) => "ModuleBundle::unknown".to_string(),
TransactionPayload::AutomationRegistration(auto_payload) => FunctionStats::function_to_key(
auto_payload.module_id(),
&auto_payload.function().into(),
),
TransactionPayload::EntryFunction(entry_function) => FunctionStats::function_to_key(
entry_function.module(),
&entry_function.function().into(),
Expand Down
235 changes: 110 additions & 125 deletions api/types/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

use crate::{
transaction::{
BlockEpilogueTransaction, DecodedTableData, DeleteModule, DeleteResource, DeleteTableItem,
DeletedTableData, MultisigPayload, MultisigTransactionPayload, StateCheckpointTransaction,
UserTransactionRequestInner, WriteModule, WriteResource, WriteTableItem,
AutomationRegistrationParams, BlockEpilogueTransaction, DecodedTableData, DeleteModule,
DeleteResource, DeleteTableItem, DeletedTableData, MultisigPayload,
MultisigTransactionPayload, StateCheckpointTransaction, UserTransactionRequestInner,
WriteModule, WriteResource, WriteTableItem,
},
view::{ViewFunction, ViewRequest},
Address, Bytecode, DirectWriteSet, EntryFunctionId, EntryFunctionPayload, Event,
Expand All @@ -22,6 +23,7 @@ use aptos_crypto::{hash::CryptoHash, HashValue};
use aptos_logger::{sample, sample::SampleRate};
use aptos_resource_viewer::AptosValueAnnotator;
use aptos_storage_interface::DbReader;
use aptos_types::transaction::automation::RegistrationParams;
use aptos_types::{
access_path::{AccessPath, Path},
chain_id::ChainId,
Expand Down Expand Up @@ -178,8 +180,8 @@ impl<'a, S: StateView> MoveConverter<'a, S> {
data: TransactionOnChainData,
) -> Result<Transaction> {
use aptos_types::transaction::Transaction::{
BlockEpilogue, BlockMetadata, BlockMetadataExt, GenesisTransaction, StateCheckpoint,
UserTransaction, AutomatedTransaction
AutomatedTransaction, BlockEpilogue, BlockMetadata, BlockMetadataExt,
GenesisTransaction, StateCheckpoint, UserTransaction,
};
let aux_data = self
.db
Expand Down Expand Up @@ -279,64 +281,19 @@ impl<'a, S: StateView> MoveConverter<'a, S> {
let ret = match payload {
Script(s) => TransactionPayload::ScriptPayload(s.try_into()?),
EntryFunction(fun) => {
let (module, function, ty_args, args) = fun.into_inner();
let func_args = self
.inner
.view_function_arguments(&module, &function, &ty_args, &args);

let json_args = match func_args {
Ok(values) => values
.into_iter()
.map(|v| MoveValue::try_from(v)?.json())
.collect::<Result<_>>()?,
Err(_e) => args
.into_iter()
.map(|arg| HexEncodedBytes::from(arg).json())
.collect::<Result<_>>()?,
};

TransactionPayload::EntryFunctionPayload(EntryFunctionPayload {
arguments: json_args,
function: EntryFunctionId {
module: module.into(),
name: function.into(),
},
type_arguments: ty_args.into_iter().map(|arg| arg.into()).collect(),
})
let entry_function_payload = self.try_into_entry_function_payload(fun)?;
TransactionPayload::EntryFunctionPayload(entry_function_payload)
},
Multisig(multisig) => {
let transaction_payload = if let Some(payload) = multisig.transaction_payload {
match payload {
aptos_types::transaction::MultisigTransactionPayload::EntryFunction(
entry_function,
) => {
let (module, function, ty_args, args) = entry_function.into_inner();
let func_args = self
.inner
.view_function_arguments(&module, &function, &ty_args, &args);
let json_args = match func_args {
Ok(values) => values
.into_iter()
.map(|v| MoveValue::try_from(v)?.json())
.collect::<Result<_>>()?,
Err(_e) => args
.into_iter()
.map(|arg| HexEncodedBytes::from(arg).json())
.collect::<Result<_>>()?,
};

let entry_function_payload =
self.try_into_entry_function_payload(entry_function)?;
Some(MultisigTransactionPayload::EntryFunctionPayload(
EntryFunctionPayload {
arguments: json_args,
function: EntryFunctionId {
module: module.into(),
name: function.into(),
},
type_arguments: ty_args
.into_iter()
.map(|arg| arg.into())
.collect(),
},
entry_function_payload,
))
},
}
Expand All @@ -351,6 +308,17 @@ impl<'a, S: StateView> MoveConverter<'a, S> {

// Deprecated.
ModuleBundle(_) => bail!("Module bundle payload has been removed"),
AutomationRegistration(params) => {
let (inner_payload, max_gas_amount, gas_price_cap, expiration_timestamp_secs) =
params.into_inner();
let auto_payload = AutomationRegistrationParams {
automated_function: self.try_into_entry_function_payload(inner_payload)?,
expiration_timestamp_secs,
max_gas_amount,
gas_price_cap,
};
TransactionPayload::AutomationRegistrationPayload(auto_payload)
},
};
Ok(ret)
}
Expand Down Expand Up @@ -656,40 +624,8 @@ impl<'a, S: StateView> MoveConverter<'a, S> {

let ret = match payload {
TransactionPayload::EntryFunctionPayload(entry_func_payload) => {
let EntryFunctionPayload {
function,
type_arguments,
arguments,
} = entry_func_payload;

let module = function.module.clone();
let code =
self.inner.view_existing_module(&module.clone().into())? as Arc<dyn Bytecode>;
let func = code
.find_entry_function(function.name.0.as_ident_str())
.ok_or_else(|| format_err!("could not find entry function by {}", function))?;
ensure!(
func.generic_type_params.len() == type_arguments.len(),
"expect {} type arguments for entry function {}, but got {}",
func.generic_type_params.len(),
function,
type_arguments.len()
);
let args = self
.try_into_vm_values(func, arguments)?
.iter()
.map(bcs::to_bytes)
.collect::<Result<_, bcs::Error>>()?;

Target::EntryFunction(EntryFunction::new(
module.into(),
function.name.into(),
type_arguments
.into_iter()
.map(|v| v.try_into())
.collect::<Result<_>>()?,
args,
))
let entry_function = self.try_into_supra_core_entry_function(entry_func_payload)?;
Target::EntryFunction(entry_function)
},
TransactionPayload::ScriptPayload(script) => {
let ScriptPayload {
Expand Down Expand Up @@ -720,44 +656,11 @@ impl<'a, S: StateView> MoveConverter<'a, S> {
let transaction_payload = if let Some(payload) = multisig.transaction_payload {
match payload {
MultisigTransactionPayload::EntryFunctionPayload(entry_function) => {
let EntryFunctionPayload {
function,
type_arguments,
arguments,
} = entry_function;

let module = function.module.clone();
let code = self.inner.view_existing_module(&module.clone().into())?
as Arc<dyn Bytecode>;
let func = code
.find_entry_function(function.name.0.as_ident_str())
.ok_or_else(|| {
format_err!("could not find entry function by {}", function)
})?;
ensure!(
func.generic_type_params.len() == type_arguments.len(),
"expect {} type arguments for entry function {}, but got {}",
func.generic_type_params.len(),
function,
type_arguments.len()
);

let args = self
.try_into_vm_values(func, arguments)?
.iter()
.map(bcs::to_bytes)
.collect::<Result<_, bcs::Error>>()?;
let entry_function =
self.try_into_supra_core_entry_function(entry_function)?;
Some(
aptos_types::transaction::MultisigTransactionPayload::EntryFunction(
EntryFunction::new(
module.into(),
function.name.into(),
type_arguments
.into_iter()
.map(|v| v.try_into())
.collect::<Result<_>>()?,
args,
),
entry_function,
),
)
},
Expand All @@ -775,6 +678,22 @@ impl<'a, S: StateView> MoveConverter<'a, S> {
TransactionPayload::ModuleBundlePayload(_) => {
bail!("Module bundle payload has been removed")
},
TransactionPayload::AutomationRegistrationPayload(payload) => {
let AutomationRegistrationParams {
automated_function,
expiration_timestamp_secs,
max_gas_amount,
gas_price_cap,
} = payload;
let core_automated_function =
self.try_into_supra_core_entry_function(automated_function)?;
Target::AutomationRegistration(RegistrationParams::new(
core_automated_function,
expiration_timestamp_secs,
max_gas_amount,
gas_price_cap,
))
},
};
Ok(ret)
}
Expand Down Expand Up @@ -1082,6 +1001,72 @@ impl<'a, S: StateView> MoveConverter<'a, S> {
let id = code.identifier_at(func.name);
Ok(id.to_string())
}

fn try_into_entry_function_payload(&self, fun: EntryFunction) -> Result<EntryFunctionPayload> {
let (module, function, ty_args, args) = fun.into_inner();
let func_args = self
.inner
.view_function_arguments(&module, &function, &ty_args, &args);

let json_args = match func_args {
Ok(values) => values
.into_iter()
.map(|v| MoveValue::try_from(v)?.json())
.collect::<Result<_>>()?,
Err(_e) => args
.into_iter()
.map(|arg| HexEncodedBytes::from(arg).json())
.collect::<Result<_>>()?,
};

Ok(EntryFunctionPayload {
arguments: json_args,
function: EntryFunctionId {
module: module.into(),
name: function.into(),
},
type_arguments: ty_args.into_iter().map(|arg| arg.into()).collect(),
})
}

fn try_into_supra_core_entry_function(
&self,
entry_function: EntryFunctionPayload,
) -> Result<EntryFunction> {
let EntryFunctionPayload {
function,
type_arguments,
arguments,
} = entry_function;

let module = function.module.clone();
let code = self.inner.view_existing_module(&module.clone().into())? as Arc<dyn Bytecode>;
let func = code
.find_entry_function(function.name.0.as_ident_str())
.ok_or_else(|| format_err!("could not find entry function by {}", function))?;
ensure!(
func.generic_type_params.len() == type_arguments.len(),
"expect {} type arguments for entry function {}, but got {}",
func.generic_type_params.len(),
function,
type_arguments.len()
);

let args = self
.try_into_vm_values(func, arguments)?
.iter()
.map(bcs::to_bytes)
.collect::<Result<_, bcs::Error>>()?;
Ok(EntryFunction::new(
module.into(),
function.name.into(),
type_arguments
.into_iter()
.map(|v| v.try_into())
.collect::<Result<_>>()?,
args,
))
}
}

fn log_missing_table_info(handle: TableHandle) {
Expand Down
Loading

0 comments on commit 6e99cf6

Please sign in to comment.