Skip to content

Commit

Permalink
feat: More reliable getTxReceipt api. (#1793)
Browse files Browse the repository at this point in the history
Closes #1402 #1548 

Previous approach had too many issues - only sender and the recipients
(after they decrypt their notes) can call this api. The data in the
receipt was not consistent.
And the role of TxDao is confusing. Delete it now and remove some
complicated code around it.


# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [ ] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [ ] Every change is related to the PR description.
- [ ] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
LeilaWang authored Aug 25, 2023
1 parent 4f62742 commit ad16b22
Show file tree
Hide file tree
Showing 25 changed files with 375 additions and 403 deletions.
8 changes: 7 additions & 1 deletion yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
L2BlockL2Logs,
L2BlockSource,
L2LogsSource,
L2Tx,
LogType,
TxHash,
} from '@aztec/types';

import omit from 'lodash.omit';
Expand Down Expand Up @@ -238,7 +240,7 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
// remove logs to serve "lightweight" block information. Logs can be fetched separately if needed.
await this.store.addL2Blocks(
retrievedBlocks.retrievedData.map(block =>
L2Block.fromFields(omit(block, ['newEncryptedLogs', 'newUnencryptedLogs'])),
L2Block.fromFields(omit(block, ['newEncryptedLogs', 'newUnencryptedLogs']), block.getBlockHash()),
),
);

Expand Down Expand Up @@ -286,6 +288,10 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
return blocks.length === 0 ? undefined : blocks[0];
}

public getL2Tx(txHash: TxHash): Promise<L2Tx | undefined> {
return this.store.getL2Tx(txHash);
}

/**
* Lookup the L2 contract data for this contract.
* Contains the contract's public function bytecode.
Expand Down
25 changes: 25 additions & 0 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
L1ToL2Message,
L2Block,
L2BlockL2Logs,
L2Tx,
LogType,
TxHash,
} from '@aztec/types';

import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js';
Expand All @@ -32,6 +34,13 @@ export interface ArchiverDataStore {
*/
getL2Blocks(from: number, limit: number): Promise<L2Block[]>;

/**
* Gets an l2 tx.
* @param txHash - The txHash of the l2 tx.
* @returns The requested L2 tx.
*/
getL2Tx(txHash: TxHash): Promise<L2Tx | undefined>;

/**
* Append new logs to the store's list.
* @param data - The logs to be added to the store.
Expand Down Expand Up @@ -145,6 +154,11 @@ export class MemoryArchiverStore implements ArchiverDataStore {
*/
private l2Blocks: L2Block[] = [];

/**
* An array containing all the L2 Txs in the L2 blocks that have been fetched so far.
*/
private l2Txs: L2Tx[] = [];

/**
* An array containing all the encrypted logs that have been fetched so far.
* Note: Index in the "outer" array equals to (corresponding L2 block's number - INITIAL_L2_BLOCK_NUM).
Expand Down Expand Up @@ -187,6 +201,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
*/
public addL2Blocks(blocks: L2Block[]): Promise<boolean> {
this.l2Blocks.push(...blocks);
this.l2Txs.push(...blocks.flatMap(b => b.getTxs()));
return Promise.resolve(true);
}

Expand Down Expand Up @@ -280,6 +295,16 @@ export class MemoryArchiverStore implements ArchiverDataStore {
return Promise.resolve(this.l2Blocks.slice(startIndex, endIndex));
}

/**
* Gets an l2 tx.
* @param txHash - The txHash of the l2 tx.
* @returns The requested L2 tx.
*/
public getL2Tx(txHash: TxHash): Promise<L2Tx | undefined> {
const l2Tx = this.l2Txs.find(tx => tx.txHash.equals(txHash));
return Promise.resolve(l2Tx);
}

/**
* Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee
* @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).
Expand Down
21 changes: 20 additions & 1 deletion yarn-project/aztec-node/src/aztec-node/http-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
L1ToL2MessageAndIndex,
L2Block,
L2BlockL2Logs,
L2Tx,
LogType,
MerkleTreeId,
SiblingPath,
Expand Down Expand Up @@ -187,6 +188,24 @@ export class HttpNode implements AztecNode {
await fetch(url, init);
}

/**
* Gets an l2 tx.
* @param txHash - The txHash of the l2 tx.
* @returns The requested L2 tx.
*/
async getTx(txHash: TxHash): Promise<L2Tx | undefined> {
const url = new URL(`${this.baseUrl}/get-tx`);
url.searchParams.append('hash', txHash.toString());
const response = await fetch(url.toString());
if (response.status === 404) {
this.log.info(`Tx ${txHash.toString()} not found`);
return undefined;
}
const txBuffer = Buffer.from(await response.arrayBuffer());
const tx = L2Tx.fromBuffer(txBuffer);
return Promise.resolve(tx);
}

/**
* Method to retrieve pending txs.
* @returns - The pending txs.
Expand All @@ -208,7 +227,7 @@ export class HttpNode implements AztecNode {
this.log.info(`Tx ${txHash.toString()} not found`);
return undefined;
}
const txBuffer = Buffer.from(await (await fetch(url.toString())).arrayBuffer());
const txBuffer = Buffer.from(await response.arrayBuffer());
const tx = Tx.fromBuffer(txBuffer);
return Promise.resolve(tx);
}
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
L2BlockL2Logs,
L2BlockSource,
L2LogsSource,
L2Tx,
LogType,
MerkleTreeId,
SiblingPath,
Expand Down Expand Up @@ -210,6 +211,10 @@ export class AztecNodeService implements AztecNode {
await this.p2pClient!.sendTx(tx);
}

public getTx(txHash: TxHash): Promise<L2Tx | undefined> {
return this.blockSource.getL2Tx(txHash);
}

/**
* Method to stop the aztec node.
*/
Expand Down
70 changes: 18 additions & 52 deletions yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {

import { RpcServerConfig } from '../config/index.js';
import { ContractDataOracle } from '../contract_data_oracle/index.js';
import { Database, TxDao } from '../database/index.js';
import { Database } from '../database/index.js';
import { KernelOracle } from '../kernel_oracle/index.js';
import { KernelProver } from '../kernel_prover/kernel_prover.js';
import { getAcirSimulator } from '../simulator/index.js';
Expand Down Expand Up @@ -186,16 +186,8 @@ export class AztecRPCServer implements AztecRPC {
const newContract = deployedContractAddress ? await this.db.getContract(deployedContractAddress) : undefined;

const tx = await this.#simulateAndProve(txRequest, newContract);

await this.db.addTx(
TxDao.from({
txHash: await tx.getTxHash(),
origin: txRequest.origin,
contractAddress: deployedContractAddress,
}),
);

this.log.info(`Executed local simulation for ${await tx.getTxHash()}`);

return tx;
}

Expand All @@ -215,40 +207,28 @@ export class AztecRPCServer implements AztecRPC {
}

public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
const localTx = await this.#getTxByHash(txHash);
const partialReceipt = new TxReceipt(
txHash,
TxStatus.PENDING,
'',
localTx?.blockHash,
localTx?.blockNumber,
localTx?.origin,
localTx?.contractAddress,
);

if (localTx?.blockHash) {
partialReceipt.status = TxStatus.MINED;
return partialReceipt;
const settledTx = await this.node.getTx(txHash);
if (settledTx) {
const deployedContractAddress = settledTx.newContractData.find(
c => !c.contractAddress.equals(AztecAddress.ZERO),
)?.contractAddress;

return new TxReceipt(
txHash,
TxStatus.MINED,
'',
settledTx.blockHash,
settledTx.blockNumber,
deployedContractAddress,
);
}

const pendingTx = await this.node.getPendingTxByHash(txHash);
if (pendingTx) {
return partialReceipt;
return new TxReceipt(txHash, TxStatus.PENDING, '');
}

// if the transaction mined it will be removed from the pending pool and there is a race condition here as the synchroniser will not have the tx as mined yet, so it will appear dropped
// until the synchroniser picks this up

const isSynchronised = await this.synchroniser.isGlobalStateSynchronised();
if (!isSynchronised) {
// there is a pending L2 block, which means the transaction will not be in the tx pool but may be awaiting mine on L1
return partialReceipt;
}

// TODO we should refactor this once the node can store transactions. At that point we should query the node and not deal with block heights.
partialReceipt.status = TxStatus.DROPPED;
partialReceipt.error = 'Tx dropped by P2P node.';
return partialReceipt;
return new TxReceipt(txHash, TxStatus.DROPPED, 'Tx dropped by P2P node.');
}

async getBlockNumber(): Promise<number> {
Expand Down Expand Up @@ -299,20 +279,6 @@ export class AztecRPCServer implements AztecRPC {
};
}

/**
* Retrieve a transaction by its hash from the database.
*
* @param txHash - The hash of the transaction to be fetched.
* @returns A TxDao instance representing the retrieved transaction.
*/
async #getTxByHash(txHash: TxHash): Promise<TxDao> {
const tx = await this.db.getTx(txHash);
if (!tx) {
throw new Error(`Transaction ${txHash} not found in RPC database`);
}
return tx;
}

/**
* Retrieves the simulation parameters required to run an ACIR simulation.
* This includes the contract address, function ABI, portal contract address, and historic tree roots.
Expand Down
33 changes: 1 addition & 32 deletions yarn-project/aztec-rpc/src/database/database.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,15 @@
import { CompleteAddress, HistoricBlockData } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { ContractDatabase, MerkleTreeId, PublicKey, TxHash } from '@aztec/types';
import { ContractDatabase, MerkleTreeId, PublicKey } from '@aztec/types';

import { NoteSpendingInfoDao } from './note_spending_info_dao.js';
import { TxDao } from './tx_dao.js';

/**
* A database interface that provides methods for retrieving, adding, and removing transactional data related to Aztec
* addresses, storage slots, and nullifiers.
*/
export interface Database extends ContractDatabase {
/**
* Retrieve a transaction from the MemoryDB using its transaction hash.
* The function searches for the transaction with the given hash in the txTable and returns it as a Promise.
* Returns 'undefined' if the transaction is not found in the database.
*
* @param txHash - The TxHash of the transaction to be retrieved.
* @returns A Promise that resolves to the found TxDao instance, or undefined if not found.
*/
getTx(txHash: TxHash): Promise<TxDao | undefined>;

/**
* Adds a TxDao instance to the transaction table.
* If a transaction with the same hash already exists in the table, it replaces the existing one.
* Otherwise, it pushes the new transaction to the table.
*
* @param tx - The TxDao instance representing the transaction to be added.
* @returns A Promise that resolves when the transaction is successfully added/updated in the table.
*/
addTx(tx: TxDao): Promise<void>;

/**
* Add an array of transaction data objects.
* If a transaction with the same hash already exists in the database, it will be updated
* with the new transaction data. Otherwise, the new transaction will be added to the database.
*
* @param txs - An array of TxDao instances representing the transactions to be added to the database.
* @returns A Promise that resolves when all the transactions have been added or updated.
*/
addTxs(txs: TxDao[]): Promise<void>;

/**
* Get auxiliary transaction data based on contract address and storage slot.
* It searches for matching NoteSpendingInfoDao objects in the MemoryDB's noteSpendingInfoTable
Expand Down
1 change: 0 additions & 1 deletion yarn-project/aztec-rpc/src/database/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './database.js';
export * from './memory_db.js';
export * from './note_spending_info_dao.js';
export * from './tx_dao.js';
22 changes: 1 addition & 21 deletions yarn-project/aztec-rpc/src/database/memory_db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { CompleteAddress, HistoricBlockData } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { MerkleTreeId, PublicKey, TxHash } from '@aztec/types';
import { MerkleTreeId, PublicKey } from '@aztec/types';

import { MemoryContractDatabase } from '../contract_database/index.js';
import { Database } from './database.js';
import { NoteSpendingInfoDao } from './note_spending_info_dao.js';
import { TxDao } from './tx_dao.js';

/**
* The MemoryDB class provides an in-memory implementation of a database to manage transactions and auxiliary data.
Expand All @@ -16,7 +15,6 @@ import { TxDao } from './tx_dao.js';
* As an in-memory database, the stored data will not persist beyond the life of the application instance.
*/
export class MemoryDB extends MemoryContractDatabase implements Database {
private txTable: TxDao[] = [];
private noteSpendingInfoTable: NoteSpendingInfoDao[] = [];
private treeRoots: Record<MerkleTreeId, Fr> | undefined;
private globalVariablesHash: Fr | undefined;
Expand All @@ -26,24 +24,6 @@ export class MemoryDB extends MemoryContractDatabase implements Database {
super(createDebugLogger(logSuffix ? 'aztec:memory_db_' + logSuffix : 'aztec:memory_db'));
}

public getTx(txHash: TxHash) {
return Promise.resolve(this.txTable.find(tx => tx.txHash.equals(txHash)));
}

public addTx(tx: TxDao) {
const index = this.txTable.findIndex(t => t.txHash.equals(tx.txHash));
if (index === -1) {
this.txTable.push(tx);
} else {
this.txTable[index] = tx;
}
return Promise.resolve();
}

public async addTxs(txs: TxDao[]) {
await Promise.all(txs.map(tx => this.addTx(tx)));
}

public addNoteSpendingInfo(noteSpendingInfoDao: NoteSpendingInfoDao) {
this.noteSpendingInfoTable.push(noteSpendingInfoDao);
return Promise.resolve();
Expand Down
Loading

0 comments on commit ad16b22

Please sign in to comment.