@aztec/pxe 0.16.3 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/bin/index.js +0 -0
- package/dest/database/memory_db.js +3 -3
- package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
- package/dest/pxe_http/pxe_http_server.js +2 -2
- package/dest/simulator_oracle/index.js +3 -3
- package/dest/synchronizer/synchronizer.js +2 -2
- package/package.json +9 -9
- package/src/bin/index.ts +0 -39
- package/src/config/index.ts +0 -37
- package/src/contract_data_oracle/index.ts +0 -163
- package/src/contract_database/index.ts +0 -1
- package/src/contract_database/memory_contract_database.ts +0 -58
- package/src/contract_tree/index.ts +0 -241
- package/src/database/database.ts +0 -140
- package/src/database/index.ts +0 -2
- package/src/database/memory_db.ts +0 -181
- package/src/database/note_dao.ts +0 -90
- package/src/index.ts +0 -11
- package/src/kernel_oracle/index.ts +0 -39
- package/src/kernel_prover/index.ts +0 -2
- package/src/kernel_prover/kernel_prover.ts +0 -325
- package/src/kernel_prover/proof_creator.ts +0 -155
- package/src/kernel_prover/proving_data_oracle.ts +0 -69
- package/src/note_processor/index.ts +0 -1
- package/src/note_processor/note_processor.ts +0 -286
- package/src/pxe_http/index.ts +0 -1
- package/src/pxe_http/pxe_http_server.ts +0 -78
- package/src/pxe_service/create_pxe_service.ts +0 -52
- package/src/pxe_service/index.ts +0 -3
- package/src/pxe_service/pxe_service.ts +0 -679
- package/src/pxe_service/test/pxe_test_suite.ts +0 -132
- package/src/simulator/index.ts +0 -24
- package/src/simulator_oracle/index.ts +0 -186
- package/src/synchronizer/index.ts +0 -1
- package/src/synchronizer/synchronizer.ts +0 -296
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { AztecAddress, CompleteAddress, Fr, FunctionData, Point, TxContext } from '@aztec/circuits.js';
|
|
2
|
-
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
|
|
3
|
-
import { ConstantKeyPair } from '@aztec/key-store';
|
|
4
|
-
import { DeployedContract, INITIAL_L2_BLOCK_NUM, PXE, TxExecutionRequest, randomDeployedContract } from '@aztec/types';
|
|
5
|
-
|
|
6
|
-
export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) => {
|
|
7
|
-
describe(testName, () => {
|
|
8
|
-
let pxe: PXE;
|
|
9
|
-
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
pxe = await pxeSetup();
|
|
12
|
-
}, 120_000);
|
|
13
|
-
|
|
14
|
-
it('registers an account and returns it as an account only and not as a recipient', async () => {
|
|
15
|
-
const keyPair = ConstantKeyPair.random(new Grumpkin());
|
|
16
|
-
const completeAddress = CompleteAddress.fromPrivateKeyAndPartialAddress(keyPair.getPrivateKey(), Fr.random());
|
|
17
|
-
|
|
18
|
-
await pxe.registerAccount(keyPair.getPrivateKey(), completeAddress.partialAddress);
|
|
19
|
-
|
|
20
|
-
// Check that the account is correctly registered using the getAccounts and getRecipients methods
|
|
21
|
-
const accounts = await pxe.getRegisteredAccounts();
|
|
22
|
-
const recipients = await pxe.getRecipients();
|
|
23
|
-
expect(accounts).toContainEqual(completeAddress);
|
|
24
|
-
expect(recipients).not.toContainEqual(completeAddress);
|
|
25
|
-
|
|
26
|
-
// Check that the account is correctly registered using the getAccount and getRecipient methods
|
|
27
|
-
const account = await pxe.getRegisteredAccount(completeAddress.address);
|
|
28
|
-
const recipient = await pxe.getRecipient(completeAddress.address);
|
|
29
|
-
expect(account).toEqual(completeAddress);
|
|
30
|
-
expect(recipient).toBeUndefined();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('registers a recipient and returns it as a recipient only and not as an account', async () => {
|
|
34
|
-
const completeAddress = CompleteAddress.random();
|
|
35
|
-
|
|
36
|
-
await pxe.registerRecipient(completeAddress);
|
|
37
|
-
|
|
38
|
-
// Check that the recipient is correctly registered using the getAccounts and getRecipients methods
|
|
39
|
-
const accounts = await pxe.getRegisteredAccounts();
|
|
40
|
-
const recipients = await pxe.getRecipients();
|
|
41
|
-
expect(accounts).not.toContainEqual(completeAddress);
|
|
42
|
-
expect(recipients).toContainEqual(completeAddress);
|
|
43
|
-
|
|
44
|
-
// Check that the recipient is correctly registered using the getAccount and getRecipient methods
|
|
45
|
-
const account = await pxe.getRegisteredAccount(completeAddress.address);
|
|
46
|
-
const recipient = await pxe.getRecipient(completeAddress.address);
|
|
47
|
-
expect(account).toBeUndefined();
|
|
48
|
-
expect(recipient).toEqual(completeAddress);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('does not throw when registering the same account twice (just ignores the second attempt)', async () => {
|
|
52
|
-
const keyPair = ConstantKeyPair.random(new Grumpkin());
|
|
53
|
-
const completeAddress = CompleteAddress.fromPrivateKeyAndPartialAddress(keyPair.getPrivateKey(), Fr.random());
|
|
54
|
-
|
|
55
|
-
await pxe.registerAccount(keyPair.getPrivateKey(), completeAddress.partialAddress);
|
|
56
|
-
await pxe.registerAccount(keyPair.getPrivateKey(), completeAddress.partialAddress);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('cannot register a recipient with the same aztec address but different pub key or partial address', async () => {
|
|
60
|
-
const recipient1 = CompleteAddress.random();
|
|
61
|
-
const recipient2 = new CompleteAddress(recipient1.address, Point.random(), Fr.random());
|
|
62
|
-
|
|
63
|
-
await pxe.registerRecipient(recipient1);
|
|
64
|
-
await expect(() => pxe.registerRecipient(recipient2)).rejects.toThrow(
|
|
65
|
-
`Complete address with aztec address ${recipient1.address}`,
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('does not throw when registering the same recipient twice (just ignores the second attempt)', async () => {
|
|
70
|
-
const completeAddress = CompleteAddress.random();
|
|
71
|
-
|
|
72
|
-
await pxe.registerRecipient(completeAddress);
|
|
73
|
-
await pxe.registerRecipient(completeAddress);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('successfully adds a contract', async () => {
|
|
77
|
-
const contracts: DeployedContract[] = [randomDeployedContract(), randomDeployedContract()];
|
|
78
|
-
await pxe.addContracts(contracts);
|
|
79
|
-
|
|
80
|
-
const expectedContractAddresses = contracts.map(contract => contract.completeAddress.address);
|
|
81
|
-
const contractAddresses = await pxe.getContracts();
|
|
82
|
-
|
|
83
|
-
// check if all the contracts were returned
|
|
84
|
-
expect(contractAddresses).toEqual(expect.arrayContaining(expectedContractAddresses));
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('throws when simulating a tx targeting public entrypoint', async () => {
|
|
88
|
-
const functionData = FunctionData.empty();
|
|
89
|
-
functionData.isPrivate = false;
|
|
90
|
-
const txExecutionRequest = TxExecutionRequest.from({
|
|
91
|
-
origin: AztecAddress.random(),
|
|
92
|
-
argsHash: new Fr(0),
|
|
93
|
-
functionData,
|
|
94
|
-
txContext: TxContext.empty(),
|
|
95
|
-
packedArguments: [],
|
|
96
|
-
authWitnesses: [],
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
await expect(async () => await pxe.simulateTx(txExecutionRequest, false)).rejects.toThrow(
|
|
100
|
-
'Public entrypoints are not allowed',
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Note: Not testing a successful run of `simulateTx`, `sendTx`, `getTxReceipt` and `viewTx` here as it requires
|
|
105
|
-
// a larger setup and it's sufficiently tested in the e2e tests.
|
|
106
|
-
|
|
107
|
-
it('throws when getting public storage for non-existent contract', async () => {
|
|
108
|
-
const contract = AztecAddress.random();
|
|
109
|
-
await expect(async () => await pxe.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow(
|
|
110
|
-
`Contract ${contract.toString()} is not deployed`,
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Note: Not testing `getExtendedContractData`, `getContractData` and `getUnencryptedLogs` here as these
|
|
115
|
-
// functions only call AztecNode and these methods are frequently used by the e2e tests.
|
|
116
|
-
|
|
117
|
-
it('successfully gets a block number', async () => {
|
|
118
|
-
const blockNum = await pxe.getBlockNumber();
|
|
119
|
-
expect(blockNum).toBeGreaterThanOrEqual(INITIAL_L2_BLOCK_NUM);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('successfully gets node info', async () => {
|
|
123
|
-
const nodeInfo = await pxe.getNodeInfo();
|
|
124
|
-
expect(typeof nodeInfo.protocolVersion).toEqual('number');
|
|
125
|
-
expect(typeof nodeInfo.chainId).toEqual('number');
|
|
126
|
-
expect(nodeInfo.l1ContractAddresses.rollupAddress.toString()).toMatch(/0x[a-fA-F0-9]+/);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Note: Not testing `isGlobalStateSynchronized`, `isAccountStateSynchronized` and `getSyncStatus` as these methods
|
|
130
|
-
// only call synchronizer.
|
|
131
|
-
});
|
|
132
|
-
};
|
package/src/simulator/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { AcirSimulator } from '@aztec/acir-simulator';
|
|
2
|
-
import { KeyStore, StateInfoProvider } from '@aztec/types';
|
|
3
|
-
|
|
4
|
-
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
5
|
-
import { Database } from '../database/database.js';
|
|
6
|
-
import { SimulatorOracle } from '../simulator_oracle/index.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Helper method to create an instance of the acir simulator.
|
|
10
|
-
*/
|
|
11
|
-
export function getAcirSimulator(
|
|
12
|
-
db: Database,
|
|
13
|
-
stateInfoProvider: StateInfoProvider,
|
|
14
|
-
keyStore: KeyStore,
|
|
15
|
-
contractDataOracle?: ContractDataOracle,
|
|
16
|
-
) {
|
|
17
|
-
const simulatorOracle = new SimulatorOracle(
|
|
18
|
-
contractDataOracle ?? new ContractDataOracle(db, stateInfoProvider),
|
|
19
|
-
db,
|
|
20
|
-
keyStore,
|
|
21
|
-
stateInfoProvider,
|
|
22
|
-
);
|
|
23
|
-
return new AcirSimulator(simulatorOracle);
|
|
24
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { DBOracle, FunctionArtifactWithDebugMetadata, MessageLoadOracleInputs } from '@aztec/acir-simulator';
|
|
2
|
-
import {
|
|
3
|
-
AztecAddress,
|
|
4
|
-
BlockHeader,
|
|
5
|
-
CompleteAddress,
|
|
6
|
-
EthAddress,
|
|
7
|
-
Fr,
|
|
8
|
-
FunctionSelector,
|
|
9
|
-
GrumpkinPrivateKey,
|
|
10
|
-
PublicKey,
|
|
11
|
-
} from '@aztec/circuits.js';
|
|
12
|
-
import { createDebugLogger } from '@aztec/foundation/log';
|
|
13
|
-
import { KeyStore, L2Block, MerkleTreeId, NullifierMembershipWitness, StateInfoProvider } from '@aztec/types';
|
|
14
|
-
|
|
15
|
-
import { ContractDataOracle } from '../contract_data_oracle/index.js';
|
|
16
|
-
import { Database } from '../database/index.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* A data oracle that provides information needed for simulating a transaction.
|
|
20
|
-
*/
|
|
21
|
-
export class SimulatorOracle implements DBOracle {
|
|
22
|
-
constructor(
|
|
23
|
-
private contractDataOracle: ContractDataOracle,
|
|
24
|
-
private db: Database,
|
|
25
|
-
private keyStore: KeyStore,
|
|
26
|
-
private stateInfoProvider: StateInfoProvider,
|
|
27
|
-
private log = createDebugLogger('aztec:pxe:simulator_oracle'),
|
|
28
|
-
) {}
|
|
29
|
-
|
|
30
|
-
getSecretKey(_contractAddress: AztecAddress, pubKey: PublicKey): Promise<GrumpkinPrivateKey> {
|
|
31
|
-
return this.keyStore.getAccountPrivateKey(pubKey);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async getCompleteAddress(address: AztecAddress): Promise<CompleteAddress> {
|
|
35
|
-
const completeAddress = await this.db.getCompleteAddress(address);
|
|
36
|
-
if (!completeAddress) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`No public key registered for address ${address.toString()}. Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/dev_docs/contracts/common_errors#no-public-key-registered-error`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
return completeAddress;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async getAuthWitness(messageHash: Fr): Promise<Fr[]> {
|
|
45
|
-
const witness = await this.db.getAuthWitness(messageHash);
|
|
46
|
-
if (!witness) {
|
|
47
|
-
throw new Error(`Unknown auth witness for message hash ${messageHash.toString()}`);
|
|
48
|
-
}
|
|
49
|
-
return witness;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async popCapsule(): Promise<Fr[]> {
|
|
53
|
-
const capsule = await this.db.popCapsule();
|
|
54
|
-
if (!capsule) {
|
|
55
|
-
throw new Error(`No capsules available`);
|
|
56
|
-
}
|
|
57
|
-
return capsule;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async getNotes(contractAddress: AztecAddress, storageSlot: Fr) {
|
|
61
|
-
const noteDaos = await this.db.getNotes({ contractAddress, storageSlot });
|
|
62
|
-
return noteDaos.map(({ contractAddress, storageSlot, nonce, note, innerNoteHash, siloedNullifier, index }) => ({
|
|
63
|
-
contractAddress,
|
|
64
|
-
storageSlot,
|
|
65
|
-
nonce,
|
|
66
|
-
note,
|
|
67
|
-
innerNoteHash,
|
|
68
|
-
siloedNullifier,
|
|
69
|
-
// PXE can use this index to get full MembershipWitness
|
|
70
|
-
index,
|
|
71
|
-
}));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async getFunctionArtifact(
|
|
75
|
-
contractAddress: AztecAddress,
|
|
76
|
-
selector: FunctionSelector,
|
|
77
|
-
): Promise<FunctionArtifactWithDebugMetadata> {
|
|
78
|
-
const artifact = await this.contractDataOracle.getFunctionArtifact(contractAddress, selector);
|
|
79
|
-
const debug = await this.contractDataOracle.getFunctionDebugMetadata(contractAddress, selector);
|
|
80
|
-
return {
|
|
81
|
-
...artifact,
|
|
82
|
-
debug,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async getFunctionArtifactByName(
|
|
87
|
-
contractAddress: AztecAddress,
|
|
88
|
-
functionName: string,
|
|
89
|
-
): Promise<FunctionArtifactWithDebugMetadata | undefined> {
|
|
90
|
-
const artifact = await this.contractDataOracle.getFunctionArtifactByName(contractAddress, functionName);
|
|
91
|
-
if (!artifact) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const debug = await this.contractDataOracle.getFunctionDebugMetadata(contractAddress, artifact.selector);
|
|
96
|
-
return {
|
|
97
|
-
...artifact,
|
|
98
|
-
debug,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async getPortalContractAddress(contractAddress: AztecAddress): Promise<EthAddress> {
|
|
103
|
-
return await this.contractDataOracle.getPortalContractAddress(contractAddress);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Retrieves the L1ToL2Message associated with a specific message key
|
|
108
|
-
* Throws an error if the message key is not found
|
|
109
|
-
*
|
|
110
|
-
* @param msgKey - The key of the message to be retrieved
|
|
111
|
-
* @returns A promise that resolves to the message data, a sibling path and the
|
|
112
|
-
* index of the message in the l1ToL2MessagesTree
|
|
113
|
-
*/
|
|
114
|
-
async getL1ToL2Message(msgKey: Fr): Promise<MessageLoadOracleInputs> {
|
|
115
|
-
const messageAndIndex = await this.stateInfoProvider.getL1ToL2MessageAndIndex(msgKey);
|
|
116
|
-
const message = messageAndIndex.message.toFieldArray();
|
|
117
|
-
const index = messageAndIndex.index;
|
|
118
|
-
const siblingPath = await this.stateInfoProvider.getL1ToL2MessageSiblingPath('latest', index);
|
|
119
|
-
return {
|
|
120
|
-
message,
|
|
121
|
-
siblingPath: siblingPath.toFieldArray(),
|
|
122
|
-
index,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Gets the index of a commitment in the note hash tree.
|
|
128
|
-
* @param commitment - The commitment.
|
|
129
|
-
* @returns - The index of the commitment. Undefined if it does not exist in the tree.
|
|
130
|
-
*/
|
|
131
|
-
async getCommitmentIndex(commitment: Fr) {
|
|
132
|
-
return await this.stateInfoProvider.findLeafIndex('latest', MerkleTreeId.NOTE_HASH_TREE, commitment);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async getNullifierIndex(nullifier: Fr) {
|
|
136
|
-
return await this.stateInfoProvider.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
public async findLeafIndex(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise<bigint | undefined> {
|
|
140
|
-
return await this.stateInfoProvider.findLeafIndex(blockNumber, treeId, leafValue);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
public async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: bigint): Promise<Fr[]> {
|
|
144
|
-
// @todo Doing a nasty workaround here because of https://github.com/AztecProtocol/aztec-packages/issues/3414
|
|
145
|
-
switch (treeId) {
|
|
146
|
-
case MerkleTreeId.NULLIFIER_TREE:
|
|
147
|
-
return (await this.stateInfoProvider.getNullifierTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
|
|
148
|
-
case MerkleTreeId.NOTE_HASH_TREE:
|
|
149
|
-
return (await this.stateInfoProvider.getNoteHashSiblingPath(blockNumber, leafIndex)).toFieldArray();
|
|
150
|
-
case MerkleTreeId.BLOCKS_TREE:
|
|
151
|
-
return (await this.stateInfoProvider.getBlocksTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
|
|
152
|
-
case MerkleTreeId.PUBLIC_DATA_TREE:
|
|
153
|
-
return (await this.stateInfoProvider.getPublicDataTreeSiblingPath(blockNumber, leafIndex)).toFieldArray();
|
|
154
|
-
default:
|
|
155
|
-
throw new Error('Not implemented');
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
public getNullifierMembershipWitness(
|
|
160
|
-
blockNumber: number,
|
|
161
|
-
nullifier: Fr,
|
|
162
|
-
): Promise<NullifierMembershipWitness | undefined> {
|
|
163
|
-
return this.stateInfoProvider.getNullifierMembershipWitness(blockNumber, nullifier);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
public getLowNullifierMembershipWitness(
|
|
167
|
-
blockNumber: number,
|
|
168
|
-
nullifier: Fr,
|
|
169
|
-
): Promise<NullifierMembershipWitness | undefined> {
|
|
170
|
-
return this.stateInfoProvider.getLowNullifierMembershipWitness(blockNumber, nullifier);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
public async getBlock(blockNumber: number): Promise<L2Block | undefined> {
|
|
174
|
-
return await this.stateInfoProvider.getBlock(blockNumber);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Retrieve the databases view of the Block Header object.
|
|
179
|
-
* This structure is fed into the circuits simulator and is used to prove against certain historical roots.
|
|
180
|
-
*
|
|
181
|
-
* @returns A Promise that resolves to a BlockHeader object.
|
|
182
|
-
*/
|
|
183
|
-
getBlockHeader(): Promise<BlockHeader> {
|
|
184
|
-
return Promise.resolve(this.db.getBlockHeader());
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './synchronizer.js';
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import { AztecAddress, BlockHeader, Fr, PublicKey } from '@aztec/circuits.js';
|
|
2
|
-
import { computeGlobalsHash } from '@aztec/circuits.js/abis';
|
|
3
|
-
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
-
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
5
|
-
import { AztecNode, INITIAL_L2_BLOCK_NUM, KeyStore, L2BlockContext, L2BlockL2Logs, LogType } from '@aztec/types';
|
|
6
|
-
import { NoteProcessorCaughtUpStats } from '@aztec/types/stats';
|
|
7
|
-
|
|
8
|
-
import { Database } from '../database/index.js';
|
|
9
|
-
import { NoteProcessor } from '../note_processor/index.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The Synchronizer class manages the synchronization of note processors and interacts with the Aztec node
|
|
13
|
-
* to obtain encrypted logs, blocks, and other necessary information for the accounts.
|
|
14
|
-
* It provides methods to start or stop the synchronization process, add new accounts, retrieve account
|
|
15
|
-
* details, and fetch transactions by hash. The Synchronizer ensures that it maintains the note processors
|
|
16
|
-
* in sync with the blockchain while handling retries and errors gracefully.
|
|
17
|
-
*/
|
|
18
|
-
export class Synchronizer {
|
|
19
|
-
private runningPromise?: Promise<void>;
|
|
20
|
-
private noteProcessors: NoteProcessor[] = [];
|
|
21
|
-
private interruptibleSleep = new InterruptibleSleep();
|
|
22
|
-
private running = false;
|
|
23
|
-
private initialSyncBlockNumber = 0;
|
|
24
|
-
private synchedToBlock = 0;
|
|
25
|
-
private log: DebugLogger;
|
|
26
|
-
private noteProcessorsToCatchUp: NoteProcessor[] = [];
|
|
27
|
-
|
|
28
|
-
constructor(private node: AztecNode, private db: Database, logSuffix = '') {
|
|
29
|
-
this.log = createDebugLogger(logSuffix ? `aztec:pxe_synchronizer_${logSuffix}` : 'aztec:pxe_synchronizer');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Starts the synchronization process by fetching encrypted logs and blocks from a specified position.
|
|
34
|
-
* Continuously processes the fetched data for all note processors until stopped. If there is no data
|
|
35
|
-
* available, it retries after a specified interval.
|
|
36
|
-
*
|
|
37
|
-
* @param from - The starting position for fetching encrypted logs and blocks.
|
|
38
|
-
* @param limit - The maximum number of encrypted, unencrypted logs and blocks to fetch in each iteration.
|
|
39
|
-
* @param retryInterval - The time interval (in ms) to wait before retrying if no data is available.
|
|
40
|
-
*/
|
|
41
|
-
public async start(from = INITIAL_L2_BLOCK_NUM, limit = 1, retryInterval = 1000) {
|
|
42
|
-
if (this.running) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
this.running = true;
|
|
46
|
-
|
|
47
|
-
if (from < this.synchedToBlock + 1) {
|
|
48
|
-
throw new Error(`From block ${from} is smaller than the currently synched block ${this.synchedToBlock}`);
|
|
49
|
-
}
|
|
50
|
-
this.synchedToBlock = from - 1;
|
|
51
|
-
|
|
52
|
-
await this.initialSync();
|
|
53
|
-
|
|
54
|
-
const run = async () => {
|
|
55
|
-
while (this.running) {
|
|
56
|
-
if (this.noteProcessorsToCatchUp.length > 0) {
|
|
57
|
-
// There is a note processor that needs to catch up. We hijack the main loop to catch up the note processor.
|
|
58
|
-
await this.workNoteProcessorCatchUp(limit, retryInterval);
|
|
59
|
-
} else {
|
|
60
|
-
// No note processor needs to catch up. We continue with the normal flow.
|
|
61
|
-
await this.work(limit, retryInterval);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
this.runningPromise = run();
|
|
67
|
-
this.log('Started');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
protected async initialSync() {
|
|
71
|
-
const [blockNumber, blockHeader] = await Promise.all([this.node.getBlockNumber(), this.node.getBlockHeader()]);
|
|
72
|
-
this.initialSyncBlockNumber = blockNumber;
|
|
73
|
-
this.synchedToBlock = this.initialSyncBlockNumber;
|
|
74
|
-
await this.db.setBlockHeader(blockHeader);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
protected async work(limit = 1, retryInterval = 1000): Promise<void> {
|
|
78
|
-
const from = this.synchedToBlock + 1;
|
|
79
|
-
try {
|
|
80
|
-
let encryptedLogs = await this.node.getLogs(from, limit, LogType.ENCRYPTED);
|
|
81
|
-
if (!encryptedLogs.length) {
|
|
82
|
-
await this.interruptibleSleep.sleep(retryInterval);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let unencryptedLogs = await this.node.getLogs(from, limit, LogType.UNENCRYPTED);
|
|
87
|
-
if (!unencryptedLogs.length) {
|
|
88
|
-
await this.interruptibleSleep.sleep(retryInterval);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Note: If less than `limit` encrypted logs is returned, then we fetch only that number of blocks.
|
|
93
|
-
const blocks = await this.node.getBlocks(from, encryptedLogs.length);
|
|
94
|
-
if (!blocks.length) {
|
|
95
|
-
await this.interruptibleSleep.sleep(retryInterval);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (blocks.length !== encryptedLogs.length) {
|
|
100
|
-
// "Trim" the encrypted logs to match the number of blocks.
|
|
101
|
-
encryptedLogs = encryptedLogs.slice(0, blocks.length);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (blocks.length !== unencryptedLogs.length) {
|
|
105
|
-
// "Trim" the unencrypted logs to match the number of blocks.
|
|
106
|
-
unencryptedLogs = unencryptedLogs.slice(0, blocks.length);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// attach logs to blocks
|
|
110
|
-
blocks.forEach((block, i) => {
|
|
111
|
-
block.attachLogs(encryptedLogs[i], LogType.ENCRYPTED);
|
|
112
|
-
block.attachLogs(unencryptedLogs[i], LogType.UNENCRYPTED);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Wrap blocks in block contexts.
|
|
116
|
-
const blockContexts = blocks.map(block => new L2BlockContext(block));
|
|
117
|
-
|
|
118
|
-
// Update latest tree roots from the most recent block
|
|
119
|
-
const latestBlock = blockContexts[blockContexts.length - 1];
|
|
120
|
-
await this.setBlockDataFromBlock(latestBlock);
|
|
121
|
-
|
|
122
|
-
const logCount = L2BlockL2Logs.getTotalLogCount(encryptedLogs);
|
|
123
|
-
this.log(`Forwarding ${logCount} encrypted logs and blocks to ${this.noteProcessors.length} note processors`);
|
|
124
|
-
for (const noteProcessor of this.noteProcessors) {
|
|
125
|
-
await noteProcessor.process(blockContexts, encryptedLogs);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.synchedToBlock = latestBlock.block.number;
|
|
129
|
-
} catch (err) {
|
|
130
|
-
this.log.error(`Error in synchronizer work`, err);
|
|
131
|
-
await this.interruptibleSleep.sleep(retryInterval);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
protected async workNoteProcessorCatchUp(limit = 1, retryInterval = 1000): Promise<void> {
|
|
136
|
-
const noteProcessor = this.noteProcessorsToCatchUp[0];
|
|
137
|
-
if (noteProcessor.status.syncedToBlock === this.synchedToBlock) {
|
|
138
|
-
// Note processor already synched, nothing to do
|
|
139
|
-
this.noteProcessorsToCatchUp.shift();
|
|
140
|
-
this.noteProcessors.push(noteProcessor);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const from = noteProcessor.status.syncedToBlock + 1;
|
|
145
|
-
// Ensuring that the note processor does not sync further than the main sync.
|
|
146
|
-
limit = Math.min(limit, this.synchedToBlock - from + 1);
|
|
147
|
-
|
|
148
|
-
if (limit < 1) {
|
|
149
|
-
throw new Error(`Unexpected limit ${limit} for note processor catch up`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
let encryptedLogs = await this.node.getLogs(from, limit, LogType.ENCRYPTED);
|
|
154
|
-
if (!encryptedLogs.length) {
|
|
155
|
-
// This should never happen because this function should only be called when the note processor is lagging
|
|
156
|
-
// behind main sync.
|
|
157
|
-
throw new Error('No encrypted logs in processor catch up mode');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Note: If less than `limit` encrypted logs is returned, then we fetch only that number of blocks.
|
|
161
|
-
const blocks = await this.node.getBlocks(from, encryptedLogs.length);
|
|
162
|
-
if (!blocks.length) {
|
|
163
|
-
// This should never happen because this function should only be called when the note processor is lagging
|
|
164
|
-
// behind main sync.
|
|
165
|
-
throw new Error('No blocks in processor catch up mode');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (blocks.length !== encryptedLogs.length) {
|
|
169
|
-
// "Trim" the encrypted logs to match the number of blocks.
|
|
170
|
-
encryptedLogs = encryptedLogs.slice(0, blocks.length);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const blockContexts = blocks.map(block => new L2BlockContext(block));
|
|
174
|
-
|
|
175
|
-
const logCount = L2BlockL2Logs.getTotalLogCount(encryptedLogs);
|
|
176
|
-
this.log(`Forwarding ${logCount} encrypted logs and blocks to note processor in catch up mode`);
|
|
177
|
-
await noteProcessor.process(blockContexts, encryptedLogs);
|
|
178
|
-
|
|
179
|
-
if (noteProcessor.status.syncedToBlock === this.synchedToBlock) {
|
|
180
|
-
// Note processor caught up, move it to `noteProcessors` from `noteProcessorsToCatchUp`.
|
|
181
|
-
this.log(`Note processor for ${noteProcessor.publicKey.toString()} has caught up`, {
|
|
182
|
-
eventName: 'note-processor-caught-up',
|
|
183
|
-
publicKey: noteProcessor.publicKey.toString(),
|
|
184
|
-
duration: noteProcessor.timer.ms(),
|
|
185
|
-
dbSize: this.db.estimateSize(),
|
|
186
|
-
...noteProcessor.stats,
|
|
187
|
-
} satisfies NoteProcessorCaughtUpStats);
|
|
188
|
-
this.noteProcessorsToCatchUp.shift();
|
|
189
|
-
this.noteProcessors.push(noteProcessor);
|
|
190
|
-
}
|
|
191
|
-
} catch (err) {
|
|
192
|
-
this.log.error(`Error in synchronizer workNoteProcessorCatchUp`, err);
|
|
193
|
-
await this.interruptibleSleep.sleep(retryInterval);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private async setBlockDataFromBlock(latestBlock: L2BlockContext) {
|
|
198
|
-
const { block } = latestBlock;
|
|
199
|
-
if (block.number < this.initialSyncBlockNumber) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const globalsHash = computeGlobalsHash(latestBlock.block.globalVariables);
|
|
204
|
-
const blockHeader = new BlockHeader(
|
|
205
|
-
block.endNoteHashTreeSnapshot.root,
|
|
206
|
-
block.endNullifierTreeSnapshot.root,
|
|
207
|
-
block.endContractTreeSnapshot.root,
|
|
208
|
-
block.endL1ToL2MessagesTreeSnapshot.root,
|
|
209
|
-
block.endBlocksTreeSnapshot.root,
|
|
210
|
-
Fr.ZERO, // todo: private kernel vk tree root
|
|
211
|
-
block.endPublicDataTreeRoot,
|
|
212
|
-
globalsHash,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
await this.db.setBlockHeader(blockHeader);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Stops the synchronizer gracefully, interrupting any ongoing sleep and waiting for the current
|
|
220
|
-
* iteration to complete before setting the running state to false. Once stopped, the synchronizer
|
|
221
|
-
* will no longer process blocks or encrypted logs and must be restarted using the start method.
|
|
222
|
-
*
|
|
223
|
-
* @returns A promise that resolves when the synchronizer has successfully stopped.
|
|
224
|
-
*/
|
|
225
|
-
public async stop() {
|
|
226
|
-
this.running = false;
|
|
227
|
-
this.interruptibleSleep.interrupt();
|
|
228
|
-
await this.runningPromise;
|
|
229
|
-
this.log('Stopped');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Add a new account to the Synchronizer with the specified private key.
|
|
234
|
-
* Creates a NoteProcessor instance for the account and pushes it into the noteProcessors array.
|
|
235
|
-
* The method resolves immediately after pushing the new note processor.
|
|
236
|
-
*
|
|
237
|
-
* @param publicKey - The public key for the account.
|
|
238
|
-
* @param keyStore - The key store.
|
|
239
|
-
* @param startingBlock - The block where to start scanning for notes for this accounts.
|
|
240
|
-
* @returns A promise that resolves once the account is added to the Synchronizer.
|
|
241
|
-
*/
|
|
242
|
-
public addAccount(publicKey: PublicKey, keyStore: KeyStore, startingBlock: number) {
|
|
243
|
-
const predicate = (x: NoteProcessor) => x.publicKey.equals(publicKey);
|
|
244
|
-
const processor = this.noteProcessors.find(predicate) ?? this.noteProcessorsToCatchUp.find(predicate);
|
|
245
|
-
if (processor) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
this.noteProcessorsToCatchUp.push(new NoteProcessor(publicKey, keyStore, this.db, this.node, startingBlock));
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Checks if the specified account is synchronized.
|
|
254
|
-
* @param account - The aztec address for which to query the sync status.
|
|
255
|
-
* @returns True if the account is fully synched, false otherwise.
|
|
256
|
-
* @remarks Checks whether all the notes from all the blocks have been processed. If it is not the case, the
|
|
257
|
-
* retrieved information from contracts might be old/stale (e.g. old token balance).
|
|
258
|
-
* @throws If checking a sync status of account which is not registered.
|
|
259
|
-
*/
|
|
260
|
-
public async isAccountStateSynchronized(account: AztecAddress) {
|
|
261
|
-
const completeAddress = await this.db.getCompleteAddress(account);
|
|
262
|
-
if (!completeAddress) {
|
|
263
|
-
throw new Error(`Checking if account is synched is not possible for ${account} because it is not registered.`);
|
|
264
|
-
}
|
|
265
|
-
const findByPublicKey = (x: NoteProcessor) => x.publicKey.equals(completeAddress.publicKey);
|
|
266
|
-
const processor = this.noteProcessors.find(findByPublicKey) ?? this.noteProcessorsToCatchUp.find(findByPublicKey);
|
|
267
|
-
if (!processor) {
|
|
268
|
-
throw new Error(
|
|
269
|
-
`Checking if account is synched is not possible for ${account} because it is only registered as a recipient.`,
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
return await processor.isSynchronized();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Checks whether all the blocks were processed (tree roots updated, txs updated with block info, etc.).
|
|
277
|
-
* @returns True if there are no outstanding blocks to be synched.
|
|
278
|
-
* @remarks This indicates that blocks and transactions are synched even if notes are not.
|
|
279
|
-
* @remarks Compares local block number with the block number from aztec node.
|
|
280
|
-
*/
|
|
281
|
-
public async isGlobalStateSynchronized() {
|
|
282
|
-
const latest = await this.node.getBlockNumber();
|
|
283
|
-
return latest <= this.synchedToBlock;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Returns the latest block that has been synchronized by the synchronizer and each account.
|
|
288
|
-
* @returns The latest block synchronized for blocks, and the latest block synched for notes for each public key being tracked.
|
|
289
|
-
*/
|
|
290
|
-
public getSyncStatus() {
|
|
291
|
-
return {
|
|
292
|
-
blocks: this.synchedToBlock,
|
|
293
|
-
notes: Object.fromEntries(this.noteProcessors.map(n => [n.publicKey.toString(), n.status.syncedToBlock])),
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|