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

feat: barebones addressbook for tagging #9572

Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ export abstract class BaseWallet implements Wallet {
getRegisteredAccount(address: AztecAddress): Promise<CompleteAddress | undefined> {
return this.pxe.getRegisteredAccount(address);
}
registerContact(address: AztecAddress): Promise<AztecAddress> {
return this.pxe.registerContact(address);
}
getContacts(): Promise<AztecAddress[]> {
return this.pxe.getContacts();
}
async removeContact(address: AztecAddress): Promise<void> {
await this.pxe.removeContact(address);
}
registerContract(contract: {
/** Instance */ instance: ContractInstanceWithAddress;
/** Associated artifact */ artifact?: ContractArtifact;
Expand Down
26 changes: 24 additions & 2 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ export interface PXE {
*/
getRegisteredAccount(address: AztecAddress): Promise<CompleteAddress | undefined>;

/**
* Registers a user contact in PXE.
*
* Once a new contact is registered, the PXE Service will be able to receive notes tagged from this contact.
* Will do nothing if the account is already registered.
*
* @param address - Address of the user to add to the address book
* @returns The address address of the account.
*/
registerContact(address: AztecAddress): Promise<AztecAddress>;

/**
* Retrieves the addresses stored as contacts on this PXE Service.
* @returns An array of the contacts on this PXE Service.
*/
getContacts(): Promise<AztecAddress[]>;

/**
* Removes a contact in the address book.
*/
removeContact(address: AztecAddress): Promise<void>;

/**
* Registers a contract class in the PXE without registering any associated contract instance with it.
*
Expand Down Expand Up @@ -328,15 +350,15 @@ export interface PXE {
getSyncStats(): Promise<{ [key: string]: NoteProcessorStats }>;

/**
* Returns a Contact Instance given its address, which includes the contract class identifier,
* Returns a Contract Instance given its address, which includes the contract class identifier,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Quick typo fix

* initialization hash, deployment salt, and public keys hash.
* TODO(@spalladino): Should we return the public keys in plain as well here?
* @param address - Deployment address of the contract.
*/
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;

/**
* Returns a Contact Class given its identifier.
* Returns a Contract Class given its identifier.
* TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also,
* should the pxe query the node for contract public info, and merge it with its own definitions?
* @param id - Identifier of the class.
Expand Down
53 changes: 40 additions & 13 deletions yarn-project/pxe/src/database/kv_pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import { type PxeDatabase } from './pxe_database.js';
*/
export class KVPxeDatabase implements PxeDatabase {
#synchronizedBlock: AztecSingleton<Buffer>;
#addresses: AztecArray<Buffer>;
#addressIndex: AztecMap<string, number>;
#completeAddresses: AztecArray<Buffer>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have renamed it to be more accurate to what it was

#completeAddressIndex: AztecMap<string, number>;
#addressBook: AztecSet<string>;
#authWitnesses: AztecMap<string, Buffer[]>;
#capsules: AztecArray<Buffer[]>;
#notes: AztecMap<string, Buffer>;
Expand Down Expand Up @@ -72,8 +73,10 @@ export class KVPxeDatabase implements PxeDatabase {
constructor(private db: AztecKVStore) {
this.#db = db;

this.#addresses = db.openArray('addresses');
this.#addressIndex = db.openMap('address_index');
this.#completeAddresses = db.openArray('complete_addresses');
this.#completeAddressIndex = db.openMap('complete_address_index');

this.#addressBook = db.openSet('address_book');

this.#authWitnesses = db.openMap('auth_witnesses');
this.#capsules = db.openArray('capsules');
Expand Down Expand Up @@ -509,15 +512,15 @@ export class KVPxeDatabase implements PxeDatabase {
return this.#db.transaction(() => {
const addressString = completeAddress.address.toString();
const buffer = completeAddress.toBuffer();
const existing = this.#addressIndex.get(addressString);
const existing = this.#completeAddressIndex.get(addressString);
if (typeof existing === 'undefined') {
const index = this.#addresses.length;
void this.#addresses.push(buffer);
void this.#addressIndex.set(addressString, index);
const index = this.#completeAddresses.length;
void this.#completeAddresses.push(buffer);
void this.#completeAddressIndex.set(addressString, index);

return true;
} else {
const existingBuffer = this.#addresses.at(existing);
const existingBuffer = this.#completeAddresses.at(existing);

if (existingBuffer?.equals(buffer)) {
return false;
Expand All @@ -531,12 +534,12 @@ export class KVPxeDatabase implements PxeDatabase {
}

#getCompleteAddress(address: AztecAddress): CompleteAddress | undefined {
const index = this.#addressIndex.get(address.toString());
const index = this.#completeAddressIndex.get(address.toString());
if (typeof index === 'undefined') {
return undefined;
}

const value = this.#addresses.at(index);
const value = this.#completeAddresses.at(index);
return value ? CompleteAddress.fromBuffer(value) : undefined;
}

Expand All @@ -545,7 +548,31 @@ export class KVPxeDatabase implements PxeDatabase {
}

getCompleteAddresses(): Promise<CompleteAddress[]> {
return Promise.resolve(Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v)));
return Promise.resolve(Array.from(this.#completeAddresses).map(v => CompleteAddress.fromBuffer(v)));
}

async addContactAddress(address: AztecAddress): Promise<boolean> {
if (this.#addressBook.has(address.toString())) {
return false;
}

await this.#addressBook.add(address.toString());

return true;
}

getContactAddresses(): AztecAddress[] {
return [...this.#addressBook.entries()].map(AztecAddress.fromString);
}

async removeContactAddress(address: AztecAddress): Promise<boolean> {
if (!this.#addressBook.has(address.toString())) {
return false;
}

await this.#addressBook.delete(address.toString());

return true;
}

getSynchedBlockNumberForAccount(account: AztecAddress): number | undefined {
Expand All @@ -570,7 +597,7 @@ export class KVPxeDatabase implements PxeDatabase {
(sum, value) => sum + value.length * Fr.SIZE_IN_BYTES,
0,
);
const addressesSize = this.#addresses.length * CompleteAddress.SIZE_IN_BYTES;
const addressesSize = this.#completeAddresses.length * CompleteAddress.SIZE_IN_BYTES;
const treeRootsSize = Object.keys(MerkleTreeId).length * Fr.SIZE_IN_BYTES;

return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize;
Expand Down
20 changes: 20 additions & 0 deletions yarn-project/pxe/src/database/pxe_database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,26 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD
*/
setHeader(header: Header): Promise<void>;

/**
* Adds contact address to the database.
* @param address - The address to add to the address book.
* @returns A promise resolving to true if the address was added, false if it already exists.
*/
addContactAddress(address: AztecAddress): Promise<boolean>;

/**
* Retrieves the list of contact addresses in the address book.
* @returns An array of Aztec addresses.
*/
getContactAddresses(): AztecAddress[];

/**
* Removes a contact address from the database.
* @param address - The address to remove from the address book.
* @returns A promise resolving to true if the address was removed, false if it does not exist.
*/
removeContactAddress(address: AztecAddress): Promise<boolean>;

/**
* Adds complete address to the database.
* @param address - The complete address to add.
Expand Down
36 changes: 36 additions & 0 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,42 @@ export class PXEService implements PXE {
return accountCompleteAddress;
}

public async registerContact(address: AztecAddress): Promise<AztecAddress> {
const accounts = await this.keyStore.getAccounts();
if (accounts.includes(address)) {
this.log.info(`Account:\n "${address.toString()}"\n already registered.`);
return address;
}

const wasAdded = await this.db.addContactAddress(address);

if (wasAdded) {
this.log.info(`Added contact:\n ${address.toString()}`);
} else {
this.log.info(`Contact:\n "${address.toString()}"\n already registered.`);
}

return address;
}

public getContacts(): Promise<AztecAddress[]> {
const contacts = this.db.getContactAddresses();

return Promise.resolve(contacts);
}

public async removeContact(address: AztecAddress): Promise<void> {
const wasRemoved = await this.db.removeContactAddress(address);

if (wasRemoved) {
this.log.info(`Removed contact:\n ${address.toString()}`);
} else {
this.log.info(`Contact:\n "${address.toString()}"\n not in address book.`);
}

return Promise.resolve();
}

public async getRegisteredAccounts(): Promise<CompleteAddress[]> {
// Get complete addresses of both the recipients and the accounts
const completeAddresses = await this.db.getCompleteAddresses();
Expand Down
10 changes: 10 additions & 0 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ export class SimulatorOracle implements DBOracle {
return this.contractDataOracle.getDebugFunctionName(contractAddress, selector);
}

/**
* Returns the full contents of your address book.
* This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and
* finally the index specified tag. We will then query the node with this tag for each address in the address book.
* @returns The full list of the users contact addresses.
*/
public getContacts(): AztecAddress[] {
return this.db.getContactAddresses();
}

/**
* Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known.
* Includes the last known index used for tagging with this secret.
Expand Down
Loading