Skip to content

Commit f49a1dc

Browse files
authored
[Indexer] Add transactions by address indexing (aptos-labs#9076)
* make changes to old indexer * apply change to grpc processors * remove unecessary log
1 parent 0cdd064 commit f49a1dc

File tree

25 files changed

+856
-193
lines changed

25 files changed

+856
-193
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- This file should undo anything in `up.sql`
2+
DROP INDEX IF EXISTS at_version_index;
3+
DROP INDEX IF EXISTS at_insat_index;
4+
DROP TABLE IF EXISTS account_transactions;
5+
ALTER TABLE objects
6+
ALTER COLUMN owner_address DROP NOT NULL;
7+
ALTER TABLE objects
8+
ALTER COLUMN guid_creation_num DROP NOT NULL;
9+
ALTER TABLE objects
10+
ALTER COLUMN allow_ungated_transfer DROP NOT NULL;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- Your SQL goes here
2+
-- Records transactions - account pairs. Account here can represent
3+
-- user account, resource account, or object account.
4+
CREATE TABLE IF NOT EXISTS account_transactions (
5+
transaction_version BIGINT NOT NULL,
6+
account_address VARCHAR(66) NOT NULL,
7+
inserted_at TIMESTAMP NOT NULL DEFAULT NOW(),
8+
PRIMARY KEY (account_address, transaction_version)
9+
);
10+
CREATE INDEX IF NOT EXISTS at_version_index ON account_transactions (transaction_version DESC);
11+
CREATE INDEX IF NOT EXISTS at_insat_index ON account_transactions (inserted_at);
12+
ALTER TABLE objects
13+
ALTER COLUMN owner_address
14+
SET NOT NULL;
15+
ALTER TABLE objects
16+
ALTER COLUMN guid_creation_num
17+
SET NOT NULL;
18+
ALTER TABLE objects
19+
ALTER COLUMN allow_ungated_transfer
20+
SET NOT NULL;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
}

crates/indexer/src/models/coin_models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright © Aptos Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

4+
pub mod account_transactions;
45
pub mod coin_activities;
56
pub mod coin_balances;
67
pub mod coin_infos;

crates/indexer/src/models/user_transactions.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,30 @@ impl UserTransaction {
9292
},
9393
epoch,
9494
},
95-
txn.request
96-
.signature
97-
.as_ref()
98-
.map(|s| {
99-
Signature::from_user_transaction(
100-
s,
101-
&txn.request.sender.to_string(),
102-
version,
103-
block_height,
104-
)
105-
.unwrap()
106-
})
107-
.unwrap_or_default(), // empty vec if signature is None
95+
Self::get_signatures(txn, version, block_height),
10896
)
10997
}
98+
99+
/// Empty vec if signature is None
100+
pub fn get_signatures(
101+
txn: &APIUserTransaction,
102+
version: i64,
103+
block_height: i64,
104+
) -> Vec<Signature> {
105+
txn.request
106+
.signature
107+
.as_ref()
108+
.map(|s| {
109+
Signature::from_user_transaction(
110+
s,
111+
&txn.request.sender.to_string(),
112+
version,
113+
block_height,
114+
)
115+
.unwrap()
116+
})
117+
.unwrap_or_default()
118+
}
110119
}
111120

112121
// Prevent conflicts with other things named `Transaction`

0 commit comments

Comments
 (0)