fix(sdk): update account service constructor args (#74)

This commit is contained in:
nigiri
2025-04-23 16:06:53 -03:00
committed by GitHub
parent a7cf232fae
commit ec4deae3b5
2 changed files with 87 additions and 61 deletions

View File

@@ -9,15 +9,27 @@ import {
PoolInfo,
PrivacyPoolAccount,
} from "../types/account.js";
import { DepositEvent, RagequitEvent, WithdrawalEvent } from "../types/events.js";
import {
DepositEvent,
RagequitEvent,
WithdrawalEvent,
} from "../types/events.js";
import { Logger } from "../utils/logger.js";
import { AccountError } from "../errors/account.error.js";
import { ErrorCode } from "../errors/base.error.js";
type AccountServiceConfig =
| {
mnemonic: string;
}
| {
account: PrivacyPoolAccount;
};
/**
* Service responsible for managing privacy pool accounts and their associated commitments.
* Handles account initialization, deposit/withdrawal tracking, and history synchronization.
*
*
* @remarks
* This service maintains the state of all pool accounts and their commitments across different
* chains and scopes. It uses deterministic key generation to recover account state from a mnemonic.
@@ -28,34 +40,38 @@ export class AccountService {
/**
* Creates a new AccountService instance.
*
*
* @param dataService - Service for fetching on-chain events
* @param account - Optional existing account to initialize with
* @param mnemonic - Optional mnemonic for deterministic key generation
*
* @param config - Configuration for the account service (either mnemonic or existing account)
* @param config.mnemonic - Optional mnemonic for deterministic key generation
* @param config.account - Optional existing account to initialize with
*
* @throws {AccountError} If account initialization fails
*/
constructor(
private readonly dataService: DataService,
mnemonic: string,
account?: PrivacyPoolAccount,
config: AccountServiceConfig
) {
this.logger = new Logger({ prefix: "Account" });
this.account = account || this._initializeAccount(mnemonic);
if("mnemonic" in config) {
this.account = this._initializeAccount(config.mnemonic);
} else {
this.account = config.account;
}
}
/**
* Initializes a new account from a mnemonic phrase.
*
*
* @param mnemonic - The mnemonic phrase to derive keys from
* @returns A new PrivacyPoolAccount with derived master keys
*
*
* @remarks
* This method derives two master keys from the mnemonic:
* 1. A master nullifier key from account index 0
* 2. A master secret key from account index 1
* These keys are used to deterministically generate nullifiers and secrets for deposits and withdrawals.
*
*
* @throws {AccountError} If account initialization fails
* @private
*/
@@ -64,11 +80,11 @@ export class AccountService {
this.logger.debug("Initializing account with mnemonic");
const masterNullifierSeed = bytesToNumber(
mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey!,
mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey!
);
const masterSecretSeed = bytesToNumber(
mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey!,
mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey!
);
const masterNullifier = poseidon([BigInt(masterNullifierSeed)]) as Secret;
@@ -78,7 +94,7 @@ export class AccountService {
masterKeys: [masterNullifier, masterSecret],
poolAccounts: new Map(),
creationTimestamp: 0n,
lastUpdateTimestamp: 0n
lastUpdateTimestamp: 0n,
};
} catch (error) {
throw AccountError.accountInitializationFailed(
@@ -89,7 +105,7 @@ export class AccountService {
/**
* Generates a deterministic nullifier for a deposit.
*
*
* @param scope - The scope of the pool
* @param index - The index of the deposit
* @returns A deterministic nullifier for the deposit
@@ -102,7 +118,7 @@ export class AccountService {
/**
* Generates a deterministic secret for a deposit.
*
*
* @param scope - The scope of the pool
* @param index - The index of the deposit
* @returns A deterministic secret for the deposit
@@ -115,7 +131,7 @@ export class AccountService {
/**
* Generates a deterministic nullifier for a withdrawal.
*
*
* @param label - The label of the commitment
* @param index - The index of the withdrawal
* @returns A deterministic nullifier for the withdrawal
@@ -128,7 +144,7 @@ export class AccountService {
/**
* Generates a deterministic secret for a withdrawal.
*
*
* @param label - The label of the commitment
* @param index - The index of the withdrawal
* @returns A deterministic secret for the withdrawal
@@ -141,7 +157,7 @@ export class AccountService {
/**
* Hashes a commitment using the Poseidon hash function.
*
*
* @param value - The value of the commitment
* @param label - The label of the commitment
* @param precommitment - The precommitment hash
@@ -151,14 +167,14 @@ export class AccountService {
private _hashCommitment(
value: bigint,
label: Hash,
precommitment: Hash,
precommitment: Hash
): Hash {
return poseidon([value, label, precommitment]) as Hash;
}
/**
* Hashes a precommitment using the Poseidon hash function.
*
*
* @param nullifier - The nullifier for the commitment
* @param secret - The secret for the commitment
* @returns The precommitment hash
@@ -170,9 +186,9 @@ export class AccountService {
/**
* Gets all spendable commitments across all pools.
*
*
* @returns A map of scope to array of spendable commitments
*
*
* @remarks
* A commitment is considered spendable if:
* 1. It has a non-zero value
@@ -209,18 +225,18 @@ export class AccountService {
/**
* Creates nullifier and secret for a new deposit
*
*
* @param scope - The scope of the pool to deposit into
* @param index - Optional index for deterministic generation
* @returns The nullifier, secret, and precommitment for the deposit
*
*
* @remarks
* If no index is provided, it uses the current number of accounts for the scope.
* The precommitment is a hash of the nullifier and secret, used in the deposit process.
*/
public createDepositSecrets(
scope: Hash,
index?: bigint,
index?: bigint
): {
nullifier: Secret;
secret: Secret;
@@ -242,15 +258,15 @@ export class AccountService {
/**
* Creates nullifier and secret for spending a commitment
*
*
* @param commitment - The commitment to spend
* @returns The nullifier and secret for the new commitment
*
*
* @remarks
* The index used for generating the withdrawal nullifier and secret is based on
* the number of children the account already has, ensuring each withdrawal has
* a unique nullifier.
*
*
* @throws {AccountError} If no account is found for the commitment
*/
public createWithdrawalSecrets(commitment: AccountCommitment): {
@@ -279,7 +295,7 @@ export class AccountService {
/**
* Adds a new pool account after depositing
*
*
* @param scope - The scope of the pool
* @param value - The deposit value
* @param nullifier - The nullifier used for the deposit
@@ -288,7 +304,7 @@ export class AccountService {
* @param blockNumber - The block number of the deposit
* @param txHash - The transaction hash of the deposit
* @returns The new pool account
*
*
* @remarks
* This method creates a new account with the deposit commitment and adds it to the
* pool accounts map under the specified scope. The commitment hash is calculated
@@ -301,7 +317,7 @@ export class AccountService {
secret: Secret,
label: Hash,
blockNumber: bigint,
txHash: Hex,
txHash: Hex
): PoolAccount {
const precommitment = this._hashPrecommitment(nullifier, secret);
const commitment = this._hashCommitment(value, label, precommitment);
@@ -327,7 +343,7 @@ export class AccountService {
this.account.poolAccounts.get(scope)!.push(newAccount);
this.logger.info(
`Added new pool account with value ${value} and label ${label}`,
`Added new pool account with value ${value} and label ${label}`
);
return newAccount;
@@ -335,7 +351,7 @@ export class AccountService {
/**
* Adds a new commitment to the account after spending
*
*
* @param parentCommitment - The commitment that was spent
* @param value - The remaining value after spending
* @param nullifier - The nullifier used for spending
@@ -343,12 +359,12 @@ export class AccountService {
* @param blockNumber - The block number of the withdrawal
* @param txHash - The transaction hash of the withdrawal
* @returns The new commitment
*
*
* @remarks
* This method finds the account containing the parent commitment, creates a new
* commitment with the provided parameters, and adds it to the account's children.
* The new commitment inherits the label from the parent commitment.
*
*
* @throws {AccountError} If no account is found for the commitment
*/
public addWithdrawalCommitment(
@@ -357,7 +373,7 @@ export class AccountService {
nullifier: Secret,
secret: Secret,
blockNumber: bigint,
txHash: Hex,
txHash: Hex
): AccountCommitment {
let foundAccount: PoolAccount | undefined;
let foundScope: bigint | undefined;
@@ -366,7 +382,7 @@ export class AccountService {
foundAccount = accounts.find((account) => {
if (account.deposit.hash === parentCommitment.hash) return true;
return account.children.some(
(child) => child.hash === parentCommitment.hash,
(child) => child.hash === parentCommitment.hash
);
});
@@ -394,7 +410,7 @@ export class AccountService {
foundAccount.children.push(newCommitment);
this.logger.info(
`Added new commitment with value ${value} to account with label ${parentCommitment.label}`,
`Added new commitment with value ${value} to account with label ${parentCommitment.label}`
);
return newCommitment;
@@ -402,16 +418,16 @@ export class AccountService {
/**
* Adds a ragequit event to an existing pool account
*
*
* @param label - The label of the account to add the ragequit to
* @param ragequit - The ragequit event to add
* @returns The updated pool account
*
*
* @remarks
* When an account has a ragequit event, it can no longer be spent.
* This method finds the account with the matching label and attaches
* the ragequit event to it.
*
*
* @throws {AccountError} If no account is found with the given label
*/
public addRagequitToAccount(
@@ -449,18 +465,18 @@ export class AccountService {
/**
* Retrieves the history of deposits and withdrawals for the given pools.
*
*
* @param pools - Array of pool configurations to sync history for
*
*
* @remarks
* This method performs the following steps:
* 1. Initializes pool accounts for each pool if they don't exist
* 2. For each pool, fetches deposit events and reconstructs accounts
* 3. Processes withdrawals and ragequits to update account state
*
*
* The account reconstruction is deterministic based on the master keys,
* allowing the full state to be recovered from on-chain events.
*
*
* @throws {DataError} If event fetching fails
* @throws {AccountError} If account state reconstruction fails
*/
@@ -480,14 +496,14 @@ export class AccountService {
pools.map(async (pool) => {
// Log which pool is being processed
this.logger.info(
`Processing pool ${pool.address} on chain ${pool.chainId} from block ${pool.deploymentBlock}`,
`Processing pool ${pool.address} on chain ${pool.chainId} from block ${pool.deploymentBlock}`
);
// Fetch all deposit events for this pool
const deposits = await this.dataService.getDeposits(pool);
this.logger.info(
`Found ${deposits.length} deposits for pool ${pool.address}`,
`Found ${deposits.length} deposits for pool ${pool.address}`
);
// Create a map of deposits by precommitment for efficient lookup
@@ -533,7 +549,7 @@ export class AccountService {
secret,
deposit.label,
deposit.blockNumber,
deposit.transactionHash,
deposit.transactionHash
);
// Track the found deposit
@@ -546,13 +562,13 @@ export class AccountService {
// If no accounts were found for this scope, log and skip further processing
if (this.account.poolAccounts.get(pool.scope)!.length === 0) {
this.logger.info(
`No Pool Accounts were found for scope ${pool.scope}`,
`No Pool Accounts were found for scope ${pool.scope}`
);
return;
}
this.logger.info(
`Found ${foundDeposits.length} deposits for pool ${pool.address}`,
`Found ${foundDeposits.length} deposits for pool ${pool.address}`
);
})
);
@@ -564,9 +580,9 @@ export class AccountService {
/**
* Processes withdrawal events for all pools and updates account state.
*
*
* @param pools - Array of pool configurations to process withdrawals for
*
*
* @remarks
* This method performs the following steps for each pool:
* 1. Identifies the earliest deposit block for each scope
@@ -577,7 +593,7 @@ export class AccountService {
* - Matching them against on-chain events
* - Adding matched withdrawals to the account state
* 5. Adds ragequit events to accounts if found
*
*
* @throws {DataError} If event fetching fails
* @private
*/
@@ -605,8 +621,14 @@ export class AccountService {
}
// Fetch withdrawal and ragequit events from the first deposit block
const withdrawals = await this.dataService.getWithdrawals(pool, firstDepositBlock);
const ragequits = await this.dataService.getRagequits(pool, firstDepositBlock);
const withdrawals = await this.dataService.getWithdrawals(
pool,
firstDepositBlock
);
const ragequits = await this.dataService.getRagequits(
pool,
firstDepositBlock
);
this.logger.info(
`Found ${withdrawals.length} withdrawals for pool ${pool.address}`
@@ -636,7 +658,9 @@ export class AccountService {
// Continue processing withdrawals until no more are found
while (true) {
// Generate nullifier for this withdrawal
const nullifierHash = poseidon([currentCommitment.nullifier]) as Hash;
const nullifierHash = poseidon([
currentCommitment.nullifier,
]) as Hash;
// Look for a withdrawal event with this nullifier
const withdrawal = withdrawalMap.get(nullifierHash);
@@ -645,7 +669,10 @@ export class AccountService {
}
// Generate secret for this withdrawal
const nullifier = this._genWithdrawalNullifier(account.label, index);
const nullifier = this._genWithdrawalNullifier(
account.label,
index
);
const secret = this._genWithdrawalSecret(account.label, index);
// Add the withdrawal commitment to the account
@@ -673,5 +700,4 @@ export class AccountService {
})
);
}
}

View File

@@ -55,7 +55,7 @@ describe("AccountService", () => {
} as unknown as DataService;
// Initialize account service with mocked data service
accountService = new AccountService(dataService, testMnemonic);
accountService = new AccountService(dataService, {mnemonic: testMnemonic});
masterKeys = accountService.account.masterKeys;
// Generate test data