Skip to content

Commit

Permalink
feat: Poor man's CLI block explorer (#6946)
Browse files Browse the repository at this point in the history
Adds a command line block explorer in the CLI via a new `get-block`
command, with an optional `--follow` for streaming new blocks. Required
adding a new call to the PXE to return a contract artifact (as opposed
to a class) and a new note filter to retrieve notes by nullifier.

```
Block 1 (0x2b89265d2de0519612c4a9efa8972cd2c4afccd12264e9ce299730ce0a0766bc)
 Total fees: 210250112
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Jan 01 1970 01:00:00 GMT+0100 (Greenwich Mean Time)

Tx 28a4883a47c7a12cc7479dab2936f05ed6b3ddce69f46fcd5203366206a29221
 Status: success (OK)
 Fee: 210250112
 Logs:
  ContractClassRegisterer<0x077649ac726c10e628e99c5f2a35fdee1fbdc171cde6fba64ca8985ca35f9045>: 0x000000006999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f81428d8e644ec87bb40301ac06cb69d6884e063109dc422ae2a13dcd22402ebf5000000000000000000000000000000000000000000000000000000000000000110b77dfc4f54289b862f5260efb46d933935ba56188fd516d0dd472b6ca0391515d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f00000000000000000000000000000000000000000000000000000000000064010000000002d11c1929000016da1f8b08000000000002ffed9ddb6f2cc95dc77b003c37db63b78f3dbe8f2fe3cbd8e3dbd833e3f16d7c7c7cbc67b311122b101200e2011e36c99220028b96041424081152de7840fc01f04c1490108a16d02a820048ab65414428e415912790f2020fac907880fefd7e55bf5f5775cfe91f0d7100b2512ccd39d5d5dfcfaf2edd5d555d555db51b9402fc1bafc2bfeb81f98b8e00c68330a844ff8dc1ef36b0aec801bf421814ac570b4e0245aec83f28b60c100094d0558cfe29a30bc2aba0ab0ce1a0ab02ff6c47ff84038ecaae8d1984134c00b66ccc26023e3109ffb46c8c26461163690444686c14514c234a982c70559200f12b0951626242a9ab2a75634addb8525756ea0a0a9dcdba42741927175b2600dfe93ec0ccc6fb00d9a297ed05ca5a93d9c5...
  ContractInstanceDeployer<0x02f1337e8c79dd0247ccbde85241ad65ee991ae283a63479e095e51f0abbc7e3>: 0x0000000085864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a6311585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011428d8e644ec87bb40301ac06cb69d6884e063109dc422ae2a13dcd22402ebf5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 Nullifiers:
  Transaction hash nullifier 0x28a4883a...9221
  Class KeyRegistryClass<0x1428d8e644ec87bb40301ac06cb69d6884e063109dc422ae2a13dcd22402ebf5> registered via nullifier 0x1744fe44...097d
  Contract KeyRegistry<0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e> deployed via nullifier 0x2c94554f...a9c4

Block 2 (0x23229ae8b82203c7d12d69c5af8a9452c495d098d96484ce24432d4890c3a066)
 Total fees: 200011840
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Mar 20 1997 06:19:57 GMT+0000 (Greenwich Mean Time)

Tx 2fe9c0b77ce7af6f2cc309b66a15fc4eadea21eff14abc1dd06b94235cfe4055
 Status: success (OK)
 Fee: 200011840
 Created notes:
  Note type 0x00000000...f345 at SchnorrAccount
    Owner: SchnorrAccount<0x0f3a5bc7d4b298a87c1b8e2cfd065dd2294e6329324092420e9bcf3899ad862b>
    0x3024c8a955160c6765e4d118c88cd4f67099bdebdd166d292aaf16341b861959
    0x18e3c5c52a7704daa972d9b3cef34be01037aa5eccbc99f9395e17fb63dc2f4a
    0x2c59248c91ae2f96d0873c9ac05fef17065229e31eb80db4c8925cd793a2258d
 Nullifiers:
  Transaction hash nullifier 0x2fe9c0b7...4055
  Unknown nullifier 0x07d5054454752a94064c6d914a7df14ee311c6a6561b0feb1031cd4d5b3bcb2b
  Contract SchnorrAccount<0x0f3a5bc7d4b298a87c1b8e2cfd065dd2294e6329324092420e9bcf3899ad862b> initialized via nullifier 0x0b22eff4...59f3

Block 3 (0x276d4ce31d7acbf40b00efe3fe967383c75ba4e8c8024372946ab463dca8b4ba)
 Total fees: 200011840
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Mar 20 1997 06:34:58 GMT+0000 (Greenwich Mean Time)

Tx 239fdac7d4dd6fd2871c2c78506f6915272acc0d6c518367b2695c3576d18047
 Status: success (OK)
 Fee: 200011840
 Created notes:
  Note type 0x00000000...f345 at SchnorrAccount
    Owner: SchnorrAccount<0x208c952a5a8caa6f0e3c9c9a2b5588e84df882a632be92ed14ed4ba3a6cb4889>
    0x20dce8a520f3d85c3c1814e11f49c1ddd5ed8795779b56ce24d04795672ea19c
    0x160ef84102b88050fdc3b5d26b4fd5552401dd1e7766428e460e8419479774c1
    0x1f22cd5b4123dccdf6bac0a135248dbbb1481fb0ecd1d86e288db71314f82bdb
 Nullifiers:
  Transaction hash nullifier 0x239fdac7...8047
  Unknown nullifier 0x20c4572a23676aeb43ff381d6f09badbbaf958426cb999946fa5a9490d0bcbf2
  Contract SchnorrAccount<0x208c952a5a8caa6f0e3c9c9a2b5588e84df882a632be92ed14ed4ba3a6cb4889> initialized via nullifier 0x17441328...4ace

Block 4 (0x1e86e3f40cc9bd904229f12aaeb0f5ccb1ed2b4cd5ef82e665075f42ce2ddea2)
 Total fees: 200011328
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Mar 20 1997 06:36:22 GMT+0000 (Greenwich Mean Time)

Tx 1935e158e53f92ee28fbb6a44d544aa6409151745888d07eb435c9adf0b6fa74
 Status: success (OK)
 Fee: 200011328
 Created notes:
  Note type 0x00000000...d345 at Counter
    Owner: SchnorrAccount<0x208c952a5a8caa6f0e3c9c9a2b5588e84df882a632be92ed14ed4ba3a6cb4889>
    0x0000000000000000000000000000000000000000000000000000000000000014
    0x1f22cd5b4123dccdf6bac0a135248dbbb1481fb0ecd1d86e288db71314f82bdb
    0x24bc453d236efaa0af7978fc40d1d32d5ad04514393686195a2592a897842352
 Nullifiers:
  Transaction hash nullifier 0x1935e158...fa74
  Contract Counter<0x2a0e4a235aa1c517e04a41bc558688e5d0fbe659bdd0e2187625ba135b0ed147> initialized via nullifier 0x2d14862d...e6fb

Block 5 (0x28c6453635fe3e6192434b82ea5b704a553b9bb983ffa8eb0634226cbec388a5)
 Total fees: 210299762
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Mar 20 1997 06:36:44 GMT+0000 (Greenwich Mean Time)

Tx 026ee6d9880d1fb58835ce759cc86b911b28f3d402a589d4b3a5aac15e7c22b5
 Status: success (OK)
 Fee: 210299762
 Logs:
  ContractClassRegisterer<0x077649ac726c10e628e99c5f2a35fdee1fbdc171cde6fba64ca8985ca35f9045>: 0x000000006999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f801222ccbfa5e4da8aa941f05b44fdb6f9e243570a4133c17131d429a128bc35500000000000000000000000000000000000000000000000000000000000000010af070de86150b457bacbaa2d68104f1f76b746e2d57a67d9064a2939da2fd050488114b880887348c50fbaeebde008bfca23238238be04c262ea0fd4d050bf8000000000000000000000000000000000000000000000000000000000000d0b900000000121076393200000b711f8b08000000000002ffe59b5b5714cb15c7bb00a7bb079111af208a8cb4d0721310111418042f2883c3c5837251514611e12c004fcc3ac7246be5cbe4fbe4210f79c8433e409ef29cd7a4f7ae5db56777cf380095ce3aae9c7566ad61aa77ff7fb5ebb2ebd2354cbfe33bf83a11c0df1e875e00f1d509a7ddc9c71f3978971c9d8a13f076db1d579b22b809944ac576c78b0800707c4c799027a6c05ffe5afca73065dc463a57a75f970772775a1c73d9627400bea52e6fa9f32c7581a5ceb5d0e96676e386f0ff8297809ec01434654ba4d9001332ab56172f41d38a844b57aea52eb0d47996babca5ceb7d4e51a05432b370062ab69ec164b9d6fa9cb5bea3c4b5d60a9732d740d82e624a64e82cc04cd490099559b6a6cd0b421e1d2956ba90b2c759ea5...
  ContractInstanceDeployer<0x02f1337e8c79dd0247ccbde85241ad65ee991ae283a63479e095e51f0abbc7e3>: 0x0000000085864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631060cfd1d7e084707f9dbff1e91e5ff25f42a96eda32c3efa7857a9c245ad349800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000ba4a4a01222ccbfa5e4da8aa941f05b44fdb6f9e243570a4133c17131d429a128bc3550f1c0b89cacad7f894b03f2df0fe34f90017b30e7a20788abf1c6eb1fc28072f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 Public data writes:
  Leaf 0x01274dce5f5b02dff31e464a12626bcce1311ec716b070179739f8a1ca7c9261 = 0x000000000000000000000000000000000000000000000000000000000000dead
  Leaf 0x071ca21d4cb06c55f09e4a686a9d108b942856a2582c73c0d43e09f8d038a756 = 0x0000000000000000000000000000000000000000000000000000000000000006
  Leaf 0x08a8ceb0fdeb27b3f98234d8603541f95306dae8ab15d4ace52357fe4d11f90a = 0x0000000000000000000000000000000000000000000000000000000000000001
  Leaf 0x0d234845ac847cb60b73201db97c6e8675d52d72adc69581aa5914fc11a0ed7c = 0x000000000000000000000000000000000000000000000000000000000000dead
  Leaf 0x0fc858ef91c153285fbc819e98b1c31b55ed5cd074eaf55605b071f3ccd4e145 = 0x0042414e414e4100000000000000000000000000000000000000000000000000
  Leaf 0x16008cf0f0385284517082f9728e372bcf1df56d725d16b73bae4a5a0c0506ce = 0x0042414e414e4100000000000000000000000000000000000000000000000000
  Leaf 0x1cc9f461a515b0b2754d9e249bb97fe69c1a62d423df9293336c978685612dbc = 0x000000000000000000000000000000000000000000000000000000000000dead
  Leaf 0x1d11db8907a98c978972653ddc4171a238495b048d3af7497211e53f18bc60c6 = 0x208c952a5a8caa6f0e3c9c9a2b5588e84df882a632be92ed14ed4ba3a6cb4889
 Nullifiers:
  Transaction hash nullifier 0x026ee6d9...22b5
  Class TokenClass<0x01222ccbfa5e4da8aa941f05b44fdb6f9e243570a4133c17131d429a128bc355> registered via nullifier 0x00419c0d...1e71
  Contract Token<0x060cfd1d7e084707f9dbff1e91e5ff25f42a96eda32c3efa7857a9c245ad3498> deployed via nullifier 0x2172e4eb...48b8
  Contract Token<0x060cfd1d7e084707f9dbff1e91e5ff25f42a96eda32c3efa7857a9c245ad3498> initialized via nullifier 0x3056b711...adce
Block 5 (0x28c6453635fe3e6192434b82ea5b704a553b9bb983ffa8eb0634226cbec388a5)
 Total fees: 210299762
 Fee per gas unit: DA=1 L2=1
 Coinbase: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
 Fee recipient: 0x0000000000000000000000000000000000000000000000000000000000000000
 Timestamp: Thu Mar 20 1997 06:36:44 GMT+0000 (Greenwich Mean Time)
```
  • Loading branch information
spalladino committed Jun 6, 2024
1 parent f970732 commit 2b79df6
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 2b79df6

Please sign in to comment.