Skip to content

Commit

Permalink
IBC out: round up to nearest 10min (#956)
Browse files Browse the repository at this point in the history
* IBC out: round up to nearest 10min

* Update timeout retrieval method to reflect core

* Review updates

* Update apps/minifront/src/state/ibc.ts

Co-authored-by: Jesse Pinho <[email protected]>

---------

Co-authored-by: Jesse Pinho <[email protected]>
  • Loading branch information
grod220 and jessepinho authored Apr 22, 2024
1 parent 32f093d commit ba66145
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 19 deletions.
8 changes: 6 additions & 2 deletions apps/extension/src/get-rpc-impls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { createPromiseClient, ServiceImpl } from '@connectrpc/connect';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { createProxyImpl, noContextHandler } from '@penumbra-zone/transport-dom/src/proxy';
import { rethrowImplErrors } from './utils/rethrow-impl-errors';
import { Query as IbcProxy } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/client/v1/query_connect';
import { Query as IbcClientService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/client/v1/query_connect';
import { Query as IbcChannelService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/channel/v1/query_connect';
import { Query as IbcConnectionService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/connection/v1/query_connect';
import { QueryService as AppService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/app/v1/app_connect';
import { QueryService as CompactBlockService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/component/compact_block/v1/compact_block_connect';
import {
Expand Down Expand Up @@ -36,7 +38,9 @@ export const getRpcImpls = async () => {
DexService,
DexSimulationService,
GovernanceService,
IbcProxy,
IbcClientService,
IbcChannelService,
IbcConnectionService,
ShieldedPoolService,
TendermintProxyService,
].map(
Expand Down
6 changes: 6 additions & 0 deletions apps/minifront/src/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { SimulationService } from '@buf/penumbra-zone_penumbra.connectrpc_es/pen
import { CustodyService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/custody/v1/custody_connect';
import { QueryService as StakeService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/component/stake/v1/stake_connect';
import { Query as IbcClientService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/client/v1/query_connect';
import { Query as IbcChannelService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/channel/v1/query_connect';
import { Query as IbcConnectionService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/connection/v1/query_connect';
import { QueryService as SctService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/component/sct/v1/sct_connect';

export const viewClient = createPraxClient(ViewService);
Expand All @@ -14,6 +16,10 @@ export const simulateClient = createPraxClient(SimulationService);

export const ibcClient = createPraxClient(IbcClientService);

export const ibcChannelClient = createPraxClient(IbcChannelService);

export const ibcConnectionClient = createPraxClient(IbcConnectionService);

export const sctClient = createPraxClient(SctService);

export const stakeClient = createPraxClient(StakeService);
11 changes: 11 additions & 0 deletions apps/minifront/src/state/ibc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { produce } from 'immer';
import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
import { bech32ToAddress } from '@penumbra-zone/bech32/src/address';
import { Chain } from '@penumbra-labs/registry';
import { currentTimePlusTwoDaysRounded } from './ibc';

describe.skip('IBC Slice', () => {
const selectionExample = new BalancesResponse({
Expand Down Expand Up @@ -107,3 +108,13 @@ describe.skip('IBC Slice', () => {
});
});
});

describe('currentTimePlusTwoDaysRounded', () => {
test('should add exactly two days to the current time and round up to the nearest ten minutes', () => {
const currentTimeMs = 1713519156000; // Apr 19 2024 9:32:36
const twoDaysRoundedNano = 1713692400000000000n; // Apr 21 2024 9:40:00

const result = currentTimePlusTwoDaysRounded(currentTimeMs);
expect(result).toEqual(twoDaysRoundedNano);
});
});
62 changes: 47 additions & 15 deletions apps/minifront/src/state/ibc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import {
import { BigNumber } from 'bignumber.js';
import { ClientState } from '@buf/cosmos_ibc.bufbuild_es/ibc/lightclients/tendermint/v1/tendermint_pb';
import { Height } from '@buf/cosmos_ibc.bufbuild_es/ibc/core/client/v1/client_pb';
import { ibcClient, viewClient } from '../clients';
import { ibcChannelClient, ibcClient, ibcConnectionClient, viewClient } from '../clients';
import {
getDisplayDenomExponentFromValueView,
getMetadata,
} from '@penumbra-zone/getters/src/value-view';
import { getAddressIndex } from '@penumbra-zone/getters/src/address-view';
import { typeRegistry } from '@penumbra-zone/types/src/registry';
import { toBaseUnit } from '@penumbra-zone/types/src/lo-hi';
import { planBuildBroadcast } from './helpers';
import { amountMoreThanBalance } from './send';
Expand Down Expand Up @@ -90,29 +89,62 @@ export const createIbcSendSlice = (): SliceCreator<IbcSendSlice> => (set, get) =
};
};

const tenMinsMs = 1000 * 60 * 10;
const twoDaysMs = 1000 * 60 * 60 * 24 * 2;

// Timeout is two days. However, in order to prevent identifying oneself by clock skew,
// timeout time is rounded up to the nearest 10 minute interval.
// Reference in core: https://github.com/penumbra-zone/penumbra/blob/1376d4b4f47f44bcc82e8bbdf18262942edf461e/crates/bin/pcli/src/command/tx.rs#L1066-L1067
export const currentTimePlusTwoDaysRounded = (currentTimeMs: number): bigint => {
const twoDaysFromNowMs = currentTimeMs + twoDaysMs;

// round to next ten-minute interval
const roundedTimeoutMs = twoDaysFromNowMs + tenMinsMs - (twoDaysFromNowMs % tenMinsMs);

// 1 million nanoseconds per millisecond (converted to bigint)
const roundedTimeoutNs = BigInt(roundedTimeoutMs) * 1_000_000n;

return roundedTimeoutNs;
};

// Reference in core: https://github.com/penumbra-zone/penumbra/blob/1376d4b4f47f44bcc82e8bbdf18262942edf461e/crates/bin/pcli/src/command/tx.rs#L998-L1050
const getTimeout = async (
chain: Chain,
): Promise<{ timeoutTime: bigint; timeoutHeight: Height }> => {
// timeout 2 days from now, in nanoseconds since epoch
const twoDaysMs = BigInt(2 * 24 * 60 * 60 * 1000); // 2 days * 24 hours/day * 60 minutes/hour * 60 seconds/minute * 1000 milliseconds per second
// truncate resolution at seconds, to obfuscate clock skew
const lowPrecisionNowMs = BigInt(Math.floor(Date.now() / 1000) * 1000); // ms/1000 to second, floor, second*1000 to ms
// (now + two days) as nanoseconds
const timeoutTime = (lowPrecisionNowMs + twoDaysMs) * 1_000_000n; // 1 million nanoseconds per millisecond
const { channel } = await ibcChannelClient.channel({
portId: 'transfer',
channelId: chain.ibcChannel,
});

const { clientStates } = await ibcClient.clientStates({});
const unpacked = clientStates
.map(cs => cs.clientState!.unpack(typeRegistry))
.filter(Boolean) as ClientState[];
const connectionId = channel?.connectionHops[0];
if (!connectionId) {
throw new Error('no connectionId in channel returned from ibcChannelClient request');
}

const clientState = unpacked.find(cs => cs.chainId === chain.chainId);
if (!clientState) throw new Error('Could not find chain id client state');
const { connection } = await ibcConnectionClient.connection({
connectionId,
});
const clientId = connection?.clientId;
if (!clientId) {
throw new Error('no clientId ConnectionEnd returned from ibcConnectionClient request');
}

const { clientState: anyClientState } = await ibcClient.clientState({ clientId: clientId });
if (!anyClientState) {
throw new Error(`Could not get state for client id ${clientId}`);
}

const clientState = new ClientState();
const success = anyClientState.unpackTo(clientState); // Side effect of augmenting input clientState with data
if (!success) {
throw new Error(`Error while trying to unpack Any to ClientState for client id ${clientId}`);
}

// assuming a block time of 10s and adding ~1000 blocks (~3 hours)
const revisionHeight = clientState.latestHeight!.revisionHeight + 1000n;

return {
timeoutTime,
timeoutTime: currentTimePlusTwoDaysRounded(Date.now()),
timeoutHeight: new Height({
revisionHeight,
revisionNumber: clientState.latestHeight!.revisionNumber,
Expand Down
8 changes: 6 additions & 2 deletions packages/types/src/registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Query as IbcQueryService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/client/v1/query_connect';
import { Query as IbcClientService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/client/v1/query_connect';
import { Query as IbcChannelService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/channel/v1/query_connect';
import { Query as IbcConnectionService } from '@buf/cosmos_ibc.connectrpc_es/ibc/core/connection/v1/query_connect';
import { QueryService as AppService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/app/v1/app_connect';
import { QueryService as CompactBlockService } from '@buf/penumbra-zone_penumbra.connectrpc_es/penumbra/core/component/compact_block/v1/compact_block_connect';
import {
Expand Down Expand Up @@ -37,7 +39,9 @@ export const typeRegistry: IMessageTypeRegistry = createRegistry(
DexService,
DexSimulationService,
GovernanceService,
IbcQueryService,
IbcClientService,
IbcChannelService,
IbcConnectionService,
SctService,
ShieldedPoolService,
StakeService,
Expand Down

0 comments on commit ba66145

Please sign in to comment.