|
| 1 | +// Copyright © Aptos Foundation |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +// This is required because a diesel macro makes clippy sad |
| 5 | +#![allow(clippy::extra_unused_lifetimes)] |
| 6 | +#![allow(clippy::unused_unit)] |
| 7 | + |
| 8 | +use crate::{ |
| 9 | + models::{ |
| 10 | + token_models::v2_token_utils::ObjectWithMetadata, user_transactions::UserTransaction, |
| 11 | + }, |
| 12 | + schema::account_transactions, |
| 13 | + util::standardize_address, |
| 14 | +}; |
| 15 | +use aptos_api_types::{DeleteResource, Event, Transaction, WriteResource, WriteSetChange}; |
| 16 | +use field_count::FieldCount; |
| 17 | +use serde::{Deserialize, Serialize}; |
| 18 | +use std::collections::HashMap; |
| 19 | + |
| 20 | +pub type AccountTransactionPK = (String, i64); |
| 21 | + |
| 22 | +#[derive(Debug, Deserialize, FieldCount, Identifiable, Insertable, Serialize)] |
| 23 | +#[diesel(primary_key(account_address, transaction_version))] |
| 24 | +#[diesel(table_name = account_transactions)] |
| 25 | +pub struct AccountTransaction { |
| 26 | + pub transaction_version: i64, |
| 27 | + pub account_address: String, |
| 28 | +} |
| 29 | + |
| 30 | +impl AccountTransaction { |
| 31 | + /// This table will record every transaction that touch an account which could be |
| 32 | + /// a user account, an object, or a resource account. |
| 33 | + /// We will consider all transactions that modify a resource or event associated with a particular account. |
| 34 | + /// We will do 1 level of redirection for now (e.g. if it's an object, we will record the owner as account address). |
| 35 | + /// We will also consider transactions that the account signed or is part of a multi sig / multi agent. |
| 36 | + /// TODO: recursively find the parent account of an object |
| 37 | + /// TODO: include table items in the detection path |
| 38 | + pub fn from_transaction( |
| 39 | + transaction: &Transaction, |
| 40 | + ) -> anyhow::Result<HashMap<AccountTransactionPK, Self>> { |
| 41 | + let (events, wscs, signatures, txn_version) = match transaction { |
| 42 | + Transaction::UserTransaction(inner) => ( |
| 43 | + &inner.events, |
| 44 | + &inner.info.changes, |
| 45 | + UserTransaction::get_signatures(inner, inner.info.version.0 as i64, 0), |
| 46 | + inner.info.version.0 as i64, |
| 47 | + ), |
| 48 | + Transaction::GenesisTransaction(inner) => ( |
| 49 | + &inner.events, |
| 50 | + &inner.info.changes, |
| 51 | + vec![], |
| 52 | + inner.info.version.0 as i64, |
| 53 | + ), |
| 54 | + Transaction::BlockMetadataTransaction(inner) => ( |
| 55 | + &inner.events, |
| 56 | + &inner.info.changes, |
| 57 | + vec![], |
| 58 | + inner.info.version.0 as i64, |
| 59 | + ), |
| 60 | + _ => { |
| 61 | + return Ok(HashMap::new()); |
| 62 | + }, |
| 63 | + }; |
| 64 | + let mut account_transactions = HashMap::new(); |
| 65 | + for sig in &signatures { |
| 66 | + account_transactions.insert((sig.signer.clone(), txn_version), Self { |
| 67 | + transaction_version: txn_version, |
| 68 | + account_address: sig.signer.clone(), |
| 69 | + }); |
| 70 | + } |
| 71 | + for event in events { |
| 72 | + account_transactions.extend(Self::from_event(event, txn_version)); |
| 73 | + } |
| 74 | + for wsc in wscs { |
| 75 | + match wsc { |
| 76 | + WriteSetChange::DeleteResource(res) => { |
| 77 | + account_transactions.extend(Self::from_delete_resource(res, txn_version)?); |
| 78 | + }, |
| 79 | + WriteSetChange::WriteResource(res) => { |
| 80 | + account_transactions.extend(Self::from_write_resource(res, txn_version)?); |
| 81 | + }, |
| 82 | + _ => {}, |
| 83 | + } |
| 84 | + } |
| 85 | + Ok(account_transactions) |
| 86 | + } |
| 87 | + |
| 88 | + /// Base case, record event account address. We don't really have to worry about |
| 89 | + /// objects here because it'll be taken care of in the resource section |
| 90 | + fn from_event(event: &Event, txn_version: i64) -> HashMap<AccountTransactionPK, Self> { |
| 91 | + let account_address = standardize_address(&event.guid.account_address.to_string()); |
| 92 | + HashMap::from([((account_address.clone(), txn_version), Self { |
| 93 | + transaction_version: txn_version, |
| 94 | + account_address, |
| 95 | + })]) |
| 96 | + } |
| 97 | + |
| 98 | + /// Base case, record resource account. If the resource is an object, then we record the owner as well |
| 99 | + /// This handles partial deletes as well |
| 100 | + fn from_write_resource( |
| 101 | + write_resource: &WriteResource, |
| 102 | + txn_version: i64, |
| 103 | + ) -> anyhow::Result<HashMap<AccountTransactionPK, Self>> { |
| 104 | + let mut result = HashMap::new(); |
| 105 | + let account_address = standardize_address(&write_resource.address.to_string()); |
| 106 | + result.insert((account_address.clone(), txn_version), Self { |
| 107 | + transaction_version: txn_version, |
| 108 | + account_address, |
| 109 | + }); |
| 110 | + if let Some(inner) = &ObjectWithMetadata::from_write_resource(write_resource, txn_version)? |
| 111 | + { |
| 112 | + result.insert((inner.object_core.get_owner_address(), txn_version), Self { |
| 113 | + transaction_version: txn_version, |
| 114 | + account_address: inner.object_core.get_owner_address(), |
| 115 | + }); |
| 116 | + } |
| 117 | + Ok(result) |
| 118 | + } |
| 119 | + |
| 120 | + /// Base case, record resource account. |
| 121 | + /// TODO: If the resource is an object, then we need to look for the latest owner. This isn't really possible |
| 122 | + /// right now given we have parallel threads so it'll be very difficult to ensure that we have the correct |
| 123 | + /// latest owner |
| 124 | + fn from_delete_resource( |
| 125 | + delete_resource: &DeleteResource, |
| 126 | + txn_version: i64, |
| 127 | + ) -> anyhow::Result<HashMap<AccountTransactionPK, Self>> { |
| 128 | + let mut result = HashMap::new(); |
| 129 | + let account_address = standardize_address(&delete_resource.address.to_string()); |
| 130 | + result.insert((account_address.clone(), txn_version), Self { |
| 131 | + transaction_version: txn_version, |
| 132 | + account_address, |
| 133 | + }); |
| 134 | + Ok(result) |
| 135 | + } |
| 136 | +} |
0 commit comments