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

Update core dependencies to v0.70.3 #839

Merged
merged 29 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b32c81e
Update dependencies
jessepinho Mar 26, 2024
a8127ca
Add epochs to constants
jessepinho Mar 26, 2024
b059118
Get the latest epoch from the database, rather than from the rate data
jessepinho Mar 27, 2024
c3b06cd
Add missing properties
jessepinho Mar 27, 2024
d3eb48e
update deps to 0.70.1
TalDerei Mar 27, 2024
30ed73e
Change how unbonding tokens are parsed
jessepinho Mar 27, 2024
d78eb7c
Fix how unbonding token symbols are displayed
jessepinho Mar 27, 2024
b97cc09
Update deps
jessepinho Mar 27, 2024
69a0d12
Fix getter name
jessepinho Mar 27, 2024
512a1fb
Fix property
jessepinho Mar 27, 2024
d2db570
Fix a few bugs with how epochs were stored
jessepinho Mar 27, 2024
d0bbdf7
Fix issues with unbonding token display denom
jessepinho Mar 27, 2024
d992488
Don't throw when asset metadata doesn't exist
jessepinho Mar 27, 2024
a952c71
Handle missing metadata
jessepinho Mar 27, 2024
b333ac7
Hide the epoch index of an undelegate action
jessepinho Mar 27, 2024
cdc8e16
Fix getter; show unbonding start height for undelegate claims
jessepinho Mar 27, 2024
9b48ccb
Fix tests
jessepinho Mar 27, 2024
01eb8e3
Tweak var name
jessepinho Mar 27, 2024
d741b34
Fix comment
jessepinho Mar 27, 2024
1f02d83
Fix deprecation warning
jessepinho Mar 27, 2024
e32f590
Fix test setup
jessepinho Mar 27, 2024
3f07204
Update to 0.70.3
jessepinho Mar 27, 2024
2f3f7ca
Add changeset
jessepinho Mar 28, 2024
f596006
Throw when metadata is not found
jessepinho Mar 28, 2024
87b6436
Rewrite getEpochByHeight as async iterator
jessepinho Mar 28, 2024
b9fc0f4
Refactor
jessepinho Mar 28, 2024
64a1525
Update proving key version
jessepinho Mar 28, 2024
cdfc04b
Fix quote marks
jessepinho Mar 28, 2024
d581b4f
Fix linting issue
jessepinho Mar 28, 2024
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
2 changes: 1 addition & 1 deletion apps/extension/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

PRAX=lkpmkhpnhknhmibgnmmhdhgdilepfghe
IDB_VERSION=30
IDB_VERSION=31
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed for the update to unbonding tokens, which now have a different symbol

USDC_ASSET_ID="reum7wQmk/owgvGMWMZn/6RFPV24zIKq3W6In/WwZgg="
MINIFRONT_URL=https://app.testnet.penumbra.zone/
PENUMBRA_NODE_PD_URL=https://grpc.testnet.penumbra.zone/
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,28 @@ import {
import { sctClient, stakeClient, viewClient } from '../../clients';
import {
getAmount,
getStartEpochIndexFromValueView,
getUnbondingStartHeightFromValueView,
getValidatorIdentityKeyAsBech32StringFromValueView,
} from '@penumbra-zone/getters/src/value-view';
import { asIdentityKey } from '@penumbra-zone/getters/src/string';

const getUndelegateClaimPlannerRequest =
(endEpochIndex: bigint) => async (unbondingToken: ValueView) => {
const startEpochIndex = getStartEpochIndexFromValueView(unbondingToken);
const unbondingStartHeight = getUnbondingStartHeightFromValueView(unbondingToken);
const validatorIdentityKeyAsBech32String =
getValidatorIdentityKeyAsBech32StringFromValueView(unbondingToken);
const identityKey = asIdentityKey(validatorIdentityKeyAsBech32String);
const { epoch: startEpoch } = await sctClient.epochByHeight({ height: unbondingStartHeight });

const { penalty } = await stakeClient.validatorPenalty({
startEpochIndex,
startEpochIndex: startEpoch?.index,
endEpochIndex,
identityKey,
});

return new TransactionPlannerRequest_UndelegateClaim({
validatorIdentity: identityKey,
startEpochIndex,
unbondingStartHeight,
penalty,
unbondingAmount: getAmount(unbondingToken),
});
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"dependencies": {
"@buf/cosmos_ibc.bufbuild_es": "1.8.0-20240327103030-e2006674271c.2",
"@buf/cosmos_ibc.connectrpc_es": "1.4.0-20240327103030-e2006674271c.2",
"@buf/penumbra-zone_penumbra.bufbuild_es": "1.8.0-20240326223351-a25fff24022e.2",
"@buf/penumbra-zone_penumbra.connectrpc_es": "1.4.0-20240326223351-a25fff24022e.2",
"@buf/penumbra-zone_penumbra.bufbuild_es": "1.8.0-20240327192233-658f3904024e.2",
"@buf/penumbra-zone_penumbra.connectrpc_es": "1.4.0-20240327192233-658f3904024e.2",
"@buf/tendermint_tendermint.bufbuild_es": "1.8.0-20231117195010-33ed361a9051.2",
"@bufbuild/protobuf": "^1.8.0",
"@connectrpc/connect": "^1.4.0",
Expand Down
13 changes: 10 additions & 3 deletions packages/constants/src/assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,22 @@ describe('assetPatterns', () => {

describe('unbondingTokenPattern', () => {
it('matches when a string is a valid unbonding token name', () => {
expect(assetPatterns.unbondingToken.matches('uunbonding_epoch_1_penumbravalid1abc123')).toBe(
true,
expect(
assetPatterns.unbondingToken.matches('unbonding_start_at_1_penumbravalid1abc123'),
).toBe(true);
});

it('captures the unbonding start height', () => {
const match = assetPatterns.unbondingToken.capture(
'unbonding_start_at_1_penumbravalid1abc123',
);
expect(match?.startAt).toBe('1');
});

it('does not match when a string contains, but does not begin with, a valid unbonding token name', () => {
expect(
assetPatterns.unbondingToken.matches(
'ibc-transfer/channel-1234/uunbonding_epoch_1_penumbravalid1abc123',
'ibc-transfer/channel-1234/unbonding_start_at_1_penumbravalid1abc123',
),
).toBe(false);
});
Expand Down
8 changes: 2 additions & 6 deletions packages/constants/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface DelegationCaptureGroups {
}

export interface UnbondingCaptureGroups {
epoch: string;
startAt: string;
id: string;
bech32IdentityKey: string;
}
Expand Down Expand Up @@ -73,12 +73,8 @@ export const assetPatterns: AssetPatterns = {
/^delegation_(?<bech32IdentityKey>penumbravalid1(?<id>[a-zA-HJ-NP-Z0-9]+))$/,
),
proposalNft: new RegexMatcher(/^proposal_/),
/**
* Unbonding tokens have only one denom unit, which is the base denom. Hence
* the extra `u` at the beginning.
*/
unbondingToken: new RegexMatcher(
/^uunbonding_epoch_(?<epoch>[0-9]+)_(?<bech32IdentityKey>penumbravalid1(?<id>[a-zA-HJ-NP-Z0-9]+))$/,
/^unbonding_start_at_(?<startAt>[0-9]+)_(?<bech32IdentityKey>penumbravalid1(?<id>[a-zA-HJ-NP-Z0-9]+))$/,
),
votingReceipt: new RegexMatcher(/^voted_on_/),
ibc: new RegexMatcher(/^transfer\/(?<channel>channel-\d+)\/(?<denom>.*)/),
Expand Down
16 changes: 8 additions & 8 deletions packages/getters/src/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/
import { describe, expect, it } from 'vitest';
import {
getDisplayDenomExponent,
getStartEpochIndex,
getUnbondingStartHeight,
getValidatorIdentityKeyAsBech32String,
} from './metadata';

Expand Down Expand Up @@ -30,21 +30,21 @@ describe('getDisplayDenomExponent()', () => {
});
});

describe('getStartEpochIndex()', () => {
it("gets the epoch index, coerced to a `BigInt`, from an unbonding token's asset ID", () => {
const metadata = new Metadata({ display: 'uunbonding_epoch_123_penumbravalid1abc123' });
describe('getUnbondingStartHeight()', () => {
it("gets the unbonding start height, coerced to a `BigInt`, from an unbonding token's asset ID", () => {
const metadata = new Metadata({ display: 'unbonding_start_at_123_penumbravalid1abc123' });

expect(getStartEpochIndex(metadata)).toBe(123n);
expect(getUnbondingStartHeight(metadata)).toBe(123n);
});

it("returns `undefined` for a non-unbonding token's metadata", () => {
const metadata = new Metadata({ display: 'penumbra' });

expect(getStartEpochIndex.optional()(metadata)).toBeUndefined();
expect(getUnbondingStartHeight.optional()(metadata)).toBeUndefined();
});

it('returns `undefined` for undefined metadata', () => {
expect(getStartEpochIndex.optional()(undefined)).toBeUndefined();
expect(getUnbondingStartHeight.optional()(undefined)).toBeUndefined();
});
});

Expand All @@ -58,7 +58,7 @@ describe('getValidatorIdentityKeyAsBech32String()', () => {
});

describe('when passed metadata of an unbonding token', () => {
const metadata = new Metadata({ display: 'uunbonding_epoch_123_penumbravalid1abc123' });
const metadata = new Metadata({ display: 'unbonding_start_at_123_penumbravalid1abc123' });

it("returns the bech32 representation of the validator's identity key", () => {
expect(getValidatorIdentityKeyAsBech32String(metadata)).toBe('penumbravalid1abc123');
Expand Down
10 changes: 5 additions & 5 deletions packages/getters/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ export const getDisplayDenomExponent = createGetter(
);

/**
* Get the start epoch index from the metadata of an unbonding token -- that is,
* the epoch at which unbonding started.
* Get the unbonding start height index from the metadata of an unbonding token
* -- that is, the block height at which unbonding started.
*
* For metadata of a non-unbonding token, will return `undefined`.
*/
export const getStartEpochIndex = createGetter((metadata?: Metadata) => {
export const getUnbondingStartHeight = createGetter((metadata?: Metadata) => {
if (!metadata) return undefined;

const unbondingMatch = assetPatterns.unbondingToken.capture(metadata.display);

if (unbondingMatch) {
const { epoch } = unbondingMatch;
if (epoch) return BigInt(epoch);
const { startAt } = unbondingMatch;
return BigInt(startAt);
}

return undefined;
Expand Down
14 changes: 8 additions & 6 deletions packages/getters/src/undelegate-claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export const getBody = createGetter((undelegateClaim?: UndelegateClaim) => undel

export const getValidatorIdentityFromUndelegateClaim = getBody.pipe(getValidatorIdentity);

export const getStartEpochIndexFromUndelegateClaim = getBody.pipe(
// Defining this inline rather than exporting `getStartEpochIndex` from
// `undelegate-claim-body.ts`, since `getStartEpochIndex` is already defined
// elsewhere and thus would result in a naming conflict in the exports from
// this package.
createGetter((undelegateClaimBody?: UndelegateClaimBody) => undelegateClaimBody?.startEpochIndex),
export const getUnbondingStartHeightFromUndelegateClaim = getBody.pipe(
// Defining this inline rather than exporting `getUnbondingStartHeight` from
// `undelegate-claim-body.ts`, since `getUnbondingStartHeight` is already
// defined elsewhere and thus would result in a naming conflict in the exports
// from this package.
createGetter(
(undelegateClaimBody?: UndelegateClaimBody) => undelegateClaimBody?.unbondingStartHeight,
),
);
7 changes: 4 additions & 3 deletions packages/getters/src/value-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createGetter } from './utils/create-getter';
import { bech32AssetId } from '@penumbra-zone/bech32/src/asset';
import {
getDisplayDenomExponent,
getStartEpochIndex,
getUnbondingStartHeight,
getValidatorIdentityKeyAsBech32String,
} from './metadata';
import { Any } from '@bufbuild/protobuf';
Expand Down Expand Up @@ -72,9 +72,10 @@ export const getAmount = createGetter(
);

/**
* For a `ValueView` containing an unbonding token, gets the start epoch index.
* For a `ValueView` containing an unbonding token, gets the unbonding start
* height.
*/
export const getStartEpochIndexFromValueView = getMetadata.pipe(getStartEpochIndex);
export const getUnbondingStartHeightFromValueView = getMetadata.pipe(getUnbondingStartHeight);

export const getDisplayDenomFromView = createGetter((view?: ValueView) => {
if (view?.valueView.case === 'unknownAssetId') {
Expand Down
21 changes: 13 additions & 8 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,18 @@ export class BlockProcessor implements BlockProcessorInterface {
const startHeight = fullSyncHeight ? fullSyncHeight + 1n : 0n;
let latestKnownBlockHeight = await this.querier.tendermint.latestBlockHeight();

// In the `for` loop below, we only update validator infos once we've
// reached the latest known epoch. This means that, if a user is syncing for
// the first time, they could experience a broken UI until the latest known
// epoch is reached, since they may have delegation tokens but no validator
// info to go with them. So we'll update validator infos at the beginning of
// sync as well, and force the rest of sync to wait until it's done.
if (startHeight === 0n) await this.updateValidatorInfos(0n);
if (startHeight === 0n) {
// In the `for` loop below, we only update validator infos once we've
// reached the latest known epoch. This means that, if a user is syncing
// for the first time, they could experience a broken UI until the latest
// known epoch is reached, since they may have delegation tokens but no
// validator info to go with them. So we'll update validator infos at the
// beginning of sync as well, and force the rest of sync to wait until
// it's done.
await this.updateValidatorInfos(0n);

await this.indexedDb.addEpoch(0n);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, we were skipping saving the first epoch to the database. This fixes that bug.

}

// this is an indefinite stream of the (compact) chain from the network
// intended to run continuously
Expand Down Expand Up @@ -273,7 +278,7 @@ export class BlockProcessor implements BlockProcessorInterface {

const isLastBlockOfEpoch = !!compactBlock.epochRoot;
if (isLastBlockOfEpoch) {
void this.handleEpochTransition(compactBlock.height, latestKnownBlockHeight);
await this.handleEpochTransition(compactBlock.height, latestKnownBlockHeight);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed to await due to an unlikely, but possible, race condition. When we save a new epoch to the database during an epoch transition, we look in the database for the previous latest known epoch. We grab its epoch index, increment it by 1, then use that index for the new epoch we're saving to the database.

If that took a long time, for some reason, and in the meantime the block processor had already gotten to the next epoch, we could have two epochs with the same epoch index.

This change makes sure that epoch indexes increment in the correct order.

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ describe('AssetMetadataById request handler', () => {
expect(metadataByIdResponse.denomMetadata?.equals(metadataFromNode)).toBeTruthy();
});

test('should fail to get metadata when metadata not found in idb and node', async () => {
test('should return an empty response when metadata not found in idb and node', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's really no need to throw, since it's acceptable to return an empty response from this RPC method. So I changed the behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Response on other comment

mockIndexedDb.getAssetsMetadata?.mockResolvedValue(undefined);
mockShieldedPool.assetMetadataById.mockResolvedValueOnce(undefined);
await expect(assetMetadataById(request, mockCtx)).rejects.toThrow();
const response = new AssetMetadataByIdResponse(await assetMetadataById(request, mockCtx));
expect(response.equals(new AssetMetadataByIdResponse())).toBeTruthy();
});

test('should fail if assetId is missing in request', async () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/router/src/grpc/view-protocol-server/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ export const getAssetMetadata = async (
return nodeMetadata;
}

// If the metadata is not found, throw an error with details about the asset.
throw new Error(`No denom metadata found for asset: ${JSON.stringify(targetAsset.toJson())}`);
return undefined;
jessepinho marked this conversation as resolved.
Show resolved Hide resolved
};
14 changes: 8 additions & 6 deletions packages/storage/src/indexed-db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,12 +458,14 @@ export class IndexedDb implements IndexedDbInterface {
});
}

async addEpoch(startHeight: bigint, index?: bigint): Promise<void> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're never actually using the index argument anywhere, so I just got rid of it.

if (index === undefined) {
const cursor = await this.db.transaction('EPOCHS', 'readonly').store.openCursor(null, 'prev');
const previousEpoch = cursor?.value ? Epoch.fromJson(cursor.value) : undefined;
index = previousEpoch?.index !== undefined ? previousEpoch.index + 1n : 0n;
}
/**
* Adds a new epoch with the given start height. Automatically sets the epoch
* index by finding the previous epoch index, and adding 1n.
*/
async addEpoch(startHeight: bigint): Promise<void> {
const cursor = await this.db.transaction('EPOCHS', 'readonly').store.openCursor(null, 'prev');
const previousEpoch = cursor?.value ? Epoch.fromJson(cursor.value) : undefined;
const index = previousEpoch?.index !== undefined ? previousEpoch.index + 1n : 0n;
jessepinho marked this conversation as resolved.
Show resolved Hide resolved

const newEpoch = {
startHeight: startHeight.toString(),
Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/customize-symbol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ describe('Customizing metadata', () => {
test('should work for unbonding token', () => {
const metadata = new Metadata({
display:
'uunbonding_epoch_29_penumbravalid1fjuj67ayaqueqxg03d65ps5aah6m39u39qeacu3zv2cw3dzxssyq3yrcez',
'unbonding_start_at_29_penumbravalid1fjuj67ayaqueqxg03d65ps5aah6m39u39qeacu3zv2cw3dzxssyq3yrcez',
});

expect(customizeSymbol(metadata).symbol).toBe('unbondUMe29(fjuj67ay…)');
expect(customizeSymbol(metadata).symbol).toBe('unbondUMat29(fjuj67ay…)');
});

test('should do nothing if no matches', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/customize-symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const customizeSymbol = (metadata: Metadata) => {
if (unbondingMatch) {
const shortenedId = unbondingMatch.id.slice(0, SHORTENED_ID_LENGTH);
const customized = metadata.clone();
customized.symbol = `unbondUMe${unbondingMatch.epoch}(${shortenedId}…)`;
customized.symbol = `unbondUMat${unbondingMatch.startAt}(${shortenedId}…)`;
return customized;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/indexed-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export interface IndexedDbInterface {
): AsyncGenerator<PositionId, void>;
addPosition(positionId: PositionId, position: Position): Promise<void>;
updatePosition(positionId: PositionId, newState: PositionState): Promise<void>;
addEpoch(startHeight: bigint, index?: bigint): Promise<void>;
addEpoch(startHeight: bigint): Promise<void>;
getEpochByHeight(height: bigint): Promise<Epoch | undefined>;
upsertValidatorInfo(validatorInfo: ValidatorInfo): Promise<void>;
iterateValidatorInfos(): AsyncGenerator<ValidatorInfo, void>;
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/components/ui/tx/view/undelegate-claim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { ViewBox } from './viewbox';
import { IdentityKeyComponent } from '../../identity-key-component';
import { ActionDetails } from './action-details';
import {
getStartEpochIndexFromUndelegateClaim,
getUnbondingStartHeightFromUndelegateClaim,
getValidatorIdentityFromUndelegateClaim,
} from '@penumbra-zone/getters/src/undelegate-claim';

/** Render an `UndelegateClaim` action. */
export const UndelegateClaimComponent = ({ value }: { value: UndelegateClaim }) => {
const validatorIdentity = getValidatorIdentityFromUndelegateClaim(value);
const startEpochIndex = getStartEpochIndexFromUndelegateClaim(value);
const unbondingStartHeight = getUnbondingStartHeightFromUndelegateClaim(value);

return (
<ViewBox
Expand All @@ -21,8 +21,8 @@ export const UndelegateClaimComponent = ({ value }: { value: UndelegateClaim })
<IdentityKeyComponent identityKey={validatorIdentity} />
</ActionDetails.Row>

<ActionDetails.Row label='Unbonding start epoch'>
{startEpochIndex.toString()}
<ActionDetails.Row label='Unbonding start height'>
{unbondingStartHeight.toString()}
</ActionDetails.Row>
</ActionDetails>
}
Expand Down
4 changes: 0 additions & 4 deletions packages/ui/components/ui/tx/view/undelegate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export const UndelegateComponent = ({ value }: { value: Undelegate }) => {
label='Undelegate'
visibleContent={
<ActionDetails>
<ActionDetails.Row label='Epoch index'>
{value.startEpochIndex.toString()}
</ActionDetails.Row>
Comment on lines -16 to -18
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a little strange to me: the startEpochIndex property is deprecated, so we should no longer show it. Ideally, we'd show the unbonding start height instead – but that property doesn't exist on the Undelegate proto! So for now, we'll just hide this, and revisit this if/when core adds that property to the Undelegate action.

Copy link
Contributor

Choose a reason for hiding this comment

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

can you leave the code here and just style it hidden or comment it out?


{!!value.delegationAmount && (
<ActionDetails.Row label='Delegation amount'>
{joinLoHiAmount(value.delegationAmount).toString()}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/components/ui/tx/view/value/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const ValueViewComponent = ({
const value = view.valueView.value;
const metadata = view.valueView.value.metadata;
const amount = value.amount ?? new Amount();
const exponent = getDisplayDenomExponent(metadata);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reviewers: I could use some help here.

When a user clicks the Undelegate button, the transaction approval dialog opens. We try to fetch the metadata for the undelegation tokens they'll be getting, but that will always come back empty, since the unbonding token type is now specific to the block height at which unbonding starts. That is, the unbonding token name is unbonding_start_at_N_penumbravalid1..., where N is the block height that unbonding started at.

There will never be token metadata for an unbonding token until after the transaction has been committed to the chain, since the token type is created when the transaction is committed.

But that means that the undelegation tokens are shown as "Unknown asset" in the transaction approval dialog:
image

Perhaps we should generate the unbonding token metadata on the fly here? If so, could someone point me in the right direction for how to do that? (That said, I'd love to get this PR merged before handling that, since all delegation is blocked until this PR is merged.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another wrinkle to all this: this issue, where the metadata request always returns an empty response for an undelegation token that doesn't exist yet, wasn't happening before when undelegation tokens were tied to epochs rather than block heights.

I don't know how that's possible. We should have had the same problem back then, right? That is:

  1. We transition to a new epoch.
  2. Someone goes to undelegate tokens from a validator.
  3. Since no one else has undelegated tokens from that validator in this epoch yet, the metadata for uunbonding_epoch_N_... doesn't exist yet, so the metadata response is empty.
  4. getDisplayDenomExponent throws, because it's being passed undefined metadata.

And yet, this wasn't happening before — no error was being thrown. It only started with this change, where unbonding tokens are tied to block height rather than epoch index.

I'm investigating this, but it's a bit concerning since I don't understand why it worked before!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps we should generate the unbonding token metadata on the fly here?

The assetMetadataById rpc method feels like a good place to generate metadata for this edge case so web apps have meaningful display data here.

And yet, this wasn't happening before

Umm, no idea! 😅

const exponent = getDisplayDenomExponent.optional()(metadata);
// The regex trims trailing zeros which toFormat adds in
const formattedAmount = fromBaseUnitAmount(amount, exponent)
.toFormat(6)
Expand Down
Loading
Loading