Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m18s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 20s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 40s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 21s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m11s
Validation / validate-documentation (push) Failing after 14s
Verify Deployment / Verify Deployment (push) Failing after 45s
Ship AddressActivityRegistry V1/V2, ISO20022IntakeGateway, Chain138ParticipantSurface, checkpoint hub contracts, checkpoint-core package, aggregator/indexer/sdk services, relay profile guards, M00 diamond bridge facet, and OMNL compliance contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
373 lines
18 KiB
JavaScript
373 lines
18 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const ethers_1 = require("ethers");
|
|
const config_1 = require("./config");
|
|
const eip712_1 = require("./eip712");
|
|
const ipfs_1 = require("./ipfs");
|
|
const blockscout_1 = require("./blockscout");
|
|
const usdEnrich_1 = require("./usdEnrich");
|
|
const scanState_1 = require("./scanState");
|
|
const checkpoint_core_1 = require("@dbis/checkpoint-core");
|
|
const leafCodec_1 = require("./leafCodec");
|
|
const adminIngress_1 = require("./ingress/adminIngress");
|
|
const dualMirror_1 = require("./dualMirror");
|
|
const activityRegistry_1 = require("./activityRegistry");
|
|
const activityRegistryV2_1 = require("./activityRegistryV2");
|
|
const iso20022Enrich_1 = require("./iso20022Enrich");
|
|
const iso20022LocalStore_1 = require("./iso20022LocalStore");
|
|
const participantSurface_1 = require("./participantSurface");
|
|
const receiptsRoot_1 = require("./receiptsRoot");
|
|
(0, config_1.assertAggregatorConfig)();
|
|
const BLOCK_ORACLE_ABI = ['function setBlockHeader(uint256,bytes32,bytes32) external'];
|
|
class CheckpointAggregator {
|
|
chain138;
|
|
mainnetWallet;
|
|
checkpoint;
|
|
ingress;
|
|
blockOracle;
|
|
buffer = [];
|
|
nextBatchId = 1n;
|
|
hubImplVersion = 3n;
|
|
flushTimer;
|
|
scanning = false;
|
|
includedTxCache = new Map();
|
|
constructor() {
|
|
const mainnet = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointAggregatorConfig.mainnetRpc);
|
|
this.mainnetWallet = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, mainnet);
|
|
this.checkpoint = (0, adminIngress_1.buildCheckpointContract)(this.mainnetWallet, config_1.checkpointAggregatorConfig.checkpointProxy, config_1.checkpointAggregatorConfig.submitMode);
|
|
this.ingress = new adminIngress_1.AdminCheckpointIngress(this.checkpoint, config_1.checkpointAggregatorConfig.submitMode);
|
|
this.chain138 = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointAggregatorConfig.chain138Rpc);
|
|
if (config_1.checkpointAggregatorConfig.blockOracleExtension) {
|
|
this.blockOracle = new ethers_1.ethers.Contract(config_1.checkpointAggregatorConfig.blockOracleExtension, BLOCK_ORACLE_ABI, this.mainnetWallet);
|
|
}
|
|
}
|
|
async start() {
|
|
await this.syncBatchId();
|
|
const state = (0, scanState_1.loadScanState)(config_1.checkpointAggregatorConfig.scanStatePath, config_1.checkpointAggregatorConfig.scanFromBlock);
|
|
console.log(`Checkpoint aggregator mode=${config_1.checkpointAggregatorConfig.submitMode} batch=${config_1.checkpointAggregatorConfig.batchSize} nextBatchId=${this.nextBatchId} impl=${this.hubImplVersion} blockscout=${config_1.checkpointAggregatorConfig.useBlockscout} emitter=${config_1.checkpointAggregatorConfig.batchEmitter138 || 'n/a'}`);
|
|
this.schedulePartialFlush();
|
|
await this.runScanLoop(state);
|
|
}
|
|
async syncBatchId() {
|
|
const latest = await this.checkpoint.getLatestBatchId();
|
|
this.nextBatchId = latest > 0n ? BigInt(latest) + 1n : 1n;
|
|
try {
|
|
this.hubImplVersion = await this.checkpoint.IMPLEMENTATION_VERSION();
|
|
}
|
|
catch {
|
|
this.hubImplVersion = 3n;
|
|
}
|
|
}
|
|
schedulePartialFlush() {
|
|
if (this.flushTimer)
|
|
clearTimeout(this.flushTimer);
|
|
this.flushTimer = setTimeout(() => this.flush(true), config_1.checkpointAggregatorConfig.maxWaitMs);
|
|
}
|
|
async runScanLoop(initialState) {
|
|
const state = { ...initialState };
|
|
for (;;) {
|
|
try {
|
|
await this.scanCatchUp(state);
|
|
}
|
|
catch (e) {
|
|
console.error('scanCatchUp', e);
|
|
}
|
|
await new Promise((r) => setTimeout(r, config_1.checkpointAggregatorConfig.scanPollMs));
|
|
}
|
|
}
|
|
async scanCatchUp(state) {
|
|
if (this.scanning)
|
|
return;
|
|
this.scanning = true;
|
|
try {
|
|
const head = await this.chain138.getBlockNumber();
|
|
const safeHead = head - config_1.checkpointAggregatorConfig.confirmationBlocks;
|
|
let from = state.lastScannedBlock + 1;
|
|
while (from <= safeHead) {
|
|
const to = Math.min(from + config_1.checkpointAggregatorConfig.scanChunkBlocks - 1, safeHead);
|
|
let added = 0;
|
|
if (config_1.checkpointAggregatorConfig.useBlockscout) {
|
|
added = await this.scanBlockscoutRange(from, to);
|
|
}
|
|
else {
|
|
for (let b = from; b <= to; b++)
|
|
added += await this.scanBlockRpc(b);
|
|
}
|
|
state.lastScannedBlock = to;
|
|
(0, scanState_1.saveScanState)(config_1.checkpointAggregatorConfig.scanStatePath, state);
|
|
console.log(`Scanned blocks ${from}-${to} (+${added} txs) buffer=${this.buffer.length}`);
|
|
from = to + 1;
|
|
}
|
|
}
|
|
finally {
|
|
this.scanning = false;
|
|
}
|
|
}
|
|
async scanBlockscoutRange(from, to) {
|
|
const txs = await (0, blockscout_1.fetchTransactionsInBlockRange)(config_1.checkpointAggregatorConfig.blockscoutApi, from, to);
|
|
let added = 0;
|
|
for (const tx of txs) {
|
|
if (await this.txAlreadyIncluded(tx.hash))
|
|
continue;
|
|
const receipt = await this.chain138.getTransactionReceipt(tx.hash);
|
|
const leaf = {
|
|
txHash: tx.hash,
|
|
from: tx.from,
|
|
to: tx.to,
|
|
value: tx.value,
|
|
blockNumber: tx.blockNumber,
|
|
blockTimestamp: tx.blockTimestamp,
|
|
gasUsed: receipt?.gasUsed ?? 0n,
|
|
success: receipt?.status === 1,
|
|
};
|
|
await this.enrichLeafFromBlockscout(leaf);
|
|
if ((0, leafCodec_1.effectiveValue)(leaf) < config_1.checkpointAggregatorConfig.minValueWei)
|
|
continue;
|
|
this.buffer.push(leaf);
|
|
added++;
|
|
if (this.buffer.length >= config_1.checkpointAggregatorConfig.batchSize)
|
|
await this.flush(false);
|
|
}
|
|
return added;
|
|
}
|
|
async scanBlockRpc(blockNumber) {
|
|
const block = await this.chain138.getBlock(blockNumber, true);
|
|
if (!block)
|
|
return 0;
|
|
let added = 0;
|
|
const entries = block.prefetchedTransactions?.length
|
|
? block.prefetchedTransactions
|
|
: block.transactions ?? [];
|
|
for (const entry of entries) {
|
|
const tx = typeof entry === 'string' ? await this.chain138.getTransaction(entry) : entry;
|
|
if (!tx || typeof tx === 'string')
|
|
continue;
|
|
const value = tx.value ?? 0n;
|
|
if (await this.txAlreadyIncluded(tx.hash))
|
|
continue;
|
|
const receipt = await this.chain138.getTransactionReceipt(tx.hash);
|
|
const leaf = {
|
|
txHash: tx.hash,
|
|
from: tx.from,
|
|
to: tx.to ?? ethers_1.ethers.ZeroAddress,
|
|
value,
|
|
blockNumber,
|
|
blockTimestamp: block.timestamp,
|
|
gasUsed: receipt?.gasUsed ?? 0n,
|
|
success: receipt?.status === 1,
|
|
};
|
|
await this.enrichLeafFromBlockscout(leaf);
|
|
if ((0, leafCodec_1.effectiveValue)(leaf) < config_1.checkpointAggregatorConfig.minValueWei)
|
|
continue;
|
|
this.buffer.push(leaf);
|
|
added++;
|
|
if (this.buffer.length >= config_1.checkpointAggregatorConfig.batchSize)
|
|
await this.flush(false);
|
|
}
|
|
return added;
|
|
}
|
|
async enrichLeafFromBlockscout(leaf) {
|
|
const { allErc20 } = await (0, usdEnrich_1.enrichLeafFromBlockscoutApi)(leaf, config_1.checkpointAggregatorConfig.blockscoutApi, config_1.checkpointAggregatorConfig.useBlockscout);
|
|
await (0, usdEnrich_1.enrichLeafUsd)(leaf, {
|
|
apiBaseUrl: config_1.checkpointAggregatorConfig.tokenAggregationApiUrl,
|
|
chainId: 138,
|
|
enabled: config_1.checkpointAggregatorConfig.usdEnrichEnabled,
|
|
requestDelayMs: config_1.checkpointAggregatorConfig.usdRequestDelayMs,
|
|
blockscoutApi: config_1.checkpointAggregatorConfig.blockscoutApi,
|
|
useBlockscout: config_1.checkpointAggregatorConfig.useBlockscout,
|
|
}, allErc20);
|
|
}
|
|
async txAlreadyIncluded(txHash) {
|
|
const cached = this.includedTxCache.get(txHash);
|
|
if (cached !== undefined)
|
|
return cached;
|
|
try {
|
|
const [included] = await this.checkpoint.isTxIncluded(txHash);
|
|
this.includedTxCache.set(txHash, included);
|
|
return included;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
async flush(partial) {
|
|
if (this.buffer.length === 0)
|
|
return;
|
|
const leaves = this.buffer.splice(0, config_1.checkpointAggregatorConfig.batchSize);
|
|
if (config_1.checkpointAggregatorConfig.attachReceiptMeta) {
|
|
await (0, receiptsRoot_1.attachReceiptMeta)(this.chain138, leaves);
|
|
}
|
|
else {
|
|
for (const leaf of leaves) {
|
|
const meta = await (0, receiptsRoot_1.fetchReceiptMeta)(this.chain138, leaf.txHash);
|
|
leaf.logCount = meta.logCount;
|
|
leaf.receiptHash = meta.receiptHash;
|
|
}
|
|
}
|
|
(0, iso20022Enrich_1.attachIso20022ToLeaves)(leaves);
|
|
const root = (0, leafCodec_1.paymentsRoot)(138, leaves);
|
|
const receiptMetas = leaves.map((l) => ({
|
|
logCount: l.logCount ?? 0,
|
|
receiptHash: l.receiptHash ?? ethers_1.ethers.ZeroHash,
|
|
}));
|
|
const receiptsRootHash = (0, receiptsRoot_1.receiptsRoot)(leaves.map((l) => l.txHash), receiptMetas);
|
|
const checkpointBlock = Math.max(...leaves.map((l) => l.blockNumber));
|
|
const block = await this.chain138.getBlock(checkpointBlock);
|
|
const blockHash = block?.hash ?? ethers_1.ethers.ZeroHash;
|
|
const stateRoot = block?.stateRoot ?? ethers_1.ethers.ZeroHash;
|
|
if (config_1.checkpointAggregatorConfig.updateBlockOracle && this.blockOracle) {
|
|
const tx = await this.blockOracle.setBlockHeader(checkpointBlock, blockHash, stateRoot);
|
|
await tx.wait();
|
|
}
|
|
const header = {
|
|
batchId: this.nextBatchId,
|
|
previousBatchId: this.nextBatchId > 1n ? this.nextBatchId - 1n : 0n,
|
|
chainId: 138n,
|
|
checkpointBlock: BigInt(checkpointBlock),
|
|
startBlock: BigInt(Math.min(...leaves.map((l) => l.blockNumber))),
|
|
endBlock: BigInt(checkpointBlock),
|
|
blockHash,
|
|
stateRoot,
|
|
paymentsRoot: root,
|
|
receiptsRoot: receiptsRootHash,
|
|
txCount: partial && leaves.length < config_1.checkpointAggregatorConfig.batchSize ? leaves.length : config_1.checkpointAggregatorConfig.batchSize,
|
|
flags: partial && leaves.length < config_1.checkpointAggregatorConfig.batchSize ? 1 : 0,
|
|
submittedAt: 0n,
|
|
submitter: ethers_1.ethers.ZeroAddress,
|
|
contentURI: ethers_1.ethers.ZeroHash,
|
|
};
|
|
const payload = {
|
|
batchId: this.nextBatchId.toString(),
|
|
chainId: 138,
|
|
checkpointBlock,
|
|
paymentsRoot: root,
|
|
leaves: leaves.map((l) => ({
|
|
txHash: l.txHash,
|
|
from: l.from,
|
|
to: l.to,
|
|
value: l.value.toString(),
|
|
nativeValueWei: l.value.toString(),
|
|
onChainValueWei: (0, leafCodec_1.effectiveValue)(l).toString(),
|
|
blockNumber: l.blockNumber,
|
|
blockTimestamp: l.blockTimestamp,
|
|
gasUsed: l.gasUsed.toString(),
|
|
success: l.success,
|
|
...(l.token
|
|
? {
|
|
token: l.token,
|
|
tokenSymbol: l.tokenSymbol,
|
|
tokenDecimals: l.tokenDecimals,
|
|
tokenValue: l.tokenValue?.toString(),
|
|
tokenLogIndex: l.tokenLogIndex,
|
|
}
|
|
: {}),
|
|
...(l.valueUsd ? { valueUsd: l.valueUsd } : {}),
|
|
...(l.nativeValueUsd ? { nativeValueUsd: l.nativeValueUsd } : {}),
|
|
...(l.tokenValueUsd ? { tokenValueUsd: l.tokenValueUsd } : {}),
|
|
...(l.nativePriceUsd != null ? { nativePriceUsd: l.nativePriceUsd } : {}),
|
|
...(l.tokenPriceUsd != null ? { tokenPriceUsd: l.tokenPriceUsd } : {}),
|
|
...(l.priceSource ? { priceSource: l.priceSource } : {}),
|
|
...(l.priceEffectiveTimestamp ? { priceEffectiveTimestamp: l.priceEffectiveTimestamp } : {}),
|
|
...(l.totalTransfersUsd ? { totalTransfersUsd: l.totalTransfersUsd } : {}),
|
|
...(l.transfers?.length ? { transfers: l.transfers } : {}),
|
|
...(l.logCount != null ? { logCount: l.logCount } : {}),
|
|
...(l.receiptHash ? { receiptHash: l.receiptHash } : {}),
|
|
...(l.iso20022 ? { iso20022: l.iso20022 } : {}),
|
|
})),
|
|
submittedAt: new Date().toISOString(),
|
|
...(config_1.checkpointAggregatorConfig.isoEnrichEnabled
|
|
? { iso20022Schema: 'canonical-v1', isoMsgTypeDefault: 'chain138.synthetic' }
|
|
: {}),
|
|
...(config_1.checkpointAggregatorConfig.usdEnrichEnabled
|
|
? {
|
|
batchTotalUsd: (0, checkpoint_core_1.sumUsdStrings)(leaves.map((l) => l.totalTransfersUsd ?? l.valueUsd)) ?? '0.000000',
|
|
usdEnrichedAt: new Date().toISOString(),
|
|
}
|
|
: {}),
|
|
};
|
|
const contentURI = await (0, ipfs_1.publishBatchPayload)(this.nextBatchId, payload);
|
|
header.contentURI = contentURI;
|
|
const validatorSignatures = config_1.checkpointAggregatorConfig.requireValidatorSigs
|
|
? await (0, eip712_1.signBatchAttestation)(this.mainnetWallet, config_1.checkpointAggregatorConfig.checkpointProxy, header)
|
|
: '0x01';
|
|
const txHashes = config_1.checkpointAggregatorConfig.submitMode === 'commitment' ? [] : leaves.map((l) => l.txHash);
|
|
const extensionData = adminIngress_1.AdminCheckpointIngress.extensionDataForMode(config_1.checkpointAggregatorConfig.submitMode, leaves);
|
|
const { hash } = await this.ingress.submit({
|
|
header,
|
|
leaves,
|
|
txHashes,
|
|
extensionData,
|
|
contentURI,
|
|
validatorSignatures,
|
|
});
|
|
const batchIdSubmitted = this.nextBatchId;
|
|
console.log(`Submitted batch ${batchIdSubmitted} (${leaves.length} txs) mode=${config_1.checkpointAggregatorConfig.submitMode} tx=${hash}`);
|
|
if (config_1.checkpointAggregatorConfig.dualWriteV1Mirror) {
|
|
try {
|
|
const mirrorTx = await (0, dualMirror_1.dualWriteV1Mirror)(this.mainnetWallet, config_1.checkpointAggregatorConfig.transactionMirrorMainnet, leaves);
|
|
if (mirrorTx)
|
|
console.log(` v1 TransactionMirror dual-write tx=${mirrorTx}`);
|
|
}
|
|
catch (e) {
|
|
console.error(' v1 TransactionMirror dual-write failed', e);
|
|
}
|
|
}
|
|
if (config_1.checkpointAggregatorConfig.recordAddressActivity && config_1.checkpointAggregatorConfig.addressActivityRegistry) {
|
|
try {
|
|
const activityTx = await (0, activityRegistry_1.recordActivityBatch)(this.mainnetWallet, config_1.checkpointAggregatorConfig.addressActivityRegistry, batchIdSubmitted, leaves);
|
|
if (activityTx)
|
|
console.log(` AddressActivityRegistry tx=${activityTx}`);
|
|
}
|
|
catch (e) {
|
|
console.error(' AddressActivityRegistry record failed', e);
|
|
}
|
|
}
|
|
if (config_1.checkpointAggregatorConfig.recordIsoAttestation && config_1.checkpointAggregatorConfig.addressActivityRegistryV2) {
|
|
try {
|
|
const isoTx = await (0, activityRegistryV2_1.recordIsoAttestationBatch)(this.mainnetWallet, config_1.checkpointAggregatorConfig.addressActivityRegistryV2, batchIdSubmitted, leaves);
|
|
if (isoTx)
|
|
console.log(` AddressActivityRegistryV2 tx=${isoTx}`);
|
|
}
|
|
catch (e) {
|
|
console.error(' AddressActivityRegistryV2 record failed', e);
|
|
}
|
|
}
|
|
if (config_1.checkpointAggregatorConfig.isoEnrichEnabled) {
|
|
try {
|
|
const stored = (0, iso20022LocalStore_1.persistIso20022ForLeaves)(leaves, batchIdSubmitted);
|
|
if (stored.length)
|
|
console.log(` ISO20022 OMNL store: ${stored.length} message(s)`);
|
|
}
|
|
catch (e) {
|
|
console.error(' ISO20022 local store failed', e);
|
|
}
|
|
}
|
|
if (config_1.checkpointAggregatorConfig.surfaceParticipants && config_1.checkpointAggregatorConfig.participantSurface) {
|
|
try {
|
|
const surfaceTx = await (0, participantSurface_1.surfaceParticipantsOnMainnet)(this.mainnetWallet, config_1.checkpointAggregatorConfig.participantSurface, batchIdSubmitted, leaves);
|
|
if (surfaceTx)
|
|
console.log(` Chain138ParticipantSurface tx=${surfaceTx}`);
|
|
}
|
|
catch (e) {
|
|
console.error(' Chain138ParticipantSurface failed', e);
|
|
}
|
|
}
|
|
if (config_1.checkpointAggregatorConfig.surfaceTopLevelZeroEth) {
|
|
try {
|
|
const n = await (0, participantSurface_1.surfaceTopLevelZeroEth)(this.mainnetWallet, leaves);
|
|
if (n > 0)
|
|
console.log(` Top-level 0 ETH surface txs=${n} (Etherscan Transactions tab)`);
|
|
}
|
|
catch (e) {
|
|
console.error(' Top-level 0 ETH surface failed', e);
|
|
}
|
|
}
|
|
this.nextBatchId += 1n;
|
|
this.schedulePartialFlush();
|
|
}
|
|
}
|
|
new CheckpointAggregator().start().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|