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

Process Dutch auction NFT metadata in the block processor #1015

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
111 changes: 71 additions & 40 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
CommitmentSource,
Nullifier,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/sct/v1/sct_pb';
import { Transaction } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';
import {
Action,
Transaction,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';
import { TransactionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/txhash/v1/txhash_pb';
import { StateCommitment } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb';
import {
Expand All @@ -37,6 +40,7 @@ import { PRICE_RELEVANCE_THRESHOLDS } from '@penumbra-zone/constants/assets';
import { toDecimalExchangeRate } from '@penumbra-zone/types/amount';
import { ValidatorInfoResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb';
import { uint8ArrayToHex } from '@penumbra-zone/types/hex';
import { getAuctionId, getAuctionNftMetadata } from '@penumbra-zone/wasm/auction';

declare global {
// `var` required for global declaration (as let/const are block scoped)
Expand All @@ -52,10 +56,16 @@ interface QueryClientProps {
stakingTokenMetadata: Metadata;
}

const blankTxSource = new CommitmentSource({
const BLANK_TX_SOURCE = new CommitmentSource({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a rename to make it clear that this is a never-changing constant

source: { case: 'transaction', value: { id: new Uint8Array() } },
});

const POSITION_STATES: PositionState[] = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sort of the same here — extracting this constant from inside of a function, where it was being defined on every function call despite never changing.

new PositionState({ state: PositionState_PositionStateEnum.OPENED }),
new PositionState({ state: PositionState_PositionStateEnum.CLOSED }),
new PositionState({ state: PositionState_PositionStateEnum.WITHDRAWN, sequence: 0n }),
];

export class BlockProcessor implements BlockProcessorInterface {
private readonly querier: RootQuerier;
private readonly indexedDb: IndexedDbInterface;
Expand Down Expand Up @@ -142,7 +152,7 @@ export class BlockProcessor implements BlockProcessorInterface {
if (txCommitments.some(txCommitment => stateCommitment.equals(txCommitment))) {
txId ??= new TransactionId({ inner: await sha256Hash(tx.toBinary()) });
relevantTx.set(txId, tx);
if (blankTxSource.equals(spendableNoteRecord.source)) {
if (BLANK_TX_SOURCE.equals(spendableNoteRecord.source)) {
spendableNoteRecord.source = new CommitmentSource({
source: { case: 'transaction', value: { id: txId.inner } },
});
Expand Down Expand Up @@ -261,7 +271,7 @@ export class BlockProcessor implements BlockProcessorInterface {
// - detect LpNft position opens
// - generate all possible position state metadata
// - update idb
await this.identifyLpNftPositions(blockTx);
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update the description above processTransactions(blockTx) to accurately describe this function? Can we also move these existing comments to identifyLpNftPositions() and add comments to identifyAuctionNfts().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, good catch — fixed.

await this.processTransactions(blockTx);

// at this point txinfo can be generated and saved. this will resolve
// pending broadcasts, and populate the transaction list.
Expand Down Expand Up @@ -361,45 +371,66 @@ export class BlockProcessor implements BlockProcessorInterface {
return spentNullifiers;
}

private async identifyLpNftPositions(txs: Transaction[]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You'll probably want to review the rest of this file with "Hide whitespace" turned on. In short, I:

  1. created a new processTransactions method that iterates over each transaction, then each action of each transaction, and calls this.identifyAuctionNfts() and this.identifyLpNftPositions() on each action.
  2. created a new identifyAuctioNfts() method that saves auction NFT metadata when it appears.
  3. modified identifyLpNftPositions() to process individual actions, rather than processing an array of transactions.

Copy link
Contributor

Choose a reason for hiding this comment

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

this a great design choice as it's extensible to other metadata-related processing we'll need to do in the future!

const positionStates: PositionState[] = [
new PositionState({ state: PositionState_PositionStateEnum.OPENED }),
new PositionState({ state: PositionState_PositionStateEnum.CLOSED }),
new PositionState({ state: PositionState_PositionStateEnum.WITHDRAWN, sequence: 0n }),
];

private async processTransactions(txs: Transaction[]) {
for (const tx of txs) {
for (const { action } of tx.body?.actions ?? []) {
if (action.case === 'positionOpen' && action.value.position) {
for (const state of positionStates) {
const metadata = getLpNftMetadata(computePositionId(action.value.position), state);
await this.indexedDb.saveAssetsMetadata(metadata);
}
// to optimize on-chain storage PositionId is not written in the positionOpen action,
// but can be computed via hashing of immutable position fields
await this.indexedDb.addPosition(
computePositionId(action.value.position),
action.value.position,
);
}
if (action.case === 'positionClose' && action.value.positionId) {
await this.indexedDb.updatePosition(
action.value.positionId,
new PositionState({ state: PositionState_PositionStateEnum.CLOSED }),
);
}
if (action.case === 'positionWithdraw' && action.value.positionId) {
// Record the LPNFT for the current sequence number.
const positionState = new PositionState({
state: PositionState_PositionStateEnum.WITHDRAWN,
sequence: action.value.sequence,
});
const metadata = getLpNftMetadata(action.value.positionId, positionState);
await this.indexedDb.saveAssetsMetadata(metadata);

await this.indexedDb.updatePosition(action.value.positionId, positionState);
}
await Promise.all([this.identifyAuctionNfts(action), this.identifyLpNftPositions(action)]);
}
}
}

private async identifyAuctionNfts(action: Action['action']) {
if (action.case === 'actionDutchAuctionSchedule' && action.value.description) {
const auctionId = getAuctionId(action.value.description);
const metadata = getAuctionNftMetadata(
auctionId,
// Always a sequence number of 0 when starting a Dutch auction
Copy link
Member

Choose a reason for hiding this comment

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

👍

0n,
);
await this.indexedDb.saveAssetsMetadata(metadata);
} else if (action.case === 'actionDutchAuctionEnd' && action.value.auctionId) {
const metadata = getAuctionNftMetadata(
action.value.auctionId,
// Always a sequence number of 1 when ending a Dutch auction
Copy link
Member

Choose a reason for hiding this comment

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

👍

1n,
);
await this.indexedDb.saveAssetsMetadata(metadata);
}
/**
* @todo Handle `actionDutchAuctionWithdraw`, and figure out how to
* determine the sequence number if there have been multiple withdrawals.
Copy link
Member

Choose a reason for hiding this comment

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

I think one way to do that is to structure the Action['action'] with a sequence number, rather than adding it ex-post in this method, unsure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, thanks, added a link to this comment in the ticket (#1020)

*/
}

private async identifyLpNftPositions(action: Action['action']) {
if (action.case === 'positionOpen' && action.value.position) {
for (const state of POSITION_STATES) {
const metadata = getLpNftMetadata(computePositionId(action.value.position), state);
await this.indexedDb.saveAssetsMetadata(metadata);
}
// to optimize on-chain storage PositionId is not written in the positionOpen action,
// but can be computed via hashing of immutable position fields
await this.indexedDb.addPosition(
computePositionId(action.value.position),
action.value.position,
);
}
if (action.case === 'positionClose' && action.value.positionId) {
await this.indexedDb.updatePosition(
action.value.positionId,
new PositionState({ state: PositionState_PositionStateEnum.CLOSED }),
);
}
if (action.case === 'positionWithdraw' && action.value.positionId) {
// Record the LPNFT for the current sequence number.
const positionState = new PositionState({
state: PositionState_PositionStateEnum.WITHDRAWN,
sequence: action.value.sequence,
});
const metadata = getLpNftMetadata(action.value.positionId, positionState);
await this.indexedDb.saveAssetsMetadata(metadata);

await this.indexedDb.updatePosition(action.value.positionId, positionState);
}
}

Expand Down
Loading
Loading