Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect relevant IbcRelay actions #1805

Merged
merged 5 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/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],
Copy link
Collaborator Author

@grod220 grod220 Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we are saving relay transactions, there is a chance of an Any field needing to be serialized. For that reason, it needs access to the registry to know how to serialize/deserialize. Hence, why you see typeRegistry being added to a number of from/toJson() methods.

() =>
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
Comment on lines -41 to -43
Copy link
Collaborator Author

@grod220 grod220 Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary to list specifically in the registry as it exists in the newly added IbcChannelMsgService already

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
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
3 changes: 2 additions & 1 deletion packages/storage/src/indexed-db/stream.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AnyMessage, JsonValue, Message, MessageType } from '@bufbuild/protobuf';
import { IDBPCursorWithValue } from 'idb';
import type { PenumbraDb, PenumbraStoreNames } from '@penumbra-zone/types/indexed-db';
import { typeRegistry } from '@penumbra-zone/protobuf';

export class IdbCursorSource<N extends PenumbraStoreNames, T extends Message<T> = AnyMessage>
implements UnderlyingDefaultSource<T>
Expand All @@ -15,7 +16,7 @@ export class IdbCursorSource<N extends PenumbraStoreNames, T extends Message<T>
void (async () => {
let cursor = await this.cursor;
while (cursor) {
cont.enqueue(this.messageType.fromJson(cursor.value as JsonValue));
cont.enqueue(this.messageType.fromJson(cursor.value as JsonValue, { typeRegistry }));
cursor = await cursor.continue();
}
cont.close();
Expand Down
Loading
Loading