Skip to content

Commit

Permalink
Merge d607143 into d492ac8
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino authored Jun 6, 2024
2 parents d492ac8 + d607143 commit e8e1cd7
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 22 deletions.
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export abstract class BaseWallet implements Wallet {
getContractClass(id: Fr): Promise<ContractClassWithId | undefined> {
return this.pxe.getContractClass(id);
}
getContractArtifact(id: Fr): Promise<ContractArtifact | undefined> {
return this.pxe.getContractArtifact(id);
}
addCapsule(capsule: Fr[]): Promise<void> {
return this.pxe.addCapsule(capsule);
}
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@ export interface PXE {
*/
getContractClass(id: Fr): Promise<ContractClassWithId | undefined>;

/**
* Returns the contract artifact associated to a contract class.
* @param id - Identifier of the class.
*/
getContractArtifact(id: Fr): Promise<ContractArtifact | undefined>;

/**
* Queries the node to check whether the contract class with the given id has been publicly registered.
* TODO(@spalladino): This method is strictly needed to decide whether to publicly register a class or not
Expand Down
6 changes: 2 additions & 4 deletions yarn-project/circuit-types/src/logs/unencrypted_l2_log.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AztecAddress } from '@aztec/circuits.js';
import { EventSelector } from '@aztec/foundation/abi';
import { randomBytes, sha256Trunc } from '@aztec/foundation/crypto';
import { BufferReader, prefixBufferWithLength } from '@aztec/foundation/serialize';
import { BufferReader, prefixBufferWithLength, toHumanReadable } from '@aztec/foundation/serialize';

/**
* Represents an individual unencrypted log entry.
Expand Down Expand Up @@ -46,9 +46,7 @@ export class UnencryptedL2Log {
* @returns A human readable representation of the log.
*/
public toHumanReadable(): string {
const payload = this.data.every(byte => byte >= 32 && byte <= 126)
? this.data.toString('ascii')
: `0x` + this.data.toString('hex');
const payload = toHumanReadable(this.data);
return `UnencryptedL2Log(contractAddress: ${this.contractAddress.toString()}, selector: ${this.selector.toString()}, data: ${payload})`;
}

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuit-types/src/notes/note_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type NoteFilter = {
owner?: AztecAddress;
/** The status of the note. Defaults to 'ACTIVE'. */
status?: NoteStatus;
/** The siloed nullifier for the note. */
siloedNullifier?: Fr;
};

/**
Expand Down
15 changes: 15 additions & 0 deletions yarn-project/circuits.js/src/structs/revert_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ export class RevertCode {
return this.equals(RevertCode.OK);
}

public getDescription() {
switch (this.code) {
case RevertCodeEnum.OK:
return 'OK';
case RevertCodeEnum.APP_LOGIC_REVERTED:
return 'Application logic reverted';
case RevertCodeEnum.TEARDOWN_REVERTED:
return 'Teardown reverted';
case RevertCodeEnum.BOTH_REVERTED:
return 'Both reverted';
default:
return `Unknown RevertCode: ${this.code}`;
}
}

/**
* Having different serialization methods allows for
* decoupling the serialization for producing the content commitment hash
Expand Down
32 changes: 32 additions & 0 deletions yarn-project/cli/src/cmds/get_block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { createCompatibleClient } from '../client.js';
import { inspectBlock } from '../inspect.js';

export async function getBlock(
rpcUrl: string,
maybeBlockNumber: number | undefined,
follow: boolean,
debugLogger: DebugLogger,
log: LogFn,
) {
const client = await createCompatibleClient(rpcUrl, debugLogger);
const blockNumber = maybeBlockNumber ?? (await client.getBlockNumber());
await inspectBlock(client, blockNumber, log, { showTxs: true });

if (follow) {
let lastBlock = blockNumber;
setInterval(async () => {
const newBlock = await client.getBlockNumber();
if (newBlock > lastBlock) {
const { blocks, notes } = await client.getSyncStatus();
const areNotesSynced = blocks >= newBlock && Object.values(notes).every(block => block >= newBlock);
if (areNotesSynced) {
log('');
await inspectBlock(client, newBlock, log, { showTxs: true });
lastBlock = newBlock;
}
}
}, 1000);
}
}
10 changes: 10 additions & 0 deletions yarn-project/cli/src/cmds/get_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type TxHash } from '@aztec/aztec.js';
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { createCompatibleClient } from '../client.js';
import { inspectTx } from '../inspect.js';

export async function getTx(rpcUrl: string, txHash: TxHash, debugLogger: DebugLogger, log: LogFn) {
const client = await createCompatibleClient(rpcUrl, debugLogger);
await inspectTx(client, txHash, log, { includeBlockInfo: true });
}
15 changes: 0 additions & 15 deletions yarn-project/cli/src/cmds/get_tx_receipt.ts

This file was deleted.

17 changes: 14 additions & 3 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,24 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
});

program
.command('get-tx-receipt')
.command('get-tx')
.description('Gets the receipt for the specified transaction hash.')
.argument('<txHash>', 'A transaction hash to get the receipt for.', parseTxHash)
.addOption(pxeOption)
.action(async (txHash, options) => {
const { getTxReceipt } = await import('./cmds/get_tx_receipt.js');
await getTxReceipt(options.rpcUrl, txHash, debugLogger, log);
const { getTx } = await import('./cmds/get_tx.js');
await getTx(options.rpcUrl, txHash, debugLogger, log);
});

program
.command('get-block')
.description('Gets info for a given block or latest.')
.argument('[blockNumber]', 'Block height', parseOptionalInteger)
.option('-f, --follow', 'Keep polling for new blocks')
.addOption(pxeOption)
.action(async (blockNumber, options) => {
const { getBlock } = await import('./cmds/get_block.js');
await getBlock(options.rpcUrl, blockNumber, options.follow, debugLogger, log);
});

program
Expand Down
202 changes: 202 additions & 0 deletions yarn-project/cli/src/inspect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { type ContractArtifact } from '@aztec/aztec.js';
import { type ExtendedNote, NoteStatus, type PXE, type TxHash } from '@aztec/circuit-types';
import { type AztecAddress, type Fr } from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { type LogFn } from '@aztec/foundation/log';
import { toHumanReadable } from '@aztec/foundation/serialize';
import { getCanonicalClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';

export async function inspectBlock(pxe: PXE, blockNumber: number, log: LogFn, opts: { showTxs?: boolean } = {}) {
const block = await pxe.getBlock(blockNumber);
if (!block) {
log(`No block found for block number ${blockNumber}`);
return;
}

log(`Block ${blockNumber} (${block.hash().toString()})`);
log(` Total fees: ${block.header.totalFees.toBigInt()}`);
log(
` Fee per gas unit: DA=${block.header.globalVariables.gasFees.feePerDaGas.toBigInt()} L2=${block.header.globalVariables.gasFees.feePerL2Gas.toBigInt()}`,
);
log(` Coinbase: ${block.header.globalVariables.coinbase}`);
log(` Fee recipient: ${block.header.globalVariables.feeRecipient}`);
log(` Timestamp: ${new Date(block.header.globalVariables.timestamp.toNumber() * 500)}`);
if (opts.showTxs) {
log(``);
const artifactMap = await getKnownArtifacts(pxe);
for (const txHash of block.body.txEffects.map(tx => tx.txHash)) {
await inspectTx(pxe, txHash, log, { includeBlockInfo: false, artifactMap });
}
} else {
log(` Transactions: ${block.body.txEffects.length}`);
}
}

export async function inspectTx(
pxe: PXE,
txHash: TxHash,
log: LogFn,
opts: { includeBlockInfo?: boolean; artifactMap?: ArtifactMap } = {},
) {
const [receipt, effects, notes] = await Promise.all([
pxe.getTxReceipt(txHash),
pxe.getTxEffect(txHash),
pxe.getNotes({ txHash, status: NoteStatus.ACTIVE_OR_NULLIFIED }),
]);

if (!receipt || !effects) {
log(`No receipt or effects found for transaction hash ${txHash.toString()}`);
return;
}

const artifactMap = opts?.artifactMap ?? (await getKnownArtifacts(pxe));

// Base tx data
log(`Tx ${txHash.toString()}`);
if (opts.includeBlockInfo) {
log(` Block: ${receipt.blockNumber} (${receipt.blockHash?.toString('hex')})`);
}
log(` Status: ${receipt.status} (${effects.revertCode.getDescription()})`);
if (receipt.error) {
log(` Error: ${receipt.error}`);
}
if (receipt.transactionFee) {
log(` Fee: ${receipt.transactionFee.toString()}`);
}

// Unencrypted logs
const unencryptedLogs = effects.unencryptedLogs.unrollLogs();
if (unencryptedLogs.length > 0) {
log(' Logs:');
for (const unencryptedLog of unencryptedLogs) {
const data = toHumanReadable(unencryptedLog.data, 1000);
log(` ${toFriendlyAddress(unencryptedLog.contractAddress, artifactMap)}: ${data}`);
}
}

// Public data writes
const writes = effects.publicDataWrites;
if (writes.length > 0) {
log(' Public data writes:');
for (const write of writes) {
log(` Leaf ${write.leafIndex.toString()} = ${write.newValue.toString()}`);
}
}

// Created notes
const noteEncryptedLogsCount = effects.noteEncryptedLogs.unrollLogs().length;
if (noteEncryptedLogsCount > 0) {
log(' Created notes:');
const notVisibleNotes = noteEncryptedLogsCount - notes.length;
if (notVisibleNotes > 0) {
log(` ${notVisibleNotes} notes not visible in the PXE`);
}
for (const note of notes) {
inspectNote(note, artifactMap, log);
}
}

// Nullifiers
const nullifierCount = effects.nullifiers.length;
const { deployNullifiers, initNullifiers, classNullifiers } = await getKnownNullifiers(pxe, artifactMap);
if (nullifierCount > 0) {
log(' Nullifiers:');
for (const nullifier of effects.nullifiers) {
const [note] = await pxe.getNotes({ siloedNullifier: nullifier });
const deployed = deployNullifiers[nullifier.toString()];
const initialized = initNullifiers[nullifier.toString()];
const registered = classNullifiers[nullifier.toString()];
if (nullifier.toBuffer().equals(txHash.toBuffer())) {
log(` Transaction hash nullifier ${nullifier.toShortString()}`);
} else if (note) {
inspectNote(note, artifactMap, log, `Nullifier ${nullifier.toShortString()} for note`);
} else if (deployed) {
log(
` Contract ${toFriendlyAddress(deployed, artifactMap)} deployed via nullifier ${nullifier.toShortString()}`,
);
} else if (initialized) {
log(
` Contract ${toFriendlyAddress(
initialized,
artifactMap,
)} initialized via nullifier ${nullifier.toShortString()}`,
);
} else if (registered) {
log(` Class ${registered} registered via nullifier ${nullifier.toShortString()}`);
} else {
log(` Unknown nullifier ${nullifier.toString()}`);
}
}
}

// L2 to L1 messages
if (effects.l2ToL1Msgs.length > 0) {
log(` L2 to L1 messages:`);
for (const msg of effects.l2ToL1Msgs) {
log(` ${msg.toString()}`);
}
}
}

function inspectNote(note: ExtendedNote, artifactMap: ArtifactMap, log: LogFn, text = 'Note') {
const artifact = artifactMap[note.contractAddress.toString()];
const contract = artifact?.name ?? note.contractAddress.toString();
const type = artifact?.notes[note.noteTypeId.toString()]?.typ ?? note.noteTypeId.toShortString();
log(` ${text} type ${type} at ${contract}`);
log(` Owner: ${toFriendlyAddress(note.owner, artifactMap)}`);
for (const field of note.note.items) {
log(` ${field.toString()}`);
}
}

function toFriendlyAddress(address: AztecAddress, artifactMap: ArtifactMap) {
const artifact = artifactMap[address.toString()];
if (!artifact) {
return address.toString();
}

return `${artifact.name}<${address.toString()}>`;
}

async function getKnownNullifiers(pxe: PXE, artifactMap: ArtifactMap) {
const knownContracts = await pxe.getContracts();
const deployerAddress = getCanonicalInstanceDeployer().address;
const registererAddress = getCanonicalClassRegistererAddress();
const initNullifiers: Record<string, AztecAddress> = {};
const deployNullifiers: Record<string, AztecAddress> = {};
const classNullifiers: Record<string, string> = {};
for (const contract of knownContracts) {
initNullifiers[siloNullifier(contract, contract).toString()] = contract;
deployNullifiers[siloNullifier(deployerAddress, contract).toString()] = contract;
}
for (const artifact of Object.values(artifactMap)) {
classNullifiers[
siloNullifier(registererAddress, artifact.classId).toString()
] = `${artifact.name}Class<${artifact.classId}>`;
}
return { initNullifiers, deployNullifiers, classNullifiers };
}

type ArtifactMap = Record<string, ContractArtifactWithClassId>;
type ContractArtifactWithClassId = ContractArtifact & { classId: Fr };
async function getKnownArtifacts(pxe: PXE): Promise<ArtifactMap> {
const knownContractAddresses = await pxe.getContracts();
const knownContracts = await Promise.all(knownContractAddresses.map(contract => pxe.getContractInstance(contract)));
const classIds = [...new Set(knownContracts.map(contract => contract?.contractClassId))];
const knownArtifacts = await Promise.all(
classIds.map(classId =>
classId ? pxe.getContractArtifact(classId).then(a => (a ? { ...a, classId } : undefined)) : undefined,
),
);
const map: Record<string, ContractArtifactWithClassId> = {};
for (const instance of knownContracts) {
if (instance) {
const artifact = knownArtifacts.find(a => a?.classId.equals(instance.contractClassId));
if (artifact) {
map[instance.address.toString()] = artifact;
}
}
}
return map;
}
8 changes: 8 additions & 0 deletions yarn-project/foundation/src/serialize/free_funcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,11 @@ export function fromTruncField(field: Fr): Buffer {
export function fromFieldsTuple(fields: Tuple<Fr, 2>): Buffer {
return from2Fields(fields[0], fields[1]);
}

export function toHumanReadable(buf: Buffer, maxLen?: number): string {
const result = buf.every(byte => byte >= 32 && byte <= 126) ? buf.toString('ascii') : `0x${buf.toString('hex')}`;
if (maxLen && result.length > maxLen) {
return result.slice(0, maxLen) + '...';
}
return result;
}
4 changes: 4 additions & 0 deletions yarn-project/pxe/src/database/kv_pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ export class KVPxeDatabase implements PxeDatabase {
continue;
}

if (filter.siloedNullifier && !note.siloedNullifier.equals(filter.siloedNullifier)) {
continue;
}

result.push(note);
}
}
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ export class PXEService implements PXE {
return artifact && getContractClassFromArtifact(artifact);
}

public getContractArtifact(id: Fr): Promise<ContractArtifact | undefined> {
return this.db.getContractArtifact(id);
}

public async registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise<CompleteAddress> {
const accounts = await this.keyStore.getAccounts();
const accountCompleteAddress = await this.keyStore.addAccount(secretKey, partialAddress);
Expand Down

0 comments on commit e8e1cd7

Please sign in to comment.