Skip to content

Commit

Permalink
Process Dutch auction NFT metadata in the block processor (#1015)
Browse files Browse the repository at this point in the history
* Process Dutch auctions in the block processor

* Fix Rust ownership issue

* Fix test file

* Simplify a bit

* Fix mutable key issue

* Fix comments
  • Loading branch information
jessepinho authored May 2, 2024
1 parent 6fb898a commit a9cb099
Show file tree
Hide file tree
Showing 11 changed files with 367 additions and 130 deletions.
134 changes: 88 additions & 46 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({
source: { case: 'transaction', value: { id: new Uint8Array() } },
});

const POSITION_STATES: PositionState[] = [
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 @@ -255,13 +265,7 @@ export class BlockProcessor implements BlockProcessorInterface {
// TODO: this is the second time we save these records, after "saveScanResult"
await this.saveRecoveredCommitmentSources(recordsWithSources);

// during wasm tx info generation later, wasm independently queries idb
// for asset metadata, so we have to pre-populate. LpNft position states
// aren't known by the chain so aren't populated by identifyNewAssets
// - detect LpNft position opens
// - generate all possible position state metadata
// - update idb
await this.identifyLpNftPositions(blockTx);
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 +365,83 @@ export class BlockProcessor implements BlockProcessorInterface {
return spentNullifiers;
}

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

/**
* Identify various pieces of data from the transaction that we need to save,
* such as metadata, liquidity positions, etc.
*/
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)]);
}
}
}

/**
* during wasm tx info generation later, wasm independently queries idb for
* asset metadata, so we have to pre-populate. Auction NFTs aren't known by
* the chain so aren't populated by identifyNewAssets.
*/
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
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
1n,
);
await this.indexedDb.saveAssetsMetadata(metadata);
}
/**
* @todo Handle `actionDutchAuctionWithdraw`, and figure out how to
* determine the sequence number if there have been multiple withdrawals.
*/
}

/**
* during wasm tx info generation later, wasm independently queries idb for
* asset metadata, so we have to pre-populate. LpNft position states aren't
* known by the chain so aren't populated by identifyNewAssets
* - detect LpNft position opens
* - generate all possible position state metadata
* - update idb
*/
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

0 comments on commit a9cb099

Please sign in to comment.