@aztec/archiver 0.0.1-commit.0c875d939 → 0.0.1-commit.0ec55a70b
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/README.md +12 -6
- package/dest/archiver.d.ts +26 -15
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +161 -153
- package/dest/config.d.ts +5 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -4
- package/dest/errors.d.ts +61 -10
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +88 -14
- package/dest/factory.d.ts +6 -7
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +38 -32
- package/dest/index.d.ts +11 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +10 -2
- package/dest/l1/bin/retrieve-calldata.js +32 -28
- package/dest/l1/calldata_retriever.d.ts +74 -52
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +197 -262
- package/dest/l1/data_retrieval.d.ts +24 -16
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +39 -45
- package/dest/l1/spire_proposer.d.ts +5 -5
- package/dest/l1/spire_proposer.d.ts.map +1 -1
- package/dest/l1/spire_proposer.js +9 -17
- package/dest/l1/validate_historical_logs.d.ts +23 -0
- package/dest/l1/validate_historical_logs.d.ts.map +1 -0
- package/dest/l1/validate_historical_logs.js +108 -0
- package/dest/modules/contract_data_source_adapter.d.ts +25 -0
- package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
- package/dest/modules/contract_data_source_adapter.js +42 -0
- package/dest/modules/data_source_base.d.ts +27 -14
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +98 -125
- package/dest/modules/data_store_updater.d.ts +37 -17
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +155 -112
- package/dest/modules/instrumentation.d.ts +21 -3
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +41 -8
- package/dest/modules/l1_synchronizer.d.ts +13 -11
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +333 -179
- package/dest/modules/validation.d.ts +4 -3
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +6 -6
- package/dest/store/block_store.d.ts +107 -31
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +477 -141
- package/dest/store/contract_class_store.d.ts +17 -4
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +24 -68
- package/dest/store/contract_instance_store.d.ts +28 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +37 -2
- package/dest/store/data_stores.d.ts +68 -0
- package/dest/store/data_stores.d.ts.map +1 -0
- package/dest/store/data_stores.js +50 -0
- package/dest/store/function_names_cache.d.ts +17 -0
- package/dest/store/function_names_cache.d.ts.map +1 -0
- package/dest/store/function_names_cache.js +30 -0
- package/dest/store/l2_tips_cache.d.ts +20 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +109 -0
- package/dest/store/log_store.d.ts +6 -3
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +95 -20
- package/dest/store/message_store.d.ts +5 -1
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +21 -9
- package/dest/test/fake_l1_state.d.ts +25 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +166 -32
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +3 -2
- package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +2 -1
- package/dest/test/mock_l2_block_source.d.ts +35 -5
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +182 -89
- package/dest/test/mock_structs.d.ts +4 -1
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +13 -1
- package/dest/test/noop_l1_archiver.d.ts +7 -4
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +14 -8
- package/package.json +13 -13
- package/src/archiver.ts +199 -174
- package/src/config.ts +23 -2
- package/src/errors.ts +133 -22
- package/src/factory.ts +37 -22
- package/src/index.ts +18 -2
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +40 -27
- package/src/l1/calldata_retriever.ts +261 -383
- package/src/l1/data_retrieval.ts +55 -69
- package/src/l1/spire_proposer.ts +7 -15
- package/src/l1/validate_historical_logs.ts +140 -0
- package/src/modules/contract_data_source_adapter.ts +59 -0
- package/src/modules/data_source_base.ts +142 -144
- package/src/modules/data_store_updater.ts +187 -141
- package/src/modules/instrumentation.ts +56 -9
- package/src/modules/l1_synchronizer.ts +418 -217
- package/src/modules/validation.ts +10 -9
- package/src/store/block_store.ts +587 -177
- package/src/store/contract_class_store.ts +31 -103
- package/src/store/contract_instance_store.ts +51 -5
- package/src/store/data_stores.ts +108 -0
- package/src/store/function_names_cache.ts +37 -0
- package/src/store/l2_tips_cache.ts +134 -0
- package/src/store/log_store.ts +128 -32
- package/src/store/message_store.ts +27 -10
- package/src/structs/inbox_message.ts +1 -1
- package/src/test/fake_l1_state.ts +213 -42
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l1_to_l2_message_source.ts +1 -0
- package/src/test/mock_l2_block_source.ts +230 -82
- package/src/test/mock_structs.ts +20 -6
- package/src/test/noop_l1_archiver.ts +16 -8
- package/dest/store/kv_archiver_store.d.ts +0 -340
- package/dest/store/kv_archiver_store.d.ts.map +0 -1
- package/dest/store/kv_archiver_store.js +0 -446
- package/src/store/kv_archiver_store.ts +0 -639
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { MULTI_CALL_3_ADDRESS, type ViemCommitteeAttestations, type ViemHeader } from '@aztec/ethereum/contracts';
|
|
2
2
|
import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
3
3
|
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
4
|
+
import { LruSet } from '@aztec/foundation/collection';
|
|
4
5
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
6
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
-
import type { ViemSignature } from '@aztec/foundation/eth-signature';
|
|
7
7
|
import type { Logger } from '@aztec/foundation/log';
|
|
8
|
-
import {
|
|
9
|
-
EmpireSlashingProposerAbi,
|
|
10
|
-
GovernanceProposerAbi,
|
|
11
|
-
RollupAbi,
|
|
12
|
-
SlashFactoryAbi,
|
|
13
|
-
TallySlashingProposerAbi,
|
|
14
|
-
} from '@aztec/l1-artifacts';
|
|
8
|
+
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
15
9
|
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
16
|
-
import { ConsensusPayload,
|
|
10
|
+
import { ConsensusPayload, getHashedSignaturePayloadTypedData } from '@aztec/stdlib/p2p';
|
|
17
11
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
18
12
|
|
|
19
13
|
import {
|
|
@@ -30,19 +24,33 @@ import {
|
|
|
30
24
|
|
|
31
25
|
import type { ArchiverInstrumentation } from '../modules/instrumentation.js';
|
|
32
26
|
import { getSuccessfulCallsFromDebug } from './debug_tx.js';
|
|
33
|
-
import {
|
|
27
|
+
import { getCallsFromSpireProposer } from './spire_proposer.js';
|
|
34
28
|
import { getSuccessfulCallsFromTrace } from './trace_tx.js';
|
|
35
29
|
import type { CallInfo } from './types.js';
|
|
36
30
|
|
|
31
|
+
/** Decoded checkpoint data from a propose calldata. */
|
|
32
|
+
type CheckpointData = {
|
|
33
|
+
checkpointNumber: CheckpointNumber;
|
|
34
|
+
archiveRoot: Fr;
|
|
35
|
+
header: CheckpointHeader;
|
|
36
|
+
attestations: CommitteeAttestation[];
|
|
37
|
+
blockHash: string;
|
|
38
|
+
feeAssetPriceModifier: bigint;
|
|
39
|
+
};
|
|
40
|
+
|
|
37
41
|
/**
|
|
38
42
|
* Extracts calldata to the `propose` method of the rollup contract from an L1 transaction
|
|
39
|
-
* in order to reconstruct an L2 block header.
|
|
43
|
+
* in order to reconstruct an L2 block header. Uses hash matching against expected hashes
|
|
44
|
+
* from the CheckpointProposed event to verify the correct propose calldata.
|
|
40
45
|
*/
|
|
41
46
|
export class CalldataRetriever {
|
|
42
|
-
/**
|
|
43
|
-
private readonly
|
|
47
|
+
/** Tx hashes we've already logged for trace+debug failure (log once per tx per process). */
|
|
48
|
+
private static readonly traceFailureWarnedTxHashes = new LruSet<string>(1000);
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/** Clears the trace-failure warned set. For testing only. */
|
|
51
|
+
static resetTraceFailureWarnedForTesting(): void {
|
|
52
|
+
CalldataRetriever.traceFailureWarnedTxHashes.clear();
|
|
53
|
+
}
|
|
46
54
|
|
|
47
55
|
constructor(
|
|
48
56
|
private readonly publicClient: ViemPublicClient,
|
|
@@ -50,15 +58,14 @@ export class CalldataRetriever {
|
|
|
50
58
|
private readonly targetCommitteeSize: number,
|
|
51
59
|
private readonly instrumentation: ArchiverInstrumentation | undefined,
|
|
52
60
|
private readonly logger: Logger,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.validContractCalls = computeValidContractCalls(contractAddresses);
|
|
61
|
+
private readonly rollupAddress: EthAddress,
|
|
62
|
+
) {}
|
|
63
|
+
|
|
64
|
+
private getSignatureContext() {
|
|
65
|
+
return {
|
|
66
|
+
chainId: this.publicClient.chain.id,
|
|
67
|
+
rollupAddress: this.rollupAddress,
|
|
68
|
+
};
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/**
|
|
@@ -67,7 +74,7 @@ export class CalldataRetriever {
|
|
|
67
74
|
* @param txHash - Hash of the tx that published it.
|
|
68
75
|
* @param blobHashes - Blob hashes for the checkpoint.
|
|
69
76
|
* @param checkpointNumber - Checkpoint number.
|
|
70
|
-
* @param expectedHashes -
|
|
77
|
+
* @param expectedHashes - Expected hashes from the CheckpointProposed event for validation
|
|
71
78
|
* @returns Checkpoint header and metadata from the calldata, deserialized
|
|
72
79
|
*/
|
|
73
80
|
async getCheckpointFromRollupTx(
|
|
@@ -75,51 +82,43 @@ export class CalldataRetriever {
|
|
|
75
82
|
_blobHashes: Buffer[],
|
|
76
83
|
checkpointNumber: CheckpointNumber,
|
|
77
84
|
expectedHashes: {
|
|
78
|
-
attestationsHash
|
|
79
|
-
payloadDigest
|
|
85
|
+
attestationsHash: Hex;
|
|
86
|
+
payloadDigest: Hex;
|
|
80
87
|
},
|
|
81
|
-
): Promise<{
|
|
82
|
-
checkpointNumber
|
|
83
|
-
archiveRoot: Fr;
|
|
84
|
-
header: CheckpointHeader;
|
|
85
|
-
attestations: CommitteeAttestation[];
|
|
86
|
-
blockHash: string;
|
|
87
|
-
feeAssetPriceModifier: bigint;
|
|
88
|
-
}> {
|
|
89
|
-
this.logger.trace(`Fetching checkpoint ${checkpointNumber} from rollup tx ${txHash}`, {
|
|
90
|
-
willValidateHashes: !!expectedHashes.attestationsHash || !!expectedHashes.payloadDigest,
|
|
91
|
-
hasAttestationsHash: !!expectedHashes.attestationsHash,
|
|
92
|
-
hasPayloadDigest: !!expectedHashes.payloadDigest,
|
|
93
|
-
});
|
|
88
|
+
): Promise<CheckpointData> {
|
|
89
|
+
this.logger.trace(`Fetching checkpoint ${checkpointNumber} from rollup tx ${txHash}`);
|
|
94
90
|
const tx = await this.publicClient.getTransaction({ hash: txHash });
|
|
95
|
-
|
|
96
|
-
return this.decodeAndBuildCheckpoint(proposeCalldata, tx.blockHash!, checkpointNumber, expectedHashes);
|
|
91
|
+
return this.getCheckpointFromTx(tx, checkpointNumber, expectedHashes);
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
/** Gets
|
|
100
|
-
protected async
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
/** Gets checkpoint data from a transaction by trying decode strategies then falling back to trace. */
|
|
95
|
+
protected async getCheckpointFromTx(
|
|
96
|
+
tx: Transaction,
|
|
97
|
+
checkpointNumber: CheckpointNumber,
|
|
98
|
+
expectedHashes: { attestationsHash: Hex; payloadDigest: Hex },
|
|
99
|
+
): Promise<CheckpointData> {
|
|
100
|
+
// Try to decode as multicall3 with hash-verified matching
|
|
101
|
+
const multicall3Result = this.tryDecodeMulticall3(tx, expectedHashes, checkpointNumber, tx.blockHash!);
|
|
102
|
+
if (multicall3Result) {
|
|
104
103
|
this.logger.trace(`Decoded propose calldata from multicall3 for tx ${tx.hash}`);
|
|
105
104
|
this.instrumentation?.recordBlockProposalTxTarget(tx.to!, false);
|
|
106
|
-
return
|
|
105
|
+
return multicall3Result;
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
// Try to decode as direct propose call
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
109
|
+
const directResult = this.tryDecodeDirectPropose(tx, expectedHashes, checkpointNumber, tx.blockHash!);
|
|
110
|
+
if (directResult) {
|
|
112
111
|
this.logger.trace(`Decoded propose calldata from direct call for tx ${tx.hash}`);
|
|
113
112
|
this.instrumentation?.recordBlockProposalTxTarget(tx.to!, false);
|
|
114
|
-
return
|
|
113
|
+
return directResult;
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
// Try to decode as Spire Proposer multicall wrapper
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
117
|
+
const spireResult = await this.tryDecodeSpireProposer(tx, expectedHashes, checkpointNumber, tx.blockHash!);
|
|
118
|
+
if (spireResult) {
|
|
120
119
|
this.logger.trace(`Decoded propose calldata from Spire Proposer for tx ${tx.hash}`);
|
|
121
120
|
this.instrumentation?.recordBlockProposalTxTarget(tx.to!, false);
|
|
122
|
-
return
|
|
121
|
+
return spireResult;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
// Fall back to trace-based extraction
|
|
@@ -127,52 +126,82 @@ export class CalldataRetriever {
|
|
|
127
126
|
`Failed to decode multicall3, direct propose, or Spire proposer for L1 tx ${tx.hash}, falling back to trace for checkpoint ${checkpointNumber}`,
|
|
128
127
|
);
|
|
129
128
|
this.instrumentation?.recordBlockProposalTxTarget(tx.to ?? EthAddress.ZERO.toString(), true);
|
|
130
|
-
|
|
129
|
+
const tracedCalldata = await this.extractCalldataViaTrace(tx.hash);
|
|
130
|
+
const tracedResult = this.tryDecodeAndVerifyPropose(
|
|
131
|
+
tracedCalldata,
|
|
132
|
+
expectedHashes,
|
|
133
|
+
checkpointNumber,
|
|
134
|
+
tx.blockHash!,
|
|
135
|
+
);
|
|
136
|
+
if (!tracedResult) {
|
|
137
|
+
throw new Error(`Hash mismatch for traced propose calldata in tx ${tx.hash} for checkpoint ${checkpointNumber}`);
|
|
138
|
+
}
|
|
139
|
+
return tracedResult;
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
/**
|
|
134
143
|
* Attempts to decode a transaction as a Spire Proposer multicall wrapper.
|
|
135
|
-
* If successful,
|
|
144
|
+
* If successful, iterates all wrapped calls and validates each as either multicall3
|
|
145
|
+
* or direct propose, verifying against expected hashes.
|
|
136
146
|
* @param tx - The transaction to decode
|
|
137
|
-
* @
|
|
147
|
+
* @param expectedHashes - Expected hashes for hash-verified matching
|
|
148
|
+
* @param checkpointNumber - The checkpoint number
|
|
149
|
+
* @param blockHash - The L1 block hash
|
|
150
|
+
* @returns The checkpoint data if successfully decoded and validated, undefined otherwise
|
|
138
151
|
*/
|
|
139
|
-
protected async tryDecodeSpireProposer(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
protected async tryDecodeSpireProposer(
|
|
153
|
+
tx: Transaction,
|
|
154
|
+
expectedHashes: { attestationsHash: Hex; payloadDigest: Hex },
|
|
155
|
+
checkpointNumber: CheckpointNumber,
|
|
156
|
+
blockHash: Hex,
|
|
157
|
+
): Promise<CheckpointData | undefined> {
|
|
158
|
+
// Try to decode as Spire Proposer multicall (extracts all wrapped calls)
|
|
159
|
+
const spireWrappedCalls = await getCallsFromSpireProposer(tx, this.publicClient, this.logger);
|
|
160
|
+
if (!spireWrappedCalls) {
|
|
143
161
|
return undefined;
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
this.logger.trace(`Decoded Spire Proposer wrapping for tx ${tx.hash},
|
|
164
|
+
this.logger.trace(`Decoded Spire Proposer wrapping for tx ${tx.hash}, ${spireWrappedCalls.length} inner call(s)`);
|
|
147
165
|
|
|
148
|
-
//
|
|
149
|
-
const
|
|
166
|
+
// Try each wrapped call as either multicall3 or direct propose
|
|
167
|
+
for (const spireWrappedCall of spireWrappedCalls) {
|
|
168
|
+
const wrappedTx = { to: spireWrappedCall.to, input: spireWrappedCall.data, hash: tx.hash };
|
|
150
169
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
170
|
+
const multicall3Result = this.tryDecodeMulticall3(wrappedTx, expectedHashes, checkpointNumber, blockHash);
|
|
171
|
+
if (multicall3Result) {
|
|
172
|
+
this.logger.trace(`Decoded propose calldata from Spire Proposer to multicall3 for tx ${tx.hash}`);
|
|
173
|
+
return multicall3Result;
|
|
174
|
+
}
|
|
156
175
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
const directResult = this.tryDecodeDirectPropose(wrappedTx, expectedHashes, checkpointNumber, blockHash);
|
|
177
|
+
if (directResult) {
|
|
178
|
+
this.logger.trace(`Decoded propose calldata from Spire Proposer to direct propose for tx ${tx.hash}`);
|
|
179
|
+
return directResult;
|
|
180
|
+
}
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
this.logger.warn(
|
|
164
|
-
`Spire Proposer wrapped
|
|
184
|
+
`Spire Proposer wrapped calls could not be decoded as multicall3 or direct propose for tx ${tx.hash}`,
|
|
165
185
|
);
|
|
166
186
|
return undefined;
|
|
167
187
|
}
|
|
168
188
|
|
|
169
189
|
/**
|
|
170
190
|
* Attempts to decode transaction input as multicall3 and extract propose calldata.
|
|
171
|
-
*
|
|
191
|
+
* Finds all calls matching the rollup address and propose selector, then decodes
|
|
192
|
+
* and verifies each candidate against expected hashes from the CheckpointProposed event.
|
|
172
193
|
* @param tx - The transaction-like object with to, input, and hash
|
|
173
|
-
* @
|
|
194
|
+
* @param expectedHashes - Expected hashes from CheckpointProposed event
|
|
195
|
+
* @param checkpointNumber - The checkpoint number
|
|
196
|
+
* @param blockHash - The L1 block hash
|
|
197
|
+
* @returns The checkpoint data if successfully validated, undefined otherwise
|
|
174
198
|
*/
|
|
175
|
-
protected tryDecodeMulticall3(
|
|
199
|
+
protected tryDecodeMulticall3(
|
|
200
|
+
tx: { to: Hex | null | undefined; input: Hex; hash: Hex },
|
|
201
|
+
expectedHashes: { attestationsHash: Hex; payloadDigest: Hex },
|
|
202
|
+
checkpointNumber: CheckpointNumber,
|
|
203
|
+
blockHash: Hex,
|
|
204
|
+
): CheckpointData | undefined {
|
|
176
205
|
const txHash = tx.hash;
|
|
177
206
|
|
|
178
207
|
try {
|
|
@@ -201,59 +230,54 @@ export class CalldataRetriever {
|
|
|
201
230
|
|
|
202
231
|
const [calls] = multicall3Args;
|
|
203
232
|
|
|
204
|
-
//
|
|
233
|
+
// Find all calls matching rollup address + propose selector
|
|
205
234
|
const rollupAddressLower = this.rollupAddress.toString().toLowerCase();
|
|
206
|
-
const
|
|
235
|
+
const proposeSelectorLower = PROPOSE_SELECTOR.toLowerCase();
|
|
236
|
+
const candidates: Hex[] = [];
|
|
207
237
|
|
|
208
|
-
for (
|
|
209
|
-
const addr =
|
|
210
|
-
const callData =
|
|
238
|
+
for (const call of calls) {
|
|
239
|
+
const addr = call.target.toLowerCase();
|
|
240
|
+
const callData = call.callData;
|
|
211
241
|
|
|
212
|
-
// Extract function selector (first 4 bytes)
|
|
213
242
|
if (callData.length < 10) {
|
|
214
|
-
|
|
215
|
-
this.logger.warn(`Invalid calldata length at index ${i} (${callData.length})`, { txHash });
|
|
216
|
-
return undefined;
|
|
243
|
+
continue;
|
|
217
244
|
}
|
|
218
|
-
const functionSelector = callData.slice(0, 10) as Hex;
|
|
219
|
-
|
|
220
|
-
// Validate this call is allowed by searching through valid calls
|
|
221
|
-
const validCall = this.validContractCalls.find(
|
|
222
|
-
vc => vc.address === addr && vc.functionSelector === functionSelector,
|
|
223
|
-
);
|
|
224
245
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
targetAddress: addr,
|
|
229
|
-
functionSelector,
|
|
230
|
-
validCalls: this.validContractCalls.map(c => ({ address: c.address, selector: c.functionSelector })),
|
|
231
|
-
txHash,
|
|
232
|
-
});
|
|
233
|
-
return undefined;
|
|
246
|
+
const selector = callData.slice(0, 10).toLowerCase();
|
|
247
|
+
if (addr === rollupAddressLower && selector === proposeSelectorLower) {
|
|
248
|
+
candidates.push(callData);
|
|
234
249
|
}
|
|
250
|
+
}
|
|
235
251
|
|
|
236
|
-
|
|
252
|
+
if (candidates.length === 0) {
|
|
253
|
+
this.logger.debug(`No propose candidates found in multicall3`, { txHash });
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
237
256
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
// Decode, verify, and build for each candidate
|
|
258
|
+
const verified: CheckpointData[] = [];
|
|
259
|
+
for (const candidate of candidates) {
|
|
260
|
+
const result = this.tryDecodeAndVerifyPropose(candidate, expectedHashes, checkpointNumber, blockHash);
|
|
261
|
+
if (result) {
|
|
262
|
+
verified.push(result);
|
|
241
263
|
}
|
|
242
264
|
}
|
|
243
265
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return undefined;
|
|
266
|
+
if (verified.length === 1) {
|
|
267
|
+
this.logger.trace(`Verified single propose candidate via hash matching`, { txHash });
|
|
268
|
+
return verified[0];
|
|
248
269
|
}
|
|
249
270
|
|
|
250
|
-
if (
|
|
251
|
-
this.logger.warn(
|
|
252
|
-
|
|
271
|
+
if (verified.length > 1) {
|
|
272
|
+
this.logger.warn(
|
|
273
|
+
`Multiple propose candidates verified (${verified.length}), returning first (identical data)`,
|
|
274
|
+
{ txHash },
|
|
275
|
+
);
|
|
276
|
+
return verified[0];
|
|
253
277
|
}
|
|
254
278
|
|
|
255
|
-
|
|
256
|
-
return
|
|
279
|
+
this.logger.debug(`No candidates verified against expected hashes`, { txHash });
|
|
280
|
+
return undefined;
|
|
257
281
|
} catch (err) {
|
|
258
282
|
// Any decoding error triggers fallback to trace
|
|
259
283
|
this.logger.warn(`Failed to decode multicall3: ${err}`, { txHash });
|
|
@@ -263,11 +287,19 @@ export class CalldataRetriever {
|
|
|
263
287
|
|
|
264
288
|
/**
|
|
265
289
|
* Attempts to decode transaction as a direct propose call to the rollup contract.
|
|
266
|
-
*
|
|
290
|
+
* Decodes, verifies hashes, and builds checkpoint data in a single pass.
|
|
267
291
|
* @param tx - The transaction-like object with to, input, and hash
|
|
268
|
-
* @
|
|
292
|
+
* @param expectedHashes - Expected hashes from CheckpointProposed event
|
|
293
|
+
* @param checkpointNumber - The checkpoint number
|
|
294
|
+
* @param blockHash - The L1 block hash
|
|
295
|
+
* @returns The checkpoint data if successfully validated, undefined otherwise
|
|
269
296
|
*/
|
|
270
|
-
protected tryDecodeDirectPropose(
|
|
297
|
+
protected tryDecodeDirectPropose(
|
|
298
|
+
tx: { to: Hex | null | undefined; input: Hex; hash: Hex },
|
|
299
|
+
expectedHashes: { attestationsHash: Hex; payloadDigest: Hex },
|
|
300
|
+
checkpointNumber: CheckpointNumber,
|
|
301
|
+
blockHash: Hex,
|
|
302
|
+
): CheckpointData | undefined {
|
|
271
303
|
const txHash = tx.hash;
|
|
272
304
|
try {
|
|
273
305
|
// Check if transaction is to the rollup address
|
|
@@ -276,18 +308,16 @@ export class CalldataRetriever {
|
|
|
276
308
|
return undefined;
|
|
277
309
|
}
|
|
278
310
|
|
|
279
|
-
//
|
|
311
|
+
// Validate it's a propose call before full decode+verify
|
|
280
312
|
const { functionName } = decodeFunctionData({ abi: RollupAbi, data: tx.input });
|
|
281
|
-
|
|
282
|
-
// If not propose, return undefined
|
|
283
313
|
if (functionName !== 'propose') {
|
|
284
314
|
this.logger.warn(`Transaction to rollup is not propose (got ${functionName})`, { txHash });
|
|
285
315
|
return undefined;
|
|
286
316
|
}
|
|
287
317
|
|
|
288
|
-
//
|
|
318
|
+
// Decode, verify hashes, and build checkpoint data
|
|
289
319
|
this.logger.trace(`Validated direct propose call to rollup`, { txHash });
|
|
290
|
-
return tx.input;
|
|
320
|
+
return this.tryDecodeAndVerifyPropose(tx.input, expectedHashes, checkpointNumber, blockHash);
|
|
291
321
|
} catch (err) {
|
|
292
322
|
// Any decoding error means it's not a valid propose call
|
|
293
323
|
this.logger.warn(`Failed to decode as direct propose: ${err}`, { txHash });
|
|
@@ -314,7 +344,8 @@ export class CalldataRetriever {
|
|
|
314
344
|
this.logger.debug(`Successfully traced using trace_transaction, found ${calls.length} calls`);
|
|
315
345
|
} catch (err) {
|
|
316
346
|
const traceError = err instanceof Error ? err : new Error(String(err));
|
|
317
|
-
this.logger.verbose(`Failed trace_transaction for ${txHash}
|
|
347
|
+
this.logger.verbose(`Failed trace_transaction for ${txHash}: ${traceError.message}`);
|
|
348
|
+
this.logger.debug(`Trace failure details for ${txHash}`, { traceError });
|
|
318
349
|
|
|
319
350
|
try {
|
|
320
351
|
// Fall back to debug_traceTransaction (Geth RPC)
|
|
@@ -323,7 +354,16 @@ export class CalldataRetriever {
|
|
|
323
354
|
this.logger.debug(`Successfully traced using debug_traceTransaction, found ${calls.length} calls`);
|
|
324
355
|
} catch (debugErr) {
|
|
325
356
|
const debugError = debugErr instanceof Error ? debugErr : new Error(String(debugErr));
|
|
326
|
-
|
|
357
|
+
// Log once per tx so we don't spam on every sync cycle when sync point doesn't advance
|
|
358
|
+
if (!CalldataRetriever.traceFailureWarnedTxHashes.has(txHash)) {
|
|
359
|
+
CalldataRetriever.traceFailureWarnedTxHashes.add(txHash);
|
|
360
|
+
this.logger.warn(
|
|
361
|
+
`Cannot decode L1 tx ${txHash}: trace and debug RPC failed or unavailable. ` +
|
|
362
|
+
`trace_transaction: ${traceError.message}; debug_traceTransaction: ${debugError.message}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
// Full error objects can be very long; keep at debug only
|
|
366
|
+
this.logger.debug(`Trace/debug failure details for tx ${txHash}`, {
|
|
327
367
|
traceError,
|
|
328
368
|
debugError,
|
|
329
369
|
txHash,
|
|
@@ -345,10 +385,106 @@ export class CalldataRetriever {
|
|
|
345
385
|
return calls[0].input;
|
|
346
386
|
}
|
|
347
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Decodes propose calldata, verifies against expected hashes, and builds checkpoint data.
|
|
390
|
+
* Returns undefined on decode errors or hash mismatches (soft failure for try-based callers).
|
|
391
|
+
* @param proposeCalldata - The propose function calldata
|
|
392
|
+
* @param expectedHashes - Expected hashes from the CheckpointProposed event
|
|
393
|
+
* @param checkpointNumber - The checkpoint number
|
|
394
|
+
* @param blockHash - The L1 block hash
|
|
395
|
+
* @returns The decoded checkpoint data, or undefined on failure
|
|
396
|
+
*/
|
|
397
|
+
protected tryDecodeAndVerifyPropose(
|
|
398
|
+
proposeCalldata: Hex,
|
|
399
|
+
expectedHashes: { attestationsHash: Hex; payloadDigest: Hex },
|
|
400
|
+
checkpointNumber: CheckpointNumber,
|
|
401
|
+
blockHash: Hex,
|
|
402
|
+
): CheckpointData | undefined {
|
|
403
|
+
try {
|
|
404
|
+
const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data: proposeCalldata });
|
|
405
|
+
if (functionName !== 'propose') {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const [decodedArgs, packedAttestations] = args! as readonly [
|
|
410
|
+
{ archive: Hex; oracleInput: { feeAssetPriceModifier: bigint }; header: ViemHeader },
|
|
411
|
+
ViemCommitteeAttestations,
|
|
412
|
+
...unknown[],
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
// Verify attestationsHash
|
|
416
|
+
const computedAttestationsHash = this.computeAttestationsHash(packedAttestations);
|
|
417
|
+
if (
|
|
418
|
+
!Buffer.from(hexToBytes(computedAttestationsHash)).equals(
|
|
419
|
+
Buffer.from(hexToBytes(expectedHashes.attestationsHash)),
|
|
420
|
+
)
|
|
421
|
+
) {
|
|
422
|
+
this.logger.warn(`Attestations hash mismatch during verification`, {
|
|
423
|
+
computed: computedAttestationsHash,
|
|
424
|
+
expected: expectedHashes.attestationsHash,
|
|
425
|
+
});
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Verify payloadDigest
|
|
430
|
+
const header = CheckpointHeader.fromViem(decodedArgs.header);
|
|
431
|
+
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
|
|
432
|
+
const feeAssetPriceModifier = decodedArgs.oracleInput.feeAssetPriceModifier;
|
|
433
|
+
const computedPayloadDigest = this.computePayloadDigest(header, archiveRoot, feeAssetPriceModifier);
|
|
434
|
+
if (
|
|
435
|
+
!Buffer.from(hexToBytes(computedPayloadDigest)).equals(Buffer.from(hexToBytes(expectedHashes.payloadDigest)))
|
|
436
|
+
) {
|
|
437
|
+
this.logger.warn(`Payload digest mismatch during verification`, {
|
|
438
|
+
computed: computedPayloadDigest,
|
|
439
|
+
expected: expectedHashes.payloadDigest,
|
|
440
|
+
});
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const attestations = CommitteeAttestation.fromPacked(packedAttestations, this.targetCommitteeSize);
|
|
445
|
+
|
|
446
|
+
this.logger.trace(`Validated and decoded propose calldata for checkpoint ${checkpointNumber}`, {
|
|
447
|
+
checkpointNumber,
|
|
448
|
+
archive: decodedArgs.archive,
|
|
449
|
+
header: decodedArgs.header,
|
|
450
|
+
l1BlockHash: blockHash,
|
|
451
|
+
attestations,
|
|
452
|
+
packedAttestations,
|
|
453
|
+
targetCommitteeSize: this.targetCommitteeSize,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
checkpointNumber,
|
|
458
|
+
archiveRoot,
|
|
459
|
+
header,
|
|
460
|
+
attestations,
|
|
461
|
+
blockHash,
|
|
462
|
+
feeAssetPriceModifier,
|
|
463
|
+
};
|
|
464
|
+
} catch {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Computes the keccak256 hash of ABI-encoded CommitteeAttestations. */
|
|
470
|
+
private computeAttestationsHash(packedAttestations: ViemCommitteeAttestations): Hex {
|
|
471
|
+
return keccak256(encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [packedAttestations]));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Computes the keccak256 payload digest from the checkpoint header, archive root, and fee asset price modifier. */
|
|
475
|
+
private computePayloadDigest(header: CheckpointHeader, archiveRoot: Fr, feeAssetPriceModifier: bigint): Hex {
|
|
476
|
+
const consensusPayload = new ConsensusPayload(
|
|
477
|
+
header,
|
|
478
|
+
archiveRoot,
|
|
479
|
+
feeAssetPriceModifier,
|
|
480
|
+
this.getSignatureContext(),
|
|
481
|
+
);
|
|
482
|
+
return getHashedSignaturePayloadTypedData(consensusPayload).toString();
|
|
483
|
+
}
|
|
484
|
+
|
|
348
485
|
/**
|
|
349
486
|
* Extracts the CommitteeAttestations struct definition from RollupAbi.
|
|
350
487
|
* Finds the _attestations parameter by name in the propose function.
|
|
351
|
-
* Lazy-loaded to avoid issues during module initialization.
|
|
352
488
|
*/
|
|
353
489
|
private getCommitteeAttestationsStructDef(): AbiParameter {
|
|
354
490
|
const proposeFunction = RollupAbi.find(item => item.type === 'function' && item.name === 'propose') as
|
|
@@ -381,265 +517,7 @@ export class CalldataRetriever {
|
|
|
381
517
|
components: tupleParam.components || [],
|
|
382
518
|
} as AbiParameter;
|
|
383
519
|
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Decodes propose calldata and builds the checkpoint header structure.
|
|
387
|
-
* @param proposeCalldata - The propose function calldata
|
|
388
|
-
* @param blockHash - The L1 block hash containing this transaction
|
|
389
|
-
* @param checkpointNumber - The checkpoint number
|
|
390
|
-
* @param expectedHashes - Optional expected hashes from the CheckpointProposed event for validation
|
|
391
|
-
* @returns The decoded checkpoint header and metadata
|
|
392
|
-
*/
|
|
393
|
-
protected decodeAndBuildCheckpoint(
|
|
394
|
-
proposeCalldata: Hex,
|
|
395
|
-
blockHash: Hex,
|
|
396
|
-
checkpointNumber: CheckpointNumber,
|
|
397
|
-
expectedHashes: {
|
|
398
|
-
attestationsHash?: Hex;
|
|
399
|
-
payloadDigest?: Hex;
|
|
400
|
-
},
|
|
401
|
-
): {
|
|
402
|
-
checkpointNumber: CheckpointNumber;
|
|
403
|
-
archiveRoot: Fr;
|
|
404
|
-
header: CheckpointHeader;
|
|
405
|
-
attestations: CommitteeAttestation[];
|
|
406
|
-
blockHash: string;
|
|
407
|
-
feeAssetPriceModifier: bigint;
|
|
408
|
-
} {
|
|
409
|
-
const { functionName: rollupFunctionName, args: rollupArgs } = decodeFunctionData({
|
|
410
|
-
abi: RollupAbi,
|
|
411
|
-
data: proposeCalldata,
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
if (rollupFunctionName !== 'propose') {
|
|
415
|
-
throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const [decodedArgs, packedAttestations, _signers, _attestationsAndSignersSignature, _blobInput] =
|
|
419
|
-
rollupArgs! as readonly [
|
|
420
|
-
{
|
|
421
|
-
archive: Hex;
|
|
422
|
-
oracleInput: { feeAssetPriceModifier: bigint };
|
|
423
|
-
header: ViemHeader;
|
|
424
|
-
},
|
|
425
|
-
ViemCommitteeAttestations,
|
|
426
|
-
Hex[],
|
|
427
|
-
ViemSignature,
|
|
428
|
-
Hex,
|
|
429
|
-
];
|
|
430
|
-
|
|
431
|
-
const attestations = CommitteeAttestation.fromPacked(packedAttestations, this.targetCommitteeSize);
|
|
432
|
-
const header = CheckpointHeader.fromViem(decodedArgs.header);
|
|
433
|
-
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
|
|
434
|
-
|
|
435
|
-
// Validate attestationsHash if provided (skip for backwards compatibility with older events)
|
|
436
|
-
if (expectedHashes.attestationsHash) {
|
|
437
|
-
// Compute attestationsHash: keccak256(abi.encode(CommitteeAttestations))
|
|
438
|
-
const computedAttestationsHash = keccak256(
|
|
439
|
-
encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [packedAttestations]),
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
// Compare as buffers to avoid case-sensitivity and string comparison issues
|
|
443
|
-
const computedBuffer = Buffer.from(hexToBytes(computedAttestationsHash));
|
|
444
|
-
const expectedBuffer = Buffer.from(hexToBytes(expectedHashes.attestationsHash));
|
|
445
|
-
|
|
446
|
-
if (!computedBuffer.equals(expectedBuffer)) {
|
|
447
|
-
throw new Error(
|
|
448
|
-
`Attestations hash mismatch for checkpoint ${checkpointNumber}: ` +
|
|
449
|
-
`computed=${computedAttestationsHash}, expected=${expectedHashes.attestationsHash}`,
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
this.logger.trace(`Validated attestationsHash for checkpoint ${checkpointNumber}`, {
|
|
454
|
-
computedAttestationsHash,
|
|
455
|
-
expectedAttestationsHash: expectedHashes.attestationsHash,
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Validate payloadDigest if provided (skip for backwards compatibility with older events)
|
|
460
|
-
if (expectedHashes.payloadDigest) {
|
|
461
|
-
// Use ConsensusPayload to compute the digest - this ensures we match the exact logic
|
|
462
|
-
// used by the network for signing and verification
|
|
463
|
-
const feeAssetPriceModifier = decodedArgs.oracleInput.feeAssetPriceModifier;
|
|
464
|
-
const consensusPayload = new ConsensusPayload(header, archiveRoot, feeAssetPriceModifier);
|
|
465
|
-
const payloadToSign = consensusPayload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation);
|
|
466
|
-
const computedPayloadDigest = keccak256(payloadToSign);
|
|
467
|
-
|
|
468
|
-
// Compare as buffers to avoid case-sensitivity and string comparison issues
|
|
469
|
-
const computedBuffer = Buffer.from(hexToBytes(computedPayloadDigest));
|
|
470
|
-
const expectedBuffer = Buffer.from(hexToBytes(expectedHashes.payloadDigest));
|
|
471
|
-
|
|
472
|
-
if (!computedBuffer.equals(expectedBuffer)) {
|
|
473
|
-
throw new Error(
|
|
474
|
-
`Payload digest mismatch for checkpoint ${checkpointNumber}: ` +
|
|
475
|
-
`computed=${computedPayloadDigest}, expected=${expectedHashes.payloadDigest}`,
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
this.logger.trace(`Validated payloadDigest for checkpoint ${checkpointNumber}`, {
|
|
480
|
-
computedPayloadDigest,
|
|
481
|
-
expectedPayloadDigest: expectedHashes.payloadDigest,
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
this.logger.trace(`Decoded propose calldata`, {
|
|
486
|
-
checkpointNumber,
|
|
487
|
-
archive: decodedArgs.archive,
|
|
488
|
-
header: decodedArgs.header,
|
|
489
|
-
l1BlockHash: blockHash,
|
|
490
|
-
attestations,
|
|
491
|
-
packedAttestations,
|
|
492
|
-
targetCommitteeSize: this.targetCommitteeSize,
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
return {
|
|
496
|
-
checkpointNumber,
|
|
497
|
-
archiveRoot,
|
|
498
|
-
header,
|
|
499
|
-
attestations,
|
|
500
|
-
blockHash,
|
|
501
|
-
feeAssetPriceModifier: decodedArgs.oracleInput.feeAssetPriceModifier,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
520
|
}
|
|
505
521
|
|
|
506
|
-
/**
|
|
507
|
-
* Pre-computed function selectors for all valid contract calls.
|
|
508
|
-
* These are computed once at module load time from the ABIs.
|
|
509
|
-
* Based on analysis of sequencer-client/src/publisher/sequencer-publisher.ts
|
|
510
|
-
*/
|
|
511
|
-
|
|
512
|
-
// Rollup contract function selectors (always valid)
|
|
522
|
+
/** Function selector for the `propose` method of the rollup contract. */
|
|
513
523
|
const PROPOSE_SELECTOR = toFunctionSelector(RollupAbi.find(x => x.type === 'function' && x.name === 'propose')!);
|
|
514
|
-
const INVALIDATE_BAD_ATTESTATION_SELECTOR = toFunctionSelector(
|
|
515
|
-
RollupAbi.find(x => x.type === 'function' && x.name === 'invalidateBadAttestation')!,
|
|
516
|
-
);
|
|
517
|
-
const INVALIDATE_INSUFFICIENT_ATTESTATIONS_SELECTOR = toFunctionSelector(
|
|
518
|
-
RollupAbi.find(x => x.type === 'function' && x.name === 'invalidateInsufficientAttestations')!,
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// Governance proposer function selectors
|
|
522
|
-
const GOVERNANCE_SIGNAL_WITH_SIG_SELECTOR = toFunctionSelector(
|
|
523
|
-
GovernanceProposerAbi.find(x => x.type === 'function' && x.name === 'signalWithSig')!,
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
// Slash factory function selectors
|
|
527
|
-
const CREATE_SLASH_PAYLOAD_SELECTOR = toFunctionSelector(
|
|
528
|
-
SlashFactoryAbi.find(x => x.type === 'function' && x.name === 'createSlashPayload')!,
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
// Empire slashing proposer function selectors
|
|
532
|
-
const EMPIRE_SIGNAL_WITH_SIG_SELECTOR = toFunctionSelector(
|
|
533
|
-
EmpireSlashingProposerAbi.find(x => x.type === 'function' && x.name === 'signalWithSig')!,
|
|
534
|
-
);
|
|
535
|
-
const EMPIRE_SUBMIT_ROUND_WINNER_SELECTOR = toFunctionSelector(
|
|
536
|
-
EmpireSlashingProposerAbi.find(x => x.type === 'function' && x.name === 'submitRoundWinner')!,
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
// Tally slashing proposer function selectors
|
|
540
|
-
const TALLY_VOTE_SELECTOR = toFunctionSelector(
|
|
541
|
-
TallySlashingProposerAbi.find(x => x.type === 'function' && x.name === 'vote')!,
|
|
542
|
-
);
|
|
543
|
-
const TALLY_EXECUTE_ROUND_SELECTOR = toFunctionSelector(
|
|
544
|
-
TallySlashingProposerAbi.find(x => x.type === 'function' && x.name === 'executeRound')!,
|
|
545
|
-
);
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Defines a valid contract call that can appear in a sequencer publisher transaction
|
|
549
|
-
*/
|
|
550
|
-
interface ValidContractCall {
|
|
551
|
-
/** Contract address (lowercase for comparison) */
|
|
552
|
-
address: string;
|
|
553
|
-
/** Function selector (4 bytes) */
|
|
554
|
-
functionSelector: Hex;
|
|
555
|
-
/** Human-readable function name for logging */
|
|
556
|
-
functionName: string;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* All valid contract calls that the sequencer publisher can make.
|
|
561
|
-
* Builds the list of valid (address, selector) pairs for validation.
|
|
562
|
-
*
|
|
563
|
-
* Alternatively, if we are absolutely sure that no code path from any of these
|
|
564
|
-
* contracts can eventually land on another call to `propose`, we can remove the
|
|
565
|
-
* function selectors.
|
|
566
|
-
*/
|
|
567
|
-
function computeValidContractCalls(addresses: {
|
|
568
|
-
rollupAddress: EthAddress;
|
|
569
|
-
governanceProposerAddress?: EthAddress;
|
|
570
|
-
slashFactoryAddress?: EthAddress;
|
|
571
|
-
slashingProposerAddress?: EthAddress;
|
|
572
|
-
}): ValidContractCall[] {
|
|
573
|
-
const { rollupAddress, governanceProposerAddress, slashFactoryAddress, slashingProposerAddress } = addresses;
|
|
574
|
-
const calls: ValidContractCall[] = [];
|
|
575
|
-
|
|
576
|
-
// Rollup contract calls (always present)
|
|
577
|
-
calls.push(
|
|
578
|
-
{
|
|
579
|
-
address: rollupAddress.toString().toLowerCase(),
|
|
580
|
-
functionSelector: PROPOSE_SELECTOR,
|
|
581
|
-
functionName: 'propose',
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
address: rollupAddress.toString().toLowerCase(),
|
|
585
|
-
functionSelector: INVALIDATE_BAD_ATTESTATION_SELECTOR,
|
|
586
|
-
functionName: 'invalidateBadAttestation',
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
address: rollupAddress.toString().toLowerCase(),
|
|
590
|
-
functionSelector: INVALIDATE_INSUFFICIENT_ATTESTATIONS_SELECTOR,
|
|
591
|
-
functionName: 'invalidateInsufficientAttestations',
|
|
592
|
-
},
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
// Governance proposer calls (optional)
|
|
596
|
-
if (governanceProposerAddress && !governanceProposerAddress.isZero()) {
|
|
597
|
-
calls.push({
|
|
598
|
-
address: governanceProposerAddress.toString().toLowerCase(),
|
|
599
|
-
functionSelector: GOVERNANCE_SIGNAL_WITH_SIG_SELECTOR,
|
|
600
|
-
functionName: 'signalWithSig',
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Slash factory calls (optional)
|
|
605
|
-
if (slashFactoryAddress && !slashFactoryAddress.isZero()) {
|
|
606
|
-
calls.push({
|
|
607
|
-
address: slashFactoryAddress.toString().toLowerCase(),
|
|
608
|
-
functionSelector: CREATE_SLASH_PAYLOAD_SELECTOR,
|
|
609
|
-
functionName: 'createSlashPayload',
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Slashing proposer calls (optional, can be either Empire or Tally)
|
|
614
|
-
if (slashingProposerAddress && !slashingProposerAddress.isZero()) {
|
|
615
|
-
// Empire calls
|
|
616
|
-
calls.push(
|
|
617
|
-
{
|
|
618
|
-
address: slashingProposerAddress.toString().toLowerCase(),
|
|
619
|
-
functionSelector: EMPIRE_SIGNAL_WITH_SIG_SELECTOR,
|
|
620
|
-
functionName: 'signalWithSig (empire)',
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
address: slashingProposerAddress.toString().toLowerCase(),
|
|
624
|
-
functionSelector: EMPIRE_SUBMIT_ROUND_WINNER_SELECTOR,
|
|
625
|
-
functionName: 'submitRoundWinner',
|
|
626
|
-
},
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
// Tally calls
|
|
630
|
-
calls.push(
|
|
631
|
-
{
|
|
632
|
-
address: slashingProposerAddress.toString().toLowerCase(),
|
|
633
|
-
functionSelector: TALLY_VOTE_SELECTOR,
|
|
634
|
-
functionName: 'vote',
|
|
635
|
-
},
|
|
636
|
-
{
|
|
637
|
-
address: slashingProposerAddress.toString().toLowerCase(),
|
|
638
|
-
functionSelector: TALLY_EXECUTE_ROUND_SELECTOR,
|
|
639
|
-
functionName: 'executeRound',
|
|
640
|
-
},
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
return calls;
|
|
645
|
-
}
|