Skip to content

Commit

Permalink
feat: transaction summary (#2000)
Browse files Browse the repository at this point in the history
* wasm: transaction summary

* summary field on transaction info idb table

* changeset

* satisfy linter

* bump idb version

* feat(services): return summary from transactionInfoByHash

* fix: format

* fix(services): tests

* don't bump idb version and make summary field optional

* changeset

* linting

---------

Co-authored-by: Max Korsunov <maxkors2014@gmail.com>
  • Loading branch information
TalDerei and VanishMax authored Mar 2, 2025
1 parent a11bfe3 commit 15d768f
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 25 deletions.
9 changes: 9 additions & 0 deletions .changeset/spotty-ways-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@penumbra-zone/protobuf': major
'@penumbra-zone/services': minor
'@penumbra-zone/storage': minor
'@penumbra-zone/types': minor
'@penumbra-zone/wasm': minor
---

transaction summary support for transaction info rpc
2 changes: 1 addition & 1 deletion packages/protobuf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"gen:ibc": "buf generate buf.build/cosmos/ibc:7ab44ae956a0488ea04e04511efa5f70",
"gen:ics23": "buf generate buf.build/cosmos/ics23:55085f7c710a45f58fa09947208eb70b",
"gen:noble": "buf generate buf.build/noble-assets/forwarding:5a8609a6772d417584a9c60cd8b80881",
"gen:penumbra": "buf generate buf.build/penumbra-zone/penumbra:ae2300bce202a7d429727f1340e7412d3b9f810c",
"gen:penumbra": "buf generate buf.build/penumbra-zone/penumbra:f6682d4ef839bb06e2da4fae487eb9d565ea04d6",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"lint:strict": "tsc --noEmit && eslint src --max-warnings 0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import {
import { fvkCtx } from '../ctx/full-viewing-key.js';

const mockTransactionInfo = vi.hoisted(() => vi.fn());
const mockTransactionSummary = vi.hoisted(() => vi.fn());
vi.mock('@penumbra-zone/wasm/transaction', () => ({
generateTransactionInfo: mockTransactionInfo,
generateTransactionSummary: mockTransactionSummary,
}));
describe('TransactionInfoByHash request handler', () => {
let mockServices: MockServices;
Expand Down
23 changes: 19 additions & 4 deletions packages/services/src/view-service/transaction-info-by-hash.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { Impl } from './index.js';
import { servicesCtx } from '../ctx/prax.js';
import { Code, ConnectError } from '@connectrpc/connect';
import { generateTransactionInfo } from '@penumbra-zone/wasm/transaction';
import {
generateTransactionInfo,
generateTransactionSummary,
} from '@penumbra-zone/wasm/transaction';
import { TransactionInfo } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';
import { fvkCtx } from '../ctx/full-viewing-key.js';
import { txvTranslator } from './util/transaction-view.js';
Expand All @@ -15,15 +18,17 @@ export const transactionInfoByHash: Impl['transactionInfoByHash'] = async (req,
const { indexedDb, querier } = await services.getWalletServices();
const fvk = ctx.values.get(fvkCtx);

// Check database for transaction first
// if not in database, query tendermint for public info on the transaction
// First, check the database for the transaction.
// If not found, query Tendermint for public transaction details.
const { transaction, height } =
(await indexedDb.getTransaction(req.id)) ?? (await querier.tendermint.getTransaction(req.id));

if (!transaction) {
throw new ConnectError('Transaction not available', Code.NotFound);
}

// TODO: avoid regenerating the transaction info (TxV, TxP, summary)
// and query from database if it already exists.
const { txp: perspective, txv } = await generateTransactionInfo(
await fvk(),
transaction,
Expand All @@ -33,6 +38,16 @@ export const transactionInfoByHash: Impl['transactionInfoByHash'] = async (req,
// Invoke a higher-level translator on the transaction view.
const view = txvTranslator(txv);

const txInfo = new TransactionInfo({ height, id: req.id, transaction, perspective, view });
// Generate transaction info summary from the TxV.
const summary = await generateTransactionSummary(txv);

const txInfo = new TransactionInfo({
height,
id: req.id,
transaction,
perspective,
view,
summary,
});
return { txInfo };
};
6 changes: 6 additions & 0 deletions packages/services/src/view-service/transaction-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import { transactionInfo } from './transaction-info.js';
import { fvkCtx } from '../ctx/full-viewing-key.js';

const mockTransactionInfo = vi.hoisted(() => vi.fn());
const mockTransactionSummary = vi.hoisted(() => vi.fn());
const mockSaveTransactionInfo = vi.hoisted(() => vi.fn());
vi.mock('@penumbra-zone/wasm/transaction', () => ({
generateTransactionInfo: mockTransactionInfo,
generateTransactionSummary: mockTransactionSummary,
saveTransactionInfo: mockSaveTransactionInfo,
}));

describe('TransactionInfo request handler', () => {
Expand All @@ -40,7 +44,9 @@ describe('TransactionInfo request handler', () => {
getTransactionInfo: vi.fn().mockResolvedValue({
txp: {},
txv: {},
summary: {},
}),
saveTransactionInfo: vi.fn().mockResolvedValue(undefined),
};

mockServices = {
Expand Down
21 changes: 17 additions & 4 deletions packages/services/src/view-service/transaction-info.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { Impl } from './index.js';
import { servicesCtx } from '../ctx/prax.js';
import { TransactionInfo } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';
import { generateTransactionInfo } from '@penumbra-zone/wasm/transaction';
import {
generateTransactionInfo,
generateTransactionSummary,
} from '@penumbra-zone/wasm/transaction';
import { fvkCtx } from '../ctx/full-viewing-key.js';
import {
TransactionPerspective,
TransactionSummary,
TransactionView,
} from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';

Expand All @@ -24,11 +28,16 @@ export const transactionInfo: Impl['transactionInfo'] = async function* (_req, c
const tx_info = await indexedDb.getTransactionInfo(txRecord.id);
let perspective: TransactionPerspective;
let view: TransactionView;
let summary: TransactionSummary | undefined;

// If TxP + TxV already exist in database, then simply yield them.
if (tx_info) {
// If TxP + TxV + summary already exist in database, then simply yield them.
// If the summary is missing, regenerate the transaction info once and update
// the table with the new field.
if (tx_info?.summary) {
perspective = tx_info.perspective;
view = tx_info.view;
summary = tx_info.summary;

// Otherwise, generate the TxP + TxV from the transaction in wasm
// and store them.
} else {
Expand All @@ -38,7 +47,10 @@ export const transactionInfo: Impl['transactionInfo'] = async function* (_req, c
indexedDb.constants(),
);

await indexedDb.saveTransactionInfo(txRecord.id, txp, txv);
// Generate and save transaction info summary from the TxV.
summary = await generateTransactionSummary(txv);
await indexedDb.saveTransactionInfo(txRecord.id, txp, txv, summary);

perspective = txp;
view = txv;
}
Expand All @@ -50,6 +62,7 @@ export const transactionInfo: Impl['transactionInfo'] = async function* (_req, c
transaction: txRecord.transaction,
perspective,
view,
summary,
}),
};
}
Expand Down
33 changes: 22 additions & 11 deletions packages/storage/src/indexed-db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ValidatorInfo } from '@penumbra-zone/protobuf/penumbra/core/component/s
import {
Transaction,
TransactionPerspective,
TransactionSummary,
TransactionView,
} from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import { bech32mAssetId } from '@penumbra-zone/bech32m/passet';
Expand Down Expand Up @@ -366,34 +367,44 @@ export class IndexedDb implements IndexedDbInterface {
);
}

async getTransactionInfo(
id: TransactionId,
): Promise<
{ id: TransactionId; perspective: TransactionPerspective; view: TransactionView } | undefined
async getTransactionInfo(id: TransactionId): Promise<
| {
id: TransactionId;
perspective: TransactionPerspective;
view: TransactionView;
summary?: TransactionSummary;
}
| undefined
> {
const existingData = await this.db.get('TRANSACTION_INFO', uint8ArrayToBase64(id.inner));
if (existingData) {
return {
id: TransactionId.fromJson(existingData.id, { typeRegistry }),
perspective: TransactionPerspective.fromJson(existingData.perspective, { typeRegistry }),
view: TransactionView.fromJson(existingData.view, { typeRegistry }),
};
} else {
if (!existingData) {
return undefined;
}

return {
id: TransactionId.fromJson(existingData.id, { typeRegistry }),
perspective: TransactionPerspective.fromJson(existingData.perspective, { typeRegistry }),
view: TransactionView.fromJson(existingData.view, { typeRegistry }),
summary: existingData.summary
? TransactionSummary.fromJson(existingData.summary, { typeRegistry })
: undefined,
};
}

async saveTransactionInfo(
id: TransactionId,
txp: TransactionPerspective,
txv: TransactionView,
summary: TransactionSummary,
): Promise<void> {
assertTransactionId(id);
const value = {
id: id.toJson({ typeRegistry }) as Jsonified<TransactionId>,
perspective: txp.toJson({ typeRegistry }) as Jsonified<TransactionPerspective>,
view: txv.toJson({ typeRegistry }) as Jsonified<TransactionView>,
summary: summary.toJson({ typeRegistry }) as Jsonified<TransactionSummary>,
};

await this.u.update({
table: 'TRANSACTION_INFO',
value,
Expand Down
15 changes: 11 additions & 4 deletions packages/types/src/indexed-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { AddressIndex, IdentityKey } from '@penumbra-zone/protobuf/penumbra/core
import {
Transaction,
TransactionPerspective,
TransactionSummary,
TransactionView,
} from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import { TransactionId } from '@penumbra-zone/protobuf/penumbra/core/txhash/v1/txhash_pb';
Expand Down Expand Up @@ -168,12 +169,17 @@ export interface IndexedDbInterface {
id: TransactionId,
txp: TransactionPerspective,
txv: TransactionView,
summary: TransactionSummary,
): Promise<void>;

getTransactionInfo(
id: TransactionId,
): Promise<
{ id: TransactionId; perspective: TransactionPerspective; view: TransactionView } | undefined
getTransactionInfo(id: TransactionId): Promise<
| {
id: TransactionId;
perspective: TransactionPerspective;
view: TransactionView;
summary?: TransactionSummary;
}
| undefined
>;

getPosition(positionId: PositionId): Promise<Position | undefined>;
Expand Down Expand Up @@ -241,6 +247,7 @@ export interface PenumbraDb extends DBSchema {
id: Jsonified<TransactionId>;
perspective: Jsonified<TransactionPerspective>;
view: Jsonified<TransactionView>;
summary?: Jsonified<TransactionSummary>;
};
};
REGISTRY_VERSION: {
Expand Down
9 changes: 9 additions & 0 deletions packages/wasm/crate/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use penumbra_transaction::view::action_view::{
ActionView, DelegatorVoteView, OutputView, SpendView,
};
use penumbra_transaction::Action;
use penumbra_transaction::TransactionView as TransactionViewComponent;
use penumbra_transaction::{AuthorizationData, Transaction, WitnessData};
use prost::Message;
use rand_core::OsRng;
Expand Down Expand Up @@ -384,3 +385,11 @@ async fn add_swap_claim_txn_to_perspective(

Ok(())
}

#[wasm_bindgen]
pub async fn transaction_summary(txv: &[u8]) -> WasmResult<Vec<u8>> {
let transaction_view = TransactionViewComponent::decode(txv)?;
let tx_summary = transaction_view.summary();

Ok(tx_summary.encode_to_vec())
}
9 changes: 8 additions & 1 deletion packages/wasm/src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { transaction_perspective_and_view } from '../wasm/index.js';
import { transaction_perspective_and_view, transaction_summary } from '../wasm/index.js';
import {
Transaction,
TransactionPerspective,
TransactionSummary,
TransactionView,
} from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import type { IdbConstants } from '@penumbra-zone/types/indexed-db';
Expand All @@ -23,3 +24,9 @@ export const generateTransactionInfo = async (
txv: TransactionView.fromBinary(txv),
};
};

export const generateTransactionSummary = async (txv: TransactionView) => {
const tx_summary = await transaction_summary(txv.toBinary());

return TransactionSummary.fromBinary(tx_summary);
};

0 comments on commit 15d768f

Please sign in to comment.