Skip to content

Commit 911c620

Browse files
authored
fix: discount re-orged microblock transactions from ft_balances table (#2322)
* test: balance file * fix: update on mb reorgs too * fix: some progress * fix: more * fix: more progress * fix: update reorg * style: single array * fix: recalculate
1 parent a071cfd commit 911c620

File tree

7 files changed

+829
-164
lines changed

7 files changed

+829
-164
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/* eslint-disable camelcase */
2+
3+
exports.shorthands = undefined;
4+
5+
exports.up = pgm => {
6+
// Remove old balances.
7+
pgm.sql(`TRUNCATE TABLE ft_balances`);
8+
9+
// Recalculate STX balances
10+
pgm.sql(`
11+
WITH all_balances AS (
12+
SELECT sender AS address, -SUM(amount) AS balance_change
13+
FROM stx_events
14+
WHERE asset_event_type_id IN (1, 3) -- Transfers and Burns affect the sender's balance
15+
AND canonical = true AND microblock_canonical = true
16+
GROUP BY sender
17+
UNION ALL
18+
SELECT recipient AS address, SUM(amount) AS balance_change
19+
FROM stx_events
20+
WHERE asset_event_type_id IN (1, 2) -- Transfers and Mints affect the recipient's balance
21+
AND canonical = true AND microblock_canonical = true
22+
GROUP BY recipient
23+
),
24+
net_balances AS (
25+
SELECT address, SUM(balance_change) AS balance
26+
FROM all_balances
27+
GROUP BY address
28+
),
29+
fees AS (
30+
SELECT address, SUM(total_fees) AS total_fees
31+
FROM (
32+
SELECT sender_address AS address, SUM(fee_rate) AS total_fees
33+
FROM txs
34+
WHERE canonical = true AND microblock_canonical = true AND sponsored = false
35+
GROUP BY sender_address
36+
UNION ALL
37+
SELECT sponsor_address AS address, SUM(fee_rate) AS total_fees
38+
FROM txs
39+
WHERE canonical = true AND microblock_canonical = true AND sponsored = true
40+
GROUP BY sponsor_address
41+
) AS subquery
42+
GROUP BY address
43+
),
44+
rewards AS (
45+
SELECT
46+
recipient AS address,
47+
SUM(
48+
coinbase_amount + tx_fees_anchored + tx_fees_streamed_confirmed + tx_fees_streamed_produced
49+
) AS total_rewards
50+
FROM miner_rewards
51+
WHERE canonical = true
52+
GROUP BY recipient
53+
),
54+
all_addresses AS (
55+
SELECT address FROM net_balances
56+
UNION
57+
SELECT address FROM fees
58+
UNION
59+
SELECT address FROM rewards
60+
)
61+
INSERT INTO ft_balances (address, balance, token)
62+
SELECT
63+
aa.address,
64+
COALESCE(nb.balance, 0) - COALESCE(f.total_fees, 0) + COALESCE(r.total_rewards, 0) AS balance,
65+
'stx' AS token
66+
FROM all_addresses aa
67+
LEFT JOIN net_balances nb ON aa.address = nb.address
68+
LEFT JOIN fees f ON aa.address = f.address
69+
LEFT JOIN rewards r ON aa.address = r.address
70+
`);
71+
72+
// Recalculate FT balances
73+
pgm.sql(`
74+
WITH all_balances AS (
75+
SELECT sender AS address, asset_identifier, -SUM(amount) AS balance_change
76+
FROM ft_events
77+
WHERE asset_event_type_id IN (1, 3) -- Transfers and Burns affect the sender's balance
78+
AND canonical = true
79+
AND microblock_canonical = true
80+
GROUP BY sender, asset_identifier
81+
UNION ALL
82+
SELECT recipient AS address, asset_identifier, SUM(amount) AS balance_change
83+
FROM ft_events
84+
WHERE asset_event_type_id IN (1, 2) -- Transfers and Mints affect the recipient's balance
85+
AND canonical = true
86+
AND microblock_canonical = true
87+
GROUP BY recipient, asset_identifier
88+
),
89+
net_balances AS (
90+
SELECT address, asset_identifier, SUM(balance_change) AS balance
91+
FROM all_balances
92+
GROUP BY address, asset_identifier
93+
)
94+
INSERT INTO ft_balances (address, balance, token)
95+
SELECT address, balance, asset_identifier AS token
96+
FROM net_balances
97+
`);
98+
};
99+
100+
exports.down = pgm => {};

src/datastore/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ export interface DbPoxCycleSignerStacker {
11091109

11101110
interface ReOrgEntities {
11111111
blocks: number;
1112+
microblockHashes: string[];
11121113
microblocks: number;
11131114
minerRewards: number;
11141115
txs: number;

src/datastore/helpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,7 @@ export function newReOrgUpdatedEntities(): ReOrgUpdatedEntities {
13131313
return {
13141314
markedCanonical: {
13151315
blocks: 0,
1316+
microblockHashes: [],
13161317
microblocks: 0,
13171318
minerRewards: 0,
13181319
txs: 0,
@@ -1333,6 +1334,7 @@ export function newReOrgUpdatedEntities(): ReOrgUpdatedEntities {
13331334
},
13341335
markedNonCanonical: {
13351336
blocks: 0,
1337+
microblockHashes: [],
13361338
microblocks: 0,
13371339
minerRewards: 0,
13381340
txs: 0,

src/datastore/pg-write-store.ts

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export class PgWriteStore extends PgStore {
207207

208208
await this.sqlWriteTransaction(async sql => {
209209
const chainTip = await this.getChainTip(sql);
210-
await this.handleReorg(sql, data.block, chainTip.block_height);
210+
const reorg = await this.handleReorg(sql, data.block, chainTip.block_height);
211211
const isCanonical = data.block.block_height > chainTip.block_height;
212212
if (!isCanonical) {
213213
markBlockUpdateDataAsNonCanonical(data);
@@ -292,6 +292,14 @@ export class PgWriteStore extends PgStore {
292292
// via microblocks.
293293
q.enqueue(() => this.updateStxBalances(sql, data.txs, data.minerRewards));
294294
q.enqueue(() => this.updateFtBalances(sql, data.txs));
295+
// If this block re-orgs past microblocks, though, we must discount the balances generated
296+
// by their txs which are now also reorged. We must do this here because the block re-org
297+
// logic is decoupled from the microblock re-org logic so previous balance updates will
298+
// not apply.
299+
await this.updateFtBalancesFromMicroblockReOrg(sql, [
300+
...reorg.markedNonCanonical.microblockHashes,
301+
...reorg.markedCanonical.microblockHashes,
302+
]);
295303
}
296304
if (data.poxSetSigners && data.poxSetSigners.signers) {
297305
const poxSet = data.poxSetSigners;
@@ -1150,6 +1158,116 @@ export class PgWriteStore extends PgStore {
11501158
}
11511159
}
11521160

1161+
async updateFtBalancesFromMicroblockReOrg(sql: PgSqlClient, microblockHashes: string[]) {
1162+
if (microblockHashes.length === 0) return;
1163+
await sql`
1164+
WITH updated_txs AS (
1165+
SELECT tx_id, sender_address, nonce, sponsor_address, fee_rate, sponsored, canonical, microblock_canonical
1166+
FROM txs
1167+
WHERE microblock_hash IN ${sql(microblockHashes)}
1168+
),
1169+
affected_addresses AS (
1170+
SELECT
1171+
sender_address AS address,
1172+
fee_rate AS fee_change,
1173+
canonical,
1174+
microblock_canonical,
1175+
sponsored
1176+
FROM updated_txs
1177+
WHERE sponsored = false
1178+
UNION ALL
1179+
SELECT
1180+
sponsor_address AS address,
1181+
fee_rate AS fee_change,
1182+
canonical,
1183+
microblock_canonical,
1184+
sponsored
1185+
FROM updated_txs
1186+
WHERE sponsored = true
1187+
),
1188+
balances_update AS (
1189+
SELECT
1190+
a.address,
1191+
SUM(CASE WHEN a.canonical AND a.microblock_canonical THEN -a.fee_change ELSE a.fee_change END) AS balance_change
1192+
FROM affected_addresses a
1193+
GROUP BY a.address
1194+
)
1195+
INSERT INTO ft_balances (address, token, balance)
1196+
SELECT b.address, 'stx', b.balance_change
1197+
FROM balances_update b
1198+
ON CONFLICT (address, token)
1199+
DO UPDATE
1200+
SET balance = ft_balances.balance + EXCLUDED.balance
1201+
RETURNING ft_balances.address
1202+
`;
1203+
await sql`
1204+
WITH updated_events AS (
1205+
SELECT sender, recipient, amount, asset_event_type_id, asset_identifier, canonical, microblock_canonical
1206+
FROM ft_events
1207+
WHERE microblock_hash IN ${sql(microblockHashes)}
1208+
),
1209+
event_changes AS (
1210+
SELECT address, asset_identifier, SUM(balance_change) AS balance_change
1211+
FROM (
1212+
SELECT sender AS address, asset_identifier,
1213+
SUM(CASE WHEN canonical AND microblock_canonical THEN -amount ELSE amount END) AS balance_change
1214+
FROM updated_events
1215+
WHERE asset_event_type_id IN (1, 3) -- Transfers and Burns affect the sender's balance
1216+
GROUP BY sender, asset_identifier
1217+
UNION ALL
1218+
SELECT recipient AS address, asset_identifier,
1219+
SUM(CASE WHEN canonical AND microblock_canonical THEN amount ELSE -amount END) AS balance_change
1220+
FROM updated_events
1221+
WHERE asset_event_type_id IN (1, 2) -- Transfers and Mints affect the recipient's balance
1222+
GROUP BY recipient, asset_identifier
1223+
) AS subquery
1224+
GROUP BY address, asset_identifier
1225+
)
1226+
INSERT INTO ft_balances (address, token, balance)
1227+
SELECT ec.address, ec.asset_identifier, ec.balance_change
1228+
FROM event_changes ec
1229+
ON CONFLICT (address, token)
1230+
DO UPDATE
1231+
SET balance = ft_balances.balance + EXCLUDED.balance
1232+
RETURNING ft_balances.address
1233+
`;
1234+
await sql`
1235+
WITH updated_events AS (
1236+
SELECT sender, recipient, amount, asset_event_type_id, canonical, microblock_canonical
1237+
FROM stx_events
1238+
WHERE microblock_hash IN ${sql(microblockHashes)}
1239+
),
1240+
event_changes AS (
1241+
SELECT
1242+
address,
1243+
SUM(balance_change) AS balance_change
1244+
FROM (
1245+
SELECT
1246+
sender AS address,
1247+
SUM(CASE WHEN canonical AND microblock_canonical THEN -amount ELSE amount END) AS balance_change
1248+
FROM updated_events
1249+
WHERE asset_event_type_id IN (1, 3) -- Transfers and Burns affect the sender's balance
1250+
GROUP BY sender
1251+
UNION ALL
1252+
SELECT
1253+
recipient AS address,
1254+
SUM(CASE WHEN canonical AND microblock_canonical THEN amount ELSE -amount END) AS balance_change
1255+
FROM updated_events
1256+
WHERE asset_event_type_id IN (1, 2) -- Transfers and Mints affect the recipient's balance
1257+
GROUP BY recipient
1258+
) AS subquery
1259+
GROUP BY address
1260+
)
1261+
INSERT INTO ft_balances (address, token, balance)
1262+
SELECT ec.address, 'stx', ec.balance_change
1263+
FROM event_changes ec
1264+
ON CONFLICT (address, token)
1265+
DO UPDATE
1266+
SET balance = ft_balances.balance + EXCLUDED.balance
1267+
RETURNING ft_balances.address
1268+
`;
1269+
}
1270+
11531271
async updateStxEvents(sql: PgSqlClient, entries: { tx: DbTx; stxEvents: DbStxEvent[] }[]) {
11541272
const values: StxEventInsertValues[] = [];
11551273
for (const { tx, stxEvents } of entries) {
@@ -3289,7 +3407,13 @@ export class PgWriteStore extends PgStore {
32893407
microblocksAccepted.add(mb);
32903408
});
32913409
updatedEntities.markedCanonical.microblocks += microblocksAccepted.size;
3410+
updatedEntities.markedCanonical.microblockHashes.push(
3411+
...microCanonicalUpdateResult.acceptedMicroblocks
3412+
);
32923413
updatedEntities.markedNonCanonical.microblocks += microblocksOrphaned.size;
3414+
updatedEntities.markedNonCanonical.microblockHashes.push(
3415+
...microCanonicalUpdateResult.orphanedMicroblocks
3416+
);
32933417

32943418
const markCanonicalResult = await this.markEntitiesCanonical(
32953419
sql,

0 commit comments

Comments
 (0)