Skip to content

Commit

Permalink
Detect relevant IbcRelay actions (#1805)
Browse files Browse the repository at this point in the history
* Save relevate ibc relay tx

* changesets

* Support ANY parsing w/ registry

* review updates

* update registry
  • Loading branch information
grod220 authored Sep 24, 2024
1 parent ff2012c commit b6e32f8
Show file tree
Hide file tree
Showing 30 changed files with 430 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-coins-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/storage': patch
---

Bump registry version
5 changes: 5 additions & 0 deletions .changeset/clean-lions-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/protobuf': minor
---

Add MsgUpdateClient to typeRegistry
5 changes: 5 additions & 0 deletions .changeset/old-emus-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/bech32m': patch
---

Add isCompatAddress() func
5 changes: 5 additions & 0 deletions .changeset/rotten-starfishes-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/wasm': minor
---

Add is_controlled_address() support
5 changes: 5 additions & 0 deletions .changeset/smooth-trains-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/query': minor
---

Check for IbcRelays to add to txs
5 changes: 5 additions & 0 deletions .changeset/wise-papayas-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/types': minor
---

Update ViewServerInterface
2 changes: 1 addition & 1 deletion apps/minifront/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@cosmos-kit/core": "^2.13.1",
"@cosmos-kit/react": "^2.18.0",
"@interchain-ui/react": "^1.23.29",
"@penumbra-labs/registry": "^11.2.0",
"@penumbra-labs/registry": "^11.3.1",
"@penumbra-zone/bech32m": "workspace:*",
"@penumbra-zone/client": "workspace:*",
"@penumbra-zone/crypto-web": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion apps/minifront/src/components/tx-details/tx-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const TxViewer = ({ txInfo }: { txInfo?: TransactionInfo }) => {

// use React-Query to invoke custom hooks that call async translators.
const { data: receiverView } = useQuery(
['receiverView', txInfo, option],
['receiverView', txInfo?.toJson({ typeRegistry }), option],
() =>
fetchReceiverView(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TODO: justify
Expand Down
9 changes: 9 additions & 0 deletions packages/bech32m/src/penumbracompat1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@ export const compatAddressFromBech32 = (penumbracompat1: string): { [innerName]:
[innerName]: fromBech32(penumbracompat1 as `${typeof prefix}1${string}`, prefix),
});

export const isCompatAddress = (check: string): check is `${typeof prefix}1${string}` => {
try {
compatAddressFromBech32(check);
return true;
} catch {
return false;
}
};

export { PENUMBRA_BECH32M_ADDRESS_LENGTH, PENUMBRA_BECH32M_ADDRESS_PREFIX } from './index.js';
7 changes: 1 addition & 6 deletions packages/protobuf/src/registry.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { IMessageTypeRegistry, createRegistry } from '@bufbuild/protobuf';
import { createRegistry, IMessageTypeRegistry } from '@bufbuild/protobuf';

import * as ibcCore from './services/cosmos-ibc-core.js';
import * as penumbraCnidarium from './services/penumbra-cnidarium.js';
import * as penumbraCore from './services/penumbra-core.js';
import * as penumbraCustody from './services/penumbra-custody.js';
import * as penumbraUtil from './services/penumbra-util.js';
import * as penumbraView from './services/penumbra-view.js';

import { MsgRecvPacket } from '../gen/ibc/core/channel/v1/tx_pb.js';
import { ClientState, Header } from '../gen/ibc/lightclients/tendermint/v1/tendermint_pb.js';
import { DutchAuction } from '../gen/penumbra/core/component/auction/v1/auction_pb.js';

Expand Down Expand Up @@ -38,9 +36,6 @@ export const typeRegistry: IMessageTypeRegistry = createRegistry(
ClientState,
Header,

// gen/ibc/core/channel/v1/tx_pb
MsgRecvPacket,

// penumbra/core/component/auction/v1/auction_pb
DutchAuction,
);
Expand Down
4 changes: 4 additions & 0 deletions packages/protobuf/src/services/cosmos-ibc-core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { Query as IbcChannelService } from '../../gen/ibc/core/channel/v1/query_connect.js';
export { Msg as IbcChannelMsgService } from '../../gen/ibc/core/channel/v1/tx_connect.js';

export { Query as IbcClientService } from '../../gen/ibc/core/client/v1/query_connect.js';
export { Msg as IbcClientMsgService } from '../../gen/ibc/core/client/v1/tx_connect.js';

export { Query as IbcConnectionService } from '../../gen/ibc/core/connection/v1/query_connect.js';
export { Msg as IbcConnectionMsgService } from '../../gen/ibc/core/connection/v1/tx_connect.js';
10 changes: 8 additions & 2 deletions packages/protobuf/src/web.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type {
import {
IbcChannelMsgService,
IbcChannelService,
IbcClientMsgService,
IbcClientService,
IbcConnectionMsgService,
IbcConnectionService,
} from './services/cosmos-ibc-core.js';
import type { CustodyService } from './services/penumbra-custody.js';
Expand All @@ -11,11 +14,11 @@ import type {
CommunityPoolService,
CompactBlockService,
DexService,
SimulationService,
FeeService,
GovernanceService,
SctService,
ShieldedPoolService,
SimulationService,
StakeService,
} from './services/penumbra-core.js';
import type { TendermintProxyService } from './services/penumbra-util.js';
Expand All @@ -30,8 +33,11 @@ export type PenumbraService =
| typeof FeeService
| typeof GovernanceService
| typeof IbcChannelService
| typeof IbcChannelMsgService
| typeof IbcClientService
| typeof IbcClientMsgService
| typeof IbcConnectionService
| typeof IbcConnectionMsgService
| typeof SctService
| typeof ShieldedPoolService
| typeof SimulationService
Expand Down
1 change: 1 addition & 0 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export class BlockProcessor implements BlockProcessorInterface {
spentNullifiers,
recordsByCommitment,
blockTx,
addr => this.viewServer.isControlledAddress(addr),
);

// this simply stores the new records with 'rehydrated' sources to idb
Expand Down
79 changes: 64 additions & 15 deletions packages/query/src/helpers/identify-txs.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { describe, expect, test } from 'vitest';
import {
CommitmentSource,
Nullifier,
} from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb';
import { Nullifier } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb';
import { StateCommitment } from '@penumbra-zone/protobuf/penumbra/crypto/tct/v1/tct_pb';
import {
Action,
Transaction,
TransactionBody,
} from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import {
BLANK_TX_SOURCE,
getCommitmentsFromActions,
getNullifiersFromActions,
identifyTransactions,
Expand All @@ -27,10 +25,15 @@ import {
SwapClaimBody,
} from '@penumbra-zone/protobuf/penumbra/core/component/dex/v1/dex_pb';
import { SpendableNoteRecord, SwapRecord } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb';

const BLANK_TX_SOURCE = new CommitmentSource({
source: { case: 'transaction', value: { id: new Uint8Array() } },
});
import { Any } from '@bufbuild/protobuf';
import {
FungibleTokenPacketData,
IbcRelay,
} from '@penumbra-zone/protobuf/penumbra/core/component/ibc/v1/ibc_pb';
import { addressFromBech32m } from '@penumbra-zone/bech32m/penumbra';
import { Address } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb';
import { Packet } from '@penumbra-zone/protobuf/ibc/core/channel/v1/channel_pb';
import { MsgRecvPacket } from '@penumbra-zone/protobuf/ibc/core/channel/v1/tx_pb';

describe('getCommitmentsFromActions', () => {
test('returns empty array when tx.body.actions is undefined', () => {
Expand Down Expand Up @@ -213,7 +216,12 @@ describe('identifyTransactions', () => {
const spentNullifiers = new Set<Nullifier>();
const commitmentRecords = new Map<StateCommitment, SpendableNoteRecord | SwapRecord>();

const result = await identifyTransactions(spentNullifiers, commitmentRecords, blockTx);
const result = await identifyTransactions(
spentNullifiers,
commitmentRecords,
blockTx,
() => false,
);

expect(result.relevantTxs).toEqual([]);
expect(result.recoveredSourceRecords).toEqual([]);
Expand Down Expand Up @@ -311,12 +319,17 @@ describe('identifyTransactions', () => {

const spentNullifiersBeforeSize = spentNullifiers.size;
const commitmentRecordsBeforeSize = commitmentRecords.size;
const result = await identifyTransactions(spentNullifiers, commitmentRecords, [
tx1, // relevant
tx2, // relevant
tx3, // not
tx4, // not
]);
const result = await identifyTransactions(
spentNullifiers,
commitmentRecords,
[
tx1, // relevant
tx2, // relevant
tx3, // not
tx4, // not
],
() => false,
);

expect(result.relevantTxs.length).toBe(2);
expect(result.recoveredSourceRecords.length).toBe(1);
Expand All @@ -328,4 +341,40 @@ describe('identifyTransactions', () => {
expect(spentNullifiersBeforeSize).toEqual(spentNullifiers.size);
expect(commitmentRecordsBeforeSize).toEqual(commitmentRecords.size);
});

test('identifies ibc relays', async () => {
const knownAddr =
'penumbra1e8k5cyds484dxvapeamwveh5khqv4jsvyvaf5wwxaaccgfghm229qw03pcar3ryy8smptevstycch0qk3uu0rgkvtjpxy3cu3rjd0agawqtlz6erev28a6sg69u7cxy0t02nd4';
const unknownAddr =
'penumbracompat1147mfall0zr6am5r45qkwht7xqqrdsp50czde7empv7yq2nk3z8yyfh9k9520ddgswkmzar22vhz9dwtuem7uxw0qytfpv7lk3q9dp8ccaw2fn5c838rfackazmgf3ahhwqq0da';
const tx = new Transaction({
body: {
actions: [createIbcRelay(knownAddr), createIbcRelay(unknownAddr)],
},
});
const blockTx = [tx];
const spentNullifiers = new Set<Nullifier>();
const commitmentRecords = new Map<StateCommitment, SpendableNoteRecord | SwapRecord>();

const result = await identifyTransactions(spentNullifiers, commitmentRecords, blockTx, addr =>
addr.equals(new Address(addressFromBech32m(knownAddr))),
);

expect(result.relevantTxs.length).toBe(1);
expect(result.relevantTxs[0]?.data.equals(tx)).toBeTruthy();
expect(result.recoveredSourceRecords.length).toBe(0);
});
});

const createIbcRelay = (receiver: string): Action => {
const tokenPacketData = new FungibleTokenPacketData({ receiver });
const encoder = new TextEncoder();
const relevantRelay = Any.pack(
new MsgRecvPacket({
packet: new Packet({ data: encoder.encode(tokenPacketData.toJsonString()) }),
}),
);
return new Action({
action: { case: 'ibcRelayAction', value: new IbcRelay({ rawAction: relevantRelay }) },
});
};
51 changes: 49 additions & 2 deletions packages/query/src/helpers/identify-txs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,50 @@ import { SpendableNoteRecord, SwapRecord } from '@penumbra-zone/protobuf/penumbr
import { Transaction } from '@penumbra-zone/protobuf/penumbra/core/transaction/v1/transaction_pb';
import { TransactionId } from '@penumbra-zone/protobuf/penumbra/core/txhash/v1/txhash_pb';
import { sha256Hash } from '@penumbra-zone/crypto-web/sha256';
import { MsgRecvPacket } from '@penumbra-zone/protobuf/ibc/core/channel/v1/tx_pb';
import { FungibleTokenPacketData } from '@penumbra-zone/protobuf/penumbra/core/component/ibc/v1/ibc_pb';
import { ViewServerInterface } from '@penumbra-zone/types/servers';
import { parseIntoAddr } from '@penumbra-zone/types/address';

const BLANK_TX_SOURCE = new CommitmentSource({
export const BLANK_TX_SOURCE = new CommitmentSource({
source: { case: 'transaction', value: { id: new Uint8Array() } },
});

// Identifies if a tx with a relay action of which the receiver is the user
const hasRelevantIbcRelay = (
tx: Transaction,
isControlledAddr: ViewServerInterface['isControlledAddress'],
) => {
return tx.body?.actions.some(action => {
if (action.action.case !== 'ibcRelayAction') {
return false;
}

if (!action.action.value.rawAction?.is(MsgRecvPacket.typeName)) {
return false;
}

const recvPacket = new MsgRecvPacket();
const success = action.action.value.rawAction.unpackTo(recvPacket);
if (!success) {
throw new Error('Error while trying to unpack Any to MsgRecvPacket');
}

if (!recvPacket.packet?.data) {
throw new Error('No FungibleTokenPacketData MsgRecvPacket');
}

try {
const dataString = new TextDecoder().decode(recvPacket.packet.data);
const { receiver } = FungibleTokenPacketData.fromJsonString(dataString);
const receivingAddr = parseIntoAddr(receiver);
return isControlledAddr(receivingAddr);
} catch (e) {
return false;
}
});
};

// Used as a type-check helper as .filter(Boolean) still results with undefined as a possible value
const isDefined = <T>(value: T | null | undefined): value is NonNullable<T> =>
value !== null && value !== undefined;
Expand Down Expand Up @@ -70,6 +109,7 @@ const searchRelevant = async (
tx: Transaction,
spentNullifiers: Set<Nullifier>,
commitmentRecords: Map<StateCommitment, SpendableNoteRecord | SwapRecord>,
isControlledAddr: ViewServerInterface['isControlledAddress'],
): Promise<
{ relevantTx: RelevantTx; recoveredSourceRecords: RecoveredSourceRecords } | undefined
> => {
Expand Down Expand Up @@ -99,6 +139,10 @@ const searchRelevant = async (
}
}

if (hasRelevantIbcRelay(tx, isControlledAddr)) {
txId ??= await generateTxId(tx);
}

if (txId) {
return {
relevantTx: { id: txId, data: tx },
Expand All @@ -115,14 +159,17 @@ export const identifyTransactions = async (
spentNullifiers: Set<Nullifier>,
commitmentRecords: Map<StateCommitment, SpendableNoteRecord | SwapRecord>,
blockTx: Transaction[],
isControlledAddr: ViewServerInterface['isControlledAddress'],
): Promise<{
relevantTxs: RelevantTx[];
recoveredSourceRecords: RecoveredSourceRecords;
}> => {
const relevantTxs: RelevantTx[] = [];
const recoveredSourceRecords: RecoveredSourceRecords = [];

const searchPromises = blockTx.map(tx => searchRelevant(tx, spentNullifiers, commitmentRecords));
const searchPromises = blockTx.map(tx =>
searchRelevant(tx, spentNullifiers, commitmentRecords, isControlledAddr),
);
const results = await Promise.all(searchPromises);

for (const result of results) {
Expand Down
4 changes: 2 additions & 2 deletions packages/storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
"idb": "^8.0.0"
},
"devDependencies": {
"@penumbra-labs/registry": "^11.2.0",
"@penumbra-labs/registry": "^11.3.1",
"@penumbra-zone/protobuf": "workspace:*",
"fetch-mock": "^10.0.7"
},
"peerDependencies": {
"@bufbuild/protobuf": "^1.10.0",
"@penumbra-labs/registry": "^11.2.0",
"@penumbra-labs/registry": "^11.3.1",
"@penumbra-zone/bech32m": "workspace:*",
"@penumbra-zone/getters": "workspace:*",
"@penumbra-zone/protobuf": "workspace:*",
Expand Down
5 changes: 3 additions & 2 deletions packages/storage/src/indexed-db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { PartialMessage, PlainMessage } from '@bufbuild/protobuf';
import { getAmountFromRecord } from '@penumbra-zone/getters/spendable-note-record';
import { isZero } from '@penumbra-zone/types/amount';
import { IDB_VERSION } from './config.js';
import { typeRegistry } from '@penumbra-zone/protobuf';

const assertBytes = (v?: Uint8Array, expect?: number, name = 'value'): v is Uint8Array => {
if (expect !== undefined && v?.length !== expect) {
Expand Down Expand Up @@ -363,7 +364,7 @@ export class IndexedDb implements IndexedDbInterface {
const tx = new TransactionInfo({ id, height, transaction });
await this.u.update({
table: 'TRANSACTIONS',
value: tx.toJson() as Jsonified<TransactionInfo>,
value: tx.toJson({ typeRegistry }) as Jsonified<TransactionInfo>,
});
}

Expand All @@ -374,7 +375,7 @@ export class IndexedDb implements IndexedDbInterface {
if (!jsonRecord) {
return undefined;
}
return TransactionInfo.fromJson(jsonRecord);
return TransactionInfo.fromJson(jsonRecord, { typeRegistry });
}

async getFmdParams(): Promise<FmdParameters | undefined> {
Expand Down
Loading

0 comments on commit b6e32f8

Please sign in to comment.