@aztec/p2p 0.0.1-commit.358457c → 0.0.1-commit.381b1a9

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.
Files changed (148) hide show
  1. package/dest/client/factory.d.ts +4 -5
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +12 -7
  4. package/dest/client/interface.d.ts +4 -4
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +4 -4
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +9 -27
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -2
  10. package/dest/config.d.ts +32 -11
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +85 -31
  13. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  14. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  15. package/dest/mem_pools/tx_pool/priority.js +4 -4
  16. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  17. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  18. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  19. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
  20. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  21. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
  22. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  27. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  28. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  30. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
  31. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool_v2/index.js +1 -1
  33. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -3
  34. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/interfaces.js +1 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +30 -7
  37. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +62 -16
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  44. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +38 -30
  45. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
  46. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  47. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  48. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
  49. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  50. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  51. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
  52. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  53. package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
  54. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  55. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  56. package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
  57. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  58. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  59. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  60. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  61. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  62. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  63. package/dest/msg_validators/tx_validator/factory.d.ts +15 -4
  64. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  65. package/dest/msg_validators/tx_validator/factory.js +21 -8
  66. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  67. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  68. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  69. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  70. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  71. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  72. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  73. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  74. package/dest/msg_validators/tx_validator/index.js +1 -0
  75. package/dest/msg_validators/tx_validator/phases_validator.d.ts +2 -2
  76. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  77. package/dest/msg_validators/tx_validator/phases_validator.js +44 -23
  78. package/dest/services/encoding.d.ts +2 -2
  79. package/dest/services/encoding.d.ts.map +1 -1
  80. package/dest/services/encoding.js +7 -7
  81. package/dest/services/libp2p/libp2p_service.d.ts +6 -7
  82. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  83. package/dest/services/libp2p/libp2p_service.js +31 -23
  84. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
  85. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  86. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +37 -14
  87. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +11 -17
  88. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  89. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +15 -49
  90. package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
  91. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  92. package/dest/test-helpers/make-test-p2p-clients.js +1 -2
  93. package/dest/test-helpers/mock-pubsub.d.ts +2 -3
  94. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  95. package/dest/test-helpers/mock-pubsub.js +2 -2
  96. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  97. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  98. package/dest/test-helpers/reqresp-nodes.js +2 -2
  99. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  100. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  101. package/dest/test-helpers/testbench-utils.js +2 -1
  102. package/dest/testbench/p2p_client_testbench_worker.js +7 -6
  103. package/dest/testbench/worker_client_manager.d.ts +3 -1
  104. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  105. package/dest/testbench/worker_client_manager.js +4 -1
  106. package/package.json +14 -14
  107. package/src/client/factory.ts +15 -14
  108. package/src/client/interface.ts +3 -9
  109. package/src/client/p2p_client.ts +7 -38
  110. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -2
  111. package/src/config.ts +124 -34
  112. package/src/mem_pools/tx_pool/priority.ts +4 -4
  113. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  114. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  115. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
  116. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  117. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  118. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  119. package/src/mem_pools/tx_pool_v2/index.ts +1 -1
  120. package/src/mem_pools/tx_pool_v2/interfaces.ts +3 -3
  121. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +89 -17
  122. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
  123. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +43 -27
  124. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
  125. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
  126. package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
  127. package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
  128. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  129. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  130. package/src/msg_validators/tx_validator/factory.ts +22 -2
  131. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  132. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  133. package/src/msg_validators/tx_validator/index.ts +1 -0
  134. package/src/msg_validators/tx_validator/phases_validator.ts +51 -26
  135. package/src/services/encoding.ts +5 -6
  136. package/src/services/libp2p/libp2p_service.ts +30 -21
  137. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +42 -14
  138. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +24 -63
  139. package/src/test-helpers/make-test-p2p-clients.ts +0 -2
  140. package/src/test-helpers/mock-pubsub.ts +3 -6
  141. package/src/test-helpers/reqresp-nodes.ts +2 -5
  142. package/src/test-helpers/testbench-utils.ts +1 -0
  143. package/src/testbench/p2p_client_testbench_worker.ts +3 -6
  144. package/src/testbench/worker_client_manager.ts +11 -4
  145. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
  146. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  147. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
  148. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
@@ -1,22 +1,35 @@
1
1
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
2
  import { NoCommitteeError } from '@aztec/ethereum/contracts';
3
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
- import { BlockProposal, CheckpointProposal, PeerErrorSeverity, type ValidationResult } from '@aztec/stdlib/p2p';
4
+ import {
5
+ type BlockProposal,
6
+ type CheckpointProposalCore,
7
+ PeerErrorSeverity,
8
+ type ValidationResult,
9
+ } from '@aztec/stdlib/p2p';
5
10
 
6
11
  import { isWithinClockTolerance } from '../clock_tolerance.js';
7
12
 
8
- export abstract class ProposalValidator<TProposal extends BlockProposal | CheckpointProposal> {
9
- protected epochCache: EpochCacheInterface;
10
- protected logger: Logger;
11
- protected txsPermitted: boolean;
13
+ /** Validates header-level and tx-level fields of block and checkpoint proposals. */
14
+ export class ProposalValidator {
15
+ private epochCache: EpochCacheInterface;
16
+ private logger: Logger;
17
+ private txsPermitted: boolean;
18
+ private maxTxsPerBlock?: number;
12
19
 
13
- constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }, loggerName: string) {
20
+ constructor(
21
+ epochCache: EpochCacheInterface,
22
+ opts: { txsPermitted: boolean; maxTxsPerBlock?: number },
23
+ loggerName: string,
24
+ ) {
14
25
  this.epochCache = epochCache;
15
26
  this.txsPermitted = opts.txsPermitted;
27
+ this.maxTxsPerBlock = opts.maxTxsPerBlock;
16
28
  this.logger = createLogger(loggerName);
17
29
  }
18
30
 
19
- public async validate(proposal: TProposal): Promise<ValidationResult> {
31
+ /** Validates header-level fields: slot, signature, and proposer. */
32
+ public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
20
33
  try {
21
34
  // Slot check
22
35
  const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
@@ -38,30 +51,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
38
51
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
39
52
  }
40
53
 
41
- // Transactions permitted check
42
- const embeddedTxCount = proposal.txs?.length ?? 0;
43
- if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
44
- this.logger.warn(
45
- `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
46
- );
47
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
48
- }
49
-
50
- // Embedded txs must be listed in txHashes
51
- const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
52
- const missingTxHashes =
53
- embeddedTxCount > 0
54
- ? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
55
- : [];
56
- if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
57
- this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
58
- embeddedTxCount,
59
- txHashesLength: proposal.txHashes.length,
60
- missingTxHashes,
61
- });
62
- return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
63
- }
64
-
65
54
  // Proposer check
66
55
  const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
67
56
  if (expectedProposer !== undefined && !proposer.equals(expectedProposer)) {
@@ -72,15 +61,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
72
61
  return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
73
62
  }
74
63
 
75
- // Validate tx hashes for all txs embedded in the proposal
76
- if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
77
- this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`, {
78
- proposer,
79
- slotNumber,
80
- });
81
- return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
82
- }
83
-
84
64
  return { result: 'accept' };
85
65
  } catch (e) {
86
66
  if (e instanceof NoCommitteeError) {
@@ -89,4 +69,47 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
89
69
  throw e;
90
70
  }
91
71
  }
72
+
73
+ /** Validates transaction-related fields of a block proposal. */
74
+ public async validateTxs(proposal: BlockProposal): Promise<ValidationResult> {
75
+ // Transactions permitted check
76
+ const embeddedTxCount = proposal.txs?.length ?? 0;
77
+ if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
78
+ this.logger.warn(
79
+ `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
80
+ );
81
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
82
+ }
83
+
84
+ // Max txs per block check
85
+ if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
86
+ this.logger.warn(
87
+ `Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
88
+ );
89
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
90
+ }
91
+
92
+ // Embedded txs must be listed in txHashes
93
+ const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
94
+ const missingTxHashes =
95
+ embeddedTxCount > 0
96
+ ? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
97
+ : [];
98
+ if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
99
+ this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
100
+ embeddedTxCount,
101
+ txHashesLength: proposal.txHashes.length,
102
+ missingTxHashes,
103
+ });
104
+ return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
105
+ }
106
+
107
+ // Validate tx hashes for all txs embedded in the proposal
108
+ if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
109
+ this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`);
110
+ return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
111
+ }
112
+
113
+ return { result: 'accept' };
114
+ }
92
115
  }
@@ -1,35 +1,30 @@
1
- import { FPCContract } from '@aztec/noir-contracts.js/FPC';
2
- import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
3
1
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
4
- import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
2
+ import { AuthRegistryArtifact } from '@aztec/protocol-contracts/auth-registry';
3
+ import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
5
4
  import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
6
5
 
7
- let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined;
6
+ import { buildAllowedElement } from './allowed_setup_helpers.js';
7
+
8
+ let defaultAllowedSetupFunctions: AllowedElement[] | undefined;
9
+
10
+ /** Returns the default list of functions allowed to run in the setup phase of a transaction. */
8
11
  export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
9
12
  if (defaultAllowedSetupFunctions === undefined) {
10
- defaultAllowedSetupFunctions = [
11
- // needed for authwit support
12
- {
13
- address: ProtocolContractAddress.AuthRegistry,
14
- },
15
- // needed for claiming on the same tx as a spend
16
- {
17
- address: ProtocolContractAddress.FeeJuice,
18
- // We can't restrict the selector because public functions get routed via dispatch.
19
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'),
20
- },
21
- // needed for private transfers via FPC
22
- {
23
- classId: (await getContractClassFromArtifact(TokenContractArtifact)).id,
24
- // We can't restrict the selector because public functions get routed via dispatch.
25
- // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'),
26
- },
27
- {
28
- classId: (await getContractClassFromArtifact(FPCContract.artifact)).id,
29
- // We can't restrict the selector because public functions get routed via dispatch.
30
- // selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
31
- },
32
- ];
13
+ defaultAllowedSetupFunctions = await Promise.all([
14
+ // AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized)
15
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, '_set_authorized', {
16
+ onlySelf: true,
17
+ rejectNullMsgSender: true,
18
+ }),
19
+ // AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly)
20
+ buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, 'set_authorized', {
21
+ rejectNullMsgSender: true,
22
+ }),
23
+ // FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this)
24
+ buildAllowedElement(FeeJuiceArtifact, { address: ProtocolContractAddress.FeeJuice }, '_increase_public_balance', {
25
+ onlySelf: true,
26
+ }),
27
+ ]);
33
28
  }
34
29
  return defaultAllowedSetupFunctions;
35
30
  }
@@ -0,0 +1,31 @@
1
+ import type { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { FunctionSelector, countArgumentsSize, getAllFunctionAbis } from '@aztec/stdlib/abi';
3
+ import type { ContractArtifact } from '@aztec/stdlib/abi';
4
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
6
+
7
+ /**
8
+ * Builds an AllowedElement from a contract artifact, deriving both the function selector
9
+ * and calldata length from the artifact instead of hardcoding signature strings.
10
+ */
11
+ export async function buildAllowedElement(
12
+ artifact: ContractArtifact,
13
+ target: { address: AztecAddress } | { classId: Fr },
14
+ functionName: string,
15
+ opts?: { onlySelf?: boolean; rejectNullMsgSender?: boolean },
16
+ ): Promise<AllowedElement> {
17
+ const allFunctions = getAllFunctionAbis(artifact);
18
+ const fn = allFunctions.find(f => f.name === functionName);
19
+ if (!fn) {
20
+ throw new Error(`Unknown function ${functionName} in artifact ${artifact.name}`);
21
+ }
22
+ const selector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
23
+ const calldataLength = 1 + countArgumentsSize(fn);
24
+ return {
25
+ ...target,
26
+ selector,
27
+ calldataLength,
28
+ ...(opts?.onlySelf ? { onlySelf: true } : {}),
29
+ ...(opts?.rejectNullMsgSender ? { rejectNullMsgSender: true } : {}),
30
+ } as AllowedElement;
31
+ }
@@ -0,0 +1,56 @@
1
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
+ import { ContractInstancePublishedEvent } from '@aztec/protocol-contracts/instance-registry';
3
+ import { computeContractAddressFromInstance } from '@aztec/stdlib/contract';
4
+ import {
5
+ TX_ERROR_INCORRECT_CONTRACT_ADDRESS,
6
+ TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG,
7
+ type Tx,
8
+ type TxValidationResult,
9
+ type TxValidator,
10
+ } from '@aztec/stdlib/tx';
11
+
12
+ /** Validates that contract instance deployment logs contain correct addresses. */
13
+ export class ContractInstanceTxValidator implements TxValidator<Tx> {
14
+ #log: Logger;
15
+
16
+ constructor(bindings?: LoggerBindings) {
17
+ this.#log = createLogger('p2p:tx_validator:contract_instance', bindings);
18
+ }
19
+
20
+ async validateTx(tx: Tx): Promise<TxValidationResult> {
21
+ const reason = await this.#hasCorrectContractInstanceAddresses(tx);
22
+ return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' };
23
+ }
24
+
25
+ async #hasCorrectContractInstanceAddresses(tx: Tx): Promise<string | undefined> {
26
+ const privateLogs = tx.data.getNonEmptyPrivateLogs();
27
+ for (const log of privateLogs) {
28
+ if (!ContractInstancePublishedEvent.isContractInstancePublishedEvent(log)) {
29
+ continue;
30
+ }
31
+
32
+ let event;
33
+ try {
34
+ event = ContractInstancePublishedEvent.fromLog(log);
35
+ } catch (e) {
36
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract instance event: ${e}`);
37
+ return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG;
38
+ }
39
+
40
+ try {
41
+ const instance = event.toContractInstance();
42
+ const computedAddress = await computeContractAddressFromInstance(instance);
43
+ if (!computedAddress.equals(instance.address)) {
44
+ this.#log.warn(
45
+ `Rejecting tx ${tx.getTxHash()}: contract instance address mismatch. Claimed ${instance.address}, computed ${computedAddress}`,
46
+ );
47
+ return TX_ERROR_INCORRECT_CONTRACT_ADDRESS;
48
+ }
49
+ } catch (e) {
50
+ this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract instance address: ${e}`);
51
+ return TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ }
@@ -53,6 +53,7 @@ import type { TxMetaData } from '../../mem_pools/tx_pool_v2/tx_metadata.js';
53
53
  import { AggregateTxValidator } from './aggregate_tx_validator.js';
54
54
  import { ArchiveCache } from './archive_cache.js';
55
55
  import { type ArchiveSource, BlockHeaderTxValidator } from './block_header_validator.js';
56
+ import { ContractInstanceTxValidator } from './contract_instance_validator.js';
56
57
  import { DataTxValidator } from './data_validator.js';
57
58
  import { DoubleSpendTxValidator, type NullifierSource } from './double_spend_validator.js';
58
59
  import { GasLimitsValidator, GasTxValidator } from './gas_validator.js';
@@ -97,6 +98,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
97
98
  txsPermitted: boolean,
98
99
  allowedInSetup: AllowedElement[] = [],
99
100
  bindings?: LoggerBindings,
101
+ gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
100
102
  ): Record<string, TransactionValidator> {
101
103
  const merkleTree = worldStateSynchronizer.getCommitted();
102
104
 
@@ -158,6 +160,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
158
160
  ProtocolContractAddress.FeeJuice,
159
161
  gasFees,
160
162
  bindings,
163
+ gasLimitOpts,
161
164
  ),
162
165
  severity: PeerErrorSeverity.MidToleranceError,
163
166
  },
@@ -165,6 +168,10 @@ export function createFirstStageTxValidationsForGossipedTransactions(
165
168
  validator: new DataTxValidator(bindings),
166
169
  severity: PeerErrorSeverity.MidToleranceError,
167
170
  },
171
+ contractInstanceValidator: {
172
+ validator: new ContractInstanceTxValidator(bindings),
173
+ severity: PeerErrorSeverity.MidToleranceError,
174
+ },
168
175
  };
169
176
  }
170
177
 
@@ -216,6 +223,7 @@ function createTxValidatorForMinimumTxIntegrityChecks(
216
223
  ),
217
224
  new SizeTxValidator(bindings),
218
225
  new DataTxValidator(bindings),
226
+ new ContractInstanceTxValidator(bindings),
219
227
  new TxProofValidator(verifier, bindings),
220
228
  );
221
229
  }
@@ -278,6 +286,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
278
286
  timestamp,
279
287
  blockNumber,
280
288
  txsPermitted,
289
+ rollupManaLimit,
290
+ maxBlockL2Gas,
291
+ maxBlockDAGas,
281
292
  }: {
282
293
  l1ChainId: number;
283
294
  rollupVersion: number;
@@ -287,6 +298,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
287
298
  timestamp: UInt64;
288
299
  blockNumber: BlockNumber;
289
300
  txsPermitted: boolean;
301
+ rollupManaLimit: number;
302
+ maxBlockL2Gas?: number;
303
+ maxBlockDAGas?: number;
290
304
  },
291
305
  bindings?: LoggerBindings,
292
306
  ): TxValidator<Tx> {
@@ -313,11 +327,16 @@ export function createTxValidatorForAcceptingTxsOverRPC(
313
327
  new BlockHeaderTxValidator(new ArchiveCache(db), bindings),
314
328
  new DoubleSpendTxValidator(new NullifierCache(db), bindings),
315
329
  new DataTxValidator(bindings),
330
+ new ContractInstanceTxValidator(bindings),
316
331
  ];
317
332
 
318
333
  if (!skipFeeEnforcement) {
319
334
  validators.push(
320
- new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings),
335
+ new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings, {
336
+ rollupManaLimit,
337
+ maxBlockL2Gas,
338
+ maxBlockDAGas,
339
+ }),
321
340
  );
322
341
  }
323
342
 
@@ -403,6 +422,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
403
422
  worldStateSynchronizer: WorldStateSynchronizer,
404
423
  timestamp: bigint,
405
424
  blockNumber: BlockNumber,
425
+ gasLimitOpts: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
406
426
  bindings?: LoggerBindings,
407
427
  ): Promise<TxValidator<TxMetaData>> {
408
428
  await worldStateSynchronizer.syncImmediate();
@@ -419,7 +439,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
419
439
  },
420
440
  };
421
441
  return new AggregateTxValidator<TxMetaData>(
422
- new GasLimitsValidator<TxMetaData>(bindings),
442
+ new GasLimitsValidator<TxMetaData>({ ...gasLimitOpts, bindings }),
423
443
  new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
424
444
  new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
425
445
  new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
@@ -1,5 +1,6 @@
1
+ import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
1
2
  import { getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server';
2
- import { FunctionSelector } from '@aztec/stdlib/abi';
3
+ import { FunctionSelector, getAllFunctionAbis } from '@aztec/stdlib/abi';
3
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
5
  import { type Tx, TxExecutionPhase } from '@aztec/stdlib/tx';
5
6
 
@@ -8,7 +9,10 @@ export type FeePayerBalanceDelta = {
8
9
  claimAmount: bigint;
9
10
  };
10
11
 
11
- const increasePublicBalanceSelectorPromise = FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
12
+ const increasePublicBalanceSelectorPromise = (() => {
13
+ const fn = getAllFunctionAbis(FeeJuiceArtifact).find(f => f.name === '_increase_public_balance')!;
14
+ return FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
15
+ })();
12
16
 
13
17
  export function getTxFeeLimit(tx: Tx): bigint {
14
18
  return tx.data.constants.txContext.gasSettings.getFeeLimit().toBigInt();
@@ -1,4 +1,5 @@
1
1
  import {
2
+ MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
2
3
  MAX_PROCESSABLE_L2_GAS,
3
4
  PRIVATE_TX_L2_GAS_OVERHEAD,
4
5
  PUBLIC_TX_L2_GAS_OVERHEAD,
@@ -49,16 +50,31 @@ export interface HasGasLimitData {
49
50
  */
50
51
  export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidator<T> {
51
52
  #log: Logger;
52
-
53
- constructor(bindings?: LoggerBindings) {
54
- this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
53
+ #effectiveMaxL2Gas: number;
54
+ #effectiveMaxDAGas: number;
55
+ #rollupManaLimit: number;
56
+ #maxBlockL2Gas: number;
57
+ #maxBlockDAGas: number;
58
+
59
+ constructor(opts?: {
60
+ rollupManaLimit?: number;
61
+ maxBlockL2Gas?: number;
62
+ maxBlockDAGas?: number;
63
+ bindings?: LoggerBindings;
64
+ }) {
65
+ this.#log = createLogger('sequencer:tx_validator:tx_gas', opts?.bindings);
66
+ this.#rollupManaLimit = opts?.rollupManaLimit ?? Infinity;
67
+ this.#maxBlockL2Gas = opts?.maxBlockL2Gas ?? Infinity;
68
+ this.#maxBlockDAGas = opts?.maxBlockDAGas ?? Infinity;
69
+ this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS, this.#rollupManaLimit, this.#maxBlockL2Gas);
70
+ this.#effectiveMaxDAGas = Math.min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, this.#maxBlockDAGas);
55
71
  }
56
72
 
57
73
  validateTx(tx: T): Promise<TxValidationResult> {
58
74
  return Promise.resolve(this.validateGasLimit(tx));
59
75
  }
60
76
 
61
- /** Checks gas limits are >= fixed minimums and <= AVM max processable L2 gas. */
77
+ /** Checks gas limits are >= fixed minimums and <= effective max gas (L2 and DA). */
62
78
  validateGasLimit(tx: T): TxValidationResult {
63
79
  const gasLimits = tx.data.constants.txContext.gasSettings.gasLimits;
64
80
  const minGasLimits = new Gas(
@@ -74,10 +90,21 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
74
90
  return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_GAS_LIMIT] };
75
91
  }
76
92
 
77
- if (gasLimits.l2Gas > MAX_PROCESSABLE_L2_GAS) {
78
- this.#log.verbose(`Rejecting transaction due to the gas limit(s) being higher than the maximum processable gas`, {
93
+ if (gasLimits.l2Gas > this.#effectiveMaxL2Gas) {
94
+ this.#log.verbose(`Rejecting transaction due to the L2 gas limit being higher than the effective maximum`, {
79
95
  gasLimits,
80
- minGasLimits,
96
+ effectiveMaxL2Gas: this.#effectiveMaxL2Gas,
97
+ rollupManaLimit: this.#rollupManaLimit,
98
+ maxBlockL2Gas: this.#maxBlockL2Gas,
99
+ });
100
+ return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
101
+ }
102
+
103
+ if (gasLimits.daGas > this.#effectiveMaxDAGas) {
104
+ this.#log.verbose(`Rejecting transaction due to the DA gas limit being higher than the effective maximum`, {
105
+ gasLimits,
106
+ effectiveMaxDAGas: this.#effectiveMaxDAGas,
107
+ maxBlockDAGas: this.#maxBlockDAGas,
81
108
  });
82
109
  return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
83
110
  }
@@ -106,21 +133,27 @@ export class GasTxValidator implements TxValidator<Tx> {
106
133
  #publicDataSource: PublicStateSource;
107
134
  #feeJuiceAddress: AztecAddress;
108
135
  #gasFees: GasFees;
136
+ #gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number };
109
137
 
110
138
  constructor(
111
139
  publicDataSource: PublicStateSource,
112
140
  feeJuiceAddress: AztecAddress,
113
141
  gasFees: GasFees,
114
142
  private bindings?: LoggerBindings,
143
+ opts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
115
144
  ) {
116
145
  this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
117
146
  this.#publicDataSource = publicDataSource;
118
147
  this.#feeJuiceAddress = feeJuiceAddress;
119
148
  this.#gasFees = gasFees;
149
+ this.#gasLimitOpts = opts;
120
150
  }
121
151
 
122
152
  async validateTx(tx: Tx): Promise<TxValidationResult> {
123
- const gasLimitValidation = new GasLimitsValidator(this.bindings).validateGasLimit(tx);
153
+ const gasLimitValidation = new GasLimitsValidator({
154
+ ...this.#gasLimitOpts,
155
+ bindings: this.bindings,
156
+ }).validateGasLimit(tx);
124
157
  if (gasLimitValidation.result === 'invalid') {
125
158
  return Promise.resolve(gasLimitValidation);
126
159
  }
@@ -8,6 +8,7 @@ export * from './gas_validator.js';
8
8
  export * from './phases_validator.js';
9
9
  export * from './test_utils.js';
10
10
  export * from './allowed_public_setup.js';
11
+ export * from './allowed_setup_helpers.js';
11
12
  export * from './archive_cache.js';
12
13
  export * from './tx_permitted_validator.js';
13
14
  export * from './timestamp_validator.js';
@@ -1,11 +1,17 @@
1
+ import { NULL_MSG_SENDER_CONTRACT_ADDRESS } from '@aztec/constants';
1
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
3
  import { PublicContractsDB, getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server';
4
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
3
5
  import type { ContractDataSource } from '@aztec/stdlib/contract';
4
6
  import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
5
7
  import {
6
8
  type PublicCallRequestWithCalldata,
7
9
  TX_ERROR_DURING_VALIDATION,
8
10
  TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED,
11
+ TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT,
12
+ TX_ERROR_SETUP_NULL_MSG_SENDER,
13
+ TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER,
14
+ TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH,
9
15
  Tx,
10
16
  TxExecutionPhase,
11
17
  type TxValidationResult,
@@ -45,7 +51,8 @@ export class PhasesTxValidator implements TxValidator<Tx> {
45
51
 
46
52
  const setupFns = getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.SETUP);
47
53
  for (const setupFn of setupFns) {
48
- if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) {
54
+ const rejectionReason = await this.checkAllowList(setupFn, this.setupAllowList);
55
+ if (rejectionReason) {
49
56
  this.#log.verbose(
50
57
  `Rejecting tx ${tx.getTxHash().toString()} because it calls setup function not on allow list: ${
51
58
  setupFn.request.contractAddress
@@ -53,7 +60,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
53
60
  { allowList: this.setupAllowList },
54
61
  );
55
62
 
56
- return { result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] };
63
+ return { result: 'invalid', reason: [rejectionReason] };
57
64
  }
58
65
  }
59
66
 
@@ -66,53 +73,71 @@ export class PhasesTxValidator implements TxValidator<Tx> {
66
73
  }
67
74
  }
68
75
 
69
- private async isOnAllowList(
76
+ /** Returns a rejection reason if the call is not on the allow list, or undefined if it is allowed. */
77
+ private async checkAllowList(
70
78
  publicCall: PublicCallRequestWithCalldata,
71
79
  allowList: AllowedElement[],
72
- ): Promise<boolean> {
80
+ ): Promise<string | undefined> {
73
81
  if (publicCall.isEmpty()) {
74
- return true;
82
+ return undefined;
75
83
  }
76
84
 
77
85
  const contractAddress = publicCall.request.contractAddress;
78
86
  const functionSelector = publicCall.functionSelector;
79
87
 
80
- // do these checks first since they don't require the contract class
88
+ // Check address-based entries first since they don't require the contract class.
81
89
  for (const entry of allowList) {
82
- if ('address' in entry && !('selector' in entry)) {
83
- if (contractAddress.equals(entry.address)) {
84
- return true;
85
- }
86
- }
87
-
88
- if ('address' in entry && 'selector' in entry) {
90
+ if ('address' in entry) {
89
91
  if (contractAddress.equals(entry.address) && entry.selector.equals(functionSelector)) {
90
- return true;
92
+ if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) {
93
+ return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH;
94
+ }
95
+ if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) {
96
+ return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER;
97
+ }
98
+ if (
99
+ entry.rejectNullMsgSender &&
100
+ publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS))
101
+ ) {
102
+ return TX_ERROR_SETUP_NULL_MSG_SENDER;
103
+ }
104
+ return undefined;
91
105
  }
92
106
  }
107
+ }
93
108
 
94
- const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.timestamp);
95
-
96
- if (!contractClass) {
97
- throw new Error(`Contract not found: ${contractAddress}`);
109
+ // Check class-based entries. Fetch the contract instance lazily (only once).
110
+ let contractClassId: undefined | { value: string | undefined };
111
+ for (const entry of allowList) {
112
+ if (!('classId' in entry)) {
113
+ continue;
98
114
  }
99
115
 
100
- if ('classId' in entry && !('selector' in entry)) {
101
- if (contractClass.currentContractClassId.equals(entry.classId)) {
102
- return true;
116
+ if (contractClassId === undefined) {
117
+ const instance = await this.contractsDB.getContractInstance(contractAddress, this.timestamp);
118
+ contractClassId = { value: instance?.currentContractClassId.toString() };
119
+ if (!contractClassId.value) {
120
+ return TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT;
103
121
  }
104
122
  }
105
123
 
106
- if ('classId' in entry && 'selector' in entry) {
124
+ if (contractClassId.value === entry.classId.toString() && entry.selector.equals(functionSelector)) {
125
+ if (entry.calldataLength !== undefined && publicCall.calldata.length !== entry.calldataLength) {
126
+ return TX_ERROR_SETUP_WRONG_CALLDATA_LENGTH;
127
+ }
128
+ if (entry.onlySelf && !publicCall.request.msgSender.equals(contractAddress)) {
129
+ return TX_ERROR_SETUP_ONLY_SELF_WRONG_SENDER;
130
+ }
107
131
  if (
108
- contractClass.currentContractClassId.equals(entry.classId) &&
109
- (entry.selector === undefined || entry.selector.equals(functionSelector))
132
+ entry.rejectNullMsgSender &&
133
+ publicCall.request.msgSender.equals(AztecAddress.fromBigInt(NULL_MSG_SENDER_CONTRACT_ADDRESS))
110
134
  ) {
111
- return true;
135
+ return TX_ERROR_SETUP_NULL_MSG_SENDER;
112
136
  }
137
+ return undefined;
113
138
  }
114
139
  }
115
140
 
116
- return false;
141
+ return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED;
117
142
  }
118
143
  }
@@ -1,11 +1,11 @@
1
1
  // Taken from lodestar: https://github.com/ChainSafe/lodestar
2
- import { sha256 } from '@aztec/foundation/crypto/sha256';
3
2
  import { createLogger } from '@aztec/foundation/log';
4
3
  import { MAX_TX_SIZE_KB, TopicType, getTopicFromString } from '@aztec/stdlib/p2p';
5
4
 
6
5
  import type { RPC } from '@chainsafe/libp2p-gossipsub/message';
7
6
  import type { DataTransform } from '@chainsafe/libp2p-gossipsub/types';
8
7
  import type { Message } from '@libp2p/interface';
8
+ import { webcrypto } from 'node:crypto';
9
9
  import { compressSync, uncompressSync } from 'snappy';
10
10
  import xxhashFactory from 'xxhash-wasm';
11
11
 
@@ -44,11 +44,10 @@ export function msgIdToStrFn(msgId: Uint8Array): string {
44
44
  * @param message - The libp2p message
45
45
  * @returns The message identifier
46
46
  */
47
- export function getMsgIdFn(message: Message) {
48
- const { topic } = message;
49
-
50
- const vec = [Buffer.from(topic), message.data];
51
- return sha256(Buffer.concat(vec)).subarray(0, 20);
47
+ export async function getMsgIdFn({ topic, data }: Message): Promise<Uint8Array> {
48
+ const buffer = Buffer.concat([Buffer.from(topic), data]);
49
+ const hash = await webcrypto.subtle.digest('SHA-256', buffer);
50
+ return Buffer.from(hash.slice(0, 20));
52
51
  }
53
52
 
54
53
  const DefaultMaxSizesKb: Record<TopicType, number> = {