From 77561ac9d15dfa55f56e7a553f2951da1126f595 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 02:14:29 +0400 Subject: [PATCH 01/94] fix: use IStakingModule for keys events; fetch prefix in security service --- src/abi/IStakingModule.abi.json | 228 +- src/abi/csm.abi.json | 2666 ----------------- src/abi/signing-key.abi.json | 20 - .../repository/interfaces/staking-module.ts | 5 - src/contracts/repository/repository.mock.ts | 17 +- .../repository/repository.service.spec.ts | 83 +- .../repository/repository.service.ts | 147 +- .../security/security.service.spec.ts | 35 +- src/contracts/security/security.service.ts | 32 +- .../signing-key-events-cache/constants.ts | 2 +- .../leveldb/leveldb.service.ts | 4 +- .../signing-key-events-cache.module.ts | 4 +- .../signing-key-events-cache.service.ts | 124 +- .../signing-key-events-cache.spec.ts | 21 +- src/{ => contracts}/staking-router/index.ts | 0 .../staking-router/staking-router.module.ts | 8 + .../staking-router/staking-router.service.ts | 46 + .../keys-duplication-checker.service.spec.ts | 4 + src/guardian/guardian.module.ts | 6 +- src/guardian/guardian.service.spec.ts | 4 +- src/guardian/guardian.service.ts | 45 +- src/staking-module-data-collector/index.ts | 2 + .../keys.fixtures.ts | 0 .../operators.fixtures.ts | 0 .../staking-module-data-collector.module.ts} | 8 +- .../staking-module-data-collector.service.ts} | 2 +- .../vetted-keys.spec.ts | 0 .../vetted-keys.ts | 0 test/duplicates-v3.e2e-spec.ts | 22 +- test/duplicates.e2e-spec.ts | 20 +- test/front-run-v3.e2e-spec.ts | 10 +- test/front-run.e2e-spec.ts | 10 +- test/guardian-balance-monitoring.e2e-spec.ts | 2 +- test/invalid-keys-v3.e2e-spec.ts | 4 +- test/invalid-keys.e2e-spec.ts | 4 +- 35 files changed, 441 insertions(+), 3144 deletions(-) delete mode 100644 src/abi/csm.abi.json delete mode 100644 src/abi/signing-key.abi.json delete mode 100644 src/contracts/repository/interfaces/staking-module.ts rename src/{ => contracts}/staking-router/index.ts (100%) create mode 100644 src/contracts/staking-router/staking-router.module.ts create mode 100644 src/contracts/staking-router/staking-router.service.ts create mode 100644 src/staking-module-data-collector/index.ts rename src/{staking-router => staking-module-data-collector}/keys.fixtures.ts (100%) rename src/{staking-router => staking-module-data-collector}/operators.fixtures.ts (100%) rename src/{staking-router/staking-router.module.ts => staking-module-data-collector/staking-module-data-collector.module.ts} (68%) rename src/{staking-router/staking-router.service.ts => staking-module-data-collector/staking-module-data-collector.service.ts} (99%) rename src/{staking-router => staking-module-data-collector}/vetted-keys.spec.ts (100%) rename src/{staking-router => staking-module-data-collector}/vetted-keys.ts (100%) diff --git a/src/abi/IStakingModule.abi.json b/src/abi/IStakingModule.abi.json index 0afd7662..34008ae9 100644 --- a/src/abi/IStakingModule.abi.json +++ b/src/abi/IStakingModule.abi.json @@ -1,4 +1,17 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "NonceChanged", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -10,30 +23,43 @@ }, { "indexed": false, - "internalType": "uint256", - "name": "trimmedKeysCount", - "type": "uint256" + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" } ], - "name": "UnusedValidatorsKeysTrimmed", + "name": "SigningKeyAdded", "type": "event" }, { "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "uint256", - "name": "validatorsKeysNonce", + "name": "nodeOperatorId", "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" } ], - "name": "ValidatorsKeysNonceChanged", + "name": "SigningKeyRemoved", "type": "event" }, { - "inputs": [], - "name": "finishUpdatingExitedValidatorsKeysCount", + "inputs": [ + { "internalType": "bytes", "name": "_nodeOperatorIds", "type": "bytes" }, + { + "internalType": "bytes", + "name": "_vettedSigningKeysCounts", + "type": "bytes" + } + ], + "name": "decreaseVettedSigningKeysCount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -45,6 +71,22 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { "internalType": "uint256", "name": "_offset", "type": "uint256" }, + { "internalType": "uint256", "name": "_limit", "type": "uint256" } + ], + "name": "getNodeOperatorIds", + "outputs": [ + { + "internalType": "uint256[]", + "name": "nodeOperatorIds", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -58,27 +100,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "getNodeOperatorsCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getType", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getValidatorsKeysNonce", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -87,21 +108,46 @@ "type": "uint256" } ], - "name": "getValidatorsKeysStats", + "name": "getNodeOperatorSummary", "outputs": [ { "internalType": "uint256", - "name": "exitedValidatorsCount", + "name": "targetLimitMode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "targetValidatorsCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stuckValidatorsCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundedValidatorsCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stuckPenaltyEndTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalExitedValidators", "type": "uint256" }, { "internalType": "uint256", - "name": "activeValidatorsKeysCount", + "name": "totalDepositedValidators", "type": "uint256" }, { "internalType": "uint256", - "name": "readyToDepositValidatorsKeysCount", + "name": "depositableValidatorsCount", "type": "uint256" } ], @@ -110,58 +156,85 @@ }, { "inputs": [], - "name": "getValidatorsKeysStats", + "name": "getNodeOperatorsCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNonce", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingModuleSummary", "outputs": [ { "internalType": "uint256", - "name": "exitedValidatorsCount", + "name": "totalExitedValidators", "type": "uint256" }, { "internalType": "uint256", - "name": "activeValidatorsKeysCount", + "name": "totalDepositedValidators", "type": "uint256" }, { "internalType": "uint256", - "name": "readyToDepositValidatorsKeysCount", + "name": "depositableValidatorsCount", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getType", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ - { "internalType": "uint256", "name": "_totalShares", "type": "uint256" } + { + "internalType": "uint256", + "name": "_depositsCount", + "type": "uint256" + }, + { "internalType": "bytes", "name": "_depositCalldata", "type": "bytes" } + ], + "name": "obtainDepositData", + "outputs": [ + { "internalType": "bytes", "name": "publicKeys", "type": "bytes" }, + { "internalType": "bytes", "name": "signatures", "type": "bytes" } ], - "name": "handleRewardsMinted", - "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], - "name": "invalidateReadyToDepositKeys", + "name": "onExitedAndStuckValidatorsCountsUpdated", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ - { "internalType": "uint256", "name": "_keysCount", "type": "uint256" }, - { "internalType": "bytes", "name": "_calldata", "type": "bytes" } - ], - "name": "requestValidatorsKeysForDeposits", - "outputs": [ - { - "internalType": "uint256", - "name": "returnedKeysCount", - "type": "uint256" - }, - { "internalType": "bytes", "name": "publicKeys", "type": "bytes" }, - { "internalType": "bytes", "name": "signatures", "type": "bytes" } + { "internalType": "uint256", "name": "_totalShares", "type": "uint256" } ], + "name": "onRewardsMinted", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "onWithdrawalCredentialsChanged", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, @@ -174,16 +247,30 @@ }, { "internalType": "uint256", - "name": "_exitedValidatorsKeysCount", + "name": "_exitedValidatorsCount", "type": "uint256" }, { "internalType": "uint256", - "name": "_stuckValidatorsKeysCount", + "name": "_stuckValidatorsCount", "type": "uint256" } ], - "name": "unsafeUpdateValidatorsKeysCount", + "name": "unsafeUpdateValidatorsCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "_nodeOperatorIds", "type": "bytes" }, + { + "internalType": "bytes", + "name": "_exitedValidatorsCounts", + "type": "bytes" + } + ], + "name": "updateExitedValidatorsCount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -197,12 +284,26 @@ }, { "internalType": "uint256", - "name": "_exitedValidatorKeysCount", + "name": "_refundedValidatorsCount", "type": "uint256" } ], - "name": "updateExitedValidatorsKeysCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "updateRefundedValidatorsCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "_nodeOperatorIds", "type": "bytes" }, + { + "internalType": "bytes", + "name": "_stuckValidatorsCounts", + "type": "bytes" + } + ], + "name": "updateStuckValidatorsCount", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, @@ -215,11 +316,12 @@ }, { "internalType": "uint256", - "name": "_stuckValidatorKeysCount", + "name": "_targetLimitMode", "type": "uint256" - } + }, + { "internalType": "uint256", "name": "_targetLimit", "type": "uint256" } ], - "name": "updateStuckValidatorsKeysCount", + "name": "updateTargetValidatorsLimits", "outputs": [], "stateMutability": "nonpayable", "type": "function" diff --git a/src/abi/csm.abi.json b/src/abi/csm.abi.json deleted file mode 100644 index bb3bcb56..00000000 --- a/src/abi/csm.abi.json +++ /dev/null @@ -1,2666 +0,0 @@ -[ - { - "type": "constructor", - "inputs": [ - { - "name": "moduleType", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "elStealingFine", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "maxKeysPerOperatorEA", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "lidoLocator", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "DEFAULT_ADMIN_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "DEPOSIT_SIZE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "EL_REWARDS_STEALING_FINE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "INITIAL_SLASHING_PENALTY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "LIDO_LOCATOR", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract ILidoLocator" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "MAX_SIGNING_KEYS_PER_OPERATOR_BEFORE_PUBLIC_RELEASE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "MODULE_MANAGER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PAUSE_INFINITELY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PAUSE_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "RECOVERER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "REPORT_EL_REWARDS_STEALING_PENALTY_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "RESUME_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "STAKING_ROUTER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "VERIFIER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "accounting", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract ICSAccounting" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "activatePublicRelease", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "addNodeOperatorETH", - "inputs": [ - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "managerAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "rewardAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "eaProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - }, - { - "name": "referrer", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "addNodeOperatorStETH", - "inputs": [ - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "managerAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "rewardAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "name": "eaProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - }, - { - "name": "referrer", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "addNodeOperatorWstETH", - "inputs": [ - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "managerAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "rewardAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "name": "eaProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - }, - { - "name": "referrer", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "addValidatorKeysETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "addValidatorKeysStETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "addValidatorKeysWstETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "cancelELRewardsStealingPenalty", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "claimRewardsStETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stETHAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "cumulativeFeeShares", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "rewardsProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "claimRewardsWstETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "wstETHAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "cumulativeFeeShares", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "rewardsProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "cleanDepositQueue", - "inputs": [ - { - "name": "maxItems", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "toRemove", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "compensateELRewardsStealingPenalty", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "confirmNodeOperatorManagerAddressChange", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "confirmNodeOperatorRewardAddressChange", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "decreaseOperatorVettedKeys", - "inputs": [ - { - "name": "nodeOperatorIds", - "type": "uint256[]", - "internalType": "uint256[]" - }, - { - "name": "vettedKeysByOperator", - "type": "uint256[]", - "internalType": "uint256[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "depositETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "depositQueueItem", - "inputs": [ - { - "name": "index", - "type": "uint128", - "internalType": "uint128" - } - ], - "outputs": [ - { - "name": "item", - "type": "uint256", - "internalType": "Batch" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "depositStETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stETHAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "depositWstETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "wstETHAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "permit", - "type": "tuple", - "internalType": "struct ICSAccounting.PermitInput", - "components": [ - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "v", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "r", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "s", - "type": "bytes32", - "internalType": "bytes32" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "earlyAdoption", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract ICSEarlyAdoption" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getActiveNodeOperatorsCount", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperator", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "tuple", - "internalType": "struct NodeOperator", - "components": [ - { - "name": "managerAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "proposedManagerAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "rewardAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "proposedRewardAddress", - "type": "address", - "internalType": "address" - }, - { - "name": "active", - "type": "bool", - "internalType": "bool" - }, - { - "name": "targetLimit", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "targetLimitMode", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "stuckPenaltyEndTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalExitedKeys", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalAddedKeys", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalWithdrawnKeys", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalDepositedKeys", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalVettedKeys", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stuckValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "refundedValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "depositableValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "enqueuedCount", - "type": "uint256", - "internalType": "uint256" - } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorIds", - "inputs": [ - { - "name": "offset", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "limit", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "nodeOperatorIds", - "type": "uint256[]", - "internalType": "uint256[]" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorIsActive", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorNonWithdrawnKeys", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorRewardAddress", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorSummary", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "targetLimitMode", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "targetValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stuckValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "refundedValidatorsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stuckPenaltyEndTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalExitedValidators", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "totalDepositedValidators", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "depositableValidatorsCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNodeOperatorsCount", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getNonce", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getResumeSinceTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleAdmin", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleMember", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "index", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleMemberCount", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getSigningKeys", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "startIndex", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getSigningKeysWithSignatures", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "startIndex", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "keys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getStakingModuleSummary", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getType", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "grantRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "hasRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "initialize", - "inputs": [ - { - "name": "_accounting", - "type": "address", - "internalType": "address" - }, - { - "name": "_earlyAdoption", - "type": "address", - "internalType": "address" - }, - { - "name": "verifier", - "type": "address", - "internalType": "address" - }, - { - "name": "admin", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "isPaused", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "isValidatorSlashed", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "isValidatorWithdrawn", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "keyRemovalCharge", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "normalizeQueue", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "obtainDepositData", - "inputs": [ - { - "name": "depositsCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [ - { - "name": "publicKeys", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "signatures", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "onExitedAndStuckValidatorsCountsUpdated", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "onRewardsMinted", - "inputs": [ - { - "name": "totalShares", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "onWithdrawalCredentialsChanged", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "pauseFor", - "inputs": [ - { - "name": "duration", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "proposeNodeOperatorManagerAddressChange", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "proposedAddress", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "proposeNodeOperatorRewardAddressChange", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "proposedAddress", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "publicRelease", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "queue", - "inputs": [], - "outputs": [ - { - "name": "head", - "type": "uint128", - "internalType": "uint128" - }, - { - "name": "length", - "type": "uint128", - "internalType": "uint128" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "recoverERC1155", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverERC20", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverERC721", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverEther", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverStETHShares", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "removeKeys", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "startIndex", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keysCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "renounceRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "callerConfirmation", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "reportELRewardsStealingPenalty", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "blockHash", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "requestRewardsETH", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "ethAmount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "cumulativeFeeShares", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "rewardsProof", - "type": "bytes32[]", - "internalType": "bytes32[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "resetNodeOperatorManagerAddress", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "resume", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revokeRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setKeyRemovalCharge", - "inputs": [ - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "settleELRewardsStealingPenalty", - "inputs": [ - { - "name": "nodeOperatorIds", - "type": "uint256[]", - "internalType": "uint256[]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "submitInitialSlashing", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "submitWithdrawal", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "supportsInterface", - "inputs": [ - { - "name": "interfaceId", - "type": "bytes4", - "internalType": "bytes4" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "unsafeUpdateValidatorsCount", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "exitedValidatorsKeysCount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "stuckValidatorsKeysCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "updateExitedValidatorsCount", - "inputs": [ - { - "name": "nodeOperatorIds", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "exitedValidatorsCounts", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "updateRefundedValidatorsCount", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "refundedValidatorsCount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "updateStuckValidatorsCount", - "inputs": [ - { - "name": "nodeOperatorIds", - "type": "bytes", - "internalType": "bytes" - }, - { - "name": "stuckValidatorsCounts", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "updateTargetValidatorsLimits", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "targetLimitMode", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "targetLimit", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "event", - "name": "BatchEnqueued", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "count", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "DepositedSigningKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "depositedKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ELRewardsStealingPenaltyCancelled", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ELRewardsStealingPenaltyReported", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "proposedBlockHash", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - }, - { - "name": "stolenAmount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ELRewardsStealingPenaltySettled", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ExitedSigningKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "exitedKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "InitialSlashingSubmitted", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Initialized", - "inputs": [ - { - "name": "version", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "KeyRemovalChargeApplied", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "KeyRemovalChargeSet", - "inputs": [ - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "NodeOperatorAdded", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "managerAddress", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "rewardAddress", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "NonceChanged", - "inputs": [ - { - "name": "nonce", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Paused", - "inputs": [ - { - "name": "duration", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "PublicRelease", - "inputs": [], - "anonymous": false - }, - { - "type": "event", - "name": "ReferrerSet", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "referrer", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RefundedKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "refundedKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Resumed", - "inputs": [], - "anonymous": false - }, - { - "type": "event", - "name": "RoleAdminChanged", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "previousAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "newAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleGranted", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleRevoked", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "SigningKeyAdded", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "pubkey", - "type": "bytes", - "indexed": false, - "internalType": "bytes" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "SigningKeyRemoved", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "pubkey", - "type": "bytes", - "indexed": false, - "internalType": "bytes" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "StuckSigningKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "stuckKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "TargetValidatorsCountChangedByRequest", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "targetLimitMode", - "type": "uint8", - "indexed": false, - "internalType": "uint8" - }, - { - "name": "targetValidatorsCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "TotalSigningKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "totalKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "VettedSigningKeysCountChanged", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "vettedKeysCount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "WithdrawalSubmitted", - "inputs": [ - { - "name": "nodeOperatorId", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "keyIndex", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "AccessControlBadConfirmation", - "inputs": [] - }, - { - "type": "error", - "name": "AccessControlUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - }, - { - "name": "neededRole", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "type": "error", - "name": "AlreadySet", - "inputs": [] - }, - { - "type": "error", - "name": "AlreadySubmitted", - "inputs": [] - }, - { - "type": "error", - "name": "EmptyKey", - "inputs": [] - }, - { - "type": "error", - "name": "ExitedKeysDecrease", - "inputs": [] - }, - { - "type": "error", - "name": "ExitedKeysHigherThanTotalDeposited", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidAmount", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidInitialization", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidKeysCount", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidLength", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidReportData", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidVetKeysPointer", - "inputs": [] - }, - { - "type": "error", - "name": "MaxSigningKeysCountExceeded", - "inputs": [] - }, - { - "type": "error", - "name": "NodeOperatorDoesNotExist", - "inputs": [] - }, - { - "type": "error", - "name": "NotAllowedToJoinYet", - "inputs": [] - }, - { - "type": "error", - "name": "NotAllowedToRecover", - "inputs": [] - }, - { - "type": "error", - "name": "NotEnoughKeys", - "inputs": [] - }, - { - "type": "error", - "name": "NotInitializing", - "inputs": [] - }, - { - "type": "error", - "name": "PauseUntilMustBeInFuture", - "inputs": [] - }, - { - "type": "error", - "name": "PausedExpected", - "inputs": [] - }, - { - "type": "error", - "name": "QueueIsEmpty", - "inputs": [] - }, - { - "type": "error", - "name": "QueueLookupNoLimit", - "inputs": [] - }, - { - "type": "error", - "name": "ResumedExpected", - "inputs": [] - }, - { - "type": "error", - "name": "SenderIsNotEligible", - "inputs": [] - }, - { - "type": "error", - "name": "SigningKeysInvalidOffset", - "inputs": [] - }, - { - "type": "error", - "name": "StuckKeysHigherThanTotalDepositedMinusTotalExited", - "inputs": [] - }, - { - "type": "error", - "name": "ZeroPauseDuration", - "inputs": [] - } -] diff --git a/src/abi/signing-key.abi.json b/src/abi/signing-key.abi.json deleted file mode 100644 index c1a16a87..00000000 --- a/src/abi/signing-key.abi.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "nodeOperatorId", "type": "uint256" }, - { "indexed": false, "name": "pubkey", "type": "bytes" } - ], - "name": "SigningKeyAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "nodeOperatorId", "type": "uint256" }, - { "indexed": false, "name": "pubkey", "type": "bytes" } - ], - "name": "SigningKeyRemoved", - "type": "event" - } -] diff --git a/src/contracts/repository/interfaces/staking-module.ts b/src/contracts/repository/interfaces/staking-module.ts deleted file mode 100644 index 413031f4..00000000 --- a/src/contracts/repository/interfaces/staking-module.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CsmAbi, SigningKeyAbi } from 'generated'; - -export interface StakingModule { - impl: SigningKeyAbi | CsmAbi; -} diff --git a/src/contracts/repository/repository.mock.ts b/src/contracts/repository/repository.mock.ts index 6f21f574..8b2118b5 100644 --- a/src/contracts/repository/repository.mock.ts +++ b/src/contracts/repository/repository.mock.ts @@ -1,4 +1,3 @@ -import { hexZeroPad } from '@ethersproject/bytes'; import { RepositoryService } from './repository.service'; export const mockRepository = async (repositoryService: RepositoryService) => { @@ -8,20 +7,10 @@ export const mockRepository = async (repositoryService: RepositoryService) => { .spyOn(repositoryService, 'getDepositAddress') .mockImplementation(async () => address1); - const mockGetPauseMessagePrefix = jest - .spyOn(repositoryService, 'getPauseMessagePrefix') - .mockImplementation(async () => hexZeroPad('0x2', 32)); - - const mockGetAttestMessagePrefix = jest - .spyOn(repositoryService, 'getAttestMessagePrefix') - .mockImplementation(async () => hexZeroPad('0x1', 32)); - - jest - .spyOn(repositoryService, 'getStakingModules') - .mockImplementation(async () => []); - await repositoryService.initCachedContracts('latest'); jest.spyOn(repositoryService, 'getCachedLidoContract'); - return { depositAddr, mockGetPauseMessagePrefix, mockGetAttestMessagePrefix }; + return { + depositAddr, + }; }; diff --git a/src/contracts/repository/repository.service.spec.ts b/src/contracts/repository/repository.service.spec.ts index 5813b609..54f5dade 100644 --- a/src/contracts/repository/repository.service.spec.ts +++ b/src/contracts/repository/repository.service.spec.ts @@ -3,15 +3,13 @@ import { Test } from '@nestjs/testing'; import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { PrometheusModule } from 'common/prometheus'; -import { MockProviderModule, ProviderService } from 'provider'; +import { MockProviderModule } from 'provider'; import { RepositoryService } from 'contracts/repository'; import { RepositoryModule } from './repository.module'; import { LocatorService } from './locator/locator.service'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { mockLocator } from './locator/locator.mock'; import { mockRepository } from './repository.mock'; -import { SecurityAbi__factory } from 'generated'; -import { Interface } from '@ethersproject/abi'; describe('RepositoryService', () => { let repositoryService: RepositoryService; @@ -115,83 +113,4 @@ describe('RepositoryService', () => { expect(contract1).toEqual(contract2); }); }); - - describe('messages prefixes', () => { - let repositoryService: RepositoryService; - let locatorService: LocatorService; - let providerService: ProviderService; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot(), - MockProviderModule.forRoot(), - LoggerModule, - PrometheusModule, - RepositoryModule, - ], - }).compile(); - - repositoryService = moduleRef.get(RepositoryService); - locatorService = moduleRef.get(LocatorService); - providerService = moduleRef.get(ProviderService); - jest - .spyOn(moduleRef.get(WINSTON_MODULE_NEST_PROVIDER), 'log') - .mockImplementation(() => undefined); - }); - - it('getAttestMessagePrefix', async () => { - const expected = '0x' + '1'.repeat(64); - - const mockProviderCall = jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(SecurityAbi__factory.abi); - const result = [expected]; - return iface.encodeFunctionResult('ATTEST_MESSAGE_PREFIX', result); - }); - - jest - .spyOn(repositoryService, 'getDepositAddress') - .mockImplementation(async () => '0x' + '5'.repeat(40)); - - jest - .spyOn(repositoryService, 'getStakingModules') - .mockImplementation(async () => []); - - mockLocator(locatorService); - - await repositoryService.initCachedContracts('latest'); - const prefix = await repositoryService.getAttestMessagePrefix(); - expect(prefix).toBe(expected); - expect(mockProviderCall).toBeCalledTimes(2); - }); - - it('getPauseMessagePrefix', async () => { - const expected = '0x' + '1'.repeat(64); - - const mockProviderCall = jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(SecurityAbi__factory.abi); - const result = [expected]; - return iface.encodeFunctionResult('PAUSE_MESSAGE_PREFIX', result); - }); - - jest - .spyOn(repositoryService, 'getDepositAddress') - .mockImplementation(async () => '0x' + '5'.repeat(40)); - - jest - .spyOn(repositoryService, 'getStakingModules') - .mockImplementation(async () => []); - - mockLocator(locatorService); - - await repositoryService.initCachedContracts('latest'); - const prefix = await repositoryService.getPauseMessagePrefix(); - expect(prefix).toBe(expected); - expect(mockProviderCall).toBeCalledTimes(2); - }); - }); }); diff --git a/src/contracts/repository/repository.service.ts b/src/contracts/repository/repository.service.ts index c86e083d..94db4b40 100644 --- a/src/contracts/repository/repository.service.ts +++ b/src/contracts/repository/repository.service.ts @@ -1,10 +1,8 @@ +import { Block } from '@ethersproject/abstract-provider'; import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { LidoAbi, LidoAbi__factory, LocatorAbi } from 'generated'; import { SecurityAbi, SecurityAbi__factory } from 'generated'; import { DepositAbi, DepositAbi__factory } from 'generated'; -import { CsmAbi, CsmAbi__factory } from 'generated'; -import { SigningKeyAbi, SigningKeyAbi__factory } from 'generated'; -import { IStakingModuleAbi__factory } from 'generated'; import { StakingRouterAbi, StakingRouterAbi__factory } from 'generated'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { BlockTag, ProviderService } from 'provider'; @@ -16,12 +14,7 @@ import { INIT_CONTRACTS_TIMEOUT, LIDO_ABI, STAKING_ROUTER_ABI, - COMMUNITY_ONCHAIN_DEVNET0_V1_TYPE, - COMMUNITY_ONCHAIN_V1_TYPE, - CURATED_ONCHAIN_V1_TYPE, } from './repository.constants'; -import { ethers } from 'ethers'; -import { StakingModule } from './interfaces/staking-module'; @Injectable() export class RepositoryService { @@ -38,7 +31,6 @@ export class RepositoryService { // if the contracts are updated we will change these addresses too private cachedDSMPrefixes: Record = {}; private permanentContractsCache: Record = {}; - private stakingModulesCache: Record = {}; /** * Init cache for each contract @@ -48,14 +40,13 @@ export class RepositoryService { // order is important: deposit contract depends on dsm await this.initCachedDSMContract(blockTag); await this.initCachedDepositContract(blockTag); - await this.initCachedStakingRouterAbiContract(blockTag); - await this.initCachedStakingModulesContracts(blockTag); + await this.initCachedStakingRouterContract(blockTag); } /** * Init cache for each contract or wait if it makes some error */ - public async initOrWaitCachedContracts() { + public async initOrWaitCachedContracts(): Promise { const block = await this.providerService.getBlock(); try { await this.initCachedContracts({ blockHash: block.hash }); @@ -95,13 +86,6 @@ export class RepositoryService { return this.getFromCache(STAKING_ROUTER_ABI) as StakingRouterAbi; } - /** - * Get Node Operator Registry contract impl - */ - public getCachedStakingModulesContracts(): Record { - return this.stakingModulesCache; - } - /** * Get cached contract impl */ @@ -133,23 +117,6 @@ export class RepositoryService { this.tempContractsCache[contractKey] = impl; } - public setStakingModuleCache(address: string, impl: SigningKeyAbi | CsmAbi) { - if (!this.stakingModulesCache[address]) { - this.logger.log('Staking module contract initial address', { address }); - } - - if ( - this.stakingModulesCache[address] && - this.stakingModulesCache[address].impl.address !== address - ) { - this.logger.log('Staking module contract address was changed', { - address, - }); - } - - this.stakingModulesCache[address] = { impl }; - } - private setPermanentContractCache( address: string, contractKey: string, @@ -188,12 +155,6 @@ export class RepositoryService { // prune dsm prefixes this.cachedDSMPrefixes = {}; - - // re-init dsm prefixes - await Promise.all([ - this.getAttestMessagePrefix(), - this.getPauseMessagePrefix(), - ]); } /** @@ -202,6 +163,8 @@ export class RepositoryService { private async initCachedDepositContract(blockTag: BlockTag): Promise { if (this.permanentContractsCache[DEPOSIT_ABI]) return; const depositAddress = await this.getDepositAddress(blockTag); + + console.log('depositAddress', depositAddress); const provider = this.providerService.provider; this.setPermanentContractCache( @@ -214,7 +177,7 @@ export class RepositoryService { /** * Init cache for SR contract */ - private async initCachedStakingRouterAbiContract( + private async initCachedStakingRouterContract( blockTag: BlockTag, ): Promise { const stakingRouterAddress = @@ -228,104 +191,6 @@ export class RepositoryService { ); } - private async initCachedStakingModulesContracts( - blockTag: BlockTag, - ): Promise { - const stakingModules = await this.getStakingModules(blockTag); - await Promise.all( - stakingModules.map(async (stakingModule) => { - const type = await this.getStakingModuleType( - stakingModule.stakingModuleAddress, - blockTag, - ); - - const provider = this.providerService.provider; - - if (type === CURATED_ONCHAIN_V1_TYPE) { - this.setStakingModuleCache( - stakingModule.stakingModuleAddress, - SigningKeyAbi__factory.connect( - stakingModule.stakingModuleAddress, - provider, - ), - ); - return; - } - - if ( - type === COMMUNITY_ONCHAIN_V1_TYPE || - type === COMMUNITY_ONCHAIN_DEVNET0_V1_TYPE - ) { - this.setStakingModuleCache( - stakingModule.stakingModuleAddress, - CsmAbi__factory.connect( - stakingModule.stakingModuleAddress, - provider, - ), - ); - return; - } - - this.logger.error(new Error(`Staking Module type ${type} is unknown`)); - process.exit(1); - }), - ); - } - - public async getStakingModules(blockTag: BlockTag) { - const stakingRouter = await this.getCachedStakingRouterContract(); - const stakingModules = await stakingRouter.getStakingModules({ - blockTag: blockTag as any, - }); - - return stakingModules; - } - - public async getStakingModuleType( - contractAddress: string, - blockTag: BlockTag, - ): Promise { - const contract = IStakingModuleAbi__factory.connect( - contractAddress, - this.providerService.provider, - ); - - const type = await contract.getType({ blockTag } as any); - return ethers.utils.parseBytes32String(type); - } - - /** - * Returns a prefix from the contract with which the deposit message should be signed - */ - public async getAttestMessagePrefix(): Promise { - if (this.cachedDSMPrefixes.attest) return this.cachedDSMPrefixes.attest; - const contract = await this.getCachedDSMContract(); - this.cachedDSMPrefixes.attest = await contract.ATTEST_MESSAGE_PREFIX(); - return this.cachedDSMPrefixes.attest; - } - - /** - * Returns a prefix from the contract with which the pause message should be signed - */ - public async getPauseMessagePrefix(): Promise { - if (this.cachedDSMPrefixes.pause) return this.cachedDSMPrefixes.pause; - const contract = await this.getCachedDSMContract(); - this.cachedDSMPrefixes.pause = await contract.PAUSE_MESSAGE_PREFIX(); - - return this.cachedDSMPrefixes.pause; - } - - /** - * Returns a prefix from the contract with which the pause message should be signed - */ - public async getUnvetMessagePrefix(): Promise { - if (this.cachedDSMPrefixes.unvet) return this.cachedDSMPrefixes.unvet; - const contract = await this.getCachedDSMContract(); - this.cachedDSMPrefixes.unvet = await contract.UNVET_MESSAGE_PREFIX(); - - return this.cachedDSMPrefixes.unvet; - } - /** * Returns Deposit contract address */ diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index dad683f0..2a618c2d 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -32,7 +32,7 @@ describe('SecurityService', () => { let walletService: WalletService; let loggerService: LoggerService; let mockGetAttestMessagePrefix: jest.SpyInstance, []>; - let mockGetPauseMessagePrefix: jest.SpyInstance, []>; + // let mockGetPauseMessagePrefix: jest.SpyInstance, []>; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ @@ -58,8 +58,8 @@ describe('SecurityService', () => { mockLocator(moduleRef.get(LocatorService)); const repo = await mockRepository(repositoryService); - mockGetAttestMessagePrefix = repo.mockGetAttestMessagePrefix; - mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; + // mockGetAttestMessagePrefix = repo.mockGetAttestMessagePrefix; + // mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; }); describe('getGuardians', () => { @@ -130,11 +130,15 @@ describe('SecurityService', () => { TEST_MODULE_ID, ] as const; + const mockGetAttestMessagePrefix = jest + .spyOn(securityService, 'getAttestMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x1', 32)); + const signDepositData = jest.spyOn(walletService, 'signDepositData'); const signature = await securityService.signDepositData(...args); - // 1 — repository, 2 — signDepositData - expect(mockGetAttestMessagePrefix).toBeCalledTimes(2); + + expect(mockGetAttestMessagePrefix).toBeCalledTimes(1); expect(signDepositData).toBeCalledWith({ prefix, depositRoot, @@ -158,14 +162,17 @@ describe('SecurityService', () => { it('should add prefix', async () => { const blockNumber = 1; + const mockGetPauseMessagePrefix = jest + .spyOn(securityService, 'getPauseMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); + const signPauseData = jest.spyOn(walletService, 'signPauseDataV2'); const signature = await securityService.signPauseDataV2( blockNumber, TEST_MODULE_ID, ); - // 1 — repository, 2 — signDepositData - expect(mockGetPauseMessagePrefix).toBeCalledTimes(2); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); expect(signPauseData).toBeCalledWith({ blockNumber: 1, prefix: @@ -217,7 +224,10 @@ describe('SecurityService', () => { beforeEach(async () => { mockWait = jest.fn().mockImplementation(async () => undefined); const repo = await mockRepository(repositoryService); - mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; + // mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; + mockGetPauseMessagePrefix = jest + .spyOn(securityService, 'getPauseMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); mockPauseDeposits = jest .fn() @@ -247,11 +257,11 @@ describe('SecurityService', () => { // mockGetPauseMessagePrefix calls 3 times because // we have more than one call under the hood // 1 - repository, 2 — signPauseData, 3 — pauseDeposits - expect(mockGetPauseMessagePrefix).toBeCalledTimes(3); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); expect(mockGetContractWithSigner).toBeCalledTimes(1); }); - it('should exit if the previous call is not completed2', async () => { + it('should exit if the previous call is not completed', async () => { await Promise.all([ securityService.pauseDepositsV2(blockNumber, TEST_MODULE_ID, signature), securityService.pauseDepositsV2(blockNumber, TEST_MODULE_ID, signature), @@ -259,10 +269,7 @@ describe('SecurityService', () => { expect(mockPauseDeposits).toBeCalledTimes(1); expect(mockWait).toBeCalledTimes(1); - // mockGetPauseMessagePrefix calls 3 times because - // we have more than one call under the hood - // 1 - repository, 2 — signPauseData, 3 — pauseDeposits - expect(mockGetPauseMessagePrefix).toBeCalledTimes(3); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); expect(mockGetContractWithSigner).toBeCalledTimes(1); }); }); diff --git a/src/contracts/security/security.service.ts b/src/contracts/security/security.service.ts index 751f7bfd..8f0a16f0 100644 --- a/src/contracts/security/security.service.ts +++ b/src/contracts/security/security.service.ts @@ -104,7 +104,7 @@ export class SecurityService { blockHash: string, stakingModuleId: number, ): Promise { - const prefix = await this.repositoryService.getAttestMessagePrefix(); + const prefix = await this.getAttestMessagePrefix(blockNumber); return await this.walletService.signDepositData({ prefix, @@ -120,7 +120,7 @@ export class SecurityService { * Signs a message to pause deposits with the prefix from the contract */ public async signPauseDataV3(blockNumber: number): Promise { - const prefix = await this.repositoryService.getPauseMessagePrefix(); + const prefix = await this.getPauseMessagePrefix(blockNumber); return await this.walletService.signPauseDataV3({ prefix, @@ -169,7 +169,7 @@ export class SecurityService { blockNumber: number, stakingModuleId: number, ): Promise { - const prefix = await this.repositoryService.getPauseMessagePrefix(); + const prefix = await this.getPauseMessagePrefix(blockNumber); return await this.walletService.signPauseDataV2({ prefix, @@ -241,7 +241,7 @@ export class SecurityService { operatorIds: string, vettedKeysByOperator: string, ): Promise { - const prefix = await this.repositoryService.getUnvetMessagePrefix(); + const prefix = await this.getUnvetMessagePrefix(blockNumber); return await this.walletService.signUnvetData({ prefix, @@ -380,4 +380,28 @@ export class SecurityService { return !isActive; } + + /** + * Returns a prefix from the contract with which the deposit message should be signed + */ + public async getAttestMessagePrefix(blockNumber: number): Promise { + const contract = await this.repositoryService.getCachedDSMContract(); + return await contract.ATTEST_MESSAGE_PREFIX({ blockTag: blockNumber }); + } + + /** + * Returns a prefix from the contract with which the pause message should be signed + */ + public async getPauseMessagePrefix(blockNumber: number): Promise { + const contract = await this.repositoryService.getCachedDSMContract(); + return await contract.PAUSE_MESSAGE_PREFIX({ blockTag: blockNumber }); + } + + /** + * Returns a prefix from the contract with which the pause message should be signed + */ + public async getUnvetMessagePrefix(blockNumber: number): Promise { + const contract = await this.repositoryService.getCachedDSMContract(); + return await contract.UNVET_MESSAGE_PREFIX({ blockTag: blockNumber }); + } } diff --git a/src/contracts/signing-key-events-cache/constants.ts b/src/contracts/signing-key-events-cache/constants.ts index dd4ed80e..e98d087c 100644 --- a/src/contracts/signing-key-events-cache/constants.ts +++ b/src/contracts/signing-key-events-cache/constants.ts @@ -9,7 +9,7 @@ export const SIGNING_KEYS_CACHE_DEFAULT = Object.freeze({ data: [], }); -export const CURATED_MODULE_DEPLOYMENT_BLOCK_NETWORK: { +export const EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK: { [key in CHAINS]?: number; } = { [CHAINS.Mainnet]: 11473216, diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts index 361d9e34..e898bff8 100644 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts +++ b/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts @@ -170,7 +170,7 @@ export class LevelDBService { * @returns {string} The serialized JSON string of the signing key event. * @public */ - public serializeEventDate(signingKeyEvent: SigningKeyEvent) { + public serializeEventData(signingKeyEvent: SigningKeyEvent) { return JSON.stringify(signingKeyEvent); } @@ -195,7 +195,7 @@ export class LevelDBService { const ops = records.data.map((event) => ({ type: 'put' as const, key: this.generateSigningKeyEventStorageKey(event), - value: this.serializeEventDate(event), + value: this.serializeEventData(event), })); ops.push({ type: 'put', diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts index 34c248f6..5e3471de 100644 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts +++ b/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common'; import { LevelDBModule } from './leveldb'; import { SigningKeyEventsCacheService } from './signing-key-events-cache.service'; import { SIGNING_KEYS_CACHE_DEFAULT } from './constants'; -import { RepositoryModule } from 'contracts/repository'; +import { StakingRouterModule } from 'contracts/staking-router'; @Module({ imports: [ - RepositoryModule, + StakingRouterModule, LevelDBModule.register(SIGNING_KEYS_CACHE_DEFAULT), ], providers: [SigningKeyEventsCacheService], diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts index 18121215..7a9d1feb 100644 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts +++ b/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts @@ -1,5 +1,4 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { RepositoryService } from 'contracts/repository'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; import { @@ -10,20 +9,21 @@ import { import { LevelDBService } from './leveldb'; import { SigningKeyEventsCache } from './interfaces/cache.interface'; import { - CURATED_MODULE_DEPLOYMENT_BLOCK_NETWORK, + EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, FETCHING_EVENTS_STEP, SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS, SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE, } from './constants'; import { performance } from 'perf_hooks'; +import { StakingRouterService } from 'contracts/staking-router'; @Injectable() export class SigningKeyEventsCacheService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private providerService: ProviderService, - private repositoryService: RepositoryService, private levelDBCacheService: LevelDBService, + private stakingRouterService: StakingRouterService, ) {} /** @@ -36,15 +36,20 @@ export class SigningKeyEventsCacheService { * @param {number} blockNumber - The block number of the newly processed block. * @returns {Promise} */ - public async handleNewBlock(blockNumber): Promise { - const wasUpdated = await this.stakingModuleListWasUpdated(); + public async handleNewBlock( + blockNumber: number, + currentstakingModulesAddresses: string[], + ): Promise { + const wasUpdated = await this.stakingModuleListWasUpdated( + currentstakingModulesAddresses, + ); if (wasUpdated) { this.logger.log('Staking module list was updated. Deleting cache'); await this.levelDBCacheService.deleteCache(); - await this.updateEventsCache(); + await this.updateEventsCache(currentstakingModulesAddresses); } else if (blockNumber % SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE === 0) { // update for every SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE block - await this.updateEventsCache(); + await this.updateEventsCache(currentstakingModulesAddresses); } } @@ -53,7 +58,10 @@ export class SigningKeyEventsCacheService { * @param {number} blockNumber - The block number to validate the cache against. * @returns {Promise} */ - public async initialize(blockNumber) { + public async initialize( + blockNumber: number, + currentstakingModulesAddresses: string[], + ) { await this.levelDBCacheService.initialize(); const cachedEvents = await this.getCachedEvents(); @@ -65,13 +73,15 @@ export class SigningKeyEventsCacheService { process.exit(1); } - const wasUpdated = await this.stakingModuleListWasUpdated(); + const wasUpdated = await this.stakingModuleListWasUpdated( + currentstakingModulesAddresses, + ); if (wasUpdated) { this.logger.log('Staking module list was updated. Deleting cache'); await this.levelDBCacheService.deleteCache(); } - await this.updateEventsCache(); + await this.updateEventsCache(currentstakingModulesAddresses); } /** @@ -80,9 +90,13 @@ export class SigningKeyEventsCacheService { * * @returns {Promise} The block number up to which the cache has been updated. */ - public async updateEventsCache(): Promise { + public async updateEventsCache( + currentStakingModulesAddresses: string[], + ): Promise { const fetchTimeStart = performance.now(); + // TODO: maybe we should add blockNumber as argument of updateEventsCache method + // and use block number from initialized and handleNewBlock methods const [latestBlock, initialCache] = await Promise.all([ this.providerService.getBlockNumber(), this.getCachedEvents(), @@ -94,8 +108,6 @@ export class SigningKeyEventsCacheService { const totalEventsCount = initialCache.data.length; let newEventsCount = 0; - const stakingModulesAddresses = await this.getStakingModules(); - for ( let block = firstNotCachedBlock; block <= toBlock; @@ -107,13 +119,14 @@ export class SigningKeyEventsCacheService { const chunkEventGroup = await this.fetchEventsFallOver( chunkStartBlock, chunkToBlock, + currentStakingModulesAddresses, ); await this.levelDBCacheService.insertEventsCacheBatch({ headers: { ...initialCache.headers, // as we update staking modules addresses always before run of this method, we can update value on every iteration - stakingModulesAddresses: stakingModulesAddresses, + stakingModulesAddresses: currentStakingModulesAddresses, endBlock: chunkEventGroup.endBlock, }, data: chunkEventGroup.events, @@ -149,13 +162,13 @@ export class SigningKeyEventsCacheService { * * @returns {Promise} Return `true` if the staking modules list was updated, `false` otherwise. */ - public async stakingModuleListWasUpdated(): Promise { + public async stakingModuleListWasUpdated( + currentModules: string[], + ): Promise { const { headers: { stakingModulesAddresses: previousModules }, } = await this.levelDBCacheService.getHeader(); - const currentModules = await this.getStakingModules(); - const wasUpdated = this.wasStakingModulesListUpdated( previousModules, currentModules, @@ -198,20 +211,6 @@ export class SigningKeyEventsCacheService { return modulesWereDeleted || modulesWereAdded; } - /** - * Retrieves the list of staking module addresses. - * - * This method fetches the cached staking modules contracts and returns the list of staking module addresses. - * - * @returns {Promise} Array of staking module addresses. - */ - public async getStakingModules(): Promise { - const stakingModulesContracts = - await this.repositoryService.getCachedStakingModulesContracts(); - - return Object.keys(stakingModulesContracts); - } - /** * Fetches signing key events within a specified block range, with fallback mechanisms. * If the request failed, it tries to repeat it or split it into two @@ -223,11 +222,15 @@ export class SigningKeyEventsCacheService { public async fetchEventsFallOver( startBlock: number, endBlock: number, + stakingModulesAddresses: string[], ): Promise { + const fetcherWrapper = (start: number, end: number) => + this.fetchEvents(start, end, stakingModulesAddresses); + return await this.providerService.fetchEventsFallOver( startBlock, endBlock, - this.fetchEvents.bind(this), + fetcherWrapper, ); } @@ -241,45 +244,37 @@ export class SigningKeyEventsCacheService { public async fetchEvents( startBlock: number, endBlock: number, + stakingModulesAddresses: string[], ): Promise { - const stakingModulesContracts = - await this.repositoryService.getCachedStakingModulesContracts(); - const events: SigningKeyEvent[] = []; await Promise.all( - Object.entries(stakingModulesContracts).map( - async ([address, { impl }]) => { - const filter = impl.filters['SigningKeyAdded(uint256,bytes)'](); - - const rawEvents = await impl.queryFilter( - filter, + stakingModulesAddresses.map(async (address) => { + const rawEvents = + await this.stakingRouterService.getSigningKeyAddedEvents( startBlock, endBlock, + address, ); - const moduleEvents: SigningKeyEvent[] = rawEvents.map((rawEvent) => { - return { - operatorIndex: rawEvent.args[0].toNumber(), - key: rawEvent.args[1], - moduleAddress: address, - blockNumber: rawEvent.blockNumber, - logIndex: rawEvent.logIndex, - blockHash: rawEvent.blockHash, - }; - }); - - events.push(...moduleEvents); - - this.logger.log( - 'Fetched signing keys add events for staking module', - { - count: moduleEvents.length, - address, - }, - ); - }, - ), + const moduleEvents: SigningKeyEvent[] = rawEvents.map((rawEvent) => { + return { + operatorIndex: rawEvent.args[0].toNumber(), + key: rawEvent.args[1], + moduleAddress: address, + blockNumber: rawEvent.blockNumber, + logIndex: rawEvent.logIndex, + blockHash: rawEvent.blockHash, + }; + }); + + events.push(...moduleEvents); + + this.logger.log('Fetched signing keys add events for staking module', { + count: moduleEvents.length, + address, + }); + }), ); return { events, startBlock, endBlock }; @@ -354,6 +349,7 @@ export class SigningKeyEventsCacheService { const freshEventGroup = await this.fetchEventsFallOver( firstNotCachedBlock, endBlock, + cachedEvents.headers.stakingModulesAddresses, ); const freshEvents = freshEventGroup.events; const lastEvent = freshEvents[freshEvents.length - 1]; @@ -484,7 +480,7 @@ export class SigningKeyEventsCacheService { public async getDeploymentBlockByNetwork(): Promise { const chainId = await this.providerService.getChainId(); - const block = CURATED_MODULE_DEPLOYMENT_BLOCK_NETWORK[chainId]; + const block = EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK[chainId]; if (block == null) throw new Error(`Chain ${chainId} is not supported`); return block; diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts index e704dff1..bcb8e332 100644 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts +++ b/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts @@ -10,7 +10,6 @@ import { mockLocator } from 'contracts/repository/locator/locator.mock'; import { cacheMock, newEvent } from './leveldb/leveldb.fixtures'; import { SigningKeyEventsCacheModule } from './signing-key-events-cache.module'; import { SigningKeyEventsCacheService } from './signing-key-events-cache.service'; -import { StakingModule } from 'contracts/repository/interfaces/staking-module'; describe('SigningKeyEventsCacheService', () => { const defaultCacheValue = { @@ -89,21 +88,6 @@ describe('SigningKeyEventsCacheService', () => { return endBlock; }); - const record: Record = {}; - - [ - ...cacheMock.headers.stakingModulesAddresses, - newEvent.moduleAddress, - ].forEach((key) => { - record[key] = {} as StakingModule; - }); - - jest - .spyOn(repositoryService, 'getCachedStakingModulesContracts') - .mockImplementation(() => { - return record; - }); - jest .spyOn(signingkeyEventsCacheService, 'getDeploymentBlockByNetwork') .mockImplementation(async () => { @@ -112,7 +96,10 @@ describe('SigningKeyEventsCacheService', () => { const deleteCache = jest.spyOn(dbService, 'deleteCache'); - await signingkeyEventsCacheService.handleNewBlock(endBlock); + await signingkeyEventsCacheService.handleNewBlock(endBlock, [ + ...cacheMock.headers.stakingModulesAddresses, + newEvent.moduleAddress, + ]); expect(deleteCache).toBeCalledTimes(1); diff --git a/src/staking-router/index.ts b/src/contracts/staking-router/index.ts similarity index 100% rename from src/staking-router/index.ts rename to src/contracts/staking-router/index.ts diff --git a/src/contracts/staking-router/staking-router.module.ts b/src/contracts/staking-router/staking-router.module.ts new file mode 100644 index 00000000..cf80647e --- /dev/null +++ b/src/contracts/staking-router/staking-router.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { StakingRouterService } from './staking-router.service'; + +@Module({ + providers: [StakingRouterService], + exports: [StakingRouterService], +}) +export class StakingRouterModule {} diff --git a/src/contracts/staking-router/staking-router.service.ts b/src/contracts/staking-router/staking-router.service.ts new file mode 100644 index 00000000..147d9c3b --- /dev/null +++ b/src/contracts/staking-router/staking-router.service.ts @@ -0,0 +1,46 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { RepositoryService } from 'contracts/repository'; +import { IStakingModuleAbi__factory } from 'generated'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { BlockTag, ProviderService } from 'provider'; + +@Injectable() +export class StakingRouterService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private providerService: ProviderService, + private repositoryService: RepositoryService, + ) {} + + /** + * @param blockTag + * @returns List of staking modules fetch from SR contract + */ + public async getStakingModules(blockTag: BlockTag) { + const stakingRouter = + await this.repositoryService.getCachedStakingRouterContract(); + const stakingModules = await stakingRouter.getStakingModules({ + blockTag: blockTag as any, + }); + + return stakingModules; + } + + public async getStakingModule(stakingModuleAddress: string) { + return IStakingModuleAbi__factory.connect( + stakingModuleAddress, + this.providerService.provider, + ); + } + + public async getSigningKeyAddedEvents( + startBlock: number, + endBlock: number, + address: string, + ) { + const contract = await this.getStakingModule(address); + const filter = contract.filters['SigningKeyAdded(uint256,bytes)'](); + + return await contract.queryFilter(filter, startBlock, endBlock); + } +} diff --git a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts index 92059bb4..5e5f0c8a 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts @@ -15,6 +15,8 @@ import { import { ConfigModule } from 'common/config'; import { MockProviderModule } from 'provider'; import { BlockData } from 'guardian/interfaces'; +import { StakingRouterModule } from 'contracts/staking-router'; +import { RepositoryModule } from 'contracts/repository'; describe('KeysDuplicationCheckerService', () => { let service: KeysDuplicationCheckerService; const mockSigningKeyEventsCacheService = { @@ -24,6 +26,8 @@ describe('KeysDuplicationCheckerService', () => { beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [ + StakingRouterModule, + RepositoryModule, ConfigModule.forRoot(), MockProviderModule.forRoot(), LoggerModule, diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index 68cd36d2..f3a2dc12 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -4,7 +4,6 @@ import { SecurityModule } from 'contracts/security'; import { LidoModule } from 'contracts/lido'; import { MessagesModule } from 'messages'; import { GuardianService } from './guardian.service'; -import { StakingRouterModule } from 'staking-router'; import { ScheduleModule } from 'common/schedule'; import { BlockGuardModule } from './block-guard/block-guard.module'; import { StakingModuleGuardModule } from './staking-module-guard'; @@ -13,6 +12,8 @@ import { GuardianMetricsModule } from './guardian-metrics'; import { KeysApiModule } from 'keys-api/keys-api.module'; import { SigningKeyEventsCacheModule } from 'contracts/signing-key-events-cache'; import { UnvettingModule } from './unvetting/unvetting.module'; +import { StakingModuleDataCollectorModule } from 'staking-module-data-collector'; +import { StakingRouterModule } from 'contracts/staking-router'; @Module({ imports: [ @@ -20,7 +21,7 @@ import { UnvettingModule } from './unvetting/unvetting.module'; SecurityModule, LidoModule, MessagesModule, - StakingRouterModule, + StakingModuleDataCollectorModule, ScheduleModule, BlockGuardModule, StakingModuleGuardModule, @@ -29,6 +30,7 @@ import { UnvettingModule } from './unvetting/unvetting.module'; GuardianMetricsModule, KeysApiModule, SigningKeyEventsCacheModule, + StakingRouterModule, ], providers: [GuardianService], exports: [GuardianService], diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index d314cb55..a9673902 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -12,7 +12,7 @@ import { SecurityModule } from 'contracts/security'; import { RepositoryModule, RepositoryService } from 'contracts/repository'; import { LidoModule } from 'contracts/lido'; import { MessagesModule } from 'messages'; -import { StakingRouterModule } from 'staking-router'; +import { StakingModuleDataCollectorModule } from 'staking-module-data-collector'; import { GuardianMetricsModule } from './guardian-metrics'; import { GuardianMessageModule } from './guardian-message'; import { StakingModuleGuardModule } from './staking-module-guard'; @@ -49,7 +49,7 @@ describe('GuardianService', () => { SecurityModule, LidoModule, MessagesModule, - StakingRouterModule, + StakingModuleDataCollectorModule, ScheduleModule, BlockGuardModule, StakingModuleGuardModule, diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 6ee7c4bb..5c3a5ff2 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -16,7 +16,7 @@ import { GUARDIAN_DEPOSIT_JOB_NAME, } from './guardian.constants'; import { OneAtTime } from 'common/decorators'; -import { StakingRouterService } from 'staking-router'; +import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { BlockGuardService } from './block-guard'; import { StakingModuleGuardService } from './staking-module-guard'; @@ -31,6 +31,7 @@ import { UnvettingService } from './unvetting/unvetting.service'; import { Meta } from 'keys-api/interfaces/Meta'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; +import { StakingRouterService } from 'contracts/staking-router'; @Injectable() export class GuardianService implements OnModuleInit { @@ -45,7 +46,7 @@ export class GuardianService implements OnModuleInit { private depositService: DepositService, private securityService: SecurityService, - private stakingRouterService: StakingRouterService, + private stakingModuleDataCollectorService: StakingModuleDataCollectorService, private blockGuardService: BlockGuardService, private stakingModuleGuardService: StakingModuleGuardService, @@ -57,8 +58,27 @@ export class GuardianService implements OnModuleInit { private signingKeyEventsCacheService: SigningKeyEventsCacheService, private unvettingService: UnvettingService, + + private stakingRouterService: StakingRouterService, ) {} + /** + * Retrieves the list of staking module addresses. + * + * This method fetches the cached staking modules contracts and returns the list of staking module addresses. + * + * @returns {Promise} Array of staking module addresses. + */ + public async getStakingModules(blockNumber: number): Promise { + const stakingModules = await this.stakingRouterService.getStakingModules( + blockNumber, + ); + + return stakingModules.map( + (stakingModule) => stakingModule.stakingModuleAddress, + ); + } + public async onModuleInit(): Promise { // Does not wait for completion, to avoid blocking the app initialization (async () => { @@ -67,10 +87,17 @@ export class GuardianService implements OnModuleInit { const block = await this.repositoryService.initOrWaitCachedContracts(); const blockHash = block.hash; + const stakingRouterModuleAddresses = await this.getStakingModules( + block.number, + ); + await Promise.all([ this.depositService.initialize(block.number), this.securityService.initialize({ blockHash }), - this.signingKeyEventsCacheService.initialize(block.number), + this.signingKeyEventsCacheService.initialize( + block.number, + stakingRouterModuleAddresses, + ), ]); const chainId = await this.providerService.getChainId(); @@ -99,7 +126,9 @@ export class GuardianService implements OnModuleInit { // The event cache is stored with an N block lag to avoid caching data from uncle blocks // so we don't worry about blockHash here await this.depositService.updateEventsCache(); - await this.signingKeyEventsCacheService.updateEventsCache(); + await this.signingKeyEventsCacheService.updateEventsCache( + stakingRouterModuleAddresses, + ); this.subscribeToModulesUpdates(); } catch (error) { @@ -230,7 +259,7 @@ export class GuardianService implements OnModuleInit { // collect some data and check keys const stakingModulesData: StakingModuleData[] = - await this.stakingRouterService.collectStakingModuleData({ + await this.stakingModuleDataCollectorService.collectStakingModuleData({ operatorsByModules, meta, lidoKeys, @@ -269,13 +298,17 @@ export class GuardianService implements OnModuleInit { blockData: BlockData, lidoKeys: RegistryKey[], ) { + const stakingRouterModuleAddresses = stakingModulesData.map( + (stakingModule) => stakingModule.stakingModuleAddress, + ); // update cache if needs await this.signingKeyEventsCacheService.handleNewBlock( blockData.blockNumber, + stakingRouterModuleAddresses, ); // check keys on duplicates, attempts of front-run and check signatures - await this.stakingRouterService.checkKeys( + await this.stakingModuleDataCollectorService.checkKeys( stakingModulesData, lidoKeys, blockData, diff --git a/src/staking-module-data-collector/index.ts b/src/staking-module-data-collector/index.ts new file mode 100644 index 00000000..3734cbb3 --- /dev/null +++ b/src/staking-module-data-collector/index.ts @@ -0,0 +1,2 @@ +export * from './staking-module-data-collector.module'; +export * from './staking-module-data-collector.service'; diff --git a/src/staking-router/keys.fixtures.ts b/src/staking-module-data-collector/keys.fixtures.ts similarity index 100% rename from src/staking-router/keys.fixtures.ts rename to src/staking-module-data-collector/keys.fixtures.ts diff --git a/src/staking-router/operators.fixtures.ts b/src/staking-module-data-collector/operators.fixtures.ts similarity index 100% rename from src/staking-router/operators.fixtures.ts rename to src/staking-module-data-collector/operators.fixtures.ts diff --git a/src/staking-router/staking-router.module.ts b/src/staking-module-data-collector/staking-module-data-collector.module.ts similarity index 68% rename from src/staking-router/staking-router.module.ts rename to src/staking-module-data-collector/staking-module-data-collector.module.ts index f5db2a41..5dc99d5b 100644 --- a/src/staking-router/staking-router.module.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from 'common/config'; -import { StakingRouterService } from './staking-router.service'; +import { StakingModuleDataCollectorService } from './staking-module-data-collector.service'; import { SecurityModule } from 'contracts/security'; import { StakingModuleGuardModule } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerModule } from 'guardian/duplicates'; @@ -14,7 +14,7 @@ import { GuardianMetricsModule } from 'guardian/guardian-metrics'; KeysDuplicationCheckerModule, GuardianMetricsModule, ], - providers: [StakingRouterService], - exports: [StakingRouterService], + providers: [StakingModuleDataCollectorService], + exports: [StakingModuleDataCollectorService], }) -export class StakingRouterModule {} +export class StakingModuleDataCollectorModule {} diff --git a/src/staking-router/staking-router.service.ts b/src/staking-module-data-collector/staking-module-data-collector.service.ts similarity index 99% rename from src/staking-router/staking-router.service.ts rename to src/staking-module-data-collector/staking-module-data-collector.service.ts index b57fdc06..b4105836 100644 --- a/src/staking-router/staking-router.service.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.service.ts @@ -18,7 +18,7 @@ type State = { }; @Injectable() -export class StakingRouterService { +export class StakingModuleDataCollectorService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private securityService: SecurityService, diff --git a/src/staking-router/vetted-keys.spec.ts b/src/staking-module-data-collector/vetted-keys.spec.ts similarity index 100% rename from src/staking-router/vetted-keys.spec.ts rename to src/staking-module-data-collector/vetted-keys.spec.ts diff --git a/src/staking-router/vetted-keys.ts b/src/staking-module-data-collector/vetted-keys.ts similarity index 100% rename from src/staking-router/vetted-keys.ts rename to src/staking-module-data-collector/vetted-keys.ts diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index bc22d066..3af70b29 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -47,7 +47,7 @@ import { SecurityService } from 'contracts/security'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; -import { StakingRouterService } from 'staking-router'; +import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; @@ -71,7 +71,7 @@ describe('Deposits in case of duplicates', () => { let stakingModuleGuardService: StakingModuleGuardService; let guardianMessageService: GuardianMessageService; - let stakingRouterService: StakingRouterService; + let stakingModuleDataCollectorService: StakingModuleDataCollectorService; // methods mocks let sendDepositMessage: jest.SpyInstance; @@ -152,7 +152,9 @@ describe('Deposits in case of duplicates', () => { // main service that check keys and make decision guardianService = moduleRef.get(GuardianService); stakingModuleGuardService = moduleRef.get(StakingModuleGuardService); - stakingRouterService = moduleRef.get(StakingRouterService); + stakingModuleDataCollectorService = moduleRef.get( + StakingModuleDataCollectorService, + ); }; beforeEach(async () => { @@ -213,7 +215,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // CSM, SANDBOX], }, }); @@ -348,7 +350,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], }, }); @@ -486,7 +488,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //, CSM, SANDBOX], }, }); @@ -617,7 +619,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // , CSM, SANDBOX], }, }); @@ -739,7 +741,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], }, }); @@ -757,7 +759,7 @@ describe('Deposits in case of duplicates', () => { ); const getVettedUnusedKeys = jest.spyOn( - stakingRouterService, + stakingModuleDataCollectorService, 'getVettedUnusedKeys', ); await guardianService.handleNewBlock(); @@ -838,7 +840,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], }, }); diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 6e87a17d..f0675b75 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -47,7 +47,7 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; -import { StakingRouterService } from 'staking-router'; +import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { addGuardians } from './helpers/dsm'; import { makeServer } from './server'; import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; @@ -67,7 +67,7 @@ describe('ganache e2e tests', () => { let signingKeyEventsCacheService: SigningKeyEventsCacheService; let stakingModuleGuardService: StakingModuleGuardService; let guardianMessageService: GuardianMessageService; - let stakingRouterService: StakingRouterService; + let stakingModuleDataCollectorService: StakingModuleDataCollectorService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; const setupServer = async () => { @@ -133,7 +133,9 @@ describe('ganache e2e tests', () => { // main service that check keys and make decision guardianService = moduleRef.get(GuardianService); stakingModuleGuardService = moduleRef.get(StakingModuleGuardService); - stakingRouterService = moduleRef.get(StakingRouterService); + stakingModuleDataCollectorService = moduleRef.get( + StakingModuleDataCollectorService, + ); }; beforeEach(async () => { @@ -149,7 +151,7 @@ describe('ganache e2e tests', () => { }); test( - 'skip deposit if find duplicated key', + 'skip deposit if find duplicated key8', async () => { const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); @@ -168,7 +170,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -308,7 +310,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // SANDBOX], }, }); @@ -420,7 +422,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -518,7 +520,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -528,7 +530,7 @@ describe('ganache e2e tests', () => { ); const getVettedUnusedKeys = jest.spyOn( - stakingRouterService, + stakingModuleDataCollectorService, 'getVettedUnusedKeys', ); await guardianService.handleNewBlock(); diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 1753eada..1b135330 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -210,7 +210,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); @@ -278,7 +278,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); @@ -354,7 +354,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); @@ -415,7 +415,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); @@ -527,7 +527,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 5d21aece..4015762c 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -194,7 +194,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -250,7 +250,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -327,7 +327,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -388,7 +388,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -510,7 +510,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index 8f8f05a5..c5961fa1 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -136,7 +136,7 @@ describe('Guardian balance monitoring test', () => { headers: { startBlock: blockNumber, endBlock: blockNumber, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); }; diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index fdbd19e4..5711a3b2 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -185,7 +185,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); @@ -316,7 +316,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], }, }); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 9e6b95d3..1d6520d9 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -170,7 +170,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); @@ -275,7 +275,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], }, }); From afef3c609a304125849c68e3ef4baf29e62ac920 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 19:19:43 +0400 Subject: [PATCH 02/94] fix: impr naming and contexts --- ...son => security.deprecated.pause.abi.json} | 0 src/contracts/lido/index.ts | 2 - src/contracts/lido/lido.module.ts | 8 --- src/contracts/lido/lido.service.ts | 19 ------ .../repository/repository.service.ts | 8 --- .../security/security.service.spec.ts | 23 +------ src/contracts/security/security.service.ts | 58 +++++++--------- .../staking-router.service.spec.ts} | 41 ++++++++--- .../staking-router/staking-router.service.ts | 68 +++++++++++++++++++ .../block-guard/block-guard.module.ts | 4 +- .../block-guard/block-guard.service.ts | 8 +-- src/guardian/guardian.module.ts | 2 - src/guardian/guardian.service.spec.ts | 2 - .../keys-validation.service.ts | 10 ++- .../staking-module-guard.service.ts | 2 +- .../staking-module-guard.spec.ts | 2 - .../staking-module-data-collector.module.ts | 2 + .../staking-module-data-collector.service.ts | 11 ++- test/duplicates-v3.e2e-spec.ts | 4 +- test/front-run-v3.e2e-spec.ts | 2 +- test/front-run.e2e-spec.ts | 4 -- test/guardian-balance-monitoring.e2e-spec.ts | 4 +- test/helpers/test-setup.ts | 10 ++- test/invalid-keys-v3.e2e-spec.ts | 8 +-- test/invalid-keys.e2e-spec.ts | 5 +- 25 files changed, 166 insertions(+), 141 deletions(-) rename src/abi/{security.pause.v2.abi.json => security.deprecated.pause.abi.json} (100%) delete mode 100644 src/contracts/lido/index.ts delete mode 100644 src/contracts/lido/lido.module.ts delete mode 100644 src/contracts/lido/lido.service.ts rename src/contracts/{lido/lido.service.spec.ts => staking-router/staking-router.service.spec.ts} (63%) diff --git a/src/abi/security.pause.v2.abi.json b/src/abi/security.deprecated.pause.abi.json similarity index 100% rename from src/abi/security.pause.v2.abi.json rename to src/abi/security.deprecated.pause.abi.json diff --git a/src/contracts/lido/index.ts b/src/contracts/lido/index.ts deleted file mode 100644 index 9272f996..00000000 --- a/src/contracts/lido/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './lido.module'; -export * from './lido.service'; diff --git a/src/contracts/lido/lido.module.ts b/src/contracts/lido/lido.module.ts deleted file mode 100644 index a430066d..00000000 --- a/src/contracts/lido/lido.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { LidoService } from './lido.service'; - -@Module({ - providers: [LidoService], - exports: [LidoService], -}) -export class LidoModule {} diff --git a/src/contracts/lido/lido.service.ts b/src/contracts/lido/lido.service.ts deleted file mode 100644 index a7cd8c98..00000000 --- a/src/contracts/lido/lido.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { RepositoryService } from 'contracts/repository'; -import { BlockTag } from 'provider'; - -@Injectable() -export class LidoService { - constructor(private repositoryService: RepositoryService) {} - - /** - * Returns withdrawal credentials from the contract - */ - public async getWithdrawalCredentials(blockTag?: BlockTag): Promise { - const contract = await this.repositoryService.getCachedLidoContract(); - - return await contract.getWithdrawalCredentials({ - blockTag: blockTag as any, - }); - } -} diff --git a/src/contracts/repository/repository.service.ts b/src/contracts/repository/repository.service.ts index 94db4b40..32dc36be 100644 --- a/src/contracts/repository/repository.service.ts +++ b/src/contracts/repository/repository.service.ts @@ -27,9 +27,6 @@ export class RepositoryService { string, LidoAbi | LocatorAbi | SecurityAbi | StakingRouterAbi > = {}; - // store prefixes on the current state of the contracts. - // if the contracts are updated we will change these addresses too - private cachedDSMPrefixes: Record = {}; private permanentContractsCache: Record = {}; /** @@ -152,9 +149,6 @@ export class RepositoryService { DSM_ABI, SecurityAbi__factory.connect(address, provider), ); - - // prune dsm prefixes - this.cachedDSMPrefixes = {}; } /** @@ -163,8 +157,6 @@ export class RepositoryService { private async initCachedDepositContract(blockTag: BlockTag): Promise { if (this.permanentContractsCache[DEPOSIT_ABI]) return; const depositAddress = await this.getDepositAddress(blockTag); - - console.log('depositAddress', depositAddress); const provider = this.providerService.provider; this.setPermanentContractCache( diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 2a618c2d..65eb9856 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -190,27 +190,6 @@ describe('SecurityService', () => { }); }); - describe('isDepositsPaused', () => { - it('should call contract method', async () => { - const expected = true; - - const mockProviderCalla = jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(StakingRouterAbi__factory.abi); - return iface.encodeFunctionResult('getStakingModuleIsActive', [ - expected, - ]); - }); - - const isPaused = await securityService.isModuleDepositsPaused( - TEST_MODULE_ID, - ); - expect(isPaused).toBe(!expected); - expect(mockProviderCalla).toBeCalledTimes(1); - }); - }); - describe('pauseDepositsV2', () => { const hash = hexZeroPad('0x1', 32); const blockNumber = 10; @@ -234,7 +213,7 @@ describe('SecurityService', () => { .mockImplementation(async () => ({ wait: mockWait, hash })); mockGetContractWithSigner = jest - .spyOn(securityService, 'getContractV2WithSigner') + .spyOn(securityService, 'getContractWithSignerDeprecated') .mockImplementation( () => ({ pauseDeposits: mockPauseDeposits } as any), ); diff --git a/src/contracts/security/security.service.ts b/src/contracts/security/security.service.ts index 8f0a16f0..e1df2fa9 100644 --- a/src/contracts/security/security.service.ts +++ b/src/contracts/security/security.service.ts @@ -7,7 +7,11 @@ import { METRIC_UNVET_ATTEMPTS, } from 'common/prometheus'; import { OneAtTime, StakingModuleId } from 'common/decorators'; -import { SecurityAbi, SecurityPauseV2Abi__factory } from 'generated'; +import { SecurityAbi } from 'generated'; +import { + SecurityDeprecatedPauseAbi, + SecurityDeprecatedPauseAbi__factory, +} from 'generated'; import { RepositoryService } from 'contracts/repository'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { Counter } from 'prom-client'; @@ -49,10 +53,13 @@ export class SecurityService { return contractWithSigner; } - public getContractV2WithSigner() { + /** + * Returns an instance of the deprecated v2 security contract with only the `pause` method. + */ + public getContractWithSignerDeprecated(): SecurityDeprecatedPauseAbi { const contract = this.repositoryService.getCachedDSMContract(); - const oldContract = SecurityPauseV2Abi__factory.connect( + const oldContract = SecurityDeprecatedPauseAbi__factory.connect( contract.address, this.providerService.provider, ); @@ -135,29 +142,32 @@ export class SecurityService { */ @OneAtTime() public async pauseDepositsV3( - blockNumber: number, + pauseBlockNumber: number, signature: Signature, - ): Promise { - this.logger.warn('Try to pause deposits', { blockNumber }); + ): Promise { + this.logger.warn('Try to pause deposits', { pauseBlockNumber }); this.pauseAttempts.inc(); const contract = this.getContractWithSigner(); const { r, _vs: vs } = signature; - const tx = await contract.pauseDeposits(blockNumber, { + const tx = await contract.pauseDeposits(pauseBlockNumber, { r, vs, }); this.logger.warn('Pause transaction sent', { txHash: tx.hash, - blockNumber, + pauseBlockNumber, }); - this.logger.warn('Waiting for block confirmation', { blockNumber }); + this.logger.warn('Waiting for block confirmation', { pauseBlockNumber }); const receipt = await tx.wait(); - this.logger.warn('Block confirmation received', { blockNumber }); + this.logger.warn('Block confirmation received for the pause tx', { + pauseBlockNumber, + txHash: tx.hash, + }); return receipt; } @@ -189,11 +199,11 @@ export class SecurityService { blockNumber: number, @StakingModuleId stakingModuleId: number, signature: Signature, - ): Promise { + ): Promise { this.logger.warn('Try to pause deposits', { stakingModuleId, blockNumber }); this.pauseAttempts.inc(); - const contract = this.getContractV2WithSigner(); + const contract = this.getContractWithSignerDeprecated(); const { r, _vs: vs } = signature; const tx = await contract.pauseDeposits(blockNumber, stakingModuleId, { @@ -276,7 +286,7 @@ export class SecurityService { operatorIds: string, vettedKeysByOperator: string, signature: Signature, - ): Promise { + ): Promise { this.logger.warn('Try to unvet keys for staking module', { stakingModuleId, blockNumber, @@ -355,32 +365,12 @@ export class SecurityService { /** * Check if deposits paused */ - public async isDepositContractPaused(blockTag?: BlockTag) { + public async isDepositsPaused(blockTag?: BlockTag) { const contract = await this.repositoryService.getCachedDSMContract(); return contract.isDepositsPaused({ blockTag: blockTag as any }); } - /** - * Returns the current state of deposits for module - */ - public async isModuleDepositsPaused( - stakingModuleId: number, - blockTag?: BlockTag, - ): Promise { - const stakingRouterContract = - await this.repositoryService.getCachedStakingRouterContract(); - - const isActive = await stakingRouterContract.getStakingModuleIsActive( - stakingModuleId, - { - blockTag: blockTag as any, - }, - ); - - return !isActive; - } - /** * Returns a prefix from the contract with which the deposit message should be signed */ diff --git a/src/contracts/lido/lido.service.spec.ts b/src/contracts/staking-router/staking-router.service.spec.ts similarity index 63% rename from src/contracts/lido/lido.service.spec.ts rename to src/contracts/staking-router/staking-router.service.spec.ts index cf3f8df8..6271374d 100644 --- a/src/contracts/lido/lido.service.spec.ts +++ b/src/contracts/staking-router/staking-router.service.spec.ts @@ -2,22 +2,22 @@ import { Test } from '@nestjs/testing'; import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { MockProviderModule, ProviderService } from 'provider'; -import { LidoAbi__factory } from 'generated'; import { RepositoryModule, RepositoryService } from 'contracts/repository'; import { Interface } from '@ethersproject/abi'; -import { LidoService } from './lido.service'; -import { LidoModule } from './lido.module'; import { LocatorService } from 'contracts/repository/locator/locator.service'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { mockLocator } from 'contracts/repository/locator/locator.mock'; import { mockRepository } from 'contracts/repository/repository.mock'; +import { StakingRouterAbi__factory } from 'generated'; +import { StakingRouterModule, StakingRouterService } from '.'; + +const TEST_MODULE_ID = 1; describe('SecurityService', () => { - let lidoService: LidoService; let providerService: ProviderService; - let repositoryService: RepositoryService; let locatorService: LocatorService; + let stakingRouterService: StakingRouterService; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ @@ -25,16 +25,16 @@ describe('SecurityService', () => { ConfigModule.forRoot(), MockProviderModule.forRoot(), LoggerModule, - LidoModule, RepositoryModule, + StakingRouterModule, ], }).compile(); - lidoService = moduleRef.get(LidoService); providerService = moduleRef.get(ProviderService); - repositoryService = moduleRef.get(RepositoryService); locatorService = moduleRef.get(LocatorService); + stakingRouterService = moduleRef.get(StakingRouterService); + jest .spyOn(moduleRef.get(WINSTON_MODULE_NEST_PROVIDER), 'log') .mockImplementation(() => undefined); @@ -43,6 +43,27 @@ describe('SecurityService', () => { await mockRepository(repositoryService); }); + describe('isDepositsPaused', () => { + it('should call contract method', async () => { + const expected = true; + + const mockProviderCalla = jest + .spyOn(providerService.provider, 'call') + .mockImplementation(async () => { + const iface = new Interface(StakingRouterAbi__factory.abi); + return iface.encodeFunctionResult('getStakingModuleIsActive', [ + expected, + ]); + }); + + const isPaused = await stakingRouterService.isModuleDepositsPaused( + TEST_MODULE_ID, + ); + expect(isPaused).toBe(!expected); + expect(mockProviderCalla).toBeCalledTimes(1); + }); + }); + describe('getWithdrawalCredentials', () => { it('should return withdrawal credentials', async () => { const expected = '0x' + '1'.repeat(64); @@ -50,12 +71,12 @@ describe('SecurityService', () => { const mockProviderCall = jest .spyOn(providerService.provider, 'call') .mockImplementation(async () => { - const iface = new Interface(LidoAbi__factory.abi); + const iface = new Interface(StakingRouterAbi__factory.abi); const result = [expected]; return iface.encodeFunctionResult('getWithdrawalCredentials', result); }); - const wc = await lidoService.getWithdrawalCredentials(); + const wc = await await stakingRouterService.getWithdrawalCredentials(); expect(wc).toBe(expected); expect(mockProviderCall).toBeCalledTimes(1); }); diff --git a/src/contracts/staking-router/staking-router.service.ts b/src/contracts/staking-router/staking-router.service.ts index 147d9c3b..7cd7c6d0 100644 --- a/src/contracts/staking-router/staking-router.service.ts +++ b/src/contracts/staking-router/staking-router.service.ts @@ -43,4 +43,72 @@ export class StakingRouterService { return await contract.queryFilter(filter, startBlock, endBlock); } + + /** + * Returns the current state of deposits for module + */ + public async isModuleDepositsPaused( + stakingModuleId: number, + blockTag?: BlockTag, + ): Promise { + const stakingRouterContract = + await this.repositoryService.getCachedStakingRouterContract(); + + const isActive = await stakingRouterContract.getStakingModuleIsActive( + stakingModuleId, + { + blockTag: blockTag as any, + }, + ); + + return !isActive; + } + + public async getWithdrawalCredentials(blockTag?: BlockTag): Promise { + const stakingRouterContract = + await this.repositoryService.getCachedStakingRouterContract(); + + return await stakingRouterContract.getWithdrawalCredentials({ + blockTag: blockTag as any, + }); + } } + +// describe('isDepositsPaused', () => { +// it('should call contract method', async () => { +// const expected = true; + +// const mockProviderCalla = jest +// .spyOn(providerService.provider, 'call') +// .mockImplementation(async () => { +// const iface = new Interface(StakingRouterAbi__factory.abi); +// return iface.encodeFunctionResult('getStakingModuleIsActive', [ +// expected, +// ]); +// }); + +// const isPaused = await securityService.isModuleDepositsPaused( +// TEST_MODULE_ID, +// ); +// expect(isPaused).toBe(!expected); +// expect(mockProviderCalla).toBeCalledTimes(1); +// }); +// }); + +// describe('getWithdrawalCredentials', () => { +// it('should return withdrawal credentials', async () => { +// const expected = '0x' + '1'.repeat(64); + +// const mockProviderCall = jest +// .spyOn(providerService.provider, 'call') +// .mockImplementation(async () => { +// const iface = new Interface(LidoAbi__factory.abi); +// const result = [expected]; +// return iface.encodeFunctionResult('getWithdrawalCredentials', result); +// }); + +// const wc = await lidoService.getWithdrawalCredentials(); +// expect(wc).toBe(expected); +// expect(mockProviderCall).toBeCalledTimes(1); +// }); +// }); diff --git a/src/guardian/block-guard/block-guard.module.ts b/src/guardian/block-guard/block-guard.module.ts index 2a3d51e0..cbff9002 100644 --- a/src/guardian/block-guard/block-guard.module.ts +++ b/src/guardian/block-guard/block-guard.module.ts @@ -2,17 +2,17 @@ import { Module } from '@nestjs/common'; import { DepositModule } from 'contracts/deposit'; import { SecurityModule } from 'contracts/security'; import { BlockGuardService } from './block-guard.service'; -import { LidoModule } from 'contracts/lido'; import { StakingModuleGuardModule } from 'guardian/staking-module-guard'; import { WalletModule } from 'wallet'; +import { StakingRouterModule } from 'contracts/staking-router'; @Module({ imports: [ - LidoModule, DepositModule, SecurityModule, StakingModuleGuardModule, WalletModule, + StakingRouterModule, ], providers: [BlockGuardService], exports: [BlockGuardService], diff --git a/src/guardian/block-guard/block-guard.service.ts b/src/guardian/block-guard/block-guard.service.ts index c15587ed..e26a5a26 100644 --- a/src/guardian/block-guard/block-guard.service.ts +++ b/src/guardian/block-guard/block-guard.service.ts @@ -12,9 +12,9 @@ import { METRIC_BLOCK_DATA_REQUEST_ERRORS, } from 'common/prometheus'; import { Counter, Histogram } from 'prom-client'; -import { LidoService } from 'contracts/lido'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { WalletService } from 'wallet'; +import { StakingRouterService } from 'contracts/staking-router'; @Injectable() export class BlockGuardService { @@ -34,7 +34,7 @@ export class BlockGuardService { private depositService: DepositService, private securityService: SecurityService, - private lidoService: LidoService, + private stakingRouterService: StakingRouterService, private stakingModuleGuardService: StakingModuleGuardService, ) {} @@ -92,7 +92,7 @@ export class BlockGuardService { this.depositService.getDepositRoot({ blockHash }), this.depositService.getAllDepositedEvents(blockNumber, blockHash), this.securityService.getGuardianIndex({ blockHash }), - this.lidoService.getWithdrawalCredentials({ blockHash }), + this.stakingRouterService.getWithdrawalCredentials({ blockHash }), this.securityService.version({ blockHash, }), @@ -146,7 +146,7 @@ export class BlockGuardService { securityVersion: number, ) { if (securityVersion === 3) { - const alreadyPaused = await this.securityService.isDepositContractPaused({ + const alreadyPaused = await this.securityService.isDepositsPaused({ blockHash, }); diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index f3a2dc12..53b005e2 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { DepositModule } from 'contracts/deposit'; import { SecurityModule } from 'contracts/security'; -import { LidoModule } from 'contracts/lido'; import { MessagesModule } from 'messages'; import { GuardianService } from './guardian.service'; import { ScheduleModule } from 'common/schedule'; @@ -19,7 +18,6 @@ import { StakingRouterModule } from 'contracts/staking-router'; imports: [ DepositModule, SecurityModule, - LidoModule, MessagesModule, StakingModuleDataCollectorModule, ScheduleModule, diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index a9673902..8cee7219 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -10,7 +10,6 @@ import { GuardianModule } from 'guardian'; import { DepositModule } from 'contracts/deposit'; import { SecurityModule } from 'contracts/security'; import { RepositoryModule, RepositoryService } from 'contracts/repository'; -import { LidoModule } from 'contracts/lido'; import { MessagesModule } from 'messages'; import { StakingModuleDataCollectorModule } from 'staking-module-data-collector'; import { GuardianMetricsModule } from './guardian-metrics'; @@ -47,7 +46,6 @@ describe('GuardianService', () => { RepositoryModule, DepositModule, SecurityModule, - LidoModule, MessagesModule, StakingModuleDataCollectorModule, ScheduleModule, diff --git a/src/guardian/keys-validation/keys-validation.service.ts b/src/guardian/keys-validation/keys-validation.service.ts index feafc61a..43064e4a 100644 --- a/src/guardian/keys-validation/keys-validation.service.ts +++ b/src/guardian/keys-validation/keys-validation.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { KeyValidatorInterface, bufferFromHexString, @@ -11,6 +11,7 @@ import { GENESIS_FORK_VERSION_BY_CHAIN_ID } from 'bls/bls.constants'; import { LRUCache } from 'lru-cache'; import { DEPOSIT_DATA_LRU_CACHE_SIZE } from './constants'; import { ProviderService } from 'provider'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; type DepositData = { key: Pubkey; @@ -26,6 +27,7 @@ export class KeysValidationService { constructor( private readonly keyValidator: KeyValidatorInterface, private readonly provider: ProviderService, + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, ) { this.depositDataCache = new LRUCache({ max: DEPOSIT_DATA_LRU_CACHE_SIZE }); } @@ -83,6 +85,12 @@ export class KeysValidationService { const { cachedDepositData, uncachedDepositData } = this.partitionCachedData(depositDataList); + this.logger.log('Validation status of deposit keys:', { + cachedKeysCount: cachedDepositData.length, + keysNeedingValidationCount: uncachedDepositData.length, + totalKeysCount: depositDataList.length, + }); + const validatedDepositData: [Key & DepositData, boolean][] = await this.keyValidator.validateKeys(uncachedDepositData); diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index 7e6d05ce..f979a19b 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -143,7 +143,7 @@ export class StakingModuleGuardService { public async alreadyPausedDeposits(blockData: BlockData, version: number) { if (version === 3) { - const alreadyPaused = await this.securityService.isDepositContractPaused({ + const alreadyPaused = await this.securityService.isDepositsPaused({ blockHash: blockData.blockHash, }); diff --git a/src/guardian/staking-module-guard/staking-module-guard.spec.ts b/src/guardian/staking-module-guard/staking-module-guard.spec.ts index c5e6cb31..846667ab 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.spec.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.spec.ts @@ -7,7 +7,6 @@ import { ConfigModule } from 'common/config'; import { PrometheusModule } from 'common/prometheus'; import { SecurityModule, SecurityService } from 'contracts/security'; import { RepositoryModule } from 'contracts/repository'; -import { LidoModule } from 'contracts/lido'; import { StakingModuleGuardModule } from './staking-module-guard.module'; import { GuardianMetricsModule } from '../guardian-metrics'; import { @@ -56,7 +55,6 @@ describe('StakingModuleGuardService', () => { LoggerModule, StakingModuleGuardModule, SecurityModule, - LidoModule, KeysApiModule, GuardianMetricsModule, GuardianMessageModule, diff --git a/src/staking-module-data-collector/staking-module-data-collector.module.ts b/src/staking-module-data-collector/staking-module-data-collector.module.ts index 5dc99d5b..c4c33944 100644 --- a/src/staking-module-data-collector/staking-module-data-collector.module.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.module.ts @@ -5,6 +5,7 @@ import { SecurityModule } from 'contracts/security'; import { StakingModuleGuardModule } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerModule } from 'guardian/duplicates'; import { GuardianMetricsModule } from 'guardian/guardian-metrics'; +import { StakingRouterModule } from 'contracts/staking-router'; @Module({ imports: [ @@ -13,6 +14,7 @@ import { GuardianMetricsModule } from 'guardian/guardian-metrics'; StakingModuleGuardModule, KeysDuplicationCheckerModule, GuardianMetricsModule, + StakingRouterModule, ], providers: [StakingModuleDataCollectorService], exports: [StakingModuleDataCollectorService], diff --git a/src/staking-module-data-collector/staking-module-data-collector.service.ts b/src/staking-module-data-collector/staking-module-data-collector.service.ts index b4105836..c48d1f16 100644 --- a/src/staking-module-data-collector/staking-module-data-collector.service.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.service.ts @@ -9,6 +9,7 @@ import { SecurityService } from 'contracts/security'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerService } from 'guardian/duplicates'; import { GuardianMetricsService } from 'guardian/guardian-metrics'; +import { StakingRouterService } from 'contracts/staking-router'; type State = { operatorsByModules: SROperatorListWithModule[]; @@ -25,6 +26,7 @@ export class StakingModuleDataCollectorService { private stakingModuleGuardService: StakingModuleGuardService, private keysDuplicationCheckerService: KeysDuplicationCheckerService, private guardianMetricsService: GuardianMetricsService, + private stakingRouterService: StakingRouterService, ) {} /** @@ -51,9 +53,12 @@ export class StakingModuleDataCollectorService { // check pause const isModuleDepositsPaused = - await this.securityService.isModuleDepositsPaused(stakingModule.id, { - blockHash: blockData.blockHash, - }); + await this.stakingRouterService.isModuleDepositsPaused( + stakingModule.id, + { + blockHash: blockData.blockHash, + }, + ); return { isModuleDepositsPaused, diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index 3af70b29..e3583a7c 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -19,8 +19,6 @@ import { NOP_REGISTRY, SIMPLE_DVT, UNLOCKED_ACCOUNTS, - CSM, - SANDBOX, } from './constants'; // Contract Factories @@ -116,7 +114,7 @@ describe('Deposits in case of duplicates', () => { // we cant make real unvetting unvetSigningKeys = jest .spyOn(securityService, 'unvetSigningKeys') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(null as any)); }; const setupTestingServices = async (moduleRef) => { diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 1b135330..cf6579a0 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -157,7 +157,7 @@ describe('ganache e2e tests', () => { // we cant make real unvetting unvetSigningKeys = jest .spyOn(securityService, 'unvetSigningKeys') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(null as any)); }; beforeEach(async () => { diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 4015762c..276dddf7 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -600,10 +600,6 @@ describe('ganache e2e tests', () => { // Your assertions after mining the block const newBlock = await providerService.provider.getBlock('latest'); - console.log('Current block number:', { - newBlock: newBlock.number, - currentBlock: currentBlock.number, - }); setupMockModules( newBlock, diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index c5961fa1..beb81977 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -28,10 +28,8 @@ import { GANACHE_PORT, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, UNLOCKED_ACCOUNTS, FORK_BLOCK, - CSM, } from './constants'; // Contract and Service Imports @@ -253,6 +251,6 @@ describe('Guardian balance monitoring test', () => { const mockUnvettingMethod = () => { unvetSigningKeys = jest .spyOn(securityService, 'unvetSigningKeys') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(null as any)); }; }); diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index d2d37a08..5a0b67b6 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -3,7 +3,6 @@ import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { PrometheusModule } from 'common/prometheus'; import { DepositModule } from 'contracts/deposit'; -import { LidoModule } from 'contracts/lido'; import { RepositoryModule } from 'contracts/repository'; import { SecurityModule } from 'contracts/security'; import { GuardianModule } from 'guardian'; @@ -12,6 +11,7 @@ import { GanacheProviderModule } from 'provider'; import { WalletModule } from 'wallet'; import { LevelDBService } from 'contracts/deposit/leveldb'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; export const setupTestingModule = async () => { const moduleRef = await Test.createTestingModule({ @@ -24,12 +24,18 @@ export const setupTestingModule = async () => { RepositoryModule, WalletModule, KeysApiModule, - LidoModule, DepositModule, SecurityModule, ], }).compile(); + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); + return moduleRef; }; diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index 5711a3b2..47086e4f 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -19,11 +19,9 @@ import { pk, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, LIDO_WC, UNLOCKED_ACCOUNTS, FORK_BLOCK, - CSM, } from './constants'; // Mock rabbit straight away @@ -152,7 +150,7 @@ describe('ganache e2e tests', () => { // we cant make real unvetting unvetSigningKeys = jest .spyOn(securityService, 'unvetSigningKeys') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(null as any)); }; beforeEach(async () => { @@ -185,7 +183,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -316,7 +314,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 1d6520d9..a118791d 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -19,7 +19,6 @@ import { pk, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, LIDO_WC, FORK_BLOCK_V2, UNLOCKED_ACCOUNTS_V2, @@ -170,7 +169,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -275,7 +274,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); From 8f77d7ca45696dba951bbd6b67964ca3d4f2adbe Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 21:24:29 +0400 Subject: [PATCH 03/94] fix: lint --- src/contracts/security/security.service.spec.ts | 10 ++-------- test/duplicates-v3.e2e-spec.ts | 14 ++++++-------- test/duplicates.e2e-spec.ts | 8 ++++---- test/front-run-v3.e2e-spec.ts | 16 +++++++--------- test/front-run.e2e-spec.ts | 13 ++++++------- test/guardian-balance-monitoring.e2e-spec.ts | 4 +--- test/invalid-keys-v3.e2e-spec.ts | 6 ++---- test/invalid-keys.e2e-spec.ts | 5 ++--- 8 files changed, 30 insertions(+), 46 deletions(-) diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 2a618c2d..2a5a289d 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -31,9 +31,6 @@ describe('SecurityService', () => { let repositoryService: RepositoryService; let walletService: WalletService; let loggerService: LoggerService; - let mockGetAttestMessagePrefix: jest.SpyInstance, []>; - // let mockGetPauseMessagePrefix: jest.SpyInstance, []>; - beforeEach(async () => { const moduleRef = await Test.createTestingModule({ imports: [ @@ -57,9 +54,7 @@ describe('SecurityService', () => { mockLocator(moduleRef.get(LocatorService)); - const repo = await mockRepository(repositoryService); - // mockGetAttestMessagePrefix = repo.mockGetAttestMessagePrefix; - // mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; + await mockRepository(repositoryService); }); describe('getGuardians', () => { @@ -223,8 +218,7 @@ describe('SecurityService', () => { beforeEach(async () => { mockWait = jest.fn().mockImplementation(async () => undefined); - const repo = await mockRepository(repositoryService); - // mockGetPauseMessagePrefix = repo.mockGetPauseMessagePrefix; + await mockRepository(repositoryService); mockGetPauseMessagePrefix = jest .spyOn(securityService, 'getPauseMessagePrefix') .mockImplementation(async () => hexZeroPad('0x2', 32)); diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index 3af70b29..236398d7 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -19,8 +19,6 @@ import { NOP_REGISTRY, SIMPLE_DVT, UNLOCKED_ACCOUNTS, - CSM, - SANDBOX, } from './constants'; // Contract Factories @@ -215,7 +213,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -350,7 +348,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -488,7 +486,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //, CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -619,7 +617,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // , CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -741,7 +739,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -840,7 +838,7 @@ describe('Deposits in case of duplicates', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //CSM, SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index f0675b75..35880e0f 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -170,7 +170,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -310,7 +310,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], // SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -422,7 +422,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -520,7 +520,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number - 2, endBlock: currentBlock.number - 1, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 1b135330..048bf440 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -28,9 +28,7 @@ import { pk, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, UNLOCKED_ACCOUNTS, - CSM, SECURITY_MODULE, } from './constants'; @@ -186,7 +184,7 @@ describe('ganache e2e tests', () => { key: toHexString(pk), depositSignature: toHexString(signature), operatorIndex: mockOperator1.index, - used: false, // TODO: true + used: false, index: 1, moduleAddress: NOP_REGISTRY, }, @@ -210,7 +208,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -278,7 +276,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -298,7 +296,7 @@ describe('ganache e2e tests', () => { key: toHexString(pk), depositSignature: toHexString(goodSign), operatorIndex: mockOperator1.index, - used: false, // TODO: true + used: false, index: 0, moduleAddress: NOP_REGISTRY, }, @@ -354,7 +352,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -415,7 +413,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -527,7 +525,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 4015762c..8f75682e 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -32,7 +32,6 @@ import { UNLOCKED_ACCOUNTS_V2, FORK_BLOCK_V2, SECURITY_MODULE_OWNER_V2, - SANDBOX, } from './constants'; // Contract Factories @@ -188,13 +187,13 @@ describe('ganache e2e tests', () => { }, }); - // dont set events for keys as we check this cahce only in case of duplicated keys + // dont set events for keys as we check this cache only in case of duplicated keys await signingKeyEventsCacheService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -250,7 +249,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -327,7 +326,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -388,7 +387,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -510,7 +509,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index c5961fa1..a98bf69d 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -28,10 +28,8 @@ import { GANACHE_PORT, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, UNLOCKED_ACCOUNTS, FORK_BLOCK, - CSM, } from './constants'; // Contract and Service Imports @@ -136,7 +134,7 @@ describe('Guardian balance monitoring test', () => { headers: { startBlock: blockNumber, endBlock: blockNumber, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); }; diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index 5711a3b2..4bd93ee5 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -19,11 +19,9 @@ import { pk, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, LIDO_WC, UNLOCKED_ACCOUNTS, FORK_BLOCK, - CSM, } from './constants'; // Mock rabbit straight away @@ -185,7 +183,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -316,7 +314,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX, CSM], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 1d6520d9..a118791d 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -19,7 +19,6 @@ import { pk, NOP_REGISTRY, SIMPLE_DVT, - SANDBOX, LIDO_WC, FORK_BLOCK_V2, UNLOCKED_ACCOUNTS_V2, @@ -170,7 +169,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); @@ -275,7 +274,7 @@ describe('ganache e2e tests', () => { headers: { startBlock: currentBlock.number, endBlock: currentBlock.number, - stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], //SANDBOX], + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, }); From ebe5eea0aba2742bc2a8b5250c01f2b16e98b423 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 21:25:03 +0400 Subject: [PATCH 04/94] fix: lint --- test/duplicates.e2e-spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 35880e0f..960a29e1 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -18,7 +18,6 @@ import { SIMPLE_DVT, UNLOCKED_ACCOUNTS_V2, FORK_BLOCK_V2, - SANDBOX, SECURITY_MODULE_V2, SECURITY_MODULE_OWNER_V2, } from './constants'; From 2a114dea77131c0dae4eb8ad8e2ba954690290c8 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 21:29:11 +0400 Subject: [PATCH 05/94] fix: lint --- src/contracts/security/security.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 3b38ad13..3127d5c2 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -4,7 +4,7 @@ import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { MockProviderModule, ProviderService } from 'provider'; import { WalletService } from 'wallet'; -import { SecurityAbi__factory, StakingRouterAbi__factory } from 'generated'; +import { SecurityAbi__factory } from 'generated'; import { RepositoryModule, RepositoryService } from 'contracts/repository'; import { LocatorService } from 'contracts/repository/locator/locator.service'; import { Interface } from '@ethersproject/abi'; From 90e76d61925ae7f7d1a5dfae637a5ef078206699 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 27 Aug 2024 22:30:40 +0400 Subject: [PATCH 06/94] fix: test --- .../security/security.service.spec.ts | 59 +++++++++++++++++++ .../staking-router/staking-router.service.ts | 39 ------------ 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 3127d5c2..c4f94fb4 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -31,6 +31,7 @@ describe('SecurityService', () => { let repositoryService: RepositoryService; let walletService: WalletService; let loggerService: LoggerService; + beforeEach(async () => { const moduleRef = await Test.createTestingModule({ imports: [ @@ -246,4 +247,62 @@ describe('SecurityService', () => { expect(mockGetContractWithSigner).toBeCalledTimes(1); }); }); + + describe('messages prefixes', () => { + const blockNumber = 10; + + beforeEach(async () => { + jest + .spyOn(repositoryService, 'getDepositAddress') + .mockImplementation(async () => '0x' + '5'.repeat(40)); + }); + + it('getAttestMessagePrefix', async () => { + const expected = '0x' + '1'.repeat(64); + + const mockProviderCall = jest + .spyOn(providerService.provider, 'call') + .mockImplementation(async () => { + const iface = new Interface(SecurityAbi__factory.abi); + const result = [expected]; + return iface.encodeFunctionResult('ATTEST_MESSAGE_PREFIX', result); + }); + + const prefix = await securityService.getAttestMessagePrefix(blockNumber); + expect(prefix).toBe(expected); + expect(mockProviderCall).toBeCalledTimes(1); + }); + + it('getPauseMessagePrefix', async () => { + const expected = '0x' + '1'.repeat(64); + + const mockProviderCall = jest + .spyOn(providerService.provider, 'call') + .mockImplementation(async () => { + const iface = new Interface(SecurityAbi__factory.abi); + const result = [expected]; + return iface.encodeFunctionResult('PAUSE_MESSAGE_PREFIX', result); + }); + + const prefix = await securityService.getPauseMessagePrefix(blockNumber); + expect(prefix).toBe(expected); + expect(mockProviderCall).toBeCalledTimes(1); + }); + + it('getUnvetMessagePrefix', async () => { + const expected = '0x' + '1'.repeat(64); + + const mockProviderCall = jest + .spyOn(providerService.provider, 'call') + .mockImplementation(async () => { + const iface = new Interface(SecurityAbi__factory.abi); + const result = [expected]; + return iface.encodeFunctionResult('UNVET_MESSAGE_PREFIX', result); + }); + + const prefix = await securityService.getUnvetMessagePrefix(blockNumber); + expect(prefix).toBe(expected); + expect(mockProviderCall).toBeCalledTimes(1); + }); + }); }); diff --git a/src/contracts/staking-router/staking-router.service.ts b/src/contracts/staking-router/staking-router.service.ts index 7cd7c6d0..22aed9a2 100644 --- a/src/contracts/staking-router/staking-router.service.ts +++ b/src/contracts/staking-router/staking-router.service.ts @@ -73,42 +73,3 @@ export class StakingRouterService { }); } } - -// describe('isDepositsPaused', () => { -// it('should call contract method', async () => { -// const expected = true; - -// const mockProviderCalla = jest -// .spyOn(providerService.provider, 'call') -// .mockImplementation(async () => { -// const iface = new Interface(StakingRouterAbi__factory.abi); -// return iface.encodeFunctionResult('getStakingModuleIsActive', [ -// expected, -// ]); -// }); - -// const isPaused = await securityService.isModuleDepositsPaused( -// TEST_MODULE_ID, -// ); -// expect(isPaused).toBe(!expected); -// expect(mockProviderCalla).toBeCalledTimes(1); -// }); -// }); - -// describe('getWithdrawalCredentials', () => { -// it('should return withdrawal credentials', async () => { -// const expected = '0x' + '1'.repeat(64); - -// const mockProviderCall = jest -// .spyOn(providerService.provider, 'call') -// .mockImplementation(async () => { -// const iface = new Interface(LidoAbi__factory.abi); -// const result = [expected]; -// return iface.encodeFunctionResult('getWithdrawalCredentials', result); -// }); - -// const wc = await lidoService.getWithdrawalCredentials(); -// expect(wc).toBe(expected); -// expect(mockProviderCall).toBeCalledTimes(1); -// }); -// }); From 437541dd4c8e241feeba06ce2bb7748a65ae2033 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 28 Aug 2024 19:07:54 +0400 Subject: [PATCH 07/94] fix: move transform ro wei logic in decorator --- .../config/config-loader.service.spec.ts | 188 +++++++++++++----- src/common/config/exceptions.ts | 1 - src/common/config/in-memory-configuration.ts | 39 +--- src/common/decorators/transform-to-wei.ts | 16 ++ 4 files changed, 164 insertions(+), 80 deletions(-) delete mode 100644 src/common/config/exceptions.ts create mode 100644 src/common/decorators/transform-to-wei.ts diff --git a/src/common/config/config-loader.service.spec.ts b/src/common/config/config-loader.service.spec.ts index 69a650cd..729caf92 100644 --- a/src/common/config/config-loader.service.spec.ts +++ b/src/common/config/config-loader.service.spec.ts @@ -1,7 +1,8 @@ +import { BigNumber } from '@ethersproject/bignumber'; import { Test } from '@nestjs/testing'; import { plainToClass } from 'class-transformer'; +import { validateOrReject, ValidationError } from 'class-validator'; import { ConfigLoaderService } from './config-loader.service'; -import { BadConfigException } from './exceptions'; import { InMemoryConfiguration } from './in-memory-configuration'; const FAKE_FS = { @@ -171,82 +172,171 @@ describe('ConfigLoaderService base spec', () => { }); describe('balance', () => { - let configLoaderService: ConfigLoaderService; const DEFAULTS_WITH_RABBIT = { ...DEFAULTS, RABBITMQ_PASSCODE: 'some-rabbit-passcode', }; - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ConfigLoaderService], - }).compile(); + test('should throw an error for an excessively small WALLET_CRITICAL_BALANCE', async () => { + const WALLET_CRITICAL_BALANCE = '0.0000000000000000001'; + const plainConfig = plainToClass(InMemoryConfiguration, { + WALLET_CRITICAL_BALANCE, + ...DEFAULTS_WITH_RABBIT, + }); - configLoaderService = moduleRef.get(ConfigLoaderService); + expect(plainConfig).toHaveProperty('WALLET_CRITICAL_BALANCE'); + expect(plainConfig.WALLET_CRITICAL_BALANCE).toBeNaN(); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).catch((errors) => { + expect(errors).toBeInstanceOf(Array); + expect(errors.length).toBe(1); + expect(errors[0]).toBeInstanceOf(ValidationError); + expect(errors[0].property).toBe('WALLET_CRITICAL_BALANCE'); + expect(errors[0].constraints).toHaveProperty( + 'isInstance', + 'WALLET_CRITICAL_BALANCE must be an instance of BigNumber', + ); + }); }); - test('should throw an error for an excessively small WALLET_CRITICAL_BALANCE', async () => { - const WALLET_CRITICAL_BALANCE = '0.0000000000000000001'; - try { - plainToClass(InMemoryConfiguration, { - WALLET_CRITICAL_BALANCE, - }); - - throw new Error('Expected BadConfigException was not thrown'); - } catch (error) { - if (error instanceof BadConfigException) { - expect(error.message).toBe( - `Invalid WALLET_CRITICAL_BALANCE value: ${WALLET_CRITICAL_BALANCE}. Please ensure it's a valid Ether amount that can be converted to Wei.`, - ); - } else { - throw new Error(`Unexpected error type`); - } - } + test('should throw an error for an empty WALLET_CRITICAL_BALANCE', async () => { + const WALLET_CRITICAL_BALANCE = ''; + const plainConfig = plainToClass(InMemoryConfiguration, { + WALLET_CRITICAL_BALANCE, + ...DEFAULTS_WITH_RABBIT, + }); + + expect(plainConfig).toHaveProperty('WALLET_CRITICAL_BALANCE'); + expect(plainConfig.WALLET_CRITICAL_BALANCE).toBeNaN(); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).catch((errors) => { + expect(errors).toBeInstanceOf(Array); + expect(errors.length).toBe(1); + expect(errors[0]).toBeInstanceOf(ValidationError); + expect(errors[0].property).toBe('WALLET_CRITICAL_BALANCE'); + expect(errors[0].constraints).toHaveProperty( + 'isInstance', + 'WALLET_CRITICAL_BALANCE must be an instance of BigNumber', + ); + }); }); test('should handle normal WALLET_CRITICAL_BALANCE values correctly', async () => { - const prepConfig = plainToClass(InMemoryConfiguration, { + const plainConfig = plainToClass(InMemoryConfiguration, { WALLET_CRITICAL_BALANCE: '0.2', ...DEFAULTS_WITH_RABBIT, }); - const config = await configLoaderService.loadSecrets(prepConfig); + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).then(() => { + expect(plainConfig).toHaveProperty('WALLET_CRITICAL_BALANCE'); + expect(plainConfig.WALLET_CRITICAL_BALANCE).toBeInstanceOf(BigNumber); + expect(plainConfig.WALLET_CRITICAL_BALANCE.toString()).toBe( + '200000000000000000', + ); + }); + }); - expect(config).toHaveProperty('WALLET_CRITICAL_BALANCE'); - expect(config.WALLET_CRITICAL_BALANCE.toString()).toBe( - '200000000000000000', - ); // Equivalent of 0.2 ETH in Wei + test('should use default WALLET_CRITICAL_BALANCE value', async () => { + const plainConfig = plainToClass(InMemoryConfiguration, { + ...DEFAULTS_WITH_RABBIT, + }); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).then(() => { + expect(plainConfig).toHaveProperty('WALLET_CRITICAL_BALANCE'); + expect(plainConfig.WALLET_CRITICAL_BALANCE).toBeInstanceOf(BigNumber); + expect(plainConfig.WALLET_CRITICAL_BALANCE.toString()).toBe( + '200000000000000000', + ); + }); }); test('should throw an error for an excessively small WALLET_MIN_BALANCE', async () => { const WALLET_MIN_BALANCE = '0.0000000000000000001'; - try { - plainToClass(InMemoryConfiguration, { - WALLET_MIN_BALANCE, - }); - - throw new Error('Expected BadConfigException was not thrown'); - } catch (error) { - if (error instanceof BadConfigException) { - expect(error.message).toBe( - `Invalid WALLET_MIN_BALANCE value: ${WALLET_MIN_BALANCE}. Please ensure it's a valid Ether amount that can be converted to Wei.`, - ); - } else { - throw new Error(`Unexpected error type`); - } - } + const plainConfig = plainToClass(InMemoryConfiguration, { + WALLET_MIN_BALANCE, + ...DEFAULTS_WITH_RABBIT, + }); + + expect(plainConfig).toHaveProperty('WALLET_MIN_BALANCE'); + expect(plainConfig.WALLET_MIN_BALANCE).toBeNaN(); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).catch((errors) => { + expect(errors).toBeInstanceOf(Array); + expect(errors.length).toBe(1); + expect(errors[0]).toBeInstanceOf(ValidationError); + expect(errors[0].property).toBe('WALLET_MIN_BALANCE'); + expect(errors[0].constraints).toHaveProperty( + 'isInstance', + 'WALLET_MIN_BALANCE must be an instance of BigNumber', + ); + }); + }); + + test('should throw an error for an empty WALLET_MIN_BALANCE', async () => { + const WALLET_MIN_BALANCE = ''; + const plainConfig = plainToClass(InMemoryConfiguration, { + WALLET_MIN_BALANCE, + ...DEFAULTS_WITH_RABBIT, + }); + + expect(plainConfig).toHaveProperty('WALLET_MIN_BALANCE'); + expect(plainConfig.WALLET_MIN_BALANCE).toBeNaN(); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).catch((errors) => { + expect(errors).toBeInstanceOf(Array); + expect(errors.length).toBe(1); + expect(errors[0]).toBeInstanceOf(ValidationError); + expect(errors[0].property).toBe('WALLET_MIN_BALANCE'); + expect(errors[0].constraints).toHaveProperty( + 'isInstance', + 'WALLET_MIN_BALANCE must be an instance of BigNumber', + ); + }); }); test('should handle normal WALLET_MIN_BALANCE values correctly', async () => { - const prepConfig = plainToClass(InMemoryConfiguration, { + const plainConfig = plainToClass(InMemoryConfiguration, { WALLET_MIN_BALANCE: '0.2', ...DEFAULTS_WITH_RABBIT, }); - const config = await configLoaderService.loadSecrets(prepConfig); + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).then(() => { + expect(plainConfig).toHaveProperty('WALLET_MIN_BALANCE'); + expect(plainConfig.WALLET_MIN_BALANCE).toBeInstanceOf(BigNumber); + expect(plainConfig.WALLET_MIN_BALANCE.toString()).toBe( + '200000000000000000', + ); + }); + }); - expect(config).toHaveProperty('WALLET_MIN_BALANCE'); - expect(config.WALLET_MIN_BALANCE.toString()).toBe('200000000000000000'); // Equivalent of 0.2 ETH in Wei + test('should use default WALLET_MIN_BALANCE value', async () => { + const plainConfig = plainToClass(InMemoryConfiguration, { + ...DEFAULTS_WITH_RABBIT, + }); + + await validateOrReject(plainConfig, { + validationError: { target: false, value: false }, + }).then(() => { + expect(plainConfig).toHaveProperty('WALLET_MIN_BALANCE'); + expect(plainConfig.WALLET_MIN_BALANCE).toBeInstanceOf(BigNumber); + expect(plainConfig.WALLET_MIN_BALANCE.toString()).toBe( + '500000000000000000', + ); + }); }); }); }); diff --git a/src/common/config/exceptions.ts b/src/common/config/exceptions.ts deleted file mode 100644 index 9810c4c0..00000000 --- a/src/common/config/exceptions.ts +++ /dev/null @@ -1 +0,0 @@ -export class BadConfigException extends Error {} diff --git a/src/common/config/in-memory-configuration.ts b/src/common/config/in-memory-configuration.ts index 636631df..e17dc2bf 100644 --- a/src/common/config/in-memory-configuration.ts +++ b/src/common/config/in-memory-configuration.ts @@ -1,6 +1,7 @@ import { Transform } from 'class-transformer'; import { IsIn, + IsInstance, IsNotEmpty, IsNumber, IsOptional, @@ -12,8 +13,8 @@ import { Injectable } from '@nestjs/common'; import { Configuration, PubsubService } from './configuration'; import { SASLMechanism } from '../../transport'; import { implementationOf } from '../di/decorators/implementationOf'; -import { ethers } from 'ethers'; -import { BadConfigException } from './exceptions'; +import { ethers, BigNumber } from 'ethers'; +import { TransformToWei } from 'common/decorators/transform-to-wei'; const RABBITMQ = 'rabbitmq'; const KAFKA = 'kafka'; @@ -142,34 +143,12 @@ export class InMemoryConfiguration implements Configuration { LOCATOR_DEVNET_ADDRESS = ''; @IsOptional() - @Transform( - ({ value }) => { - try { - const weiValue = ethers.utils.parseEther(value || '0.5'); - return weiValue; - } catch (error) { - throw new BadConfigException( - `Invalid WALLET_MIN_BALANCE value: ${value}. Please ensure it's a valid Ether amount that can be converted to Wei.`, - ); - } - }, - { toClassOnly: true }, - ) - WALLET_MIN_BALANCE: ethers.BigNumber = ethers.utils.parseEther('0.5'); + @TransformToWei() + @IsInstance(BigNumber) + WALLET_MIN_BALANCE: BigNumber = ethers.utils.parseEther('0.5'); @IsOptional() - @Transform( - ({ value }) => { - try { - const weiValue = ethers.utils.parseEther(value || '0.2'); - return weiValue; - } catch (error) { - throw new BadConfigException( - `Invalid WALLET_CRITICAL_BALANCE value: ${value}. Please ensure it's a valid Ether amount that can be converted to Wei.`, - ); - } - }, - { toClassOnly: true }, - ) - WALLET_CRITICAL_BALANCE: ethers.BigNumber = ethers.utils.parseEther('0.2'); + @TransformToWei() + @IsInstance(BigNumber) + WALLET_CRITICAL_BALANCE: BigNumber = ethers.utils.parseEther('0.2'); } diff --git a/src/common/decorators/transform-to-wei.ts b/src/common/decorators/transform-to-wei.ts new file mode 100644 index 00000000..5960636a --- /dev/null +++ b/src/common/decorators/transform-to-wei.ts @@ -0,0 +1,16 @@ +import { Transform } from 'class-transformer'; +import { ethers } from 'ethers'; + +export function TransformToWei() { + return Transform( + ({ value }) => { + try { + const weiValue = ethers.utils.parseEther(value); + return weiValue; + } catch (error) { + return NaN; + } + }, + { toClassOnly: true }, + ); +} From 8a86b32e3fa2bb35a6e521f33be0f23c479acdf2 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 28 Aug 2024 21:53:01 +0400 Subject: [PATCH 08/94] fix: StakingModuleId -> OneAtTimeCallId --- src/common/decorators/one-at-time.spec.ts | 26 ++++++++-------- src/common/decorators/one-at-time.ts | 35 +++++++++++----------- src/contracts/security/security.service.ts | 6 ++-- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/common/decorators/one-at-time.spec.ts b/src/common/decorators/one-at-time.spec.ts index 3088667c..514b14d7 100644 --- a/src/common/decorators/one-at-time.spec.ts +++ b/src/common/decorators/one-at-time.spec.ts @@ -1,8 +1,8 @@ -import { OneAtTime, StakingModuleId } from './one-at-time'; +import { OneAtTime, OneAtTimeCallId } from './one-at-time'; class TestOneAtTime { public value; - public stakingModuleId = new Map(); + public oneAtTimeCallId = new Map(); public executionLog: string[] = []; @@ -20,9 +20,9 @@ class TestOneAtTime { } @OneAtTime(2000) - async testStakingModuleId(@StakingModuleId id, value) { + async testOneAtTimeCallId(@OneAtTimeCallId id, value) { this.executionLog.push(`start-${id}-${value}`); - this.stakingModuleId.set(id, value); + this.oneAtTimeCallId.set(id, value); await this.sleep(1000); this.executionLog.push(`end-${id}-${value}`); @@ -50,12 +50,12 @@ it('OneAtTime', async () => { it('StakingModuleId', async () => { const testOneAtTime = new TestOneAtTime(); - expect(testOneAtTime.stakingModuleId.get(1)).toBeUndefined(); - expect(testOneAtTime.stakingModuleId.get(2)).toBeUndefined(); + expect(testOneAtTime.oneAtTimeCallId.get(1)).toBeUndefined(); + expect(testOneAtTime.oneAtTimeCallId.get(2)).toBeUndefined(); - testOneAtTime.testStakingModuleId(1, 1); - testOneAtTime.testStakingModuleId(1, 2); - testOneAtTime.testStakingModuleId(2, 2); + testOneAtTime.testOneAtTimeCallId(1, 1); + testOneAtTime.testOneAtTimeCallId(1, 2); + testOneAtTime.testOneAtTimeCallId(2, 2); await testOneAtTime.sleep(1500); @@ -64,15 +64,15 @@ it('StakingModuleId', async () => { expect.arrayContaining(['start-1-1', 'end-1-1', 'start-2-2', 'end-2-2']), ); - expect(testOneAtTime.stakingModuleId.get(1)).toEqual(1); - expect(testOneAtTime.stakingModuleId.get(2)).toEqual(2); + expect(testOneAtTime.oneAtTimeCallId.get(1)).toEqual(1); + expect(testOneAtTime.oneAtTimeCallId.get(2)).toEqual(2); testOneAtTime.executionLog = []; - await testOneAtTime.testStakingModuleId(1, 2); + await testOneAtTime.testOneAtTimeCallId(1, 2); expect(testOneAtTime.executionLog.length).toEqual(2); expect(testOneAtTime.executionLog).toEqual( expect.arrayContaining(['start-1-2', 'end-1-2']), ); - expect(testOneAtTime.stakingModuleId.get(1)).toEqual(2); + expect(testOneAtTime.oneAtTimeCallId.get(1)).toEqual(2); }); diff --git a/src/common/decorators/one-at-time.ts b/src/common/decorators/one-at-time.ts index 501ac74e..9060a03b 100644 --- a/src/common/decorators/one-at-time.ts +++ b/src/common/decorators/one-at-time.ts @@ -1,27 +1,28 @@ import 'reflect-metadata'; -const stakingModuleId = Symbol('StakingModuleId'); +const oneAtTimeCallIdKey = Symbol('OneAtTimeCallId'); /** - * A decorator that marks a specific parameter in a method for identifying the staking module ID + * A decorator that marks a specific parameter in a method for identifying the OneAtTime call ID. + * This ID allows the same method to be executed concurrently with different parameters. */ -export function StakingModuleId( +export function OneAtTimeCallId( target: any, propertyKey: string | symbol, parameterIndex: number, ) { const existingMetadata: number[] = - Reflect.getOwnMetadata(stakingModuleId, target, propertyKey) || []; + Reflect.getOwnMetadata(oneAtTimeCallIdKey, target, propertyKey) || []; if (existingMetadata.length === 0) { Reflect.defineMetadata( - stakingModuleId, + oneAtTimeCallIdKey, [parameterIndex], target, propertyKey, ); } else { throw new Error( - `StakingModuleId decorator can only be applied to one parameter in method ${String( + `OneAtTimeCallId decorator can only be applied to one parameter in method ${String( propertyKey, )}. It is already applied to parameter index ${existingMetadata[0]}`, ); @@ -29,9 +30,9 @@ export function StakingModuleId( } /** - * A decorator factory that produces a method decorator ensuring a function executes one at a time. + * A decorator factory that ensures a function executes one at a time. * Calls to the decorated method are restricted so that only one instance can be executed concurrently, - * either globally or per staking module ID + * either globally or per OneAtTime call ID. * A stuck function with the OneAtTime decorator will prevent the next executions of this function. * That is why a timeout is set. If the execution of the promise is stuck, a timeout will occur. The default timeout is 10 minutes. */ @@ -48,13 +49,13 @@ export function OneAtTime Promise>( const isExecutingMap = new Map(); descriptor.value = async function (this: any, ...args) { - const stakingModuleIdArgs = - Reflect.getMetadata(stakingModuleId, target, propertyName) || []; + const oneAtTimeCallIdArgs = + Reflect.getMetadata(oneAtTimeCallIdKey, target, propertyName) || []; - const moduleId = - stakingModuleIdArgs.length > 0 ? args[stakingModuleIdArgs[0]] : null; + const callId = + oneAtTimeCallIdArgs.length > 0 ? args[oneAtTimeCallIdArgs[0]] : null; - if ((moduleId && isExecutingMap.get(moduleId)) || isExecuting) { + if ((callId && isExecutingMap.get(callId)) || isExecuting) { this.logger?.debug(`Already running ${propertyName}`, { propertyName, executing: isExecuting, @@ -63,8 +64,8 @@ export function OneAtTime Promise>( return; } - if (moduleId) { - isExecutingMap.set(moduleId, true); + if (callId) { + isExecutingMap.set(callId, true); } else { isExecuting = true; } @@ -86,8 +87,8 @@ export function OneAtTime Promise>( } catch (error) { this.logger.error(error); } finally { - if (moduleId) { - isExecutingMap.set(moduleId, false); + if (callId) { + isExecutingMap.set(callId, false); } else { isExecuting = false; } diff --git a/src/contracts/security/security.service.ts b/src/contracts/security/security.service.ts index e1df2fa9..241d2c1a 100644 --- a/src/contracts/security/security.service.ts +++ b/src/contracts/security/security.service.ts @@ -6,7 +6,7 @@ import { METRIC_PAUSE_ATTEMPTS, METRIC_UNVET_ATTEMPTS, } from 'common/prometheus'; -import { OneAtTime, StakingModuleId } from 'common/decorators'; +import { OneAtTime, OneAtTimeCallId } from 'common/decorators'; import { SecurityAbi } from 'generated'; import { SecurityDeprecatedPauseAbi, @@ -197,7 +197,7 @@ export class SecurityService { @OneAtTime() public async pauseDepositsV2( blockNumber: number, - @StakingModuleId stakingModuleId: number, + @OneAtTimeCallId stakingModuleId: number, signature: Signature, ): Promise { this.logger.warn('Try to pause deposits', { stakingModuleId, blockNumber }); @@ -282,7 +282,7 @@ export class SecurityService { nonce: number, blockNumber: number, blockHash: string, - @StakingModuleId stakingModuleId: number, + @OneAtTimeCallId stakingModuleId: number, operatorIds: string, vettedKeysByOperator: string, signature: Signature, From 1bb772f3426e54eabf39e4933f78f8bcb232f4ac Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 29 Aug 2024 00:57:00 +0400 Subject: [PATCH 09/94] fix: covent new methods with unit tests --- .../repository/repository.service.spec.ts | 24 ++ .../security/security.service.spec.ts | 219 +++++++++++++++++- .../signing-key-events-cache.spec.ts | 5 + .../keys-duplication-checker.service.spec.ts | 5 + .../keys-validation/keys-validation.spec.ts | 5 + .../unvetting/unvetting.service.spec.ts | 5 + 6 files changed, 260 insertions(+), 3 deletions(-) diff --git a/src/contracts/repository/repository.service.spec.ts b/src/contracts/repository/repository.service.spec.ts index 54f5dade..2676b5d1 100644 --- a/src/contracts/repository/repository.service.spec.ts +++ b/src/contracts/repository/repository.service.spec.ts @@ -113,4 +113,28 @@ describe('RepositoryService', () => { expect(contract1).toEqual(contract2); }); }); + + describe('staking router', () => { + let mockGetAddress; + + beforeEach(async () => { + mockGetAddress = mockLocator(locatorService).SRAddr; + await mockRepository(repositoryService); + }); + + it('should return contract instance', async () => { + const contract = await repositoryService.getCachedStakingRouterContract(); + expect(contract).toBeInstanceOf(Contract); + }); + + it('should call getDepositAddress once and cache instance ', async () => { + const contract1 = + await repositoryService.getCachedStakingRouterContract(); + const contract2 = + await repositoryService.getCachedStakingRouterContract(); + expect(mockGetAddress).toBeCalledTimes(1); + + expect(contract1).toEqual(contract2); + }); + }); }); diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index c4f94fb4..6cbfb610 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -186,6 +186,34 @@ describe('SecurityService', () => { }); }); + describe('signPauseDataV3', () => { + it('should add prefix', async () => { + const blockNumber = 1; + + const mockGetPauseMessagePrefix = jest + .spyOn(securityService, 'getPauseMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); + + const signPauseData = jest.spyOn(walletService, 'signPauseDataV3'); + + const signature = await securityService.signPauseDataV3(blockNumber); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); + expect(signPauseData).toBeCalledWith({ + blockNumber: 1, + prefix: + '0x0000000000000000000000000000000000000000000000000000000000000002', + }); + expect(signature).toEqual( + expect.objectContaining({ + _vs: expect.any(String), + r: expect.any(String), + s: expect.any(String), + v: expect.any(Number), + }), + ); + }); + }); + describe('pauseDepositsV2', () => { const hash = hexZeroPad('0x1', 32); const blockNumber = 10; @@ -228,9 +256,6 @@ describe('SecurityService', () => { expect(mockPauseDeposits).toBeCalledTimes(1); expect(mockWait).toBeCalledTimes(1); - // mockGetPauseMessagePrefix calls 3 times because - // we have more than one call under the hood - // 1 - repository, 2 — signPauseData, 3 — pauseDeposits expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); expect(mockGetContractWithSigner).toBeCalledTimes(1); }); @@ -248,6 +273,194 @@ describe('SecurityService', () => { }); }); + describe('pauseDepositsV3', () => { + const hash = hexZeroPad('0x1', 32); + const blockNumber = 10; + + let mockWait; + let mockPauseDeposits; + let mockGetPauseMessagePrefix; + let mockGetContractWithSigner; + let signature; + + beforeEach(async () => { + mockWait = jest.fn().mockImplementation(async () => undefined); + await mockRepository(repositoryService); + mockGetPauseMessagePrefix = jest + .spyOn(securityService, 'getPauseMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); + + mockPauseDeposits = jest + .fn() + .mockImplementation(async () => ({ wait: mockWait, hash })); + + mockGetContractWithSigner = jest + .spyOn(securityService, 'getContractWithSigner') + .mockImplementation( + () => ({ pauseDeposits: mockPauseDeposits } as any), + ); + + signature = await securityService.signPauseDataV3(blockNumber); + }); + + it('should call contract method', async () => { + await securityService.pauseDepositsV3(blockNumber, signature); + + expect(mockPauseDeposits).toBeCalledTimes(1); + expect(mockWait).toBeCalledTimes(1); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); + expect(mockGetContractWithSigner).toBeCalledTimes(1); + }); + + it('should exit if the previous call is not completed', async () => { + await Promise.all([ + securityService.pauseDepositsV3(blockNumber, signature), + securityService.pauseDepositsV3(blockNumber, signature), + ]); + + expect(mockPauseDeposits).toBeCalledTimes(1); + expect(mockWait).toBeCalledTimes(1); + expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); + expect(mockGetContractWithSigner).toBeCalledTimes(1); + }); + }); + + describe('signUnvetData', () => { + it('should add prefix', async () => { + const nonce = 1; + const blockNumber = 10; + const blockHash = hexZeroPad('0x3', 32); + const stakingModuleId = 1; + const operatorIds = '0x00000000000000010000000000000002'; + const vettedKeysByOperator = + '0x0000000000000000000000000000000000000000000000000000000000000002'; + + const mockGetUnvetMessagePrefix = jest + .spyOn(securityService, 'getUnvetMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); + + const signUnvetData = jest.spyOn(walletService, 'signUnvetData'); + + const signature = await securityService.signUnvetData( + nonce, + blockNumber, + blockHash, + stakingModuleId, + operatorIds, + vettedKeysByOperator, + ); + expect(mockGetUnvetMessagePrefix).toBeCalledTimes(1); + expect(signUnvetData).toBeCalledWith({ + blockNumber, + blockHash, + stakingModuleId, + nonce, + operatorIds, + vettedKeysByOperator, + prefix: + '0x0000000000000000000000000000000000000000000000000000000000000002', + }); + expect(signature).toEqual( + expect.objectContaining({ + _vs: expect.any(String), + r: expect.any(String), + s: expect.any(String), + v: expect.any(Number), + }), + ); + }); + }); + + describe('unvetSigningKeys', () => { + const hash = hexZeroPad('0x1', 32); + + const nonce = 1; + const blockNumber = 10; + const blockHash = hexZeroPad('0x3', 32); + const stakingModuleId = 1; + const operatorIds = '0x00000000000000010000000000000002'; + const vettedKeysByOperator = + '0x0000000000000000000000000000000000000000000000000000000000000002'; + + let mockWait; + let mockUnvetSigningKeys; + let mockGetUnvetMessagePrefix; + let mockGetContractWithSigner; + let signature; + + beforeEach(async () => { + mockWait = jest.fn().mockImplementation(async () => undefined); + await mockRepository(repositoryService); + mockGetUnvetMessagePrefix = jest + .spyOn(securityService, 'getUnvetMessagePrefix') + .mockImplementation(async () => hexZeroPad('0x2', 32)); + + mockUnvetSigningKeys = jest + .fn() + .mockImplementation(async () => ({ wait: mockWait, hash })); + + mockGetContractWithSigner = jest + .spyOn(securityService, 'getContractWithSigner') + .mockImplementation( + () => ({ unvetSigningKeys: mockUnvetSigningKeys } as any), + ); + + signature = await securityService.signUnvetData( + nonce, + blockNumber, + blockHash, + stakingModuleId, + operatorIds, + vettedKeysByOperator, + ); + }); + + it('should call contract method', async () => { + await securityService.unvetSigningKeys( + nonce, + blockNumber, + blockHash, + stakingModuleId, + operatorIds, + vettedKeysByOperator, + signature, + ); + + expect(mockUnvetSigningKeys).toBeCalledTimes(1); + expect(mockWait).toBeCalledTimes(1); + expect(mockGetUnvetMessagePrefix).toBeCalledTimes(1); + expect(mockGetContractWithSigner).toBeCalledTimes(1); + }); + + it('should exit if the previous call is not completed', async () => { + await Promise.all([ + securityService.unvetSigningKeys( + nonce, + blockNumber, + blockHash, + stakingModuleId, + operatorIds, + vettedKeysByOperator, + signature, + ), + securityService.unvetSigningKeys( + nonce, + blockNumber, + blockHash, + stakingModuleId, + operatorIds, + vettedKeysByOperator, + signature, + ), + ]); + + expect(mockUnvetSigningKeys).toBeCalledTimes(1); + expect(mockWait).toBeCalledTimes(1); + expect(mockGetUnvetMessagePrefix).toBeCalledTimes(1); + expect(mockGetContractWithSigner).toBeCalledTimes(1); + }); + }); + describe('messages prefixes', () => { const blockNumber = 10; diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts index bcb8e332..e21693ef 100644 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts +++ b/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts @@ -10,6 +10,7 @@ import { mockLocator } from 'contracts/repository/locator/locator.mock'; import { cacheMock, newEvent } from './leveldb/leveldb.fixtures'; import { SigningKeyEventsCacheModule } from './signing-key-events-cache.module'; import { SigningKeyEventsCacheService } from './signing-key-events-cache.service'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; describe('SigningKeyEventsCacheService', () => { const defaultCacheValue = { @@ -45,6 +46,10 @@ describe('SigningKeyEventsCacheService', () => { signingkeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); providerService = moduleRef.get(ProviderService); + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + mockLocator(locatorService); await mockRepository(repositoryService); await dbService.initialize(); diff --git a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts index 5e5f0c8a..097014cf 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts @@ -17,6 +17,7 @@ import { MockProviderModule } from 'provider'; import { BlockData } from 'guardian/interfaces'; import { StakingRouterModule } from 'contracts/staking-router'; import { RepositoryModule } from 'contracts/repository'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; describe('KeysDuplicationCheckerService', () => { let service: KeysDuplicationCheckerService; const mockSigningKeyEventsCacheService = { @@ -42,6 +43,10 @@ describe('KeysDuplicationCheckerService', () => { service = moduleRef.get( KeysDuplicationCheckerService, ); + + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); }); describe('findDuplicateKeys', () => { diff --git a/src/guardian/keys-validation/keys-validation.spec.ts b/src/guardian/keys-validation/keys-validation.spec.ts index 1192d2cd..f3fe1f70 100644 --- a/src/guardian/keys-validation/keys-validation.spec.ts +++ b/src/guardian/keys-validation/keys-validation.spec.ts @@ -16,6 +16,7 @@ import { validKeys, } from './keys.fixtures'; import { GENESIS_FORK_VERSION_BY_CHAIN_ID } from 'bls/bls.constants'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; describe('KeysValidationService', () => { let keysValidationService: KeysValidationService; @@ -40,6 +41,10 @@ describe('KeysValidationService', () => { keysValidator = moduleRef.get(KeyValidatorInterface); validateKeysFun = jest.spyOn(keysValidator, 'validateKeys'); + + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); }); it('should find and return invalid keys from the provided list', async () => { diff --git a/src/guardian/unvetting/unvetting.service.spec.ts b/src/guardian/unvetting/unvetting.service.spec.ts index 9cb00994..7240042a 100644 --- a/src/guardian/unvetting/unvetting.service.spec.ts +++ b/src/guardian/unvetting/unvetting.service.spec.ts @@ -11,6 +11,7 @@ import { LoggerModule } from 'common/logger'; import { UnvettingModule } from './unvetting.module'; import { PrometheusModule } from 'common/prometheus'; import { MockProviderModule } from 'provider'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; jest.mock('../../transport/stomp/stomp.client'); @@ -52,6 +53,10 @@ describe('UnvettingService', () => { guardianMessageService = moduleRef.get( GuardianMessageService, ); + + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); }); beforeEach(() => { From b109a7642fbf1af8114c661ee70d75d4a18646e2 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 29 Aug 2024 15:12:53 +0400 Subject: [PATCH 10/94] fix: comment isSameContractsStates --- .../staking-module-guard.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index f979a19b..2eae6efc 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -458,10 +458,12 @@ export class StakingModuleGuardService { if (!firstState || !secondState) return false; if (firstState.depositRoot !== secondState.depositRoot) return false; - // If the nonce is unchanged, the state might still have changed. - // Therefore, we need to compare the 'lastChangedBlockHash' instead - // It's important to note that it's not possible for the nonce to be different - // while having the same 'lastChangedBlockHash'. + // If the nonce is unchanged, the state might still have changed due to a reorganization. + // Therefore, we need to compare the 'lastChangedBlockHash' instead. + // It's important to note that the nonce cannot be different while having the same 'lastChangedBlockHash'. + // Additionally, it's important to note that 'lastChangedBlockHash' will change not only during key update-related events, + // but also when a node operator is added, when node operator data is changed, during a reorganization, and so on. + // TODO: We may need to reconsider this approach for the Data Bus. if (firstState.lastChangedBlockHash !== secondState.lastChangedBlockHash) return false; From 2e722e68a70680e958d660b39c00bd957a274cc7 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 29 Aug 2024 19:16:25 +0400 Subject: [PATCH 11/94] fix: front-run --- .../staking-module-guard.service.ts | 23 +-- test/front-run-v3.e2e-spec.ts | 132 ++++++++++++++++++ 2 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index 2eae6efc..d4600a6c 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -80,6 +80,7 @@ export class StakingModuleGuardService { potentialLidoDepositsKeysMap[event.pubkey] = event; }); + // suspicious events with non lido wc const duplicatedDepositEvents: VerifiedDepositEvent[] = []; depositedEvents.events.forEach((event) => { @@ -106,6 +107,7 @@ export class StakingModuleGuardService { const sameKeyLidoDeposit = potentialLidoDepositsKeysMap[suspectedEvent.pubkey]; + // TODO: do we need to leave here this check if (!sameKeyLidoDeposit) throw new Error('expected event not found'); return this.isFirstEventEarlier(suspectedEvent, sameKeyLidoDeposit); @@ -124,21 +126,26 @@ export class StakingModuleGuardService { return false; } - // TODO: deposit could be made by someone else - // and pubkey maybe not used. and we are able to unvet it - // but we will pause - // so maybe we need to filter by used field + // If someone adds a key and then, in the same block, + // makes a deposit on an unused Lido key with a non-Lido withdrawal credential (WC), + // and then makes a deposit on this key with a Lido WC, we will trigger a pause. + // To handle this, we can filter only for used keys in lidoDepositedKeys. + // If any are found, we should trigger a pause. If no used key is found, we will unvet this key later. const lidoDepositedKeys = await this.keysApiService.getKeysByPubkeys( frontRunnedDepositKeys, ); - const isLidoDepositedKeys = lidoDepositedKeys.data.length; + const usedLidoDepositedKeys = lidoDepositedKeys.data.filter( + (key) => key.used, + ); + + const isUsedLidoDepositedKeys = usedLidoDepositedKeys.length; - if (isLidoDepositedKeys) { + if (isUsedLidoDepositedKeys) { this.logger.warn('historical front-run found'); } - return !!isLidoDepositedKeys; + return !!isUsedLidoDepositedKeys; } public async alreadyPausedDeposits(blockData: BlockData, version: number) { @@ -362,13 +369,11 @@ export class StakingModuleGuardService { const { nonce, stakingModuleId, lastChangedBlockHash } = stakingModuleData; - // if we are here we didn't find invalid keys const currentContractState = { nonce, depositRoot, blockNumber, lastChangedBlockHash, - // if we are here we didn't find invalid keys }; const lastContractsState = diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index db4939ff..716671d8 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -30,6 +30,7 @@ import { SIMPLE_DVT, UNLOCKED_ACCOUNTS, SECURITY_MODULE, + NO_PRIVKEY_MESSAGE, } from './constants'; // Contract Factories @@ -59,6 +60,7 @@ import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-chec import { BlsService } from 'bls'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { mockKey, mockKey2 } from './helpers/keys-fixtures'; +import { ethers } from 'ethers'; // Mock rabbit straight away jest.mock('../src/transport/stomp/stomp.client.ts'); @@ -611,4 +613,134 @@ describe('ganache e2e tests', () => { }, TESTS_TIMEOUT, ); + + test( + 'pause will not happen because of front-run attempt (key unused)', + async () => { + const currentBlock = await providerService.provider.getBlock('latest'); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + }, + }); + + await signingKeyEventsCacheService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], + }, + }); + + const { signature: lidoSign } = signDeposit(pk, sk); + const { signature: theftDepositSign } = signDeposit(pk, sk, BAD_WC); + + if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); + const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); + + const keys = [ + { + key: toHexString(pk), + depositSignature: toHexString(lidoSign), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + }, + ]; + + const { curatedModule, sdvtModule } = setupMockModules( + currentBlock, + keysApiService, + [mockOperator1, mockOperator2], + mockedDvtOperators, + keys, + ); + + mockedKeysApiFind( + keysApiService, + keys, + mockedMeta(currentBlock, currentBlock.hash), + ); + + await depositService.setCachedEvents({ + data: [ + { + valid: true, + pubkey: toHexString(pk), + amount: '32000000000', + wc: BAD_WC, + signature: toHexString(theftDepositSign), + tx: '0x122', + blockHash: '0x123456', + blockNumber: currentBlock.number - 1, + logIndex: 1, + depositCount: 1, + depositDataRoot: new Uint8Array(), + index: '', + }, + { + valid: true, + pubkey: toHexString(pk), + amount: '32000000000', + wc: LIDO_WC, + signature: toHexString(lidoSign), + tx: '0x123', + blockHash: currentBlock.hash, + blockNumber: currentBlock.number, + logIndex: 1, + depositCount: 2, + depositDataRoot: new Uint8Array(), + index: '', + }, + ], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + }, + }); + + await guardianService.handleNewBlock(); + + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + + expect(sendPauseMessage).toBeCalledTimes(0); + + const securityContract = SecurityAbi__factory.connect( + SECURITY_MODULE, + providerService.provider, + ); + + const isOnPause = await securityContract.isDepositsPaused(); + + expect(isOnPause).toBe(false); + + expect(sendUnvetMessage).toBeCalledTimes(1); + expect(sendUnvetMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: curatedModule.id, + operatorIds: '0x0000000000000000', + vettedKeysByOperator: '0x00000000000000000000000000000000', + }), + ); + expect(unvetSigningKeys).toBeCalledTimes(1); + expect(sendDepositMessage).toBeCalledTimes(1); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: sdvtModule.id, + }), + ); + }, + TESTS_TIMEOUT, + ); }); From 19b8b9756e17b19c082102cbdf2aee10dac31968 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 30 Aug 2024 00:02:31 +0400 Subject: [PATCH 12/94] fix: refactoring historical front-run --- .../staking-module-guard.service.ts | 207 ++++++++++++++---- 1 file changed, 161 insertions(+), 46 deletions(-) diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index d4600a6c..2f038d5c 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -33,7 +33,15 @@ export class StakingModuleGuardService { private lastContractsStateByModuleId: Record = {}; - isFirstEventEarlier( + /** + * Determines if the first event occurred earlier than the second event. + * Compares block numbers first; if they are equal, compares log indexes. + * + * @param firstEvent - The first event to compare. + * @param secondEvent - The second event to compare. + * @returns True if the first event is earlier, false otherwise. + */ + private isFirstEventEarlier( firstEvent: VerifiedDepositEvent, secondEvent: VerifiedDepositEvent, ) { @@ -49,63 +57,127 @@ export class StakingModuleGuardService { return isFirstEventEarlier; } + /** - * Method is not taking into account WC rotation since historical deposits were checked manually - * @param blockData - * @returns + * Filters and retrieves deposit events that have Lido's withdrawal credentials + * and are marked as valid. + * + * @param depositedEvents - A group of deposit events. + * @param lidoWC - The withdrawal credential associated with Lido. + * @returns An array of deposit events that match the Lido withdrawal credential and are valid. */ - public async getHistoricalFrontRun( + private getDepositsWithLidoWC( depositedEvents: VerifiedDepositEventGroup, lidoWC: string, - ) { - const potentialLidoDepositsEvents = depositedEvents.events.filter( + ): VerifiedDepositEvent[] { + // Filter events for those with Lido withdrawal credentials and valid status + const depositsMatchingLidoWC = depositedEvents.events.filter( ({ wc, valid }) => wc === lidoWC && valid, ); - this.logger.log('potential lido deposits events count', { - count: potentialLidoDepositsEvents.length, + this.logger.log('Deposits matching Lido WC count', { + count: depositsMatchingLidoWC.length, }); - const potentialLidoDepositsKeysMap: Record = + return depositsMatchingLidoWC; + } + + /** + * Creates a map of the earliest deposit events for each public key. + * If multiple deposits are found for the same public key, only the earliest one is stored. + * + * @param depositsMatchingLidoWC - Array of deposit events that match the Lido withdrawal credential + * @returns A record map with public keys as keys and the earliest deposit events as values. + */ + private getEarliestDepositsMap( + depositsMatchingLidoWC: VerifiedDepositEvent[], + ): Record { + const earliestLidoWCDepositsByPubkey: Record = {}; - potentialLidoDepositsEvents.forEach((event) => { - if (potentialLidoDepositsKeysMap[event.pubkey]) { - const existed = potentialLidoDepositsKeysMap[event.pubkey]; - const isExisted = this.isFirstEventEarlier(existed, event); - // this should not happen, since Lido deposits once per key. - // but someone can still make such a deposit. - if (isExisted) return; + depositsMatchingLidoWC.forEach((event) => { + const existingDeposit = earliestLidoWCDepositsByPubkey[event.pubkey]; + + if (existingDeposit) { + const isExistingEarlier = this.isFirstEventEarlier( + existingDeposit, + event, + ); + // This should not happen, since only one deposit per key is expected. + // However, someone could still make such a deposit. + if (isExistingEarlier) return; } - potentialLidoDepositsKeysMap[event.pubkey] = event; + earliestLidoWCDepositsByPubkey[event.pubkey] = event; }); - // suspicious events with non lido wc - const duplicatedDepositEvents: VerifiedDepositEvent[] = []; + return earliestLidoWCDepositsByPubkey; + } + + /** + * Identifies duplicated deposit events that have non-Lido withdrawal credentials. + * These are deposits made on the same public key but with different withdrawal credentials. + * + * @param depositEventGroup - A group of deposit events. + * @param lidoWithdrawalCredential - The withdrawal credential associated with Lido. + * @param earliestDepositsByPubkey - A map of the earliest deposit events with Lido wc by public key. + * @returns An array of duplicated deposit events with non-Lido withdrawal credentials. + */ + private getNonLidoDuplicatedDeposits( + depositedEventsGroup: VerifiedDepositEventGroup, + lidoWC: string, + earliestLidoWCDepositsByPubkey: Record, + ): VerifiedDepositEvent[] { + const nonLidoDuplicatedDeposits: VerifiedDepositEvent[] = []; + + const { events: depositedEvents } = depositedEventsGroup; - depositedEvents.events.forEach((event) => { - if (potentialLidoDepositsKeysMap[event.pubkey] && event.wc !== lidoWC) { - duplicatedDepositEvents.push(event); + depositedEvents.forEach((event) => { + if (earliestLidoWCDepositsByPubkey[event.pubkey] && event.wc !== lidoWC) { + nonLidoDuplicatedDeposits.push(event); } }); - this.logger.log('duplicated deposit events', { - count: duplicatedDepositEvents.length, + this.logger.log('Non-Lido duplicated deposit events count', { + count: nonLidoDuplicatedDeposits.length, }); - const validDuplicatedDepositEvents = duplicatedDepositEvents.filter( + return nonLidoDuplicatedDeposits; + } + + /** + * Filters and returns valid duplicated deposit events from a given list. + * @param nonLidoDuplicatedDeposits - An array of duplicated deposit events with non-Lido withdrawal credentials + * @returns An array of valid duplicated deposit events. + */ + private getValidNonLidoDuplicatedDeposits( + nonLidoDuplicatedDeposits: VerifiedDepositEvent[], + ): VerifiedDepositEvent[] { + const validNonLidoDuplicatedDeposits = nonLidoDuplicatedDeposits.filter( (event) => event.valid, ); - this.logger.log('valid duplicated deposit events', { - count: validDuplicatedDepositEvents.length, + this.logger.log('Valid non-lido duplicated deposit events count', { + count: validNonLidoDuplicatedDeposits.length, }); - const frontRunnedDepositEvents = validDuplicatedDepositEvents.filter( + return validNonLidoDuplicatedDeposits; + } + + /** + * Identifies and returns the public keys associated with deposit events that front-ran the deposits with lido withdrawal credentials. + * @param validNonLidoDuplicatedDeposits - An array of duplicated deposit events with non-Lido withdrawal credentials. + * @param earliestLidoWCDepositsByPubkey - A map of the earliest deposit events with Lido withdrawal credentials by public key. + * @returns An array of public keys for events that front-ran deposits with lido withdrawal credentials. + */ + private getFrontRun( + validNonLidoDuplicatedDeposits: VerifiedDepositEvent[], + earliestLidoWCDepositsByPubkey: Record, + ): string[] { + const frontRunnedDepositEvents = validNonLidoDuplicatedDeposits.filter( (suspectedEvent) => { // get event from lido map const sameKeyLidoDeposit = - potentialLidoDepositsKeysMap[suspectedEvent.pubkey]; + earliestLidoWCDepositsByPubkey[suspectedEvent.pubkey]; // TODO: do we need to leave here this check if (!sameKeyLidoDeposit) throw new Error('expected event not found'); @@ -114,7 +186,7 @@ export class StakingModuleGuardService { }, ); - this.logger.log('front runned deposit events', { + this.logger.log('Front-ran deposit events', { events: frontRunnedDepositEvents, }); @@ -122,30 +194,73 @@ export class StakingModuleGuardService { ({ pubkey }) => pubkey, ); - if (!frontRunnedDepositKeys.length) { + return frontRunnedDepositKeys; + } + + /** + * Retrieves the keys associated with front-runned deposits that were previously deposited by Lido. + * + * @param frontRunnedDepositKeys - An array of public keys for events that front-ran deposits with lido withdrawal credentials. + * @returns An array of registry keys that were previously deposited by Lido. + */ + private async getKeysDepositedByLido( + frontRunnedDepositKeys: string[], + ): Promise { + const { data: lidoDepositedKeys } = + await this.keysApiService.getKeysByPubkeys(frontRunnedDepositKeys); + + return lidoDepositedKeys.filter((key) => key.used); + } + + /** + * Checks if Lido deposits have been front-ran in the past based on historical deposit data. + * This method does not account for WC rotation as historical deposits were manually checked. + * + * @param depositedEvents - A group of historical deposit events. + * @param lidoWC - The withdrawal credential associated with Lido. + * @returns True if front-running was detected at any point in the past; false if no front-running occurred. + */ + public async getHistoricalFrontRun( + depositedEvents: VerifiedDepositEventGroup, + lidoWC: string, + ) { + const lidoWCDeposits = this.getDepositsWithLidoWC(depositedEvents, lidoWC); + + const earliestDepositsMap = this.getEarliestDepositsMap(lidoWCDeposits); + + const nonLidoDuplicatedDeposits = this.getNonLidoDuplicatedDeposits( + depositedEvents, + lidoWC, + earliestDepositsMap, + ); + + const validNonLidoDeposits = this.getValidNonLidoDuplicatedDeposits( + nonLidoDuplicatedDeposits, + ); + + const frontRunnedDepositKeys = this.getFrontRun( + validNonLidoDeposits, + earliestDepositsMap, + ); + + if (frontRunnedDepositKeys.length === 0) { return false; } - // If someone adds a key and then, in the same block, - // makes a deposit on an unused Lido key with a non-Lido withdrawal credential (WC), - // and then makes a deposit on this key with a Lido WC, we will trigger a pause. - // To handle this, we can filter only for used keys in lidoDepositedKeys. - // If any are found, we should trigger a pause. If no used key is found, we will unvet this key later. - const lidoDepositedKeys = await this.keysApiService.getKeysByPubkeys( + // front run happened only if these keys exist in lido contracts + const frontRunnedLidoDeposits = await this.getKeysDepositedByLido( frontRunnedDepositKeys, ); - const usedLidoDepositedKeys = lidoDepositedKeys.data.filter( - (key) => key.used, - ); - - const isUsedLidoDepositedKeys = usedLidoDepositedKeys.length; + const hasFrontRunning = frontRunnedLidoDeposits.length > 0; - if (isUsedLidoDepositedKeys) { - this.logger.warn('historical front-run found'); + if (hasFrontRunning) { + this.logger.warn('Found historical front-run', { + frontRunnedLidoDeposits, + }); } - return !!isUsedLidoDepositedKeys; + return hasFrontRunning; } public async alreadyPausedDeposits(blockData: BlockData, version: number) { From 0aeb1a2906835c34c23d991a5211c333720f35d3 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 30 Aug 2024 14:53:45 +0400 Subject: [PATCH 13/94] fix: fetch prefixes and staking modules by hash --- .../repository/repository.service.ts | 2 - .../security/security.service.spec.ts | 10 +++- src/contracts/security/security.service.ts | 58 ++++++++++++++----- .../staking-router/staking-router.service.ts | 34 ++++++++++- src/guardian/guardian.service.ts | 22 +------ .../staking-module-guard.service.ts | 9 ++- src/wallet/wallet.interfaces.ts | 2 +- src/wallet/wallet.service.spec.ts | 4 +- src/wallet/wallet.service.ts | 13 +---- 9 files changed, 95 insertions(+), 59 deletions(-) diff --git a/src/contracts/repository/repository.service.ts b/src/contracts/repository/repository.service.ts index 94db4b40..2623de69 100644 --- a/src/contracts/repository/repository.service.ts +++ b/src/contracts/repository/repository.service.ts @@ -163,8 +163,6 @@ export class RepositoryService { private async initCachedDepositContract(blockTag: BlockTag): Promise { if (this.permanentContractsCache[DEPOSIT_ABI]) return; const depositAddress = await this.getDepositAddress(blockTag); - - console.log('depositAddress', depositAddress); const provider = this.providerService.provider; this.setPermanentContractCache( diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 2a5a289d..713b0424 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -114,12 +114,12 @@ describe('SecurityService', () => { it('should add prefix', async () => { const prefix = hexZeroPad('0x1', 32); const depositRoot = hexZeroPad('0x2', 32); - const keysOpIndex = 1; + const nonce = 1; const blockNumber = 1; const blockHash = hexZeroPad('0x3', 32); const args = [ depositRoot, - keysOpIndex, + nonce, blockNumber, blockHash, TEST_MODULE_ID, @@ -137,7 +137,7 @@ describe('SecurityService', () => { expect(signDepositData).toBeCalledWith({ prefix, depositRoot, - keysOpIndex, + nonce, blockNumber, blockHash, stakingModuleId: TEST_MODULE_ID, @@ -156,6 +156,7 @@ describe('SecurityService', () => { describe('signPauseDataV2', () => { it('should add prefix', async () => { const blockNumber = 1; + const blockHash = '0x'; const mockGetPauseMessagePrefix = jest .spyOn(securityService, 'getPauseMessagePrefix') @@ -165,6 +166,7 @@ describe('SecurityService', () => { const signature = await securityService.signPauseDataV2( blockNumber, + blockHash, TEST_MODULE_ID, ); expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); @@ -209,6 +211,7 @@ describe('SecurityService', () => { describe('pauseDepositsV2', () => { const hash = hexZeroPad('0x1', 32); const blockNumber = 10; + const blockHash = '0x'; let mockWait; let mockPauseDeposits; @@ -235,6 +238,7 @@ describe('SecurityService', () => { signature = await securityService.signPauseDataV2( blockNumber, + blockHash, TEST_MODULE_ID, ); }); diff --git a/src/contracts/security/security.service.ts b/src/contracts/security/security.service.ts index 8f0a16f0..ddbbb7d2 100644 --- a/src/contracts/security/security.service.ts +++ b/src/contracts/security/security.service.ts @@ -96,20 +96,27 @@ export class SecurityService { /** * Signs a message to deposit buffered ethers with the prefix from the contract + * + * @param depositRoot: Root of deposit contract + * @param nonce - Current index of keys operations from the registry contract + * @param blockNumber - The block number, included as part of the message for signing. + * @param blockHash - The block hash, included as part of the message for signing and is used to fetch the pause prefix + * @param stakingModuleId - The staking module ID, included as part of the message for signing. + * @returns Signature for deposit. */ public async signDepositData( depositRoot: string, - keysOpIndex: number, + nonce: number, blockNumber: number, blockHash: string, stakingModuleId: number, ): Promise { - const prefix = await this.getAttestMessagePrefix(blockNumber); + const prefix = await this.getAttestMessagePrefix(blockHash); return await this.walletService.signDepositData({ prefix, depositRoot, - keysOpIndex, + nonce, blockNumber, blockHash, stakingModuleId, @@ -117,10 +124,17 @@ export class SecurityService { } /** - * Signs a message to pause deposits with the prefix from the contract + * Signs a message to pause deposits, including the pause prefix from the contract. + * + * @param blockNumber - The block number, included as part of the message for signing. + * @param blockHash - The block hash, used to fetch the pause prefix. + * @returns Signature for pausing deposits. */ - public async signPauseDataV3(blockNumber: number): Promise { - const prefix = await this.getPauseMessagePrefix(blockNumber); + public async signPauseDataV3( + blockNumber: number, + blockHash: string, + ): Promise { + const prefix = await this.getPauseMessagePrefix(blockHash); return await this.walletService.signPauseDataV3({ prefix, @@ -163,13 +177,19 @@ export class SecurityService { } /** - * Signs a message to pause deposits with the prefix from the contract + * Signs a message to pause deposits, including the pause prefix from the contract. + * + * @param blockNumber - The block number, included as part of the message for signing. + * @param blockHash - The block hash, used to fetch the pause prefix. + * @param stakingModuleId - The staking module ID, included as part of the message for signing. + * @returns Signature for pausing deposits. */ public async signPauseDataV2( blockNumber: number, + blockHash: string, stakingModuleId: number, ): Promise { - const prefix = await this.getPauseMessagePrefix(blockNumber); + const prefix = await this.getPauseMessagePrefix(blockHash); return await this.walletService.signPauseDataV2({ prefix, @@ -226,7 +246,7 @@ export class SecurityService { * * @param nonce - The nonce for the staking module. * @param blockNumber - The block number at which the message is signed. - * @param blockHash - The hash of the block corresponding to the block number. + * @param blockHash - The hash of the block corresponding to the block number, used to fetch the pause prefix. * @param stakingModuleId - The ID of the target staking module. * @param operatorIds - A string containing the IDs of the operators whose keys are being unvetted. * @param vettedKeysByOperator - A string representing the new staking limit amount per operator. @@ -241,7 +261,7 @@ export class SecurityService { operatorIds: string, vettedKeysByOperator: string, ): Promise { - const prefix = await this.getUnvetMessagePrefix(blockNumber); + const prefix = await this.getUnvetMessagePrefix(blockHash); return await this.walletService.signUnvetData({ prefix, @@ -384,24 +404,30 @@ export class SecurityService { /** * Returns a prefix from the contract with which the deposit message should be signed */ - public async getAttestMessagePrefix(blockNumber: number): Promise { + public async getAttestMessagePrefix(blockHash: string): Promise { const contract = await this.repositoryService.getCachedDSMContract(); - return await contract.ATTEST_MESSAGE_PREFIX({ blockTag: blockNumber }); + return await contract.ATTEST_MESSAGE_PREFIX({ + blockTag: { blockHash } as any, + }); } /** * Returns a prefix from the contract with which the pause message should be signed */ - public async getPauseMessagePrefix(blockNumber: number): Promise { + public async getPauseMessagePrefix(blockHash: string): Promise { const contract = await this.repositoryService.getCachedDSMContract(); - return await contract.PAUSE_MESSAGE_PREFIX({ blockTag: blockNumber }); + return await contract.PAUSE_MESSAGE_PREFIX({ + blockTag: { blockHash } as any, + }); } /** * Returns a prefix from the contract with which the pause message should be signed */ - public async getUnvetMessagePrefix(blockNumber: number): Promise { + public async getUnvetMessagePrefix(blockHash: string): Promise { const contract = await this.repositoryService.getCachedDSMContract(); - return await contract.UNVET_MESSAGE_PREFIX({ blockTag: blockNumber }); + return await contract.UNVET_MESSAGE_PREFIX({ + blockTag: { blockHash } as any, + }); } } diff --git a/src/contracts/staking-router/staking-router.service.ts b/src/contracts/staking-router/staking-router.service.ts index 147d9c3b..6061045e 100644 --- a/src/contracts/staking-router/staking-router.service.ts +++ b/src/contracts/staking-router/staking-router.service.ts @@ -14,7 +14,7 @@ export class StakingRouterService { /** * @param blockTag - * @returns List of staking modules fetch from SR contract + * @returns List of staking modules fetched from the SR contract */ public async getStakingModules(blockTag: BlockTag) { const stakingRouter = @@ -26,6 +26,27 @@ export class StakingRouterService { return stakingModules; } + /** + * Retrieves the list of staking module addresses. + * This method fetches the cached staking modules contracts and returns the list of staking module addresses. + * @param blockHash - Block hash + * @returns Array of staking module addresses. + */ + public async getStakingModulesAddresses( + blockHash: string, + ): Promise { + const stakingModules = await this.getStakingModules({ blockHash }); + + return stakingModules.map( + (stakingModule) => stakingModule.stakingModuleAddress, + ); + } + + /** + * Retrieves contract factory + * @param stakingModuleAddress Staking module address + * @returns Contract factory + */ public async getStakingModule(stakingModuleAddress: string) { return IStakingModuleAbi__factory.connect( stakingModuleAddress, @@ -33,12 +54,19 @@ export class StakingRouterService { ); } + /** + * Retrieves SigningKeyAdded events list + * @param startBlock - Start block for fetching events + * @param endBlock - End block for fetching events + * @param stakingModuleAddress - Staking module address + * @returns List of SigningKeyAdded events + */ public async getSigningKeyAddedEvents( startBlock: number, endBlock: number, - address: string, + stakingModuleAddress: string, ) { - const contract = await this.getStakingModule(address); + const contract = await this.getStakingModule(stakingModuleAddress); const filter = contract.filters['SigningKeyAdded(uint256,bytes)'](); return await contract.queryFilter(filter, startBlock, endBlock); diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 5c3a5ff2..41f07965 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -62,23 +62,6 @@ export class GuardianService implements OnModuleInit { private stakingRouterService: StakingRouterService, ) {} - /** - * Retrieves the list of staking module addresses. - * - * This method fetches the cached staking modules contracts and returns the list of staking module addresses. - * - * @returns {Promise} Array of staking module addresses. - */ - public async getStakingModules(blockNumber: number): Promise { - const stakingModules = await this.stakingRouterService.getStakingModules( - blockNumber, - ); - - return stakingModules.map( - (stakingModule) => stakingModule.stakingModuleAddress, - ); - } - public async onModuleInit(): Promise { // Does not wait for completion, to avoid blocking the app initialization (async () => { @@ -87,9 +70,8 @@ export class GuardianService implements OnModuleInit { const block = await this.repositoryService.initOrWaitCachedContracts(); const blockHash = block.hash; - const stakingRouterModuleAddresses = await this.getStakingModules( - block.number, - ); + const stakingRouterModuleAddresses = + await this.stakingRouterService.getStakingModulesAddresses(blockHash); await Promise.all([ this.depositService.initialize(block.number), diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index 7e6d05ce..0d49aa87 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -239,9 +239,13 @@ export class StakingModuleGuardService { } public async handlePauseV3(blockData: BlockData): Promise { - const { blockNumber, guardianAddress, guardianIndex } = blockData; + const { blockNumber, blockHash, guardianAddress, guardianIndex } = + blockData; - const signature = await this.securityService.signPauseDataV3(blockNumber); + const signature = await this.securityService.signPauseDataV3( + blockNumber, + blockHash, + ); const pauseMessage = { guardianAddress, @@ -322,6 +326,7 @@ export class StakingModuleGuardService { const signature = await this.securityService.signPauseDataV2( blockNumber, + blockHash, stakingModuleId, ); diff --git a/src/wallet/wallet.interfaces.ts b/src/wallet/wallet.interfaces.ts index be2bcdc5..26cdf8cd 100644 --- a/src/wallet/wallet.interfaces.ts +++ b/src/wallet/wallet.interfaces.ts @@ -3,7 +3,7 @@ export interface SignDepositDataParams { blockNumber: number; blockHash: string; depositRoot: string; - keysOpIndex: number; + nonce: number; stakingModuleId: number; } diff --git a/src/wallet/wallet.service.spec.ts b/src/wallet/wallet.service.spec.ts index 0c93207c..159ff943 100644 --- a/src/wallet/wallet.service.spec.ts +++ b/src/wallet/wallet.service.spec.ts @@ -79,13 +79,13 @@ describe('WalletService', () => { it('should sign deposit data', async () => { const prefix = hexZeroPad('0x1', 32); const depositRoot = hexZeroPad('0x2', 32); - const keysOpIndex = 1; + const nonce = 1; const blockNumber = 1; const blockHash = hexZeroPad('0x3', 32); const signature = await walletService.signDepositData({ prefix, depositRoot, - keysOpIndex, + nonce, blockNumber, blockHash, stakingModuleId: TEST_MODULE_ID, diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 0852242d..1292dffd 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -167,7 +167,7 @@ export class WalletService implements OnModuleInit { * @param signDepositDataParams - parameters for signing deposit message * @param signDepositDataParams.prefix - unique prefix from the contract for this type of message * @param signDepositDataParams.depositRoot - current deposit root from the deposit contract - * @param signDepositDataParams.keysOpIndex - current index of keys operations from the registry contract + * @param signDepositDataParams.nonce - current index of keys operations from the registry contract * @param signDepositDataParams.blockNumber - current block number * @param signDepositDataParams.blockHash - current block hash * @param signDepositDataParams.stakingModuleId - target module id @@ -178,19 +178,12 @@ export class WalletService implements OnModuleInit { blockNumber, blockHash, depositRoot, - keysOpIndex, + nonce, stakingModuleId, }: SignDepositDataParams): Promise { const encodedData = defaultAbiCoder.encode( ['bytes32', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256'], - [ - prefix, - blockNumber, - blockHash, - depositRoot, - stakingModuleId, - keysOpIndex, - ], + [prefix, blockNumber, blockHash, depositRoot, stakingModuleId, nonce], ); const messageHash = keccak256(encodedData); From 65bfff28900986206245de548aa2958d90dfc06a Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 30 Aug 2024 14:59:35 +0400 Subject: [PATCH 14/94] fix: test name --- test/front-run-v3.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 716671d8..e88c95cd 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -615,7 +615,7 @@ describe('ganache e2e tests', () => { ); test( - 'pause will not happen because of front-run attempt (key unused)', + 'should not trigger pause for front-run attempt with non-Lido WC and Lido WC deposits when key is unused', async () => { const currentBlock = await providerService.provider.getBlock('latest'); From 3e89529725c65cdb0f3f8823fd6afa32094411f5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 1 Sep 2024 13:00:08 +0200 Subject: [PATCH 15/94] feat: deposit-registry wip --- .../deposits-registry/deposit.utils.ts | 20 ++ .../deposits-registry.service.ts | 244 ++++++++++++++++++ .../fetcher/fetcher.module.ts | 10 + .../fetcher/fetcher.service.ts | 111 ++++++++ .../interfaces/cache.interface.ts | 11 + .../interfaces/deposit-tree.interface.ts | 6 + .../interfaces/event.interface.ts | 31 +++ .../deposits-registry/interfaces/index.ts | 3 + .../blockchain-checker.service.ts | 60 +++++ .../integrity-checker/constants.ts | 2 + .../deposit-tree/deposit-tree.spec.ts | 48 ++++ .../deposit-tree/deposit-tree.ts | 142 ++++++++++ .../integrity-checker/deposit-tree/index.ts | 1 + .../sanity-checker/integrity-checker/index.ts | 1 + .../integrity-checker.service.ts | 140 ++++++++++ .../sanity-checker/sanity-checker.service.ts | 0 .../deposits-registry/store/index.ts | 3 + .../store/store.constants.ts | 15 ++ .../deposits-registry/store/store.fixtures.ts | 50 ++++ .../deposits-registry/store/store.module.ts | 34 +++ .../store/store.service.spec.ts | 51 ++++ .../deposits-registry/store/store.service.ts | 190 ++++++++++++++ 22 files changed, 1173 insertions(+) create mode 100644 src/contracts/deposits-registry/deposit.utils.ts create mode 100644 src/contracts/deposits-registry/deposits-registry.service.ts create mode 100644 src/contracts/deposits-registry/fetcher/fetcher.module.ts create mode 100644 src/contracts/deposits-registry/fetcher/fetcher.service.ts create mode 100644 src/contracts/deposits-registry/interfaces/cache.interface.ts create mode 100644 src/contracts/deposits-registry/interfaces/deposit-tree.interface.ts create mode 100644 src/contracts/deposits-registry/interfaces/event.interface.ts create mode 100644 src/contracts/deposits-registry/interfaces/index.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/constants.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/index.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts create mode 100644 src/contracts/deposits-registry/store/index.ts create mode 100644 src/contracts/deposits-registry/store/store.constants.ts create mode 100644 src/contracts/deposits-registry/store/store.fixtures.ts create mode 100644 src/contracts/deposits-registry/store/store.module.ts create mode 100644 src/contracts/deposits-registry/store/store.service.spec.ts create mode 100644 src/contracts/deposits-registry/store/store.service.ts diff --git a/src/contracts/deposits-registry/deposit.utils.ts b/src/contracts/deposits-registry/deposit.utils.ts new file mode 100644 index 00000000..555b676a --- /dev/null +++ b/src/contracts/deposits-registry/deposit.utils.ts @@ -0,0 +1,20 @@ +const changeEndianness = (string: any) => { + string = string.replace('0x', ''); + const result: string[] = []; + let len = string.length - 2; + while (len >= 0) { + result.push(string.substr(len, 2)); + len -= 2; + } + return '0x' + result.join(''); +}; + +export const parseLittleEndian64 = (str: string) => { + return parseInt(changeEndianness(str), 16); +}; + +export const toLittleEndian64 = (value: number): string => { + const buffer = Buffer.allocUnsafe(8); + buffer.writeBigUInt64LE(BigInt(value)); + return '0x' + buffer.toString('hex'); +}; diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts new file mode 100644 index 00000000..cd69e99b --- /dev/null +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -0,0 +1,244 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { performance } from 'perf_hooks'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { ProviderService } from 'provider'; +import { + DEPOSIT_EVENTS_STEP, + getDeploymentBlockByNetwork, + DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE, + DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, +} from './deposit.constants'; +import { + VerifiedDepositEventsCache, + VerifiedDepositedEventGroup, +} from './interfaces'; +import { RepositoryService } from 'contracts/repository'; +import { BlockTag } from 'provider'; +import { BlsService } from 'bls'; +import { DepositIntegrityCheckerService } from './integrity-checker'; +import { LevelDBService } from './leveldb'; +import { DepositCacheIntegrityError } from './integrity-checker/constants'; + +@Injectable() +export class DepositService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private providerService: ProviderService, + private repositoryService: RepositoryService, + + private blsService: BlsService, + private depositIntegrityCheckerService: DepositIntegrityCheckerService, + private levelDBCacheService: LevelDBService, + ) {} + + public async handleNewBlock(blockNumber: number): Promise { + if (blockNumber % DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE !== 0) return; + + // The event cache is stored with an N block lag to avoid caching data from uncle blocks + // so we don't worry about blockHash here + const toBlockNumber = await this.updateEventsCache(); + await this.checkDepositCacheIntegrity(toBlockNumber); + } + + public async initialize(blockNumber: number) { + await this.levelDBCacheService.initialize(); + + const cachedEvents = await this.levelDBCacheService.getEventsCache(); + const isCacheValid = this.validateCache(cachedEvents, blockNumber); + + if (!isCacheValid) { + process.exit(1); + } + await this.depositIntegrityCheckerService.initialize(cachedEvents); + // it is necessary to load fresh events before integrity check + // because we can only compare roots of the last 128 blocks. + const toBlockNumber = await this.updateEventsCache(); + await this.checkDepositCacheIntegrity(toBlockNumber); + } + + public async checkDepositCacheIntegrity(toBlockNumber: number) { + try { + await this.depositIntegrityCheckerService.checkFinalizedRoot( + toBlockNumber, + ); + } catch (error) { + if (error instanceof DepositCacheIntegrityError) { + return this.logger.error( + `Deposit event cache integrity error on block number: ${toBlockNumber}`, + ); + } + throw error; + } + } + + /** + * Returns a block number when the deposited contract was deployed + * @returns block number + */ + public async getDeploymentBlockByNetwork(): Promise { + const chainId = await this.providerService.getChainId(); + return getDeploymentBlockByNetwork(chainId); + } + + /** + * Gets node operators data from cache + * @returns event group + */ + public async getCachedEvents(): Promise { + const { headers, ...rest } = + await this.levelDBCacheService.getEventsCache(); + const deploymentBlock = await this.getDeploymentBlockByNetwork(); + + return { + headers: { + ...headers, + startBlock: Math.max(headers.startBlock, deploymentBlock), + endBlock: Math.max(headers.endBlock, deploymentBlock), + }, + ...rest, + }; + } + + /** + * Saves deposited events to cache + */ + public async setCachedEvents( + cachedEvents: VerifiedDepositEventsCache, + ): Promise { + await this.levelDBCacheService.deleteCache(); + await this.levelDBCacheService.insertEventsCacheBatch({ + ...cachedEvents, + headers: { + ...cachedEvents.headers, + }, + }); + } + + /** + * Updates the cache deposited events + * The last N blocks are not stored, in order to avoid storing reorganized blocks + */ + public async updateEventsCache(): Promise { + const fetchTimeStart = performance.now(); + + const [currentBlock, initialCache] = await Promise.all([ + this.providerService.getBlockNumber(), + this.getCachedEvents(), + ]); + + const firstNotCachedBlock = initialCache.headers.endBlock + 1; + const toBlock = currentBlock - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS; + + const totalEventsCount = initialCache.data.length; + let newEventsCount = 0; + + for ( + let block = firstNotCachedBlock; + block <= toBlock; + block += DEPOSIT_EVENTS_STEP + ) { + const chunkStartBlock = block; + const chunkToBlock = Math.min(toBlock, block + DEPOSIT_EVENTS_STEP - 1); + + const chunkEventGroup = await this.fetchEventsFallOver( + chunkStartBlock, + chunkToBlock, + ); + + await this.levelDBCacheService.insertEventsCacheBatch({ + headers: { + ...initialCache.headers, + endBlock: chunkEventGroup.endBlock, + }, + data: chunkEventGroup.events, + }); + + await this.depositIntegrityCheckerService.putFinalizedEvents( + chunkEventGroup.events, + ); + + newEventsCount += chunkEventGroup.events.length; + + this.logger.log('Historical events are fetched', { + toBlock, + startBlock: chunkStartBlock, + endBlock: chunkToBlock, + }); + } + + const fetchTimeEnd = performance.now(); + const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; + // TODO: replace timer with metric + + this.logger.log('Deposit events cache is updated', { + newEventsCount, + totalEventsCount: totalEventsCount + newEventsCount, + fetchTime, + }); + + return toBlock; + } + + /** + * Returns all deposited events based on cache and fresh data + */ + public async getAllDepositedEvents( + blockNumber: number, + blockHash: string, + ): Promise { + const endBlock = blockNumber; + const cachedEvents = await this.getCachedEvents(); + //!!!! + // если мы измкеним поведение таким образом + // то у нас появится инвариант когда мы сами кораптим кэш + // 1,2,3,4 + // + const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); + if (!isCacheValid) process.exit(1); + + const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; + const freshEventGroup = await this.fetchEventsFallOver( + firstNotCachedBlock, + endBlock, + ); + const freshEvents = freshEventGroup.events; + const lastEvent = freshEvents[freshEvents.length - 1]; + const lastEventBlockHash = lastEvent?.blockHash; + + this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); + + this.logger.debug?.('Fresh deposit events are fetched', { + events: freshEvents.length, + startBlock: firstNotCachedBlock, + endBlock, + blockHash, + lastEventBlockHash, + }); + + const mergedEvents = cachedEvents.data.concat(freshEvents); + + return { + events: mergedEvents, + startBlock: cachedEvents.headers.startBlock, + endBlock, + // declare a separate method where we store the latest events in the closure + checkRoot: async () => { + await this.depositIntegrityCheckerService.checkLatestRoot( + blockNumber, + freshEvents, + ); + }, + }; + } + /** + * Returns a deposit root + */ + public async getDepositRoot(blockTag?: BlockTag): Promise { + const contract = await this.repositoryService.getCachedDepositContract(); + const depositRoot = await contract.get_deposit_root({ + blockTag: blockTag as any, + }); + + return depositRoot; + } +} diff --git a/src/contracts/deposits-registry/fetcher/fetcher.module.ts b/src/contracts/deposits-registry/fetcher/fetcher.module.ts new file mode 100644 index 00000000..88fced62 --- /dev/null +++ b/src/contracts/deposits-registry/fetcher/fetcher.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { BlsModule } from 'bls'; +import { DepositsRegistryFetcherService } from './fetcher.service'; + +@Module({ + imports: [BlsModule], + providers: [DepositsRegistryFetcherService], + exports: [DepositsRegistryFetcherService], +}) +export class DepositsRegistryFetcherModule {} diff --git a/src/contracts/deposits-registry/fetcher/fetcher.service.ts b/src/contracts/deposits-registry/fetcher/fetcher.service.ts new file mode 100644 index 00000000..e2d7348e --- /dev/null +++ b/src/contracts/deposits-registry/fetcher/fetcher.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@nestjs/common'; +import { BlsService } from 'bls'; +import { RepositoryService } from 'contracts/repository'; +import { DepositEventEvent } from 'generated/DepositAbi'; + +import { ProviderService } from 'provider'; +import { parseLittleEndian64 } from '../deposit.utils'; +import { DepositEvent, VerifiedDepositEventGroup } from '../interfaces'; +import { DepositTree } from '../sanity-checker/integrity-checker/deposit-tree'; + +@Injectable() +export class DepositsRegistryFetcherService { + constructor( + private providerService: ProviderService, + private repositoryService: RepositoryService, + private blsService: BlsService, + ) {} + + /** + * Returns events in the block range + * If the request failed, it tries to repeat it or split it into two + * @param startBlock - start of the range + * @param endBlock - end of the range + * @returns event group + */ + public async fetchEventsFallOver( + startBlock: number, + endBlock: number, + ): Promise { + return await this.providerService.fetchEventsFallOver( + startBlock, + endBlock, + this.fetchEvents.bind(this), + ); + } + + /** + * Returns events in the block range + * @param startBlock - start of the range + * @param endBlock - end of the range + * @returns event group + */ + public async fetchEvents( + startBlock: number, + endBlock: number, + ): Promise { + const contract = await this.repositoryService.getCachedDepositContract(); + const filter = contract.filters.DepositEvent(); + const rawEvents = await contract.queryFilter(filter, startBlock, endBlock); + const events = rawEvents.map((rawEvent) => { + const formatted = this.formatEvent(rawEvent); + const valid = this.verifyDeposit(formatted); + return { valid, ...formatted }; + }); + + return { events, startBlock, endBlock }; + } + + /** + * Returns only required information about the event, + * to reduce the size of the information stored in the cache + */ + public formatEvent(rawEvent: DepositEventEvent): DepositEvent { + const { + args, + transactionHash: tx, + blockNumber, + blockHash, + logIndex, + } = rawEvent; + const { + withdrawal_credentials: wc, + pubkey, + amount, + signature, + index, + ...rest + } = args; + + const depositCount = rest['4']; + + const depositDataRoot = DepositTree.formDepositNode({ + pubkey, + wc, + signature, + amount, + }); + + return { + pubkey, + wc, + amount, + signature, + tx, + blockNumber, + blockHash, + logIndex, + index, + depositCount: parseLittleEndian64(depositCount), + depositDataRoot, + }; + } + + /** + * Verifies a deposit signature + */ + public verifyDeposit(depositEvent: DepositEvent): boolean { + const { pubkey, wc, amount, signature } = depositEvent; + return this.blsService.verify({ pubkey, wc, amount, signature }); + } +} diff --git a/src/contracts/deposits-registry/interfaces/cache.interface.ts b/src/contracts/deposits-registry/interfaces/cache.interface.ts new file mode 100644 index 00000000..b42b7fcb --- /dev/null +++ b/src/contracts/deposits-registry/interfaces/cache.interface.ts @@ -0,0 +1,11 @@ +import { VerifiedDepositEvent } from './event.interface'; + +export interface VerifiedDepositEventsCacheHeaders { + startBlock: number; + endBlock: number; +} + +export interface VerifiedDepositEventsCache { + headers: VerifiedDepositEventsCacheHeaders; + data: VerifiedDepositEvent[]; +} diff --git a/src/contracts/deposits-registry/interfaces/deposit-tree.interface.ts b/src/contracts/deposits-registry/interfaces/deposit-tree.interface.ts new file mode 100644 index 00000000..7d0b082a --- /dev/null +++ b/src/contracts/deposits-registry/interfaces/deposit-tree.interface.ts @@ -0,0 +1,6 @@ +export interface NodeData { + pubkey: string; + wc: string; + amount: string; + signature: string; +} diff --git a/src/contracts/deposits-registry/interfaces/event.interface.ts b/src/contracts/deposits-registry/interfaces/event.interface.ts new file mode 100644 index 00000000..45da4f73 --- /dev/null +++ b/src/contracts/deposits-registry/interfaces/event.interface.ts @@ -0,0 +1,31 @@ +export interface DepositEvent { + pubkey: string; + wc: string; + amount: string; + signature: string; + tx: string; + blockNumber: number; + blockHash: string; + logIndex: number; + index: string; + depositCount: number; + depositDataRoot: Uint8Array; +} + +export interface VerifiedDepositEvent extends DepositEvent { + valid: boolean; +} + +export interface DepositEventGroup { + events: DepositEvent[]; + startBlock: number; + endBlock: number; +} + +export interface VerifiedDepositEventGroup extends DepositEventGroup { + events: VerifiedDepositEvent[]; +} + +export interface VerifiedDepositedEventGroup extends VerifiedDepositEventGroup { + checkRoot(): Promise; +} diff --git a/src/contracts/deposits-registry/interfaces/index.ts b/src/contracts/deposits-registry/interfaces/index.ts new file mode 100644 index 00000000..8edb9bb4 --- /dev/null +++ b/src/contracts/deposits-registry/interfaces/index.ts @@ -0,0 +1,3 @@ +export * from './cache.interface'; +export * from './event.interface'; +export * from './deposit-tree.interface'; diff --git a/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts new file mode 100644 index 00000000..fd0cfa23 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts @@ -0,0 +1,60 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { DepositEvent, VerifiedDepositEventsCache } from '../../interfaces'; + +@Injectable() +export class BlockchainCheckerService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + ) {} + + /** + * Validates block number in the cache + * @param cachedEvents - cached events + * @param currentBlock - current block number + * @returns true if cached app version is the same + */ + public validateCacheBlock( + cachedEvents: VerifiedDepositEventsCache, + currentBlock: number, + ): boolean { + const isCacheValid = currentBlock >= cachedEvents.headers.endBlock; + + const blocks = { + cachedStartBlock: cachedEvents.headers.startBlock, + cachedEndBlock: cachedEvents.headers.endBlock, + currentBlock, + }; + + if (isCacheValid) { + this.logger.log('Deposit events cache has valid age', blocks); + } + + if (!isCacheValid) { + this.logger.warn( + 'Deposit events cache is newer than the current block', + blocks, + ); + } + + return isCacheValid; + } + + /** + * Checks events block hash + * An additional check to avoid events processing in an alternate chain + */ + public checkEventsBlockHash( + events: DepositEvent[], + blockNumber: number, + blockHash: string, + ): void { + events.forEach((event) => { + if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { + throw new Error( + 'Blockhash of the received events does not match the current blockhash', + ); + } + }); + } +} diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/constants.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/constants.ts new file mode 100644 index 00000000..7c9c3e1d --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/constants.ts @@ -0,0 +1,2 @@ +export const DEPOSIT_TREE_STEP_SYNC = 200_000; +export class DepositCacheIntegrityError extends Error {} diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts new file mode 100644 index 00000000..97cacbcb --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -0,0 +1,48 @@ +import { DepositTree } from './deposit-tree'; + +describe('DepositTree', () => { + let depositTree; + + beforeEach(() => { + depositTree = new DepositTree(); + }); + + test('should correctly initialize', () => { + expect(depositTree.nodeCount).toBe(0); + expect(depositTree.branch.length).toBe(0); + expect(depositTree.zeroHashes[0]).toEqual(DepositTree.ZERO_HASH); + }); + + test('insert should correctly modify the tree', () => { + depositTree.insert({ + pubkey: '0xaabbccdd', + wc: '0x11223344', + amount: '0x0000000000000001', // Little endian of 1 + signature: '0x55667788', + }); + + expect(depositTree.nodeCount).toBe(1); + + depositTree.insert({ + pubkey: '0xaabbccdd', + wc: '0x11223344', + amount: '0x0000000000000002', // Little endian of 2 + signature: '0x55667788', + }); + + expect(depositTree.nodeCount).toBe(2); + }); + + test('clone should create an exact copy of the tree', () => { + const nodeData = { + pubkey: '0xaabbccdd', + wc: '0x11223344', + amount: '0x0000000000000001', + signature: '0x55667788', + }; + depositTree.insert(nodeData); + const clonedTree = depositTree.clone(); + expect(clonedTree).toEqual(depositTree); + expect(clonedTree.getRoot()).toEqual(depositTree.getRoot()); + }); +}); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts new file mode 100644 index 00000000..ff2334d2 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -0,0 +1,142 @@ +import { ethers } from 'ethers'; +import { digest2Bytes32 } from '@chainsafe/as-sha256'; +import { fromHexString } from '@chainsafe/ssz'; +import { parseLittleEndian64, toLittleEndian64 } from '../../../deposit.utils'; +import { DepositData } from 'bls/bls.containers'; +import { NodeData } from '../../../interfaces'; + +export class DepositTree { + static DEPOSIT_CONTRACT_TREE_DEPTH = 32; + static ZERO_HASH = fromHexString( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ); + zeroHashes: Uint8Array[] = new Array(DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH); + branch: Uint8Array[] = []; + nodeCount = 0; + + constructor() { + this.formZeroHashes(); + } + + /** + * Initializes the zero hashes used in the tree. + */ + private formZeroHashes() { + this.zeroHashes[0] = DepositTree.ZERO_HASH; + for ( + let height = 0; + height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH - 1; + height++ + ) { + this.zeroHashes[height + 1] = digest2Bytes32( + this.zeroHashes[height], + this.zeroHashes[height], + ); + } + } + + /** + * Forms the branch of the tree needed to update the root when a new node is inserted. + * @param {Uint8Array} node - The node's data to be inserted. + * @param {number} depositCount - The sequential index of the deposit, representing the total deposits. + * @returns {Uint8Array[] | undefined} The updated branch of the tree after inserting the node. + */ + private formBranch( + node: Uint8Array, + depositCount: number, + ): Uint8Array[] | undefined { + let size = depositCount; + for ( + let height = 0; + height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; + height++ + ) { + if ((size & 1) == 1) { + this.branch[height] = node; + return this.branch; + } + + node = digest2Bytes32(this.branch[height], node); + + // Using size /= 2 is not a mistake. In JavaScript, when performing bitwise operations + // like & 1, floating-point numbers are implicitly converted to integers, discarding the fractional part. + // This ensures the algorithm works correctly and matches the logic of a Solidity smart contract. + // Solidity does not have floating-point numbers, and all division is performed as integer division, rounding down the result. + size /= 2; + } + } + + /** + * Inserts a new deposit into the tree using detailed node data. + * @param {NodeData} nodeData - The detailed data of the deposit to be inserted. + */ + public insert(nodeData: NodeData) { + const node = DepositTree.formDepositNode(nodeData); + this.nodeCount++; + this.formBranch(node, this.nodeCount); + } + + /** + * Inserts a new node into the tree using already computed node hash. + * @param {Uint8Array} node - The node's hash to be inserted. + */ + public insertNode(node: Uint8Array) { + this.nodeCount++; + this.formBranch(node, this.nodeCount); + } + + /** + * Computes and returns the root hash of the deposit tree. + * @returns {string} The computed root hash of the tree. + */ + public getRoot() { + let node = DepositTree.ZERO_HASH; + let size = this.nodeCount; + for ( + let height = 0; + height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; + height++ + ) { + if ((size & 1) == 1) { + node = digest2Bytes32(this.branch[height], node); + } else { + node = digest2Bytes32(node, this.zeroHashes[height]); + } + size /= 2; + } + const finalRoot = ethers.utils.soliditySha256( + ['bytes', 'bytes', 'bytes'], + [ + node, + toLittleEndian64(this.nodeCount), + '0x000000000000000000000000000000000000000000000000', + ], + ); + return finalRoot; + } + + /** + * Creates a clone of the current tree instance, copying the branch structure and node count. + * @returns {DepositTree} A new DepositTree instance with the same state. + */ + public clone() { + const tree = new DepositTree(); + tree.branch = [...this.branch]; + tree.nodeCount = this.nodeCount; + return tree; + } + + /** + * Forms the deposit node from the given NodeData structure. + * @param {NodeData} nodeData - Detailed data of the deposit, including public key, withdrawal credentials, signature, and amount. + * @returns {Uint8Array} The hashed node as a Uint8Array. + */ + static formDepositNode(nodeData: NodeData): Uint8Array { + return DepositData.hashTreeRoot({ + withdrawalCredentials: fromHexString(nodeData.wc), + pubkey: fromHexString(nodeData.pubkey), + signature: fromHexString(nodeData.signature), + amount: parseLittleEndian64(nodeData.amount), + }); + } +} diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/index.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/index.ts new file mode 100644 index 00000000..943e7a74 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/index.ts @@ -0,0 +1 @@ +export * from './deposit-tree'; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts new file mode 100644 index 00000000..f580e0ad --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts @@ -0,0 +1 @@ +export * from './integrity-checker.service'; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts new file mode 100644 index 00000000..4b40e973 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -0,0 +1,140 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { RepositoryService } from 'contracts/repository'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { BlockTag } from 'provider'; +import { DepositTree } from './deposit-tree'; +import { + VerifiedDepositEvent, + VerifiedDepositEventsCache, +} from '../../interfaces'; +import { DEPOSIT_TREE_STEP_SYNC } from './constants'; + +@Injectable() +export class DepositIntegrityCheckerService { + finalizedTree = new DepositTree(); + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private repositoryService: RepositoryService, + ) {} + + /** + * Initializes the deposit tree with an initial cache of verified deposit events. + * @param {VerifiedDepositEventsCache} initialEventsCache - Cache of verified deposit events to initialize the tree. + */ + public async initialize(initialEventsCache: VerifiedDepositEventsCache) { + await this.putEventsToTree(this.finalizedTree, initialEventsCache.data); + } + + /** + * Inserts a list of finalized verified deposit events into the deposit tree and returns the updated tree. + * @param {VerifiedDepositEvent[]} eventsCache - Array of verified deposit events to be added to the tree. + * @returns {Promise} The updated deposit tree after adding the events. + */ + public async putFinalizedEvents( + eventsCache: VerifiedDepositEvent[], + ): Promise { + await this.putEventsToTree(this.finalizedTree, eventsCache); + return this.finalizedTree; + } + + /** + * Inserts a list of latest verified deposit events into a clone of the deposit tree and returns the cloned tree. + * @param {VerifiedDepositEvent[]} eventsCache - Array of verified deposit events to be added to the cloned tree. + * @returns {Promise} The cloned and updated deposit tree after adding the events. + */ + public async putLatestEvents( + eventsCache: VerifiedDepositEvent[], + ): Promise { + const clone = this.finalizedTree.clone(); + await this.putEventsToTree(clone, eventsCache); + return clone; + } + + /** + * Checks the integrity of the latest root against the blockchain deposit root for a given block number. + * @param {number} blockNumber - Block number to check the deposit root against. + * @param {VerifiedDepositEvent[]} eventsCache - Latest events to verify against the deposit root. + * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. + */ + public async checkLatestRoot( + blockNumber: number, + eventsCache: VerifiedDepositEvent[], + ): Promise { + const tree = await this.putLatestEvents( + eventsCache.sort((a, b) => a.depositCount - b.depositCount), + ); + + return this.checkRoot(blockNumber, tree); + } + + /** + * Checks the integrity of the finalized root against the blockchain deposit root for a given block number. + * @param {number} blockNumber - Block number to check the deposit root against. + * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. + */ + public async checkFinalizedRoot(blockNumber: number): Promise { + return this.checkRoot(blockNumber, this.finalizedTree); + } + + /** + * A private helper method to compare the local deposit tree root with the remote deposit root from the blockchain. + * @param {number} blockNumber - Block number associated with the deposit root to verify. + * @param {DepositTree} tree - Deposit tree to use for comparison. + * @returns {Promise} A promise that resolves if the roots match, otherwise logs an error and throws. + */ + private async checkRoot(blockNumber: number, tree: DepositTree) { + const localRoot = tree.getRoot(); + const remoteRoot = await this.getDepositRoot(blockNumber); + + if (localRoot === remoteRoot) { + this.logger.log('Integrity check successfully completed', { + blockNumber, + }); + return true; + } + + this.logger.error( + 'Deposit root is different from deposit root from the network', + { localRoot, remoteRoot }, + ); + + return false; + } + + /** + * Inserts verified deposit events into the provided deposit tree and logs progress periodically. + * @param {DepositTree} tree - Deposit tree to insert events into. + * @param {VerifiedDepositEvent[]} eventsCache - Events to insert into the tree. + */ + public async putEventsToTree( + tree: DepositTree, + eventsCache: VerifiedDepositEvent[], + ) { + for (const [index, event] of eventsCache.entries()) { + tree.insertNode(event.depositDataRoot); + + if (index % DEPOSIT_TREE_STEP_SYNC === 0) { + await new Promise((res) => setTimeout(res, 1)); + + this.logger.log('Checking integrity of saved deposit events', { + processed: index, + remaining: eventsCache.length - index, + }); + } + } + } + + /** + * Retrieves the deposit root from the blockchain for a specific block. + * @param {BlockTag | undefined} blockTag - Specific block number or tag to retrieve the deposit root for. + * @returns {Promise} Promise that resolves with the deposit root. + */ + public async getDepositRoot(blockTag?: BlockTag): Promise { + const contract = await this.repositoryService.getCachedDepositContract(); + const depositRoot = await contract.get_deposit_root({ + blockTag: blockTag as any, + }); + + return depositRoot; + } +} diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/contracts/deposits-registry/store/index.ts b/src/contracts/deposits-registry/store/index.ts new file mode 100644 index 00000000..eed6aa71 --- /dev/null +++ b/src/contracts/deposits-registry/store/index.ts @@ -0,0 +1,3 @@ +export * from './leveldb.constants'; +export * from './leveldb.module'; +export * from './leveldb.service'; diff --git a/src/contracts/deposits-registry/store/store.constants.ts b/src/contracts/deposits-registry/store/store.constants.ts new file mode 100644 index 00000000..3198f854 --- /dev/null +++ b/src/contracts/deposits-registry/store/store.constants.ts @@ -0,0 +1,15 @@ +export const DB_DIR = 'cache'; +export const DB_LAYER_DIR = 'cache:layer'; + +export const DB_DEFAULT_VALUE = 'cacheDefaultValue'; + +export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ + headers: { + version: '-1', + startBlock: 0, + endBlock: 0, + }, + data: [], +}); + +export const MAX_DEPOSIT_COUNT = 2 ** 32; diff --git a/src/contracts/deposits-registry/store/store.fixtures.ts b/src/contracts/deposits-registry/store/store.fixtures.ts new file mode 100644 index 00000000..10bda7c4 --- /dev/null +++ b/src/contracts/deposits-registry/store/store.fixtures.ts @@ -0,0 +1,50 @@ +import { + VerifiedDepositEvent, + VerifiedDepositEventsCacheHeaders, +} from '../interfaces'; + +// Mock for VerifiedDepositEventsCacheHeaders +export const headersMock: VerifiedDepositEventsCacheHeaders = { + startBlock: 1000, + endBlock: 1050, +}; + +// Mock for VerifiedDepositEvent +export const eventMock1: VerifiedDepositEvent = { + pubkey: 'abc123', + wc: '0', + amount: '100', + signature: 'def456', + tx: 'ghi789', + blockNumber: 1001, + blockHash: 'aaa111', + logIndex: 1, + index: '0', + depositCount: 1, + depositDataRoot: new Uint8Array([1, 2, 3, 4, 5]), + valid: true, +}; + +export const eventMock2: VerifiedDepositEvent = { + pubkey: 'xyz123', + wc: '0', + amount: '200', + signature: 'uvw456', + tx: 'rst789', + blockNumber: 1002, + blockHash: 'bbb222', + logIndex: 2, + index: '1', + depositCount: 2, + depositDataRoot: new Uint8Array([6, 7, 8, 9, 10]), + valid: true, +}; + +// Mock for the structure {data: VerifiedDepositEvent[], headers: VerifiedDepositEventsCacheHeaders} +export const cacheMock: { + data: VerifiedDepositEvent[]; + headers: VerifiedDepositEventsCacheHeaders; +} = { + data: [eventMock1, eventMock2], + headers: headersMock, +}; diff --git a/src/contracts/deposits-registry/store/store.module.ts b/src/contracts/deposits-registry/store/store.module.ts new file mode 100644 index 00000000..0bf10e80 --- /dev/null +++ b/src/contracts/deposits-registry/store/store.module.ts @@ -0,0 +1,34 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { ProviderModule } from 'provider'; +import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './store.constants'; +import { DepositsRegistryStoreService } from './store.service'; + +@Module({}) +export class DepositsRegistryStoreModule { + static register( + defaultValue: unknown, + cacheDir = 'cache', + cacheLayerDir = 'deposit-cache', + ): DynamicModule { + return { + module: DepositsRegistryStoreModule, + imports: [ProviderModule], + providers: [ + DepositsRegistryStoreService, + { + provide: DB_DIR, + useValue: cacheDir, + }, + { + provide: DB_LAYER_DIR, + useValue: cacheLayerDir, + }, + { + provide: DB_DEFAULT_VALUE, + useValue: defaultValue, + }, + ], + exports: [DepositsRegistryStoreService], + }; + } +} diff --git a/src/contracts/deposits-registry/store/store.service.spec.ts b/src/contracts/deposits-registry/store/store.service.spec.ts new file mode 100644 index 00000000..90e41ac5 --- /dev/null +++ b/src/contracts/deposits-registry/store/store.service.spec.ts @@ -0,0 +1,51 @@ +import { Test } from '@nestjs/testing'; +import { MockProviderModule } from 'provider'; +import { ConfigModule } from 'common/config'; +import { LoggerModule } from 'common/logger'; +import { DepositsRegistryStoreModule } from './store.module'; +import { DepositsRegistryStoreService } from './store.service'; +import { cacheMock } from './store.fixtures'; + +describe('dbService', () => { + const defaultCacheValue = { + headers: {}, + data: [] as any[], + }; + + let dbService: DepositsRegistryStoreService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot(), + MockProviderModule.forRoot(), + DepositsRegistryStoreModule.register(defaultCacheValue, 'leveldb-spec'), + LoggerModule, + ], + }).compile(); + + dbService = moduleRef.get(DepositsRegistryStoreService); + await dbService.initialize(); + }); + + afterEach(async () => { + try { + await dbService.deleteCache(); + await dbService.close(); + } catch (error) {} + }); + + it('should return default cache', async () => { + const result = await dbService.getEventsCache(); + expect(result).toEqual(defaultCacheValue); + }); + + it('should return saved cache', async () => { + const expected = cacheMock; + + await dbService.insertEventsCacheBatch(expected); + const result = await dbService.getEventsCache(); + + expect(result).toEqual(expected); + }); +}); diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts new file mode 100644 index 00000000..c272ef5f --- /dev/null +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -0,0 +1,190 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Level } from 'level'; +import { join } from 'path'; +import { + DB_DIR, + DB_DEFAULT_VALUE, + MAX_DEPOSIT_COUNT, + DB_LAYER_DIR, +} from './store.constants'; +import { ProviderService } from 'provider'; +import { + VerifiedDepositEvent, + VerifiedDepositEventsCacheHeaders, +} from '../interfaces'; + +@Injectable() +export class DepositsRegistryStoreService { + private db!: Level; + constructor( + private providerService: ProviderService, + @Inject(DB_DIR) private cacheDir: string, + @Inject(DB_LAYER_DIR) private cacheLayerDir: string, + @Inject(DB_DEFAULT_VALUE) + private cacheDefaultValue: { + data: VerifiedDepositEvent[]; + headers: VerifiedDepositEventsCacheHeaders; + }, + ) {} + + public async initialize() { + await this.setupLevel(); + } + + /** + * Initializes LevelDB with JSON encoding at the cache directory path. + * + * @returns {Promise} A promise that resolves when the database is successfully initialized. + * @private + */ + private async setupLevel() { + this.db = new Level(await this.getDBDirPath(), { + valueEncoding: 'json', + }); + await this.db.open(); + } + + /** + * Fetches and constructs the cache directory path for the current blockchain network. + * + * @returns {Promise} A promise that resolves to the full path of the network-specific cache directory. + * @private + */ + private async getDBDirPath(): Promise { + const chainId = await this.providerService.getChainId(); + const networkDir = `chain-${chainId}`; + + return join(this.cacheDir, this.cacheLayerDir, networkDir); + } + + /** + * Asynchronously retrieves deposit events and headers from the database. + * Iterates through entries starting with 'deposit:' to collect data and fetches headers stored under 'header'. + * Handles errors by logging and returning default cache values. + * + * @returns {Promise<{data: VerifiedDepositEvent[], headers: VerifiedDepositEventsCacheHeaders}>} Cache data and headers. + * @public + */ + public async getEventsCache(): Promise<{ + data: VerifiedDepositEvent[]; + headers: VerifiedDepositEventsCacheHeaders; + }> { + try { + const stream = this.db.iterator({ gte: 'deposit:', lte: 'deposit:\xFF' }); + + const data: VerifiedDepositEvent[] = []; + + for await (const [, value] of stream) { + data.push(this.parseDepositEvent(value)); + } + const headers: VerifiedDepositEventsCacheHeaders = JSON.parse( + await this.db.get('headers'), + ); + + return { data, headers }; + } catch (error: any) { + if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; + throw error; + } + } + + /** + * Generates a deposit key string based on a given number. + * The number is checked to ensure it falls within a valid range (from 0 up to MAX_DEPOSIT_COUNT). + * If the number is out of bounds, an error is thrown. + * The method creates a buffer, writes the number to the buffer in big-endian format, + * and returns a deposit key string that includes the hexadecimal representation of the number. + * + * @param {number} number - The number used to generate the deposit key. + * @returns {string} The deposit key in the format 'deposit:XXXX', where 'XXXX' is the hexadecimal representation of the number. + * @throws {Error} If the number is less than 0 or greater than MAX_DEPOSIT_COUNT. + * @private + */ + private generateDepositKey(number: number): string { + if (number < 0 || number > MAX_DEPOSIT_COUNT) { + throw new Error( + `Deposit count is out of the valid range (0 to ${MAX_DEPOSIT_COUNT}) received ${number}`, + ); + } + const index = Buffer.alloc(4); + index.writeUInt32BE(number, 0); + return `deposit:${index.toString('hex')}`; + } + + /** + * Parses a JSON string to a VerifiedDepositEvent, adding a Uint8Array for the depositDataRoot. + * + * @param {string} dataString - The JSON string representing a deposit event. + * @returns {VerifiedDepositEvent} The parsed deposit event. + * @private + */ + private parseDepositEvent(dataString: string): VerifiedDepositEvent { + const data = JSON.parse(dataString); + const depositEvent: VerifiedDepositEvent = { + ...data, + depositDataRoot: new Uint8Array(data.depositDataRoot), + }; + return depositEvent; + } + + /** + * Serializes a VerifiedDepositEvent into a JSON string, converting `depositDataRoot` from Uint8Array to an array. + * + * @param {VerifiedDepositEvent} depositEvent - The deposit event to serialize. + * @returns {string} The serialized JSON string of the deposit event. + * @public + */ + public serializeDepositEvent(depositEvent: VerifiedDepositEvent) { + const { depositDataRoot, ...rest } = depositEvent; + const value = { + ...rest, + depositDataRoot: Array.from(depositDataRoot), + }; + return JSON.stringify(value); + } + + /** + * Inserts a batch of deposit events and a header into the database. + * + * @param {VerifiedDepositEvent[]} events - An array of verified deposit events to be inserted into the database. + * @param {VerifiedDepositEventsCacheHeaders} header - The header information to be stored along with the events. + * @returns {Promise} A promise that resolves when all operations have been successfully committed to the database. + * @public + */ + public async insertEventsCacheBatch(records: { + data: VerifiedDepositEvent[]; + headers: VerifiedDepositEventsCacheHeaders; + }) { + const ops = records.data.map((event) => ({ + type: 'put' as const, + key: this.generateDepositKey(event.depositCount), + value: this.serializeDepositEvent(event), + })); + ops.push({ + type: 'put', + key: 'headers', + value: JSON.stringify(records.headers), + }); + await this.db.batch(ops); + } + + /** + * Clears all entries from the database. + * + * @returns {Promise} + * @public + */ + public async deleteCache(): Promise { + await this.db.clear(); + } + + /** + * Close the database connection. + * + * @returns {Promise} + * @public + */ + public async close(): Promise { + await this.db.close(); + } +} From 9c33bc7c9d79c38e7b0d9fde4eef22c77015f029 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 1 Sep 2024 13:52:11 +0200 Subject: [PATCH 16/94] feat: deposit-tree tests --- .../deposit-tree/deposit-tree.fixture.ts | 1084 +++++++++++++++++ .../deposit-tree/deposit-tree.spec.ts | 103 +- 2 files changed, 1159 insertions(+), 28 deletions(-) create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts new file mode 100644 index 00000000..de67b7f2 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts @@ -0,0 +1,1084 @@ +export const fixture_10k = { + events: [ + '0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e', + '0x76fffc948646005fce32e27555238dfe801c9e7eea28ff40dbe2afe8f83cf0c6', + '0x3e74b357fbf0bf36bed50de7ee3a3caaa11006e7ea5ce644d16de8d666b2c7a9', + '0x790284c0a36abd53ec0ce9284f4ad4af72c891a38456e73e15685bb99dfc09e9', + '0x0fd0bdf2cee28566c006a622cf5a1763de51233faa3a4bf7d95fed76c9a5fd05', + '0xdbd986dc85ceb382708cf90a3500f500f0a393c5ece76963ac3ed72eccd2c301', + '0x0cf210b6d6dcf3581ab34266ae25e2b0e9b20c50581b004dea1080dd0939a97d', + '0x227f7f4f8a112532572bc53ae300d06e7aed9b3ea531a36221629af6783cb1f8', + '0xf963d928e334ef682fa5cf0e651988ea32fddada361f09d04e951b952038dc74', + '0xaacbc03b795b8aec28b10500d3410dd7c5aa4031b083b3d20388e52fdacc7f6e', + '0x01a9de4c3f8775c14384c405745bc6bb34ef87cb9bf8275201f19a28b3f99aa2', + '0x64dc8d9e3cec6a41a42ea105bb38b59d7ca49b14d6c7e678cba309fbfb0e7af4', + '0xbbabd378476db7f6f0520a943b2e6186c305610601ca59fa11212eacdc2ef317', + '0x685ed3e1f3d46604214a101cd5825d55e0df8250af7442403f4ebcfc15be6e99', + '0x6faa51b25ff2f0a1297feec95e1d95e6041c3b2270b55fd46d52e09557bac5fc', + '0x6292c84fb0b7f5bd88fbe9ba7ec38bf589ae2811a8503a6c60cd76f52b63586f', + '0xbb88366a75271d570e88f940fc7de86f119575b755fea171867b38c9e9b4e1df', + '0xaa28011f9bbab5d924e249e9516bdbdc48aa80110e9f59b09b74373c91866002', + '0x4d1ce04b3aebac268eb9c329c8c96e3da8ba133e73dac32f6e14423e1df4f9fe', + '0x291684946eb577357efd7d119f05addc928a780961f3cab307dceb9ed346f26f', + '0x5c094a65e756690096e53f90c262fd88b2ed3bd7b2a1ec943df138d9b6794b7a', + '0xebe8ec1d0329d2bb285efcc157df907d48d0c56eadcd7e2be7e5a71d6d4f59a8', + '0x8053b81185dd809b973c71f54df69c6e82eef774e43f8b12c9a113a4374118bb', + '0xa9ed7ddf21134290767102defa883b87861c891f5d4a8d2d78632a791116258f', + '0x15afffabc64e3eb7b6833494bd38808a2c3600f8c40ecbe4500d1602f6330552', + '0x8f2e40a9507594979456ab30d150905cd85c585e83bfc57ad2b7d301dccfdec4', + '0x8abe605ff94d099346225b998a4986271d0445efd2329ced0db45d0ca3228fcc', + '0x31d3bf344ecfb44fc014abd2f86340f47263fc59bc11b20783cd1e4456a0f177', + '0x27a4028494edee3471bf6d6681db3558f468d347b1be12899e0416f011f1caaa', + '0x240389c323a9830f1c4354d32e0b5a319edfcd8f786d8a709e6c605206b64d27', + '0x2202f5507ec3a809d26ad969b4bae1a34d21574de375347c1749b926e223b3a0', + '0x3f51ee386cc183b1e43da81c5aee14e817496f3cca30bac5a896e58578b71f77', + '0x773bc477c150bcd733d4e8413d682e0d411fa0f68605cf8b86108efc9040b8f6', + '0xa9d57ad371b3889f5f50c241f5e8de1d2f0f053dbd3011934266670989e79133', + '0x5c2ee552a2907fcef809b9b34045644bf635d72ac6f579c8ea8f17e2545e0447', + '0x8b212b94494bdd11fee558807ea2365048d3a2d965e5801f5f6bf29ed385f674', + '0xb468dd1ee4bac68b8dd6980fb806fd6fc158acac1b267dda9383df724776e32d', + '0x1f50cda24d72ee068b4683862919f1b30f15fce6e3bc31f0af8651b9cd59f9a2', + '0x9bc78e7b0c29ec858370dcd9604e7c4a118f8647770f0be8d92ac0a044897b63', + '0x256287af00e8ab72e0947bcea414427154f9d72062d1079c7c6fbb1a8be97af1', + '0xdc53ebcd02d9a355be29b66821abc916e720883e1024b5adf62b4219d5480b44', + '0x7a6e5ae6f425fb538ae763a6900285f6433a476d2ace3d4a50dda1b5be55041a', + '0x0461628cdc499ae9d52b45fa6df2c64fcaf3c70a13142b66e6e9fbfb3374746e', + '0xc148821c28b65063d3863505de5efd14dbae01200f4dcd04e732f579e657d9df', + '0xcdbe7bff81fb547017918e5634fe40d3b35ee91b32831dab8ef8ac99d57bdbf0', + '0xd8d4dd4db26e35cd1a00c899c7286bc3a883427a475aac6365139bca33e04ffa', + '0x14c289d0754ebccc4642bbd494fee4624323febb7573b4589ba300d45616c0d5', + '0xbfd2bd584723243047c5aaabf613b0a52bcbab481983d600a57addde642227a9', + '0xf98dce5a5e98ad51843f922b26b685de87f9041277a4c6a8a12756bac2ae1e80', + '0x3ed5bb4a73e8485df93d3588bf91a5408bd3ba635167656d360077d12f861e31', + '0xabdc41954cc9c783d64acdcf610322ccd29cb2288d1544dd31b9a6e72c370deb', + '0x844b44d5a0738358a3938646881b563a8068aaeb7e10c97ae7a1251147039a9a', + '0x9b9cf0518542594e9d5fab0e65494bf6e62186c3a94da4e19b89b8a4d5f8eaa1', + '0x8a7192e917fdb883bc7bb3c20077c2e211e18c9ef27cfa8016763a90b0fc9056', + '0x68656891f8d5517f59f8b520819440a2e4a6519c3168a18218307d60fd2d1d31', + '0xf4bc40e60d41266effe10f3c128e7ec2745cc18d190be8ec0cadb1ab461bb6f3', + '0xaa2848f1960fd195f924cd96dbe0f490dc7d184010d7dc970e3f54c8291da5dd', + '0x5c351787db91f668b3c70801cdb8d3087a4469ad88235f02fa74ef880341028f', + '0xda17919d50092ae60e1e2df4a5f5537ed3d963be358ef6b56af9f1080f588ad5', + '0xb05465f51be5137b84e1d04d2f2ef9bf7dbd82542c70bc562fcbbdfdd362d96a', + '0x48b36388ae89979c433535573f3f9f347067cc159338d33434a2f4ca65c74bb1', + '0xf4129e092304d083ca7f31296aa296768f6dc51d85ed5f96278fb5be48ef2296', + '0xe622aefd42e8e3c70858da6eb29500bd1135f5670874140f308f435b454c015c', + '0x3d725b0ee40b0563332a6e09baa49ce777c90abe5404f77ada2429620f644e16', + '0xb8f3b35acd787284e4214eb1284517f197266733c0c5ea3baf6cf4aeb04be8a9', + '0x946424faab4e5c047ae9870236b641a73c8e1f42346d59e38cbb77f9f88dd6eb', + '0xac90de872f1f092a8258c35a2440c776c840e4ac0d8eed6a39920fe19a48d60d', + '0x9970cc53fef13e734266a0a00d43b8349addc442ef08b122c54e85c6efaec64d', + '0xbd94fea0a4c628a72474210bd0c01a89c2c9470a429e491190ecedbaeff03ea6', + '0xbe74b4ba0f91af15365258bf2384b2c1ec897c80aee856d40fb97b1057a3f9b0', + '0x53978685ca0b3a5123326756578ddd00b933cac87dc515b25994bbd0b6b11af2', + '0x5f9a348196532d8a0486f7a082071498016d2d1c90c6783fca22020265a3ceca', + '0x7ad296df98be10af7deeb1567847bea529f879829a7029d5d84084cb2ef06395', + '0xe6ac2082359d3cad3217b80b9d004236b64b78e0a9d6fbcd204b56dba360c003', + '0x075f089a2d8069c0062275ab8b20093dcb7a63577f0d367dfe3e5093126336e5', + '0x79cfc5cc08541d85ebe21cfa8d367aff7b4da0bbec3b48fb325be60f7c0cd4a3', + '0x7294fb60e3bcc9e719fd4e7822bb933702d65c53dfde1537e92e28d285bbdc25', + '0xb3e82cfa34dc502d28d412a59ea7e42bb96c2a24c1df16a414ebded848f0c5cc', + '0xc1fc69a88517e0585887f234b417c775c526da2af7956cdf9c81285e94d8797d', + '0xf9e7ddb3b78d0b71f829c3f56f99f6ab45232bce37b0f4a8350c58b55bf85c10', + '0xb6e1cca9693ac9488c6914b8d698fd490958c687fe6d047faa0e5fd76440da1c', + '0x8c880663bc3f919b0504a6a151d04e78f607532fafadfbcfc2388a6aab3fb717', + '0x22ae224e105d7a62afb5c3203975acd9d6e94aab2d1f0b5bbefe7ee6d57453d5', + '0xc938e0775050b42a324ec832cb945cb1a9782ef58295763533cf8fa89c0d8ab0', + '0x580abe3ff2494d245ff23c27483651b9cfdc51c011347d6dc34abfdff52ce03c', + '0x5242278356feddc272521712273f8083409a915cb40721e2690e14759a3ad20a', + '0x12cceef0598e018b4192f160c2f2175b67adeeffbfdf52a21fa646c6c0c9d24a', + '0xa18a1d47c2229c441ab2ff24d78a86b0d966ad8a2e92cbe3544773f032a295a2', + '0x5e97cd8b6401d254e100199bc523c41c2f313a6980cf33c3b86d195c71e68054', + '0xef3cb1ec905ff3c537f34be3008b3e98100c938bf9fe3f79a4854cbd90b2ff03', + '0x169675c35ffbacf44fd3c994a9d18452914cd8fdee7a7910386dc3505b9f1c5a', + '0x4c525e191c3a67aa3ece110b5571d9d6900c277867026379f9cdf8ba6ebe1024', + '0x806040ae46bc27b9f48ab2cd660e0f7c44a905d3f05a3b9f002c0766c354f876', + '0x1ed2be894a6bc85a74682c0c4045a15fa9b3e1671c30e67140de2fbb9fec4151', + '0x01de4f2f53213f326d7c774a6b3a26f3a5efcddb515053a530286632f7393806', + '0x60f5591e1459df0ac8b2496e92dd69bc3ef8a39ee00b49ccaf0fc7d725da9339', + '0x944be7472306ef5ac4f3ad63387ed1f56f586882fd1394ca88f8057b648d305e', + '0xf4aca036b84864b07d27d7445105613b616e321d815aa25a0083db7c7446881a', + '0xb4caeb6fcf9ac5694e8757353b5aa80f2e9b7c42968e7623f2eca1f41f97a343', + '0x082140ea627495e3a842fc7a047695ec237a332f5c937720ab852f01c33f5ff7', + '0xc04efa5c6944e4f27370a8afce7b56a61309495413139bc6e128c68b961386b4', + '0x7ee14aa819f0819a6ef4d22a57bd59c2322662c1f794fa125aea9b9256d48d9e', + '0x6101eb5459ee526c2195322882ea69b74685daa7c7f9d66d85489daf0303079f', + '0xd2ca717681d06630fc60b68ae31eb416e1afb5a70e1d45713247804bec96b198', + '0x473e6abec3c5945505958388bfc8202ed5e1dd9e52f0ab6489d595583c7ecca2', + '0x953ebdb1b2dbc473562b8739c203c49d692531bdc8fe57aba3e0b4cb42c2844e', + '0xb89f3d6cce3046b103b19cd5e023473391e76e61fbafc44b13a32de8c037f69b', + '0x50aebcd7137f166fa59a25bb0ba68fdb08a8c64d858b1106a6f67465dca8b49a', + '0xbba6adce21ae1d20d22e0dbc52127834756c571f0feb23748709f3336e299a04', + '0x28ea7221dfa4fc014023912b3a41db2d3a44be136872bb13eea86b28f4965605', + '0x49250acba166e802e2a4d2d11d1d7222d653f93d664e70d6f6bd6bcc88fff8c8', + '0x4a9f15b0b3c2eb034f4decf41ec6c64b6a4881fe8a637cbcb82e800e9dc32278', + '0x4160268dc31fb5eb3f522e6928c99f2ad1c9f920c5649001ca294aff3a109f3f', + '0xd55140cb0c8bed4ffb4170390bf2b3a1198b0a8cf69148d0c8bed0c3a5a440f5', + '0x06500da7b85c17a7d168fae170dbb70d962fbf587fc6cf79c160db453eed4afb', + '0x1ccd8fb070400f4ed2ccfb1876fc74d924eaf8738394d67cc0cce53ce5937b6c', + '0x55bd2acb82617f032145541ddcde126e5d86507bdf5e3aaef74fe908504dc50a', + '0x9312533bd8b9612bc0cfe113e669d187a8c0c2116ca4324da1524edbbe0b014e', + '0x854c4ae0a25dea79d1a3beef2f2b43ec9f88e664403a925b13f01a68cd725bc8', + '0x433b187b7e8cf4065ea15441c4bad8f886a23cbe553394fc69edca119d37c18b', + '0xd563637c6e5101a657915a418bc4825638132073e18d6fb79757a2d0e9912ced', + '0x634685fc6263a04971c12ca95eeb8ab718c19a3f2f4afc9afe69c7fa320ffb6a', + '0x71f1cd6ea922e3167b159f23f3d649f67697ec2d4cfce63bb08893147c95f0f8', + '0x0914090e5e2206661e66985f53901895c67e923c94a438c906e863b0ceb09e78', + '0x3caaf484ef19cbeefabcacc5478aa65ec4416e3c8f899d190333e45c0b1ea566', + '0xb3165c968851b62f318d1285571b0409e864be256f4817cae88672eeb31d32e1', + '0x41cfe1c698c9043be596a147aa418081b062401cc73bccaa0cfa933714c0f4b8', + '0xf05cbc59d191faeff9ed3d3f4cd690567f20792246f61059944c63770ebdc5d7', + '0x4c3b75eee105866f388ddc157535fd95be88031e1cb6ae39fce3f52b33158550', + '0x3e8da0723685545a8e2904865125c326eff75c3f41bb3c6ab2f799d51fd462e5', + '0xb27fe2aa2cf4563038a3cd41e43ad89797e9e6876e0b38b3a04023c588bb4683', + '0x14c6de0a761ae1d1d94007b4f1926a54f5c2afe5262abc7116d7602b9a39baf7', + '0xa025651ef54b13c36c12199b9bb770808fcbe2fbc06027dcb417864ea675f676', + '0x03aa2160fb1089fb3512cc62019ca94bd1ea909d3ebd2ab139a75618d2ef1031', + '0x1858cbcaa0bac301639154458a05d6e9e09179c82fe7c4e4d44294c734ba827b', + '0x17664f7bf31cfc1cbcc45e93a56e7506121005ab96c67ae4b2791386bb4e2e80', + '0x38d45c31af6fa5a8c9311ff1f5bafa5ffc3d83aee06e6984e4f4712592cbb962', + '0x8d4aa80fc5bbb3bfd4523199ee42bf9cc324185c5c39f25d58c4f3830ce2bec3', + '0x7e1cac47a243baed0360966304e5ab0433f1bd31baeea0c9f8ff3b0a194cf936', + '0x748763344f58c131c83f33f9f3ab67197ce538ea580b964c0891d3e93febb0b6', + '0x5d87cbd61f541df420f3d7182988ea3985392d6248145f75ed8c39c94bf78f8c', + '0xe42c765df85cf5a762766023d4f03f1957ba8c03ac8d0ec9ebf7d941e461d637', + '0xbe85b7eab113fd6c9dc5a79dc54919f320bb16a6c15ccad1bf4188b3a92605f8', + '0x5a0685fc3047a67e1001d9ca2b53a44651893318ba38c48a3537859aed33b7da', + '0x684984da2fa594aeabebf162dd378873d2c18fe7728aaaa0ba2bb8db5aa3d789', + '0xd1a8a85b78da62fcb74190ebe93e353b7952892adcb5f8eb3429e62636d4929c', + '0x557415000cc04400d649a4ed29d15277a7bbf8ac25921481ee54eb2fb9e7ed27', + '0x5cf33d6c47bcff8db958b614b06f05cf7fc03b26d3e80bf8a57d6a7669f198ea', + '0x837523a5308087bfdd8e902a70c65d21a7c369f18ffaea7ec4f11110ad6f8927', + '0x95d83e60206935310e0f544ba18a62af694800d978937ea53de10fa56844b7e9', + '0xc1b83eddf9d20e5506ac2740cd09fc5454fac62237a9840fe36e730af64ef4d5', + '0xb656d797a9778964ebb0ce04f4aee42f5cfb92a8c8180a2f224f2b8eae2a11e8', + '0x6a1032b0252df99a7d4fba20dcce566da57d9b8b2c6889b7544781bbb6b0dfbe', + '0x7832e17600e4410a5944a6b98e321378556487f7479c7a61d76745a73c128929', + '0x617296d4d696a5dcdb14316fccb5391a52a8ed132e686026cf1ef8afcc4dff23', + '0x134730ed4259cc79c2538c276085cd7b829bb0d578ccbc922b26ca944014ad67', + '0xfafe65deead69e2173d3eb1ab2384ff1cd3a814f5bb1f15c1082f4ae519d0f06', + '0x5ded236f4bed3f24e1b8f325f94ef363128519aa7d0248a250655bad8b52fc79', + '0x38f086a7cacf753a6d527cffe6c989fde983f43ce8ea37268dd70580f35d925a', + '0x6fffefc15f8e6055b510c366a9f9d2f080365eb11f0b6037cebb2b3304d30c87', + '0x90a8bf8e435b0290cc8ce073de91da37f7986d61d5f26c4af6c3e33782075a4b', + '0x132bcfef2e758dfbbfecf90cbd3ccd1eda75f0b409d80722970592ab22d5274c', + '0xb738d02ee2c884dcdbf76dda6f30805cefd86169157d34c3705c2ee6c1fe75cc', + '0xd477bd9ce819f3b98a22de0039e82bc95f660c7b50e4434f9ffef0181f48f829', + '0xaa55fcd3865134de79d0be329f17d4b9f50c1f208bdbd88f4a2c0009f39c28e1', + '0x9288ba8bb044603e8872c7ebd997fb15e07f7959c9c36f4d849d881b7f43fa53', + '0x6820043d6bbe9930568ad96d889b6ab5f2106fa210179717e25e9279766c7cda', + '0x5ed2932f5e1c7949218f27be45f17a745e1c1116e2b335c09bfc98f8bb79b97b', + '0x212c20f2421e5028316e736666271db96c85fdd2d908885999266aef098b6d03', + '0x6be230f8b0120d371cdd1b207505a1e2a947fc82da4cac7f6e0d577e58a2341e', + '0xdadc3db8486becc8327c1e80d77039a2d609237cdcf1e19bb25e2a7b23a9d0fc', + '0xe90d991aa875f6773fbb12337616303f972236aa71b7611885093bd22b53773e', + '0x77bf6c89399a8dd1612098a3b4b7823e61211f241bb3b6415fbd127abd20d4d9', + '0x2200b9cc7dfa84050dd2f14a7e8d52689790d8cb1bdc7f072b66b992aa74ca8b', + '0x2c371ccbbabdfb895e39a07257d73fa021fcdd94caadfa1d294c9a718b629f48', + '0x3cc465a2184f91c2014d07d99a00827709f996779d8d0d155be54e009fbf4d81', + '0x0455b8d543cd783b66be8ef7c9154b15a9df91d604ccd75e311acb7cd8aae713', + '0x105cedd0ff44c7c8f01c84c1dfe747f42043c7487e766a6b8f864c3d6fb8e056', + '0x5b957726b8b0e2824240925b28f1de33b0d26f6c3449378e0253bf2af12539b9', + '0x7a3b4851fab70861611ef909e00e07d3566789eb1215a0087b5cfaf9f054b0aa', + '0x39e6cd9b9efd3b84afab9b5d57a8f6b20de2213467ac007025bf49c3df1f58e9', + '0x257f57f2fee42172d1fb1593eecba42eac7c67f4162c7df35c72f623072133e1', + '0xa4e82c6255985d6adb6e5a7fa133b6b42a3d1216c2a942893e4ac03b9afe825d', + '0xd374f2538241162221855d3ff7a725562f36121f6ee916a71a02fb000bc76c3f', + '0x208b3c485daa20c13711ab86dec6e1fbd2084d3b4308c80c00e5639ac34b63e4', + '0x035d6d9f92078f590891318a462bf84168f4c0d9646d0388098aa40b3e26476b', + '0xd20b62daae2c307040fc6411729d272adc881fac68d58cb6ae1bed8ccd243ddc', + '0x1ebf18167807fd24564cbe8c0783d896b895038519a18d6b751011d7fc0e2982', + '0x90bf16c43f6b5010e835d39448151b1cb4a0c6431579d63451c3f88a33611490', + '0x79d7d15364e68c881ad6ae0b2f57b7d61e6ba09fce8e5b8b371695e73fa0ce38', + '0x93b4a9ad7be7f8b70f8f938c8e55bd221dd52230c754ac5fc7614a935159fe6c', + '0x0489f287085ce771aae94e269a9ba02f6a4903141d46784af7ab54c192b6fd08', + ], + root: '0x3ab1589b2853143f682dad19e443648489d318c8619e79f59a44d14a846fbb1a', +}; +export const fixture_20k = { + events: [ + '0x01ba5818c17223777b13dd194f5e64ca92927af901d0fd1599e93c4554f17b00', + '0x48367a14c733b6b4907a2c60fd9b254bb6d7e4d00d0792544309f097d3b76a44', + '0xd0637c1d813bb304c3e955dc869e80be1447848f4e909d231d728543902858c7', + '0xf0232e374baa5d6ddfe7c9cb4fa092145133d094a8e2e09c10ef6029e7c76d22', + '0xafb4a665b1c72e2987e4d157a761cce824b6f7ed024914f9ebe0b7a034dcb08a', + '0xc23c024afec5f0a51d98678b9df5fc76a30b8822e0f5f8f28afb102ed47a427f', + '0x738aba50404c467b8a81ff6233d5d961ed1c560ed08ddbc1420d4a994c093d7f', + '0xa2dfd76a79a5f21ec8530436d63daeae105d86da535abdb3af14b8a322718a42', + '0x0afa5b014a0adc3aadf67fcc32825daac16a58458cd57949aa590241868d2723', + '0x9fe0af4b96294da5ec3fecc02a35207e04a3cc3fb4513991257414a8416a4927', + '0x6c769169d0cca53ca6e31e0ff4d79ed2768a9155b4fe2edb7507df3a26cc365a', + '0xf1d5421bd3410cf6838b797dbda63ff32f4f36795b0cb613e1cbcc7c04d8e000', + '0x2a174fd52565a39472a8a3397f0315e6a2a44d8d582b3fede3743d2e3e11b818', + '0xd21564ed94c14a109fe15b3ceb6a0896f75af33d34993827f484c9960306229b', + '0xbeb5ed5674e4ca6f8718fd7a6a1b9107cf46d84a6c382a39480ada92cc3fedc0', + '0x88b90504375f3cd5c8ba6162d27290de53935abca037ba75bc7303b869959347', + '0xfc044a27fb392dbe824ff903820c993b16f283d2fef0e1f997c8b0c0396452a0', + '0xb3e3e66f4bfd4b987396f18be8b479b14479658dea5c0e333d7324035df75618', + '0x3ae63505d86feb7b07cede59db3f3c3641fb9d8525db232fe29170f9b509ec11', + '0xb644322ce4a4f0dcaa755b6c893b34b69a65df7a3213d14bff1195c1b61a360e', + '0xf62ffe4b04d36639c9e370ba854c475657a704f4fd5ec94945cfc46b3f3b8433', + '0xf20e43a2bcc1230691b6d022b160847e95c2e99195631fe239eeb11c8aecabdf', + '0xab7ff6ad81b9eb26b24c89b179f5568df2eb90d4a9b68bc465615e5396000bd4', + '0x52a3df54c682c0d1100da3fb5385328ff73b4e257d324b568bccf541a6020ff3', + '0xac4d7a7baa5c8a485e696cb1c75ae184c042db1967a63e644f6deb28afc8bc9b', + '0x65ab1005b284b46ccee8b20922fba6d41bde3afaafc0ea98993c06e2eee3af79', + '0x4876db3d488827e29b6c63b8cd6b53f11f9237d7a3fc6d60770553018e963605', + '0xab74447c39a3c53740387f22a1220503004765130d5ebecde325d5a334ab3b04', + '0x74ff13436dbc3122b86b341a78ec7c5af02b756de29c1341004630078456ac5e', + '0x67b5c22da6a35a72fd3ddbde426a6b89a3c08a454a3900fe357197097c4e8e14', + '0x0879bce9478489f53033eaf052f5570d74db4570ea88f766bc900d432fbd6a4d', + '0xa57b65bb32e860f7c3064880c1ea13f5e76c1eb97ac241136f52bb6d4390380f', + '0xc90f7751c3b7d307e98bab47f01b3f2a70efd819036af31351ac5702c9e7de19', + '0x6ff19cb4dd3c57c3f2f1ce02ee04fdbdea80e51740f57b3080b6d913ad108426', + '0x186de53ae9e7f7f8315497c8730275b47eac51c7fd0972d638f30b1b0b77c930', + '0x5797aad88f16d359f6d33ab2d7f333fd7c6e361d5307bec9d09147e8f1523ed2', + '0x1c40c73618f726e5ba91732209819038aab6c25befb7e54f49f570944de8ef7d', + '0xa7aada7398b92b5259f13cbfd10e61baac13958e998f1e355af817eb7edf85fc', + '0xe3a50f5bb8741caa1253fd59be88c252bf80fce7115ca5fca87bcedc75010b62', + '0xb99385870b0334fab7b9ff08b6ccabc044900a2a431715ddb2c537a5c4e4d335', + '0x18bc4025754916d402bfcfd813ff77d440cb7ba3fb8400d11b3bf965e3755771', + '0x331859bea72554da55a5bf27370fb5d8ae2dd9b512bd47d6e2549a8a1e3f1d78', + '0x853bbdac6d5ed928dbf396ae2081ecf44f2b3056f0525f62eb9452182a3e7318', + '0x85c08d9bc962886c1c74240839f363f651d547f0546ff1a08e0a2125d1f5763f', + '0x13ac980d9789e45510751e6efe80e3c20f3135eddd3ba02ca12ce3b4a5cecbf1', + '0x6a404b40f37dd9fa0570cd3a82800ae5de48063bfc6d8532f567a3c9cb0366fd', + '0xe23a27f1edcad66dbef2f2eb6e8af9587899d8fa23099f9af8a5cff9bd5de24a', + '0x9dac244ef28797fddcc2e6b295af1d0e3921adadfeebc75c69e59a400ae0308b', + '0x2517dd29acb875e4f59e4b2ac3a37c63e29a580704ce270f9c727961fe161462', + '0x72272014309e0e3ab91bd3c41b4b75e904933a7193582746cd760a36606fd7ca', + '0xa9a87aff71782de400fde8c9de01558b8e02652b2e9d180709881d1729e6e95e', + '0x6adef730f475053eb8d43fd0ecb6565e66efbd37e4e3cd7a845cade93b05adc8', + '0x2042fd8e45f17fdd3c2636358c1ab247924f1c6945175a1f05a31fa1875ba522', + '0x52030e7190dee69cdfa87d2b57fe0d9e524455fd3a3bf04c6129a779ab28a9be', + '0x6bf1fb0df70bc1ccc67ed1166e1c62396ad4df5a2c0734176b343f156dbe5541', + '0x7c113edea9f06387f3bd2c902c26901f11f3e710372e52c0d5ab0ef224d981b0', + '0x38a3caf12eb5e7e97978787ea2e83139f13399fc84a686b83f553b9c9c4cae43', + '0x917520db093df70a06c2187f5ba77980d883b0472a2483d2f2986c11a3f2745f', + '0x39f70fca4d19206619693f1cbf8c140461bcd4cfd89a10bc7cbc55a9c12dd891', + '0x0f962d2a841e4ae490eb5d508618ec4efad7883992204e7e5cc59ca8e4b139c2', + '0x2d7fac279fe9edf0a3ed13ea309a6eae3cb137c9b035783decb69e53c44d8964', + '0x90c5f46d074ccae4c7865879fd4be382b4ac72b00286a248062d371e49a232fe', + '0x8fe6747d267438b03e3bdd62e98a0a9a0bc9b3e860a77be49c6b7410e7eb8cf4', + '0xe6da0c5e86660c0a6baef412b1f265035e8010dcd4f5fa6c700669fc729f6800', + '0xd6dcd3465d04ef1053e3f984eec7cb2815b49f7dd4dc4a07d221388770326856', + '0x79e4042cbc1bcddfbee3b0953679cfef3539d75e05458254f3414476fa8f55b4', + '0xb3f8096a92cccf01c0cc68b11c7b119fea7c2308662922d827d432975186bbeb', + '0xf85e15a50aafd4d7ff17821c301dc7b07249f6a4909917b18659c804ac944858', + '0xc1192656ba98c8be058d9976e95eb397d83326d8c36a762e12035eb5d865a5d7', + '0x3c5618f212ede686ecb55b6c634668478f4634ae37007387f64e39045c49eaf2', + '0xa282dfc3b8d9feb1ebacd29db524ae015bdc198263f81e9c466fa858c4ec1364', + '0xf18886da457750a2febcd2d45ed091ece920f7854dff4115ea88c841e0c25ac1', + '0x62c413646c7e7d85103063085a94e3285c7f1d34495eb1464c27aad095dd3c85', + '0xb2c9cd6dd0065a4427fea3b9b07bad1f2f6c767608adf62ea09edeb58695dc7d', + '0x601f17bf11fedc05f492999071ff3df8011263ca63da89ddb70b511f610e6dc7', + '0x6fc1fb78455e460c57808caa7f81aa462b056f5b45e6b89738efcddf7e55b42b', + '0x0a0dccb95d1b824a4bc4e9647bcfd3624c0e3390d7fde7b9985ef62f227d0719', + '0x04a28692d8688a08cc96c184f1ceef45de98ec83a74815f082835258067f9b0c', + '0x986b988e1ad8b9c69a79def36b62792506e828676b4374e178af74765aada323', + '0x2bd621c35b1dd9436e38ebdee320d766b6d660e90ae039cc09b24828239b23c0', + '0x450eda98c32e3f1eb2506a4bda2365945d2a2910b13967069ff83a1f150b7ee6', + '0x5fd11f8e5884e7e07bf3c84a9ecfe7e4872c4d81466b524b8d6c9cc4908ec8f1', + '0x33642611f89ce08c1190633e2d1d00a17612c4ac04ec21048b633a0c6b5d4ebd', + '0x3246f523d12f5b827eaae3c1046d1c39dd0bf96774b91e448426d57e0c3fd2b6', + '0x5748defe03171b500ca1c18fafaea82d0dc8a04a99e9107238624d038981ca23', + '0x7cef95703a90b54b8121ce514393f4fb087fc89e3bbae0ecbae9f4b6c32e155d', + '0xf438427d7f9e2fb898d0cef4ff5678f66a3566c9aecbbeadfb2c0283b6a8d33e', + '0x482334e2a16192dd58f5b62bab284b071022ee0d99ea5b119f00e4a1f4ceed0d', + '0x87956b1d1c44983869fe5006e34d1463ae98698c1c8b434e9ef90becd83cba13', + '0x53857c9faf5e4b1c7b879e98e90d3e71d8d84996da595723f2bfb15d4d3fd326', + '0x42f1d5c4c53dd02bacf00dda88a3f1e24afcb69c3072a2045010cf35eb68e621', + '0x9d7908cecb052e9a21852fe7e9157ccad594d79a051df9ea21d6f211b3b80c76', + '0x329206088ce91e857c700deb54ded677d74cafb55827148c3f3151a2ee066c10', + '0x36f7e5008d2ca8869c94d702bf72dcdf1d44b39551cf639fe2971c6e120899b3', + '0x23045aeffe2becce54a8e22a561353179a6a4a471e413d2d1b7304b5417bc825', + '0xdff2cf4e82b66c7707617b2d8affeb9ee8f6d6e18a1c35caf95b151eeb1a574c', + '0xe41ffcf451445333e5d92c742df40d3546e09891cb2899a81cadc2bdadeeaf9c', + '0x9f2341d3d22e01bed1bd677988c99f9436520be79c9d310f105c7e233be9d247', + '0xbcbcec8fb97015275f49ce9449ace992e55b6bcf21ed16250b10c73c37c13f0f', + '0xc21b334b44f6fa096601d72ec3b43097e9190fdd3e612b8829a539db261563f7', + '0x02bb11f61cf8a627b1ad416f29d4321db3427c6c2a864eb4d900cd52d4e9683c', + '0x986f49c10d61d9ab94213fb94e9aa22f41c3e4ceaf29d75ee33f6239d6b3a110', + '0x3ccc087186a668e8df2bdc666252350bc8422f70cd86b94ca4503b73d241c0ce', + '0x6f6a71e9a7c9499e483de6496310800d02d377370f29a717cd4b860e9bcc39e6', + '0xb848b6102fc24e04c186c7c00fa0c9bd4b5ca4d3b35607757fd9e994911a6c44', + '0x20ddaa225234e8e3215f5fb0586e0218f6586878741949c188bbe0ea3eeeabe6', + '0x69e517d5af6091e7499447d0d761712a3b2a6dc54a14331808decc96409d7093', + '0x7b8450572bf47ab26c84da93a09337a0e37fb36f082520358165d7e4cf2ada20', + '0x73a613b22efd940383171608b9ea99a9eb18019f97a91a4fff31574b938a40d2', + '0x2fe791820140ccfa102db44375531724ea10a8be45c18145d69a608b98a8ea8f', + '0x7ace10c6e817842388c9f6ac2fbf48d9fd29c70acec560bfe4a1df8ebf648d08', + '0x88b727e22e523a9fe07207b27439b9ce34c360a5c39ecb6102e4ae9c9ac3d152', + '0x1566cf193efd738df3cd06cdcd89b9fa16aa9ad72227d2916214a3e59bc2c145', + '0x6441bec229912f62abc1fdeb22c897aba2c435b6272f47ddd819a58cd36bdc0b', + '0x2386b9ff6dba153bba1fbf3b70fdfa70ba36ab0a62a8679679ec56dd8188f7e1', + '0x167917d2f2bca88f38504e5c8f3911ac62a3c2b2c6bf08810cae00859c113cca', + '0x10c51df5262296630f167b1b1c1a3815b09e00622a36340fae2cfe1909e6a618', + '0x7817ba36b0326cae32dfe1bfea673356aefa54ba729ed17ed0767662784008d3', + '0xeb1aa1ec67a3809cf9153a14effb03868984f82407916550b855bfe4438e24a9', + '0xfdf155771bf0a5f81340535d64b75938e031abdb23a4796ecbb07829d5e2a9b9', + '0xbe7e8ec078707cc91eed548b23b64657071c7ba0dce0eb69adbb465b115c2e72', + '0x03415d9e7db35ecb191884d8cdffb8ebef2ed9c05179c08210eee9d93f77f387', + '0x1b728ec9e4b399ba9141dc17ff999ec31a3942a20223570719c4819aed333248', + '0x251cc9f7b9c50c9e8fe3c7edc5910c5bf59c58db3fb766914f81f5ef405ff3e9', + '0x432a35fc9ce08a123b245eb0e3257caa9e9d1e4be0b593f00959c90b44efa9e8', + '0xe19d0b08d5ff0677b6f84096c10fc8b0e51a058961de65b7c0583da33f661204', + '0xb1c2fed86f8852b8fdc818767e95a55f717b8ce8a9825e45fa3afeba9b581d8b', + '0x00b4dcab8424fa02957f978f32d7af4131993b3950963b3648bee6dff27f6800', + '0x9fea6304431500e4b5fcd0dd0d87e772d9212a69f49597c84809e344fec2d765', + '0x89449b7170baa165bac7b1e75af24b86e35f6bc7c7665ead14ddf81a9f8a66c0', + '0x021017335ba6896cd5897962d227791ce359f1cb6b2d569da663b382b26b5650', + '0x23bdfec8c32ba1d06c194270d1f2191a2491fc71779cb7b5e68040e2b0b7f1ca', + '0x90bc9d019fee4cfcca225ea967ddd5ccfceb0743bd5361bb29d966caa2bca458', + '0xcfba13d576aa11faa82ec4d60ae9f5f59b1c512d910a9813c0bc8493a8ea75e5', + '0x1c56f5529138438edddcf4affac5469a4b87c242c2c36446b313b781e871d7ac', + '0x22bd4d68d340e059f70677c158e80f03e81860b26ba6b633ac25c1968112c32f', + '0x6d56426496814bf26e4a1f0ea51150f8ee1428033bf391632c7bd11abbfe77b3', + '0x44c595711d4e20f4448e120c8de48e20ecf07b808d04af7f98b9e089af3ceca6', + '0x6d0281cca8ecf1a9795f20db48e946b3e247257a41623235f1755f70f8714aa0', + '0x50157a4c71620a10063ca117e87903d205af0644f1870d22da47fd97e24476ab', + '0x93dce6cc88e79e424952f7f389c6b19ee0161c21abe2aaec3af4871c742118c1', + '0x1225c7d0d531140a050cf132fe72211e7e70bd93755760c5c0ebbf47bc8bac40', + '0xd1412fbd031c7e16f801e158fa7cae634c392a7ebdcf7cc7abc96fd372ac8906', + '0x7f7cce141b3d9067e9c3aeecaac0bb12da981c9b8011d584327ffd3c287e3152', + '0xa14ce909ae68614a293d5ed838fc015b6145180744a6b1b497b9a192f2f4e852', + '0x48e549eb1cd44747ce9566be2f697f3aba1b2356a6fbf7c42a787db86ead78da', + '0x001281502652fd04e520af9655ca7f3e2428557455c9adc0de65a16d1a5f60af', + '0xfa49ada4d99dc82782fff02d620db56ac8565e767f09220e8065710e0dfe5067', + '0x564852cca4c77607b179418c84f26d867773f6e11b5a76d1a14259e666236038', + '0x3b1afa4d71e3488d98263b0c3cef5cd0a563bf83b66d857dc6656a84e7740c42', + '0xde56fbb1b06ef7b9c0f4f92dca3df1cd4e2df509f317ceebee4e612568457247', + '0xe32ee7156484eec816317b51fd3b3f95094eee53416dec8c51fc84a4a537a26d', + '0xf5372d7996557762d83d9851f1676d12fcb4936dbb864fdc50c803b925c21933', + '0x6b17efbcfc7f307d1d3154dd8690a5d0c84bc2e435dddd321755ae51f352fef8', + '0x689e099f4e7a95c74d40f53e7ba3b4a5005251b81ebe306b8ecd498c9a16a068', + '0x56da4b7290fdbee62bf01b1f213ec37bb3a346771c55c6c2b9c9050fa168f88e', + '0xeb291218e2b6825a9d2b0389a769a9811c4712d07a3987ad007d7386c8353fb3', + '0xd0873af33e81a107a15a28484377fb972ea98f4c45e12ab57d5253d0acd0907c', + '0xd920cc5c4d514785f22399dbc03c1191e67133b2a960f2d60fbd0bdeba50e4d8', + '0x187b54e41f65300f6a6acb73527ab90cec410beba31bec62458832a3c5665c3a', + '0x23cc25e9c2de9d7b8805d17e27c874728f010bb4d4c58f8e6027cb0830ae9488', + '0x84b88e3186f32fae491c85d43ae14828fb8c3b2500af7c11fce5b2ecf3fc0a89', + '0xba71f251426c6301b4a8c940b3350abb71c5da0988d421461b3f9f414d8d1021', + '0x238424b33c91dce5400d0a5ed77f31c5ddd6575da680547bc4b571c7a38486f5', + '0x33e53d6b868c638401e0a0474423fb0ec53485592ed7a1b0ce9036e061053f5f', + '0x1ced92a780a18870e3fb3c11f18060647b1fb62283cee6f6ec2a0d715aa85046', + '0x329a9d3c223badac24243f8b32490d771cd888c6efe5c3f8dd34fff70ccda779', + '0x74d245812c705dd25b7dfb9c6726d9f680d3dc3c2ab86892b999078b1fe42cbe', + '0x67645ba66486de9fbc6d913ade0772332c0a8e6e3e82d8cccf3e6e14fc275dc7', + '0x957bca177575c8a9bca2766f30fd47764e59fa3c141d31f68f43530d0a558bbf', + '0x063056bd2748eb83f78ee7060abd21f112058fe13dea5f569d62cc0cab2510c8', + '0x1b17e80ba898ee84fdc99ddac942150e0fb43e9379d6ff1183864f73a8c9479d', + '0x7a22e38d2f06de2b23d18e1c8665f48c3c88d66df5198a800fc6a9407d0f2591', + '0xf23b50a0a7708326db1c31c8a577cf75f178a0e211db79cda07a00ae125d3920', + '0x53ece540693e08f24cd5d4aad1f071e25af158ebdcaf0d03946671b56105ca45', + '0x3bc672e957b8d5ece38f5198840919afb7ec7d67d4bab4684aaad862ef1f20b7', + '0x7cebefa8c96807531c67e3f25b6630d14f3758ccd990707d628a5d656d06f8df', + '0x0435f09c6578f07a311aa9c9877e603370e2ae2d23a5f0a31251a4d4c715cf7d', + '0xc4983a7925242a483db8e1c4a6b5baa6d6bfa8be53f07826fd26712b30a3a296', + '0x2f6e48ee4e307ea32ebf3d7ef151b50341b8f1578bf5b60e0c386b28d7f0ac5d', + '0x33082ac46884c24031d5802f82a0099e8b056fe2a7c54e0c52b089ce36bb8b9c', + '0xc43ca322036e9b40b55886a0fddae4cac639a46674851d678958bb6554db2011', + '0xbd694bbbf5fefa8203d0907313326aeab829d69b7a9c9cd7e881ab3a24ce406e', + '0x0933f8e6866dd3371c6a4d70b0205c3f30ad28a4c96f00510e0c48baf31c5a15', + '0x1c57f03b104b82e481c0e0d823117b930cf4ee338289d4426815c900d1861b28', + '0xf3ebee6f4a9481bf94e333e1eae7e56b073e13aa15028cb0004d661d4ced2875', + '0x43865ec25ad9986f1431da349be25e6d0852a56c4573a13f7b62602dc2428b5b', + '0xeaecf3719d9000fed03f8b82a69a7cf876ed9909f8d4a862b03398c9bced04fd', + '0x323ba57eb34834954f8265a0064d70cf61975428926e75dfd82315de10c5ac40', + '0x87f3ced240576df4bd495adadc600ae59cbce60e8a93bb972628cb66a308ce24', + '0x85262601962d57f29e068deddac0920d9b5a4f7dbe87f5c522d64c695300457a', + '0xba331943067c903d5add7ad72280f9a5b56ac4be656cc07c190d30f44a3543b4', + '0x0839ea04a6eec46a6f00f199f6985b80bd8f7eb17724aec3c1e423d1b0c4e441', + '0x45616d1f139e1ce704a264fa81f92fded8cafbf5feed808c99106e5c622f3743', + '0x0e42f380cb9f639383813fb3a992506b4050c857b9cc55f4b23cb39c83d5fbb1', + '0x995d3aa2167b104e45b8e27093f2fdfa909f6bd648fd67c3f585ee8030abcaec', + '0xb0972a362eadf19d23acc327262663e31dac0954d5dc5299742fc9908e19291e', + '0x5d648d205d43092e9b332020cde403001806548a95d6d89607a9009657c56cf9', + '0xa0c1e398eb5512ca64eff128dc9ad272350103f7dde31077fcc1e307053ad49a', + '0x9eb2d3bbfdea568805d65298f095abfc5d9e6e032e6924e78b00981a76368851', + '0x6a295653b7080d6da7782cb26fbcbca329b32218dd2a803bb69ca4c9894f6cc2', + '0x8be72a010583833277f8f14999d8cd72a645537c1417dee6fdf8385b61f3f45b', + '0x7950e1d8c047bfde4526581bae61a77882473fc98493cb6b5abe7de067013e84', + '0x515569b56641cac2810e9aae0b6d16cda4c7673b010f3caed77b799b3b0438b5', + '0x5c6b3db949b2806461cab20ca1d87137589d98511cba85fde8706652e84fb994', + '0x813465f31b90109542edf0d06cf4bf92460a3480479f2026f372118fe6950f43', + '0xcfefea30cda086ed77a85f51e07683ab3b262cc06eb9a191d640e5348c5df53a', + '0x5199fe02a183754e49dbf0e0908574d46ad07600a07aa2390f0f99c73a160654', + '0xd2c7475ae1a3aee23ea73b3f8107f84160b7306598af025cdde6fd2fcc557db8', + '0xbf895f7fe69f19007aa2304c75e1470efd7e3ceebe49828afc15b4257b88f425', + '0x33c93e7c7bf1fcbd8d921f9c16e6dced448e26909cf18e113327b473d595d7b8', + '0xdfa84b48ee49870a406794815f20d21204dd2caeeef711725d50799e68b132e3', + '0xd73d0fc34a37d798a7c77d182c8a498db8476d9eac3e6deeac95a4f36741968d', + '0x3d5046e0f0a1174ff1148e43cdaf8590860b340b319ca5ce06802eec8f2e6886', + '0x4ad4bf54cb585a53d6e60b5bd488105b835940fbe8a43ece880600bf893716d4', + '0xc71a899711b0209f0d505ff4dc3a09420f0545d7a824378bbed0d16b23e30ead', + '0x0d7ecde289d067baac71302fcf5ac5f4453ea5e99e5d65cf0d97d08a25ba2ec1', + '0x4251575a46dffde5a1360ddec2dcfd5c95d16dd607190ffce66df89be00c7291', + '0xaeb038e06220eb289c8757620d7774947eb6ad25a1992a6ae16ff5f8d91788ec', + '0x75a323aea4f405d1ac042f243cd52e7b22f65d6d90928de0348f1fe916358587', + '0x58cbdfa0b244e2faf0585e3e5d225de33510f9993c210bf42957505e8954116b', + '0xd25c524b53c4c95e811e73d9d2a9c4cda25b794647f4050130eca1800037d029', + '0x9a58dc64a5016ca083e17248b43fb574d7dbd20f1297eea0215b813369420fa1', + '0x74f9c299cba6845b73538058b442f90abbb9d8b89d25ee3c6e0062b7f2e7b5c2', + '0x7437fbfff9d8bb1ff83f89a1e3524c91218fa40a5de18f006b9ace7d9e565d62', + '0xb6006a14c0165415fedc88ba039168a8313c582caee7661212970209ba732faa', + '0xde75bc6f1ef4f15caa61d0f20be909d46a9a8f26d8a7df951030e3c981a6f03c', + '0x5ac38b99bf0f2ec09d80c40c03a26a61f12221c84bad39512a1785237ce0c461', + '0xa43c50803e6cb4426d0140d33ced0d29b67a7f37382ce642a5a082c39ec3ff93', + '0x411bc322c6c4aee920d1a63af845361f9e07098f38b6644ea7e159db6c5e6ceb', + '0xf9f34fcb57fe873083e2ef353dd98c5f04860db5b33a37d7666a2d757f35a1d0', + '0x456eed24e6c0b828e535953c7355eecb7e38566c3543045ef722fc613e6bab4b', + '0xa30545e45ee61794004110088cf7cc7c0ee252368c7b3f25f188f096b0808d77', + '0x4c2737003937f8e133bc3b03ab170129cfa73e531ed8a21c8aa6651a92cb3204', + '0x64f56f19ca2b90e6f4b098f803b7f29999cbcb111b4146b1afc3f89d21f7cde8', + '0xcb3aff7c8296ea2a06315be8e52d09d3a387ba7aee74e60b242795bb09bafe7b', + '0xe64416c5882f35180a4ce22d749029c9a3425887b6f5c50c3d25fd1532f413a3', + '0x917a849eb5db1b40ed8fab13dd437d25b317373c211edad9801f6ec73635acb5', + '0xebae06c87362c1968738fbc5ff21df9f5e5d434c53125a7b31f28120fa7be8ed', + '0x3300444acc27d479d8b23dbe39526bbfe3bf478377bf988b7d0d5bb95aa3c438', + '0x971a5fb1cebe5550ad65e7a54e7e2035ee4aff36f8870a3d411a555997dc96fc', + '0xbfb8d27108bddaf4afcd6d9fdad299bbb1daa2a06705832b7f8426290f12c83c', + '0x507865e60ab9aeb3c77da442eb0ca624dc7d00f3f640dfa6a9336125d0245dbb', + '0xc4776f8cc9d822922bceb1beaecb41e5b87fc49b395d1c35b3ad7174d0a04f28', + '0x10e53e31f92832eb537b29d9b999517b9ba84a20c1e1d7a99fe2684903f78f80', + '0x51396807cb41a2b659fe61ec5940ec5d37e33386e6fad3323b5da044dc7dbe57', + '0xd6cfc2c153f9873deb7a0927d720d6df7c0696e7d9974526841ce0ed1e34cf62', + '0x9b929ca3fe779e0994a4668a8bf6737d3faa808b5b67c050cc6c25f15c945932', + '0x21a8daad93d56c4877d976ba154179a7965e3b8fad12d97a5267de1558872bfe', + '0x7f69ad4abd54224ddb93eabef7d941d8d4b83176a4d523d67ad1901d33885036', + '0x9a3cb03d2579c5550741f7bda6ad83c48b60a912467758dcb61ea917282f9ed4', + '0x93f49248701de56083e70005a3e907c23c73d612ce00aa77e014fec40a95c201', + '0x70060481f4ba403ea54ce7722d00f37904e6b410e19adddd7dec83514fcfe771', + '0xf7aa235d2b78a5e72170c31042bc969f2b4f6be697714b5f9c6b6293838e7455', + '0xcfb5408341acc00e8732c31554e0dbf5e818c5d16e1bbc686f20248c7ba0faa3', + '0xb65cc497753387c0507796135b621e5fe3fda430769c749c0f8702e4fc08a093', + '0x5fe1265daef316a522bbbd3c11919a499eaba35fd43e679f65eff0850dd51389', + '0x2f62fc96d7f281f6c2485b3b04e48eb18edbe4ab65ad27d640133681b9624f04', + '0x455619dfb17f0a5141e2ce78caf45d96280b3c16c58d0206bb5835c8b7b2ad1c', + '0x298d9c05ba6c2fa323ed7e2c138ad6e7617f4a5361d85f048ece702eaa372995', + '0xd0addbaaed5c3ff61bc0b8b5ccda8b6a94b347423a4f84893e2529d51929bb76', + '0x2492b617ea9de5082e3522b7d0bff411618768089c3ea656627ad8986d7f4e01', + '0x54839878bf6f29c855e15c1762e2d8b298afd6bd032ea631db0083a79b99f6b4', + '0xdc5036cfe485b38beb5c3be2dacee3e96ce6fafd295b05111402ce24eee26e59', + '0x51b0d7db21369b00c7846f5f8baf2dc514028c06d44a190f46a26bf58f3e60fe', + '0x6ed56c96b3a3a0263b9d4e6f0f544535d7876245a2c718e83d9a2d965faf32cc', + '0x701182142765490d2050dcc502d69feb7a657fa44c6aaabe555881158aa6b486', + '0x28a4d3f1cc783e7cf62d3ef71d5636c5e1554747cb0611af1031dd136b7a269b', + '0x647d0f47feb079561fe21bb68bcff9a3f236bdbecac774a50f95d6af22b859c1', + '0x30b3ac6b2c8650eb9778938bc61d1b5e45a485a6d021f3114bf7963c0fec83b8', + '0x83c9e26fbc21cf0416489cabce48d0eaf8405eb59aeaea428fe79c5496549458', + '0xcee8df28d845c17e1dba7a4ac51578b648d9bcc6b37ee2821def5d62ad3c7541', + '0xaf47067a4baac7ff568dd8f6eb6abe7da640b0eecfc250e98e418da64a77eae8', + '0x213aab4fa9468dae77f68d701b1a32c5f6fc6d5c7c38bc930704965e8c430c7f', + '0x27b50f2d9afd82ee2a0d53e1430563f040f70ea84f2a7c2318b1d2a14a7dd690', + '0xde058ee2250cd7c6e82f0025d8e6aa6a24399f45bfdac5629704218a7c6b83ba', + '0xd6a09624c254efeacdbcfcdc5588a1bce9ab0778eb892369444f47cbf2c34717', + '0x9b515020b8c869ded9d6125402696ba62d243bee374cc3155c8dc619b1709e9e', + '0xc218e83841241b324f94f2e484c4d175a36611a0d70e0e17a1b01fd0644e62bf', + '0x979d145c9a20f9a29b7bd1aaf7765af4e25edaa36ab2282b05d4dd5079dce7d2', + '0xb5efe4927c8d96a7e0702364afc265293491842c0f61a7035af359fa7733c51e', + '0x9a6915160b0861917c894af196d04804fc186fcac250f411d3de9f50f4935ad0', + '0x15d1fc7fdb4a899eaf9f630433af38deb72a0c3da34b5a2cf7d1609963b6f12f', + '0x5f4c82ee68e98bfe2429e5e941e9b519541adfdb4ad2ccbf2ad4a4a28ec0df81', + '0x8c821a42ef5c0ca0e4f4fbb25fe1d125605b1948e58faeb366aa428147fa5564', + '0xda3115f56c99c7d765ed69cc189a8d5605768b07b935b7a0d86c4e10dc582829', + '0xf620f0c0e1a3984b0366ea6b65c89a9f263d707c8a27146577661229ed0523c8', + '0xb8aba00d6d5437834073e9eb9f5a114fdfc47e341fe0dc4ff0ebe96bb20f5d04', + '0x7276a939ede7733ec1297bb1ecadb4ac7569c8ecb9173cb9471687485a1c5451', + '0xff8129b10a3d7e4db5f9db4f69e82650ac4a6b1db845d1fe5fb620ee7ca113c4', + '0x7d23b7d69cdbb3af1a63737196583642f2cbafe35c8bf5a5b5f2f3fa7a4d83fc', + '0xe82b328245b6a554dceb7c928fc537ebc77a562b6196674736dc061b3a13b80e', + '0x93389f1da009d453f443aab01aa18c48cec628cd4ffebbb5a5f7a652f0b2a0e8', + '0x79c640ed577f4f00046cfe21e899133eeb87da71321ee688738867336e57a497', + '0xeb60bb68eafc01090d977cfeb90389df221de557748548e8d6754f10b8e65a81', + '0xd24214038947c3ae0f71709274dede7f896c2de8601b177ea681880ff224c42f', + '0x5342444e7e4039d135c669144caf63abfb4e67cc1d6a127bf66405a733afcff8', + '0x0834eaaacdf3e0e1298f37d8df2d940757402498c246cd5a50cc8666eebe607d', + '0x9e9199a285eba216db915bfabb8bda9f925f3026419e385261ca33028539a5e2', + '0x340ede2b359f116bd7dd820f717a6dc90a950368f438e374a49661fcdfaa3c57', + '0x1addb9d8cb77315e234110f73383c0aa13d95ec14aae1a5a30cd403b212ab9b8', + '0xf45b9b193301fe484d378d7a7639a8233f04db453b35298842a67db5da6937ad', + '0x27f75db4d6fcb21f4b052737d2af3164b01ac4f6bb58ea29f545aedbc5d2f2c1', + '0xef56737c32f2ffde7d20bab3fc8c76d36adb7b604cd4eef34af2070346216039', + '0x7fa6d52aaa05e6f42943e003c7d84614b695dd67353ec99b9817222453117bcf', + '0x718dbb1c8e144df01254b3b2e2f51ef04d77ac0ab0a29e2d6dbc83f1bfce7df3', + '0x794e230acb4f8513969a68c7cd8b0cfe77937936fa95c54fa6ef027bd659b022', + '0xc20b3a1c12fcab565fe4a9469a30a796c59c0d1fd45929a2b85c719707e8edfc', + '0xf96d0802bfbc4fc73195799067d43efae6868087f41367c38a4f798db465f091', + '0x148413f66a0d98d9f067c945c88ad8eae4727833e6270dbd94f48982f4acebe8', + '0x6df67aca084b26650991e69ee00e7e34e9bde608c5b3fb97664ff6eabbd638c8', + '0x03fa6633bcab2bbbcd2e8ffe59a76652c4e685e70f974d1024f396f819f3a8de', + '0x54a1543ad2e1d98b28610e9bd3ee62a9ea7f279305eeedb53c02a570a899c2b5', + '0x5ea5397ca8463115ff7baa0cd721b4b1bbfefd87d68228b5e7a7d13ec91386e1', + '0x4ae1c680c49e468c5018d207d0c61ca26b3d79b28673929c58739a465c49be3c', + '0xacf145138ca97e0b4105a926a3ef4851f1db30259510fdd3130a7c03e392b117', + '0xf617421d312e14ae42b90a13df69f09674e738fc3d297a5b560361a052f02e50', + '0x0db7c5b8c761cff909e75f96ee1e77037a0a7139ccb363bd92b9911571c13047', + '0x61b1f8a054369aad59ae194fb4c5586209d6d9c106c504091f1b7164ffa838b1', + '0x370d187e50d97ba31d0785d8f09dbfa2d3c95d15124456e478db86947fd68b59', + '0x3785e673a79b3d8d8f8c899a1aeda96506f4ddc7d10dedbeb126d5b1ee644b63', + '0xbcede27efcc8b2681abb8848fd3daad03f05d6c2198f94a800d79c03b4653ba3', + '0xc811b6d7553610d3d307e18fb52d3eb148e1177188be4c1c84fb35a454f41590', + '0x21dea2ceee271045cf6f5ee63757ab68280bfffe867e70ffec83f68138aca80a', + '0x87576e126ad47a5e4a04bc7f7a1de20f17d65214e2de4d98b1b54cd4565fc52e', + '0x3bf016a7d83f99d0d7d2fe06c8f50277ed2dde7fa964e0df99cbcb77c9e7d4a1', + '0xd9e4a95e341960dd1ff81c54d105c5ea948031f1fb1f361fd823107bf51de678', + '0x99531d33670a1b6d226a57b4679ecb53962bafd55894a208b3c1e5172e0dfd80', + '0x10a8a0e374e938a2542f2a5e993ff581a1f2537407dd79294b4897f72da8b086', + '0x9dcc02c38007b0f59ec74ad79901efec5081d4f75be082fc9de0203c758812a4', + '0x55768b6a767a6b86832e308590f52ef97e9ddbe11accff530b5669af0f744847', + '0x2a7323979d16e345e107775f174ee9e1ffa5ddf0ce15843bba858430629554ad', + '0x6f06a392e7b0e55d98468bf70e51f75e02a0305d7fcc49e5f7e370a3f406f569', + '0x6fd4c65461ab1682c8580b608d9c4cdeb1dd73961f4e8a28b0e14869985bc437', + '0xe821b5337d4fdc9557dcee5de278f8d83b276a9a044e2df694fbf9eb42bd4701', + '0x1bb1625a8d2262dde6bcd67bcfc604056275d1f77510344c27d3c58708ee8a49', + '0x982b8087c12fe879731ab8aef20fc6f57f71cccd34ce285856c39c898f7857bd', + '0xbf61b54b104c5ac678c2c503fac534f4ec9d60730a3fb8db9f04abaeb47fc009', + '0x49f1483a03e66eae9acb372a7670632970fafe6e812c1fca47c1985ec0047ff2', + '0xf4e2fca070f8e9b99314932db936d2444f3b5b2ef802f87c8627a9eedc52cb85', + '0x7b737c3142d82efe7f92cd3138f25fe7dbe69d84d8d90a6e2e268a1db7469195', + '0x9d306ffe94a6707057a4ed8746d9ce781d7a99a64378f281ed5c70b6f13a409a', + '0xae758e46e28732e046240c87eb6c38b1df5f970e476daa73df4b55e95af06b6f', + '0xa0e502e5d953773050810fda523c2f631acca8b9090778eb4ce6fde07213ddd1', + '0xede25ad1e027f9c19bb0d5432b2b320992bfc7cedc866e368f66a7b10202c050', + '0xeb751b6d66abee3b68aa09926625845e3da2ddc31d7fc36f8344929d9c0b677f', + '0xf5bd963625e431a178d7d4cc6a6fb625b0e9d0d92c26366ecd2d59623aeb51d7', + '0x0edcd0210ec5b256a40704c166a0f5ceef7b90af0198c2d6748e639cf63a1bfc', + '0xdaa30799780b9a581e3bae49b7da5d6f392bd77d2dbf80a18493ba0a690186e1', + '0x8fac00a6e858e845a5ab9c6396f24502f138468b579ef18946e72fa182bd931d', + '0x6a4f96677f758029d022b7d02663efbb1c17fe57fc60a96fc2c7e38ef1f574c3', + '0x223d16715efc95ac69b164dc4019c59a96581b33395d35177863f57d52f8130f', + '0x0c1f9077b3f5c33a1d0b85dd3e94a10ad0edb6502d77d6d71160f0d64a0ad9b9', + '0x1fb6d0f2a58ec0402f2e91763bb2d88dba52b748923dfc1de293dbc675e0da3a', + '0x396d727d78c719087431b343c0a0c33d4367807a4f8ad1f2e94947c35200e3f1', + '0x6188ece27cb7336ffd62490465df2d22e3124d2ba3340dad897353653e253377', + '0x9fb62b820ca6c09ff15c514c5a42fd89243c1cdeb959eaa8744aa04e737b2d8f', + '0xa45389202028f8a102df134091538a75df8b5a2306e8903519a6486ac6a8e5f8', + '0x89ad0db9cd37fa6c6b37da489486f50880df16be6aa702a75060f51090f8af28', + '0x784b8857c0c00a292b8d7fc82b84bbb06f941314797a49153394f7c2aebb2ee4', + '0x4ff707a3ed214b124b8e0e9067bfa7f8325ec041065c0fd8e67f19fb86f2dc6a', + '0xf1b9d58e05b9309874d3d288c118fd0926ae8a209b91bd7e06c1e5539d2b5c80', + '0x9c104856396f2608e8b4c65dcaadc28ac4673fe865d6b557b19ba72417f76c5b', + '0xe667641c0e7f8883dfddf71f8a36dfcacd8e8a201c7d417c63d22ebbe5f92c4f', + '0xec64f16579e9bc0be680ef019e6bbb9e20779e39dea018499ab06567d9a09117', + '0x34c0b7a4e6dc72fa27f6f1ff2752220fe8f45213cdb23acabfefb2c1dfcc8a95', + '0x62eeb213441d09380ef6153e09d5f3d01f7726a7b428fbc26c06c35de054cba2', + '0x322103b1063fe6a50ee9e454ad707f1735b04f2d57e868638390a30c360eae43', + '0x2272bb5acd29a8a41bf997e045d8ecaf2c877c39a2672efc0d7cbfccc2e523c4', + '0x82f29dc93e8192972425c891d8dbeb11332d99fde81e98ba55210c0d28f296f4', + '0x0ad30883b6e4256e8a2acb0e4330bd24a634be587dec4e607fc39700a29dd5a0', + '0xb9f02067057dab54febe78bfaed9e839a2a80a0897c676f678a74e1839a74502', + '0x989e5fe08599a3c43df3c6b4b65a06e46b9f5f89109b4accc4ed3bb0947416cd', + '0xff143fd7f3d278364d7f82cc3180b38d14c612ffcab82a120a341c76bbc781ec', + '0xc8c7e784af251f8a3d3c8838728c3631ec59e82aa45ecc818a823ec72a08b463', + '0x8a62815364cb918265d864820cbc59dda953212dd1b582bb9d51fe3ba821497b', + '0xafcc281833fa08279c2d0c354a6598cb42cbd860a27073507b5787b8b5792314', + '0x03c6917e2e5aa5aaf2b3caa46fd99964c4e5a9a2ef641d4dc9d7f1eb58bb525e', + '0xb33ed288d07b1e78320dd9c0d5e1a8c009e8990c99106df4f0232412fa41eaba', + '0x2ee253dc324194aea5ccb096967f709a06cfaaea68ebdcce5b4214bd540da830', + '0xd2fe9eb95731090f13ab828f7d8a98016ec67f760433bb10a5a592b1eabdbf1f', + '0x8396987d0ba5656b308eeadbbcf91e92d721075ba187bddb332126c2651a976b', + '0x5810b8e6bfa25221393c287cdc106b774f1029dbac256b2823bfcc558bdc0c0b', + '0xf34189ea4e617db50a08247a210795690b3ef45541c78dd19832cc8e2e76936e', + '0xfd81e724a707280bd6860e5951e7649cb170442f0f0f4b4a6abb913164cce4c5', + '0x4f81b4c5d9141e8f5926764edcbaf77fb889464b59cab961226f5a0565a3cf2a', + '0xa913b02bc404dc65ddbf550e1e971def05c509589ff3c5bfe8b116256b17f74b', + '0xd0bd339bb6e6d13568d7e347b3096a97ee2052e64fbe926da0c4aaff0ac75ece', + '0x7d5c263a9f55dde97b4d28f2e5b2c4935f602535aaab1ddc179f4e2c62be0474', + '0x79841f2b95a44c5e9a19e120fa97b62b1591c4cc1a997fe1a63863cb7200d565', + '0x6402a2586c2ee7237be93136db29a22cca83465e397335c9a5815ea041356c35', + '0xb38567c314c1a9331bb91745b0068576ebdaa399c1fa3ae746eb778ff88ef687', + '0x445d684a863a49519f536d990347d5947e6fa31c2b2191be1c49b1b2bd3a1d1b', + '0xd7b90d6b9de7cfe29e1d34c304f85a1abd795f0487f61f6b3b5b9ed5c212aa41', + '0xa1fc37acd45c40f1bc006c8d91669c2dc9fe2aff5b01eafd2910e8108f3d33a8', + '0xbc3dd887a69c9206789c12cd9e27236c2cecac8b3660a609a1207763cfc0089a', + '0xb7a27c4016714a1327cff46dbb54ef6e903f3931b54b9c68a6b0e773073cb7dd', + '0x9fbf72d3ce7d5cd8f407d5dbd65390b43f170adf4b6aec3f9329740f98a88bdb', + '0xcd63a65b84db283ccfcbeb114652f8fd069d446f2070202b8083ae5b5241dd45', + '0x1816185a5b64195b52f0fa724f73dd362eb72bb99bf9b401310e512e4f55fbe6', + '0xa5f54a4f0006fc26d8339deaa0ea36c347c0b5bbd8983ad147e48ed1e983885c', + '0x880acc62c41101c5131178620d6009eb6a65d29c1b40268b44e6634c18c24145', + '0xbc62e763426078ca4860822f9d0423d083cb77fff384acd623e77c4dbd32aa93', + '0x223b166edcc12d39357e9ee41564efe36cae401d647ac88c7d94a798a5179fec', + '0x1ea750272df98dac06e59e5fb033147b09a704846c4a48706a075a70860fa8a1', + '0x2eb04e64bea8c22f9ffe4d5512de246f2bde70a9542ec1e2db0ebcc162d06c6b', + '0x92005eda35c886e369b8bbd2452b9af41fc3f40858b840fa30382d2063069d19', + '0x8a7bbe702facd0dcd46acb5145b82785f0d0da59f1cb0420747e6a55cbac43f5', + '0xaa71f949e03a9dcc022b1b9dffafbbda42d15790c8af4f34291e84998d44d59f', + '0x07a94388f0535b2355df0aaf78bf150b86c362fc1927793557dd16df39856640', + '0xf8b916c7d90de8df713e8c3d26847bf4ec1a46feb7be65668b6bf3aa1c182ef3', + '0xd874dbd786b82ce452a2b9628f4c9f78e7f1804a1dc30b5a58d7eaa9345a9706', + '0x9e5b7188c281cc4c80275fe6d2b7390a8bc3de6f71cc65cbf6ab5a5216956e75', + '0x84f4084726ad896c737d9bb7ddb5ab97d4e25937604c391415b9fa8bf56bf4b3', + '0x5c0159ba7392fdad1a236a6a438c1f786870ee8702a2f1aa03ec1e7b4b287b11', + '0x4369001538aea0af15a0445cc61baa2c5f91db709cc6ae68e572b0ae5f61543e', + '0x55c991508bf5a76dc7fb9cf5e19a7b7a0dff4654a1495a1c4af456c88921fbd0', + '0xf1523208e6a6f6b95d89e2c7e8d15f222ef9e620ca04001a013b7a120e09ac91', + '0x50ab76e385c4ad491a49e936b12fc6bfcae60e58b30b5aeafe07cf9f477d4b1f', + '0xd1de3655da56bcbbd576f80e549a6c5580dcf9f3639b8040c294b6343435d86e', + '0x7081a20264908d5944f1fffb88b27ad9b27dacd09e788c6c18d64c02ac113131', + '0xfd786d1c20a090bb983c3fa2851aa7d08c72d6ab2b93fc8417e069130b5d4f3e', + '0x0657fd1c9e522f9db7ee4c39345dc8bcb7e5668e3e5dec61ce36691fae2be9e6', + '0x5118b7737de0e5a8a2ce6c986034725fe515a5c0b940a12ccca3a677bb777711', + '0xaae06bed83833933c78972b5fe0f04f65679a7c64d5480f5622817b5ae5a0fe1', + '0x6795eaeb9264267e89180196ff8b45f8b7772b8db3d78c670b3953dfb0261aa4', + '0xa750855000b79d6d8b026718f843e68cfc44b15be129cb38f518d786f9ce6870', + '0xa6672597d737e7123223180ee440fbfdd472c25633a14e9804f3b1e64f1cc1cd', + '0xcdcdd13a8bcc4598d41c3000c6fde4ea4e691ddee1322ce70137f805e88f6d43', + '0x9b50cd0563eff1a8245d42bc942481adec75745e49b2fc7e72c6e2c21d118199', + '0xb1ad2f18cabfdef0b9a176dcfbb24841cbe35c5411fd859055e9b72b671ab1bc', + '0xe5b1c747eb4f25f049bb2ab22840d86272c95d1cd691a2f2f765a1ce022bbf6b', + '0x5d0a063af7e0c0f7c4a05781602fd08ead4da56ace0b072f05bbeac79461932a', + '0x7af68574ac32a10096342fa1f42216a234b4296ac4e1516f445362e87d6e8d64', + '0xb5da98feb287465c6e1ab8fff301bd9cca43e93a660189f94127e38b4c43dad2', + '0xcf4d2ff970d771ebf62580a87bce5bcd88b436e35d993f8d031d91a20e9bf69c', + '0xd370696e2dac78a1697eb3fe6dc6c89a1b77ca8fe07bdde3dbda117bc60d9b58', + '0x06396a3b0be004ec093204e1e2de3a961eb006069b8933600e880366db32c7f4', + '0xddc325ee0450525864740c7a9e996d16d586d591b0c509d2c8c84357e72e2837', + '0x23070ecb04a71bdc3ef9272c611da71dfd688a213a7e15144e21f148790e6ea1', + '0xf584634834d6bd1a9f3d430b61b3c14c311699d105f762159c8400b09a62ecbd', + '0xa34727c8c242893511f60bbcf864779284a6ea513f6916c05de1be7956bc3bfa', + '0xecc2bcb630d0d261e0ad40db559a114d42f99bcb1eda05d9b3caf29ec55ec748', + '0x50bc14691ca9e2e25915e16d691cf686b4b58cee672e0efe6bd3398fa2321d62', + '0x2e5a7dbc0479e6f5222e4ad69e11636942fcb07218d3ce3c1d8e01f408709fbd', + '0xbcabe1a5f41a71cb422f5e121c9b7972adb942271ab166f77f15d6a71adfb89d', + '0x9c822497087d07d59c4de6479c0af84d62b3308f9c66ab6d00a8e598a0f9f601', + '0xae70369e455e741404df67500960eec4bec609580d88293e4a1060dedb0ae8cc', + '0x5bca2fdde7c8d8a09688681fc23c0c316e6d76b575f2e2c73a809bcdc225d4a2', + '0xca51aaff1f5cd5ad46591a49d542e196a6152245ceb9078a129c38eb2488ef4e', + '0x764c215ea6f8c73d7a9e6f4bf45ece5323cb0bf182f74e6c64f093d79291567f', + '0xc476aad91b49b35cbc0d5ef61d864c9127faac7ff9993909f546f9c94c504494', + '0xa8359d1e7d1bb019b9c7ff863f71943a35ad407c5f2932256f5aa2ccea3fc1e5', + '0x987577a001072e51258ad06fa78c7fb4d456daa3326290ad383b6166b3fef263', + '0x5932a8135faf71bc8194eab1d1e826d8b7bd90d6108b029361407f35abe0129f', + '0x93b40fe8441021f6fc5db8a1463eccdc503a3ce9b2923e53a514a4263b55e345', + '0xe742d7d066cb3d8bdc6957dea1894fa4e4c928e103aa18461646c9a93742411f', + '0x649b4bd5f4d35fc84b3d85a6e33340720b23d79218b8987d9d3a273161eb1ed7', + '0xc95af09762920b32d07adc05ea2dcfc08e7e42822f74b91f5ae9bd73f67f2db5', + '0x8514aed4a46491464d86adeb53d4da5e0874b3b0c56bdf1e8df9e6caacb8bfc3', + '0xa7cf5bc6e5fe5b6bcb34ff61cf65be22496c14bfb364885d7bba6fe6dcfc70e6', + '0xf32d8ae12aefa2f25d230b72c01a5b015cd4202e31d00c65917a88a286540706', + '0x774c3346ca6193c162045d27fa93e1480af18f6971c360fe358f3293bab8d26d', + '0xbffbb583273780af20b99f61177920062fa25389a7682fdce12c7fcc7be31b7f', + '0x08bc3cdb0cf555699cee480604ce490a5ddcec7b132c9dd1d60c90acb9477482', + '0xbdc96b252079ad9eec0fb5ebf72fd9679e751eaa0ee8a3b854f42ed2cfd09618', + '0xdb25fe48c93d8b85e4acd0870ea0aac0ed2c7888e04886760d62a6867612f8d8', + '0x035cff04ab39e9872e61511d5cc9ebbd4da87cfc28910316c855441b5bccc73b', + '0x9a38981f902f69ca224c17aa3f967d954b0fc7c1e576ff0555c04216e806caa7', + '0xe3f282c0f34dbdcdf5257b3b66f14b4d5c0d455fc47b530bae81f68635b3e3d8', + '0xde826de4ed3cbf98f6dda38adc09c9ab52a96119f592ac1d49f7c6df394de258', + '0xa74f8dbf5b996bec1e15d083082d9bfb51315481fd72bf71358903349d57340f', + '0xae6cd9c7a270cb409a5db27109252a37c53a47637e95f23826854e616e52a870', + '0x0322c82755bd4c6803270f541ef5f0ab92f19ab421056629d25702560679acf2', + '0x246c11c206ac1349356e5e2fd0ba517de3d17b21c3071b67773177fda4a2bfe6', + '0x3598c384e8e2f6b930051c3e4ce9a8158cb2a5d5dda05997c44bb8e3732caa1e', + '0xfd7407aab4bcc5be6473007ed5e348245693f10e38d85c6445149c8264b3ea61', + '0x2666c83b368b23f8874bcbaec8b3a5552cb21b753a59078774feb5bb607def7b', + '0x65165a4341b175a7e85052b33cbd44292c4ec481237565821ae10c92d8dc24d7', + '0x90b93b52d0d6f7efb959f57fd72dac8af235f9276530df95e2a0095be27fb8e1', + '0xdd757f99812cb76cd98eb7898be52ad5074001aa9458713b334f36909144f52c', + '0x70910d079da353935d7c22d1f38c023d53dadc37fa77d542afb588d68e5060fa', + '0xc0665e0cb520e96e10e73b2b3977eecd29a6e6acf8aa8172a84ba7daf57bfced', + '0x4f767d8dcd8c115d984ee878b9fd252f94293bbad82337c254eb1c03458c192d', + '0x338d0e5e92db4d9cf7cdfafdd0ed5c2790dce4bf1e6d2087ef97392abdd1aa5f', + '0xb3be5d559a1b9f85241c97587972b7f5202a939ab3e9921e0ccabd3e80a1e44a', + '0x5d384c2112ffbb4e48b6117bfd42659449e9f2cf025e1938513485aefbec33fb', + '0x28a4ccfe42262ab7f144063c638744d82ae3502cf2ece26ad2c349fd84492f9b', + '0x2ec8776ea96b12f64cadbff9a745eb01fb6cf2da2272871a167fb5f44c55dff8', + '0x52dc90f7bf6196ce3c7d7c1b18d58a8e3ca306b8d4a2416b148956c0285cc9c7', + '0xf375b028b9c00139523b4da818a826a36aec9c9b4c82c23f0feca6344509dfb0', + '0xe47f5c0fd50ea2ca81d95b4a7ef06e408df23ed0571576a863b1ace175ee841f', + '0x2e6a6919df3e97995f9f1f5bdf48147f8df1ecb02190a3e12c6bbdc2b9edbd2d', + '0x7705f24351df00e0fb058341fa86c3aac754a2725679863cd0b2cd512e9e0a79', + '0x7fe899fb43066b002b2950f59db15ff8d3051f2006699c3c6c5343294039c9d0', + '0x2f026ba0433a89fb5c19c519218f4fc28a054f0d7639285ae2194ee2fcdc53b7', + '0x555d51bd461cf9490f466cad3055a9d3221209b18479e594f7a9e456a2bedbd2', + '0xad32a923298511d8878051c307a3b054fa3895b1ae5566b251384b6a87d6b5c4', + '0x06bbfe2135d26115d8545854aefcab9c69d0eaee9e2e8ab9e492090badf06665', + '0xcefab5bd4bc710356a96fcf050805c0e8324532a08c1da3ef28d3b9822e1fe97', + '0xaafe9d1f090afb8537e7e25a60846dfe814e58a2b457b30ec5c2c030e87b1b78', + '0x28a5578009170f6ebeb68c66e027da76800a87ba332c0d0db06c229b09cf90b8', + '0x8a5b720af445936e4a1b42653fdbb659213c1a3580a5ee92a09831105fc9d00f', + '0xb110e55649ad87a16675769432c779080d535400ff0867b2c0e2b2cbb1b90449', + '0xd269d87c50fbf802d7848e94f608c7b543b0ed6049e9a4d915f38f1c7b091d0a', + '0xcf212a7c6b3fccac37bdee34f03894842e03bf0e3b4e4b378686241f2681b614', + '0x0adbb0afdb2b6c67f35f01f80a12da46968b5ade0da3695b7691ae6840078012', + '0xe2abfd10350daec9601d79ddef3ac7193e68baefc1b9911fba071713ffe14ea1', + '0x203f6f55c35c47b78f554a3abf5afbaf425688a1160661fe8b92a3535ebdf9b9', + '0xd64c9f20866f995f5746f4e703992f5c884ff4985ceafc370ea89801e380141f', + '0x20b696000c927144eb65689e07a3952ff968622ce653825fe7b7394e1a4e4e44', + '0x9dc2888b213712669a8b2151a4470d6f626f9ffef99653d2a225262ab4005a7f', + '0x8a2d9f3a52c60faea1dffc6493e50aa556f6741e5e699d0913a6723fce70e17a', + '0xa6a271eba297dd99505534cd6ab3475a896011c459defd18740b247bb570cfe0', + '0x67c67d22ea819afdbe41d2793ede49d5db33273540f0eda56ebce280c352698e', + '0xe984c837b893ae68de0d854b554197dc448a1572d2aaa9472de5c01590ab143f', + '0xfc8c2d77a3f1375d304886ebd4cef1d35a4de9d29f26bc131d2da36a334909ce', + '0xf2fa75a1d3e13ed4a5b1da58bb52f8078150feb0e3d0753ec43af6e792956f3f', + '0x1c3af2028778ec1686e7a24d2aa9c676d6d1a581ddb469c52eb1fdb0e99add67', + '0xaf340609676013fdb6a10c683440b451eccb4dc499e72e3135daf566a85b56c3', + '0xfd12f609c7f4a06296348e5d81906fb5ed32f8d07a02d7b672bda49dafffcac3', + '0xa9643f99820035ceb464b26c1a3093bfcf6c7fc022a63820b3e3602596922d26', + '0x5cd61b3083b7f0e4cef6e6104cdbdd2bd5dfe4fd2bdd21a372c6f8eeb9437aaf', + '0xc456158ad965afa71d7b62b857911a8e8a4e8a43e6beea09e384ddf4dd927b53', + '0x00c88df08fac1c02d3a0aeba0c1e70b8f3e8cbb46ec35f0ab18f4d095855186c', + '0x8809edfe1bb0d106b0a173f8c666b70bc90efb747d16bd92a7477fffad763651', + '0x9411f10193fd4b816de214bc314a2b875a9ec7a3bdc63fe3a1c4bcf56954efbd', + '0xaa4ae30d72b454588bc3269582c2d824748c9b55393601b82c2b07d5f89a1e4c', + '0x906a654056823b64a0c10793c5e679fc8efd9edf3b814f43001a993b095d3df9', + '0xf5ac12bff3f7fa2cf08bb0b32480774f5e341790171a16d4bd1a44d82fbf4c53', + '0xe8b3c343ea8a1504698147f40a451f9e150c5e28d7d43e8ad36c53c8430718f4', + '0xb788cf33c1dfe73c2f18ac6cacf5cd50f20e96aef61d798b71d9e8ff456ef69e', + '0xe3eb7bdfbb7efa2fbc056e1af3cbd6beb7523fc51339907a337272c83747103c', + '0x3c3b879ccd07e261fb7fedac668994f217722f4a01a0707267e7771b7a1c6a1e', + '0x086cf931389154fbd733fd6b782b873ab63484aaa36a0a21edb117426b4f45ef', + '0x280008a367aab7f2959b475ca06c3bb3b9245c85ba5bf9ee83193f17e6951eaa', + '0x0428cb0ad810a5b1380597cc4f84e035974891419eb2b558a7aaf67dd8844e59', + '0xc77aeffcf3083f010fd27e08cbe3af1f43aeb81dd13a8da9f81f52cff4c53924', + '0xe0aa2b3b3efdc3a99e1ca7372beed03b824f88d7f003c3b03fffdb1de178a05d', + '0x8a110d0260c6e46a78f6fc4d8622ba4b8c8f5e5f2cac9fecbe29be3345188682', + '0x193796a7054cbf06e0a4262e3e28e4fccf6a21a8b80075e4e692a3c8b36279e0', + '0x47bad9bd998731ef3c559dc63f95bd8f1c12d53d870d874982091ef1e80753cb', + '0xf7ee397971146aaaadc2575a10b5e978231e4457b70484ed0a12ba17763d142d', + '0x7d29ab857ea0d3ce32331073a1becf5e426dc03e9db480b9c9aa4ce3ba5529db', + '0x2747080aaa140c3d18d0351e922ed20ab63ebdbadf44026b4f69469c83c35ab8', + '0x10bca0dc250f16a175790e1b8307c991411d351532ed035539e34ad13f449445', + '0x7398f4dd903a5be0e1117c2433abd8477cc6e14a15d36c332722ee605d70d587', + '0x69f6cba9436e4f2f48129ec1a57325f46a8cb9c13ca6e81f7a29b667e1f700eb', + '0x7b15e945ed2226b486fa3da81090aa4f6e7296dbd799586643ff6437058bb7c1', + '0xd08e715819a94aa24e993e25151969b7c8f1f4e38fd35c30f9e31a30316941c4', + '0x09aab042b10398f4a3bb1bfd7b6b16567dd7bcd0e671a9a371cd59dd0ba704d9', + '0xb5db8739a0a6e97cb453d109e27f943353ccc865a916195e38cb25d60ecd8c61', + '0xfc7972d7d82c3dca25737b094b286e151548c5704122e7cb52ac45351ffc3cec', + '0xf7527d3be1a9354334e1264e530a2783fbc8daa92571b680636f9e50afde54d6', + '0x856a53df758186e954e657713793c8806561fb9e1033ead49639a19e16263342', + '0x052b227e1db5adce825e75c44b65a037d0a68d22f14273aa3178ee2b9bd9838a', + '0xcc7ed47dd46322ba4dd61f5c34b29a6932486d53a4a3f375ebb71ca54c2979dd', + '0xe962a94241620a73bd6557227002e22b2a20ed7ffbfd5ba0eb725c78e64b58a4', + '0xd4a3ab722b0c05cad0f4c4604cff9f35ea59cf8ed335777454af95f13a74f60e', + '0xe88592652967bc455de4294249ed76d427de85d01d64e001bd6990ad3620663b', + '0x48bc00f261aea636e95e8aea1d286d3b65cd5751b551ef8cf46a8bd3f8501667', + '0xf7e7e46654be9066c538cf7606c53f5fb0f5d7db9a8cc574af44709d53c10423', + '0x6b2e6b8222d126330c72dcc72a7b1866f9eede2809b79cf584527b4c81a301c5', + '0xf1cc2395a67a1ccf59ab0714eb66f4d707706a6613301acc43b505389d82f7ee', + '0x9f0eb633305494f773a558bd65f4f4b64e4564c8d81edd8bc6d738a29fd30c2e', + '0xdb08cc24e61b99da4ab7a510ea73b541cb48be6b48faeb0d4e0b7695dc9e0721', + '0xa68bba1ca522b9b05b191dea0e10340f1d48295a80c805c27200615962b4f1e7', + '0x19eccf6dcaf0eece2d597a4cb80bb4a21e5dff7c3ba47170ed225003a3816e89', + '0xe52217d4c4c1e164e262524fb35ef5b55664493e1dd57f6126234fe3f0cc1745', + '0x0d0e96c57eafbd95991101135eacc42cd1730e15e2184d24cd98edb78e382825', + '0x32035926d5ce8f7bd1f4a99d52d3c2587ded1194316caf9d1bf5d1c5b61e0bb8', + '0x9accdc963000adb5d008ee83c78c57a589003c7bef8cfc8df117e4ad83311740', + '0x7f4b91435297e8696cdd0f47cc9964edd7361d1b046658a9e439f7c42aacb248', + '0x725762297de1229711061522e58feb79ad2640f8ed04c1b46d08de75915df6de', + '0x9cea6085ee816562d64b44603162be193307b42708a4ff73f478106dc628bfd8', + '0xe272476bde4fdbb5ecc4b63844232e2fdd1fc47cd823780befb8aacf9223b56b', + '0x5f24f11ac42d09e4c36226052e96926250fb39af0f1a56285e4128b21203f7cc', + '0x3a9cefc0b74164bf5b86cc02ba3140116a86fcf5a801b11d732448c32903bb60', + '0x0bcd8f04122d9da6a2af177cef285048f67427e3d270b3f6b9727c1ccbd199c1', + '0xd8388989574a90579a16191f20409f823497808d3ff4aef1c8883fa95657f33b', + '0x814c6241e2ce1fca2aaa19b219421c9b0a42506204b06e790c65a730d7990758', + '0x05158805c28d50b451183ce02d0e648bf201cc23d249c7d018539c120c1f4d73', + '0x0574723db18d038f4e7a72af88bf78c015f349282e40d30b8d4706839415b246', + '0xb0f1d693f1b69649f0a09076d25a3ea75ec74d3cc8e2c1a1e9d7a2f4cabfac02', + '0x429e98b4b32321a40ffcf08ca0cfb18a5ce1b3e7e4f087f31aca3428c8e38e2a', + '0x06971a89dd3959947803403bee6a99950aecad0b1af4267c677db1b6aa9a034c', + '0xc4e75b9827971660d218e37f826ff1c9af300fd3fc04f05da701cbd319e0579f', + '0x3c2937c6efcba2e17ebe53197f34faca9561e89fbcf80fe710eaf9f00b3af2d5', + '0x09c10e10a865bbf7f7f77036f659d567eb03aa359cb3bd4c847e9386c1baaef5', + '0xba3524c519f6ac5ec90810d995e6115c47bff05cc31144ca04885eba8323f29f', + '0xc1e6facd3624368e16f8a4c124c5642f32aa39f6659397f352e72f3a873dea5f', + '0xef16b6e9f58ab9ce8448a31db347aef47abdd913aff9be134e858d33697f3ac5', + '0xcfd4bb8b7ed1e1c61553f49f5d818a0589e37b720aad8cb227fa615bc1c1b462', + '0xac241ca93c28e2cde02c3e0202bb970fc9b33e79ca49d1b723e1b69dfc001c69', + '0x09bcf479b6cfb96de663c8f0675912868bd81b685944432a9e173c606021ec64', + '0xae67c6d1b4fb378126b565a30a309b6d4e3889e65066cfbc36b278c9e77f062b', + '0xb9026abcc1cbd57615e05c4e4480a74fd6a0715d9a6ca781ad6e40e8a2b120ce', + '0x2f321a6e6560e052c3d6853f5e43446ed38e374b764d49f8715426762bf30a74', + '0x477966b45a4de0160b4d40c71f21c1d255afaab4a9dc182d8bf02e439e0e4292', + '0x6e69e6abea103589873cb985f3f0a46b3c3722e707bd44503edbfa92c023359e', + '0x57fa0fb9ed02f6639e39f8cd22d9bb40f9979b503bf2a934abe138f522e731a1', + '0xd12c73a5f8d32e85a46547ce7f6e7b9a4edd49f8623445710a54c38e47de77bd', + '0xa072dd42158364d80191fa0dcbcaf4a8b60644002f6d36eb90c0f47d726781a2', + '0x4d48f6750b38682d032055adbbe8bc776b075fef3759f74cadfefc43c8fa1fce', + '0xd340958601188c379b674e10b1fdc5eb4f80091d980bf9949ace31da7c09b97f', + '0xef91fc8b2eb06b2cc48f0b1b5bbe852aa403b86a6ce328a0649cbb50bf725987', + '0x3d9ee5cf464f2cfcb0bbf577f68e6687095834f8e30e3d2bd2d5d65493058d6a', + '0xc839fa7e818d90429a5466011a1f9143a31386d202dea3c04f33dd74d6af5a86', + '0x1068e479ca764893cc04758f5046f24285b2f197ba66e8abd148dc768e8bf7cb', + '0x4ecdb6bacc8de656c7324fbc4c0bcfceb68cc2af051c0039153d3c2859d3e536', + '0xb45f881916b9ab619d95f5445fbe0d967289d62816e43511cf64e3efea40e100', + '0xa83bfc3c79660b75ab5da1a73491077e889d6a2e409e845a43b1d73adc4a54fd', + '0xdac4afa56053527a664d7dc3ac911dcc257599cb14a8365a4b101c98e9989054', + '0x44e6d8a3046f2d5ab0eef4b7e9042973393ad731f9af7d2b6643f7c2e73a4551', + '0xedaddf0d415f2cf2fdf0d8ea7427fdc8f424df7aba5e400591ce09f9fb7e0e99', + '0xeaac1fba4f6fec3a2bb34a05f4222856f1f45566a0065075edba6c80b129494e', + '0x36c087c869e67167931d701c531aa36438e216c85519e69aad91313f77420608', + '0x5470bc2ebfacf5e247c05c73ed3a7e92f8668f946d6b20b47fe48e5643eb2e5d', + '0xd7f04c73fffe2f2b85f2a2f0939c638558dee6dad365798c481aed71ba4cfa5d', + '0x119de40139cb5ecc7f4735a5b4eb1f8855d1d08db54bd1583a16719eca2fddf4', + '0x5931a37f0a3e2e8e8faff34d5810ecfffb5b038f9fef5a942706723046fe5060', + '0x19b625cff7ae6fbd120bdf8702747aef144ecffc1e4f8788ecfa47c527bd9ea2', + '0x9accb92b1cadc8592f4c8dd7061b68ae27fa742a0679a49c42d2e68af9daa89f', + '0xd018632c946f26729f624162690c553dc38f03c772a8863d1c4b7d123eaa6950', + '0x6f223f98880538b2af75e4754a871e251f6a1ffe41fcd61346e58d3405239812', + '0x6e7c5c56bbf1cafc5d8e2242249c16a25fa725fc9e6f740135d3bb798f833b4d', + '0x9f3897eff0f8e01ca18187493bfeab1793b6f78a9c08af16e4ca98b9f0894710', + '0x32556cb742c4ca617d8eca345bb70ff0bfee095200893c1cd6d9478b2d4dd0ad', + '0xa9f03e79a7cd6e1b5c898fe2ecfb6cb5f5c1d05bd3a5ec0c6bcd83da66fe3c65', + '0x7a11513b5ee188b5c1adb59bec07153a6e1d3b323cfe823c8f6b39fdc85ddf24', + '0x9706adc411f056b1b4693b139bdb149cebe0bafd3e4896f8c5c56a620f6b5d3a', + '0x044bba94a72055cf10576c7c695c9c7d3305a28d545f03a9ba29d819730b1493', + '0x0551fd903c7b64e061041f3054f371c5df2157773bb1b6ddb4c247a8d6afcfe8', + '0xa00c4205dcb2e5c4dfb6b20fc81d6b0f4ec2e1d79598100d0d5f9dfd6a4dbaf3', + '0x82a944140e659ac2a06b5861a9257672ba2067f23bfe0ec4c5bebc0cd81f1f6b', + '0x2cf0c38c0a45f1ba5a6d677744747701d0d07e49dcf24e56dbfde92bfef68441', + '0xb28146d02fed330c74aaf8f9d4d182d9260de7971f33ecbf4a7c1a094d800886', + '0xa7aaa83cd091152a6b626493b74b873411453e8ef277445ba8d46862fac667f1', + '0xa99ac56d54ddf28c30ae3ef129369246cb2bae89fc54d943611e560ba63af475', + '0x1a362ca5ae721ffd33ad5d1c2494d95137d46f38e66d5de5b16786f5a2c2a664', + '0xbcce30923a817b2fd17200ba8a9a32d88a1ac55b64524dd2738e2799b60b7649', + '0x74f3691caddb42105cc7ffdfd79d506b2c73a7997647d0f377209f7f57a89ed6', + '0x0792576ff6a75321ab3448b9b6fbb85fcc2b53a0f797e5a5ea0a5ab79cba4806', + '0x55a8e154093a8140c9f01bc33b4e0ad8c497fdadcbdda464c261a4862fe3218c', + '0x44ab518257f27d6c9fbfd5dd8f127d335a9830dfcb84592f2c8efec709b9b2ef', + '0xd6216d02ac29224917cdc34e51eade6d00060c9ca073199ff1c42e29f99285b7', + '0x8fb9a01c83eba3f174910c330aadff7ff7b2e26a8c1bbbebe04489acf4913240', + '0xfc19372d1349fee34a5db2063f4618ef4ec3de1242eeba2d79153297d0449366', + '0x58f853d88f8f3fb250c9b8083002ee75b527c528cb9fa4fd11d07108c9720d7c', + '0xa85722e5bb4bf4e42dc47013b0c0837f6705830507207849a3624afd7f9f1832', + '0xac28a06292aad999f9c9de082e6b3c5c94bb9453fadfd03bbed57fb7ddd36aba', + '0xef385aa4da8085065941802f853c30de86bda7c0751c544e50163b9af744b562', + '0x5ab408f4c17762892004285fdb2095e2d97b50661fa770d0ba92bee534c92924', + '0x73bf6c09fa9f71001139184853eee6695e0b8660b4385cf44546669ac40ddb2b', + '0xbf08e2850e0800f24241167af75556b3de37552c6e812f6f88065eb248286386', + '0xdebfbf5d933deb047f9bb23f7f3f6984fa71c0310a0f6e93f25b0216c59bb126', + '0xbdb45a48f1badccb10496d9b1fd89cf2a5f127ba7fb51572d6d893202b0d22f9', + '0x41a0384444b998a2b067c0dec4d8b163649d2a73b28f3c87640aed1c027716e5', + '0x2bd541194105a0652287f4e5ab9896a3dbc95aeb9eae3a2604f8142385b6a7dc', + '0x58c0a2ee23f506f3cfe911009a316775037d68984da8256947d3611ed7429050', + '0x799f185a61133549f988bb545467d84f28afe144f8f867eec10dc09072dbaf03', + '0xaecc5d9770d2403e38b5dfcd646c434962fc8f35bf7d3a5cd721c688c92890aa', + '0xa34b345b4e8e424c14dccda06e2c303d2db72a4bdea434f794626767a4cdb1d3', + '0x3fb8ba04fc57038208eeae7b302a586319a29020f4daa6f27c61c2ff34e70b45', + '0x3a8bb258775e0370e7ecc30515106790b9dc47412bc2ce6c7961e79821832ce0', + '0x4149b4e2525af0ffd0c7d30e2676838353591abf011721158d78824a8d212964', + '0x0a5ec8da07f59df12f678a0259d165aa7c577e176eff9e4e3e7af104ea38b361', + '0xe01335e25f32678d411109e8c5d166bb9ac05f333987eda9a22b078f6cd79494', + '0xda1b062ab2f0746636af3e9ccffe8d87784835b99d1b7603d26e9f512470f50c', + '0xbf9b14b37570148cff331b946c402534dac5c2870b42b4e78b29787c659f1e01', + '0xb608e097e82ad20f0a0d2666b267ea467a7b2216b86bb50eea145ab4e7536426', + '0x578e3074e4dde36f81bf4f6ea524a5cf00dd20c2091463479d41ceea2096dec0', + '0xbad1c6ce85e7695cf7d6cd1c8b2e65a83cfe0adf37e9052559b4905c537a245d', + '0xe1a32fddb412ddea95850ad652cf644e137eede1d08a96ef10c1a26db8b6e22b', + '0x3ab89590df7e198ea14f96e71ad82eb50676c5d920a7c400f8dd957a554506a8', + '0x6dd0b06d8b9d6f89a78da4e10588b66eb0c28e8daf2b915c1d5ba15650b94a2e', + '0x8eb0ec04f2a0f8045cd987ab4aeb87c8ce3c2ec40042e385e36b92eefd7abf06', + '0x3e8a4061daf3d2496f5b2ceb7d05e2a12b327071f3ad28129e0dcdbec69a426f', + '0x177b7d7f9a2a0f9c448b135a205e7b688a430c88b333ba3300630473bdc1fcb9', + '0xfd8260a6cddb08ef3def644fd9d7eccd3805d0b91372745f28eff6b7cbb70483', + '0x030bb3bad5c6f66066a1ade5c5af0da7e74e2397757e407a3672160c6b511a12', + '0xda8f4909203991c657b95d1f5d2488a80844c2ef9d62127248bcf5a4ecc06545', + '0xe4261c194ba8eb412642f8f64c33d49de8c24bc54146a3c8f0585309a4d0c778', + '0xd7a1e40b425353d103b460c04c0b07367de146194ae04aa53b572a652824bbb5', + '0x7524d77df781970f29f481df2a2ee575205c7c7a6b336b9aaf8d2d04afefd4f4', + '0x89aadb2bc85a8ae14c309c8ccd8a00180d3d6244b03884fc7b983b164a109cab', + '0x4c56148f8c21bedeeb20f04a6af31945ff68290cc8d55ea86960f3de9faf09ea', + '0xf7fe0b7f4d8932de565e1742bbc5ad1496f32a6ec075837e3faa07bccdcd2479', + '0x2c479667bebdc88b8f349d3a3db7700a899170abb9155f4d526bc3b6521b32f3', + '0xf77a6ab9e4882f4be2b171e98c80835cb188b67425d1fc3ce73ad84375ad97f4', + '0x24b3c86cecb45ad7fc9bca0dcddba7cf41f6a3da7d64bfb85401f412ed4629d6', + '0xee231d36c90f6dabcd1ca51472ae392fb93918e4ce7394f4888a02f6850c6166', + '0x6ba4c80ab2b70146b6a5997b4922be4e83c5e028b0eb2dd431646754fb764b3d', + '0xa6f71d865e8d47fb804da2c3f2a42a8e6f517adab6ade593e2298c145603fc81', + '0x7369e84f16bd0afe51dc906a93fc5ab58b969d622929492b63c54a51d6eef36f', + '0x96a35de5c5ed9c7e2874d67d8717e90eddd65bef90250f79d890539e515e55de', + '0x3a5bd62ff9a0a0ec40f09d0020420978560472ff9ce83f409ed12ff10fa19a21', + '0x6d5c2be9fa4b0ba05ab8ed6b1921b29cd0f1882766af99d71e25c08320c54305', + '0x29d90a8a5ceafc749e99641cb88f7c1f83b9c7f95d0a0b64b474a1823d6ace07', + '0x36a8fbb1fb6baeb38b5606d3b2749ebadbe471927001b3e7d8256d073dc9b9f4', + '0xe31cee3cd95a961a7c7c71934efbf4c194de0a6789e56314865e4d181bc8d0ab', + '0xbf9377752ad013e8e5edab9ef391302eb7bda09ec84986ca27d41cedbb87d5ab', + '0x79c4f4d27994953ff8a57859f0dfdfacb092fef2e8d12fac3c4797b28084298f', + '0x22502748767ae69add877804f8c0b3b6284c381708ae2297a02b615b2487a6ce', + '0xf1c33ded14735e52d535858699949aad648c14abde589b53249e22483cd09279', + '0xc33e968c21ef92e26eb5a6014eca85b99bf8a57e1d6ee0a6d1ad4ce43b7c062d', + '0x3553e67b438920909537930fb72a22e1bc89b98ec1e90458fa544ba468534c15', + '0x0242bf1904dc28b2e785a6da65971a02fe23d91aee379d56cafdce4b9a8fea28', + '0xda2717d3e4c6f4465d55972a9641abbefb8963aab871582429c5fb8510042210', + '0xdafde42a0d5d24c04a44bc61da1f1ba97d41fad7207d3e2a280fd1f66cc61731', + '0x437d9c644114b1d9d6a7909e4fe92198b25697da177ec9d66517a07a82899e07', + '0xdc46ee3a5fbcacda94f6d134125860d53c8f91b34e2056bcb11ec92571761c6f', + '0xa629243ce5a3e2ba64b7b528acae3eac6ea3a0ff3b128d0a22f8975ce66dd410', + '0xe5cf7cd5da89a69aa9bba29019181b252f63d24bd71e310217339a0aaba1cca4', + '0x764d42f689832afc94d3a09ffcf19f0ca61b5fcfbf5444cd22574bfbb34eceeb', + '0x4b7d6d36cdce3f71d5f6e1be747adc31051539435bc60a22c6745e6b92efb9bc', + '0xfc1949a19004999e3d2dff0c2d40273f66fcb12252f62541978d70cb17d337d8', + '0x48f18510848adb85520f4d9556b2929043827037c229b752ce0fef1c085694d9', + '0x8f462ad4ab82152cb0ee0931e30835414c240ae5294c9a8ba219abc3c096a634', + '0x51ce6119a4537d645720c22cbf90a24b7297f564da95587af5c7b1a35299e1e5', + '0xa9eabc7aa5533b33a838aad2aeabf33efaa0cfc75d112316c30245a3ddc5dba1', + '0x45fc89947a696ca7d2fd7ff71cea6a1df18c9dc2f03f8eda245ce8ab7a35658a', + '0x2eb9a7ca384c0344f598fa90cacf3265ad1bec108241b58249252912e0408986', + '0x80b864e948b5bbc7e71e3d662bc3038bc37e6dca21c3777c1c511bbd6165181e', + '0xc07e39f09dded2e0caa1e8eabab200f98a43fd02ebe1ef3593c27875c53092fa', + '0xc58ed564835acfba5cdb526dc4773bff6c9ead95f9b7aa1760c46767f597fcc7', + '0x6f2e64d8c7a00d82721689f0621b806ef96a76293c42e0afaccdec5092bd2dd4', + '0xdf2560107d05033cd20145db53c9bd9cd38450d8c680b8f41ff4b03aa7acd49a', + '0xe13cb45fdca4db232c5c49dbec0557998870c1726c433e340e7e5c8a32f7d374', + '0x031296bcb01da02399e37cc381f87b14f1c6678273b5e5557315efa1e04753a7', + '0xca3b3f46079b8debbbcea54d38fd8b481671b1699fe6142d37852592c1c6db64', + '0x1bc7545eabd83cf0f59b848a2adf269bd13eb70e9ede9a38272ea89b1446be99', + '0xd0aee813335ddd258d591ebfe252d39d87be23d079aa37e80edbc1ee00e4eb0e', + '0x3d101ef1220b9fe9b5692e2fa67bcd5b578291481a598096f1fa99dc277755f2', + '0x665ad0fe4e94ea2d254be7e965adecb5d5b2dc170b37c8f74df7d6749d13fde0', + '0x627235f924b2d61d6c4eb9b4b0c48abdabce092f0cff368e2a3873096a5cd6ae', + '0xe960b6d5610e23d0585c6ddc515b9f6b9cd95337d9279a5821b60499edfdf1d7', + '0x39dbbb4c54dc114bf7f6dd1534c76359c8c33c925b37980f9d3a8b232aecf0d0', + '0x93306011c95061bbeb11de9e72d7add21b78e41b143d8136d63088c5673e15bf', + '0x4bb5a127686a32bccb354ed4b029e525ddcdb00a5eb5561285e19c8f3102f753', + '0xa00d35e61c5fb33333b9c7404a09c71d01694a9309a8b862ade4ecf55ed87f49', + '0x3bd582c7e75816329613a71c930af381be11761ba36d0e4e8251c5a0c4fe32a2', + '0xe8e1dab5cbbaf4000f4b203fe99145f61909d5feea32ab9cba1e300bae7fa99f', + '0x78dd130d7b6e0ff5b5a9bb11ca57dfaea7c6d11c13a54537592ac9d0ee1e1013', + '0x88e68d58e3f5917bb9592c1a7b89c9852361f9ffa0854dd0119775e4be8a739f', + '0x9e2b6c91c28d1b3f826da6936e9b38a90e82e17d9a4d31734a98a8e98b4a0d95', + '0xf2231dc224d9eb57f72eb7b198ab8c5be14a052723d64b91bd86f412b7fddc1f', + '0x62c5a27756665f34f3f88ee0c9d63866ce471a13e3dcf3a759db0dce9fdf143f', + '0x5b5d51ba64f42e6d60195e9cbe51363659cd84baf9865bbd7f027ab65f575c5e', + '0xaeeb5b59aa84f3c66e875809d24b62aad131d1013ac50b3fa89606f2473addee', + '0xca512c0cfbd93ed5c31aa09af1f2cf5edf075a90a5e058726f558d683d6d4ab1', + '0x9a6b45605c4625c8e5aca57e556890682944954ed1e4baf9293ce61a69df14c0', + '0x3c8a0d1742b4b49fe9e91256db8664ccc7e92f8e6d1ddc7c1b5ff6103f8cd9ac', + '0xe5d635d341fdb6dbec17afb5f0cca7a77e13e8430dd59a04aa4105bccde17cec', + '0xa5da3b4e409f69a7156258291da7952dbb9ba73231ee5158f51dee3446381e1f', + '0x6765b2c49263bad9a72866379f6da1942739f39a5fb70bb0b85c76c64443bc81', + '0xcc70556cc125837c2b60cb6109f0223c081240a070de9f70b203a81a56149877', + '0xd8d1634f52badd619adf824b5e9ecad0f152b6fde514fd30cd4a12ebf2c77344', + '0x4303a5e776df08cf2caf1cf001e809fa406abb8bd4e30efe315bb2ea9d75a590', + '0x31b5d1700b3ae3972685dfc6e85f0ae2ca5cafe451116aaa052fb8b23483ecec', + '0xa25f01d667d135d49263718f469691cb4030e85a065990cb0aa407a045447779', + '0x5ef4dfedc64cca17daf38cb0c34e38b02557672309f7136c0b75f2d6de3240c6', + '0x8461f9771a2affcf8b70fe6738c129b953ebc450329d49becc2de0ca4b23f750', + '0x9d8fbf1a43e3d59eb4bb99598b47bfdb81ef25dfe6c5d80cc44092345f338c11', + '0x434acecdd544aaf22dcad1f21bc60386e433187c71fa3e0381c32abab77186bb', + '0x2a77c426c095a341419146189f4deb2d74d15381f35df93e1f8448639ef18f03', + '0x8746be7d7de5e656a381d16b8a1cb0c2f45bcd514a02ac80884e1af6e91d5246', + '0x5edc07b6d5b5a57ac0faec9cb283954189565dce02c26ad08211d9ffa56c84bb', + '0x792b3dcc3094f7405ec61da02f977c2b2b5194bdfa62907a277923c855c2caca', + '0x19620712734dc5664fff560537d6f6df0a7a43cc8532bb396b1a2c74edc55cde', + '0x4158e643cebe417933e93ffb349c059c98438bddb71179055ef1e49ef4897edb', + '0xb980d5406ed9105c602c89c73f3d2de224ea6041240428135adfe5e1465e398e', + '0xf96a354b6f3ca498b8f25bf3c76bcfbbae78c051ad6596882811e4053406316c', + '0x3b2c58a1b3bb93c17331f9172b9f2b494d0c5d35b594d30323807aef5efd9d33', + '0xa12a581db2f673a7dce27ea514a182898e0329235a44387843a1e98dfac83a6a', + '0xc81db8366061da9c83c6522e6f57e450bc3136f72bb3cde175956cac5d3c5988', + '0xf8b4e0c0ad46c9c8bbe34d198faee7c37f6c8b4635ed7db14cb9084db85c5f3e', + '0x49fde0b19ce6eb03e3d592f8a599aed33ab3ae5811834af37bfe99ab9476d1ff', + '0x18555b454eb9e11538fc14049fcdb69445c5bdf25d350b4cc73b8418c89488a2', + '0x5a95135d540460d78e60234e4ea4a3fbfe0dc6530ed20bb6551f391c58364e26', + '0x83f3f224e2c77b457ceae501bb237727141feb03ecb34789a3decffa667b8f8e', + '0x53942c6b953f3903b1bae1e55131e2b9f409e2e900a919f4baee384471c5fb60', + '0x6b75634d1f134e27f87cf38db4442c5c19a037e00c1c687e125aed9e7f7a2467', + '0x0054e0f24689fe8758a821a00a05653acac76a92d3542d3f6fa3e01a7fd3d399', + '0x6c08147c1a4a4be7b8e689721589287b05cf161c62607e9c6bd91bf3c8cb249a', + '0x16276cc50334e3d3ede3d83282450ea5706de229f3a910d78b28e1d32fcfeba9', + '0x3a6084a56efe718c411f1cad1d19e9d477eda8091321f64112465e5164790279', + '0x07f1c9d7ee59bb31df91ebe5f5cb89284e23117b6e5d3648095b695cb77d92aa', + '0x3a177ce6a9854aca88e7f8fb3f3241f2fed96a8e3dfc58e48058e62e6fbf4dd5', + '0x3732818df6f504e949ac4111daebdf66df47d2dca4fcad53c3aa5d3828249d37', + '0x995ad10e9ff9e2c06064b2918d0f279981e4e6e60fc2a44062e290fdf6792b33', + '0xeb31b019502f21d165d30e95a3197a7bbbebb34780fbd165523e273500c470e1', + '0x4e82240bba88f57d595f0015cbf7b47c46a3f3976535e5638870763e7e469948', + '0xc05545c94df8c7b04e6bef4823a7d433691158dcb96924410a28c7deb3dd0708', + '0xa915c9bb26a053325d3aac7b238ce91706eb25bfe3dcfa38317534a32759dc22', + '0xbd9546a4b8104aa31f0cb13ca5c5908853f8dbf234eeac1ff046c8961dee9e98', + '0x4a26911c649c3de55373c48c9a48a848ac9fba67ebdffd307e45f991ddf40e6e', + '0xc5906a5edbc4d7e86199c5525600e939406b6f594b1020656f039fe0d8fc7f9c', + '0xdec58969142c3b766808ee5e47de94ec2bcbff37da8a06d971e20795006bc252', + '0x3a3b39dc3b38739e5571fa44c5280698458facc98a28bc96188e7c35e91a1a38', + '0x3aa93ba61124dcf6bd19c16c61b7c62711ce5955f001bbc67fe75a93e092e35e', + '0x067007537b503c3414f9f8e328a1b7353110b14861e374c6d42e432e5b5472a6', + '0x0f8c5a40023026f8e7a58c9d59ccc40e1a745c8732edcf1d5e2e1abaff7aca4e', + '0xe2761e88f38d536a7473ecf27998874f5857e44f3c06becf7747fea09216d5e7', + '0x1654b70c6582eb553f81f6c8d3917c0cf2bc6a1e06dde3ae64d966a58e13bce6', + '0xb5089766b015ca66263695ae50e705ee917ff93c8799a3db8338cb12ff84a3da', + '0xce001c26dc604b83a240fb194ca3435bf4b4825a31bba5183f1bfb77492003b9', + '0x50a9da4d14d8f97c6a166c5e0e256ecfed81a4afc005f67dffb52f85d3f528f3', + '0x967c021c09e23dd777a250f167518ebebd232ea2e2a63173075f631563a44978', + '0xa3f8fe7cab9ebf3bbf6711704f65afaec9cb75744bd098770a70835a6ba5eed8', + '0x44741405982c9a87d3758930b4b51a649700ceccfce0ff68642f810e280c3392', + '0xbc4777bdce57f572db079910be82cbe6cbb8a751c2f1eaf3ce8406678919f107', + '0x473325dd009f7ce64169c7b4d220dd473723f404a549ea2ea1fb19d540815a94', + '0xda841a8bf1d8505c585d045aff3c0f2e8bed6af0933bb820395c20942bbd30bf', + '0x7da907b1f5413c4a1ddaa76b4f52319f30978211002d26435eb042ba76f1276f', + '0x702053a4f7d00b5347d9e51fe2848fd1925d49c6ad85a06053ecac15dac6cdbe', + '0x9686c57251c11661439d2e6c1c8cbc07b043a1f82e0eff4fc02e191a7b62653a', + '0x224efd67d481074c83f05955c0a956b8f73de135b1b125d0d75b655cd94ff48b', + '0x9ee1cce0ad17812f92d4dbc5f6706514a80ab55d572d2dc26a3d3b052bfd8e46', + '0x5f808f1dea6a5c868b7d1351b443a6bbfdccdd507793fb95b1cee0f8e8f9aaa5', + '0xd2f672257722f5e42961663056c7b340430c37bf868fc440e81ff4594b8d4ea2', + '0x721f30048d29db878f3f43ae8ab5a67bfd5026e232a59d425d504a8f2b3ba8e1', + '0x75803567400aab20282d7c35c73f45716c526331a77cb49b77f5841e5d4e3e70', + '0x1e7a9e0492fe9a198047f02699eef05cb19641e9eaa0e95dd9206a91094ab12b', + '0xf3ae4b5bffbc8cb58315385d6f0fdc2d133a5bbc1676b9c9d19baf584f76e775', + '0x3d52c0c958ff238520aad6d62f44b45d4adb87db030e7df0068fddeb16b9f876', + '0x3033ef7c1df0025319d82fe1dbcf8ca2f2588e4f7a2fad524ad488f623e022ea', + '0x242dc765560a67f4e4044e08178e753d1902fb704ac7f1915d669868851446fa', + '0xaab360be2560e4fd5dd5e4e1e028c5c9bb6f7ac7c5acb01e0d022316c5eff493', + '0xaffebb9305c90e982801d9b90d15d88bdde28de49d9a9911d248255faf1702c9', + '0xa7fce71f799dffba8957d5be1908b598cad9edcc277b24a2dd79ee91425d20e1', + '0x637421d6411b1d2aaf7a5197b5ade4beec6b5beaf87bdfb15012133e3c5f1755', + '0x208fc72529cccdf41694ee6557c16f2b42b369f9cc9593504c0c30304a4c44d9', + '0x49f90e5f89f5e138e3257e76b3491d56d8c870952c2951f55e75ac6515a1034f', + '0x6a5fde5a6dd419c478ab08d090e29d6500ec00b7f966ce48f882d4784d7e4aba', + '0x3cfd5a8169d1b70d689aae73c32f632f77032cc1349850e720da703000a70106', + '0x8218739d7faf35439a3c424cdbd57566316c4cf18bad6fcb229cfcf2f0d280fe', + '0x11f998cc78b0a11eedec5c2721f719e5c4fa444a48deb43802bd80cf9e7ca1b6', + '0x378baca4e2d3b1772db988f4db5ef269535fd1134f606f35a98ad4a46ca2ecea', + '0xea2606990ac2f8d863ff49e9d58e25a158ef09bf6fe84bc9e3690d79c4550023', + '0x5412f1267dd06eea95f5d5917613c6e610870c2c65879ee2609eb13129448324', + '0x51696ea2dcbdb4de89f0db26c015c7e3c991d511faab126c5221177109f893fd', + '0x0415dde0c24a77334ad9c29821c3de0fe5859c61f09f413cc00f4212b10b4419', + '0xe2bb9751cb41fcc05ba942444073c341538701dc0ec1c233eb41b7b01d7bb1b0', + '0x87c44b99ed51fbdb22f0d529aeb45bcd40feac801811f5a73417f580dfa599f9', + '0x9400167d781eb70192983a003af8eda27c7abcf98e26d1999baf0b113717b031', + '0x07fab0635e408994ab7cbef0fd59a5a1f7c4dcfc11e04b469db040be565089e9', + '0x21129a4585401f3bbe29461e31493e403dcb1dc4b0f3d1616b30f96ed50dc072', + '0x76a682a8e2e55751de91635b6ae4aa16edcf5c92d9bff8ba38074bfe579f3d1b', + '0xce610adb504b9669f221ee951d7125c35a98955e935e8e8189aa097b4332fc74', + '0xc1da492066f1911ec123dcc3b90fd8742ae80629a8d2be1f071ee439df0fad36', + '0x0aeb5abc5ac25e53b4ce1acd8ee31944a671c0bf999788dfd66026bfd824c35c', + '0x0ddd552f60d99917bdb295310a07b18d4979b63be40babcc3882c509b89fcad5', + '0xe9fc62fde021320a620ea3cc229f6d81d7ef3bbdfac638e6a7fa5a9efa53f7fd', + '0x115d56f83d033d0342ad29c5e611f0aaf0fda946ea8df88557c54cc4c54d596d', + '0xe57a719daff2a6ad5cfa3cbcfbd6fa2c240451f03e31606bc1f447dc2e05a7c1', + '0x28edc89d848fb380f2d77ac24a5d51a3ffadbac384750c493dae7900d0408fa6', + '0x9d45ead11495b1dc370afa9437a9ec8cf04c668dbca71381cb0c17d8d413b7e3', + '0xa97f3acff860ff73cec3488946117cb6e7ff1582c715eab2b2ac784e76881007', + '0x188a67b979160d3c690aa44b2a9c61f3749866fa446a8eb08c5efad429328104', + '0xd5a127ff4828f9409819ef9d3c68ddb2542a86cb5e4b26ecb1ee3175f957f056', + '0xb1801e611dd2595d7eea1460a1987696af1acfcbe595e4e49683c02265360596', + '0xa2973d1088e1aeb252a119312149e165105eb2613da1373c2094bcfa84307cb7', + '0xdd9505703e13a93c326c1bcb985df44681f45edb36a28f2778a8dd19d3be4862', + '0x9d659b3d8cbb302a03407abc87816b9a35800f60d8ba57c1edd36bf5bea9a449', + '0x01d91571acfe7280b9c4fa250c1b17e56667342e8a7ef6a82ad30ae4a125a82d', + '0x52b5506e3ebde360635b1c2a8cef1a0ced68398867a9b775483097b25c70e900', + '0x366af927a2447f65d3eaaadb61d1db4aa066e8dcec0cd37a356f059b15104762', + '0x28225c34d9733d24ba72aa788de08f8c0aa455c5e332b242474ccac323172b92', + '0x2aef043594846f9575f4bf802de495a878b28858688b9b1db341e8cd8e536699', + '0x2fc3b1757b7c316ee2b59860b35101a129048b62c8a1d387451e46c612ad7259', + '0x69bffc6033df35d200d915fe4394381b855939aba7251edfeaadabb91767f1cb', + '0x4262bdd739dd0bc51cd2554482cde22d6e3a3440fc99392ed14a669b0854f4a9', + '0x0771289859d1f94591e4020ab7d8f02e7946800590934860230dcf59e581f9ee', + '0x8ff8a593d0a68428e66feaf570a22a36956ac285e2c688700e77a593a9edc61d', + '0x2a1af1a05d6a6c55e54e1a8329c4f91acb150025ba193d6da9f8188b2594947f', + '0x9d1f43be39fc57f261f1e27bdde1cec817eb8d4cb6b49d04a0920b08faf0aaeb', + '0x686e16c3cc1027bd7ed634c09c4f3605001d7aae32f38dec0fe42eb417d283ba', + '0x493d40f340fdf70a2d967f7af4d76bb3ce5b2166a70a7bc2bbf58d1fc461b686', + '0x7da118149f80b412dae2aeb1e23f975f128c299505da315c3f637ae88f01da7b', + '0x372705225cb4139530166ed9cd4c013b53ba8b4efcb7bb05a545e59ec634e962', + '0x69d7658d4a356090383cc706814156ca12d6fde655ff66ba87694f6ea314510b', + ], + root: '0xe1108bc2d965f0c397fa2a98a7e107a7a319ddc2f1c005f5a8f99ce598b34b58', +}; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index 97cacbcb..f12fa1a8 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -1,48 +1,95 @@ +import { toLittleEndian64 } from '../../../deposit.utils'; import { DepositTree } from './deposit-tree'; +import { digest2Bytes32 } from '@chainsafe/as-sha256'; +import { fixture_10k, fixture_20k } from './deposit-tree.fixture'; +import { fromHexString } from '@chainsafe/ssz'; describe('DepositTree', () => { - let depositTree; + let depositTree: DepositTree; beforeEach(() => { depositTree = new DepositTree(); }); - test('should correctly initialize', () => { - expect(depositTree.nodeCount).toBe(0); - expect(depositTree.branch.length).toBe(0); + test('should initialize zero hashes correctly', () => { expect(depositTree.zeroHashes[0]).toEqual(DepositTree.ZERO_HASH); + for (let i = 1; i < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; i++) { + expect(depositTree.zeroHashes[i]).not.toEqual(undefined); + } }); - test('insert should correctly modify the tree', () => { - depositTree.insert({ - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000001', // Little endian of 1 - signature: '0x55667788', - }); + test('should correctly insert a node and update the tree', () => { + const initialNodeCount = depositTree.nodeCount; + const node = new Uint8Array(32).fill(1); // Example node hash + depositTree.insertNode(node); + expect(depositTree.nodeCount).toBe(initialNodeCount + 1); + }); + test('should handle detailed node data correctly', () => { + console.log(toLittleEndian64(1)); + const nodeData = { + wc: '0x123456789abcdef0', // Ensure hex strings are of even length + pubkey: '0xabcdef1234567890', + signature: '0x987654321fedcba0', + amount: '0x01000000000000', // Example amount + }; + depositTree.insert(nodeData); expect(depositTree.nodeCount).toBe(1); + }); + + test('should clone the tree correctly', () => { + depositTree.insertNode(new Uint8Array(32).fill(1)); + const clonedTree = depositTree.clone(); + expect(clonedTree.nodeCount).toEqual(depositTree.nodeCount); + expect(clonedTree.branch).toEqual(depositTree.branch); + expect(clonedTree).not.toBe(depositTree); + }); + + test('branch updates correctly after multiple insertions', () => { + const node1 = new Uint8Array(32).fill(1); // First example node + depositTree.insertNode(node1); // First insertion + + expect(depositTree.branch[0]).toEqual(node1); - depositTree.insert({ - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000002', // Little endian of 2 - signature: '0x55667788', - }); + const node2 = new Uint8Array(32).fill(2); // Second example node + depositTree.insertNode(node2); // Second insertion - expect(depositTree.nodeCount).toBe(2); + // Now, we need to check the second level of the branch + // This should use the same hashing function as used in your actual code + const expectedHashAfterSecondInsert = digest2Bytes32( + depositTree.branch[0], + node2, + ); + expect(depositTree.branch[1]).toEqual(expectedHashAfterSecondInsert); }); - test('clone should create an exact copy of the tree', () => { - const nodeData = { - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000001', - signature: '0x55667788', + test('should throw error on invalid NodeData', () => { + const invalidNodeData = { + wc: 'xyz', + pubkey: 'abc', + signature: '123', + amount: 'not a number', }; - depositTree.insert(nodeData); - const clonedTree = depositTree.clone(); - expect(clonedTree).toEqual(depositTree); - expect(clonedTree.getRoot()).toEqual(depositTree.getRoot()); + expect(() => depositTree.insert(invalidNodeData)).toThrowError(); + }); + + test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { + fixture_10k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + + expect(depositTree.nodeCount).toEqual(fixture_10k.events.length); + expect(depositTree.getRoot()).toEqual(fixture_10k.root); + }); + + test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { + fixture_10k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + + expect(depositTree.nodeCount).toEqual(fixture_10k.events.length); + expect(depositTree.getRoot()).toEqual(fixture_10k.root); + + fixture_20k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + expect(depositTree.nodeCount).toEqual( + fixture_10k.events.length + fixture_20k.events.length, + ); + expect(depositTree.getRoot()).toEqual(fixture_20k.root); }); }); From ddc001893f91b46fbbc57d260207d3ae1febf278 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 1 Sep 2024 14:27:23 +0200 Subject: [PATCH 17/94] feat: deposit-tree crypto refactoring --- .../deposits-registry/crypto/containers.ts | 1 + .../deposits-registry/crypto/index.ts | 2 ++ .../deposits-registry/crypto/utils.ts | 12 +++++++++ .../deposits-registry/deposit.utils.ts | 20 --------------- .../fetcher/fetcher.service.ts | 2 +- .../deposit-tree/deposit-tree.spec.ts | 12 ++++++--- .../deposit-tree/deposit-tree.ts | 25 ++++++++++--------- .../deposits-registry/store/index.ts | 6 ++--- 8 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 src/contracts/deposits-registry/crypto/containers.ts create mode 100644 src/contracts/deposits-registry/crypto/index.ts create mode 100644 src/contracts/deposits-registry/crypto/utils.ts delete mode 100644 src/contracts/deposits-registry/deposit.utils.ts diff --git a/src/contracts/deposits-registry/crypto/containers.ts b/src/contracts/deposits-registry/crypto/containers.ts new file mode 100644 index 00000000..572bb858 --- /dev/null +++ b/src/contracts/deposits-registry/crypto/containers.ts @@ -0,0 +1 @@ +export { DepositData } from 'bls/bls.containers'; diff --git a/src/contracts/deposits-registry/crypto/index.ts b/src/contracts/deposits-registry/crypto/index.ts new file mode 100644 index 00000000..374b83cf --- /dev/null +++ b/src/contracts/deposits-registry/crypto/index.ts @@ -0,0 +1,2 @@ +export * from './containers'; +export * from './utils'; diff --git a/src/contracts/deposits-registry/crypto/utils.ts b/src/contracts/deposits-registry/crypto/utils.ts new file mode 100644 index 00000000..26060e8c --- /dev/null +++ b/src/contracts/deposits-registry/crypto/utils.ts @@ -0,0 +1,12 @@ +import { fromHexString, toHexString } from '@chainsafe/ssz'; +import { UintNum64 } from 'bls/bls.constants'; +export { digest2Bytes32 } from '@chainsafe/as-sha256'; +export { fromHexString, toHexString }; + +export const parseLittleEndian64 = (str: string) => { + return UintNum64.deserialize(fromHexString(str)); +}; + +export const toLittleEndian64 = (value: number): string => { + return toHexString(UintNum64.serialize(value)); +}; diff --git a/src/contracts/deposits-registry/deposit.utils.ts b/src/contracts/deposits-registry/deposit.utils.ts deleted file mode 100644 index 555b676a..00000000 --- a/src/contracts/deposits-registry/deposit.utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -const changeEndianness = (string: any) => { - string = string.replace('0x', ''); - const result: string[] = []; - let len = string.length - 2; - while (len >= 0) { - result.push(string.substr(len, 2)); - len -= 2; - } - return '0x' + result.join(''); -}; - -export const parseLittleEndian64 = (str: string) => { - return parseInt(changeEndianness(str), 16); -}; - -export const toLittleEndian64 = (value: number): string => { - const buffer = Buffer.allocUnsafe(8); - buffer.writeBigUInt64LE(BigInt(value)); - return '0x' + buffer.toString('hex'); -}; diff --git a/src/contracts/deposits-registry/fetcher/fetcher.service.ts b/src/contracts/deposits-registry/fetcher/fetcher.service.ts index e2d7348e..6376a5af 100644 --- a/src/contracts/deposits-registry/fetcher/fetcher.service.ts +++ b/src/contracts/deposits-registry/fetcher/fetcher.service.ts @@ -4,7 +4,7 @@ import { RepositoryService } from 'contracts/repository'; import { DepositEventEvent } from 'generated/DepositAbi'; import { ProviderService } from 'provider'; -import { parseLittleEndian64 } from '../deposit.utils'; +import { parseLittleEndian64 } from '../crypto'; import { DepositEvent, VerifiedDepositEventGroup } from '../interfaces'; import { DepositTree } from '../sanity-checker/integrity-checker/deposit-tree'; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index f12fa1a8..9acd2927 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -1,8 +1,10 @@ -import { toLittleEndian64 } from '../../../deposit.utils'; +import { + digest2Bytes32, + fromHexString, + toLittleEndian64, +} from '../../../crypto'; import { DepositTree } from './deposit-tree'; -import { digest2Bytes32 } from '@chainsafe/as-sha256'; import { fixture_10k, fixture_20k } from './deposit-tree.fixture'; -import { fromHexString } from '@chainsafe/ssz'; describe('DepositTree', () => { let depositTree: DepositTree; @@ -31,7 +33,7 @@ describe('DepositTree', () => { wc: '0x123456789abcdef0', // Ensure hex strings are of even length pubkey: '0xabcdef1234567890', signature: '0x987654321fedcba0', - amount: '0x01000000000000', // Example amount + amount: '0x0100000000000000', // Example amount }; depositTree.insert(nodeData); expect(depositTree.nodeCount).toBe(1); @@ -73,6 +75,8 @@ describe('DepositTree', () => { expect(() => depositTree.insert(invalidNodeData)).toThrowError(); }); + test.todo('actual validation using data and hash from blockchain'); + test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { fixture_10k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index ff2334d2..4d4548ed 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -1,15 +1,20 @@ +import { + DepositData, + digest2Bytes32, + fromHexString, + parseLittleEndian64, + toLittleEndian64, +} from '../../../crypto'; import { ethers } from 'ethers'; -import { digest2Bytes32 } from '@chainsafe/as-sha256'; -import { fromHexString } from '@chainsafe/ssz'; -import { parseLittleEndian64, toLittleEndian64 } from '../../../deposit.utils'; -import { DepositData } from 'bls/bls.containers'; import { NodeData } from '../../../interfaces'; +const ZERO_HASH_HEX = + '0x0000000000000000000000000000000000000000000000000000000000000000'; +const ZERO_HASH_ROOT_HEX = '0x000000000000000000000000000000000000000000000000'; + export class DepositTree { static DEPOSIT_CONTRACT_TREE_DEPTH = 32; - static ZERO_HASH = fromHexString( - '0x0000000000000000000000000000000000000000000000000000000000000000', - ); + static ZERO_HASH = fromHexString(ZERO_HASH_HEX); zeroHashes: Uint8Array[] = new Array(DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH); branch: Uint8Array[] = []; nodeCount = 0; @@ -106,11 +111,7 @@ export class DepositTree { } const finalRoot = ethers.utils.soliditySha256( ['bytes', 'bytes', 'bytes'], - [ - node, - toLittleEndian64(this.nodeCount), - '0x000000000000000000000000000000000000000000000000', - ], + [node, toLittleEndian64(this.nodeCount), ZERO_HASH_ROOT_HEX], ); return finalRoot; } diff --git a/src/contracts/deposits-registry/store/index.ts b/src/contracts/deposits-registry/store/index.ts index eed6aa71..99322182 100644 --- a/src/contracts/deposits-registry/store/index.ts +++ b/src/contracts/deposits-registry/store/index.ts @@ -1,3 +1,3 @@ -export * from './leveldb.constants'; -export * from './leveldb.module'; -export * from './leveldb.service'; +export * from './store.constants'; +export * from './store.module'; +export * from './store.service'; From 8d93f63dd8d2d115201cc89934b55d42a1e2a20d Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 1 Sep 2024 14:45:15 +0200 Subject: [PATCH 18/94] feat: data cast tests --- .../deposit-tree/deposit-tree.fixture.ts | 80 ++++++++++++++++++- .../deposit-tree/deposit-tree.spec.ts | 54 ++++++++++--- .../deposit-tree/deposit-tree.ts | 10 --- 3 files changed, 119 insertions(+), 25 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts index de67b7f2..32ebec88 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.fixture.ts @@ -1,4 +1,79 @@ -export const fixture_10k = { +export const dataTransformFixtures = [ + { + valid: true, + pubkey: + '0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95', + wc: '0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71', + amount: '0x00ca9a3b00000000', + signature: + '0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c1324b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8', + tx: '0x7085c586686d666e8bb6e9477a0f0b09565b2060a11f1c4209d3a52295033832', + blockNumber: 11185311, + blockHash: + '0x1ecb9dd23676c9201af1e8026e7d83a1f979b8abd381064fba0b593fcff7b235', + logIndex: 131, + index: '0x0000000000000000', + depositCount: 0, + depositDataRoot: + '0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e', + }, + { + valid: true, + pubkey: + '0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c', + wc: '0x0092c20062cee70389f1cb4fa566a2be5e2319ff43965db26dbaa3ce90b9df99', + amount: '0x00ca9a3b00000000', + signature: + '0x985f365b3459176da437560337cc074d153663f65e3c6bab28197e34cd7f926fa940176ba43484fb5297f679bc869f5d10ee62f64a119d756182005fbb28046c0541f627b430cabfeb3599ebaa1b8efd08de562ec03a8d78c2f9e1b6f01d8aba', + tx: '0xa90ed27521c07e66d52db6ee47d729d118229925303706b35e4d36d8e830ba7a', + blockNumber: 11191448, + blockHash: + '0xa51cbe797cac4ef0297862576e64444a90d3bec332949c352f253405aa129f1e', + logIndex: 126, + index: '0x0100000000000000', + depositCount: 1, + depositDataRoot: + '0x76fffc948646005fce32e27555238dfe801c9e7eea28ff40dbe2afe8f83cf0c6', + }, + { + valid: true, + pubkey: + '0xb2ff4716ed345b05dd1dfc6a5a9fa70856d8c75dcc9e881dd2f766d5f891326f0d10e96f3a444ce6c912b69c22c6754d', + wc: '0x00d66cf353931500a54cbd0bc59cbaac6690cb0932f42dc8afeddc88feeaad6f', + amount: '0x00ca9a3b00000000', + signature: + '0xb868229df29f2b48409c5aac70594c9882be4a7b1e60ba1a9c985f87f4a9cad18bbf74a78734cd9b4911b57a23dc9d4118b70da8e2ae1faaab91c04076d66ead359a0be26845410d18a42910bdf0b9ae4b4bfcc90f8bb528f1a92c91a1ad6547', + tx: '0x14f1d17ef6051109bf4b9e5dd9b494f12580a508a8d412af6d5e857f8d6a0f0b', + blockNumber: 11191495, + blockHash: + '0x37c7097adfd4e30c93b8840d32c215e189d95200a9ef1e3445b926efb48ae99f', + logIndex: 76, + index: '0x0200000000000000', + depositCount: 2, + depositDataRoot: + '0x3e74b357fbf0bf36bed50de7ee3a3caaa11006e7ea5ce644d16de8d666b2c7a9', + }, + { + valid: true, + pubkey: + '0x8e323fd501233cd4d1b9d63d74076a38de50f2f584b001a5ac2412e4e46adb26d2fb2a6041e7e8c57cd4df0916729219', + wc: '0x00d6b91fbbce0146739afb0f541d6c21e8c41e92b97874828f402597bf530ce4', + amount: '0x00ca9a3b00000000', + signature: + '0xb9a4bccc6fc91192b603dd7ee1c99eabee415bdde9d96146c71b2ce4ce9e292ded93fa150850242c327e6ce2f50cb75b134afe5bb7ecca9c328e6f2dc1da931389a2d15d435eaed1222991d22aeecc026b2390afa5f941d2ed5277b3d3fbc350', + tx: '0x6e1e30cb4b6e0029fc4762cf74b264ce66a9d078a0f732583a71544fdadddf72', + blockNumber: 11191501, + blockHash: + '0xa6d7b9926b794b8de798720f058badbf436ef52907b19ebd090172b75d24ed20', + logIndex: 328, + index: '0x0300000000000000', + depositCount: 3, + depositDataRoot: + '0x790284c0a36abd53ec0ce9284f4ad4af72c891a38456e73e15685bb99dfc09e9', + }, +]; + +export const depositDataRootsFixture10k = { events: [ '0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e', '0x76fffc948646005fce32e27555238dfe801c9e7eea28ff40dbe2afe8f83cf0c6', @@ -195,7 +270,8 @@ export const fixture_10k = { ], root: '0x3ab1589b2853143f682dad19e443648489d318c8619e79f59a44d14a846fbb1a', }; -export const fixture_20k = { + +export const depositDataRootsFixture20k = { events: [ '0x01ba5818c17223777b13dd194f5e64ca92927af901d0fd1599e93c4554f17b00', '0x48367a14c733b6b4907a2c60fd9b254bb6d7e4d00d0792544309f097d3b76a44', diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index 9acd2927..f8d76ee0 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -1,10 +1,15 @@ import { digest2Bytes32, fromHexString, + toHexString, toLittleEndian64, } from '../../../crypto'; import { DepositTree } from './deposit-tree'; -import { fixture_10k, fixture_20k } from './deposit-tree.fixture'; +import { + depositDataRootsFixture20k, + depositDataRootsFixture10k, + dataTransformFixtures, +} from './deposit-tree.fixture'; describe('DepositTree', () => { let depositTree: DepositTree; @@ -35,7 +40,7 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', // Example amount }; - depositTree.insert(nodeData); + depositTree.insertNode(DepositTree.formDepositNode(nodeData)); expect(depositTree.nodeCount).toBe(1); }); @@ -72,28 +77,51 @@ describe('DepositTree', () => { signature: '123', amount: 'not a number', }; - expect(() => depositTree.insert(invalidNodeData)).toThrowError(); + expect(() => DepositTree.formDepositNode(invalidNodeData)).toThrowError(); }); - test.todo('actual validation using data and hash from blockchain'); + test.each(dataTransformFixtures)( + 'actual validation using data and hash from blockchain', + (event) => { + const depositDataRoot = DepositTree.formDepositNode({ + wc: event.wc, + pubkey: event.pubkey, + signature: event.signature, + amount: event.amount, + }); + + expect(toHexString(depositDataRoot)).toEqual(event.depositDataRoot); + }, + ); test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { - fixture_10k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + depositDataRootsFixture10k.events.map((ev) => + depositTree.insertNode(fromHexString(ev)), + ); - expect(depositTree.nodeCount).toEqual(fixture_10k.events.length); - expect(depositTree.getRoot()).toEqual(fixture_10k.root); + expect(depositTree.nodeCount).toEqual( + depositDataRootsFixture10k.events.length, + ); + expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); }); test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { - fixture_10k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + depositDataRootsFixture10k.events.map((ev) => + depositTree.insertNode(fromHexString(ev)), + ); - expect(depositTree.nodeCount).toEqual(fixture_10k.events.length); - expect(depositTree.getRoot()).toEqual(fixture_10k.root); + expect(depositTree.nodeCount).toEqual( + depositDataRootsFixture10k.events.length, + ); + expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); - fixture_20k.events.map((ev) => depositTree.insertNode(fromHexString(ev))); + depositDataRootsFixture20k.events.map((ev) => + depositTree.insertNode(fromHexString(ev)), + ); expect(depositTree.nodeCount).toEqual( - fixture_10k.events.length + fixture_20k.events.length, + depositDataRootsFixture10k.events.length + + depositDataRootsFixture20k.events.length, ); - expect(depositTree.getRoot()).toEqual(fixture_20k.root); + expect(depositTree.getRoot()).toEqual(depositDataRootsFixture20k.root); }); }); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index 4d4548ed..d87d94b0 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -71,16 +71,6 @@ export class DepositTree { } } - /** - * Inserts a new deposit into the tree using detailed node data. - * @param {NodeData} nodeData - The detailed data of the deposit to be inserted. - */ - public insert(nodeData: NodeData) { - const node = DepositTree.formDepositNode(nodeData); - this.nodeCount++; - this.formBranch(node, this.nodeCount); - } - /** * Inserts a new node into the tree using already computed node hash. * @param {Uint8Array} node - The node's hash to be inserted. From 6ea326892fa6d239c14d54f942f7784a3be63021 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 1 Sep 2024 15:26:53 +0200 Subject: [PATCH 19/94] feat: safe branch clone --- .../deposits-registry.service.ts | 5 +- .../deposit-tree/deposit-tree.spec.ts | 68 ++++++++++++++++--- .../deposit-tree/deposit-tree.ts | 2 +- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index cd69e99b..fdb0074b 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -189,10 +189,7 @@ export class DepositService { const endBlock = blockNumber; const cachedEvents = await this.getCachedEvents(); //!!!! - // если мы измкеним поведение таким образом - // то у нас появится инвариант когда мы сами кораптим кэш - // 1,2,3,4 - // + // TODO: To make changes. We will now give validation status, verification status to the reorganisation. const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); if (!isCacheValid) process.exit(1); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index f8d76ee0..2b7d45a8 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -1,9 +1,4 @@ -import { - digest2Bytes32, - fromHexString, - toHexString, - toLittleEndian64, -} from '../../../crypto'; +import { digest2Bytes32, fromHexString, toHexString } from '../../../crypto'; import { DepositTree } from './deposit-tree'; import { depositDataRootsFixture20k, @@ -33,12 +28,67 @@ describe('DepositTree', () => { }); test('should handle detailed node data correctly', () => { - console.log(toLittleEndian64(1)); + const originalTree = new DepositTree(); + const nodeData = { + wc: '0x123456789abcdef0', + pubkey: '0xabcdef1234567890', + signature: '0x987654321fedcba0', + amount: '0x0100000000000000', + }; + originalTree.insertNode(DepositTree.formDepositNode(nodeData)); + expect(originalTree.nodeCount).toBe(1); + + const oldDepositRoot = originalTree.getRoot(); + const cloned = originalTree.clone(); + + cloned.insertNode( + DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + ); + + expect(cloned.getRoot()).not.toEqual(oldDepositRoot); + expect(cloned.getRoot()).not.toEqual(originalTree.getRoot()); + expect(originalTree.getRoot()).toEqual(oldDepositRoot); + + const freshTree = new DepositTree(); + + freshTree.insertNode(DepositTree.formDepositNode(nodeData)); + freshTree.insertNode( + DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + ); + + expect(cloned.getRoot()).toEqual(freshTree.getRoot()); + }); + + test('branches from cloned tree do not linked with original tree', () => { + const originalTree = new DepositTree(); + const nodeData = { + wc: '0x123456789abcdef0', + pubkey: '0xabcdef1234567890', + signature: '0x987654321fedcba0', + amount: '0x0100000000000000', + }; + + originalTree.insertNode( + DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + ); + originalTree.insertNode( + DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + ); + + originalTree.branch[0][0] = 1; + const clone = originalTree.clone(); + originalTree.branch[0][1] = 1; + + expect(clone.branch[0][1]).toBe(142); + expect(originalTree.branch[0][1]).toBe(1); + }); + + test('clone works correctly', () => { const nodeData = { - wc: '0x123456789abcdef0', // Ensure hex strings are of even length + wc: '0x123456789abcdef0', pubkey: '0xabcdef1234567890', signature: '0x987654321fedcba0', - amount: '0x0100000000000000', // Example amount + amount: '0x0100000000000000', }; depositTree.insertNode(DepositTree.formDepositNode(nodeData)); expect(depositTree.nodeCount).toBe(1); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index d87d94b0..4ba11b74 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -112,7 +112,7 @@ export class DepositTree { */ public clone() { const tree = new DepositTree(); - tree.branch = [...this.branch]; + tree.branch = this.branch.map((array) => Uint8Array.from(array)); tree.nodeCount = this.nodeCount; return tree; } From 8a9300eed85e0b7034a446afb1b15b900daa9e7d Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Sun, 1 Sep 2024 21:02:55 +0400 Subject: [PATCH 20/94] fix: duplicates algorithm --- .../keys-duplication-checker.service.spec.ts | 530 +++++++++++------- .../keys-duplication-checker.service.ts | 317 +++++++---- src/guardian/duplicates/keys.fixtures.ts | 28 +- test/helpers/test-setup.ts | 8 +- 4 files changed, 574 insertions(+), 309 deletions(-) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts index 097014cf..06745a4d 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts @@ -6,12 +6,7 @@ import { } from 'contracts/signing-key-events-cache'; import { KeysDuplicationCheckerModule } from './keys-duplication-checker.module'; import { KeysDuplicationCheckerService } from './keys-duplication-checker.service'; -import { - eventMock, - keyMock1, - keyMock1Duplicate, - keysMock, -} from './keys.fixtures'; +import { eventMock1, keyMock1, keyMock2 } from './keys.fixtures'; import { ConfigModule } from 'common/config'; import { MockProviderModule } from 'provider'; import { BlockData } from 'guardian/interfaces'; @@ -24,6 +19,8 @@ describe('KeysDuplicationCheckerService', () => { getUpdatedSigningKeyEvents: jest.fn(), }; + const emptyBlockData = {} as BlockData; + beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [ @@ -49,223 +46,380 @@ describe('KeysDuplicationCheckerService', () => { jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); }); - describe('findDuplicateKeys', () => { + describe('getDuplicateKeyGroups', () => { it('should identify and return tuples of duplicated keys along with their occurrences', () => { - const result = service.findDuplicateKeys(keysMock); - const expectedKey = - '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3'; - const expectedOccurrences = keysMock.filter( - (key) => key.key === expectedKey, - ); + const result = service.getDuplicateKeyGroups([ + { ...keyMock1, index: 1 }, + { ...keyMock1, index: 2 }, + { ...keyMock2, index: 3 }, + { ...keyMock2, index: 4 }, + ]); // Check the number of groups of duplicated keys identified - expect(result.length).toEqual(1); + expect(result.length).toEqual(2); + + expect(result[0][0]).toEqual(keyMock1.key); + expect(result[0][1].length).toEqual(2); + expect(result[0][1]).toEqual([ + { ...keyMock1, index: 1 }, + { ...keyMock1, index: 2 }, + ]); - const [key, occurrences] = result[0]; - expect(key).toEqual(expectedKey); - expect(occurrences.length).toEqual(2); - expect(occurrences).toEqual(expect.arrayContaining(expectedOccurrences)); + expect(result[1][0]).toEqual(keyMock2.key); + expect(result[1][1].length).toEqual(2); + expect(result[1][1]).toEqual([ + { ...keyMock2, index: 3 }, + { ...keyMock2, index: 4 }, + ]); }); }); describe('getDuplicatedKeys', () => { - it('duplicates across one operator', async () => { - const result = await service.getDuplicatedKeys(keysMock, {} as BlockData); + describe('Detect duplicates within a single operator', () => { + it('Returns unused keys as duplicates if the list contains a deposited key', async () => { + // will be return key with smallest index + // deposited keys has a smallest index + const unusedKey = { ...keyMock1, index: 2, used: false }; + const usedKey = { ...keyMock1, index: 1, used: true }; + const duplicatedKeysAmongSingleOperator = [unusedKey, usedKey]; - const expected = { duplicates: [keyMock1Duplicate], unresolved: [] }; + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongSingleOperator, + emptyBlockData, + ); - expect(result.duplicates.length).toEqual(1); - expect(result.duplicates).toEqual( - expect.arrayContaining(expected.duplicates), - ); - expect(result.unresolved).toEqual([]); - }); + expect(result.duplicates).toEqual([unusedKey]); + expect(result.unresolved).toEqual([]); + }); - it('original key is deposited', async () => { - const result = await service.getDuplicatedKeys( - [ - ...keysMock, - { - ...keyMock1Duplicate, - used: true, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - }, - ], - {} as BlockData, - ); - - const expected = { - duplicates: [keyMock1, keyMock1Duplicate], - unresolved: [], - }; - - expect(result.duplicates).toEqual( - expect.arrayContaining(expected.duplicates), - ); - expect(result.duplicates[0].used).toBeFalsy(); - expect(result.duplicates[1].used).toBeFalsy(); - - expect(result.unresolved).toEqual([]); - }); + it('Identifies the key with the smallest index as the earliest and returns the others as duplicates', async () => { + const unusedKey1 = { ...keyMock1, index: 1, used: false }; + const unusedKey2 = { ...keyMock1, index: 2, used: false }; + const duplicatedKeysAmongSingleOperator = [unusedKey1, unusedKey2]; - it('original key is deposited and from another module', async () => { - const result = await service.getDuplicatedKeys( - [ - ...keysMock, - { - ...keyMock1Duplicate, - used: true, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - moduleAddress: '0x12344556', - }, - ], - {} as BlockData, - ); - - const expected = { - duplicates: [keyMock1, keyMock1Duplicate], - unresolved: [], - }; - - expect(result.duplicates).toEqual( - expect.arrayContaining(expected.duplicates), - ); - expect(result.duplicates[0].used).toBeFalsy(); - expect(result.duplicates[1].used).toBeFalsy(); - - expect(result.unresolved).toEqual([]); + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongSingleOperator, + emptyBlockData, + ); + + expect(result.duplicates).toEqual([unusedKey2]); + expect(result.unresolved).toEqual([]); + }); }); - describe('duplicate across two operators', () => { - it('keys were added in different blocks', async () => { - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( - async () => { - return { - events: [ - eventMock, - { ...eventMock, logIndex: eventMock.logIndex + 1 }, - { - ...eventMock, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - blockNumber: eventMock.blockNumber + 1, - }, - ], - }; - }, - ); + describe('Detect duplicates across multiple operators within the same module', () => { + it('Returns unused keys as duplicates if the list contains a deposited key', async () => { + const unusedKey = { ...keyMock1, used: false, operatorIndex: 1 }; + const usedKey = { ...keyMock1, used: true, operatorIndex: 2 }; + const duplicatedKeysAmongMultipleOperator = [unusedKey, usedKey]; const result = await service.getDuplicatedKeys( - [ - ...keysMock, - { - ...keyMock1Duplicate, - used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - }, - ], - {} as BlockData, + duplicatedKeysAmongMultipleOperator, + emptyBlockData, ); - const expected = { - duplicates: [ - keyMock1Duplicate, - { - ...keyMock1Duplicate, - used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, + expect(result.duplicates).toEqual([unusedKey]); + expect(result.unresolved).toEqual([]); + }); + + describe('Detect duplicates based on SigningKeyAdded events', () => { + it('Returns all keys as unresolved if there is no event for operator', async () => { + const unusedKey1 = { ...keyMock1, used: false, operatorIndex: 1 }; + const unusedKey2 = { ...keyMock1, used: false, operatorIndex: 2 }; + + // unresolved will not influence detection of other keys duplicates + const unusedKey3 = { ...keyMock2, used: false, operatorIndex: 1 }; + const usedKeys = { ...keyMock2, used: true, operatorIndex: 2 }; + + const keyMock1Event = { + ...eventMock1, + operatorIndex: 1, + logIndex: 1, + blockNumber: 1, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event], + }; + }, + ); + + const duplicatedKeysAmongMultipleOperators = [ + unusedKey1, + unusedKey2, + unusedKey3, + usedKeys, + ]; + + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleOperators, + emptyBlockData, + ); + + expect(result.duplicates).toEqual([unusedKey3]); + expect(result.unresolved).toEqual([unusedKey1, unusedKey2]); + }); + + it('Returns all keys as duplicates if multiple events occur in the smallest block', async () => { + const unusedKey1 = { ...keyMock1, used: false, operatorIndex: 1 }; + const unusedKey2 = { ...keyMock1, used: false, operatorIndex: 2 }; + + const keyMock1Event = { + ...eventMock1, + operatorIndex: 1, + logIndex: 1, + blockNumber: 1, + }; + + const keyMock2Event = { + ...eventMock1, + operatorIndex: 2, + logIndex: 2, + blockNumber: 1, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event, keyMock2Event], + }; + }, + ); + + const duplicatedKeysAmongMultipleOperators = [unusedKey1, unusedKey2]; + + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleOperators, + emptyBlockData, + ); + + expect(result.duplicates).toEqual([unusedKey1, unusedKey2]); + expect(result.unresolved).toEqual([]); + }); + + it('Returns all keys as duplicates except the one with the smallest block number and key index', async () => { + const unusedKey1 = { + ...keyMock1, + index: 1, + used: false, + operatorIndex: 1, + }; + const unusedKey2 = { + ...keyMock1, + index: 2, + used: false, + operatorIndex: 1, + }; + const unusedKey3 = { ...keyMock1, used: false, operatorIndex: 2 }; + + const keyMock1Event = { + ...eventMock1, + operatorIndex: 1, + logIndex: 1, + blockNumber: 1, + }; + + const keyMock2Event = { + ...eventMock1, + operatorIndex: 2, + logIndex: 1, + blockNumber: 2, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event, keyMock2Event], + }; }, - ], - unresolved: [], + ); + + const duplicatedKeysAmongMultipleOperators = [ + unusedKey1, + unusedKey2, + unusedKey3, + ]; + + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleOperators, + emptyBlockData, + ); + + expect(result.duplicates).toEqual([unusedKey2, unusedKey3]); + expect(result.unresolved).toEqual([]); + }); + }); + }); + + describe('Detect duplicates across multiple operators in different modules', () => { + it('Returns unused keys as duplicates if the list contains a deposited key', async () => { + const unusedKey = { + ...keyMock1, + used: false, + moduleAddress: 'address1', }; + const usedKey = { ...keyMock1, used: true, moduleAddress: 'address2' }; + const duplicatedKeysAmongMultipleModules = [unusedKey, usedKey]; - expect(result.duplicates).toEqual( - expect.arrayContaining(expected.duplicates), + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleModules, + emptyBlockData, ); + + expect(result.duplicates).toEqual([unusedKey]); expect(result.unresolved).toEqual([]); }); - it('keys were added in the same block', async () => { - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( - async () => { - return { - events: [ - eventMock, - { ...eventMock, logIndex: eventMock.logIndex + 1 }, - { - ...eventMock, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - logIndex: eventMock.logIndex + 2, - }, - ], - }; - }, - ); + describe('Detect duplicates based on SigningKeyAdded events', () => { + it('Return all keys as unresolved if there are no event for operator', async () => { + const unusedKey1 = { + ...keyMock1, + used: false, + moduleAddress: 'address1', + }; + const unusedKey2 = { + ...keyMock1, + used: false, + moduleAddress: 'address2', + }; - const result = await service.getDuplicatedKeys( - [ - ...keysMock, - { - ...keyMock1Duplicate, - used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, + // unresolved will not influence detection of other keys duplicates + const unusedKey3 = { ...keyMock2, used: false, operatorIndex: 1 }; + const usedKeys = { ...keyMock2, used: true, operatorIndex: 2 }; + + const keyMock1Event = { + ...eventMock1, + moduleAddress: 'address1', + logIndex: 1, + blockNumber: 1, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event], + }; }, - ], - {} as BlockData, - ); + ); + + const duplicatedKeysAmongMultipleModules = [ + unusedKey1, + unusedKey2, + unusedKey3, + usedKeys, + ]; + + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleModules, + emptyBlockData, + ); - const expected = { - duplicates: [ - keyMock1Duplicate, - { - ...keyMock1Duplicate, - used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, + expect(result.duplicates).toEqual([unusedKey3]); + expect(result.unresolved).toEqual([unusedKey1, unusedKey2]); + }); + + it('Returns all keys as duplicates if multiple events occur in the smallest block', async () => { + const unusedKey1 = { + ...keyMock1, + used: false, + moduleAddress: 'address1', + }; + const unusedKey2 = { + ...keyMock1, + used: false, + moduleAddress: 'address2', + }; + + const keyMock1Event = { + ...eventMock1, + moduleAddress: 'address1', + logIndex: 1, + blockNumber: 1, + }; + + const keyMock2Event = { + ...eventMock1, + moduleAddress: 'address2', + logIndex: 2, + blockNumber: 1, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event, keyMock2Event], + }; }, - ], - unresolved: [], - }; + ); - expect(result.duplicates).toEqual( - expect.arrayContaining(expected.duplicates), - ); - }); + const duplicatedKeysAmongMultipleModules = [unusedKey1, unusedKey2]; - it('should return unresolved keys list if no event for operator', async () => { - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( - async () => { - return { - events: [ - eventMock, - { ...eventMock, logIndex: eventMock.logIndex + 1 }, - ], - }; - }, - ); + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleModules, + emptyBlockData, + ); - const expected = [ - keyMock1, - keyMock1Duplicate, - { - ...keyMock1Duplicate, + expect(result.duplicates).toEqual([unusedKey1, unusedKey2]); + expect(result.unresolved).toEqual([]); + }); + + it('Returns all keys as duplicates except the one with the smallest block number and key index', async () => { + const unusedKey1 = { + ...keyMock1, + index: 1, + used: false, + moduleAddress: 'address1', + }; + const unusedKey2 = { + ...keyMock1, + index: 2, + used: false, + moduleAddress: 'address1', + }; + const unusedKey3 = { + ...keyMock1, used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, - }, - ]; - - const { duplicates, unresolved } = await service.getDuplicatedKeys( - [ - ...keysMock, - { - ...keyMock1Duplicate, - used: false, - operatorIndex: keyMock1Duplicate.operatorIndex + 1, + moduleAddress: 'address2', + }; + + const keyMock1Event = { + ...eventMock1, + moduleAddress: 'address1', + logIndex: 1, + blockNumber: 1, + }; + + const keyMock2Event = { + ...eventMock1, + moduleAddress: 'address2', + logIndex: 1, + blockNumber: 2, + }; + + mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + async () => { + return { + events: [keyMock1Event, keyMock2Event], + }; }, - ], - {} as BlockData, - ); + ); + + const duplicatedKeysAmongMultipleModules = [ + unusedKey1, + unusedKey2, + unusedKey3, + ]; + + const result = await service.getDuplicatedKeys( + duplicatedKeysAmongMultipleModules, + emptyBlockData, + ); - expect(duplicates).toEqual([]); - expect(unresolved).toEqual(expect.arrayContaining(expected)); + expect(result.duplicates).toEqual([unusedKey2, unusedKey3]); + expect(result.unresolved).toEqual([]); + }); }); }); }); diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index 37272e37..f728b3db 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -19,56 +19,30 @@ export class KeysDuplicationCheckerService { * 1. If there are duplicates within one operator, the key with the lowest index is considered the original, and the others are considered duplicates. * 2. If there are duplicates between different operators, check if a deposited key exists in the duplicates list; all others are considered duplicates. * 3. If there is no deposited key, check the SigningKeyAdded events for operators. - * 4. Sort events by block number and logIndex. The earliest event is considered the original, and the others are marked as duplicates. + * 4. Sort events by block number. The earliest event is considered the original, and the others are marked as duplicates. * * If there is no event for the key it will return list of unresolved keys. + * + * @param key public key + * @param blockData - collected data from the current block + * @returns An object containing two properties: + * - `duplicates`: An array of `RegistryKey` objects that are identified as duplicates. + * - `unresolved`: An array of `RegistryKey` objects for which no corresponding events were found. */ - async getDuplicatedKeys( + public async getDuplicatedKeys( keys: RegistryKey[], blockData: BlockData, ): Promise<{ duplicates: RegistryKey[]; unresolved: RegistryKey[] }> { - // List of all duplicates + if (keys.length === 0) { + return { duplicates: [], unresolved: [] }; + } // First element of sub-arrays is a key, second - all it's occurrences - const duplicatedKeys = this.findDuplicateKeys(keys); - - // async function that identify duplicates across list of duplicates - const getDuplicatedAndUnresolvedKeys = async ([key, occurrences]) => { - const operators = this.extractOperators(occurrences); - - // Function for identify duplicates across one operator - const duplicatesWithinOperator = () => - this.findDuplicatesWithinOperator(occurrences); - - // Function for identify duplicates if list contains deposited key - const duplicatesForDepositedKeys = () => - occurrences.filter((key) => !key.used); - - // Function for identify duplicates across multiple operators - const duplicatesAcrossOperators = async () => { - const { duplicateKeys, missingEvents } = - await this.getDuplicatesAcrossOperators( - key, - occurrences, - operators, - blockData, - ); - return { duplicates: duplicateKeys, unresolved: missingEvents }; - }; - - // if list contains only 1 operator - if (operators.size == 1) { - return { duplicates: duplicatesWithinOperator(), unresolved: [] }; - } else if (occurrences.some((key) => key.used)) { - // if list contains deposited key - return { duplicates: duplicatesForDepositedKeys(), unresolved: [] }; - } else { - // if list contain multiple operators and doesn't contain deposited keys - return await duplicatesAcrossOperators(); - } - }; + const suspectedDuplicateKeyGroups = this.getDuplicateKeyGroups(keys); const result = await Promise.all( - duplicatedKeys.map(getDuplicatedAndUnresolvedKeys), + suspectedDuplicateKeyGroups.map(([key, suspectedDuplicateKeys]) => + this.processDuplicateKeyGroup(key, suspectedDuplicateKeys, blockData), + ), ); const duplicates = result.flatMap(({ duplicates }) => duplicates); @@ -77,35 +51,107 @@ export class KeysDuplicationCheckerService { return { duplicates, unresolved }; } - public findDuplicateKeys(keys: RegistryKey[]): [string, RegistryKey[]][] { - const keyOccurrencesMap = keys.reduce((acc, key) => { - const occurrences = acc.get(key.key) || []; - occurrences.push(key); - acc.set(key.key, occurrences); + /** + * Groups keys by their pubkey and returns a list of those with duplicates. + * + * This method iterates over the provided keys and groups them by their unique pubkey. + * It then filters out any groups that do not have duplicates, returning only the groups + * that contain more than one instance of the pubkey. + * + * @param keys - An array of `RegistryKey` objects to be checked for duplicates. + * @returns An array of tuples where each tuple contains a pubkey string and an array of + * `RegistryKey` objects that share that pubkey. Only keys with duplicates are included. + */ + public getDuplicateKeyGroups(keys: RegistryKey[]): [string, RegistryKey[]][] { + const keyMap = keys.reduce((acc, key) => { + const duplicateKeys = acc.get(key.key) || []; + duplicateKeys.push(key); + acc.set(key.key, duplicateKeys); return acc; }, new Map()); - return Array.from(keyOccurrencesMap.entries()).filter( - ([, occurrences]) => occurrences.length > 1, + return Array.from(keyMap.entries()).filter( + ([, duplicateKeys]) => duplicateKeys.length > 1, + ); + } + + private async processDuplicateKeyGroup( + key: string, + suspectedDuplicateKeys: RegistryKey[], + blockData: BlockData, + ): Promise<{ duplicates: RegistryKey[]; unresolved: RegistryKey[] }> { + const operators = this.getOperators(suspectedDuplicateKeys); + + if (operators.length === 1) { + return this.handleSingleOperatorDuplicates(suspectedDuplicateKeys); + } + + if (this.hasDepositedKey(suspectedDuplicateKeys)) { + return this.handleDepositedKeyDuplicates(suspectedDuplicateKeys); + } + + return await this.handleMultiOperatorDuplicates( + key, + suspectedDuplicateKeys, + operators, + blockData, ); } - private extractOperators(occurrences: RegistryKey[]): Set { - return new Set( - occurrences.map((key) => `${key.moduleAddress}-${key.operatorIndex}`), + private getOperators(keys: RegistryKey[]): string[] { + return [ + ...new Set( + keys.map((key) => `${key.moduleAddress}-${key.operatorIndex}`), + ), + ]; + } + + private handleSingleOperatorDuplicates( + suspectedDuplicateKeys: RegistryKey[], + ): { + duplicates: RegistryKey[]; + unresolved: RegistryKey[]; + } { + const duplicates = this.findDuplicatesWithinOperator( + suspectedDuplicateKeys, ); + return { duplicates, unresolved: [] }; + } + + private handleDepositedKeyDuplicates(suspectedDuplicateKeys: RegistryKey[]): { + duplicates: RegistryKey[]; + unresolved: RegistryKey[]; + } { + const duplicates = suspectedDuplicateKeys.filter((key) => !key.used); + return { duplicates, unresolved: [] }; + } + + private async handleMultiOperatorDuplicates( + key: string, + suspectedDuplicateKeys: RegistryKey[], + operators: string[], + blockData: BlockData, + ) { + const { duplicateKeys, unresolvedKeys } = + await this.getDuplicatesAcrossOperators( + key, + suspectedDuplicateKeys, + operators, + blockData, + ); + return { duplicates: duplicateKeys, unresolved: unresolvedKeys }; } private findDuplicatesWithinOperator( operatorKeys: RegistryKey[], ): RegistryKey[] { // Assuming keys belong to a single operator - const originalKey = this.findOriginalKeyWithinOperator(operatorKeys); - return operatorKeys.filter((key) => key.index !== originalKey.index); + const earliestKey = this.findEarliestKeyWithinOperator(operatorKeys); + return operatorKeys.filter((key) => key.index !== earliestKey.index); } - private findOriginalKeyWithinOperator( + private findEarliestKeyWithinOperator( operatorKeys: RegistryKey[], ): RegistryKey { return operatorKeys.reduce( @@ -114,44 +160,66 @@ export class KeysDuplicationCheckerService { ); } + private hasDepositedKey(keys: RegistryKey[]): boolean { + return keys.some((key) => key.used); + } + private async getDuplicatesAcrossOperators( key: string, - occurrences: RegistryKey[], - operators: Set, + suspectedDuplicateKeys: RegistryKey[], + operators: string[], blockData: BlockData, ) { const events = await this.fetchSigningKeyEvents(key, blockData); - const missingOperators = this.findMissingOperators(operators, events); + const operatorsWithoutEvents = this.getOperatorsWithoutEvents( + operators, + events, + ); - if (missingOperators.length) { + if (operatorsWithoutEvents.length) { this.logger.error('Missing events for operators', { - missingOperators, + operatorsWithoutEvents, currentBlockNumber: blockData.blockNumber, currentBlockHash: blockData.blockHash, }); - // Return the entire occurrence set as unresolved - return { duplicateKeys: [], missingEvents: occurrences }; + // Return the entire list of duplicates as unresolved + return { duplicateKeys: [], unresolvedKeys: suspectedDuplicateKeys }; } - const originalEvent = this.findOriginalEvent(events); - const originalKey = this.findOriginalKey(occurrences, originalEvent); - - this.logger.log('Original key is', { - ...{ - originalKey, - createBlockNumber: originalEvent.blockNumber, - createBlockHash: originalEvent.blockHash, - createLogIndex: originalEvent.logIndex, - }, - currentBlockNumber: blockData.blockNumber, - currentBlockHash: blockData.blockHash, - }); - const duplicateKeys = occurrences.filter( - (k) => !this.isSameKey(k, originalKey), + return this.handleEventsForDuplicates( + events, + suspectedDuplicateKeys, + blockData, ); + } + + private handleEventsForDuplicates( + events: SigningKeyEvent[], + suspectedDuplicateKeys: RegistryKey[], + blockData: BlockData, + ) { + const earliestEvents = this.findEarliestEvents(events); + + // have only one event + if (earliestEvents.length === 1) { + const earliestEvent = earliestEvents[0]; + + const duplicateKeys = this.filterNonEarliestKeys( + earliestEvent, + suspectedDuplicateKeys, + blockData, + ); + + return { duplicateKeys, unresolvedKeys: [] }; + } - return { duplicateKeys, missingEvents: [] }; + // If there are few events at the same block + // There can be an attempt to front-run the key submission transaction, + // in this case, it's difficult to determine who was first, + // therefore it is proposed to unvet the entire set of duplicates. + // If trying to look at the log index, then a malicious actor can make a back-run + return { duplicateKeys: suspectedDuplicateKeys, unresolvedKeys: [] }; } private async fetchSigningKeyEvents( @@ -167,37 +235,84 @@ export class KeysDuplicationCheckerService { return events; } - private findOriginalEvent(events: SigningKeyEvent[]): SigningKeyEvent { - return events.reduce( - (prev, curr) => - prev.blockNumber < curr.blockNumber || - (prev.blockNumber === curr.blockNumber && prev.logIndex < curr.logIndex) - ? prev - : curr, - events[0], + private getOperatorsWithoutEvents( + operators: string[], + events: SigningKeyEvent[], + ): string[] { + const eventOperators = new Set( + events.map((event) => `${event.moduleAddress}-${event.operatorIndex}`), ); + return operators.filter((op) => !eventOperators.has(op)); } - private findOriginalKey( - occurrences: RegistryKey[], - originalEvent: SigningKeyEvent, - ): RegistryKey { - const keyOwnerKeys = occurrences.filter( - (key) => - key.moduleAddress === originalEvent.moduleAddress && - key.operatorIndex === originalEvent.operatorIndex, + private filterNonEarliestKeys( + earliestEvent: SigningKeyEvent, + suspectedDuplicateKeys: RegistryKey[], + blockData: BlockData, + ) { + const operatorKeys = this.findOperatorKeys( + suspectedDuplicateKeys, + earliestEvent.moduleAddress, + earliestEvent.operatorIndex, + ); + + const earliestKey = this.findEarliestKeyWithinOperator(operatorKeys); + + this.logger.log('Earliest key is', { + ...{ + earliestKey, + createBlockNumber: earliestEvent.blockNumber, + createBlockHash: earliestEvent.blockHash, + }, + currentBlockNumber: blockData.blockNumber, + currentBlockHash: blockData.blockHash, + }); + return suspectedDuplicateKeys.filter( + (key) => !this.isSameKey(key, earliestKey), ); - return this.findOriginalKeyWithinOperator(keyOwnerKeys); } - private findMissingOperators( - operators: Set, - events: SigningKeyEvent[], - ): string[] { - const eventOperators = new Set( - events.map((event) => `${event.moduleAddress}-${event.operatorIndex}`), + private findEarliestEvents(events: SigningKeyEvent[]): SigningKeyEvent[] { + if (events.length <= 1) return events; + + const { blockEvents } = events.reduce( + ({ earliestBlockNumber, blockEvents }, currEvent) => { + if (earliestBlockNumber === currEvent.blockNumber) { + blockEvents.push(currEvent); + return { + earliestBlockNumber, + blockEvents, + }; + } + + if (earliestBlockNumber > currEvent.blockNumber) { + return { + earliestBlockNumber: currEvent.blockNumber, + blockEvents: [currEvent], + }; + } + + return { earliestBlockNumber, blockEvents }; + }, + { + earliestBlockNumber: events[0].blockNumber, + blockEvents: [], + } as { earliestBlockNumber: number; blockEvents: SigningKeyEvent[] }, + ); + + return blockEvents; + } + + private findOperatorKeys( + keys: RegistryKey[], + moduleAddress: string, + operatorIndex: number, + ): RegistryKey[] { + return keys.filter( + (key) => + key.moduleAddress === moduleAddress && + key.operatorIndex === operatorIndex, ); - return [...operators].filter((op) => !eventOperators.has(op)); } private isSameKey(key1: RegistryKey, key2: RegistryKey): boolean { diff --git a/src/guardian/duplicates/keys.fixtures.ts b/src/guardian/duplicates/keys.fixtures.ts index d23109ab..e045b0f9 100644 --- a/src/guardian/duplicates/keys.fixtures.ts +++ b/src/guardian/duplicates/keys.fixtures.ts @@ -1,5 +1,4 @@ import { SigningKeyEvent } from 'contracts/signing-key-events-cache/interfaces/event.interface'; -import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; export const keyMock1 = { key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', @@ -11,16 +10,6 @@ export const keyMock1 = { index: 52, }; -export const keyMock1Duplicate = { - key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', - depositSignature: - '0x8a77d9411781360cc107344a99f6660b206d2c708ae7fa35565b76ec661a0b86b6c78f5b5691d2cf469c27d0655dfc6311451a9e0501f3c19c6f7e35a770d1a908bfec7cba2e07339dc633b8b6626216ce76ec0fa48ee56aaaf2f9dc7ccb2fe2', - operatorIndex: 1, - used: false, - moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', - index: 53, -}; - export const keyMock2 = { key: '0xa9bfaa8207ee6c78644c079ffc91b6e5abcc5eede1b7a06abb8fb40e490a75ea269c178dd524b65185299d2bbd2eb7b2', depositSignature: @@ -31,12 +20,19 @@ export const keyMock2 = { index: 51, }; -export const keysMock: RegistryKey[] = [keyMock1, keyMock1Duplicate, keyMock2]; +export const eventMock1: SigningKeyEvent = { + operatorIndex: keyMock1.operatorIndex, + key: keyMock1.key, + moduleAddress: keyMock1.moduleAddress, + logIndex: 1, + blockNumber: 1, + blockHash: '0x', +}; -export const eventMock: SigningKeyEvent = { - operatorIndex: keyMock1Duplicate.operatorIndex, - key: keyMock1Duplicate.key, - moduleAddress: keyMock1Duplicate.moduleAddress, +export const eventMock2: SigningKeyEvent = { + operatorIndex: keyMock2.operatorIndex, + key: keyMock2.key, + moduleAddress: keyMock2.moduleAddress, logIndex: 1, blockNumber: 1, blockHash: '0x', diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index 5a0b67b6..02ef6f7d 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -31,10 +31,10 @@ export const setupTestingModule = async () => { const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); - jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); return moduleRef; }; From a2b9a41a46f2dac0aadb0fe5ce52bf7e6cc7b5b1 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Sun, 1 Sep 2024 21:05:38 +0400 Subject: [PATCH 21/94] fix: tests --- test/helpers/test-setup.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index 02ef6f7d..5a0b67b6 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -31,10 +31,10 @@ export const setupTestingModule = async () => { const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); - // jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); - // jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); - // jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); - // jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); return moduleRef; }; From 097af727dadd33a0bcc6ae2bc1a7d5792a76a225 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 2 Sep 2024 13:36:54 +0400 Subject: [PATCH 22/94] fix: small refactoring block-guard service --- .../block-guard/block-guard.service.ts | 35 ++----------------- src/guardian/guardian.service.spec.ts | 3 +- src/guardian/guardian.service.ts | 32 +++++++++++++++-- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/guardian/block-guard/block-guard.service.ts b/src/guardian/block-guard/block-guard.service.ts index e26a5a26..be9f6aee 100644 --- a/src/guardian/block-guard/block-guard.service.ts +++ b/src/guardian/block-guard/block-guard.service.ts @@ -18,8 +18,6 @@ import { StakingRouterService } from 'contracts/staking-router'; @Injectable() export class BlockGuardService { - protected lastProcessedStateMeta?: { blockHash: string; blockNumber: number }; - constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, @@ -39,34 +37,6 @@ export class BlockGuardService { private stakingModuleGuardService: StakingModuleGuardService, ) {} - public isNeedToProcessNewState(newMeta: { - blockHash: string; - blockNumber: number; - }) { - const lastMeta = this.lastProcessedStateMeta; - if (!lastMeta) return true; - if (lastMeta.blockNumber > newMeta.blockNumber) { - this.logger.error('Keys-api returns old state', newMeta); - return false; - } - const isSameBlock = lastMeta.blockHash !== newMeta.blockHash; - - if (!isSameBlock) { - this.logger.log(`The block has not changed since the last cycle. Exit`, { - newMeta, - }); - } - - return isSameBlock; - } - - public setLastProcessedStateMeta(newMeta: { - blockHash: string; - blockNumber: number; - }) { - this.lastProcessedStateMeta = newMeta; - } - /** * Collects data from contracts in one place and by block hash, * to reduce the probability of getting data from different blocks @@ -88,6 +58,7 @@ export class BlockGuardService { guardianIndex, lidoWC, securityVersion, + walletBalanceCritical, ] = await Promise.all([ this.depositService.getDepositRoot({ blockHash }), this.depositService.getAllDepositedEvents(blockNumber, blockHash), @@ -96,6 +67,7 @@ export class BlockGuardService { this.securityService.version({ blockHash, }), + this.walletService.isBalanceCritical(), ]); const theftHappened = @@ -116,9 +88,6 @@ export class BlockGuardService { }); } - const walletBalanceCritical = - await this.walletService.isBalanceCritical(); - return { blockNumber, blockHash, diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index 8cee7219..10880306 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -58,7 +58,6 @@ describe('GuardianService', () => { }).compile(); keysApiService = moduleRef.get(KeysApiService); - blockGuardService = moduleRef.get(BlockGuardService); repositoryService = moduleRef.get(RepositoryService); locatorService = moduleRef.get(LocatorService); @@ -104,7 +103,7 @@ describe('GuardianService', () => { })); const getBlockGuardServiceMock = jest - .spyOn(blockGuardService, 'isNeedToProcessNewState') + .spyOn(guardianService, 'isNeedToProcessNewState') .mockImplementation(() => false); // run concurrently and check that second attempt diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 5c3a5ff2..e993381e 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -175,7 +175,7 @@ export class GuardianService implements OnModuleInit { // contracts init await this.repositoryService.initCachedContracts({ blockHash }); - const isNewBlock = this.blockGuardService.isNeedToProcessNewState({ + const isNewBlock = this.isNeedToProcessNewState({ blockHash, blockNumber, }); @@ -285,7 +285,7 @@ export class GuardianService implements OnModuleInit { await this.handleDeposit(stakingModulesData, blockData); const { blockHash, blockNumber } = blockData; - this.blockGuardService.setLastProcessedStateMeta({ + this.setLastProcessedStateMeta({ blockHash, blockNumber, }); @@ -412,4 +412,32 @@ export class GuardianService implements OnModuleInit { stakingModuleData.isModuleDepositsPaused ); } + + public isNeedToProcessNewState(newMeta: { + blockHash: string; + blockNumber: number; + }) { + const lastMeta = this.lastProcessedStateMeta; + if (!lastMeta) return true; + if (lastMeta.blockNumber > newMeta.blockNumber) { + this.logger.error('Keys-api returns old state', newMeta); + return false; + } + const isSameBlock = lastMeta.blockHash !== newMeta.blockHash; + + if (!isSameBlock) { + this.logger.log(`The block has not changed since the last cycle. Exit`, { + newMeta, + }); + } + + return isSameBlock; + } + + public setLastProcessedStateMeta(newMeta: { + blockHash: string; + blockNumber: number; + }) { + this.lastProcessedStateMeta = newMeta; + } } From eb75294e928f9eb8906cbac8cd885077eb4533ac Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:20:39 +0200 Subject: [PATCH 23/94] feat: new deposit service --- .../deposits-registry.service.ts | 138 ++++++++++-------- .../interfaces/event.interface.ts | 2 +- .../blockchain-checker.service.ts | 34 +---- .../deposit-tree/deposit-tree.spec.ts | 28 ++-- .../deposit-tree/deposit-tree.ts | 2 +- .../integrity-checker.service.ts | 2 +- .../sanity-checker/sanity-checker.service.ts | 135 +++++++++++++++++ 7 files changed, 234 insertions(+), 107 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index fdb0074b..ebeeb9b9 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -14,10 +14,9 @@ import { } from './interfaces'; import { RepositoryService } from 'contracts/repository'; import { BlockTag } from 'provider'; -import { BlsService } from 'bls'; -import { DepositIntegrityCheckerService } from './integrity-checker'; -import { LevelDBService } from './leveldb'; -import { DepositCacheIntegrityError } from './integrity-checker/constants'; +import { DepositsRegistryStoreService } from './store'; +import { DepositsRegistryFetcherService } from './fetcher/fetcher.service'; +import { DepositRegistrySanityCheckerService } from './sanity-checker/sanity-checker.service'; @Injectable() export class DepositService { @@ -26,9 +25,9 @@ export class DepositService { private providerService: ProviderService, private repositoryService: RepositoryService, - private blsService: BlsService, - private depositIntegrityCheckerService: DepositIntegrityCheckerService, - private levelDBCacheService: LevelDBService, + private sanityChecker: DepositRegistrySanityCheckerService, + private fetcher: DepositsRegistryFetcherService, + private store: DepositsRegistryStoreService, ) {} public async handleNewBlock(blockNumber: number): Promise { @@ -36,39 +35,16 @@ export class DepositService { // The event cache is stored with an N block lag to avoid caching data from uncle blocks // so we don't worry about blockHash here - const toBlockNumber = await this.updateEventsCache(); - await this.checkDepositCacheIntegrity(toBlockNumber); + await this.updateEventsCache(); } - public async initialize(blockNumber: number) { - await this.levelDBCacheService.initialize(); - - const cachedEvents = await this.levelDBCacheService.getEventsCache(); - const isCacheValid = this.validateCache(cachedEvents, blockNumber); - - if (!isCacheValid) { - process.exit(1); - } - await this.depositIntegrityCheckerService.initialize(cachedEvents); + public async initialize() { + await this.store.initialize(); + const cachedEvents = await this.store.getEventsCache(); + await this.sanityChecker.initialize(cachedEvents); // it is necessary to load fresh events before integrity check // because we can only compare roots of the last 128 blocks. - const toBlockNumber = await this.updateEventsCache(); - await this.checkDepositCacheIntegrity(toBlockNumber); - } - - public async checkDepositCacheIntegrity(toBlockNumber: number) { - try { - await this.depositIntegrityCheckerService.checkFinalizedRoot( - toBlockNumber, - ); - } catch (error) { - if (error instanceof DepositCacheIntegrityError) { - return this.logger.error( - `Deposit event cache integrity error on block number: ${toBlockNumber}`, - ); - } - throw error; - } + await this.updateEventsCache(); } /** @@ -85,8 +61,7 @@ export class DepositService { * @returns event group */ public async getCachedEvents(): Promise { - const { headers, ...rest } = - await this.levelDBCacheService.getEventsCache(); + const { headers, ...rest } = await this.store.getEventsCache(); const deploymentBlock = await this.getDeploymentBlockByNetwork(); return { @@ -105,8 +80,8 @@ export class DepositService { public async setCachedEvents( cachedEvents: VerifiedDepositEventsCache, ): Promise { - await this.levelDBCacheService.deleteCache(); - await this.levelDBCacheService.insertEventsCacheBatch({ + await this.store.deleteCache(); + await this.store.insertEventsCacheBatch({ ...cachedEvents, headers: { ...cachedEvents.headers, @@ -118,20 +93,29 @@ export class DepositService { * Updates the cache deposited events * The last N blocks are not stored, in order to avoid storing reorganized blocks */ - public async updateEventsCache(): Promise { + public async updateEventsCache(): Promise { const fetchTimeStart = performance.now(); const [currentBlock, initialCache] = await Promise.all([ - this.providerService.getBlockNumber(), + this.providerService.getBlock(), this.getCachedEvents(), ]); + const { number: currentBlockNumber, hash: currentBlockHash } = currentBlock; const firstNotCachedBlock = initialCache.headers.endBlock + 1; - const toBlock = currentBlock - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS; + const toBlock = currentBlockNumber - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS; const totalEventsCount = initialCache.data.length; let newEventsCount = 0; + // verify blockchain + const isCacheValid = this.sanityChecker.verifyCacheBlock( + initialCache, + currentBlockNumber, + ); + + if (!isCacheValid) return; + for ( let block = firstNotCachedBlock; block <= toBlock; @@ -140,12 +124,18 @@ export class DepositService { const chunkStartBlock = block; const chunkToBlock = Math.min(toBlock, block + DEPOSIT_EVENTS_STEP - 1); - const chunkEventGroup = await this.fetchEventsFallOver( + const chunkEventGroup = await this.fetcher.fetchEventsFallOver( chunkStartBlock, chunkToBlock, ); - await this.levelDBCacheService.insertEventsCacheBatch({ + await this.sanityChecker.verifyEventsChunk( + currentBlockNumber, + currentBlockHash, + chunkEventGroup.events, + ); + + await this.store.insertEventsCacheBatch({ headers: { ...initialCache.headers, endBlock: chunkEventGroup.endBlock, @@ -153,10 +143,6 @@ export class DepositService { data: chunkEventGroup.events, }); - await this.depositIntegrityCheckerService.putFinalizedEvents( - chunkEventGroup.events, - ); - newEventsCount += chunkEventGroup.events.length; this.logger.log('Historical events are fetched', { @@ -170,13 +156,22 @@ export class DepositService { const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; // TODO: replace timer with metric + const isRootValid = await this.sanityChecker.verifyUpdatedEvents( + currentBlockNumber, + ); + + if (!isRootValid) { + this.logger.error('Integrity check failed on block', { + currentBlock, + currentBlockHash, + }); + } + this.logger.log('Deposit events cache is updated', { newEventsCount, totalEventsCount: totalEventsCount + newEventsCount, fetchTime, }); - - return toBlock; } /** @@ -188,13 +183,23 @@ export class DepositService { ): Promise { const endBlock = blockNumber; const cachedEvents = await this.getCachedEvents(); - //!!!! - // TODO: To make changes. We will now give validation status, verification status to the reorganisation. - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - if (!isCacheValid) process.exit(1); + + const isCacheValid = this.sanityChecker.verifyCacheBlock( + cachedEvents, + blockNumber, + ); + + if (!isCacheValid) { + return { + events: cachedEvents.data, + startBlock: cachedEvents.headers.startBlock, + endBlock, + isValid: false, + }; + } const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; - const freshEventGroup = await this.fetchEventsFallOver( + const freshEventGroup = await this.fetcher.fetchEventsFallOver( firstNotCachedBlock, endBlock, ); @@ -202,7 +207,18 @@ export class DepositService { const lastEvent = freshEvents[freshEvents.length - 1]; const lastEventBlockHash = lastEvent?.blockHash; - this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); + const isValid = await this.sanityChecker.verifyFreshEvents( + blockNumber, + blockHash, + freshEvents, + ); + + if (!isValid) { + this.logger.warn('Integrity check failed on block', { + blockNumber, + blockHash, + }); + } this.logger.debug?.('Fresh deposit events are fetched', { events: freshEvents.length, @@ -218,13 +234,7 @@ export class DepositService { events: mergedEvents, startBlock: cachedEvents.headers.startBlock, endBlock, - // declare a separate method where we store the latest events in the closure - checkRoot: async () => { - await this.depositIntegrityCheckerService.checkLatestRoot( - blockNumber, - freshEvents, - ); - }, + isValid, }; } /** diff --git a/src/contracts/deposits-registry/interfaces/event.interface.ts b/src/contracts/deposits-registry/interfaces/event.interface.ts index 45da4f73..a7322cab 100644 --- a/src/contracts/deposits-registry/interfaces/event.interface.ts +++ b/src/contracts/deposits-registry/interfaces/event.interface.ts @@ -27,5 +27,5 @@ export interface VerifiedDepositEventGroup extends DepositEventGroup { } export interface VerifiedDepositedEventGroup extends VerifiedDepositEventGroup { - checkRoot(): Promise; + isValid: boolean; } diff --git a/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts index fd0cfa23..5f2d4706 100644 --- a/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.service.ts @@ -20,23 +20,6 @@ export class BlockchainCheckerService { ): boolean { const isCacheValid = currentBlock >= cachedEvents.headers.endBlock; - const blocks = { - cachedStartBlock: cachedEvents.headers.startBlock, - cachedEndBlock: cachedEvents.headers.endBlock, - currentBlock, - }; - - if (isCacheValid) { - this.logger.log('Deposit events cache has valid age', blocks); - } - - if (!isCacheValid) { - this.logger.warn( - 'Deposit events cache is newer than the current block', - blocks, - ); - } - return isCacheValid; } @@ -44,17 +27,16 @@ export class BlockchainCheckerService { * Checks events block hash * An additional check to avoid events processing in an alternate chain */ - public checkEventsBlockHash( + public findReorganizedEvent( events: DepositEvent[], blockNumber: number, blockHash: string, - ): void { - events.forEach((event) => { - if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { - throw new Error( - 'Blockhash of the received events does not match the current blockhash', - ); - } - }); + ): DepositEvent | null { + return ( + events.find( + (event) => + event.blockNumber === blockNumber && event.blockHash !== blockHash, + ) || null + ); } } diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index 2b7d45a8..d09f9ab9 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -23,7 +23,7 @@ describe('DepositTree', () => { test('should correctly insert a node and update the tree', () => { const initialNodeCount = depositTree.nodeCount; const node = new Uint8Array(32).fill(1); // Example node hash - depositTree.insertNode(node); + depositTree.insert(node); expect(depositTree.nodeCount).toBe(initialNodeCount + 1); }); @@ -35,13 +35,13 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - originalTree.insertNode(DepositTree.formDepositNode(nodeData)); + originalTree.insert(DepositTree.formDepositNode(nodeData)); expect(originalTree.nodeCount).toBe(1); const oldDepositRoot = originalTree.getRoot(); const cloned = originalTree.clone(); - cloned.insertNode( + cloned.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), ); @@ -51,8 +51,8 @@ describe('DepositTree', () => { const freshTree = new DepositTree(); - freshTree.insertNode(DepositTree.formDepositNode(nodeData)); - freshTree.insertNode( + freshTree.insert(DepositTree.formDepositNode(nodeData)); + freshTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), ); @@ -68,10 +68,10 @@ describe('DepositTree', () => { amount: '0x0100000000000000', }; - originalTree.insertNode( + originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), ); - originalTree.insertNode( + originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), ); @@ -90,12 +90,12 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - depositTree.insertNode(DepositTree.formDepositNode(nodeData)); + depositTree.insert(DepositTree.formDepositNode(nodeData)); expect(depositTree.nodeCount).toBe(1); }); test('should clone the tree correctly', () => { - depositTree.insertNode(new Uint8Array(32).fill(1)); + depositTree.insert(new Uint8Array(32).fill(1)); const clonedTree = depositTree.clone(); expect(clonedTree.nodeCount).toEqual(depositTree.nodeCount); expect(clonedTree.branch).toEqual(depositTree.branch); @@ -104,12 +104,12 @@ describe('DepositTree', () => { test('branch updates correctly after multiple insertions', () => { const node1 = new Uint8Array(32).fill(1); // First example node - depositTree.insertNode(node1); // First insertion + depositTree.insert(node1); // First insertion expect(depositTree.branch[0]).toEqual(node1); const node2 = new Uint8Array(32).fill(2); // Second example node - depositTree.insertNode(node2); // Second insertion + depositTree.insert(node2); // Second insertion // Now, we need to check the second level of the branch // This should use the same hashing function as used in your actual code @@ -146,7 +146,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev) => - depositTree.insertNode(fromHexString(ev)), + depositTree.insert(fromHexString(ev)), ); expect(depositTree.nodeCount).toEqual( @@ -157,7 +157,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev) => - depositTree.insertNode(fromHexString(ev)), + depositTree.insert(fromHexString(ev)), ); expect(depositTree.nodeCount).toEqual( @@ -166,7 +166,7 @@ describe('DepositTree', () => { expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); depositDataRootsFixture20k.events.map((ev) => - depositTree.insertNode(fromHexString(ev)), + depositTree.insert(fromHexString(ev)), ); expect(depositTree.nodeCount).toEqual( depositDataRootsFixture10k.events.length + diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index 4ba11b74..ed40061f 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -75,7 +75,7 @@ export class DepositTree { * Inserts a new node into the tree using already computed node hash. * @param {Uint8Array} node - The node's hash to be inserted. */ - public insertNode(node: Uint8Array) { + public insert(node: Uint8Array) { this.nodeCount++; this.formBranch(node, this.nodeCount); } diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 4b40e973..6feb08c3 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -111,7 +111,7 @@ export class DepositIntegrityCheckerService { eventsCache: VerifiedDepositEvent[], ) { for (const [index, event] of eventsCache.entries()) { - tree.insertNode(event.depositDataRoot); + tree.insert(event.depositDataRoot); if (index % DEPOSIT_TREE_STEP_SYNC === 0) { await new Promise((res) => setTimeout(res, 1)); diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index e69de29b..94989871 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -0,0 +1,135 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { + VerifiedDepositEvent, + VerifiedDepositEventsCache, +} from '../interfaces'; +import { BlockchainCheckerService } from './blockchain-checker/blockchain-checker.service'; +import { DepositIntegrityCheckerService } from './integrity-checker'; + +@Injectable() +export class DepositRegistrySanityCheckerService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private blockchainSanityChecker: BlockchainCheckerService, + private depositsIntegrityChecker: DepositIntegrityCheckerService, + ) {} + + public async initialize(initialEventsCache: VerifiedDepositEventsCache) { + await this.depositsIntegrityChecker.initialize(initialEventsCache); + } + + private async indexEventsChunk(events: VerifiedDepositEvent[]) { + return await this.depositsIntegrityChecker.putFinalizedEvents(events); + } + // putLatestEvents + private async checkFreshEventsChunk( + blockNumber: number, + events: VerifiedDepositEvent[], + ) { + return await this.depositsIntegrityChecker.checkLatestRoot( + blockNumber, + events, + ); + } + + private findReorganization( + blockNumber: number, + blockHash: string, + events: VerifiedDepositEvent[], + ) { + const event = this.blockchainSanityChecker.findReorganizedEvent( + events, + blockNumber, + blockHash, + ); + + if (event) { + this.logger.error('Reorganization found in deposit event', { + blockHash: event.blockHash, + blockNumber: event.blockNumber, + depositDataRoot: event.depositDataRoot, + }); + return true; + } + return false; + } + + public verifyCacheBlock( + cachedEvents: VerifiedDepositEventsCache, + currentBlock: number, + ) { + const isCacheValid = this.blockchainSanityChecker.validateCacheBlock( + cachedEvents, + currentBlock, + ); + + const blocks = { + cachedStartBlock: cachedEvents.headers.startBlock, + cachedEndBlock: cachedEvents.headers.endBlock, + currentBlock, + }; + + if (isCacheValid) { + this.logger.log('Deposit events cache has valid age', blocks); + } + + if (!isCacheValid) { + this.logger.error( + 'Deposit events cache is newer than the current block', + blocks, + ); + } + + return isCacheValid; + } + + public async verifyEventsChunk( + blockNumber: number, + blockHash: string, + events: VerifiedDepositEvent[], + ) { + const isReorgFound = this.findReorganization( + blockNumber, + blockHash, + events, + ); + + if (isReorgFound) return false; + + const tree = await this.indexEventsChunk(events); + + this.logger.log('Deposit events chunk was verified', { + blockNumber, + blockHash, + depositRoot: tree.getRoot(), + }); + + return true; + } + + public async verifyFreshEvents( + blockNumber: number, + blockHash: string, + events: VerifiedDepositEvent[], + ) { + const isReorgFound = this.findReorganization( + blockNumber, + blockHash, + events, + ); + + if (isReorgFound) return false; + + const isDepositRootMatches = await this.checkFreshEventsChunk( + blockNumber, + events, + ); + + return isDepositRootMatches; + } + + public async verifyUpdatedEvents(blockNumber: number) { + return this.depositsIntegrityChecker.checkFinalizedRoot(blockNumber); + } +} From 8b7b4cb9e5e924bc033903ba692bfd8126a7a685 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:35:03 +0200 Subject: [PATCH 24/94] refactor: deposit registry --- .../deposit-registry.constants.ts | 13 +++++++++ .../deposits-registry.service.ts | 29 ++----------------- .../fetcher/fetcher.service.ts | 13 +++++++++ .../deposits-registry/store/store.service.ts | 16 ++++++++++ 4 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 src/contracts/deposits-registry/deposit-registry.constants.ts diff --git a/src/contracts/deposits-registry/deposit-registry.constants.ts b/src/contracts/deposits-registry/deposit-registry.constants.ts new file mode 100644 index 00000000..11e67787 --- /dev/null +++ b/src/contracts/deposits-registry/deposit-registry.constants.ts @@ -0,0 +1,13 @@ +import { CHAINS } from '@lido-sdk/constants'; + +export const DEPLOYMENT_BLOCK_NETWORK: { + [key in CHAINS]?: number; +} = { + [CHAINS.Mainnet]: 11052984, + [CHAINS.Goerli]: 4367322, + [CHAINS.Holesky]: 0, +}; + +export const DEPOSIT_EVENTS_CACHE_LAG_BLOCKS = 100; +export const DEPOSIT_EVENTS_STEP = 10_000; +export const DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index ebeeb9b9..70d72195 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -4,10 +4,9 @@ import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; import { DEPOSIT_EVENTS_STEP, - getDeploymentBlockByNetwork, DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE, DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, -} from './deposit.constants'; +} from './deposit-registry.constants'; import { VerifiedDepositEventsCache, VerifiedDepositedEventGroup, @@ -47,22 +46,13 @@ export class DepositService { await this.updateEventsCache(); } - /** - * Returns a block number when the deposited contract was deployed - * @returns block number - */ - public async getDeploymentBlockByNetwork(): Promise { - const chainId = await this.providerService.getChainId(); - return getDeploymentBlockByNetwork(chainId); - } - /** * Gets node operators data from cache * @returns event group */ public async getCachedEvents(): Promise { const { headers, ...rest } = await this.store.getEventsCache(); - const deploymentBlock = await this.getDeploymentBlockByNetwork(); + const deploymentBlock = await this.fetcher.getDeploymentBlockByNetwork(); return { headers: { @@ -74,21 +64,6 @@ export class DepositService { }; } - /** - * Saves deposited events to cache - */ - public async setCachedEvents( - cachedEvents: VerifiedDepositEventsCache, - ): Promise { - await this.store.deleteCache(); - await this.store.insertEventsCacheBatch({ - ...cachedEvents, - headers: { - ...cachedEvents.headers, - }, - }); - } - /** * Updates the cache deposited events * The last N blocks are not stored, in order to avoid storing reorganized blocks diff --git a/src/contracts/deposits-registry/fetcher/fetcher.service.ts b/src/contracts/deposits-registry/fetcher/fetcher.service.ts index 6376a5af..65c8be36 100644 --- a/src/contracts/deposits-registry/fetcher/fetcher.service.ts +++ b/src/contracts/deposits-registry/fetcher/fetcher.service.ts @@ -5,6 +5,7 @@ import { DepositEventEvent } from 'generated/DepositAbi'; import { ProviderService } from 'provider'; import { parseLittleEndian64 } from '../crypto'; +import { DEPLOYMENT_BLOCK_NETWORK } from '../deposit-registry.constants'; import { DepositEvent, VerifiedDepositEventGroup } from '../interfaces'; import { DepositTree } from '../sanity-checker/integrity-checker/deposit-tree'; @@ -108,4 +109,16 @@ export class DepositsRegistryFetcherService { const { pubkey, wc, amount, signature } = depositEvent; return this.blsService.verify({ pubkey, wc, amount, signature }); } + + /** + * Returns a block number when the deposited contract was deployed + * @returns block number + */ + public async getDeploymentBlockByNetwork(): Promise { + const chainId = await this.providerService.getChainId(); + const address = DEPLOYMENT_BLOCK_NETWORK[chainId]; + if (address == null) throw new Error(`Chain ${chainId} is not supported`); + + return address; + } } diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts index c272ef5f..b574aa82 100644 --- a/src/contracts/deposits-registry/store/store.service.ts +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -10,6 +10,7 @@ import { import { ProviderService } from 'provider'; import { VerifiedDepositEvent, + VerifiedDepositEventsCache, VerifiedDepositEventsCacheHeaders, } from '../interfaces'; @@ -187,4 +188,19 @@ export class DepositsRegistryStoreService { public async close(): Promise { await this.db.close(); } + + /** + * Saves deposited events to cache + */ + public async setCachedEvents( + cachedEvents: VerifiedDepositEventsCache, + ): Promise { + await this.deleteCache(); + await this.insertEventsCacheBatch({ + ...cachedEvents, + headers: { + ...cachedEvents.headers, + }, + }); + } } From 90ea139465dc0bec0284e0c95eb6f641ba5bfae4 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:36:18 +0200 Subject: [PATCH 25/94] refactor: rename deposit registry service --- src/contracts/deposits-registry/deposits-registry.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 70d72195..d6822252 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -18,7 +18,7 @@ import { DepositsRegistryFetcherService } from './fetcher/fetcher.service'; import { DepositRegistrySanityCheckerService } from './sanity-checker/sanity-checker.service'; @Injectable() -export class DepositService { +export class DepositRegistryService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private providerService: ProviderService, From ae4acd4d9562901fd5dd5c547f10a867009d1d4e Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:39:31 +0200 Subject: [PATCH 26/94] feat: add based module declaration --- .../deposit-registry.constants.ts | 8 ++++++++ .../deposits-registry/deposit.module.ts | 17 +++++++++++++++++ .../deposits-registry/fetcher/index.ts | 2 ++ 3 files changed, 27 insertions(+) create mode 100644 src/contracts/deposits-registry/deposit.module.ts create mode 100644 src/contracts/deposits-registry/fetcher/index.ts diff --git a/src/contracts/deposits-registry/deposit-registry.constants.ts b/src/contracts/deposits-registry/deposit-registry.constants.ts index 11e67787..e3db17f6 100644 --- a/src/contracts/deposits-registry/deposit-registry.constants.ts +++ b/src/contracts/deposits-registry/deposit-registry.constants.ts @@ -11,3 +11,11 @@ export const DEPLOYMENT_BLOCK_NETWORK: { export const DEPOSIT_EVENTS_CACHE_LAG_BLOCKS = 100; export const DEPOSIT_EVENTS_STEP = 10_000; export const DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; + +export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ + headers: { + startBlock: 0, + endBlock: 0, + }, + data: [], +}); diff --git a/src/contracts/deposits-registry/deposit.module.ts b/src/contracts/deposits-registry/deposit.module.ts new file mode 100644 index 00000000..aa2fc569 --- /dev/null +++ b/src/contracts/deposits-registry/deposit.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { SecurityModule } from 'contracts/security'; +import { DepositsRegistryStoreModule } from './store'; +import { DepositRegistryService } from './deposits-registry.service'; +import { DEPOSIT_CACHE_DEFAULT } from './deposit-registry.constants'; +import { DepositsRegistryFetcherModule } from './fetcher'; + +@Module({ + imports: [ + SecurityModule, + DepositsRegistryFetcherModule, + DepositsRegistryStoreModule.register(DEPOSIT_CACHE_DEFAULT), + ], + providers: [DepositRegistryService], + exports: [DepositRegistryService], +}) +export class DepositModule {} diff --git a/src/contracts/deposits-registry/fetcher/index.ts b/src/contracts/deposits-registry/fetcher/index.ts new file mode 100644 index 00000000..128136bc --- /dev/null +++ b/src/contracts/deposits-registry/fetcher/index.ts @@ -0,0 +1,2 @@ +export * from './fetcher.module'; +export * from './fetcher.service'; From 053ccec112e3fe2637f544c9f06379d43ccba9d8 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:45:31 +0200 Subject: [PATCH 27/94] feat: add module declarations --- ...{deposit.module.ts => deposits-registry.module.ts} | 2 ++ .../blockchain-checker/blockchain-checker.module.ts | 8 ++++++++ .../sanity-checker/blockchain-checker/index.ts | 2 ++ .../deposits-registry/sanity-checker/index.ts | 2 ++ .../sanity-checker/integrity-checker/index.ts | 1 + .../integrity-checker/integrity-checker.module.ts | 8 ++++++++ .../sanity-checker/sanity-checker.module.ts | 11 +++++++++++ 7 files changed, 34 insertions(+) rename src/contracts/deposits-registry/{deposit.module.ts => deposits-registry.module.ts} (84%) create mode 100644 src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.module.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/blockchain-checker/index.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/index.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.module.ts create mode 100644 src/contracts/deposits-registry/sanity-checker/sanity-checker.module.ts diff --git a/src/contracts/deposits-registry/deposit.module.ts b/src/contracts/deposits-registry/deposits-registry.module.ts similarity index 84% rename from src/contracts/deposits-registry/deposit.module.ts rename to src/contracts/deposits-registry/deposits-registry.module.ts index aa2fc569..e76e1c8f 100644 --- a/src/contracts/deposits-registry/deposit.module.ts +++ b/src/contracts/deposits-registry/deposits-registry.module.ts @@ -4,11 +4,13 @@ import { DepositsRegistryStoreModule } from './store'; import { DepositRegistryService } from './deposits-registry.service'; import { DEPOSIT_CACHE_DEFAULT } from './deposit-registry.constants'; import { DepositsRegistryFetcherModule } from './fetcher'; +import { DepositRegistrySanityCheckerModule } from './sanity-checker'; @Module({ imports: [ SecurityModule, DepositsRegistryFetcherModule, + DepositRegistrySanityCheckerModule, DepositsRegistryStoreModule.register(DEPOSIT_CACHE_DEFAULT), ], providers: [DepositRegistryService], diff --git a/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.module.ts b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.module.ts new file mode 100644 index 00000000..5bc33ab5 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/blockchain-checker.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { BlockchainCheckerService } from './blockchain-checker.service'; + +@Module({ + providers: [BlockchainCheckerService], + exports: [BlockchainCheckerService], +}) +export class BlockchainCheckerModule {} diff --git a/src/contracts/deposits-registry/sanity-checker/blockchain-checker/index.ts b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/index.ts new file mode 100644 index 00000000..30d286a5 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/blockchain-checker/index.ts @@ -0,0 +1,2 @@ +export * from './blockchain-checker.module'; +export * from './blockchain-checker.service'; diff --git a/src/contracts/deposits-registry/sanity-checker/index.ts b/src/contracts/deposits-registry/sanity-checker/index.ts new file mode 100644 index 00000000..04e24741 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/index.ts @@ -0,0 +1,2 @@ +export * from './sanity-checker.module'; +export * from './sanity-checker.service'; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts index f580e0ad..06b7860a 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/index.ts @@ -1 +1,2 @@ export * from './integrity-checker.service'; +export * from './integrity-checker.module'; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.module.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.module.ts new file mode 100644 index 00000000..cfa2c139 --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { DepositIntegrityCheckerService } from './integrity-checker.service'; + +@Module({ + providers: [DepositIntegrityCheckerService], + exports: [DepositIntegrityCheckerService], +}) +export class DepositIntegrityCheckerModule {} diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.module.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.module.ts new file mode 100644 index 00000000..a1f0ab8d --- /dev/null +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { BlockchainCheckerModule } from './blockchain-checker'; +import { DepositIntegrityCheckerModule } from './integrity-checker'; +import { DepositRegistrySanityCheckerService } from './sanity-checker.service'; + +@Module({ + imports: [BlockchainCheckerModule, DepositIntegrityCheckerModule], + providers: [DepositRegistrySanityCheckerService], + exports: [DepositRegistrySanityCheckerService], +}) +export class DepositRegistrySanityCheckerModule {} From 9abbf0ff1d678322b3df94dda71cf4a5264fd7c6 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:47:32 +0200 Subject: [PATCH 28/94] refactor: file structure --- ...it-registry.constants.ts => deposits-registry.constants.ts} | 0 src/contracts/deposits-registry/deposits-registry.module.ts | 2 +- src/contracts/deposits-registry/deposits-registry.service.ts | 2 +- src/contracts/deposits-registry/fetcher/fetcher.service.ts | 2 +- src/contracts/deposits-registry/index.ts | 3 +++ 5 files changed, 6 insertions(+), 3 deletions(-) rename src/contracts/deposits-registry/{deposit-registry.constants.ts => deposits-registry.constants.ts} (100%) create mode 100644 src/contracts/deposits-registry/index.ts diff --git a/src/contracts/deposits-registry/deposit-registry.constants.ts b/src/contracts/deposits-registry/deposits-registry.constants.ts similarity index 100% rename from src/contracts/deposits-registry/deposit-registry.constants.ts rename to src/contracts/deposits-registry/deposits-registry.constants.ts diff --git a/src/contracts/deposits-registry/deposits-registry.module.ts b/src/contracts/deposits-registry/deposits-registry.module.ts index e76e1c8f..0d448e69 100644 --- a/src/contracts/deposits-registry/deposits-registry.module.ts +++ b/src/contracts/deposits-registry/deposits-registry.module.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { SecurityModule } from 'contracts/security'; import { DepositsRegistryStoreModule } from './store'; import { DepositRegistryService } from './deposits-registry.service'; -import { DEPOSIT_CACHE_DEFAULT } from './deposit-registry.constants'; +import { DEPOSIT_CACHE_DEFAULT } from './deposits-registry.constants'; import { DepositsRegistryFetcherModule } from './fetcher'; import { DepositRegistrySanityCheckerModule } from './sanity-checker'; diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index d6822252..5be405e0 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -6,7 +6,7 @@ import { DEPOSIT_EVENTS_STEP, DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE, DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, -} from './deposit-registry.constants'; +} from './deposits-registry.constants'; import { VerifiedDepositEventsCache, VerifiedDepositedEventGroup, diff --git a/src/contracts/deposits-registry/fetcher/fetcher.service.ts b/src/contracts/deposits-registry/fetcher/fetcher.service.ts index 65c8be36..688f2edc 100644 --- a/src/contracts/deposits-registry/fetcher/fetcher.service.ts +++ b/src/contracts/deposits-registry/fetcher/fetcher.service.ts @@ -5,7 +5,7 @@ import { DepositEventEvent } from 'generated/DepositAbi'; import { ProviderService } from 'provider'; import { parseLittleEndian64 } from '../crypto'; -import { DEPLOYMENT_BLOCK_NETWORK } from '../deposit-registry.constants'; +import { DEPLOYMENT_BLOCK_NETWORK } from '../deposits-registry.constants'; import { DepositEvent, VerifiedDepositEventGroup } from '../interfaces'; import { DepositTree } from '../sanity-checker/integrity-checker/deposit-tree'; diff --git a/src/contracts/deposits-registry/index.ts b/src/contracts/deposits-registry/index.ts new file mode 100644 index 00000000..e573762e --- /dev/null +++ b/src/contracts/deposits-registry/index.ts @@ -0,0 +1,3 @@ +export * from './deposits-registry.module'; +export * from './deposits-registry.service'; +export * from './interfaces'; From df13574c5377bad272de35beb8c3e2fed8ce782c Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:52:54 +0200 Subject: [PATCH 29/94] refactor: remove old module --- .../deposit/deposit-tree/deposit-tree.spec.ts | 48 -- .../deposit/deposit-tree/deposit-tree.ts | 142 ----- src/contracts/deposit/deposit-tree/index.ts | 1 - src/contracts/deposit/deposit.constants.ts | 31 - src/contracts/deposit/deposit.module.ts | 18 - src/contracts/deposit/deposit.service.spec.ts | 562 ------------------ src/contracts/deposit/deposit.service.ts | 402 ------------- src/contracts/deposit/deposit.utils.ts | 20 - src/contracts/deposit/index.ts | 3 - .../deposit/integrity-checker/constants.ts | 2 - .../deposit/integrity-checker/index.ts | 1 - .../integrity-checker.service.ts | 145 ----- .../deposit/interfaces/cache.interface.ts | 11 - .../interfaces/deposit-tree.interface.ts | 6 - .../deposit/interfaces/event.interface.ts | 31 - src/contracts/deposit/interfaces/index.ts | 3 - src/contracts/deposit/leveldb/index.ts | 3 - .../deposit/leveldb/leveldb.constants.ts | 15 - .../deposit/leveldb/leveldb.fixtures.ts | 47 -- .../deposit/leveldb/leveldb.module.ts | 34 -- .../deposit/leveldb/leveldb.service.spec.ts | 51 -- .../deposit/leveldb/leveldb.service.ts | 187 ------ 22 files changed, 1763 deletions(-) delete mode 100644 src/contracts/deposit/deposit-tree/deposit-tree.spec.ts delete mode 100644 src/contracts/deposit/deposit-tree/deposit-tree.ts delete mode 100644 src/contracts/deposit/deposit-tree/index.ts delete mode 100644 src/contracts/deposit/deposit.constants.ts delete mode 100644 src/contracts/deposit/deposit.module.ts delete mode 100644 src/contracts/deposit/deposit.service.spec.ts delete mode 100644 src/contracts/deposit/deposit.service.ts delete mode 100644 src/contracts/deposit/deposit.utils.ts delete mode 100644 src/contracts/deposit/index.ts delete mode 100644 src/contracts/deposit/integrity-checker/constants.ts delete mode 100644 src/contracts/deposit/integrity-checker/index.ts delete mode 100644 src/contracts/deposit/integrity-checker/integrity-checker.service.ts delete mode 100644 src/contracts/deposit/interfaces/cache.interface.ts delete mode 100644 src/contracts/deposit/interfaces/deposit-tree.interface.ts delete mode 100644 src/contracts/deposit/interfaces/event.interface.ts delete mode 100644 src/contracts/deposit/interfaces/index.ts delete mode 100644 src/contracts/deposit/leveldb/index.ts delete mode 100644 src/contracts/deposit/leveldb/leveldb.constants.ts delete mode 100644 src/contracts/deposit/leveldb/leveldb.fixtures.ts delete mode 100644 src/contracts/deposit/leveldb/leveldb.module.ts delete mode 100644 src/contracts/deposit/leveldb/leveldb.service.spec.ts delete mode 100644 src/contracts/deposit/leveldb/leveldb.service.ts diff --git a/src/contracts/deposit/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposit/deposit-tree/deposit-tree.spec.ts deleted file mode 100644 index 97cacbcb..00000000 --- a/src/contracts/deposit/deposit-tree/deposit-tree.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { DepositTree } from './deposit-tree'; - -describe('DepositTree', () => { - let depositTree; - - beforeEach(() => { - depositTree = new DepositTree(); - }); - - test('should correctly initialize', () => { - expect(depositTree.nodeCount).toBe(0); - expect(depositTree.branch.length).toBe(0); - expect(depositTree.zeroHashes[0]).toEqual(DepositTree.ZERO_HASH); - }); - - test('insert should correctly modify the tree', () => { - depositTree.insert({ - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000001', // Little endian of 1 - signature: '0x55667788', - }); - - expect(depositTree.nodeCount).toBe(1); - - depositTree.insert({ - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000002', // Little endian of 2 - signature: '0x55667788', - }); - - expect(depositTree.nodeCount).toBe(2); - }); - - test('clone should create an exact copy of the tree', () => { - const nodeData = { - pubkey: '0xaabbccdd', - wc: '0x11223344', - amount: '0x0000000000000001', - signature: '0x55667788', - }; - depositTree.insert(nodeData); - const clonedTree = depositTree.clone(); - expect(clonedTree).toEqual(depositTree); - expect(clonedTree.getRoot()).toEqual(depositTree.getRoot()); - }); -}); diff --git a/src/contracts/deposit/deposit-tree/deposit-tree.ts b/src/contracts/deposit/deposit-tree/deposit-tree.ts deleted file mode 100644 index 7bd4405c..00000000 --- a/src/contracts/deposit/deposit-tree/deposit-tree.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { ethers } from 'ethers'; -import { digest2Bytes32 } from '@chainsafe/as-sha256'; -import { fromHexString } from '@chainsafe/ssz'; -import { parseLittleEndian64, toLittleEndian64 } from '../deposit.utils'; -import { DepositData } from 'bls/bls.containers'; -import { NodeData } from '../interfaces'; - -export class DepositTree { - static DEPOSIT_CONTRACT_TREE_DEPTH = 32; - static ZERO_HASH = fromHexString( - '0x0000000000000000000000000000000000000000000000000000000000000000', - ); - zeroHashes: Uint8Array[] = new Array(DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH); - branch: Uint8Array[] = []; - nodeCount = 0; - - constructor() { - this.formZeroHashes(); - } - - /** - * Initializes the zero hashes used in the tree. - */ - private formZeroHashes() { - this.zeroHashes[0] = DepositTree.ZERO_HASH; - for ( - let height = 0; - height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH - 1; - height++ - ) { - this.zeroHashes[height + 1] = digest2Bytes32( - this.zeroHashes[height], - this.zeroHashes[height], - ); - } - } - - /** - * Forms the branch of the tree needed to update the root when a new node is inserted. - * @param {Uint8Array} node - The node's data to be inserted. - * @param {number} depositCount - The sequential index of the deposit, representing the total deposits. - * @returns {Uint8Array[] | undefined} The updated branch of the tree after inserting the node. - */ - private formBranch( - node: Uint8Array, - depositCount: number, - ): Uint8Array[] | undefined { - let size = depositCount; - for ( - let height = 0; - height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; - height++ - ) { - if ((size & 1) == 1) { - this.branch[height] = node; - return this.branch; - } - - node = digest2Bytes32(this.branch[height], node); - - // Using size /= 2 is not a mistake. In JavaScript, when performing bitwise operations - // like & 1, floating-point numbers are implicitly converted to integers, discarding the fractional part. - // This ensures the algorithm works correctly and matches the logic of a Solidity smart contract. - // Solidity does not have floating-point numbers, and all division is performed as integer division, rounding down the result. - size /= 2; - } - } - - /** - * Inserts a new deposit into the tree using detailed node data. - * @param {NodeData} nodeData - The detailed data of the deposit to be inserted. - */ - public insert(nodeData: NodeData) { - const node = DepositTree.formDepositNode(nodeData); - this.nodeCount++; - this.formBranch(node, this.nodeCount); - } - - /** - * Inserts a new node into the tree using already computed node hash. - * @param {Uint8Array} node - The node's hash to be inserted. - */ - public insertNode(node: Uint8Array) { - this.nodeCount++; - this.formBranch(node, this.nodeCount); - } - - /** - * Computes and returns the root hash of the deposit tree. - * @returns {string} The computed root hash of the tree. - */ - public getRoot() { - let node = DepositTree.ZERO_HASH; - let size = this.nodeCount; - for ( - let height = 0; - height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; - height++ - ) { - if ((size & 1) == 1) { - node = digest2Bytes32(this.branch[height], node); - } else { - node = digest2Bytes32(node, this.zeroHashes[height]); - } - size /= 2; - } - const finalRoot = ethers.utils.soliditySha256( - ['bytes', 'bytes', 'bytes'], - [ - node, - toLittleEndian64(this.nodeCount), - '0x000000000000000000000000000000000000000000000000', - ], - ); - return finalRoot; - } - - /** - * Creates a clone of the current tree instance, copying the branch structure and node count. - * @returns {DepositTree} A new DepositTree instance with the same state. - */ - public clone() { - const tree = new DepositTree(); - tree.branch = [...this.branch]; - tree.nodeCount = this.nodeCount; - return tree; - } - - /** - * Forms the deposit node from the given NodeData structure. - * @param {NodeData} nodeData - Detailed data of the deposit, including public key, withdrawal credentials, signature, and amount. - * @returns {Uint8Array} The hashed node as a Uint8Array. - */ - static formDepositNode(nodeData: NodeData): Uint8Array { - return DepositData.hashTreeRoot({ - withdrawalCredentials: fromHexString(nodeData.wc), - pubkey: fromHexString(nodeData.pubkey), - signature: fromHexString(nodeData.signature), - amount: parseLittleEndian64(nodeData.amount), - }); - } -} diff --git a/src/contracts/deposit/deposit-tree/index.ts b/src/contracts/deposit/deposit-tree/index.ts deleted file mode 100644 index 943e7a74..00000000 --- a/src/contracts/deposit/deposit-tree/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './deposit-tree'; diff --git a/src/contracts/deposit/deposit.constants.ts b/src/contracts/deposit/deposit.constants.ts deleted file mode 100644 index 3620efba..00000000 --- a/src/contracts/deposit/deposit.constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CHAINS } from '@lido-sdk/constants'; - -export const DEPLOYMENT_BLOCK_NETWORK: { - [key in CHAINS]?: number; -} = { - [CHAINS.Mainnet]: 11052984, - [CHAINS.Goerli]: 4367322, - [CHAINS.Holesky]: 0, -}; - -export const getDeploymentBlockByNetwork = (chainId: CHAINS): number => { - const address = DEPLOYMENT_BLOCK_NETWORK[chainId]; - if (address == null) throw new Error(`Chain ${chainId} is not supported`); - - return address; -}; - -export const DEPOSIT_EVENTS_CACHE_LAG_BLOCKS = 100; -export const DEPOSIT_EVENTS_STEP = 10_000; -export const DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; - -export const DEPOSIT_CACHE_FILE_NAME = 'deposit.events.json'; -export const DEPOSIT_CACHE_BATCH_SIZE = 100_000; - -export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ - headers: { - startBlock: 0, - endBlock: 0, - }, - data: [], -}); diff --git a/src/contracts/deposit/deposit.module.ts b/src/contracts/deposit/deposit.module.ts deleted file mode 100644 index 96e5a19b..00000000 --- a/src/contracts/deposit/deposit.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SecurityModule } from 'contracts/security'; -import { LevelDBModule } from './leveldb'; -import { BlsModule } from 'bls'; -import { DepositService } from './deposit.service'; -import { DEPOSIT_CACHE_DEFAULT } from './deposit.constants'; -import { DepositIntegrityCheckerService } from './integrity-checker'; - -@Module({ - imports: [ - BlsModule, - SecurityModule, - LevelDBModule.register(DEPOSIT_CACHE_DEFAULT), - ], - providers: [DepositService, DepositIntegrityCheckerService], - exports: [DepositService], -}) -export class DepositModule {} diff --git a/src/contracts/deposit/deposit.service.spec.ts b/src/contracts/deposit/deposit.service.spec.ts deleted file mode 100644 index 53565a16..00000000 --- a/src/contracts/deposit/deposit.service.spec.ts +++ /dev/null @@ -1,562 +0,0 @@ -jest.mock('utils/sleep'); - -import { CHAINS } from '@lido-sdk/constants'; -import { Test } from '@nestjs/testing'; -import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { Interface } from '@ethersproject/abi'; -import { LoggerService } from '@nestjs/common'; -import { getNetwork } from '@ethersproject/networks'; -import { sleep } from 'utils'; -import { LevelDBService } from './leveldb'; -import { - ERRORS_LIMIT_EXCEEDED, - MockProviderModule, - ProviderService, -} from 'provider'; -import { DepositAbi__factory } from 'generated'; -import { RepositoryModule, RepositoryService } from 'contracts/repository'; - -import { DepositModule } from './deposit.module'; -import { DepositService } from './deposit.service'; -import { PrometheusModule } from 'common/prometheus'; -import { LoggerModule } from 'common/logger'; -import { ConfigModule } from 'common/config'; -import { BlsService } from 'bls'; -import { LocatorService } from 'contracts/repository/locator/locator.service'; -import { mockLocator } from 'contracts/repository/locator/locator.mock'; -import { mockRepository } from 'contracts/repository/repository.mock'; -import { DepositTree } from './deposit-tree'; - -const mockSleep = sleep as jest.MockedFunction; - -describe('DepositService', () => { - let providerService: ProviderService; - let cacheService: LevelDBService; - let depositService: DepositService; - let loggerService: LoggerService; - let repositoryService: RepositoryService; - let blsService: BlsService; - let locatorService: LocatorService; - - const depositAddress = '0x' + '1'.repeat(40); - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot(), - MockProviderModule.forRoot(), - DepositModule, - PrometheusModule, - LoggerModule, - RepositoryModule, - ], - }).compile(); - - providerService = moduleRef.get(ProviderService); - cacheService = moduleRef.get(LevelDBService); - depositService = moduleRef.get(DepositService); - repositoryService = moduleRef.get(RepositoryService); - blsService = moduleRef.get(BlsService); - loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); - - locatorService = moduleRef.get(LocatorService); - - jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); - - mockLocator(locatorService); - await mockRepository(repositoryService); - - jest - .spyOn(repositoryService, 'getDepositAddress') - .mockImplementation(async () => depositAddress); - }); - - describe('getDeploymentBlockByNetwork', () => { - it('should return block number for goerli', async () => { - jest - .spyOn(providerService.provider, 'detectNetwork') - .mockImplementation(async () => getNetwork(CHAINS.Goerli)); - - const blockNumber = await depositService.getDeploymentBlockByNetwork(); - expect(typeof blockNumber).toBe('number'); - expect(blockNumber).toBeGreaterThan(0); - }); - - it('should return block number for mainnet', async () => { - jest - .spyOn(providerService.provider, 'detectNetwork') - .mockImplementation(async () => getNetwork(CHAINS.Mainnet)); - - const blockNumber = await depositService.getDeploymentBlockByNetwork(); - expect(typeof blockNumber).toBe('number'); - expect(blockNumber).toBeGreaterThan(0); - }); - }); - - describe('deposit cache', () => { - beforeEach(async () => { - await cacheService.initialize(); - }); - - afterEach(async () => { - await cacheService.deleteCache(); - await cacheService.close(); - }); - describe('getCachedEvents', () => { - const deploymentBlock = 100; - - beforeEach(async () => { - jest - .spyOn(depositService, 'getDeploymentBlockByNetwork') - .mockImplementation(async () => deploymentBlock); - }); - - it('should return events from cache', async () => { - const cache = { - data: [{} as any], - headers: { - startBlock: deploymentBlock, - endBlock: deploymentBlock + 100, - }, - }; - - const mockCache = jest - .spyOn(cacheService, 'getEventsCache') - .mockImplementation(async () => cache); - - const result = await depositService.getCachedEvents(); - - expect(mockCache).toBeCalledTimes(1); - expect(result).toEqual(cache); - }); - - it('should return deploymentBlock if cache is empty', async () => { - const cache = { - data: [{} as any], - headers: { - startBlock: 0, - endBlock: 0, - }, - }; - - const mockCache = jest - .spyOn(cacheService, 'getEventsCache') - .mockImplementation(async () => cache); - - const result = await depositService.getCachedEvents(); - - expect(mockCache).toBeCalledTimes(1); - expect(result.headers.startBlock).toBe(deploymentBlock); - expect(result.headers.endBlock).toBe(deploymentBlock); - }); - }); - - describe('setCachedEvents', () => { - it('should call setCache from the cacheService', async () => { - const eventGroup = {} as any; - - const mockSetCache = jest - .spyOn(cacheService, 'insertEventsCacheBatch') - .mockImplementation(async () => undefined); - - await depositService.setCachedEvents(eventGroup); - - expect(mockSetCache).toBeCalledTimes(1); - expect(mockSetCache).toBeCalledWith({ headers: {} }); - }); - }); - - describe('fetchEventsFallOver', () => { - it('should fetch events', async () => { - const expected = { - endBlock: 0, - events: [], - startBlock: 10, - }; - - const from = 0; - const to = 10; - - const mockFetchEvents = jest - .spyOn(depositService, 'fetchEvents') - .mockImplementation(async () => expected); - - const result = await depositService.fetchEventsFallOver(from, to); - - expect(mockFetchEvents).toBeCalledTimes(1); - expect(mockFetchEvents).toBeCalledWith(from, to); - expect(result).toEqual(expected); - }); - - it('should fetch recursive if limit exceeded', async () => { - const event1 = {} as any; - const event2 = {} as any; - const expectedFirst = { events: [event1], startBlock: 0, endBlock: 4 }; - const expectedSecond = { - events: [event2], - startBlock: 5, - endBlock: 10, - }; - - const startBlock = 0; - const endBlock = 10; - - const mockFetchEvents = jest - .spyOn(depositService, 'fetchEvents') - .mockImplementationOnce(async () => { - throw { error: { code: ERRORS_LIMIT_EXCEEDED[0] } }; - }) - .mockImplementationOnce(async () => expectedFirst) - .mockImplementationOnce(async () => expectedSecond); - - const result = await depositService.fetchEventsFallOver( - startBlock, - endBlock, - ); - - const { calls, results } = mockFetchEvents.mock; - const events = [event1, event2]; - - expect(result).toEqual({ events, startBlock, endBlock }); - expect(mockFetchEvents).toBeCalledTimes(3); - expect(calls[0]).toEqual([startBlock, endBlock]); - expect(calls[1]).toEqual([ - expectedFirst.startBlock, - expectedFirst.endBlock, - ]); - expect(calls[2]).toEqual([ - expectedSecond.startBlock, - expectedSecond.endBlock, - ]); - await expect(results[1].value).resolves.toEqual(expectedFirst); - await expect(results[2].value).resolves.toEqual(expectedSecond); - }); - - it('should retry if error is unknown', async () => { - const events = []; - const startBlock = 0; - const endBlock = 10; - const expected = { events, startBlock, endBlock }; - - mockSleep.mockImplementationOnce(async () => undefined); - - const mockFetchEvents = jest - .spyOn(depositService, 'fetchEvents') - .mockImplementationOnce(async () => { - throw new Error(); - }) - .mockImplementationOnce(async () => expected); - - const result = await depositService.fetchEventsFallOver( - startBlock, - endBlock, - ); - - const { calls, results } = mockFetchEvents.mock; - - expect(result).toEqual(expected); - expect(mockFetchEvents).toBeCalledTimes(2); - expect(calls[0]).toEqual([startBlock, endBlock]); - expect(calls[1]).toEqual([startBlock, endBlock]); - await expect(results[0].value).rejects.toThrow(); - await expect(results[1].value).resolves.toEqual(expected); - - expect(mockSleep).toBeCalledTimes(1); - expect(mockSleep).toBeCalledWith(expect.any(Number)); - }); - }); - - describe('fetchEvents', () => { - it('should fetch events', async () => { - const freshPubkeys = ['0x4321', '0x8765']; - const startBlock = 100; - const endBlock = 200; - - jest - .spyOn(providerService.provider, 'getBlockNumber') - .mockImplementation(async () => endBlock); - - jest.spyOn(blsService, 'verify').mockImplementation(() => true); - - const mockProviderCall = jest - .spyOn(providerService.provider, 'getLogs') - .mockImplementation(async () => { - const iface = new Interface(DepositAbi__factory.abi); - const eventFragment = iface.getEvent('DepositEvent'); - - return freshPubkeys.map((pubkey) => { - const args = [pubkey, '0x', '0x', '0x', 1]; - return iface.encodeEventLog(eventFragment, args) as any; - }); - }); - - const result = await depositService.fetchEvents(startBlock, endBlock); - expect(result).toEqual( - expect.objectContaining({ - startBlock, - endBlock, - events: freshPubkeys.map((pubkey) => - expect.objectContaining({ pubkey }), - ), - }), - ); - expect(mockProviderCall).toBeCalledTimes(1); - }); - }); - - describe('updateEventsCache', () => { - const cachedPubkeys = ['0x1234', '0x5678']; - const cache = { - headers: { - startBlock: 0, - endBlock: 2, - }, - data: cachedPubkeys.map((pubkey) => ({ pubkey } as any)), - }; - const currentBlock = 1000; - const firstNotCachedBlock = cache.headers.endBlock + 1; - - beforeEach(async () => { - jest - .spyOn(depositService, 'getCachedEvents') - .mockImplementation(async () => ({ ...cache })); - - jest - .spyOn(providerService, 'getBlockNumber') - .mockImplementation(async () => currentBlock); - }); - - it('should collect events', async () => { - const mockFetchEventsFallOver = jest - .spyOn(depositService, 'fetchEventsFallOver') - .mockImplementation(async (startBlock, endBlock) => ({ - startBlock, - endBlock, - events: [], - })); - - jest - .spyOn(depositService, 'setCachedEvents') - .mockImplementation(async () => undefined); - - await depositService.updateEventsCache(); - - expect(mockFetchEventsFallOver).toBeCalledTimes(1); - const { calls: fetchCalls } = mockFetchEventsFallOver.mock; - expect(fetchCalls[0][0]).toBe(firstNotCachedBlock); - expect(fetchCalls[0][1]).toBeLessThan(currentBlock); - }); - - it('should save events to the cache', async () => { - jest - .spyOn(depositService, 'fetchEventsFallOver') - .mockImplementation(async (startBlock, endBlock) => ({ - startBlock, - endBlock, - events: [], - })); - - const mockSetCachedEvents = jest - .spyOn(cacheService, 'insertEventsCacheBatch') - .mockImplementation(async () => undefined); - - await depositService.updateEventsCache(); - - expect(mockSetCachedEvents).toBeCalledTimes(1); - const { calls: cacheCalls } = mockSetCachedEvents.mock; - expect(cacheCalls[0][0].headers.startBlock).toBe( - cache.headers.startBlock, - ); - expect(cacheCalls[0][0].headers.endBlock).toBeLessThan(currentBlock); - }); - }); - - describe('getAllDepositedEvents', () => { - const cachedPubkeys = ['0x1234', '0x5678']; - const freshPubkeys = ['0x4321', '0x8765']; - const cachedEvents = { - headers: { - startBlock: 0, - endBlock: 2, - }, - data: cachedPubkeys.map((pubkey) => ({ pubkey } as any)), - }; - const currentBlock = 10; - const currentBlockHash = '0x12'; - const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; - - beforeEach(async () => { - jest - .spyOn(depositService, 'getCachedEvents') - .mockImplementation(async () => ({ ...cachedEvents })); - - jest - .spyOn(providerService, 'getBlockNumber') - .mockImplementation(async () => currentBlock); - }); - - it('should return cached events', async () => { - const tree = new DepositTree(); - - jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(DepositAbi__factory.abi); - return iface.encodeFunctionResult('get_deposit_root', [ - tree.getRoot(), - ]); - }); - const mockFetchEventsFallOver = jest - .spyOn(depositService, 'fetchEventsFallOver') - .mockImplementation(async () => ({ - startBlock: firstNotCachedBlock, - endBlock: currentBlock, - events: [], - })); - - const result = await depositService.getAllDepositedEvents( - currentBlock, - currentBlockHash, - ); - expect(result).toEqual({ - events: cachedEvents.data, - startBlock: cachedEvents.headers.startBlock, - endBlock: currentBlock, - checkRoot: expect.any(Function), - }); - - expect(mockFetchEventsFallOver).toBeCalledTimes(1); - expect(mockFetchEventsFallOver).toBeCalledWith( - firstNotCachedBlock, - currentBlock, - ); - }); - - it('should return merged pub keys', async () => { - const depositDataRoot = new Uint8Array([ - 185, 198, 196, 67, 108, 68, 92, 238, 17, 164, 72, 110, 30, 168, 28, - 57, 33, 93, 199, 57, 212, 165, 179, 74, 247, 55, 220, 97, 138, 135, - 59, 101, - ]); - - const events = freshPubkeys.map((pubkey) => ({ - pubkey, - depositDataRoot, - })); - - const mockFetchEventsFallOver = jest - .spyOn(depositService, 'fetchEventsFallOver') - .mockImplementation(async () => ({ - startBlock: firstNotCachedBlock, - endBlock: currentBlock, - events: events as any, - })); - - const tree = new DepositTree(); - events.map(({ depositDataRoot }) => { - tree.insertNode(depositDataRoot); - }); - - jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(DepositAbi__factory.abi); - return iface.encodeFunctionResult('get_deposit_root', [ - tree.getRoot(), - ]); - }); - - const result = await depositService.getAllDepositedEvents( - currentBlock, - currentBlockHash, - ); - - expect(result).toEqual({ - startBlock: cachedEvents.headers.startBlock, - endBlock: currentBlock, - events: cachedPubkeys - .map((pubkey) => ({ pubkey } as any)) - .concat( - freshPubkeys.map( - (pubkey) => ({ pubkey, depositDataRoot } as any), - ), - ), - checkRoot: expect.any(Function), - }); - - expect(mockFetchEventsFallOver).toBeCalledTimes(1); - expect(mockFetchEventsFallOver).toBeCalledWith( - firstNotCachedBlock, - currentBlock, - ); - }); - - it('should throw if event blockhash is different', async () => { - const anotherBlockHash = '0x34'; - - jest - .spyOn(depositService, 'fetchEventsFallOver') - .mockImplementation(async () => ({ - startBlock: firstNotCachedBlock, - endBlock: currentBlock, - events: freshPubkeys.map( - (pubkey) => - ({ - pubkey, - blockNumber: currentBlock, - blockHash: anotherBlockHash, - } as any), - ), - })); - - await expect( - depositService.getAllDepositedEvents(currentBlock, currentBlockHash), - ).rejects.toThrow(); - }); - }); - - describe('checkEventsBlockHash', () => { - const events = [ - { blockNumber: 1, blockHash: '0x1' }, - { blockNumber: 2, blockHash: '0x2' }, - ] as any; - - it('should throw if blockhash is different', async () => { - expect(() => { - depositService.checkEventsBlockHash(events, 2, '0x3'); - }).toThrow(); - }); - - it('should not throw if there are no events for the block', async () => { - expect(() => { - depositService.checkEventsBlockHash(events, 3, '0x3'); - }).not.toThrow(); - }); - - it('should not throw if blockhash is the same', async () => { - expect(() => { - depositService.checkEventsBlockHash(events, 2, '0x2'); - }).not.toThrow(); - }); - }); - - describe('getDepositRoot', () => { - it('should return deposit root', async () => { - const expected = '0x' + '0'.repeat(64); - - const mockProviderCall = jest - .spyOn(providerService.provider, 'call') - .mockImplementation(async () => { - const iface = new Interface(DepositAbi__factory.abi); - return iface.encodeFunctionResult('get_deposit_root', [expected]); - }); - - const result = await depositService.getDepositRoot(); - expect(result).toEqual(expected); - expect(mockProviderCall).toBeCalledTimes(1); - }); - }); - }); -}); diff --git a/src/contracts/deposit/deposit.service.ts b/src/contracts/deposit/deposit.service.ts deleted file mode 100644 index fbad8ad8..00000000 --- a/src/contracts/deposit/deposit.service.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { performance } from 'perf_hooks'; -import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { ProviderService } from 'provider'; -import { DepositEventEvent } from 'generated/DepositAbi'; -import { - DEPOSIT_EVENTS_STEP, - getDeploymentBlockByNetwork, - DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE, - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, -} from './deposit.constants'; -import { - DepositEvent, - VerifiedDepositEventsCache, - VerifiedDepositEventGroup, - VerifiedDepositedEventGroup, -} from './interfaces'; -import { RepositoryService } from 'contracts/repository'; -import { BlockTag } from 'provider'; -import { BlsService } from 'bls'; -import { DepositIntegrityCheckerService } from './integrity-checker'; -import { parseLittleEndian64 } from './deposit.utils'; -import { DepositTree } from './deposit-tree'; -import { LevelDBService } from './leveldb'; -import { DepositCacheIntegrityError } from './integrity-checker/constants'; - -@Injectable() -export class DepositService { - constructor( - @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private providerService: ProviderService, - private repositoryService: RepositoryService, - - private blsService: BlsService, - private depositIntegrityCheckerService: DepositIntegrityCheckerService, - private levelDBCacheService: LevelDBService, - ) {} - - public async handleNewBlock(blockNumber: number): Promise { - if (blockNumber % DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE !== 0) return; - - // The event cache is stored with an N block lag to avoid caching data from uncle blocks - // so we don't worry about blockHash here - const toBlockNumber = await this.updateEventsCache(); - await this.checkDepositCacheIntegrity(toBlockNumber); - } - - public async initialize(blockNumber: number) { - await this.levelDBCacheService.initialize(); - - const cachedEvents = await this.levelDBCacheService.getEventsCache(); - const isCacheValid = this.validateCache(cachedEvents, blockNumber); - - if (!isCacheValid) { - process.exit(1); - } - await this.depositIntegrityCheckerService.initialize(cachedEvents); - // it is necessary to load fresh events before integrity check - // because we can only compare roots of the last 128 blocks. - const toBlockNumber = await this.updateEventsCache(); - await this.checkDepositCacheIntegrity(toBlockNumber); - } - - public async checkDepositCacheIntegrity(toBlockNumber: number) { - try { - await this.depositIntegrityCheckerService.checkFinalizedRoot( - toBlockNumber, - ); - } catch (error) { - if (error instanceof DepositCacheIntegrityError) { - return this.logger.error( - `Deposit event cache integrity error on block number: ${toBlockNumber}`, - ); - } - throw error; - } - } - - /** - * Validates the app cache - * @param cachedEvents - cached events - * @param currentBlock - current block number - * @returns true if cache is valid - */ - public validateCache( - cachedEvents: VerifiedDepositEventsCache, - currentBlock: number, - ): boolean { - return this.validateCacheBlock(cachedEvents, currentBlock); - } - - /** - * Validates block number in the cache - * @param cachedEvents - cached events - * @param currentBlock - current block number - * @returns true if cached app version is the same - */ - public validateCacheBlock( - cachedEvents: VerifiedDepositEventsCache, - currentBlock: number, - ): boolean { - const isCacheValid = currentBlock >= cachedEvents.headers.endBlock; - - const blocks = { - cachedStartBlock: cachedEvents.headers.startBlock, - cachedEndBlock: cachedEvents.headers.endBlock, - currentBlock, - }; - - if (isCacheValid) { - this.logger.log('Deposit events cache has valid age', blocks); - } - - if (!isCacheValid) { - this.logger.warn( - 'Deposit events cache is newer than the current block', - blocks, - ); - } - - return isCacheValid; - } - - /** - * Returns only required information about the event, - * to reduce the size of the information stored in the cache - */ - public formatEvent(rawEvent: DepositEventEvent): DepositEvent { - const { - args, - transactionHash: tx, - blockNumber, - blockHash, - logIndex, - } = rawEvent; - const { - withdrawal_credentials: wc, - pubkey, - amount, - signature, - index, - ...rest - } = args; - - const depositCount = rest['4']; - - const depositDataRoot = DepositTree.formDepositNode({ - pubkey, - wc, - signature, - amount, - }); - - return { - pubkey, - wc, - amount, - signature, - tx, - blockNumber, - blockHash, - logIndex, - index, - depositCount: parseLittleEndian64(depositCount), - depositDataRoot, - }; - } - - /** - * Returns a block number when the deposited contract was deployed - * @returns block number - */ - public async getDeploymentBlockByNetwork(): Promise { - const chainId = await this.providerService.getChainId(); - return getDeploymentBlockByNetwork(chainId); - } - - /** - * Gets node operators data from cache - * @returns event group - */ - public async getCachedEvents(): Promise { - const { headers, ...rest } = - await this.levelDBCacheService.getEventsCache(); - const deploymentBlock = await this.getDeploymentBlockByNetwork(); - - return { - headers: { - ...headers, - startBlock: Math.max(headers.startBlock, deploymentBlock), - endBlock: Math.max(headers.endBlock, deploymentBlock), - }, - ...rest, - }; - } - - /** - * Saves deposited events to cache - */ - public async setCachedEvents( - cachedEvents: VerifiedDepositEventsCache, - ): Promise { - await this.levelDBCacheService.deleteCache(); - await this.levelDBCacheService.insertEventsCacheBatch({ - ...cachedEvents, - headers: { - ...cachedEvents.headers, - }, - }); - } - - /** - * Returns events in the block range - * If the request failed, it tries to repeat it or split it into two - * @param startBlock - start of the range - * @param endBlock - end of the range - * @returns event group - */ - public async fetchEventsFallOver( - startBlock: number, - endBlock: number, - ): Promise { - return await this.providerService.fetchEventsFallOver( - startBlock, - endBlock, - this.fetchEvents.bind(this), - ); - } - - /** - * Returns events in the block range - * @param startBlock - start of the range - * @param endBlock - end of the range - * @returns event group - */ - public async fetchEvents( - startBlock: number, - endBlock: number, - ): Promise { - const contract = await this.repositoryService.getCachedDepositContract(); - const filter = contract.filters.DepositEvent(); - const rawEvents = await contract.queryFilter(filter, startBlock, endBlock); - const events = rawEvents.map((rawEvent) => { - const formatted = this.formatEvent(rawEvent); - const valid = this.verifyDeposit(formatted); - return { valid, ...formatted }; - }); - - return { events, startBlock, endBlock }; - } - - /** - * Updates the cache deposited events - * The last N blocks are not stored, in order to avoid storing reorganized blocks - */ - public async updateEventsCache(): Promise { - const fetchTimeStart = performance.now(); - - const [currentBlock, initialCache] = await Promise.all([ - this.providerService.getBlockNumber(), - this.getCachedEvents(), - ]); - - const firstNotCachedBlock = initialCache.headers.endBlock + 1; - const toBlock = currentBlock - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS; - - const totalEventsCount = initialCache.data.length; - let newEventsCount = 0; - - for ( - let block = firstNotCachedBlock; - block <= toBlock; - block += DEPOSIT_EVENTS_STEP - ) { - const chunkStartBlock = block; - const chunkToBlock = Math.min(toBlock, block + DEPOSIT_EVENTS_STEP - 1); - - const chunkEventGroup = await this.fetchEventsFallOver( - chunkStartBlock, - chunkToBlock, - ); - - await this.levelDBCacheService.insertEventsCacheBatch({ - headers: { - ...initialCache.headers, - endBlock: chunkEventGroup.endBlock, - }, - data: chunkEventGroup.events, - }); - - await this.depositIntegrityCheckerService.putFinalizedEvents( - chunkEventGroup.events, - ); - - newEventsCount += chunkEventGroup.events.length; - - this.logger.log('Historical events are fetched', { - toBlock, - startBlock: chunkStartBlock, - endBlock: chunkToBlock, - }); - } - - const fetchTimeEnd = performance.now(); - const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; - // TODO: replace timer with metric - - this.logger.log('Deposit events cache is updated', { - newEventsCount, - totalEventsCount: totalEventsCount + newEventsCount, - fetchTime, - }); - - return toBlock; - } - - /** - * Returns all deposited events based on cache and fresh data - */ - public async getAllDepositedEvents( - blockNumber: number, - blockHash: string, - ): Promise { - const endBlock = blockNumber; - const cachedEvents = await this.getCachedEvents(); - - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - if (!isCacheValid) process.exit(1); - - const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; - const freshEventGroup = await this.fetchEventsFallOver( - firstNotCachedBlock, - endBlock, - ); - const freshEvents = freshEventGroup.events; - const lastEvent = freshEvents[freshEvents.length - 1]; - const lastEventBlockHash = lastEvent?.blockHash; - - this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); - - this.logger.debug?.('Fresh deposit events are fetched', { - events: freshEvents.length, - startBlock: firstNotCachedBlock, - endBlock, - blockHash, - lastEventBlockHash, - }); - - const mergedEvents = cachedEvents.data.concat(freshEvents); - - return { - events: mergedEvents, - startBlock: cachedEvents.headers.startBlock, - endBlock, - // declare a separate method where we store the latest events in the closure - checkRoot: async () => { - await this.depositIntegrityCheckerService.checkLatestRoot( - blockNumber, - freshEvents, - ); - }, - }; - } - - /** - * Checks events block hash - * An additional check to avoid events processing in an alternate chain - */ - public checkEventsBlockHash( - events: DepositEvent[], - blockNumber: number, - blockHash: string, - ): void { - events.forEach((event) => { - if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { - throw new Error( - 'Blockhash of the received events does not match the current blockhash', - ); - } - }); - } - - /** - * Returns a deposit root - */ - public async getDepositRoot(blockTag?: BlockTag): Promise { - const contract = await this.repositoryService.getCachedDepositContract(); - const depositRoot = await contract.get_deposit_root({ - blockTag: blockTag as any, - }); - - return depositRoot; - } - - /** - * Verifies a deposit signature - */ - public verifyDeposit(depositEvent: DepositEvent): boolean { - const { pubkey, wc, amount, signature } = depositEvent; - return this.blsService.verify({ pubkey, wc, amount, signature }); - } -} diff --git a/src/contracts/deposit/deposit.utils.ts b/src/contracts/deposit/deposit.utils.ts deleted file mode 100644 index 555b676a..00000000 --- a/src/contracts/deposit/deposit.utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -const changeEndianness = (string: any) => { - string = string.replace('0x', ''); - const result: string[] = []; - let len = string.length - 2; - while (len >= 0) { - result.push(string.substr(len, 2)); - len -= 2; - } - return '0x' + result.join(''); -}; - -export const parseLittleEndian64 = (str: string) => { - return parseInt(changeEndianness(str), 16); -}; - -export const toLittleEndian64 = (value: number): string => { - const buffer = Buffer.allocUnsafe(8); - buffer.writeBigUInt64LE(BigInt(value)); - return '0x' + buffer.toString('hex'); -}; diff --git a/src/contracts/deposit/index.ts b/src/contracts/deposit/index.ts deleted file mode 100644 index 753548e5..00000000 --- a/src/contracts/deposit/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './deposit.module'; -export * from './deposit.service'; -export * from './interfaces'; diff --git a/src/contracts/deposit/integrity-checker/constants.ts b/src/contracts/deposit/integrity-checker/constants.ts deleted file mode 100644 index 7c9c3e1d..00000000 --- a/src/contracts/deposit/integrity-checker/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DEPOSIT_TREE_STEP_SYNC = 200_000; -export class DepositCacheIntegrityError extends Error {} diff --git a/src/contracts/deposit/integrity-checker/index.ts b/src/contracts/deposit/integrity-checker/index.ts deleted file mode 100644 index f580e0ad..00000000 --- a/src/contracts/deposit/integrity-checker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './integrity-checker.service'; diff --git a/src/contracts/deposit/integrity-checker/integrity-checker.service.ts b/src/contracts/deposit/integrity-checker/integrity-checker.service.ts deleted file mode 100644 index 68150ee4..00000000 --- a/src/contracts/deposit/integrity-checker/integrity-checker.service.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { RepositoryService } from 'contracts/repository'; -import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { BlockTag } from 'provider'; -import { DepositTree } from '../deposit-tree'; -import { - VerifiedDepositEvent, - VerifiedDepositEventsCache, -} from '../interfaces'; -import { - DepositCacheIntegrityError, - DEPOSIT_TREE_STEP_SYNC, -} from './constants'; - -@Injectable() -export class DepositIntegrityCheckerService { - finalizedTree = new DepositTree(); - constructor( - @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private repositoryService: RepositoryService, - ) {} - - /** - * Initializes the deposit tree with an initial cache of verified deposit events. - * @param {VerifiedDepositEventsCache} initialEventsCache - Cache of verified deposit events to initialize the tree. - */ - public async initialize(initialEventsCache: VerifiedDepositEventsCache) { - await this.putEventsToTree(this.finalizedTree, initialEventsCache.data); - } - - /** - * Inserts a list of finalized verified deposit events into the deposit tree and returns the updated tree. - * @param {VerifiedDepositEvent[]} eventsCache - Array of verified deposit events to be added to the tree. - * @returns {Promise} The updated deposit tree after adding the events. - */ - public async putFinalizedEvents( - eventsCache: VerifiedDepositEvent[], - ): Promise { - await this.putEventsToTree(this.finalizedTree, eventsCache); - return this.finalizedTree; - } - - /** - * Inserts a list of latest verified deposit events into a clone of the deposit tree and returns the cloned tree. - * @param {VerifiedDepositEvent[]} eventsCache - Array of verified deposit events to be added to the cloned tree. - * @returns {Promise} The cloned and updated deposit tree after adding the events. - */ - public async putLatestEvents( - eventsCache: VerifiedDepositEvent[], - ): Promise { - const clone = this.finalizedTree.clone(); - await this.putEventsToTree(clone, eventsCache); - return clone; - } - - /** - * Checks the integrity of the latest root against the blockchain deposit root for a given block number. - * @param {number} blockNumber - Block number to check the deposit root against. - * @param {VerifiedDepositEvent[]} eventsCache - Latest events to verify against the deposit root. - * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. - */ - public async checkLatestRoot( - blockNumber: number, - eventsCache: VerifiedDepositEvent[], - ): Promise { - const tree = await this.putLatestEvents( - eventsCache.sort((a, b) => a.depositCount - b.depositCount), - ); - - return this.checkRoot(blockNumber, tree); - } - - /** - * Checks the integrity of the finalized root against the blockchain deposit root for a given block number. - * @param {number} blockNumber - Block number to check the deposit root against. - * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. - */ - public async checkFinalizedRoot(blockNumber: number): Promise { - return this.checkRoot(blockNumber, this.finalizedTree); - } - - /** - * A private helper method to compare the local deposit tree root with the remote deposit root from the blockchain. - * @param {number} blockNumber - Block number associated with the deposit root to verify. - * @param {DepositTree} tree - Deposit tree to use for comparison. - * @returns {Promise} A promise that resolves if the roots match, otherwise logs an error and throws. - */ - private async checkRoot(blockNumber: number, tree: DepositTree) { - const localRoot = tree.getRoot(); - const remoteRoot = await this.getDepositRoot(blockNumber); - - if (localRoot === remoteRoot) { - this.logger.log('Integrity check successfully completed', { - blockNumber, - }); - return; - } - - this.logger.error( - 'Deposit root is different from deposit root from the network', - { localRoot, remoteRoot }, - ); - - throw new DepositCacheIntegrityError( - 'Deposit root is different from deposit root from the network', - ); - } - - /** - * Inserts verified deposit events into the provided deposit tree and logs progress periodically. - * @param {DepositTree} tree - Deposit tree to insert events into. - * @param {VerifiedDepositEvent[]} eventsCache - Events to insert into the tree. - */ - public async putEventsToTree( - tree: DepositTree, - eventsCache: VerifiedDepositEvent[], - ) { - for (const [index, event] of eventsCache.entries()) { - tree.insertNode(event.depositDataRoot); - - if (index % DEPOSIT_TREE_STEP_SYNC === 0) { - await new Promise((res) => setTimeout(res, 1)); - - this.logger.log('Checking integrity of saved deposit events', { - processed: index, - remaining: eventsCache.length - index, - }); - } - } - } - - /** - * Retrieves the deposit root from the blockchain for a specific block. - * @param {BlockTag | undefined} blockTag - Specific block number or tag to retrieve the deposit root for. - * @returns {Promise} Promise that resolves with the deposit root. - */ - public async getDepositRoot(blockTag?: BlockTag): Promise { - const contract = await this.repositoryService.getCachedDepositContract(); - const depositRoot = await contract.get_deposit_root({ - blockTag: blockTag as any, - }); - - return depositRoot; - } -} diff --git a/src/contracts/deposit/interfaces/cache.interface.ts b/src/contracts/deposit/interfaces/cache.interface.ts deleted file mode 100644 index b42b7fcb..00000000 --- a/src/contracts/deposit/interfaces/cache.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { VerifiedDepositEvent } from './event.interface'; - -export interface VerifiedDepositEventsCacheHeaders { - startBlock: number; - endBlock: number; -} - -export interface VerifiedDepositEventsCache { - headers: VerifiedDepositEventsCacheHeaders; - data: VerifiedDepositEvent[]; -} diff --git a/src/contracts/deposit/interfaces/deposit-tree.interface.ts b/src/contracts/deposit/interfaces/deposit-tree.interface.ts deleted file mode 100644 index 7d0b082a..00000000 --- a/src/contracts/deposit/interfaces/deposit-tree.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface NodeData { - pubkey: string; - wc: string; - amount: string; - signature: string; -} diff --git a/src/contracts/deposit/interfaces/event.interface.ts b/src/contracts/deposit/interfaces/event.interface.ts deleted file mode 100644 index 45da4f73..00000000 --- a/src/contracts/deposit/interfaces/event.interface.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface DepositEvent { - pubkey: string; - wc: string; - amount: string; - signature: string; - tx: string; - blockNumber: number; - blockHash: string; - logIndex: number; - index: string; - depositCount: number; - depositDataRoot: Uint8Array; -} - -export interface VerifiedDepositEvent extends DepositEvent { - valid: boolean; -} - -export interface DepositEventGroup { - events: DepositEvent[]; - startBlock: number; - endBlock: number; -} - -export interface VerifiedDepositEventGroup extends DepositEventGroup { - events: VerifiedDepositEvent[]; -} - -export interface VerifiedDepositedEventGroup extends VerifiedDepositEventGroup { - checkRoot(): Promise; -} diff --git a/src/contracts/deposit/interfaces/index.ts b/src/contracts/deposit/interfaces/index.ts deleted file mode 100644 index 8edb9bb4..00000000 --- a/src/contracts/deposit/interfaces/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './cache.interface'; -export * from './event.interface'; -export * from './deposit-tree.interface'; diff --git a/src/contracts/deposit/leveldb/index.ts b/src/contracts/deposit/leveldb/index.ts deleted file mode 100644 index eed6aa71..00000000 --- a/src/contracts/deposit/leveldb/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './leveldb.constants'; -export * from './leveldb.module'; -export * from './leveldb.service'; diff --git a/src/contracts/deposit/leveldb/leveldb.constants.ts b/src/contracts/deposit/leveldb/leveldb.constants.ts deleted file mode 100644 index 3198f854..00000000 --- a/src/contracts/deposit/leveldb/leveldb.constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const DB_DIR = 'cache'; -export const DB_LAYER_DIR = 'cache:layer'; - -export const DB_DEFAULT_VALUE = 'cacheDefaultValue'; - -export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ - headers: { - version: '-1', - startBlock: 0, - endBlock: 0, - }, - data: [], -}); - -export const MAX_DEPOSIT_COUNT = 2 ** 32; diff --git a/src/contracts/deposit/leveldb/leveldb.fixtures.ts b/src/contracts/deposit/leveldb/leveldb.fixtures.ts deleted file mode 100644 index bbca5314..00000000 --- a/src/contracts/deposit/leveldb/leveldb.fixtures.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { VerifiedDepositEvent, VerifiedDepositEventsCacheHeaders } from '..'; - -// Mock for VerifiedDepositEventsCacheHeaders -export const headersMock: VerifiedDepositEventsCacheHeaders = { - startBlock: 1000, - endBlock: 1050, -}; - -// Mock for VerifiedDepositEvent -export const eventMock1: VerifiedDepositEvent = { - pubkey: 'abc123', - wc: '0', - amount: '100', - signature: 'def456', - tx: 'ghi789', - blockNumber: 1001, - blockHash: 'aaa111', - logIndex: 1, - index: '0', - depositCount: 1, - depositDataRoot: new Uint8Array([1, 2, 3, 4, 5]), - valid: true, -}; - -export const eventMock2: VerifiedDepositEvent = { - pubkey: 'xyz123', - wc: '0', - amount: '200', - signature: 'uvw456', - tx: 'rst789', - blockNumber: 1002, - blockHash: 'bbb222', - logIndex: 2, - index: '1', - depositCount: 2, - depositDataRoot: new Uint8Array([6, 7, 8, 9, 10]), - valid: true, -}; - -// Mock for the structure {data: VerifiedDepositEvent[], headers: VerifiedDepositEventsCacheHeaders} -export const cacheMock: { - data: VerifiedDepositEvent[]; - headers: VerifiedDepositEventsCacheHeaders; -} = { - data: [eventMock1, eventMock2], - headers: headersMock, -}; diff --git a/src/contracts/deposit/leveldb/leveldb.module.ts b/src/contracts/deposit/leveldb/leveldb.module.ts deleted file mode 100644 index 6c754d6e..00000000 --- a/src/contracts/deposit/leveldb/leveldb.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; -import { ProviderModule } from 'provider'; -import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './leveldb.constants'; -import { LevelDBService } from './leveldb.service'; - -@Module({}) -export class LevelDBModule { - static register( - defaultValue: unknown, - cacheDir = 'cache', - cacheLayerDir = 'deposit-cache', - ): DynamicModule { - return { - module: LevelDBModule, - imports: [ProviderModule], - providers: [ - LevelDBService, - { - provide: DB_DIR, - useValue: cacheDir, - }, - { - provide: DB_LAYER_DIR, - useValue: cacheLayerDir, - }, - { - provide: DB_DEFAULT_VALUE, - useValue: defaultValue, - }, - ], - exports: [LevelDBService], - }; - } -} diff --git a/src/contracts/deposit/leveldb/leveldb.service.spec.ts b/src/contracts/deposit/leveldb/leveldb.service.spec.ts deleted file mode 100644 index 8af08776..00000000 --- a/src/contracts/deposit/leveldb/leveldb.service.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Test } from '@nestjs/testing'; -import { MockProviderModule } from 'provider'; -import { ConfigModule } from 'common/config'; -import { LoggerModule } from 'common/logger'; -import { LevelDBModule } from './leveldb.module'; -import { LevelDBService } from './leveldb.service'; -import { cacheMock } from './leveldb.fixtures'; - -describe('dbService', () => { - const defaultCacheValue = { - headers: {}, - data: [] as any[], - }; - - let dbService: LevelDBService; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot(), - MockProviderModule.forRoot(), - LevelDBModule.register(defaultCacheValue, 'leveldb-spec'), - LoggerModule, - ], - }).compile(); - - dbService = moduleRef.get(LevelDBService); - await dbService.initialize(); - }); - - afterEach(async () => { - try { - await dbService.deleteCache(); - await dbService.close(); - } catch (error) {} - }); - - it('should return default cache', async () => { - const result = await dbService.getEventsCache(); - expect(result).toEqual(defaultCacheValue); - }); - - it('should return saved cache', async () => { - const expected = cacheMock; - - await dbService.insertEventsCacheBatch(expected); - const result = await dbService.getEventsCache(); - - expect(result).toEqual(expected); - }); -}); diff --git a/src/contracts/deposit/leveldb/leveldb.service.ts b/src/contracts/deposit/leveldb/leveldb.service.ts deleted file mode 100644 index d1e7ccbb..00000000 --- a/src/contracts/deposit/leveldb/leveldb.service.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Level } from 'level'; -import { join } from 'path'; -import { - DB_DIR, - DB_DEFAULT_VALUE, - MAX_DEPOSIT_COUNT, - DB_LAYER_DIR, -} from './leveldb.constants'; -import { ProviderService } from 'provider'; -import { VerifiedDepositEvent, VerifiedDepositEventsCacheHeaders } from '..'; - -@Injectable() -export class LevelDBService { - private db!: Level; - constructor( - private providerService: ProviderService, - @Inject(DB_DIR) private cacheDir: string, - @Inject(DB_LAYER_DIR) private cacheLayerDir: string, - @Inject(DB_DEFAULT_VALUE) - private cacheDefaultValue: { - data: VerifiedDepositEvent[]; - headers: VerifiedDepositEventsCacheHeaders; - }, - ) {} - - public async initialize() { - await this.setupLevel(); - } - - /** - * Initializes LevelDB with JSON encoding at the cache directory path. - * - * @returns {Promise} A promise that resolves when the database is successfully initialized. - * @private - */ - private async setupLevel() { - this.db = new Level(await this.getDBDirPath(), { - valueEncoding: 'json', - }); - await this.db.open(); - } - - /** - * Fetches and constructs the cache directory path for the current blockchain network. - * - * @returns {Promise} A promise that resolves to the full path of the network-specific cache directory. - * @private - */ - private async getDBDirPath(): Promise { - const chainId = await this.providerService.getChainId(); - const networkDir = `chain-${chainId}`; - - return join(this.cacheDir, this.cacheLayerDir, networkDir); - } - - /** - * Asynchronously retrieves deposit events and headers from the database. - * Iterates through entries starting with 'deposit:' to collect data and fetches headers stored under 'header'. - * Handles errors by logging and returning default cache values. - * - * @returns {Promise<{data: VerifiedDepositEvent[], headers: VerifiedDepositEventsCacheHeaders}>} Cache data and headers. - * @public - */ - public async getEventsCache(): Promise<{ - data: VerifiedDepositEvent[]; - headers: VerifiedDepositEventsCacheHeaders; - }> { - try { - const stream = this.db.iterator({ gte: 'deposit:', lte: 'deposit:\xFF' }); - - const data: VerifiedDepositEvent[] = []; - - for await (const [, value] of stream) { - data.push(this.parseDepositEvent(value)); - } - const headers: VerifiedDepositEventsCacheHeaders = JSON.parse( - await this.db.get('headers'), - ); - - return { data, headers }; - } catch (error: any) { - if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; - throw error; - } - } - - /** - * Generates a deposit key string based on a given number. - * The number is checked to ensure it falls within a valid range (from 0 up to MAX_DEPOSIT_COUNT). - * If the number is out of bounds, an error is thrown. - * The method creates a buffer, writes the number to the buffer in big-endian format, - * and returns a deposit key string that includes the hexadecimal representation of the number. - * - * @param {number} number - The number used to generate the deposit key. - * @returns {string} The deposit key in the format 'deposit:XXXX', where 'XXXX' is the hexadecimal representation of the number. - * @throws {Error} If the number is less than 0 or greater than MAX_DEPOSIT_COUNT. - * @private - */ - private generateDepositKey(number: number): string { - if (number < 0 || number > MAX_DEPOSIT_COUNT) { - throw new Error( - `Deposit count is out of the valid range (0 to ${MAX_DEPOSIT_COUNT}) received ${number}`, - ); - } - const index = Buffer.alloc(4); - index.writeUInt32BE(number, 0); - return `deposit:${index.toString('hex')}`; - } - - /** - * Parses a JSON string to a VerifiedDepositEvent, adding a Uint8Array for the depositDataRoot. - * - * @param {string} dataString - The JSON string representing a deposit event. - * @returns {VerifiedDepositEvent} The parsed deposit event. - * @private - */ - private parseDepositEvent(dataString: string): VerifiedDepositEvent { - const data = JSON.parse(dataString); - const depositEvent: VerifiedDepositEvent = { - ...data, - depositDataRoot: new Uint8Array(data.depositDataRoot), - }; - return depositEvent; - } - - /** - * Serializes a VerifiedDepositEvent into a JSON string, converting `depositDataRoot` from Uint8Array to an array. - * - * @param {VerifiedDepositEvent} depositEvent - The deposit event to serialize. - * @returns {string} The serialized JSON string of the deposit event. - * @public - */ - public serializeDepositEvent(depositEvent: VerifiedDepositEvent) { - const { depositDataRoot, ...rest } = depositEvent; - const value = { - ...rest, - depositDataRoot: Array.from(depositDataRoot), - }; - return JSON.stringify(value); - } - - /** - * Inserts a batch of deposit events and a header into the database. - * - * @param {VerifiedDepositEvent[]} events - An array of verified deposit events to be inserted into the database. - * @param {VerifiedDepositEventsCacheHeaders} header - The header information to be stored along with the events. - * @returns {Promise} A promise that resolves when all operations have been successfully committed to the database. - * @public - */ - public async insertEventsCacheBatch(records: { - data: VerifiedDepositEvent[]; - headers: VerifiedDepositEventsCacheHeaders; - }) { - const ops = records.data.map((event) => ({ - type: 'put' as const, - key: this.generateDepositKey(event.depositCount), - value: this.serializeDepositEvent(event), - })); - ops.push({ - type: 'put', - key: 'headers', - value: JSON.stringify(records.headers), - }); - await this.db.batch(ops); - } - - /** - * Clears all entries from the database. - * - * @returns {Promise} - * @public - */ - public async deleteCache(): Promise { - await this.db.clear(); - } - - /** - * Close the database connection. - * - * @returns {Promise} - * @public - */ - public async close(): Promise { - await this.db.close(); - } -} From c8ebcdb67cdf84a0a3464bd3a1c4e410259e6ce8 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 16:55:15 +0200 Subject: [PATCH 30/94] feat: use new module as deposit module replacement --- .../deposits-registry.module.ts | 2 +- src/guardian/block-guard/block-guard.module.ts | 4 ++-- src/guardian/block-guard/block-guard.service.ts | 4 ++-- .../guardian-metrics/guardian-metrics.service.ts | 2 +- src/guardian/guardian.module.ts | 4 ++-- src/guardian/guardian.service.ts | 16 ++++++++-------- src/guardian/interfaces/block.interface.ts | 2 +- .../staking-module-guard.service.ts | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.module.ts b/src/contracts/deposits-registry/deposits-registry.module.ts index 0d448e69..6fb033b7 100644 --- a/src/contracts/deposits-registry/deposits-registry.module.ts +++ b/src/contracts/deposits-registry/deposits-registry.module.ts @@ -16,4 +16,4 @@ import { DepositRegistrySanityCheckerModule } from './sanity-checker'; providers: [DepositRegistryService], exports: [DepositRegistryService], }) -export class DepositModule {} +export class DepositsRegistryModule {} diff --git a/src/guardian/block-guard/block-guard.module.ts b/src/guardian/block-guard/block-guard.module.ts index 2a3d51e0..8673e50f 100644 --- a/src/guardian/block-guard/block-guard.module.ts +++ b/src/guardian/block-guard/block-guard.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { DepositModule } from 'contracts/deposit'; +import { DepositsRegistryModule } from 'contracts/deposits-registry'; import { SecurityModule } from 'contracts/security'; import { BlockGuardService } from './block-guard.service'; import { LidoModule } from 'contracts/lido'; @@ -9,7 +9,7 @@ import { WalletModule } from 'wallet'; @Module({ imports: [ LidoModule, - DepositModule, + DepositsRegistryModule, SecurityModule, StakingModuleGuardModule, WalletModule, diff --git a/src/guardian/block-guard/block-guard.service.ts b/src/guardian/block-guard/block-guard.service.ts index c15587ed..3b616bed 100644 --- a/src/guardian/block-guard/block-guard.service.ts +++ b/src/guardian/block-guard/block-guard.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { DepositService } from 'contracts/deposit'; +import { DepositRegistryService } from 'contracts/deposits-registry'; import { SecurityService } from 'contracts/security'; import { BlockData } from '../interfaces'; @@ -32,7 +32,7 @@ export class BlockGuardService { private walletService: WalletService, - private depositService: DepositService, + private depositService: DepositRegistryService, private securityService: SecurityService, private lidoService: LidoService, diff --git a/src/guardian/guardian-metrics/guardian-metrics.service.ts b/src/guardian/guardian-metrics/guardian-metrics.service.ts index 78685bf4..1d0506ec 100644 --- a/src/guardian/guardian-metrics/guardian-metrics.service.ts +++ b/src/guardian/guardian-metrics/guardian-metrics.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { VerifiedDepositEvent } from 'contracts/deposit'; +import { VerifiedDepositEvent } from 'contracts/deposits-registry'; import { BlockData, StakingModuleData } from '../interfaces'; import { InjectMetric } from '@willsoto/nestjs-prometheus'; import { diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index 68cd36d2..c200930a 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { DepositModule } from 'contracts/deposit'; +import { DepositsRegistryModule } from 'contracts/deposits-registry'; import { SecurityModule } from 'contracts/security'; import { LidoModule } from 'contracts/lido'; import { MessagesModule } from 'messages'; @@ -16,7 +16,7 @@ import { UnvettingModule } from './unvetting/unvetting.module'; @Module({ imports: [ - DepositModule, + DepositsRegistryModule, SecurityModule, LidoModule, MessagesModule, diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 6ee7c4bb..05e557f4 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -8,7 +8,7 @@ import { compare } from 'compare-versions'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { SchedulerRegistry } from '@nestjs/schedule'; import { CronJob } from 'cron'; -import { DepositService } from 'contracts/deposit'; +import { DepositRegistryService } from 'contracts/deposits-registry'; import { SecurityService } from 'contracts/security'; import { RepositoryService } from 'contracts/repository'; import { @@ -43,7 +43,7 @@ export class GuardianService implements OnModuleInit { private schedulerRegistry: SchedulerRegistry, - private depositService: DepositService, + private depositService: DepositRegistryService, private securityService: SecurityService, private stakingRouterService: StakingRouterService, @@ -68,7 +68,7 @@ export class GuardianService implements OnModuleInit { const blockHash = block.hash; await Promise.all([ - this.depositService.initialize(block.number), + this.depositService.initialize(), this.securityService.initialize({ blockHash }), this.signingKeyEventsCacheService.initialize(block.number), ]); @@ -333,20 +333,18 @@ export class GuardianService implements OnModuleInit { blockData, ); - // Check the integrity of the cache, we can only make a deposit - // if the integrity of the deposit event data is intact - await blockData.depositedEvents.checkRoot(); - if ( this.cannotDeposit( stakingModuleData, blockData.theftHappened, blockData.alreadyPausedDeposits, + blockData.depositedEvents.isValid, ) ) { this.logger.warn('Deposits are not available', { stakingModuleId: stakingModuleData.stakingModuleId, blockHash: blockData.blockHash, + isDepositsCacheValid: blockData.depositedEvents.isValid, }); return; } @@ -363,6 +361,7 @@ export class GuardianService implements OnModuleInit { stakingModuleData: StakingModuleData, theftHappened: boolean, alreadyPausedDeposits: boolean, + isDepositsCacheValid: boolean, ): boolean { const keysForUnvetting = [ ...stakingModuleData.invalidKeys, @@ -376,7 +375,8 @@ export class GuardianService implements OnModuleInit { stakingModuleData.unresolvedDuplicatedKeys.length > 0 || alreadyPausedDeposits || theftHappened || - stakingModuleData.isModuleDepositsPaused + stakingModuleData.isModuleDepositsPaused || + !isDepositsCacheValid ); } } diff --git a/src/guardian/interfaces/block.interface.ts b/src/guardian/interfaces/block.interface.ts index 47b6ed53..9580e095 100644 --- a/src/guardian/interfaces/block.interface.ts +++ b/src/guardian/interfaces/block.interface.ts @@ -1,4 +1,4 @@ -import { VerifiedDepositedEventGroup } from 'contracts/deposit'; +import { VerifiedDepositedEventGroup } from 'contracts/deposits-registry'; export interface BlockData { blockNumber: number; diff --git a/src/guardian/staking-module-guard/staking-module-guard.service.ts b/src/guardian/staking-module-guard/staking-module-guard.service.ts index 7e6d05ce..12ae1ba7 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.service.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.service.ts @@ -4,7 +4,7 @@ import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { VerifiedDepositEvent, VerifiedDepositEventGroup, -} from 'contracts/deposit'; +} from 'contracts/deposits-registry'; import { SecurityService } from 'contracts/security'; import { ContractsState, BlockData, StakingModuleData } from '../interfaces'; From c7fd4109913d81489127b1b2bb19a208bd3de354 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 2 Sep 2024 19:07:14 +0400 Subject: [PATCH 31/94] fix: minor changes in guardian service --- .../block-data-collector.module.ts} | 8 +- .../block-data-collector.service.ts} | 2 +- src/guardian/block-data-collector/index.ts | 2 + src/guardian/block-guard/index.ts | 2 - src/guardian/guardian.module.ts | 4 +- src/guardian/guardian.service.spec.ts | 10 +-- src/guardian/guardian.service.ts | 89 ++++++++++--------- .../staking-module-data-collector.module.ts | 2 - .../staking-module-data-collector.service.ts | 8 +- 9 files changed, 62 insertions(+), 65 deletions(-) rename src/guardian/{block-guard/block-guard.module.ts => block-data-collector/block-data-collector.module.ts} (70%) rename src/guardian/{block-guard/block-guard.service.ts => block-data-collector/block-data-collector.service.ts} (98%) create mode 100644 src/guardian/block-data-collector/index.ts delete mode 100644 src/guardian/block-guard/index.ts diff --git a/src/guardian/block-guard/block-guard.module.ts b/src/guardian/block-data-collector/block-data-collector.module.ts similarity index 70% rename from src/guardian/block-guard/block-guard.module.ts rename to src/guardian/block-data-collector/block-data-collector.module.ts index cbff9002..7ebed8ab 100644 --- a/src/guardian/block-guard/block-guard.module.ts +++ b/src/guardian/block-data-collector/block-data-collector.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { DepositModule } from 'contracts/deposit'; import { SecurityModule } from 'contracts/security'; -import { BlockGuardService } from './block-guard.service'; import { StakingModuleGuardModule } from 'guardian/staking-module-guard'; import { WalletModule } from 'wallet'; import { StakingRouterModule } from 'contracts/staking-router'; +import { BlockDataCollectorService } from './block-data-collector.service'; @Module({ imports: [ @@ -14,7 +14,7 @@ import { StakingRouterModule } from 'contracts/staking-router'; WalletModule, StakingRouterModule, ], - providers: [BlockGuardService], - exports: [BlockGuardService], + providers: [BlockDataCollectorService], + exports: [BlockDataCollectorService], }) -export class BlockGuardModule {} +export class BlockDataCollectorModule {} diff --git a/src/guardian/block-guard/block-guard.service.ts b/src/guardian/block-data-collector/block-data-collector.service.ts similarity index 98% rename from src/guardian/block-guard/block-guard.service.ts rename to src/guardian/block-data-collector/block-data-collector.service.ts index be9f6aee..b90d1db2 100644 --- a/src/guardian/block-guard/block-guard.service.ts +++ b/src/guardian/block-data-collector/block-data-collector.service.ts @@ -17,7 +17,7 @@ import { WalletService } from 'wallet'; import { StakingRouterService } from 'contracts/staking-router'; @Injectable() -export class BlockGuardService { +export class BlockDataCollectorService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, diff --git a/src/guardian/block-data-collector/index.ts b/src/guardian/block-data-collector/index.ts new file mode 100644 index 00000000..4013ae8e --- /dev/null +++ b/src/guardian/block-data-collector/index.ts @@ -0,0 +1,2 @@ +export * from './block-data-collector.module'; +export * from './block-data-collector.service'; diff --git a/src/guardian/block-guard/index.ts b/src/guardian/block-guard/index.ts deleted file mode 100644 index 78e3eaf6..00000000 --- a/src/guardian/block-guard/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './block-guard.module'; -export * from './block-guard.service'; diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index 53b005e2..71ad4b42 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -4,7 +4,7 @@ import { SecurityModule } from 'contracts/security'; import { MessagesModule } from 'messages'; import { GuardianService } from './guardian.service'; import { ScheduleModule } from 'common/schedule'; -import { BlockGuardModule } from './block-guard/block-guard.module'; +import { BlockDataCollectorModule } from './block-data-collector/block-data-collector.module'; import { StakingModuleGuardModule } from './staking-module-guard'; import { GuardianMessageModule } from './guardian-message'; import { GuardianMetricsModule } from './guardian-metrics'; @@ -21,7 +21,7 @@ import { StakingRouterModule } from 'contracts/staking-router'; MessagesModule, StakingModuleDataCollectorModule, ScheduleModule, - BlockGuardModule, + BlockDataCollectorModule, StakingModuleGuardModule, UnvettingModule, GuardianMessageModule, diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index 10880306..0c4e6272 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -15,7 +15,7 @@ import { StakingModuleDataCollectorModule } from 'staking-module-data-collector' import { GuardianMetricsModule } from './guardian-metrics'; import { GuardianMessageModule } from './guardian-message'; import { StakingModuleGuardModule } from './staking-module-guard'; -import { BlockGuardModule, BlockGuardService } from './block-guard'; +import { BlockDataCollectorModule } from './block-data-collector'; import { ScheduleModule } from 'common/schedule'; import { LocatorService } from 'contracts/repository/locator/locator.service'; import { mockLocator } from 'contracts/repository/locator/locator.mock'; @@ -27,8 +27,6 @@ jest.mock('../transport/stomp/stomp.client'); describe('GuardianService', () => { let keysApiService: KeysApiService; - let blockGuardService: BlockGuardService; - let guardianService: GuardianService; let loggerService: LoggerService; @@ -49,7 +47,7 @@ describe('GuardianService', () => { MessagesModule, StakingModuleDataCollectorModule, ScheduleModule, - BlockGuardModule, + BlockDataCollectorModule, StakingModuleGuardModule, GuardianMessageModule, GuardianMetricsModule, @@ -102,7 +100,7 @@ describe('GuardianService', () => { }, })); - const getBlockGuardServiceMock = jest + const isNeedToProcessNewStatMock = jest .spyOn(guardianService, 'isNeedToProcessNewState') .mockImplementation(() => false); @@ -112,7 +110,7 @@ describe('GuardianService', () => { guardianService.handleNewBlock(), ]); - expect(getBlockGuardServiceMock).toBeCalledTimes(1); + expect(isNeedToProcessNewStatMock).toBeCalledTimes(1); expect(getOperatorsAndModulesMock).toBeCalledTimes(1); }); }); diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index e993381e..3ca18b97 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -18,7 +18,8 @@ import { import { OneAtTime } from 'common/decorators'; import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; -import { BlockGuardService } from './block-guard'; +import { BlockDataCollectorService } from './block-data-collector'; + import { StakingModuleGuardService } from './staking-module-guard'; import { GuardianMessageService } from './guardian-message'; import { GuardianMetricsService } from './guardian-metrics'; @@ -48,7 +49,7 @@ export class GuardianService implements OnModuleInit { private securityService: SecurityService, private stakingModuleDataCollectorService: StakingModuleDataCollectorService, - private blockGuardService: BlockGuardService, + private blockDataCollectorService: BlockDataCollectorService, private stakingModuleGuardService: StakingModuleGuardService, private guardianMessageService: GuardianMessageService, private guardianMetricsService: GuardianMetricsService, @@ -163,18 +164,15 @@ export class GuardianService implements OnModuleInit { this.logger.log('New staking router state cycle start'); try { - // Fetch the minimum required data to make an early exit - // fetch data from Keys api - const { data: operatorsByModules, meta } = + // Fetch the minimum required data fro Keys Api to make an early exit + const { data: operatorsByModules, meta: firstRequestMeta } = await this.keysApiService.getOperatorListWithModule(); const { elBlockSnapshot: { blockHash, blockNumber }, - } = meta; - - // contracts init - await this.repositoryService.initCachedContracts({ blockHash }); + } = firstRequestMeta; + // Compare the block stored in memory from the previous iteration with the current block from the Keys API. const isNewBlock = this.isNeedToProcessNewState({ blockHash, blockNumber, @@ -189,20 +187,23 @@ export class GuardianService implements OnModuleInit { }); // fetch all lido keys - const { data: lidoKeys, meta: currMeta } = + const { data: lidoKeys, meta: secondRequestMeta } = await this.keysApiService.getKeys(); // check that there were no updates in Keys Api between two requests this.keysApiService.verifyMetaDataConsistency( - meta.elBlockSnapshot.lastChangedBlockHash, - currMeta.elBlockSnapshot.lastChangedBlockHash, + firstRequestMeta.elBlockSnapshot.lastChangedBlockHash, + secondRequestMeta.elBlockSnapshot.lastChangedBlockHash, ); + // contracts initialization + await this.repositoryService.initCachedContracts({ blockHash }); + await this.depositService.handleNewBlock(blockNumber); const { stakingModulesData, blockData } = await this.collectData( operatorsByModules, - meta, + firstRequestMeta, lidoKeys, ); @@ -223,6 +224,9 @@ export class GuardianService implements OnModuleInit { return; } + // To avoid blocking the pause, run the following tasks asynchronously: + // updating the SigningKeyAdded events cache, checking keys, handling the unvetting of keys, + // and sending deposit messages to the queue. this.handleKeys(stakingModulesData, blockData, lidoKeys).catch( this.logger.error, ); @@ -237,7 +241,7 @@ export class GuardianService implements OnModuleInit { } } - async collectData( + private async collectData( operatorsByModules: SROperatorListWithModule[], meta: Meta, lidoKeys: RegistryKey[], @@ -245,10 +249,20 @@ export class GuardianService implements OnModuleInit { const { elBlockSnapshot: { blockHash, blockNumber }, } = meta; - const blockData = await this.blockGuardService.getCurrentBlockData({ - blockHash, - blockNumber, - }); + + const [blockData, stakingModulesData] = await Promise.all([ + this.blockDataCollectorService.getCurrentBlockData({ + blockHash, + blockNumber, + }), + // Construct the Staking Module data array using information fetched from the Keys API, + // identifying vetted unused keys and checking the module pause status + this.stakingModuleDataCollectorService.collectStakingModuleData({ + operatorsByModules, + meta, + lidoKeys, + }), + ]); this.logger.debug?.('Current block data loaded', { guardianIndex: blockData.guardianIndex, @@ -257,23 +271,14 @@ export class GuardianService implements OnModuleInit { securityVersion: blockData.securityVersion, }); - // collect some data and check keys - const stakingModulesData: StakingModuleData[] = - await this.stakingModuleDataCollectorService.collectStakingModuleData({ - operatorsByModules, - meta, - lidoKeys, - blockData, - }); - return { blockData, stakingModulesData }; } /** - * This method check keys and if they are correct send deposit message in queue, another way send unvet transation + * This method check keys and if they are correct send deposit message in queue, another way send unvet transaction */ @OneAtTime() - async handleKeys( + private async handleKeys( stakingModulesData: StakingModuleData[], blockData: BlockData, lidoKeys: RegistryKey[], @@ -293,7 +298,7 @@ export class GuardianService implements OnModuleInit { this.logger.log('New staking router state cycle end'); } - async checkKeys( + private async checkKeys( stakingModulesData: StakingModuleData[], blockData: BlockData, lidoKeys: RegistryKey[], @@ -315,7 +320,7 @@ export class GuardianService implements OnModuleInit { ); } - async handleUnvetting( + private async handleUnvetting( stakingModulesData: StakingModuleData[], blockData: BlockData, ) { @@ -355,10 +360,14 @@ export class GuardianService implements OnModuleInit { return keys.length > 0; } - async handleDeposit( + private async handleDeposit( stakingModulesData: StakingModuleData[], blockData: BlockData, ) { + // Check the integrity of the cache, we can only make a deposit + // if the integrity of the deposit event data is intact + await blockData.depositedEvents.checkRoot(); + await Promise.all( stakingModulesData.map(async (stakingModuleData) => { this.guardianMetricsService.collectMetrics( @@ -366,10 +375,6 @@ export class GuardianService implements OnModuleInit { blockData, ); - // Check the integrity of the cache, we can only make a deposit - // if the integrity of the deposit event data is intact - await blockData.depositedEvents.checkRoot(); - if ( this.cannotDeposit( stakingModuleData, @@ -392,7 +397,7 @@ export class GuardianService implements OnModuleInit { ); } - cannotDeposit( + private cannotDeposit( stakingModuleData: StakingModuleData, theftHappened: boolean, alreadyPausedDeposits: boolean, @@ -420,21 +425,21 @@ export class GuardianService implements OnModuleInit { const lastMeta = this.lastProcessedStateMeta; if (!lastMeta) return true; if (lastMeta.blockNumber > newMeta.blockNumber) { - this.logger.error('Keys-api returns old state', newMeta); + this.logger.error('Keys API returns old state', { newMeta, lastMeta }); return false; } - const isSameBlock = lastMeta.blockHash !== newMeta.blockHash; + const isSameBlock = lastMeta.blockHash === newMeta.blockHash; - if (!isSameBlock) { + if (isSameBlock) { this.logger.log(`The block has not changed since the last cycle. Exit`, { newMeta, }); } - return isSameBlock; + return !isSameBlock; } - public setLastProcessedStateMeta(newMeta: { + private setLastProcessedStateMeta(newMeta: { blockHash: string; blockNumber: number; }) { diff --git a/src/staking-module-data-collector/staking-module-data-collector.module.ts b/src/staking-module-data-collector/staking-module-data-collector.module.ts index c4c33944..4f1dc34a 100644 --- a/src/staking-module-data-collector/staking-module-data-collector.module.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from 'common/config'; import { StakingModuleDataCollectorService } from './staking-module-data-collector.service'; -import { SecurityModule } from 'contracts/security'; import { StakingModuleGuardModule } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerModule } from 'guardian/duplicates'; import { GuardianMetricsModule } from 'guardian/guardian-metrics'; @@ -10,7 +9,6 @@ import { StakingRouterModule } from 'contracts/staking-router'; @Module({ imports: [ ConfigModule, - SecurityModule, StakingModuleGuardModule, KeysDuplicationCheckerModule, GuardianMetricsModule, diff --git a/src/staking-module-data-collector/staking-module-data-collector.service.ts b/src/staking-module-data-collector/staking-module-data-collector.service.ts index c48d1f16..4e472e48 100644 --- a/src/staking-module-data-collector/staking-module-data-collector.service.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.service.ts @@ -5,7 +5,6 @@ import { getVettedUnusedKeys } from './vetted-keys'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { Meta } from 'keys-api/interfaces/Meta'; import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; -import { SecurityService } from 'contracts/security'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerService } from 'guardian/duplicates'; import { GuardianMetricsService } from 'guardian/guardian-metrics'; @@ -15,14 +14,12 @@ type State = { operatorsByModules: SROperatorListWithModule[]; meta: Meta; lidoKeys: RegistryKey[]; - blockData: BlockData; }; @Injectable() export class StakingModuleDataCollectorService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private securityService: SecurityService, private stakingModuleGuardService: StakingModuleGuardService, private keysDuplicationCheckerService: KeysDuplicationCheckerService, private guardianMetricsService: GuardianMetricsService, @@ -36,7 +33,6 @@ export class StakingModuleDataCollectorService { operatorsByModules, meta, lidoKeys, - blockData, }: State): Promise { return await Promise.all( operatorsByModules.map(async ({ operators, module: stakingModule }) => { @@ -56,7 +52,7 @@ export class StakingModuleDataCollectorService { await this.stakingRouterService.isModuleDepositsPaused( stakingModule.id, { - blockHash: blockData.blockHash, + blockHash: meta.elBlockSnapshot.blockHash, }, ); @@ -65,7 +61,7 @@ export class StakingModuleDataCollectorService { nonce: stakingModule.nonce, stakingModuleId: stakingModule.id, stakingModuleAddress: stakingModule.stakingModuleAddress, - blockHash: blockData.blockHash, + blockHash: meta.elBlockSnapshot.blockHash, lastChangedBlockHash: meta.elBlockSnapshot.lastChangedBlockHash, vettedUnusedKeys: moduleVettedUnusedKeys, duplicatedKeys: [], From 2961c7115ee53f7fb5d71bf3e6272d7770b97f88 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 2 Sep 2024 19:08:50 +0200 Subject: [PATCH 32/94] fix: integrity checks --- .../deposits-registry/deposits-registry.service.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 5be405e0..abdf75e3 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -131,9 +131,7 @@ export class DepositRegistryService { const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; // TODO: replace timer with metric - const isRootValid = await this.sanityChecker.verifyUpdatedEvents( - currentBlockNumber, - ); + const isRootValid = await this.sanityChecker.verifyUpdatedEvents(toBlock); if (!isRootValid) { this.logger.error('Integrity check failed on block', { @@ -183,8 +181,8 @@ export class DepositRegistryService { const lastEventBlockHash = lastEvent?.blockHash; const isValid = await this.sanityChecker.verifyFreshEvents( - blockNumber, - blockHash, + lastEvent.blockNumber, + lastEvent.blockHash, freshEvents, ); From 347f38e24dbcd25940c80f133a1c3b05f1814eb4 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 3 Sep 2024 10:50:43 +0200 Subject: [PATCH 33/94] fix: remove unused check --- .../deposits-registry.service.ts | 4 ++-- .../sanity-checker/sanity-checker.service.ts | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index abdf75e3..edb1a55b 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -105,8 +105,8 @@ export class DepositRegistryService { ); await this.sanityChecker.verifyEventsChunk( - currentBlockNumber, - currentBlockHash, + chunkStartBlock, + chunkToBlock, chunkEventGroup.events, ); diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index 94989871..631f9397 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -85,27 +85,19 @@ export class DepositRegistrySanityCheckerService { } public async verifyEventsChunk( - blockNumber: number, - blockHash: string, + chunkStartBlock: number, + chunkToBlock: number, events: VerifiedDepositEvent[], ) { - const isReorgFound = this.findReorganization( - blockNumber, - blockHash, - events, - ); - - if (isReorgFound) return false; + if (!events.length) return; const tree = await this.indexEventsChunk(events); this.logger.log('Deposit events chunk was verified', { - blockNumber, - blockHash, + chunkStartBlock, + chunkToBlock, depositRoot: tree.getRoot(), }); - - return true; } public async verifyFreshEvents( From 61c79400241739f6ff8e8116ab6447f284528c40 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 3 Sep 2024 13:46:34 +0400 Subject: [PATCH 34/94] fix: improve valdiation algorithm --- .../keys-validation.service.ts | 142 ++++++++---------- .../keys-validation/keys-validation.spec.ts | 138 +++++++---------- .../staking-module-guard.spec.ts | 5 - 3 files changed, 120 insertions(+), 165 deletions(-) diff --git a/src/guardian/keys-validation/keys-validation.service.ts b/src/guardian/keys-validation/keys-validation.service.ts index 43064e4a..ed3c83ba 100644 --- a/src/guardian/keys-validation/keys-validation.service.ts +++ b/src/guardian/keys-validation/keys-validation.service.ts @@ -2,7 +2,6 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { KeyValidatorInterface, bufferFromHexString, - Pubkey, WithdrawalCredentialsBuffer, Key, } from '@lido-nestjs/key-validation'; @@ -13,9 +12,7 @@ import { DEPOSIT_DATA_LRU_CACHE_SIZE } from './constants'; import { ProviderService } from 'provider'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -type DepositData = { - key: Pubkey; - depositSignature: string; +type DepositKey = RegistryKey & { withdrawalCredentials: WithdrawalCredentialsBuffer; genesisForkVersion: Buffer; }; @@ -46,29 +43,44 @@ export class KeysValidationService { ); const genesisForkVersion: Uint8Array = await this.forkVersion(); const genesisForkVersionBuffer = Buffer.from(genesisForkVersion.buffer); - const depositDataList = this.createDepositDataList( - keys, - withdrawalCredentialsBuffer, - genesisForkVersionBuffer, - ); - return await this.findInvalidKeys(keys, depositDataList); - } - async findInvalidKeys( - keys: RegistryKey[], - depositDataList: DepositData[], - ): Promise { - const validatedKeys = await this.validateKeys(depositDataList); + const { cachedInvalidKeyList, uncachedDepositKeyList } = + this.partitionCachedData( + keys, + withdrawalCredentialsBuffer, + genesisForkVersionBuffer, + ); + + this.logger.log('Validation status of deposit keys:', { + cachedInvalidKeyCount: cachedInvalidKeyList.length, + keysNeedingValidationCount: uncachedDepositKeyList.length, + totalKeysCount: keys.length, + }); + + const validatedDepositKeyList: [DepositKey & Key, boolean][] = + await this.keyValidator.validateKeys(uncachedDepositKeyList); + this.updateCache(validatedDepositKeyList); + + const invalidKeys = this.filterInvalidKeys(validatedDepositKeyList); + + return [...cachedInvalidKeyList, ...invalidKeys]; + } + + filterInvalidKeys( + validatedKeys: [DepositKey & Key, boolean][], + ): RegistryKey[] { return validatedKeys.reduce( (invalidKeys, [data, isValid]) => { if (!isValid) { - const matchingInvalidKeys = keys.filter( - (key) => - key.key === data.key && - key.depositSignature === data.depositSignature, - ); - invalidKeys.push(...matchingInvalidKeys); + invalidKeys.push({ + key: data.key, + depositSignature: data.depositSignature, + operatorIndex: data.operatorIndex, + used: data.used, + index: data.index, + moduleAddress: data.moduleAddress, + }); } return invalidKeys; }, @@ -76,61 +88,47 @@ export class KeysValidationService { ); } - /* - * Validate data with use of cache - */ - public async validateKeys( - depositDataList: DepositData[], - ): Promise<[Key & DepositData, boolean][]> { - const { cachedDepositData, uncachedDepositData } = - this.partitionCachedData(depositDataList); - - this.logger.log('Validation status of deposit keys:', { - cachedKeysCount: cachedDepositData.length, - keysNeedingValidationCount: uncachedDepositData.length, - totalKeysCount: depositDataList.length, - }); - - const validatedDepositData: [Key & DepositData, boolean][] = - await this.keyValidator.validateKeys(uncachedDepositData); - - this.updateCache(validatedDepositData); - - return [...cachedDepositData, ...validatedDepositData]; - } - /** * Partition the deposit data into cached invalid data and uncached data. * @param depositDataList List of deposit data to check against the cache * @returns An object containing cached invalid data and uncached data */ - private partitionCachedData(depositDataList: DepositData[]): { - cachedDepositData: [DepositData, boolean][]; - uncachedDepositData: DepositData[]; + private partitionCachedData( + keys: RegistryKey[], + withdrawalCredentialsBuffer: WithdrawalCredentialsBuffer, + genesisForkVersionBuffer: Buffer, + ): { + cachedInvalidKeyList: RegistryKey[]; + uncachedDepositKeyList: DepositKey[]; } { - return depositDataList.reduce<{ - cachedDepositData: [DepositData, boolean][]; - uncachedDepositData: DepositData[]; + return keys.reduce<{ + cachedInvalidKeyList: RegistryKey[]; + uncachedDepositKeyList: DepositKey[]; }>( - (acc, depositData) => { - const cacheResult = this.getCachedDepositData(depositData); - - if (cacheResult === false || cacheResult === true) { - acc.cachedDepositData.push([depositData, cacheResult]); + (acc, key) => { + const depositKey = { + ...key, + withdrawalCredentials: withdrawalCredentialsBuffer, + genesisForkVersion: genesisForkVersionBuffer, + }; + const cacheResult = this.getCachedDepositData(depositKey); + + if (cacheResult === false) { + acc.cachedInvalidKeyList.push(key); } if (cacheResult === undefined) { - acc.uncachedDepositData.push(depositData); + acc.uncachedDepositKeyList.push(depositKey); } return acc; }, - { cachedDepositData: [], uncachedDepositData: [] }, + { cachedInvalidKeyList: [], uncachedDepositKeyList: [] }, ); } - private getCachedDepositData(depositData: DepositData): boolean | undefined { - return this.depositDataCache.get(this.serializeDepositData(depositData)); + private getCachedDepositData(depositKey: DepositKey): boolean | undefined { + return this.depositDataCache.get(this.serializeDepositData(depositKey)); } private async forkVersion(): Promise { @@ -144,7 +142,7 @@ export class KeysValidationService { return forkVersion; } - private async updateCache(validatedKeys: [Key & DepositData, boolean][]) { + private async updateCache(validatedKeys: [Key & DepositKey, boolean][]) { validatedKeys.forEach(([depositData, isValid]) => this.depositDataCache.set( this.serializeDepositData(depositData), @@ -153,24 +151,12 @@ export class KeysValidationService { ); } - private serializeDepositData(depositData: DepositData): string { + private serializeDepositData(depositKey: DepositKey): string { return JSON.stringify({ - ...depositData, - withdrawalCredentials: depositData.withdrawalCredentials.toString('hex'), - genesisForkVersion: depositData.genesisForkVersion.toString('hex'), + key: depositKey.key, + depositSignature: depositKey.depositSignature, + withdrawalCredentials: depositKey.withdrawalCredentials.toString('hex'), + genesisForkVersion: depositKey.genesisForkVersion.toString('hex'), }); } - - private createDepositDataList( - keys: RegistryKey[], - withdrawalCredentialsBuffer: WithdrawalCredentialsBuffer, - genesisForkVersionBuffer: Buffer, - ): DepositData[] { - return keys.map((key) => ({ - key: key.key, - depositSignature: key.depositSignature, - withdrawalCredentials: withdrawalCredentialsBuffer, - genesisForkVersion: genesisForkVersionBuffer, - })); - } } diff --git a/src/guardian/keys-validation/keys-validation.spec.ts b/src/guardian/keys-validation/keys-validation.spec.ts index f3fe1f70..5e79ed9a 100644 --- a/src/guardian/keys-validation/keys-validation.spec.ts +++ b/src/guardian/keys-validation/keys-validation.spec.ts @@ -26,7 +26,9 @@ describe('KeysValidationService', () => { const wc = '0x010000000000000000000000dc62f9e8c34be08501cdef4ebde0a280f576d762'; - beforeEach(async () => { + const fork = GENESIS_FORK_VERSION_BY_CHAIN_ID[5]; + + beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forRoot(), @@ -47,95 +49,67 @@ describe('KeysValidationService', () => { jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); }); - it('should find and return invalid keys from the provided list', async () => { - // Test scenario where new invalid keys are added to the list - const result = await keysValidationService.getInvalidKeys( - [...validKeys, invalidKey1, invalidKey2], - wc, - ); - - const expected = [invalidKey1, invalidKey2]; + describe('Validate again if signature was changed', () => { + beforeEach(() => { + validateKeysFun.mockClear(); + }); - const fork = GENESIS_FORK_VERSION_BY_CHAIN_ID[5]; + it('validate without use of cache', async () => { + const keysForValidation = [...validKeys, invalidKey1, invalidKey2]; + const result = await keysValidationService.getInvalidKeys( + keysForValidation, + wc, + ); - const depositData = [...validKeys, invalidKey1, invalidKey2].map((key) => ({ - key: key.key, - depositSignature: key.depositSignature, - withdrawalCredentials: bufferFromHexString(wc), - genesisForkVersion: Buffer.from(fork.buffer), - })); + // we extended RegistryKey to satisfy DepositData type + const depositKeyList = keysForValidation.map((key) => ({ + ...key, + depositSignature: key.depositSignature, + withdrawalCredentials: bufferFromHexString(wc), + genesisForkVersion: Buffer.from(fork.buffer), + })); - expect(validateKeysFun).toBeCalledTimes(1); - expect(validateKeysFun).toBeCalledWith(depositData); - expect(result).toEqual(expect.arrayContaining(expected)); - expect(result.length).toEqual(expected.length); + expect(validateKeysFun).toBeCalledTimes(1); + expect(validateKeysFun).toBeCalledWith(depositKeyList); + expect(result).toEqual([invalidKey1, invalidKey2]); + }); - validateKeysFun.mockClear(); - // Test scenario where one invalid key was removed from request's list - const newResult = await keysValidationService.getInvalidKeys( - [...validKeys, invalidKey1], - wc, - ); + it('validate with use of cache ', async () => { + // Test scenario where one invalid key was removed from request's list + const newResult = await keysValidationService.getInvalidKeys( + [...validKeys, invalidKey1, invalidKey2], + wc, + ); - const newExpected = [invalidKey1]; - const invalidKey2DepositData = JSON.stringify({ - key: invalidKey2.key, - depositSignature: invalidKey2.depositSignature, - withdrawalCredentials: wc.replace(/^0x/, ''), - genesisForkVersion: Buffer.from(fork.buffer).toString('hex'), + expect(validateKeysFun).toBeCalledTimes(1); + expect(validateKeysFun).toBeCalledWith([]); + expect(newResult).toEqual([invalidKey1, invalidKey2]); }); - expect( - keysValidationService['depositDataCache'].get(invalidKey2DepositData), - ).toEqual(false); - expect(validateKeysFun).toBeCalledTimes(1); - expect(validateKeysFun).toBeCalledWith([]); - expect(newResult).toEqual(expect.arrayContaining(newExpected)); - expect(newResult.length).toEqual(newExpected.length); - }); - - it('should validate key again if signature was changed', async () => { - // if signature was changed we need to repeat validation - // invalid key could become valid and visa versa - // Test scenario where new invalid keys are added to the list - const result = await keysValidationService.getInvalidKeys( - [...validKeys, invalidKey1, invalidKey2], - wc, - ); - const expected = [invalidKey1, invalidKey2]; - const fork = GENESIS_FORK_VERSION_BY_CHAIN_ID[5]; - const depositData = [...validKeys, invalidKey1, invalidKey2].map((key) => ({ - key: key.key, - depositSignature: key.depositSignature, - withdrawalCredentials: bufferFromHexString(wc), - genesisForkVersion: Buffer.from(fork.buffer), - })); - expect(validateKeysFun).toBeCalledTimes(1); - expect(validateKeysFun).toBeCalledWith(depositData); - expect(result).toEqual(expect.arrayContaining(expected)); - expect(result.length).toEqual(expected.length); - validateKeysFun.mockClear(); - // Test scenario where one invalid key was changed - const newResult = await keysValidationService.getInvalidKeys( - [ + it('validate without use of cache because of signature change', async () => { + const invalidKey2Fix = { + ...invalidKey2, + depositSignature: invalidKey2GoodSign, + }; + const keyForValidation = [ ...validKeys, invalidKey1, - { ...invalidKey2, depositSignature: invalidKey2GoodSign }, - ], - wc, - ); - const newDepositData = [ - { ...invalidKey2, depositSignature: invalidKey2GoodSign }, - ].map((key) => ({ - key: key.key, - depositSignature: key.depositSignature, - withdrawalCredentials: bufferFromHexString(wc), - genesisForkVersion: Buffer.from(fork.buffer), - })); - const newExpected = [invalidKey1]; - expect(validateKeysFun).toBeCalledTimes(1); - expect(validateKeysFun).toBeCalledWith(newDepositData); - expect(newResult).toEqual(expect.arrayContaining(newExpected)); - expect(newResult.length).toEqual(newExpected.length); + // change signature on valid + invalidKey2Fix, + ]; + const newResult = await keysValidationService.getInvalidKeys( + keyForValidation, + wc, + ); + const depositKeyList = [invalidKey2Fix].map((key) => ({ + ...key, + withdrawalCredentials: bufferFromHexString(wc), + genesisForkVersion: Buffer.from(fork.buffer), + })); + + expect(validateKeysFun).toBeCalledTimes(1); + expect(validateKeysFun).toBeCalledWith(depositKeyList); + expect(newResult).toEqual([invalidKey1]); + }); }); }); diff --git a/src/guardian/staking-module-guard/staking-module-guard.spec.ts b/src/guardian/staking-module-guard/staking-module-guard.spec.ts index 846667ab..479fb90d 100644 --- a/src/guardian/staking-module-guard/staking-module-guard.spec.ts +++ b/src/guardian/staking-module-guard/staking-module-guard.spec.ts @@ -307,7 +307,6 @@ describe('StakingModuleGuardService', () => { nonce: 1, blockNumber: 100, lastChangedBlockHash: 'hash', - invalidKeysFound: false, }; const result = stakingModuleGuardService.isSameContractsStates( { ...state }, @@ -322,7 +321,6 @@ describe('StakingModuleGuardService', () => { nonce: 1, blockNumber: 100, lastChangedBlockHash: 'hash', - invalidKeysFound: false, }; const result = stakingModuleGuardService.isSameContractsStates(state, { ...state, @@ -337,7 +335,6 @@ describe('StakingModuleGuardService', () => { nonce: 1, blockNumber: 100, lastChangedBlockHash: 'hash', - invalidKeysFound: false, }; const result = stakingModuleGuardService.isSameContractsStates(state, { ...state, @@ -352,7 +349,6 @@ describe('StakingModuleGuardService', () => { nonce: 1, blockNumber: 100, lastChangedBlockHash: 'hash', - invalidKeysFound: false, }; const result = stakingModuleGuardService.isSameContractsStates(state, { ...state, @@ -369,7 +365,6 @@ describe('StakingModuleGuardService', () => { nonce: 1, blockNumber: 100, lastChangedBlockHash: 'hash', - invalidKeysFound: false, }; const result = stakingModuleGuardService.isSameContractsStates(state, { ...state, From 883017e7b008c75311c133bddf87fbd047138828 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 3 Sep 2024 13:24:18 +0200 Subject: [PATCH 35/94] fix: deposits tests --- .../deposits-registry/sanity-checker/index.ts | 2 ++ src/guardian/guardian.service.spec.ts | 4 +-- test/duplicates-v3.e2e-spec.ts | 29 +++++++++---------- test/duplicates.e2e-spec.ts | 23 +++++++-------- test/front-run-v3.e2e-spec.ts | 29 +++++++++---------- test/front-run.e2e-spec.ts | 29 +++++++++---------- test/guardian-balance-monitoring.e2e-spec.ts | 17 +++++------ test/helpers/test-setup.ts | 12 ++++---- test/invalid-keys-v3.e2e-spec.ts | 19 +++++------- 9 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/index.ts b/src/contracts/deposits-registry/sanity-checker/index.ts index 04e24741..3af948e6 100644 --- a/src/contracts/deposits-registry/sanity-checker/index.ts +++ b/src/contracts/deposits-registry/sanity-checker/index.ts @@ -1,2 +1,4 @@ export * from './sanity-checker.module'; export * from './sanity-checker.service'; + +export * from './integrity-checker'; diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index d314cb55..694feec7 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -7,7 +7,7 @@ import { LoggerService } from '@nestjs/common'; import { ConfigModule } from 'common/config'; import { PrometheusModule } from 'common/prometheus'; import { GuardianModule } from 'guardian'; -import { DepositModule } from 'contracts/deposit'; +import { DepositsRegistryModule } from 'contracts/deposits-registry'; import { SecurityModule } from 'contracts/security'; import { RepositoryModule, RepositoryService } from 'contracts/repository'; import { LidoModule } from 'contracts/lido'; @@ -45,7 +45,7 @@ describe('GuardianService', () => { PrometheusModule, GuardianModule, RepositoryModule, - DepositModule, + DepositsRegistryModule, SecurityModule, LidoModule, MessagesModule, diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index bc22d066..9a1f8791 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -37,10 +37,9 @@ import { initLevelDB, } from './helpers/test-setup'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { ProviderService } from 'provider'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { SecurityService } from 'contracts/security'; @@ -52,7 +51,7 @@ import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; import { mockKey, mockKey2, mockKeyEvent } from './helpers/keys-fixtures'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; describe('Deposits in case of duplicates', () => { @@ -60,10 +59,9 @@ describe('Deposits in case of duplicates', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let securityService: SecurityService; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; let signKeyLevelDBService: SignKeyLevelDBService; @@ -106,10 +104,10 @@ describe('Deposits in case of duplicates', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); // mock unvetting method of contract // as we dont use real keys api and work with fixtures of operators and keys @@ -121,7 +119,7 @@ describe('Deposits in case of duplicates', () => { const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -130,7 +128,6 @@ describe('Deposits in case of duplicates', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -177,7 +174,7 @@ describe('Deposits in case of duplicates', () => { const { wallet } = await makeDeposit(depositData, providerService); // Set deposit cache - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -310,7 +307,7 @@ describe('Deposits in case of duplicates', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -448,7 +445,7 @@ describe('Deposits in case of duplicates', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -587,7 +584,7 @@ describe('Deposits in case of duplicates', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -708,7 +705,7 @@ describe('Deposits in case of duplicates', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -743,7 +740,7 @@ describe('Deposits in case of duplicates', () => { }, }); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -809,7 +806,7 @@ describe('Deposits in case of duplicates', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 6e87a17d..5090d31b 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -37,11 +37,10 @@ import { initLevelDB, } from './helpers/test-setup'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { ProviderService } from 'provider'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { Server } from 'ganache'; @@ -50,7 +49,7 @@ import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-e import { StakingRouterService } from 'staking-router'; import { addGuardians } from './helpers/dsm'; import { makeServer } from './server'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { BlsService } from 'bls'; import { mockKey, mockKey2, mockKeyEvent } from './helpers/keys-fixtures'; @@ -59,10 +58,9 @@ describe('ganache e2e tests', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let sendDepositMessage: jest.SpyInstance; let sendPauseMessage: jest.SpyInstance; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; let stakingModuleGuardService: StakingModuleGuardService; @@ -97,15 +95,15 @@ describe('ganache e2e tests', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); }; const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -114,7 +112,6 @@ describe('ganache e2e tests', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -155,7 +152,7 @@ describe('ganache e2e tests', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -271,7 +268,7 @@ describe('ganache e2e tests', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -389,7 +386,7 @@ describe('ganache e2e tests', () => { const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -486,7 +483,7 @@ describe('ganache e2e tests', () => { const { depositData } = signDeposit(pk, sk); await makeDeposit(depositData, providerService); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 1753eada..1b897d43 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -46,18 +46,17 @@ import { initLevelDB, } from './helpers/test-setup'; import { SecurityService } from 'contracts/security'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { GuardianMessageService } from 'guardian/guardian-message'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { BlsService } from 'bls'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { mockKey, mockKey2 } from './helpers/keys-fixtures'; @@ -72,9 +71,8 @@ describe('ganache e2e tests', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let securityService: SecurityService; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; @@ -97,7 +95,7 @@ describe('ganache e2e tests', () => { const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -106,7 +104,6 @@ describe('ganache e2e tests', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -147,10 +144,10 @@ describe('ganache e2e tests', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); // mock unvetting method of contract // as we dont use real keys api and work with fixtures of operators and keys @@ -196,7 +193,7 @@ describe('ganache e2e tests', () => { // add in deposit cache event of deposit on key with lido creds // TODO: replace with real deposit - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -265,7 +262,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -341,7 +338,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -402,7 +399,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -476,7 +473,7 @@ describe('ganache e2e tests', () => { 'inconsistent kapi requests data', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -514,7 +511,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -559,7 +556,7 @@ describe('ganache e2e tests', () => { mockedMeta(currentBlock, currentBlock.hash), ); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [ { valid: true, diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 5d21aece..aed27bf0 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -46,18 +46,17 @@ import { closeServer, initLevelDB, } from './helpers/test-setup'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { GuardianMessageService } from 'guardian/guardian-message'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { BlsService } from 'bls'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { mockKey, mockKey2 } from './helpers/keys-fixtures'; @@ -72,10 +71,9 @@ describe('ganache e2e tests', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let sendDepositMessage: jest.SpyInstance; let sendPauseMessage: jest.SpyInstance; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; @@ -95,7 +93,7 @@ describe('ganache e2e tests', () => { const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -104,7 +102,6 @@ describe('ganache e2e tests', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -138,10 +135,10 @@ describe('ganache e2e tests', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); }; beforeEach(async () => { @@ -180,7 +177,7 @@ describe('ganache e2e tests', () => { // add in deposit cache event of deposit on key with lido creds // TODO: replace with real deposit - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -237,7 +234,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -314,7 +311,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -375,7 +372,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -459,7 +456,7 @@ describe('ganache e2e tests', () => { 'inconsistent kapi requests data', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -497,7 +494,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -542,7 +539,7 @@ describe('ganache e2e tests', () => { mockedMeta(currentBlock, currentBlock.hash), ); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [ { valid: true, diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index 8f8f05a5..2a64cddd 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -36,16 +36,15 @@ import { // Contract and Service Imports import { SecurityService } from 'contracts/security'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { BlsService } from 'bls'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; // Test Data import { mockKey, mockKey2 } from './helpers/keys-fixtures'; @@ -56,8 +55,7 @@ describe('Guardian balance monitoring test', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; @@ -123,7 +121,7 @@ describe('Guardian balance monitoring test', () => { } const setupDefaultCache = async (blockNumber) => { - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: blockNumber, @@ -188,7 +186,7 @@ describe('Guardian balance monitoring test', () => { }; const initializeLevelDBServices = async (moduleRef) => { - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); }; @@ -197,7 +195,6 @@ describe('Guardian balance monitoring test', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); }; const initializeKeyEventServices = (moduleRef) => { @@ -244,10 +241,10 @@ describe('Guardian balance monitoring test', () => { const mockDepositCacheMethods = () => { jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); }; const mockUnvettingMethod = () => { diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index d2d37a08..bd01eed3 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing'; import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { PrometheusModule } from 'common/prometheus'; -import { DepositModule } from 'contracts/deposit'; +import { DepositsRegistryModule } from 'contracts/deposits-registry'; import { LidoModule } from 'contracts/lido'; import { RepositoryModule } from 'contracts/repository'; import { SecurityModule } from 'contracts/security'; @@ -10,7 +10,7 @@ import { GuardianModule } from 'guardian'; import { KeysApiModule } from 'keys-api/keys-api.module'; import { GanacheProviderModule } from 'provider'; import { WalletModule } from 'wallet'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; export const setupTestingModule = async () => { @@ -25,7 +25,7 @@ export const setupTestingModule = async () => { WalletModule, KeysApiModule, LidoModule, - DepositModule, + DepositsRegistryModule, SecurityModule, ], }).compile(); @@ -34,7 +34,7 @@ export const setupTestingModule = async () => { }; export const initLevelDB = async ( - levelDBService: LevelDBService, + levelDBService: DepositsRegistryStoreService, signKeyLevelDBService: SignKeyLevelDBService, ) => { await levelDBService.initialize(); @@ -43,7 +43,7 @@ export const initLevelDB = async ( export const closeServer = async ( server, - levelDBService: LevelDBService, + levelDBService: DepositsRegistryStoreService, signKeyLevelDBService: SignKeyLevelDBService, ) => { await server.close(); @@ -54,7 +54,7 @@ export const closeServer = async ( }; export const closeLevelDB = async ( - levelDBService: LevelDBService, + levelDBService: DepositsRegistryStoreService, signKeyLevelDBService: SignKeyLevelDBService, ) => { await levelDBService.deleteCache(); diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index fdbd19e4..80676629 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -37,20 +37,19 @@ import { initLevelDB, } from './helpers/test-setup'; import { SecurityService } from 'contracts/security'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { makeServer } from './server'; import { mockKey } from './helpers/keys-fixtures'; @@ -59,9 +58,8 @@ describe('ganache e2e tests', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let keyValidator: KeyValidatorInterface; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; @@ -86,7 +84,7 @@ describe('ganache e2e tests', () => { const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -95,7 +93,6 @@ describe('ganache e2e tests', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -139,10 +136,10 @@ describe('ganache e2e tests', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); // sign validation validateKeys = jest.spyOn(keyValidator, 'validateKeys'); @@ -172,7 +169,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -303,7 +300,7 @@ describe('ganache e2e tests', () => { test('should validate again if deposit data was changed', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, From 36091d25a26e048d01ad3b787e76e7e1d0ad7a7d Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 3 Sep 2024 13:46:49 +0200 Subject: [PATCH 36/94] fix: fresh events verifying --- .../deposits-registry.service.ts | 3 +- .../integrity-checker.service.ts | 10 +++---- .../sanity-checker/sanity-checker.service.ts | 30 +++++++++++++++---- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index edb1a55b..1157a8b7 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -181,8 +181,7 @@ export class DepositRegistryService { const lastEventBlockHash = lastEvent?.blockHash; const isValid = await this.sanityChecker.verifyFreshEvents( - lastEvent.blockNumber, - lastEvent.blockHash, + blockHash, freshEvents, ); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 6feb08c3..08368a04 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -72,8 +72,8 @@ export class DepositIntegrityCheckerService { * @param {number} blockNumber - Block number to check the deposit root against. * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. */ - public async checkFinalizedRoot(blockNumber: number): Promise { - return this.checkRoot(blockNumber, this.finalizedTree); + public async checkFinalizedRoot(tag: string | number): Promise { + return this.checkRoot(tag, this.finalizedTree); } /** @@ -82,13 +82,13 @@ export class DepositIntegrityCheckerService { * @param {DepositTree} tree - Deposit tree to use for comparison. * @returns {Promise} A promise that resolves if the roots match, otherwise logs an error and throws. */ - private async checkRoot(blockNumber: number, tree: DepositTree) { + private async checkRoot(tag: string | number, tree: DepositTree) { const localRoot = tree.getRoot(); - const remoteRoot = await this.getDepositRoot(blockNumber); + const remoteRoot = await this.getDepositRoot(tag); if (localRoot === remoteRoot) { this.logger.log('Integrity check successfully completed', { - blockNumber, + tag, }); return true; } diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index 631f9397..d70a228a 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -100,22 +100,42 @@ export class DepositRegistrySanityCheckerService { }); } + /** + * Verifies the integrity of the latest deposit events. If the last event is absent, + * it checks the validity of the last finalized root using the current block hash. + * Otherwise, it checks for reorganizations and matches the deposit root of the events. + * + * @param {string} currentBlockHash - The hash of the current block being processed. + * @param {VerifiedDepositEvent[]} freshEvents - Array of freshly verified deposit events. + * @returns {Promise} - Returns true if the deposit root matches and no reorganization is found, otherwise false. + */ public async verifyFreshEvents( - blockNumber: number, - blockHash: string, - events: VerifiedDepositEvent[], + currentBlockHash: string, + freshEvents: VerifiedDepositEvent[], ) { + const lastEvent = freshEvents[freshEvents.length - 1]; + + // If there is no last event, validate the finalized root for the current block hash. + if (!lastEvent) { + return this.depositsIntegrityChecker.checkFinalizedRoot(currentBlockHash); + } + + const { blockHash, blockNumber } = lastEvent; + + // Check for a reorganization in the blockchain that might affect the deposit events. const isReorgFound = this.findReorganization( blockNumber, blockHash, - events, + freshEvents, ); + // If a reorganization is found, return false as the events might not be in the correct state. if (isReorgFound) return false; + // Check if the deposit root of the events matches the expected values. const isDepositRootMatches = await this.checkFreshEventsChunk( blockNumber, - events, + freshEvents, ); return isDepositRootMatches; From cf62e6a748edc6a199e4b0cafbabd306e750a7e0 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 3 Sep 2024 13:55:25 +0200 Subject: [PATCH 37/94] fix: e2e test --- test/invalid-keys.e2e-spec.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 9e6b95d3..babe0bfe 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -37,20 +37,19 @@ import { closeServer, initLevelDB, } from './helpers/test-setup'; -import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { LevelDBService } from 'contracts/deposit/leveldb'; +import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; -import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; +import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; import { makeServer } from './server'; import { mockKey } from './helpers/keys-fixtures'; @@ -59,12 +58,11 @@ describe('ganache e2e tests', () => { let providerService: ProviderService; let keysApiService: KeysApiService; let guardianService: GuardianService; - let depositService: DepositService; let keyValidator: KeyValidatorInterface; let sendDepositMessage: jest.SpyInstance; let sendPauseMessage: jest.SpyInstance; let validateKeys: jest.SpyInstance; - let levelDBService: LevelDBService; + let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; @@ -84,7 +82,7 @@ describe('ganache e2e tests', () => { const setupTestingServices = async (moduleRef) => { // leveldb service - levelDBService = moduleRef.get(LevelDBService); + levelDBService = moduleRef.get(DepositsRegistryStoreService); signKeyLevelDBService = moduleRef.get(SignKeyLevelDBService); await initLevelDB(levelDBService, signKeyLevelDBService); @@ -93,7 +91,6 @@ describe('ganache e2e tests', () => { depositIntegrityCheckerService = moduleRef.get( DepositIntegrityCheckerService, ); - depositService = moduleRef.get(DepositService); const blsService = moduleRef.get(BlsService); await blsService.onModuleInit(); @@ -131,10 +128,10 @@ describe('ganache e2e tests', () => { // deposit cache mocks jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); jest .spyOn(depositIntegrityCheckerService, 'checkFinalizedRoot') - .mockImplementation(() => Promise.resolve()); + .mockImplementation(() => Promise.resolve(true)); // sign validation validateKeys = jest.spyOn(keyValidator, 'validateKeys'); @@ -157,7 +154,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -262,7 +259,7 @@ describe('ganache e2e tests', () => { test('should validate again if deposit data was changed', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, From 75ae3000acfa7d1c67ef058d2b3cc7cec55d85ec Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 3 Sep 2024 16:28:14 +0200 Subject: [PATCH 38/94] feat: update ethers & deposit indexing with finalized tag --- package.json | 4 +- .../deposits-registry.constants.ts | 1 - .../deposits-registry.service.ts | 12 +- src/guardian/guardian.service.ts | 4 +- src/provider/provider.service.ts | 4 +- yarn.lock | 466 +++--------------- 6 files changed, 71 insertions(+), 420 deletions(-) diff --git a/package.json b/package.json index 51e5343e..4f1ea577 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dependencies": { "@chainsafe/blst": "^0.2.4", "@chainsafe/ssz": "^0.9.2", - "@ethersproject/providers": "^5.4.5", + "@ethersproject/providers": "5.7.2", "@lido-nestjs/fetch": "^1.3.1", "@lido-nestjs/key-validation": "^7.4.0", "@lido-nestjs/middleware": "^1.1.1", @@ -48,7 +48,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "compare-versions": "^6.1.0", - "ethers": "^5.4.7", + "ethers": "5.7.2", "glob": "^7.1.2", "kafkajs": "^1.15.0", "level": "^8.0.1", diff --git a/src/contracts/deposits-registry/deposits-registry.constants.ts b/src/contracts/deposits-registry/deposits-registry.constants.ts index e3db17f6..9640c057 100644 --- a/src/contracts/deposits-registry/deposits-registry.constants.ts +++ b/src/contracts/deposits-registry/deposits-registry.constants.ts @@ -10,7 +10,6 @@ export const DEPLOYMENT_BLOCK_NETWORK: { export const DEPOSIT_EVENTS_CACHE_LAG_BLOCKS = 100; export const DEPOSIT_EVENTS_STEP = 10_000; -export const DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ headers: { diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 1157a8b7..5795a02b 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -4,7 +4,6 @@ import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; import { DEPOSIT_EVENTS_STEP, - DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE, DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, } from './deposits-registry.constants'; import { @@ -29,11 +28,7 @@ export class DepositRegistryService { private store: DepositsRegistryStoreService, ) {} - public async handleNewBlock(blockNumber: number): Promise { - if (blockNumber % DEPOSIT_EVENTS_CACHE_UPDATE_BLOCK_RATE !== 0) return; - - // The event cache is stored with an N block lag to avoid caching data from uncle blocks - // so we don't worry about blockHash here + public async handleNewBlock(): Promise { await this.updateEventsCache(); } @@ -41,8 +36,7 @@ export class DepositRegistryService { await this.store.initialize(); const cachedEvents = await this.store.getEventsCache(); await this.sanityChecker.initialize(cachedEvents); - // it is necessary to load fresh events before integrity check - // because we can only compare roots of the last 128 blocks. + await this.updateEventsCache(); } @@ -72,7 +66,7 @@ export class DepositRegistryService { const fetchTimeStart = performance.now(); const [currentBlock, initialCache] = await Promise.all([ - this.providerService.getBlock(), + this.providerService.getBlock('finalized'), this.getCachedEvents(), ]); diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 05e557f4..c185c5d3 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -98,7 +98,7 @@ export class GuardianService implements OnModuleInit { // The event cache is stored with an N block lag to avoid caching data from uncle blocks // so we don't worry about blockHash here - await this.depositService.updateEventsCache(); + // TODO: rewrite signingKeyEventsCacheService await this.signingKeyEventsCacheService.updateEventsCache(); this.subscribeToModulesUpdates(); @@ -169,7 +169,7 @@ export class GuardianService implements OnModuleInit { currMeta.elBlockSnapshot.lastChangedBlockHash, ); - await this.depositService.handleNewBlock(blockNumber); + await this.depositService.handleNewBlock(); const { stakingModulesData, blockData } = await this.collectData( operatorsByModules, diff --git a/src/provider/provider.service.ts b/src/provider/provider.service.ts index bb107bd2..d591b2d9 100644 --- a/src/provider/provider.service.ts +++ b/src/provider/provider.service.ts @@ -54,8 +54,8 @@ export class ProviderService { /** * Returns current block */ - public async getBlock(): Promise { - return await this.provider.getBlock('latest'); + public async getBlock(tag: string | number = 'latest'): Promise { + return await this.provider.getBlock(tag); } /** diff --git a/yarn.lock b/yarn.lock index 3c975a48..948aea51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -461,22 +461,7 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613" - integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w== - dependencies: - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -491,19 +476,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0": - version "5.5.1" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5" - integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg== - dependencies: - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/web" "^5.5.0" - "@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" @@ -517,17 +489,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d" - integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA== - dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" @@ -539,17 +500,6 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.5.0", "@ethersproject/address@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f" - integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw== - dependencies: - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" @@ -561,13 +511,6 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" -"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090" - integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" @@ -575,14 +518,6 @@ dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3" - integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" @@ -591,15 +526,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527" - integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - bn.js "^4.11.9" - "@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" @@ -609,13 +535,6 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" - integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== - dependencies: - "@ethersproject/logger" "^5.5.0" - "@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" @@ -623,13 +542,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e" - integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ== - dependencies: - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" @@ -637,22 +549,6 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197" - integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg== - dependencies: - "@ethersproject/abi" "^5.5.0" - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/contracts@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" @@ -669,20 +565,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" -"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9" - integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg== - dependencies: - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" @@ -698,24 +580,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6" - integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q== - dependencies: - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/basex" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/pbkdf2" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/wordlists" "^5.5.0" - "@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" @@ -734,25 +598,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325" - integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ== - dependencies: - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hdnode" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/pbkdf2" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - aes-js "3.0.0" - scrypt-js "3.0.1" - "@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" @@ -772,14 +617,6 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492" - integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg== - dependencies: - "@ethersproject/bytes" "^5.5.0" - js-sha3 "0.8.0" - "@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" @@ -788,23 +625,11 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" - integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== - "@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.5.2", "@ethersproject/networks@^5.5.0": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.2.tgz#784c8b1283cd2a931114ab428dae1bd00c07630b" - integrity sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ== - dependencies: - "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" @@ -812,14 +637,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050" - integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" @@ -828,13 +645,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" -"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995" - integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA== - dependencies: - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" @@ -842,31 +652,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.5.3", "@ethersproject/providers@^5.4.5": - version "5.5.3" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.3.tgz#56c2b070542ac44eb5de2ed3cf6784acd60a3130" - integrity sha512-ZHXxXXXWHuwCQKrgdpIkbzMNJMvs+9YWemanwp1fA7XZEv7QlilseysPvQe0D7Q7DlkJX/w/bGA1MdgK2TbGvA== - dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/basex" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/web" "^5.5.0" - bech32 "1.1.4" - ws "7.4.6" - "@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.5.3": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" @@ -893,14 +678,6 @@ bech32 "1.1.4" ws "7.4.6" -"@ethersproject/random@5.5.1", "@ethersproject/random@^5.5.0": - version "5.5.1" - resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.1.tgz#7cdf38ea93dc0b1ed1d8e480ccdaf3535c555415" - integrity sha512-YaU2dQ7DuhL5Au7KbcQLHxcRHfgyNgvFV4sQOo0HrtW3Zkrc9ctWNz8wXQ4uCSfSDsqX2vcjhroxU5RQRV0nqA== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" @@ -909,14 +686,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0" - integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" @@ -925,15 +694,6 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7" - integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - hash.js "1.1.7" - "@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" @@ -943,18 +703,6 @@ "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" -"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0" - integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - bn.js "^4.11.9" - elliptic "6.5.4" - hash.js "1.1.7" - "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" @@ -967,18 +715,6 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f" - integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw== - dependencies: - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/solidity@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" @@ -991,15 +727,6 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549" - integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" @@ -1009,21 +736,6 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908" - integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA== - dependencies: - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" - "@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" @@ -1039,15 +751,6 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" -"@ethersproject/units@5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e" - integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag== - dependencies: - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/units@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" @@ -1057,27 +760,6 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/wallet@5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75" - integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q== - dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/hdnode" "^5.5.0" - "@ethersproject/json-wallets" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/wordlists" "^5.5.0" - "@ethersproject/wallet@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" @@ -1099,17 +781,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/web@5.5.1", "@ethersproject/web@^5.5.0": - version "5.5.1" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.1.tgz#cfcc4a074a6936c657878ac58917a61341681316" - integrity sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg== - dependencies: - "@ethersproject/base64" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" @@ -1121,17 +792,6 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f" - integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" @@ -1654,9 +1314,9 @@ integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== "@typechain/ethers-v5@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.1.2.tgz#dbf31663f75cc50f2d9ad232f6e354c6a3e81465" - integrity sha512-sD4HVkTL5aIJa3Ft+CmqiOapba0zzZ8xa+QywcWH40Rm/dcxvZWwcCMnnI3En0JebkxOcAVfH3do+kQ9rKSxYw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.2.0.tgz#d559cffe0efe6bdbc20e644b817f6fa8add5e8f8" + integrity sha512-jfcmlTvaaJjng63QsT49MT6R1HFhtO/TBMWbyzPFSzMmVIqb2tL6prnKBs4ZJrSvmgIXWy+ttSjpaxCTq8D/Tw== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" @@ -1829,7 +1489,12 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.1", "@types/prettier@^2.1.5": +"@types/prettier@^2.1.1": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/prettier@^2.1.5": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== @@ -2186,7 +1851,7 @@ acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0: aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" - integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0= + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -2353,7 +2018,7 @@ argparse@^1.0.7: array-back@^1.0.3, array-back@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/array-back/-/array-back-1.0.4.tgz#644ba7f095f7ffcf7c43b5f0dc39d3c1f03c063b" - integrity sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs= + integrity sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw== dependencies: typical "^2.6.0" @@ -2549,7 +2214,7 @@ braces@^3.0.1, braces@~3.0.2: brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== browser-level@^1.0.1: version "1.0.1" @@ -3108,13 +2773,20 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" +debug@^4.1.1: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -3541,43 +3213,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -ethers@^5.4.7: - version "5.5.4" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.4.tgz#e1155b73376a2f5da448e4a33351b57a885f4352" - integrity sha512-N9IAXsF8iKhgHIC6pquzRgPBJEzc9auw3JoRkaKe+y4Wl/LFBtDDunNe7YmdomontECAcC5APaAgWZBiu1kirw== - dependencies: - "@ethersproject/abi" "5.5.0" - "@ethersproject/abstract-provider" "5.5.1" - "@ethersproject/abstract-signer" "5.5.0" - "@ethersproject/address" "5.5.0" - "@ethersproject/base64" "5.5.0" - "@ethersproject/basex" "5.5.0" - "@ethersproject/bignumber" "5.5.0" - "@ethersproject/bytes" "5.5.0" - "@ethersproject/constants" "5.5.0" - "@ethersproject/contracts" "5.5.0" - "@ethersproject/hash" "5.5.0" - "@ethersproject/hdnode" "5.5.0" - "@ethersproject/json-wallets" "5.5.0" - "@ethersproject/keccak256" "5.5.0" - "@ethersproject/logger" "5.5.0" - "@ethersproject/networks" "5.5.2" - "@ethersproject/pbkdf2" "5.5.0" - "@ethersproject/properties" "5.5.0" - "@ethersproject/providers" "5.5.3" - "@ethersproject/random" "5.5.1" - "@ethersproject/rlp" "5.5.0" - "@ethersproject/sha2" "5.5.0" - "@ethersproject/signing-key" "5.5.0" - "@ethersproject/solidity" "5.5.0" - "@ethersproject/strings" "5.5.0" - "@ethersproject/transactions" "5.5.0" - "@ethersproject/units" "5.5.0" - "@ethersproject/wallet" "5.5.0" - "@ethersproject/web" "5.5.1" - "@ethersproject/wordlists" "5.5.0" - -ethers@^5.5.4: +ethers@5.7.2, ethers@^5.5.4: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3822,7 +3458,7 @@ finalhandler@~1.1.2: find-replace@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" - integrity sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A= + integrity sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA== dependencies: array-back "^1.0.4" test-value "^2.1.0" @@ -3941,7 +3577,7 @@ fs-monkey@1.0.3: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" @@ -4041,7 +3677,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -4053,6 +3689,18 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4077,7 +3725,12 @@ globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -4144,7 +3797,7 @@ hdr-histogram-percentiles-obj@^3.0.0: hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -4275,7 +3928,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -5012,7 +4665,7 @@ jsonc-parser@3.0.0: jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" @@ -5357,9 +5010,9 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4: +minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5678,7 +5331,7 @@ on-finished@^2.3.0, on-finished@~2.3.0: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" @@ -5841,7 +5494,7 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" @@ -5920,7 +5573,12 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.1.2, prettier@^2.3.2: +prettier@^2.1.2: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^2.3.2: version "2.4.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== @@ -6655,7 +6313,7 @@ test-exclude@^6.0.0: test-value@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/test-value/-/test-value-2.1.0.tgz#11da6ff670f3471a73b625ca4f3fdcf7bb748291" - integrity sha1-Edpv9nDzRxpztiXKTz/c97t0gpE= + integrity sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w== dependencies: array-back "^1.0.3" typical "^2.6.0" @@ -6894,9 +6552,9 @@ type@^2.5.0: integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== typechain@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.1.2.tgz#c8784d6155a8e69397ca47f438a3b4fb2aa939da" - integrity sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA== + version "5.2.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.2.0.tgz#10525a44773a34547eb2eed8978cb72c0a39a0f4" + integrity sha512-0INirvQ+P+MwJOeMct+WLkUE4zov06QxC96D+i3uGFEHoiSkZN70MKDQsaj8zkL86wQwByJReI2e7fOUwECFuw== dependencies: "@types/prettier" "^2.1.1" command-line-args "^4.0.7" @@ -6934,7 +6592,7 @@ typescript@^4.3.5: typical@^2.6.0, typical@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" - integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0= + integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== unique-filename@^1.1.1: version "1.1.1" @@ -7201,7 +6859,7 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^3.0.0: version "3.0.3" From 57d8f66b9665bda2c272d67fa5474bc6879caf35 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 5 Sep 2024 00:42:05 +0200 Subject: [PATCH 39/94] fix: use finalizedBlockNumber instead toBlock --- .../deposits-registry.constants.ts | 1 - .../deposits-registry.service.ts | 34 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.constants.ts b/src/contracts/deposits-registry/deposits-registry.constants.ts index 9640c057..8f7c7c74 100644 --- a/src/contracts/deposits-registry/deposits-registry.constants.ts +++ b/src/contracts/deposits-registry/deposits-registry.constants.ts @@ -8,7 +8,6 @@ export const DEPLOYMENT_BLOCK_NETWORK: { [CHAINS.Holesky]: 0, }; -export const DEPOSIT_EVENTS_CACHE_LAG_BLOCKS = 100; export const DEPOSIT_EVENTS_STEP = 10_000; export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 5795a02b..e98e7ffa 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -2,10 +2,7 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { performance } from 'perf_hooks'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; -import { - DEPOSIT_EVENTS_STEP, - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS, -} from './deposits-registry.constants'; +import { DEPOSIT_EVENTS_STEP } from './deposits-registry.constants'; import { VerifiedDepositEventsCache, VerifiedDepositedEventGroup, @@ -65,14 +62,14 @@ export class DepositRegistryService { public async updateEventsCache(): Promise { const fetchTimeStart = performance.now(); - const [currentBlock, initialCache] = await Promise.all([ + const [finalizedBlock, initialCache] = await Promise.all([ this.providerService.getBlock('finalized'), this.getCachedEvents(), ]); - const { number: currentBlockNumber, hash: currentBlockHash } = currentBlock; + const { number: finalizedBlockNumber, hash: finalizedBlockHash } = + finalizedBlock; const firstNotCachedBlock = initialCache.headers.endBlock + 1; - const toBlock = currentBlockNumber - DEPOSIT_EVENTS_CACHE_LAG_BLOCKS; const totalEventsCount = initialCache.data.length; let newEventsCount = 0; @@ -80,18 +77,21 @@ export class DepositRegistryService { // verify blockchain const isCacheValid = this.sanityChecker.verifyCacheBlock( initialCache, - currentBlockNumber, + finalizedBlockNumber, ); if (!isCacheValid) return; for ( let block = firstNotCachedBlock; - block <= toBlock; + block <= finalizedBlockNumber; block += DEPOSIT_EVENTS_STEP ) { const chunkStartBlock = block; - const chunkToBlock = Math.min(toBlock, block + DEPOSIT_EVENTS_STEP - 1); + const chunkToBlock = Math.min( + finalizedBlockNumber, + block + DEPOSIT_EVENTS_STEP - 1, + ); const chunkEventGroup = await this.fetcher.fetchEventsFallOver( chunkStartBlock, @@ -104,6 +104,10 @@ export class DepositRegistryService { chunkEventGroup.events, ); + // Even if the cache is not valid we can't help but write it down + // because the delay in updating the cache will eventually cause + // the getAllDepositedEvents method to take a very long time to process, as changes + // will be accumulated and not processed. await this.store.insertEventsCacheBatch({ headers: { ...initialCache.headers, @@ -115,7 +119,7 @@ export class DepositRegistryService { newEventsCount += chunkEventGroup.events.length; this.logger.log('Historical events are fetched', { - toBlock, + finalizedBlockNumber, startBlock: chunkStartBlock, endBlock: chunkToBlock, }); @@ -125,12 +129,14 @@ export class DepositRegistryService { const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; // TODO: replace timer with metric - const isRootValid = await this.sanityChecker.verifyUpdatedEvents(toBlock); + const isRootValid = await this.sanityChecker.verifyUpdatedEvents( + finalizedBlockNumber, + ); if (!isRootValid) { this.logger.error('Integrity check failed on block', { - currentBlock, - currentBlockHash, + finalizedBlock, + finalizedBlockHash, }); } From bdc538a913c292684e898e659a7423ac088d4dbb Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 5 Sep 2024 14:18:33 +0200 Subject: [PATCH 40/94] fix: ganache tests --- .../deposits-registry.constants.ts | 4 ++ .../deposits-registry.module.ts | 50 ++++++++++++++----- .../deposits-registry.service.ts | 9 +++- .../block-guard/block-guard.module.ts | 2 +- src/guardian/guardian.module.ts | 2 +- src/guardian/guardian.service.spec.ts | 2 +- test/helpers/test-setup.ts | 2 +- 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.constants.ts b/src/contracts/deposits-registry/deposits-registry.constants.ts index 8f7c7c74..417a338f 100644 --- a/src/contracts/deposits-registry/deposits-registry.constants.ts +++ b/src/contracts/deposits-registry/deposits-registry.constants.ts @@ -17,3 +17,7 @@ export const DEPOSIT_CACHE_DEFAULT = Object.freeze({ }, data: [], }); + +export const DEPOSIT_REGISTRY_FINALIZED_TAG = Symbol.for( + 'DEPOSIT_REGISTRY_FINALIZED_TAG', +); diff --git a/src/contracts/deposits-registry/deposits-registry.module.ts b/src/contracts/deposits-registry/deposits-registry.module.ts index 6fb033b7..caad9c70 100644 --- a/src/contracts/deposits-registry/deposits-registry.module.ts +++ b/src/contracts/deposits-registry/deposits-registry.module.ts @@ -1,19 +1,43 @@ -import { Module } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { SecurityModule } from 'contracts/security'; import { DepositsRegistryStoreModule } from './store'; import { DepositRegistryService } from './deposits-registry.service'; -import { DEPOSIT_CACHE_DEFAULT } from './deposits-registry.constants'; +import { + DEPOSIT_CACHE_DEFAULT, + DEPOSIT_REGISTRY_FINALIZED_TAG, +} from './deposits-registry.constants'; import { DepositsRegistryFetcherModule } from './fetcher'; import { DepositRegistrySanityCheckerModule } from './sanity-checker'; -@Module({ - imports: [ - SecurityModule, - DepositsRegistryFetcherModule, - DepositRegistrySanityCheckerModule, - DepositsRegistryStoreModule.register(DEPOSIT_CACHE_DEFAULT), - ], - providers: [DepositRegistryService], - exports: [DepositRegistryService], -}) -export class DepositsRegistryModule {} +@Module({}) +export class DepositsRegistryModule { + /** + * Registers the deposits module with a specific tag to handle block finality. + * The `finalizedTag` is primarily used to address issues with the Ganache handling of the 'finalized' tag, + * where it needs to be substituted with 'latest' for end-to-end tests. This tag is necessary only on a full Ethereum node + * to avoid issues with blockchain reorganizations. + * In a production environment, this argument should either be empty or set to 'finalized'. + * + * @param {string} [finalizedTag='finalized'] - The tag to be used for identifying the status of blocks concerning finality. + * @returns {DynamicModule} - The dynamic module configuration for the Deposits Registry. + */ + static register(finalizedTag = 'finalized'): DynamicModule { + return { + module: DepositsRegistryModule, + imports: [ + SecurityModule, + DepositsRegistryFetcherModule, + DepositRegistrySanityCheckerModule, + DepositsRegistryStoreModule.register(DEPOSIT_CACHE_DEFAULT), + ], + providers: [ + DepositRegistryService, + { + provide: DEPOSIT_REGISTRY_FINALIZED_TAG, + useValue: finalizedTag, + }, + ], + exports: [DepositRegistryService], + }; + } +} diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index e98e7ffa..fb66e3e3 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -2,7 +2,10 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { performance } from 'perf_hooks'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; -import { DEPOSIT_EVENTS_STEP } from './deposits-registry.constants'; +import { + DEPOSIT_EVENTS_STEP, + DEPOSIT_REGISTRY_FINALIZED_TAG, +} from './deposits-registry.constants'; import { VerifiedDepositEventsCache, VerifiedDepositedEventGroup, @@ -23,6 +26,8 @@ export class DepositRegistryService { private sanityChecker: DepositRegistrySanityCheckerService, private fetcher: DepositsRegistryFetcherService, private store: DepositsRegistryStoreService, + + @Inject(DEPOSIT_REGISTRY_FINALIZED_TAG) private finalizedTag: string, ) {} public async handleNewBlock(): Promise { @@ -63,7 +68,7 @@ export class DepositRegistryService { const fetchTimeStart = performance.now(); const [finalizedBlock, initialCache] = await Promise.all([ - this.providerService.getBlock('finalized'), + this.providerService.getBlock(this.finalizedTag), this.getCachedEvents(), ]); diff --git a/src/guardian/block-guard/block-guard.module.ts b/src/guardian/block-guard/block-guard.module.ts index 8673e50f..27e0d068 100644 --- a/src/guardian/block-guard/block-guard.module.ts +++ b/src/guardian/block-guard/block-guard.module.ts @@ -9,7 +9,7 @@ import { WalletModule } from 'wallet'; @Module({ imports: [ LidoModule, - DepositsRegistryModule, + DepositsRegistryModule.register(), SecurityModule, StakingModuleGuardModule, WalletModule, diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index c200930a..1e906dac 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -16,7 +16,7 @@ import { UnvettingModule } from './unvetting/unvetting.module'; @Module({ imports: [ - DepositsRegistryModule, + DepositsRegistryModule.register(), SecurityModule, LidoModule, MessagesModule, diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index 694feec7..1d7ee0e8 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -45,7 +45,7 @@ describe('GuardianService', () => { PrometheusModule, GuardianModule, RepositoryModule, - DepositsRegistryModule, + DepositsRegistryModule.register('latest'), SecurityModule, LidoModule, MessagesModule, diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index bd01eed3..2fedc2ac 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -25,7 +25,7 @@ export const setupTestingModule = async () => { WalletModule, KeysApiModule, LidoModule, - DepositsRegistryModule, + DepositsRegistryModule.register('latest'), SecurityModule, ], }).compile(); From 018d4b1c1bfe64b789fb4f7f4dd962797826f0d5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 5 Sep 2024 17:10:33 +0200 Subject: [PATCH 41/94] fix: e2e tests --- test/duplicates-v3.e2e-spec.ts | 27 ++++++++++++++++++++------- test/front-run-v3.e2e-spec.ts | 16 +++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index 9a1f8791..02436791 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -167,12 +167,12 @@ describe('Deposits in case of duplicates', () => { test( 'skip deposits for module if find duplicated key across operator', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); - - // TODO: mine new block instead + // TODO: mine new block instead (? @Anna) const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); + // Set deposit cache await levelDBService.setCachedEvents({ data: [], @@ -252,6 +252,9 @@ describe('Deposits in case of duplicates', () => { ); expect(unvetSigningKeys).toBeCalledTimes(1); + // Mine a new block + await providerService.provider.send('evm_mine', []); + // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ @@ -303,9 +306,9 @@ describe('Deposits in case of duplicates', () => { test( 'skip deposits for module if find duplicated key across operators of two modules', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -390,6 +393,9 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toBeCalledTimes(1); expect(sendPauseMessage).toBeCalledTimes(0); + // Mine a new block + await providerService.provider.send('evm_mine', []); + // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ @@ -441,9 +447,9 @@ describe('Deposits in case of duplicates', () => { test( 'skip deposits for module if find duplicated key across operators of one modules', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -528,6 +534,9 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toBeCalledTimes(1); expect(sendPauseMessage).toBeCalledTimes(0); + // Mine a new block + await providerService.provider.send('evm_mine', []); + // after deleting duplicates in staking module, // council will resume deposits to module @@ -580,9 +589,9 @@ describe('Deposits in case of duplicates', () => { test( 'added unused keys for that deposit was already made', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -657,6 +666,9 @@ describe('Deposits in case of duplicates', () => { ); expect(unvetSigningKeys).toBeCalledTimes(1); + // Mine a new block + await providerService.provider.send('evm_mine', []); + // after deleting duplicates in staking module, // council will resume deposits to module const newBlock = await providerService.provider.getBlock('latest'); @@ -701,10 +713,11 @@ describe('Deposits in case of duplicates', () => { ); test('adding not vetted duplicate will not set on soft pause module', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); + await levelDBService.setCachedEvents({ data: [], headers: { diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 1b897d43..42a313ce 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -303,8 +303,10 @@ describe('ganache e2e tests', () => { mockKey2, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -377,8 +379,10 @@ describe('ganache e2e tests', () => { }, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -436,8 +440,10 @@ describe('ganache e2e tests', () => { }, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -451,7 +457,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(2); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 1, @@ -459,7 +465,7 @@ describe('ganache e2e tests', () => { ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 2, From af7e9741eeebe88d29e235540ba1b83cf373804b Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 5 Sep 2024 18:35:08 +0200 Subject: [PATCH 42/94] fix: e2e tests --- test/duplicates.e2e-spec.ts | 13 +++++++++---- test/front-run.e2e-spec.ts | 16 +++++++++++----- test/invalid-keys-v3.e2e-spec.ts | 17 ++++++++++++----- test/invalid-keys.e2e-spec.ts | 10 ++++++++-- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 5090d31b..8844b014 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -148,9 +148,9 @@ describe('ganache e2e tests', () => { test( 'skip deposit if find duplicated key', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -202,6 +202,7 @@ describe('ganache e2e tests', () => { expect(isOnPause).toBe(false); await guardianService.handleNewBlock(); + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); // just skip on this iteration deposit for Curated staking module @@ -216,6 +217,8 @@ describe('ganache e2e tests', () => { ); expect(sendPauseMessage).toBeCalledTimes(0); + await providerService.provider.send('evm_mine', []); + // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ @@ -264,9 +267,9 @@ describe('ganache e2e tests', () => { test( 'skip deposit if find duplicated key in another staking module', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -333,6 +336,7 @@ describe('ganache e2e tests', () => { ); expect(sendPauseMessage).toBeCalledTimes(0); + await providerService.provider.send('evm_mine', []); // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ @@ -382,9 +386,9 @@ describe('ganache e2e tests', () => { test( 'added unused keys for that deposit was already made', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], @@ -437,6 +441,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(1); expect(sendPauseMessage).toBeCalledTimes(0); + await providerService.provider.send('evm_mine', []); // after deleting duplicates in staking module, // council will resume deposits to module @@ -479,9 +484,9 @@ describe('ganache e2e tests', () => { ); test('adding not vetted duplicate will not set on soft pause module', async () => { - const currentBlock = await providerService.provider.getBlock('latest'); const { depositData } = signDeposit(pk, sk); await makeDeposit(depositData, providerService); + const currentBlock = await providerService.provider.getBlock('latest'); await levelDBService.setCachedEvents({ data: [], diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index aed27bf0..6b8ff002 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -275,8 +275,10 @@ describe('ganache e2e tests', () => { mockKey2, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -350,8 +352,10 @@ describe('ganache e2e tests', () => { }, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -409,8 +413,10 @@ describe('ganache e2e tests', () => { }, ]; + const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( - currentBlock, + newBlock, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -424,7 +430,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(2); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 1, @@ -432,7 +438,7 @@ describe('ganache e2e tests', () => { ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 2, diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index 80676629..a93e02e1 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -197,15 +197,20 @@ describe('ganache e2e tests', () => { moduleAddress: NOP_REGISTRY, }; + const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); + const { wallet } = await makeDeposit(depositData, providerService); + + const blockAfterDeposit = await providerService.provider.getBlock( + 'latest', + ); + const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, + blockAfterDeposit, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, [keyWithWrongSign], ); - const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); - const { wallet } = await makeDeposit(depositData, providerService); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); @@ -226,7 +231,7 @@ describe('ganache e2e tests', () => { expect(sendUnvetMessage).toBeCalledTimes(1); expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: blockAfterDeposit.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: curatedModule.id, @@ -239,7 +244,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(1); expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: blockAfterDeposit.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: sdvtModule.id, @@ -247,6 +252,8 @@ describe('ganache e2e tests', () => { ); expect(sendPauseMessage).toBeCalledTimes(0); + await providerService.provider.send('evm_mine', []); + // if depositData was not changed it will not validate again const newBlock = await providerService.provider.getBlock('latest'); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index babe0bfe..d0784b97 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -185,8 +185,12 @@ describe('ganache e2e tests', () => { moduleAddress: NOP_REGISTRY, }; + const blockAfterDeposit = await providerService.provider.getBlock( + 'latest', + ); + const { sdvtModule } = setupMockModules( - currentBlock, + blockAfterDeposit, keysApiService, [mockOperator1, mockOperator2], mockedDvtOperators, @@ -212,7 +216,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(1); expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: blockAfterDeposit.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: sdvtModule.id, @@ -221,8 +225,10 @@ describe('ganache e2e tests', () => { expect(sendPauseMessage).toBeCalledTimes(0); // if depositData was not changed it will not validate again + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); + setupMockModules( newBlock, keysApiService, From 6956e9c7fd2e52e359d25553bda7371ac08470ef Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 6 Sep 2024 15:51:50 +0200 Subject: [PATCH 43/94] feat: add logging to cannotDeposit --- src/guardian/guardian.service.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index c185c5d3..3ab25d1c 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -339,13 +339,9 @@ export class GuardianService implements OnModuleInit { blockData.theftHappened, blockData.alreadyPausedDeposits, blockData.depositedEvents.isValid, + stakingModuleData.stakingModuleId, ) ) { - this.logger.warn('Deposits are not available', { - stakingModuleId: stakingModuleData.stakingModuleId, - blockHash: blockData.blockHash, - isDepositsCacheValid: blockData.depositedEvents.isValid, - }); return; } @@ -362,6 +358,7 @@ export class GuardianService implements OnModuleInit { theftHappened: boolean, alreadyPausedDeposits: boolean, isDepositsCacheValid: boolean, + stakingModuleId: number, ): boolean { const keysForUnvetting = [ ...stakingModuleData.invalidKeys, @@ -370,13 +367,26 @@ export class GuardianService implements OnModuleInit { ]; // if neither of this conditions is true, deposits are allowed for module - return ( + const isCannot = keysForUnvetting.length > 0 || stakingModuleData.unresolvedDuplicatedKeys.length > 0 || alreadyPausedDeposits || theftHappened || stakingModuleData.isModuleDepositsPaused || - !isDepositsCacheValid - ); + !isDepositsCacheValid; + + if (isCannot) { + this.logger.warn('Deposits are not available', { + keysForUnvetting: keysForUnvetting.length, + duplicates: stakingModuleData.unresolvedDuplicatedKeys.length, + alreadyPausedDeposits, + theftHappened, + isModuleDepositsPaused: stakingModuleData.isModuleDepositsPaused, + isDepositsCacheValid, + stakingModuleId, + }); + } + + return isCannot; } } From ee0039c2ab96fde13fc1fe77542caa8b68ff7579 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 9 Sep 2024 17:06:53 +0400 Subject: [PATCH 44/94] fix: get unveetin keys status from keys api --- src/guardian/duplicates/keys.fixtures.ts | 7 +- src/guardian/guardian.service.spec.ts | 14 +- src/guardian/guardian.service.ts | 27 +- .../keys-validation.service.ts | 1 + src/guardian/keys-validation/keys.fixtures.ts | 4 + .../staking-module-guard/keys.fixtures.ts | 260 +-------- src/guardian/unvetting/fixtures.ts | 12 +- src/keys-api/interfaces/RegistryKey.ts | 4 + .../interfaces/SRModuleListResponse.ts | 7 + src/keys-api/keys-api.service.ts | 6 + .../staking-module-data-collector.service.ts | 176 +++--- .../vetted-keys.spec.ts | 65 --- .../vetted-keys.ts | 17 - test/duplicates-v3.e2e-spec.ts | 501 ++++++++++-------- test/duplicates.e2e-spec.ts | 218 ++++---- test/front-run-v3.e2e-spec.ts | 291 ++++++---- test/front-run.e2e-spec.ts | 292 ++++++---- test/guardian-balance-monitoring.e2e-spec.ts | 42 +- test/helpers/deposit.ts | 6 + test/helpers/keys-fixtures.ts | 2 + test/helpers/mockKeysApi.ts | 229 ++++---- test/helpers/test-setup.ts | 8 +- test/invalid-keys-v3.e2e-spec.ts | 204 ++++--- test/invalid-keys.e2e-spec.ts | 111 ++-- 24 files changed, 1267 insertions(+), 1237 deletions(-) create mode 100644 src/keys-api/interfaces/SRModuleListResponse.ts delete mode 100644 src/staking-module-data-collector/vetted-keys.spec.ts delete mode 100644 src/staking-module-data-collector/vetted-keys.ts diff --git a/src/guardian/duplicates/keys.fixtures.ts b/src/guardian/duplicates/keys.fixtures.ts index e045b0f9..a7d4efe0 100644 --- a/src/guardian/duplicates/keys.fixtures.ts +++ b/src/guardian/duplicates/keys.fixtures.ts @@ -1,6 +1,7 @@ import { SigningKeyEvent } from 'contracts/signing-key-events-cache/interfaces/event.interface'; +import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -export const keyMock1 = { +export const keyMock1: RegistryKey = { key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', depositSignature: '0x8a77d9411781360cc107344a99f6660b206d2c708ae7fa35565b76ec661a0b86b6c78f5b5691d2cf469c27d0655dfc6311451a9e0501f3c19c6f7e35a770d1a908bfec7cba2e07339dc633b8b6626216ce76ec0fa48ee56aaaf2f9dc7ccb2fe2', @@ -8,9 +9,10 @@ export const keyMock1 = { used: false, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', index: 52, + vetted: true, }; -export const keyMock2 = { +export const keyMock2: RegistryKey = { key: '0xa9bfaa8207ee6c78644c079ffc91b6e5abcc5eede1b7a06abb8fb40e490a75ea269c178dd524b65185299d2bbd2eb7b2', depositSignature: '0xaa5f2a1053ba7d197495df44d4a32b7ae10265cf9e38560a16b782978c0a24271a113c9538453b7e45f35cb64c7adb460d7a9fe8c8ce6b8c80ca42fd5c48e180c73fc08f7d35ba32e39f32c902fd333faf47611827f0b7813f11c4c518dd2e59', @@ -18,6 +20,7 @@ export const keyMock2 = { used: false, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', index: 51, + vetted: true, }; export const eventMock1: SigningKeyEvent = { diff --git a/src/guardian/guardian.service.spec.ts b/src/guardian/guardian.service.spec.ts index 0c4e6272..f233e583 100644 --- a/src/guardian/guardian.service.spec.ts +++ b/src/guardian/guardian.service.spec.ts @@ -75,16 +75,14 @@ describe('GuardianService', () => { it('should exit if the previous call is not completed', async () => { // OneAtTime test const getOperatorsAndModulesMock = jest - .spyOn(keysApiService, 'getOperatorListWithModule') + .spyOn(keysApiService, 'getModules') .mockImplementation(async () => ({ data: [], - meta: { - elBlockSnapshot: { - blockNumber: 0, - blockHash: 'string', - timestamp: 0, - lastChangedBlockHash: '', - }, + elBlockSnapshot: { + blockNumber: 0, + blockHash: 'string', + timestamp: 0, + lastChangedBlockHash: '', }, })); diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 3ca18b97..3bc3b04a 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -33,6 +33,9 @@ import { Meta } from 'keys-api/interfaces/Meta'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; import { StakingRouterService } from 'contracts/staking-router'; +import { SRModuleListResponse } from 'keys-api/interfaces/SRModuleListResponse'; +import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; +import { SRModule } from 'keys-api/interfaces'; @Injectable() export class GuardianService implements OnModuleInit { @@ -165,12 +168,10 @@ export class GuardianService implements OnModuleInit { try { // Fetch the minimum required data fro Keys Api to make an early exit - const { data: operatorsByModules, meta: firstRequestMeta } = - await this.keysApiService.getOperatorListWithModule(); + const { data: stakingModules, elBlockSnapshot: firstRequestMeta } = + await this.keysApiService.getModules(); - const { - elBlockSnapshot: { blockHash, blockNumber }, - } = firstRequestMeta; + const { blockHash, blockNumber } = firstRequestMeta; // Compare the block stored in memory from the previous iteration with the current block from the Keys API. const isNewBlock = this.isNeedToProcessNewState({ @@ -180,7 +181,7 @@ export class GuardianService implements OnModuleInit { if (!isNewBlock) return; - const stakingModulesCount = operatorsByModules.length; + const stakingModulesCount = stakingModules.length; this.logger.log('Staking modules loaded', { modulesCount: stakingModulesCount, @@ -192,7 +193,7 @@ export class GuardianService implements OnModuleInit { // check that there were no updates in Keys Api between two requests this.keysApiService.verifyMetaDataConsistency( - firstRequestMeta.elBlockSnapshot.lastChangedBlockHash, + firstRequestMeta.lastChangedBlockHash, secondRequestMeta.elBlockSnapshot.lastChangedBlockHash, ); @@ -202,7 +203,7 @@ export class GuardianService implements OnModuleInit { await this.depositService.handleNewBlock(blockNumber); const { stakingModulesData, blockData } = await this.collectData( - operatorsByModules, + stakingModules, firstRequestMeta, lidoKeys, ); @@ -242,13 +243,11 @@ export class GuardianService implements OnModuleInit { } private async collectData( - operatorsByModules: SROperatorListWithModule[], - meta: Meta, + stakingModules: SRModule[], + meta: ELBlockSnapshot, lidoKeys: RegistryKey[], ) { - const { - elBlockSnapshot: { blockHash, blockNumber }, - } = meta; + const { blockHash, blockNumber } = meta; const [blockData, stakingModulesData] = await Promise.all([ this.blockDataCollectorService.getCurrentBlockData({ @@ -258,7 +257,7 @@ export class GuardianService implements OnModuleInit { // Construct the Staking Module data array using information fetched from the Keys API, // identifying vetted unused keys and checking the module pause status this.stakingModuleDataCollectorService.collectStakingModuleData({ - operatorsByModules, + stakingModules, meta, lidoKeys, }), diff --git a/src/guardian/keys-validation/keys-validation.service.ts b/src/guardian/keys-validation/keys-validation.service.ts index ed3c83ba..b577a9c4 100644 --- a/src/guardian/keys-validation/keys-validation.service.ts +++ b/src/guardian/keys-validation/keys-validation.service.ts @@ -80,6 +80,7 @@ export class KeysValidationService { used: data.used, index: data.index, moduleAddress: data.moduleAddress, + vetted: data.vetted, }); } return invalidKeys; diff --git a/src/guardian/keys-validation/keys.fixtures.ts b/src/guardian/keys-validation/keys.fixtures.ts index a3de6fa1..e45c348a 100644 --- a/src/guardian/keys-validation/keys.fixtures.ts +++ b/src/guardian/keys-validation/keys.fixtures.ts @@ -10,6 +10,7 @@ export const validKeys: RegistryKey[] = [ used: false, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', index: 51, + vetted: true, }, { key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', @@ -19,6 +20,7 @@ export const validKeys: RegistryKey[] = [ used: false, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', index: 52, + vetted: true, }, ]; export const invalidKey1: RegistryKey = { @@ -29,6 +31,7 @@ export const invalidKey1: RegistryKey = { used: false, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 5, + vetted: true, }; export const invalidKey2: RegistryKey = { @@ -39,6 +42,7 @@ export const invalidKey2: RegistryKey = { used: false, moduleAddress: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', index: 54, + vetted: true, }; export const invalidKey2GoodSign = diff --git a/src/guardian/staking-module-guard/keys.fixtures.ts b/src/guardian/staking-module-guard/keys.fixtures.ts index d9a83f61..0d38d3b2 100644 --- a/src/guardian/staking-module-guard/keys.fixtures.ts +++ b/src/guardian/staking-module-guard/keys.fixtures.ts @@ -1,4 +1,6 @@ -export const vettedKeys = [ +import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; + +export const vettedKeys: RegistryKey[] = [ { key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', depositSignature: @@ -7,260 +9,6 @@ export const vettedKeys = [ used: false, moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', index: 101, - }, -]; - -export const vettedKeysDuplicatesAcrossModules: any = [ - { - stakingModuleId: 100, - vettedUnusedKeys: [ - { - key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', - depositSignature: - '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 100, - }, - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 101, - }, - ], - }, - { - stakingModuleId: 102, - vettedUnusedKeys: [ - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0xa13833d96f4b98291dbf428cb69e7a3bdce61c9d20efcdb276423c7d6199ebd10cf1728dbd418c592701a41983cb02330e736610be254f617140af48a9d20b31cdffdd1d4fc8c0776439fca3330337d33042768acf897000b9e5da386077be44', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 4, - }, - { - key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 5, - }, - ], - }, - { - stakingModuleId: 103, - vettedUnusedKeys: [ - { - key: '0x84ff489c1e07c75ac635914d4fa20bb37b30f7cf37a8fb85298a88e6f45daab122b43a352abce2132bdde96fd4a01599', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: 'another_module', - index: 5, - }, - ], - }, -]; - -export const vettedKeysDuplicatesAcrossOneModule: any = [ - { - stakingModuleId: 100, - vettedUnusedKeys: [ - { - key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', - depositSignature: - '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 100, - }, - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 101, - }, - { - key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', - depositSignature: - '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 102, - }, - ], - }, - { - stakingModuleId: 102, - vettedUnusedKeys: [ - { - key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 5, - }, - ], - }, - - { - stakingModuleId: 103, - vettedUnusedKeys: [ - { - key: '0x84ff489c1e07c75ac635914d4fa20bb37b30f7cf37a8fb85298a88e6f45daab122b43a352abce2132bdde96fd4a01599', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: 'another_module', - index: 5, - }, - ], - }, -]; - -export const vettedKeysDuplicatesAcrossOneModuleAndFew: any = [ - { - stakingModuleId: 100, - vettedUnusedKeys: [ - { - key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', - depositSignature: - '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 100, - }, - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 101, - }, - ], - }, - { - stakingModuleId: 102, - vettedUnusedKeys: [ - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0xa13833d96f4b98291dbf428cb69e7a3bdce61c9d20efcdb276423c7d6199ebd10cf1728dbd418c592701a41983cb02330e736610be254f617140af48a9d20b31cdffdd1d4fc8c0776439fca3330337d33042768acf897000b9e5da386077be44', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 4, - }, - { - key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 5, - }, - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0xa13833d96f4b98291dbf428cb69e7a3bdce61c9d20efcdb276423c7d6199ebd10cf1728dbd418c592701a41983cb02330e736610be254f617140af48a9d20b31cdffdd1d4fc8c0776439fca3330337d33042768acf897000b9e5da386077be44', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 6, - }, - ], - }, - { - stakingModuleId: 103, - vettedUnusedKeys: [ - { - key: '0x84ff489c1e07c75ac635914d4fa20bb37b30f7cf37a8fb85298a88e6f45daab122b43a352abce2132bdde96fd4a01599', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: 'another_module', - index: 5, - }, - ], - }, -]; - -export const vettedKeysWithoutDuplicates: any = [ - { - stakingModuleId: 100, - vettedUnusedKeys: [ - { - key: '0x9948d2becf42e9f76922bc6f664545e6f50401050af95785a984802d32a95c4c61f8e3de312b78167f86e047f83a7796', - depositSignature: - '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 100, - }, - { - key: '0x911dd3091cfb1b42c960e4f343ea98d9ee6a1dc8ef215afa976fb557bd627a901717c0008bc33a0bfea15f0dfe9c5d01', - depositSignature: - '0x898ac7072aa26d983f9ece384c4037966dde614b75dddf982f6a415f3107cb2569b96f6d1c44e608a250ac4bbe908df51473f0de2cf732d283b07d88f3786893124967b8697a8b93d31976e7ac49ab1e568f98db0bbb13384477e8357b6d7e9b', - operatorIndex: 0, - used: false, - moduleAddress: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', - index: 101, - }, - ], - }, - - { - stakingModuleId: 102, - vettedUnusedKeys: [ - { - key: '0x84e85db03bee714dbecf01914460d9576b7f7226030bdbeae9ee923bf5f8e01eec4f7dfe54aa7eca6f4bccce59a0bf42', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - index: 5, - }, - ], - }, - - { - stakingModuleId: 103, - vettedUnusedKeys: [ - { - key: '0x84ff489c1e07c75ac635914d4fa20bb37b30f7cf37a8fb85298a88e6f45daab122b43a352abce2132bdde96fd4a01599', - depositSignature: - '0xb024b67a2f6c579213529e143bd4ebb81c5a2dc385cb526de4a816c8fe0317ebfb38369b08622e9f27e62cce2811679a13a459d4e9a8d7bd00080c36b359c1ca03bdcf4a0fcbbc2e18fe9923d8c4edb503ade58bdefe690760611e3738d5e64f', - operatorIndex: 28, - used: false, - moduleAddress: 'another_module', - index: 5, - }, - ], + vetted: true, }, ]; diff --git a/src/guardian/unvetting/fixtures.ts b/src/guardian/unvetting/fixtures.ts index 7681946a..af0bf964 100644 --- a/src/guardian/unvetting/fixtures.ts +++ b/src/guardian/unvetting/fixtures.ts @@ -1,5 +1,7 @@ +import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; + // holesky -export const mockKeys = [ +export const mockKeys: RegistryKey[] = [ { key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', depositSignature: @@ -8,6 +10,7 @@ export const mockKeys = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 0, + vetted: true, }, { key: '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', @@ -17,6 +20,7 @@ export const mockKeys = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 1, + vetted: true, }, { key: '0x823c9c577aead54ac40c7986ceb8596eaf45df0140fe9b637bb8d465f878884e3f9e39914edf39c3c64f5720ec0be3a4', @@ -26,6 +30,7 @@ export const mockKeys = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 2, + vetted: true, }, { key: '0x837851278c4ab4a4641128a709c9c985f7e4c7c35082e5e2a75ae4ed712c8161b493b135b35d39ee8a65024122feb7c1', @@ -35,6 +40,7 @@ export const mockKeys = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 3, + vetted: true, }, { key: '0x80db3318374e7c1489e1f421a66bf1ef51a48f6ad02a6ad304c67fbbad60a0a5ce51a939aa008930c3b0ed25db63710f', @@ -44,10 +50,11 @@ export const mockKeys = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 0, + vetted: true, }, ]; -export const mockKeys2 = [ +export const mockKeys2: RegistryKey[] = [ ...mockKeys, { key: '0x8101cf19c664f22c5209c4129cf20629d8375a2de6a26f089ea37d142d000abe6b3585ab5bc7818c7449ed5089c86054', @@ -57,5 +64,6 @@ export const mockKeys2 = [ used: true, moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', index: 1, + vetted: true, }, ]; diff --git a/src/keys-api/interfaces/RegistryKey.ts b/src/keys-api/interfaces/RegistryKey.ts index 4b133b3f..4fd43f8d 100644 --- a/src/keys-api/interfaces/RegistryKey.ts +++ b/src/keys-api/interfaces/RegistryKey.ts @@ -24,4 +24,8 @@ export type RegistryKey = { * Staking module address */ moduleAddress: string; + /** + * Vetted key status + */ + vetted: boolean; }; diff --git a/src/keys-api/interfaces/SRModuleListResponse.ts b/src/keys-api/interfaces/SRModuleListResponse.ts new file mode 100644 index 00000000..9bbf7f3a --- /dev/null +++ b/src/keys-api/interfaces/SRModuleListResponse.ts @@ -0,0 +1,7 @@ +import { SRModule } from '.'; +import { ELBlockSnapshot } from './ELBlockSnapshot'; + +export type SRModuleListResponse = { + data: Array; + elBlockSnapshot: ELBlockSnapshot; +}; diff --git a/src/keys-api/keys-api.service.ts b/src/keys-api/keys-api.service.ts index e79931ad..9a1d5a59 100644 --- a/src/keys-api/keys-api.service.ts +++ b/src/keys-api/keys-api.service.ts @@ -7,6 +7,7 @@ import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { Configuration } from 'common/config'; import { GroupedByModuleOperatorListResponse } from './interfaces/GroupedByModuleOperatorListResponse'; import { InconsistentLastChangedBlockHash } from 'common/custom-errors'; +import { SRModuleListResponse } from './interfaces/SRModuleListResponse'; @Injectable() export class KeysApiService { @@ -80,6 +81,11 @@ export class KeysApiService { return result; } + public async getModules() { + const result = await this.fetch(`/v1/modules`); + return result; + } + /** * Verifies the consistency of metadata by comparing hashes. * @param firstRequestHash - Hash of the first request diff --git a/src/staking-module-data-collector/staking-module-data-collector.service.ts b/src/staking-module-data-collector/staking-module-data-collector.service.ts index 4e472e48..1ac12067 100644 --- a/src/staking-module-data-collector/staking-module-data-collector.service.ts +++ b/src/staking-module-data-collector/staking-module-data-collector.service.ts @@ -1,18 +1,17 @@ import { Injectable, LoggerService, Inject } from '@nestjs/common'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { StakingModuleData, BlockData } from 'guardian'; -import { getVettedUnusedKeys } from './vetted-keys'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -import { Meta } from 'keys-api/interfaces/Meta'; -import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { KeysDuplicationCheckerService } from 'guardian/duplicates'; import { GuardianMetricsService } from 'guardian/guardian-metrics'; import { StakingRouterService } from 'contracts/staking-router'; +import { SRModule } from 'keys-api/interfaces'; +import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; type State = { - operatorsByModules: SROperatorListWithModule[]; - meta: Meta; + stakingModules: SRModule[]; + meta: ELBlockSnapshot; lidoKeys: RegistryKey[]; }; @@ -30,40 +29,29 @@ export class StakingModuleDataCollectorService { * Collects basic data about the staking module, including activity status, vetted unused keys list, ID, address, and nonce. */ public async collectStakingModuleData({ - operatorsByModules, + stakingModules, meta, lidoKeys, }: State): Promise { return await Promise.all( - operatorsByModules.map(async ({ operators, module: stakingModule }) => { - const unusedKeys = lidoKeys.filter( - (key) => - !key.used && - key.moduleAddress === stakingModule.stakingModuleAddress, - ); - - const moduleVettedUnusedKeys = getVettedUnusedKeys( - operators, - unusedKeys, - ); - - // check pause - const isModuleDepositsPaused = - await this.stakingRouterService.isModuleDepositsPaused( - stakingModule.id, - { - blockHash: meta.elBlockSnapshot.blockHash, - }, - ); - + stakingModules.map(async (stakingModule) => { return { - isModuleDepositsPaused, + isModuleDepositsPaused: + await this.stakingRouterService.isModuleDepositsPaused( + stakingModule.id, + { + blockHash: meta.blockHash, + }, + ), nonce: stakingModule.nonce, stakingModuleId: stakingModule.id, stakingModuleAddress: stakingModule.stakingModuleAddress, - blockHash: meta.elBlockSnapshot.blockHash, - lastChangedBlockHash: meta.elBlockSnapshot.lastChangedBlockHash, - vettedUnusedKeys: moduleVettedUnusedKeys, + blockHash: meta.blockHash, + lastChangedBlockHash: meta.lastChangedBlockHash, + vettedUnusedKeys: this.getModuleVettedUnusedKeys( + stakingModule.stakingModuleAddress, + lidoKeys, + ), duplicatedKeys: [], invalidKeys: [], frontRunKeys: [], @@ -89,80 +77,118 @@ export class StakingModuleDataCollectorService { await Promise.all( stakingModulesData.map(async (stakingModuleData) => { + // identify keys that were front-run withing vetted unused keys stakingModuleData.frontRunKeys = this.stakingModuleGuardService.getFrontRunAttempts( stakingModuleData, blockData, ); + // identify keys with invalid signatures within vetted unused keys stakingModuleData.invalidKeys = await this.stakingModuleGuardService.getInvalidKeys( stakingModuleData, blockData, ); - const allDuplicatedKeys = this.getModuleKeys( + + // Filter all keys for the module to get the total number of duplicated keys, + // for Prometheus metrics + const allModuleDuplicatedKeys = this.getModuleKeys( stakingModuleData.stakingModuleAddress, duplicates, ); - stakingModuleData.duplicatedKeys = this.getVettedUnusedKeys( - stakingModuleData.vettedUnusedKeys, - allDuplicatedKeys, + // Filter vetted and unused duplicated keys for the module + stakingModuleData.duplicatedKeys = this.getModuleVettedUnusedKeys( + stakingModuleData.stakingModuleAddress, + duplicates, ); - const allUnresolved = this.getModuleKeys( + // Filter all unresolved keys (keys without a SigningKeyAdded event) for the module, + // including both vetted and unvetted keys, to show the total count of unresolved keys + // for Prometheus metrics + const allModuleUnresolved = this.getModuleKeys( stakingModuleData.stakingModuleAddress, unresolved, ); + // Filter vetted and unused duplicated keys for the module + stakingModuleData.unresolvedDuplicatedKeys = + this.getModuleVettedUnusedKeys( + stakingModuleData.stakingModuleAddress, + unresolved, + ); - stakingModuleData.unresolvedDuplicatedKeys = this.getVettedUnusedKeys( - stakingModuleData.vettedUnusedKeys, - allUnresolved, + this.collectModuleMetric( + stakingModuleData, + allModuleUnresolved, + stakingModuleData.unresolvedDuplicatedKeys, + allModuleDuplicatedKeys, + stakingModuleData.duplicatedKeys, ); - this.guardianMetricsService.collectDuplicatedKeysMetrics( - stakingModuleData.stakingModuleId, - allUnresolved.length, - stakingModuleData.unresolvedDuplicatedKeys.length, - allDuplicatedKeys.length, - stakingModuleData.duplicatedKeys.length, - ); + this.logKeysCheckState(stakingModuleData); + }), + ); + } - this.guardianMetricsService.collectInvalidKeysMetrics( - stakingModuleData.stakingModuleId, - stakingModuleData.invalidKeys.length, - ); + private collectModuleMetric( + stakingModuleData: StakingModuleData, + unresolvedKeys: RegistryKey[], + vettedUnusedUnresolvedKeys: RegistryKey[], + duplicatedKeys: RegistryKey[], + vettedUnusedDuplcaitedKeys: RegistryKey[], + ) { + const { invalidKeys, stakingModuleId } = stakingModuleData; + + // Collect metrics for unresolved and duplicated keys in the staking module: + // - Total unresolved keys (keys without a corresponding SigningKeyAdded event) + // - Subset of unresolved keys that are vetted and unused + // - Total duplicated keys + // - Subset of duplicated keys that are vetted and unused + this.guardianMetricsService.collectDuplicatedKeysMetrics( + stakingModuleId, + unresolvedKeys.length, + vettedUnusedUnresolvedKeys.length, + duplicatedKeys.length, + vettedUnusedDuplcaitedKeys.length, + ); - this.logger.log('Keys check state', { - stakingModuleId: stakingModuleData.stakingModuleId, - frontRunAttempt: stakingModuleData.frontRunKeys.length, - invalid: stakingModuleData.invalidKeys.length, - duplicated: stakingModuleData.duplicatedKeys.length, - unresolvedDuplicated: - stakingModuleData.unresolvedDuplicatedKeys.length, - blockNumber: blockData.blockNumber, - }); - }), + // Collect metrics for the total number of vetted unused keys with invalid signatures within the staking module + this.guardianMetricsService.collectInvalidKeysMetrics( + stakingModuleId, + invalidKeys.length, ); } + private logKeysCheckState(stakingModuleData: StakingModuleData) { + const { + stakingModuleId, + blockHash, + frontRunKeys, + invalidKeys, + duplicatedKeys, + unresolvedDuplicatedKeys, + } = stakingModuleData; + this.logger.log('Keys check state', { + stakingModuleId: stakingModuleId, + frontRunAttempt: frontRunKeys.length, + invalid: invalidKeys.length, + duplicated: duplicatedKeys.length, + unresolvedDuplicated: unresolvedDuplicatedKeys.length, + blockHash: blockHash, + }); + } + private getModuleKeys(stakingModuleAddress: string, keys: RegistryKey[]) { return keys.filter((key) => key.moduleAddress === stakingModuleAddress); } - /** - * filter from the list all keys that are not vetted as unused - */ - public getVettedUnusedKeys( - vettedUnusedKeys: RegistryKey[], - keys: RegistryKey[], + private getModuleVettedUnusedKeys( + stakingModuleAddress: string, + lidoKeys: RegistryKey[], ) { - const vettedUnused = keys.filter((key) => { - const r = vettedUnusedKeys.some( - (k) => k.index == key.index && k.operatorIndex == key.operatorIndex, - ); - - return r; - }); - - return vettedUnused; + const vettedUnusedKeys = lidoKeys.filter( + (key) => + !key.used && key.vetted && key.moduleAddress === stakingModuleAddress, + ); + return vettedUnusedKeys; } } diff --git a/src/staking-module-data-collector/vetted-keys.spec.ts b/src/staking-module-data-collector/vetted-keys.spec.ts deleted file mode 100644 index caa6360e..00000000 --- a/src/staking-module-data-collector/vetted-keys.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getVettedUnusedKeys } from './vetted-keys'; // Replace with your actual module path - -describe('getVettedUnusedKeys', () => { - test('should return an empty array for empty input arrays', () => { - expect(getVettedUnusedKeys([], [])).toEqual([]); - }); - - test('should correctly filter and sort keys for multiple operators', () => { - // totalSigningKeys is used here only to describe cases, - // we don't use is in algorithm in function to determine vetted unused keys - const operators = [ - // 2 vetted unused keys, have some available limit - { index: 1, stakingLimit: 3, usedSigningKeys: 1, totalSigningKeys: 4 }, - // 1 vetted unused key, have some available limit - { index: 2, stakingLimit: 1, usedSigningKeys: 0, totalSigningKeys: 2 }, - // 0 vetted unused keys, staking limit wasnt increased - { index: 3, stakingLimit: 0, usedSigningKeys: 0, totalSigningKeys: 1 }, - // 0 vetted unused keys, staking limit exceeded have one used key - { index: 4, stakingLimit: 1, usedSigningKeys: 1, totalSigningKeys: 2 }, - // 0 vetted unused keys, have staking limit, but don't have keys to deposit - { index: 5, stakingLimit: 1, usedSigningKeys: 0, totalSigningKeys: 0 }, - ] as any; - - const unusedKeys = [ - // operator 1 unused keys - { operatorIndex: 1, index: 1 }, - { operatorIndex: 1, index: 0 }, - { operatorIndex: 1, index: 2 }, - // operator 2 unused keys - { operatorIndex: 2, index: 0 }, - { operatorIndex: 2, index: 1 }, - // operator 3 unused keys - { operatorIndex: 3, index: 0 }, - // operator 4 unused keys - { operatorIndex: 4, index: 0 }, - ] as any; - - const expected = [ - { operatorIndex: 1, index: 0 }, - { operatorIndex: 1, index: 1 }, - { operatorIndex: 2, index: 0 }, - ]; - const result = getVettedUnusedKeys(operators, unusedKeys); - expect(result.length).toEqual(expected.length); - expect(getVettedUnusedKeys(operators, unusedKeys)).toEqual(expected); - }); - - test('should correctly sort keys within operators', () => { - const operators = [ - { index: 1, stakingLimit: 4, usedSigningKeys: 1, totalSigningKeys: 5 }, - ] as any; - const unusedKeys = [ - { operatorIndex: 1, index: 3 }, - { operatorIndex: 1, index: 1 }, - { operatorIndex: 1, index: 2 }, - { operatorIndex: 1, index: 4 }, - ] as any; - const expected = [ - { operatorIndex: 1, index: 1 }, - { operatorIndex: 1, index: 2 }, - { operatorIndex: 1, index: 3 }, - ]; - expect(getVettedUnusedKeys(operators, unusedKeys)).toEqual(expected); - }); -}); diff --git a/src/staking-module-data-collector/vetted-keys.ts b/src/staking-module-data-collector/vetted-keys.ts deleted file mode 100644 index cfdc0671..00000000 --- a/src/staking-module-data-collector/vetted-keys.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -import { RegistryOperator } from 'keys-api/interfaces/RegistryOperator'; - -export function getVettedUnusedKeys( - operators: RegistryOperator[], - unusedKeys: RegistryKey[], -): RegistryKey[] { - return operators.flatMap((operator) => { - const operatorKeys = unusedKeys - .filter((key) => key.operatorIndex === operator.index) - .sort((a, b) => a.index - b.index) - // stakingLimit limit cant be less than usedSigningKeys - .slice(0, operator.stakingLimit - operator.usedSigningKeys); - - return operatorKeys; - }); -} diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index b383eb46..a8978cd3 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -1,11 +1,3 @@ -import { - mockOperator1, - mockOperator2, - mockedDvtOperators, - mockedOperators, - setupMockModules, -} from './helpers'; - // Constants import { TESTS_TIMEOUT, @@ -36,7 +28,7 @@ import { } from './helpers/test-setup'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { LevelDBService } from 'contracts/deposit/leveldb'; -import { makeDeposit, signDeposit } from './helpers/deposit'; +import { getWalletAddress } from './helpers/deposit'; import { ProviderService } from 'provider'; import { DepositService } from 'contracts/deposit'; import { GuardianService } from 'guardian'; @@ -45,13 +37,18 @@ import { SecurityService } from 'contracts/security'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; -import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; import { mockKey, mockKey2, mockKeyEvent } from './helpers/keys-fixtures'; import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-checker'; -import { StakingModuleGuardService } from 'guardian/staking-module-guard'; +import { + keysApiMockGetAllKeys, + keysApiMockGetModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, +} from './helpers'; describe('Deposits in case of duplicates', () => { let server: Server<'ethereum'>; @@ -67,10 +64,7 @@ describe('Deposits in case of duplicates', () => { let signKeyLevelDBService: SignKeyLevelDBService; let signingKeyEventsCacheService: SigningKeyEventsCacheService; - let stakingModuleGuardService: StakingModuleGuardService; let guardianMessageService: GuardianMessageService; - let stakingModuleDataCollectorService: StakingModuleDataCollectorService; - // methods mocks let sendDepositMessage: jest.SpyInstance; let sendUnvetMessage: jest.SpyInstance; @@ -149,10 +143,6 @@ describe('Deposits in case of duplicates', () => { // main service that check keys and make decision guardianService = moduleRef.get(GuardianService); - stakingModuleGuardService = moduleRef.get(StakingModuleGuardService); - stakingModuleDataCollectorService = moduleRef.get( - StakingModuleDataCollectorService, - ); }; beforeEach(async () => { @@ -171,10 +161,7 @@ describe('Deposits in case of duplicates', () => { 'skip deposits for module if find duplicated key across operator', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - - // TODO: mine new block instead - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + // await providerService.provider.send('evm_mine', []); // Set deposit cache await depositService.setCachedEvents({ @@ -185,27 +172,33 @@ describe('Deposits in case of duplicates', () => { }, }); - // Keys api mock - const unusedKeys = [ - mockKey, - { ...mockKey, index: 1 }, - { - ...mockKey, - index: 2, - }, + const earliestKey = { + ...mockKey, + operatorIndex: 0, + moduleAddress: NOP_REGISTRY, + index: 0, + }; + const duplicates = [ + earliestKey, + { ...mockKey, operatorIndex: 0, moduleAddress: NOP_REGISTRY, index: 1 }, + { ...mockKey, operatorIndex: 0, moduleAddress: NOP_REGISTRY, index: 2 }, + ]; + // Mock Keys API + const vettedUnusedKeys = [ + ...duplicates, { ...mockKey2, moduleAddress: SIMPLE_DVT, }, ]; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, vettedUnusedKeys, meta); // mock events cache to check await signingKeyEventsCacheService.setCachedEvents({ @@ -230,25 +223,25 @@ describe('Deposits in case of duplicates', () => { await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + const walletAddress = getWalletAddress(); // just skip on this iteration deposit for Curated staking module expect(sendDepositMessage).toBeCalledTimes(1); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); - // check that duplicates problem didnt trigger pause expect(sendPauseMessage).toBeCalledTimes(0); expect(sendUnvetMessage).toBeCalledTimes(1); expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000001', }), @@ -258,22 +251,25 @@ describe('Deposits in case of duplicates', () => { // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ - mockKey, + earliestKey, { ...mockKey2, moduleAddress: SIMPLE_DVT, }, ]; + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); expect(newBlock.number).toBeGreaterThan(currentBlock.number); - setupMockModules( - newBlock, + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys( keysApiService, - mockedOperators, - mockedDvtOperators, unusedKeysWithoutDuplicates, + newMeta, ); sendDepositMessage.mockClear(); @@ -285,7 +281,7 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage.mock.calls[0][0]).toEqual( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 1, }), @@ -293,7 +289,7 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage.mock.calls[1][0]).toEqual( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 2, }), @@ -307,8 +303,7 @@ describe('Deposits in case of duplicates', () => { 'skip deposits for module if find duplicated key across operators of two modules', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -318,21 +313,21 @@ describe('Deposits in case of duplicates', () => { }, }); - const unusedKeys = [ - mockKey, + const duplicates = [ + { ...mockKey, moduleAddress: NOP_REGISTRY }, { ...mockKey, moduleAddress: SIMPLE_DVT, }, ]; - const { sdvtModule, curatedModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [ @@ -371,9 +366,9 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); // check that duplicates problem didnt trigger pause @@ -382,9 +377,9 @@ describe('Deposits in case of duplicates', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -396,19 +391,20 @@ describe('Deposits in case of duplicates', () => { // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ - mockKey, - { - ...mockKey2, - moduleAddress: SIMPLE_DVT, - }, + { ...mockKey, moduleAddress: NOP_REGISTRY }, ]; + + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, + // setup elBlockSnapshot + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys( keysApiService, - mockedOperators, - mockedDvtOperators, unusedKeysWithoutDuplicates, + newMeta, ); sendDepositMessage.mockClear(); @@ -422,18 +418,17 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); - expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendUnvetMessage).toBeCalledTimes(0); @@ -445,8 +440,7 @@ describe('Deposits in case of duplicates', () => { 'skip deposits for module if find duplicated key across operators of one modules', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -456,35 +450,40 @@ describe('Deposits in case of duplicates', () => { }, }); - const unusedKeys = [ - { ...mockKey, operatorIndex: mockOperator1.index }, + const duplicates = [ + { ...mockKey, index: 0, operatorIndex: 0 }, { ...mockKey, - operatorIndex: mockOperator2.index, + index: 0, + operatorIndex: 1, }, ]; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [ - { ...mockKeyEvent, operatorIndex: mockOperator1.index }, - // key of second module was added later { ...mockKeyEvent, - operatorIndex: mockOperator2.index, - blockNumber: mockKeyEvent.blockNumber + 1, - blockHash: 'somefakehash', + blockNumber: currentBlock.number - 4, + blockHash: 'somefakehash1', + operatorIndex: 0, + }, + // key of second operator was added later + { + ...mockKeyEvent, + blockNumber: currentBlock.number - 3, + blockHash: 'somefakehash2', + operatorIndex: 1, }, ], headers: { - startBlock: currentBlock.number - 2, + startBlock: currentBlock.number - 5, endBlock: currentBlock.number - 1, stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], }, @@ -509,9 +508,9 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); // check that duplicates problem didnt trigger pause @@ -520,9 +519,9 @@ describe('Deposits in case of duplicates', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000001', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -534,22 +533,16 @@ describe('Deposits in case of duplicates', () => { // after deleting duplicates in staking module, // council will resume deposits to module - const unusedKeysWithoutDuplicates = [ - { ...mockKey, operatorIndex: mockOperator1.index }, - { - ...mockKey2, - operatorIndex: mockOperator2.index, - }, - ]; + const noDuplicatesKeys = [{ ...mockKey, operatorIndex: 0 }]; + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeysWithoutDuplicates, - ); + // setup elBlockSnapshot + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, noDuplicatesKeys, newMeta); sendDepositMessage.mockClear(); sendUnvetMessage.mockClear(); @@ -562,18 +555,18 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); }, @@ -584,8 +577,7 @@ describe('Deposits in case of duplicates', () => { 'added unused keys for that deposit was already made', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -595,22 +587,22 @@ describe('Deposits in case of duplicates', () => { }, }); - const keys = [ - { ...mockKey, operatorIndex: mockOperator1.index, used: true }, + const duplicates = [ + { ...mockKey, operatorIndex: 0, used: true }, { ...mockKey, - operatorIndex: mockOperator2.index, + operatorIndex: 1, used: false, }, ]; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - keys, - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [], @@ -636,14 +628,13 @@ describe('Deposits in case of duplicates', () => { await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); // deposit will be skipped until unvetting - // so list of keys can be changed expect(sendDepositMessage).toBeCalledTimes(1); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); @@ -651,9 +642,9 @@ describe('Deposits in case of duplicates', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000001', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -662,17 +653,14 @@ describe('Deposits in case of duplicates', () => { // after deleting duplicates in staking module, // council will resume deposits to module + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - const keysWithoutDuplicates = [ - { ...mockKey, operatorIndex: mockOperator1.index, used: true }, - ]; - setupMockModules( - newBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - keysWithoutDuplicates, - ); + const noDuplicatesKeys = [{ ...mockKey, operatorIndex: 0, used: true }]; + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, noDuplicatesKeys, newMeta); sendDepositMessage.mockClear(); sendUnvetMessage.mockClear(); @@ -684,18 +672,18 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendUnvetMessage).toBeCalledTimes(0); @@ -705,8 +693,7 @@ describe('Deposits in case of duplicates', () => { test('adding not vetted duplicate will not set on soft pause module', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -716,23 +703,24 @@ describe('Deposits in case of duplicates', () => { }, }); - const keys = [ - { ...mockKey, operatorIndex: mockOperator1.index, used: false }, + const duplicates = [ + { ...mockKey, index: 0, operatorIndex: 1, used: false, vetted: true }, { ...mockKey, - index: mockKey.index + 1, - operatorIndex: mockOperator1.index, + index: 1, + operatorIndex: 1, used: false, + vetted: false, }, ]; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [{ ...mockOperator1, stakingLimit: 1 }], - mockedDvtOperators, - keys, - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [], @@ -751,15 +739,6 @@ describe('Deposits in case of duplicates', () => { }, }); - const handleCorrectKeys = jest.spyOn( - stakingModuleGuardService, - 'handleCorrectKeys', - ); - - const getVettedUnusedKeys = jest.spyOn( - stakingModuleDataCollectorService, - 'getVettedUnusedKeys', - ); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); @@ -767,47 +746,26 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); - expect(getVettedUnusedKeys).toBeCalledTimes(4); - expect(getVettedUnusedKeys).toHaveBeenCalledWith( - expect.arrayContaining([keys[0]]), - expect.arrayContaining([keys[1]]), - ); - - //unresolved duplicates - expect(getVettedUnusedKeys).toHaveBeenCalledWith( - expect.arrayContaining([keys[0]]), - [], - ); - expect(getVettedUnusedKeys).toHaveBeenCalledWith([], []); - //unresolved duplicates - expect(getVettedUnusedKeys).toHaveBeenCalledWith([], []); - - expect(handleCorrectKeys).toBeCalledTimes(2); - expect(handleCorrectKeys).toHaveBeenCalledWith( - expect.objectContaining({ duplicatedKeys: [] }), - expect.anything(), - ); }); test( 'skip deposits if cannot resolve duplicates', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -817,21 +775,20 @@ describe('Deposits in case of duplicates', () => { }, }); - const unusedKeys = [ - mockKey, + const duplicates = [ + { ...mockKey, moduleAddress: NOP_REGISTRY }, { ...mockKey, moduleAddress: SIMPLE_DVT, }, ]; - const { sdvtModule, curatedModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [mockKeyEvent], @@ -868,21 +825,14 @@ describe('Deposits in case of duplicates', () => { // after deleting duplicates in staking module, // council will resume deposits to module - const unusedKeysWithoutDuplicates = [ - mockKey, - { - ...mockKey2, - moduleAddress: SIMPLE_DVT, - }, - ]; + await providerService.provider.send('evm_mine', []); + const noDuplicatesKeys = [{ ...mockKey, moduleAddress: NOP_REGISTRY }]; const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeysWithoutDuplicates, - ); + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, noDuplicatesKeys, newMeta); sendDepositMessage.mockClear(); sendUnvetMessage.mockClear(); @@ -895,18 +845,18 @@ describe('Deposits in case of duplicates', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendUnvetMessage).toBeCalledTimes(0); @@ -914,5 +864,106 @@ describe('Deposits in case of duplicates', () => { TESTS_TIMEOUT, ); - // TODO: test on unvetting of key of two modules + test( + 'if duplicates in both modules, skip deposits for modules and unvet only for first on first iteration', + async () => { + const currentBlock = await providerService.provider.getBlock('latest'); + // await providerService.provider.send('evm_mine', []); + + // Set deposit cache + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + }, + }); + + const earliestKey = { + ...mockKey, + operatorIndex: 0, + moduleAddress: NOP_REGISTRY, + index: 0, + }; + const duplicatesСurated = [ + earliestKey, + { ...mockKey, operatorIndex: 0, moduleAddress: NOP_REGISTRY, index: 1 }, + { ...mockKey, operatorIndex: 0, moduleAddress: NOP_REGISTRY, index: 2 }, + ]; + + const duplicatesSimpleDVT = [ + { + ...mockKey2, + operatorIndex: 0, + moduleAddress: SIMPLE_DVT, + index: 0, + }, + { + ...mockKey2, + operatorIndex: 0, + moduleAddress: SIMPLE_DVT, + index: 1, + }, + ]; + + // Mock Keys API + const vettedUnusedKeys = [...duplicatesСurated, ...duplicatesSimpleDVT]; + + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, vettedUnusedKeys, meta); + + // mock events cache to check + await signingKeyEventsCacheService.setCachedEvents({ + data: [], // dont need events in this test + headers: { + startBlock: currentBlock.number - 2, + endBlock: currentBlock.number, + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], + }, + }); + + // Check that module was not paused + const routerContract = StakingRouterAbi__factory.connect( + STAKING_ROUTER, + providerService.provider, + ); + const isOnPause = await routerContract.getStakingModuleIsDepositsPaused( + 1, + ); + expect(isOnPause).toBe(false); + await guardianService.handleNewBlock(); + + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + + const walletAddress = getWalletAddress(); + // just skip on this iteration deposit for Curated staking module + expect(sendDepositMessage).toBeCalledTimes(1); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 2, + }), + ); + expect(sendPauseMessage).toBeCalledTimes(0); + expect(sendUnvetMessage).toBeCalledTimes(1); + expect(sendUnvetMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 1, + operatorIds: '0x0000000000000000', + vettedKeysByOperator: '0x00000000000000000000000000000001', + }), + ); + }, + TESTS_TIMEOUT, + ); }); diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 960a29e1..39e92249 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -1,8 +1,9 @@ import { - mockOperator1, - mockedDvtOperators, - mockedOperators, - setupMockModules, + mockMeta, + keysApiMockGetModules, + keysApiMockGetAllKeys, + mockedModuleCurated, + mockedModuleDvt, } from './helpers'; // Constants @@ -37,7 +38,7 @@ import { } from './helpers/test-setup'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { LevelDBService } from 'contracts/deposit/leveldb'; -import { makeDeposit, signDeposit } from './helpers/deposit'; +import { getWalletAddress, signDeposit } from './helpers/deposit'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; import { ProviderService } from 'provider'; import { DepositService } from 'contracts/deposit'; @@ -150,11 +151,10 @@ describe('ganache e2e tests', () => { }); test( - 'skip deposit if find duplicated key8', + 'skip deposit if find duplicated key', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -174,26 +174,19 @@ describe('ganache e2e tests', () => { }); // Keys api mock - const unusedKeys = [ - mockKey, + const duplicates = [ + { ...mockKey, index: 0 }, { ...mockKey, index: 1 }, - { - ...mockKey, - index: 2, - }, - { - ...mockKey2, - moduleAddress: SIMPLE_DVT, - }, + { ...mockKey, index: 2 }, + { ...mockKey2, moduleAddress: SIMPLE_DVT }, ]; - setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); // Check that module was not paused const routerContract = StakingRouterAbi__factory.connect( @@ -213,7 +206,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 2, }), @@ -223,40 +216,40 @@ describe('ganache e2e tests', () => { // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ - mockKey, - { - ...mockKey2, - moduleAddress: SIMPLE_DVT, - }, + { ...mockKey, index: 0, moduleAddress: NOP_REGISTRY }, + { ...mockKey2, index: 0, moduleAddress: SIMPLE_DVT }, ]; + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys( keysApiService, - mockedOperators, - mockedDvtOperators, unusedKeysWithoutDuplicates, + newMeta, ); + sendDepositMessage.mockClear(); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); expect(sendDepositMessage).toBeCalledTimes(2); - - expect(sendDepositMessage.mock.calls[0][0]).toEqual( + expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 1, }), ); - expect(sendDepositMessage.mock.calls[1][0]).toEqual( + expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 2, }), @@ -269,8 +262,7 @@ describe('ganache e2e tests', () => { 'skip deposit if find duplicated key in another staking module', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -281,21 +273,17 @@ describe('ganache e2e tests', () => { }); // Keys api mock - const unusedKeys = [ - mockKey, - { - ...mockKey, - moduleAddress: SIMPLE_DVT, - }, + const duplicates = [ + { ...mockKey, index: 0, moduleAddress: NOP_REGISTRY }, + { ...mockKey, index: 0, moduleAddress: SIMPLE_DVT }, ]; - const { curatedModule } = setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - unusedKeys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [ @@ -330,9 +318,9 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendPauseMessage).toBeCalledTimes(0); @@ -340,20 +328,23 @@ describe('ganache e2e tests', () => { // after deleting duplicates in staking module, // council will resume deposits to module const unusedKeysWithoutDuplicates = [ - mockKey, + { ...mockKey, index: 0, moduleAddress: NOP_REGISTRY }, { ...mockKey2, moduleAddress: SIMPLE_DVT, }, ]; + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys( keysApiService, - mockedOperators, - mockedDvtOperators, unusedKeysWithoutDuplicates, + newMeta, ); sendDepositMessage.mockClear(); @@ -365,7 +356,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 1, }), @@ -374,7 +365,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 2, }), @@ -387,8 +378,7 @@ describe('ganache e2e tests', () => { 'added unused keys for that deposit was already made', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - const { wallet } = await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -399,7 +389,7 @@ describe('ganache e2e tests', () => { }); // Keys api mock - const keys = [ + const duplicates = [ { ...mockKey, used: true }, { ...mockKey, @@ -408,13 +398,12 @@ describe('ganache e2e tests', () => { }, ]; - setupMockModules( - currentBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - keys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [], @@ -443,18 +432,14 @@ describe('ganache e2e tests', () => { // after deleting duplicates in staking module, // council will resume deposits to module - + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - const keysWithoutDulicates = [{ ...mockKey, used: true }]; - - setupMockModules( - newBlock, - keysApiService, - mockedOperators, - mockedDvtOperators, - keysWithoutDulicates, - ); + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keysWithoutDulicates, newMeta); sendDepositMessage.mockClear(); @@ -465,7 +450,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 1, }), @@ -473,7 +458,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, stakingModuleId: 2, }), @@ -484,8 +469,7 @@ describe('ganache e2e tests', () => { test('adding not vetted duplicate will not set on soft pause module', async () => { const currentBlock = await providerService.provider.getBlock('latest'); - const { depositData } = signDeposit(pk, sk); - await makeDeposit(depositData, providerService); + const walletAddress = await getWalletAddress(); await depositService.setCachedEvents({ data: [], @@ -496,23 +480,24 @@ describe('ganache e2e tests', () => { }); // Keys api mock - const keys = [ - { ...mockKey, index: 0, operatorIndex: mockOperator1.index, used: false }, + const duplicates = [ + { ...mockKey, index: 0, operatorIndex: 0, used: false, vetted: true }, { ...mockKey, index: 1, - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, + + vetted: false, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [{ ...mockOperator1, stakingLimit: 1 }], - mockedDvtOperators, - keys, - ); + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, duplicates, meta); await signingKeyEventsCacheService.setCachedEvents({ data: [], @@ -523,34 +508,25 @@ describe('ganache e2e tests', () => { }, }); - const handleCorrectKeys = jest.spyOn( - stakingModuleGuardService, - 'handleCorrectKeys', - ); - - const getVettedUnusedKeys = jest.spyOn( - stakingModuleDataCollectorService, - 'getVettedUnusedKeys', - ); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); expect(sendDepositMessage).toBeCalledTimes(2); - expect(handleCorrectKeys).toBeCalledTimes(2); - expect(getVettedUnusedKeys).toBeCalledTimes(4); - - expect(getVettedUnusedKeys).toHaveBeenCalledWith( - expect.arrayContaining([keys[0]]), - expect.arrayContaining([keys[1]]), - ); - //unresolved duplicates - expect(getVettedUnusedKeys).toHaveBeenCalledWith( - expect.arrayContaining([keys[0]]), - expect.arrayContaining([]), + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 1, + }), ); - expect(handleCorrectKeys).toHaveBeenCalledWith( - expect.objectContaining({ duplicatedKeys: [] }), - expect.anything(), + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 2, + }), ); }); }); diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index e88c95cd..45b5bbd6 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -3,16 +3,12 @@ import { toHexString } from '@chainsafe/ssz'; // Helpers import { - mockedDvtOperators, + keysApiMockGetAllKeys, + keysApiMockGetModules, mockedKeysApiFind, - mockedKeysApiGetAllKeys, - mockedKeysApiOperatorsMany, - mockedMeta, - mockedModule, - mockedOperators, - mockOperator1, - mockOperator2, - setupMockModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, } from './helpers'; // Constants @@ -185,17 +181,21 @@ describe('ganache e2e tests', () => { { key: toHexString(pk), depositSignature: toHexString(signature), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 1, moduleAddress: NOP_REGISTRY, + vetted: true, + }, + { + ...mockKey2, + index: 0, + moduleAddress: SIMPLE_DVT, + operatorIndex: 0, + vetted: true, }, - // simple dvt - mockKey2, ]; - // add in deposit cache event of deposit on key with lido creds - // TODO: replace with real deposit await depositService.setCachedEvents({ data: [], headers: { @@ -204,7 +204,7 @@ describe('ganache e2e tests', () => { }, }); - // dont set events for keys as we check this cache only in case of duplicated keys + // don't set events for keys as we check this cache only in case of duplicated keys await signingKeyEventsCacheService.setCachedEvents({ data: [], headers: { @@ -221,13 +221,15 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - const { curatedModule } = setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -241,7 +243,7 @@ describe('ganache e2e tests', () => { blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000001', }), @@ -297,26 +299,30 @@ describe('ganache e2e tests', () => { { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, + }, + { + ...mockKey2, + index: 0, + moduleAddress: SIMPLE_DVT, + operatorIndex: 0, + vetted: true, }, - // simple dvt - mockKey2, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - // we make check that there are no duplicated used keys - // this request return keys along with their duplicates - // mockedKeysApiFind(keysApiService, unusedKeys, newMeta); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -369,24 +375,27 @@ describe('ganache e2e tests', () => { 1, ); - const unusedKeys = [ + const keys = [ { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - unusedKeys, - ); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -428,24 +437,27 @@ describe('ganache e2e tests', () => { const { wallet } = await makeDeposit(depositData, providerService); - const unusedKeys = [ + const keys = [ { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - unusedKeys, - ); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Check if the service is ok and ready to go await guardianService.handleNewBlock(); @@ -454,7 +466,7 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledTimes(2); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 1, @@ -462,7 +474,7 @@ describe('ganache e2e tests', () => { ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 2, @@ -484,22 +496,18 @@ describe('ganache e2e tests', () => { }, }); - // mocked curated module - const stakingModule = mockedModule(currentBlock, currentBlock.hash); - const meta = mockedMeta(currentBlock, currentBlock.hash); - - mockedKeysApiOperatorsMany( - keysApiService, - [{ operators: mockedOperators, module: stakingModule }], - meta, - ); - - const unusedKeys = [mockKey]; - - const hashWasChanged = - '0xd921055dbb407e09f64afe5182a64c1bd309fe28f26909a96425cdb6bfc48959'; - const newMeta = mockedMeta(currentBlock, hashWasChanged); - mockedKeysApiGetAllKeys(keysApiService, unusedKeys, newMeta); + // Mock Keys API + const keys = [mockKey]; + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + await providerService.provider.send('evm_mine', []); + const newBlock = await providerService.provider.getBlock('latest'); + const newMeta = mockMeta(newBlock, newBlock.hash); + keysApiMockGetAllKeys(keysApiService, keys, newMeta); await guardianService.handleNewBlock(); @@ -542,22 +550,18 @@ describe('ganache e2e tests', () => { used: true, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - mockedKeysApiFind( - keysApiService, - keys, - mockedMeta(currentBlock, currentBlock.hash), - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); + mockedKeysApiFind(keysApiService, keys, meta); await depositService.setCachedEvents({ data: [ @@ -591,7 +595,7 @@ describe('ganache e2e tests', () => { }, ], headers: { - startBlock: currentBlock.number, + startBlock: currentBlock.number - 2, endBlock: currentBlock.number, }, }); @@ -608,7 +612,6 @@ describe('ganache e2e tests', () => { ); const isOnPause = await securityContract.isDepositsPaused(); - expect(isOnPause).toBe(true); }, TESTS_TIMEOUT, @@ -650,22 +653,18 @@ describe('ganache e2e tests', () => { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - mockedKeysApiFind( - keysApiService, - keys, - mockedMeta(currentBlock, currentBlock.hash), - ); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); + mockedKeysApiFind(keysApiService, keys, meta); await depositService.setCachedEvents({ data: [ @@ -699,7 +698,7 @@ describe('ganache e2e tests', () => { }, ], headers: { - startBlock: currentBlock.number, + startBlock: currentBlock.number - 2, endBlock: currentBlock.number, }, }); @@ -725,7 +724,7 @@ describe('ganache e2e tests', () => { blockNumber: currentBlock.number, guardianAddress: wallet.address, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -737,10 +736,94 @@ describe('ganache e2e tests', () => { blockNumber: currentBlock.number, guardianAddress: wallet.address, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); }, TESTS_TIMEOUT, ); + + test( + 'frontrun of unvetted key will not set module on soft pause', + async () => { + const currentBlock = await providerService.provider.getBlock('latest'); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + }, + }); + + await signingKeyEventsCacheService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], + }, + }); + + const { signature: goodSign } = signDeposit(pk, sk, LIDO_WC, 32000000000); + + const { depositData: theftDepositData } = signDeposit(pk, sk, BAD_WC); + const { wallet } = await makeDeposit(theftDepositData, providerService); + + const unvettedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSign), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + vetted: false, + }, + ]; + + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, unvettedKeys, meta); + + // Check if the service is ok and ready to go + // the same scenario as "failed 1eth deposit attack to stop deposits" + await guardianService.handleNewBlock(); + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + + expect(sendDepositMessage).toBeCalledTimes(2); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: 1, + }), + ); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: 2, + }), + ); + expect(unvetSigningKeys).toBeCalledTimes(0); + + const securityContract = SecurityAbi__factory.connect( + SECURITY_MODULE, + providerService.provider, + ); + + const isOnPause = await securityContract.isDepositsPaused(); + expect(isOnPause).toBe(false); + }, + TESTS_TIMEOUT, + ); }); diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 1ab15ed5..f2f97f10 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -3,16 +3,12 @@ import { toHexString } from '@chainsafe/ssz'; // Helpers import { - mockedDvtOperators, mockedKeysApiFind, - mockedKeysApiGetAllKeys, - mockedKeysApiOperatorsMany, - mockedMeta, - mockedModule, - mockedOperators, - mockOperator1, - mockOperator2, - setupMockModules, + keysApiMockGetAllKeys, + keysApiMockGetModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, } from './helpers'; // Constants @@ -163,22 +159,25 @@ describe('ganache e2e tests', () => { const { signature } = signDeposit(pk, sk, LIDO_WC); // Keys api mock - // all keys in keys api on current block state const keys = [ { key: toHexString(pk), depositSignature: toHexString(signature), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, + }, + { + ...mockKey2, + index: 0, + moduleAddress: SIMPLE_DVT, + operatorIndex: 0, + vetted: true, }, - // simple dvt - mockKey2, ]; - // add in deposit cache event of deposit on key with lido creds - // TODO: replace with real deposit await depositService.setCachedEvents({ data: [], headers: { @@ -203,14 +202,13 @@ describe('ganache e2e tests', () => { // Mock Keys API again on new block const newBlock = await providerService.provider.getBlock('latest'); - - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -268,26 +266,30 @@ describe('ganache e2e tests', () => { { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, - used: false, // TODO: true + operatorIndex: 0, + used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, + }, + { + ...mockKey2, + index: 0, + moduleAddress: SIMPLE_DVT, + operatorIndex: 0, + vetted: true, }, - // simple dvt - mockKey2, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - // we make check that there are no duplicated used keys - // this request return keys along with their duplicates - // mockedKeysApiFind(keysApiService, unusedKeys, newMeta); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -341,24 +343,27 @@ describe('ganache e2e tests', () => { 1, ); - const unusedKeys = [ + const keys = [ { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, + operatorIndex: 0, used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - unusedKeys, - ); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Run a cycle and wait for possible changes await guardianService.handleNewBlock(); @@ -400,33 +405,37 @@ describe('ganache e2e tests', () => { const { wallet } = await makeDeposit(depositData, providerService); - const unusedKeys = [ + const keys = [ { key: toHexString(pk), depositSignature: toHexString(goodSign), - operatorIndex: mockOperator1.index, - used: false, + operatorIndex: 0, + used: true, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - unusedKeys, - ); + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); // Check if the service is ok and ready to go + // the same scenario as "failed 1eth deposit attack to stop deposits" await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); expect(sendDepositMessage).toBeCalledTimes(2); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 1, @@ -434,7 +443,7 @@ describe('ganache e2e tests', () => { ); expect(sendDepositMessage).toHaveBeenCalledWith( expect.objectContaining({ - blockNumber: currentBlock.number, + blockNumber: newBlock.number, guardianAddress: wallet.address, guardianIndex: 7, stakingModuleId: 2, @@ -450,6 +459,10 @@ describe('ganache e2e tests', () => { 1, ); expect(isOnPause).toBe(false); + const isOnPause2 = await routerContract.getStakingModuleIsDepositsPaused( + 2, + ); + expect(isOnPause2).toBe(false); }, TESTS_TIMEOUT, ); @@ -466,22 +479,19 @@ describe('ganache e2e tests', () => { }, }); - // mocked curated module - const stakingModule = mockedModule(currentBlock, currentBlock.hash); - const meta = mockedMeta(currentBlock, currentBlock.hash); - - mockedKeysApiOperatorsMany( - keysApiService, - [{ operators: mockedOperators, module: stakingModule }], - meta, - ); - - const unusedKeys = [mockKey]; + const keys = [mockKey]; - const hashWasChanged = - '0xd921055dbb407e09f64afe5182a64c1bd309fe28f26909a96425cdb6bfc48959'; - const newMeta = mockedMeta(currentBlock, hashWasChanged); - mockedKeysApiGetAllKeys(keysApiService, unusedKeys, newMeta); + // Mock Keys API + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + await providerService.provider.send('evm_mine', []); + const newBlock = await providerService.provider.getBlock('latest'); + const newMeta = mockMeta(newBlock, newBlock.hash); + keysApiMockGetAllKeys(keysApiService, keys, newMeta); await guardianService.handleNewBlock(); @@ -492,7 +502,7 @@ describe('ganache e2e tests', () => { ); test( - 'historical front-run', + 'frontrun of unvetted key will not set module on soft pause', async () => { const currentBlock = await providerService.provider.getBlock('latest'); @@ -513,6 +523,83 @@ describe('ganache e2e tests', () => { }, }); + const { signature: goodSign } = signDeposit(pk, sk, LIDO_WC, 32000000000); + + const { depositData: theftDepositData } = signDeposit(pk, sk, BAD_WC); + const { wallet } = await makeDeposit(theftDepositData, providerService); + + const unvettedKeys = [ + { + key: toHexString(pk), + depositSignature: toHexString(goodSign), + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + vetted: false, + }, + ]; + + // Mock Keys API again on new block + const newBlock = await providerService.provider.getBlock('latest'); + // setup elBlockSnapshot + const meta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, unvettedKeys, meta); + + // Check if the service is ok and ready to go + // the same scenario as "failed 1eth deposit attack to stop deposits" + await guardianService.handleNewBlock(); + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + + expect(sendDepositMessage).toBeCalledTimes(2); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: 1, + }), + ); + expect(sendDepositMessage).toHaveBeenCalledWith( + expect.objectContaining({ + blockNumber: newBlock.number, + guardianAddress: wallet.address, + guardianIndex: 7, + stakingModuleId: 2, + }), + ); + + // Check if on pause now + const routerContract = StakingRouterAbi__factory.connect( + STAKING_ROUTER, + providerService.provider, + ); + const isOnPause = await routerContract.getStakingModuleIsDepositsPaused( + 1, + ); + expect(isOnPause).toBe(false); + }, + TESTS_TIMEOUT, + ); + + test( + 'historical front-run', + async () => { + const currentBlock = await providerService.provider.getBlock('latest'); + + await signingKeyEventsCacheService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], + }, + }); + const { signature: lidoSign } = signDeposit(pk, sk); const { signature: theftDepositSign } = signDeposit(pk, sk, BAD_WC); @@ -524,23 +611,10 @@ describe('ganache e2e tests', () => { used: true, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }, ]; - setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - mockedKeysApiFind( - keysApiService, - keys, - mockedMeta(currentBlock, currentBlock.hash), - ); - await depositService.setCachedEvents({ data: [ { @@ -573,11 +647,20 @@ describe('ganache e2e tests', () => { }, ], headers: { - startBlock: currentBlock.number, + startBlock: currentBlock.number - 2, endBlock: currentBlock.number, }, }); + // setup elBlockSnapshot + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); + mockedKeysApiFind(keysApiService, keys, meta); + await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); @@ -592,27 +675,24 @@ describe('ganache e2e tests', () => { expect(isOnPause).toBe(true); - await routerContract.getStakingModuleIsDepositsPaused(2); + const isOnPause2Module = + await routerContract.getStakingModuleIsDepositsPaused(2); + + expect(isOnPause2Module).toBe(false); + expect(sendDepositMessage).toBeCalledTimes(0); // Mine a new block await providerService.provider.send('evm_mine', []); - // Your assertions after mining the block + // // Your assertions after mining the block const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - keys, - ); - - mockedKeysApiFind( - keysApiService, - keys, - mockedMeta(newBlock, newBlock.hash), - ); + // setup elBlockSnapshot + const newMeta = mockMeta(newBlock, newBlock.hash); + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, newMeta); + mockedKeysApiFind(keysApiService, keys, newMeta); await guardianService.handleNewBlock(); @@ -627,6 +707,8 @@ describe('ganache e2e tests', () => { await routerContract.getStakingModuleIsDepositsPaused(2); expect(isOnPause2NextIter).toBe(true); + + expect(sendDepositMessage).toBeCalledTimes(0); }, TESTS_TIMEOUT, ); diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index 03d38aab..d031d74b 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -7,10 +7,11 @@ import { Server } from 'ganache'; // Helper Functions and Mocks import { - mockedDvtOperators, - mockOperator1, - mockOperator2, - setupMockModules, + keysApiMockGetAllKeys, + keysApiMockGetModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, } from './helpers'; import { @@ -48,6 +49,8 @@ import { DepositIntegrityCheckerService } from 'contracts/deposit/integrity-chec // Test Data import { mockKey, mockKey2 } from './helpers/keys-fixtures'; import { addGuardians, setGuardianBalance } from './helpers/dsm'; +import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; +import { ethers } from 'ethers'; describe('Guardian balance monitoring test', () => { let server: Server<'ethereum'>; @@ -139,27 +142,34 @@ describe('Guardian balance monitoring test', () => { }); }; - const setupKAPIWithInvalidSignProblem = (block) => { - const norKeyWithWrongSign = { + const setupKAPIWithInvalidSignProblem = (block: ethers.providers.Block) => { + // keys fixtures + const norKeyWithWrongSign: RegistryKey = { ...mockKey, depositSignature: '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + vetted: true, }; - - const dvtKey = { + const dvtKey: RegistryKey = { ...mockKey2, + index: 1, used: false, - operatorIndex: mockedDvtOperators[0].index, + operatorIndex: 0, moduleAddress: SIMPLE_DVT, + vetted: true, }; + const dvtKey2 = { ...dvtKey, index: 2 }; - setupMockModules( - block, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [norKeyWithWrongSign, dvtKey, { ...dvtKey, index: dvtKey.index + 1 }], - ); + // setup elBlockSnapshot + const meta = mockMeta(block, block.hash); + + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + + // setup /v1/keys + const keys = [norKeyWithWrongSign, dvtKey, dvtKey2]; + keysApiMockGetAllKeys(keysApiService, keys, meta); }; async function waitForProcessing() { diff --git a/test/helpers/deposit.ts b/test/helpers/deposit.ts index c601d518..896ddee4 100644 --- a/test/helpers/deposit.ts +++ b/test/helpers/deposit.ts @@ -53,3 +53,9 @@ export async function makeDeposit( return { wallet: signer, depositSign: depositData.signature }; } + +export function getWalletAddress() { + if (!process.env.WALLET_PRIVATE_KEY) throw new Error(NO_PRIVKEY_MESSAGE); + const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY); + return wallet.address; +} diff --git a/test/helpers/keys-fixtures.ts b/test/helpers/keys-fixtures.ts index 25075a70..cbaf0754 100644 --- a/test/helpers/keys-fixtures.ts +++ b/test/helpers/keys-fixtures.ts @@ -8,6 +8,7 @@ export const mockKey = { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }; export const mockKeyEvent = { @@ -27,4 +28,5 @@ export const mockKey2 = { used: true, moduleAddress: NOP_REGISTRY, index: 1, + vetted: true, }; diff --git a/test/helpers/mockKeysApi.ts b/test/helpers/mockKeysApi.ts index b4378432..f0112ac2 100644 --- a/test/helpers/mockKeysApi.ts +++ b/test/helpers/mockKeysApi.ts @@ -2,42 +2,51 @@ import ethers from 'ethers'; import { KeysApiService } from '../../src/keys-api/keys-api.service'; import { SIMPLE_DVT, NOP_REGISTRY } from './../constants'; -import { RegistryOperator } from 'keys-api/interfaces/RegistryOperator'; +// import { RegistryOperator } from 'keys-api/interfaces/RegistryOperator'; import { SRModule } from 'keys-api/interfaces'; import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -export const setupMockModules = ( - currentBlock: ethers.providers.Block, - keysApiService: KeysApiService, - mockedOperators: RegistryOperator[], - mockedDvtOperators: RegistryOperator[], - unusedKeys: RegistryKey[], -) => { - const curatedModule = mockedModule(currentBlock, currentBlock.hash); - const sdvtModule = mockedModuleDvt(currentBlock, currentBlock.hash); - const meta = mockedMeta(currentBlock, currentBlock.hash); - - mockedKeysApiOperatorsMany( - keysApiService, - [ - { operators: mockedOperators, module: curatedModule }, - { operators: mockedDvtOperators, module: sdvtModule }, - ], - meta, - ); - - mockedKeysApiGetAllKeys(keysApiService, unusedKeys, meta); - - return { curatedModule, sdvtModule, meta }; -}; +// export const setupMockModules = ( +// currentBlock: ethers.providers.Block, +// keysApiService: KeysApiService, +// mockedOperators: RegistryOperator[], +// mockedDvtOperators: RegistryOperator[], +// unusedKeys: RegistryKey[], +// ) => { +// const curatedModule = mockedModule(currentBlock, currentBlock.hash); +// const sdvtModule = mockedModuleDvt(currentBlock, currentBlock.hash); +// const meta = mockedMeta(currentBlock, currentBlock.hash); -export const mockedModule = ( - block: ethers.providers.Block, - lastChangedBlockHash: string, - nonce = 6046, -): SRModule => ({ - nonce, +// mockedKeysApiOperatorsMany( +// keysApiService, +// [ +// { operators: mockedOperators, module: curatedModule }, +// { operators: mockedDvtOperators, module: sdvtModule }, +// ], +// meta, +// ); + +// mockedKeysApiGetAllKeys(keysApiService, unusedKeys, meta); + +// return { curatedModule, sdvtModule, meta }; +// }; + +// export const setupKeysAPI = ( +// block: ethers.providers.Block, +// keysApiService: KeysApiService, +// modules: SRModule[], +// keys: RegistryKey[], +// lastChangedBlockHash = undefined, +// ) => { +// const meta = mockMeta(block, lastChangedBlockHash || block.hash); +// keysApiMockGetAllKeys(keysApiService, keys, meta); +// mod + +// return { curatedModule, sdvtModule, meta }; +// }; + +export const mockedModuleCurated: SRModule = { type: 'curated-onchain-v1', id: 1, stakingModuleAddress: NOP_REGISTRY, @@ -46,19 +55,15 @@ export const mockedModule = ( targetShare: 10, status: 1, name: 'NodeOperatorRegistry', - lastDepositAt: block.timestamp, - lastDepositBlock: block.number, - lastChangedBlockHash, + lastDepositAt: 1234345657, + lastDepositBlock: 12345, + lastChangedBlockHash: '', + nonce: 6046, exitedValidatorsCount: 0, active: true, -}); +}; -export const mockedModuleDvt = ( - block: ethers.providers.Block, - lastChangedBlockHash: string, - nonce = 6046, -): SRModule => ({ - nonce, +export const mockedModuleDvt: SRModule = { type: 'curated-onchain-v1', id: 2, stakingModuleAddress: SIMPLE_DVT, @@ -67,14 +72,15 @@ export const mockedModuleDvt = ( targetShare: 10, status: 1, name: 'NodeOperatorRegistrySimpleDvt', - lastDepositAt: block.timestamp, - lastDepositBlock: block.number, - lastChangedBlockHash, + lastDepositAt: 1234345657, + lastDepositBlock: 12345, + lastChangedBlockHash: '', + nonce: 6046, exitedValidatorsCount: 0, active: true, -}); +}; -export const mockedMeta = ( +export const mockMeta = ( block: ethers.providers.Block, lastChangedBlockHash: string, ) => ({ @@ -84,88 +90,97 @@ export const mockedMeta = ( lastChangedBlockHash, }); -export const mockOperator1 = { - name: 'Dev team', - rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', - stakingLimit: 12, - stoppedValidators: 0, - totalSigningKeys: 12, - usedSigningKeys: 0, - index: 0, - active: true, - moduleAddress: NOP_REGISTRY, -}; +// export const mockOperator1 = { +// name: 'Dev team', +// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', +// stakingLimit: 12, +// stoppedValidators: 0, +// totalSigningKeys: 12, +// usedSigningKeys: 0, +// index: 0, +// active: true, +// moduleAddress: NOP_REGISTRY, +// }; -export const mockOperator2 = { - name: 'Dev team', - rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', - stakingLimit: 12, - stoppedValidators: 0, - totalSigningKeys: 12, - usedSigningKeys: 0, - index: 1, - active: true, - moduleAddress: NOP_REGISTRY, -}; +// export const mockOperator2 = { +// name: 'Dev team', +// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', +// stakingLimit: 12, +// stoppedValidators: 0, +// totalSigningKeys: 12, +// usedSigningKeys: 0, +// index: 1, +// active: true, +// moduleAddress: NOP_REGISTRY, +// }; + +// export const mockedOperators: RegistryOperator[] = [ +// mockOperator1, +// mockOperator2, +// ]; + +// export const mockedDvtOperator: RegistryOperator = { +// name: 'Dev DVT team', +// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', +// stakingLimit: 12, +// stoppedValidators: 0, +// totalSigningKeys: 12, +// usedSigningKeys: 0, +// index: 0, +// active: true, +// moduleAddress: SIMPLE_DVT, +// }; -export const mockedOperators: RegistryOperator[] = [ - mockOperator1, - mockOperator2, -]; - -export const mockedDvtOperators: RegistryOperator[] = [ - { - name: 'Dev DVT team', - rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', - stakingLimit: 12, - stoppedValidators: 0, - totalSigningKeys: 12, - usedSigningKeys: 0, - index: 0, - active: true, - moduleAddress: SIMPLE_DVT, - }, -]; - -export const mockedKeysApiOperatorsMany = ( +// export const mockedKeysApiOperatorsMany = ( +// keysApiService: KeysApiService, +// data: { operators: RegistryOperator[]; module: SRModule }[], +// mockedMeta: ELBlockSnapshot, +// ) => { +// jest +// .spyOn(keysApiService, 'getOperatorListWithModule') +// .mockImplementation(async () => ({ +// data: data, +// meta: { +// elBlockSnapshot: mockedMeta, +// }, +// })); +// }; + +export const keysApiMockGetModules = ( keysApiService: KeysApiService, - data: { operators: RegistryOperator[]; module: SRModule }[], - mockedMeta: ELBlockSnapshot, + modules: SRModule[], + meta: ELBlockSnapshot, ) => { - jest - .spyOn(keysApiService, 'getOperatorListWithModule') - .mockImplementation(async () => ({ - data: data, - meta: { - elBlockSnapshot: mockedMeta, - }, - })); + jest.spyOn(keysApiService, 'getModules').mockImplementation(async () => ({ + data: modules, + elBlockSnapshot: meta, + })); }; -export const mockedKeysApiGetAllKeys = ( +export const keysApiMockGetAllKeys = ( keysApiService: KeysApiService, - mockedKeys: RegistryKey[], - mockedMeta: ELBlockSnapshot, + keys: RegistryKey[], + meta: ELBlockSnapshot, ) => { jest.spyOn(keysApiService, 'getKeys').mockImplementation(async () => ({ - data: mockedKeys, + data: keys, meta: { - elBlockSnapshot: mockedMeta, + elBlockSnapshot: meta, }, })); }; export const mockedKeysApiFind = ( keysApiService: KeysApiService, - mockedKeys: RegistryKey[], - mockedMeta: ELBlockSnapshot, + keys: RegistryKey[], + meta: ELBlockSnapshot, ) => { jest .spyOn(keysApiService, 'getKeysByPubkeys') .mockImplementation(async () => ({ - data: mockedKeys, + data: keys, meta: { - elBlockSnapshot: mockedMeta, + elBlockSnapshot: meta, }, })); }; diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index 5a0b67b6..02ef6f7d 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -31,10 +31,10 @@ export const setupTestingModule = async () => { const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); - jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'debug').mockImplementation(() => undefined); + // jest.spyOn(loggerService, 'error').mockImplementation(() => undefined); return moduleRef; }; diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index 47086e4f..46316fac 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -3,10 +3,11 @@ import { toHexString } from '@chainsafe/ssz'; // Helpers import { - mockedDvtOperators, - mockOperator1, - mockOperator2, - setupMockModules, + keysApiMockGetAllKeys, + keysApiMockGetModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, } from './helpers'; // Constants @@ -44,7 +45,7 @@ import { GuardianMessageService } from 'guardian/guardian-message'; import { LevelDBService } from 'contracts/deposit/leveldb'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; -import { makeDeposit, signDeposit } from './helpers/deposit'; +import { getWalletAddress, signDeposit } from './helpers/deposit'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; @@ -196,17 +197,19 @@ describe('ganache e2e tests', () => { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign], - ); - const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); - const { wallet } = await makeDeposit(depositData, providerService); + const invalidKeys = [keyWithWrongSign]; + + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, invalidKeys, meta); + + const walletAddress = await getWalletAddress(); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); @@ -228,9 +231,9 @@ describe('ganache e2e tests', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -241,23 +244,21 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); // if depositData was not changed it will not validate again - + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign], - ); + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, invalidKeys, newMeta); validateKeys.mockClear(); sendDepositMessage.mockClear(); @@ -275,9 +276,9 @@ describe('ganache e2e tests', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -288,9 +289,9 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); @@ -327,6 +328,7 @@ describe('ganache e2e tests', () => { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }; const dvtKey = { @@ -334,24 +336,22 @@ describe('ganache e2e tests', () => { moduleAddress: SIMPLE_DVT, }; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign, dvtKey], - ); + const invalidKeys = [keyWithWrongSign, dvtKey]; + + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, invalidKeys, meta); + + const walletAddress = await getWalletAddress(); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); - const { depositData: depositData, signature: lidoSign } = signDeposit( - pk, - sk, - LIDO_WC, - ); - const { wallet } = await makeDeposit(depositData, providerService); + const { signature: lidoSign } = signDeposit(pk, sk, LIDO_WC); expect(validateKeys).toBeCalledTimes(2); expect(validateKeys).toHaveBeenNthCalledWith( @@ -378,9 +378,9 @@ describe('ganache e2e tests', () => { expect(sendUnvetMessage).toHaveBeenCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, operatorIds: '0x0000000000000000', vettedKeysByOperator: '0x00000000000000000000000000000000', }), @@ -390,27 +390,27 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); - const newBlock = await providerService.provider.getBlock('latest'); - const fixedKey = { ...keyWithWrongSign, depositSignature: toHexString(lidoSign), }; - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [fixedKey, dvtKey], - ); + const fixedKeys = [fixedKey, dvtKey]; + + await providerService.provider.send('evm_mine', []); + const newBlock = await providerService.provider.getBlock('latest'); + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, fixedKeys, newMeta); validateKeys.mockClear(); sendDepositMessage.mockClear(); @@ -436,21 +436,105 @@ describe('ganache e2e tests', () => { 1, expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenNthCalledWith( 2, expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); }); + + test('adding not vetted invalid key will not set on soft pause module', async () => { + const currentBlock = await providerService.provider.getBlock('latest'); + + await depositService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + }, + }); + + await signingKeyEventsCacheService.setCachedEvents({ + data: [], + headers: { + startBlock: currentBlock.number, + endBlock: currentBlock.number, + stakingModulesAddresses: [NOP_REGISTRY, SIMPLE_DVT], + }, + }); + + const keyWithWrongSign = { + key: toHexString(pk), + // just some random sign + depositSignature: + '0x8bf4401a354de243a3716ee2efc0bde1ded56a40e2943ac7c50290bec37e935d6170b21e7c0872f203199386143ef12612a1488a8e9f1cdf1229c382f29c326bcbf6ed6a87d8fbfe0df87dacec6632fc4709d9d338f4cf81e861d942c23bba1e', + operatorIndex: 0, + used: false, + index: 0, + moduleAddress: NOP_REGISTRY, + vetted: false, + }; + + const dvtKey = { + ...mockKey, + moduleAddress: SIMPLE_DVT, + }; + + const keys = [keyWithWrongSign, dvtKey]; + + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); + + const walletAddress = await getWalletAddress(); + + await guardianService.handleNewBlock(); + + await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); + + expect(validateKeys).toBeCalledTimes(2); + expect(validateKeys).toHaveBeenNthCalledWith(1, []); + expect(validateKeys).toHaveBeenNthCalledWith( + 2, + expect.arrayContaining([ + expect.objectContaining({ + key: mockKey.key, + depositSignature: mockKey.depositSignature, + }), + ]), + ); + expect(sendUnvetMessage).toBeCalledTimes(0); + expect(sendDepositMessage).toBeCalledTimes(2); + expect(sendDepositMessage).toBeCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 1, + }), + ); + expect(sendDepositMessage).toBeCalledWith( + expect.objectContaining({ + blockNumber: currentBlock.number, + guardianAddress: walletAddress, + guardianIndex: 7, + stakingModuleId: 2, + }), + ); + expect(sendPauseMessage).toBeCalledTimes(0); + }); }); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index a118791d..a5765b1c 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -3,10 +3,11 @@ import { toHexString } from '@chainsafe/ssz'; // Helpers import { - mockedDvtOperators, - mockOperator1, - mockOperator2, - setupMockModules, + keysApiMockGetAllKeys, + keysApiMockGetModules, + mockedModuleCurated, + mockedModuleDvt, + mockMeta, } from './helpers'; // Constants @@ -45,7 +46,7 @@ import { GuardianMessageService } from 'guardian/guardian-message'; import { LevelDBService } from 'contracts/deposit/leveldb'; import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; -import { makeDeposit, signDeposit } from './helpers/deposit'; +import { getWalletAddress, makeDeposit, signDeposit } from './helpers/deposit'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; @@ -173,8 +174,8 @@ describe('ganache e2e tests', () => { }, }); - const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); - const { wallet } = await makeDeposit(depositData, providerService); + // const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); + const walletAddress = await getWalletAddress(); const keyWithWrongSign = { key: toHexString(pk), @@ -185,15 +186,17 @@ describe('ganache e2e tests', () => { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }; - const { sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign], - ); + const keys = [keyWithWrongSign]; + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); + await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); @@ -215,23 +218,21 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); // if depositData was not changed it will not validate again - + await providerService.provider.send('evm_mine', []); const newBlock = await providerService.provider.getBlock('latest'); - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign], - ); + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, newMeta); validateKeys.mockClear(); sendDepositMessage.mockClear(); @@ -248,9 +249,9 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); @@ -287,31 +288,29 @@ describe('ganache e2e tests', () => { used: false, index: 0, moduleAddress: NOP_REGISTRY, + vetted: true, }; const dvtKey = { ...mockKey, moduleAddress: SIMPLE_DVT, + vetted: true, }; - const { curatedModule, sdvtModule } = setupMockModules( - currentBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [keyWithWrongSign, dvtKey], - ); + const keys = [keyWithWrongSign, dvtKey]; + const meta = mockMeta(currentBlock, currentBlock.hash); + // setup /v1/modules + const stakingModules = [mockedModuleCurated, mockedModuleDvt]; + keysApiMockGetModules(keysApiService, stakingModules, meta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, keys, meta); await guardianService.handleNewBlock(); await new Promise((res) => setTimeout(res, SLEEP_FOR_RESULT)); - const { depositData: depositData, signature: lidoSign } = signDeposit( - pk, - sk, - LIDO_WC, - ); - const { wallet } = await makeDeposit(depositData, providerService); + const { signature: lidoSign } = signDeposit(pk, sk, LIDO_WC); + const walletAddress = await getWalletAddress(); expect(validateKeys).toBeCalledTimes(2); expect(validateKeys).toHaveBeenNthCalledWith( @@ -329,8 +328,8 @@ describe('ganache e2e tests', () => { 2, expect.arrayContaining([ expect.objectContaining({ - key: mockKey.key, - depositSignature: mockKey.depositSignature, + key: dvtKey.key, + depositSignature: dvtKey.depositSignature, }), ]), ); @@ -338,27 +337,27 @@ describe('ganache e2e tests', () => { expect(sendDepositMessage).toBeCalledWith( expect.objectContaining({ blockNumber: currentBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); expect(sendPauseMessage).toBeCalledTimes(0); - const newBlock = await providerService.provider.getBlock('latest'); - const fixedKey = { ...keyWithWrongSign, depositSignature: toHexString(lidoSign), }; - setupMockModules( - newBlock, - keysApiService, - [mockOperator1, mockOperator2], - mockedDvtOperators, - [fixedKey, dvtKey], - ); + const fixedKeys = [fixedKey, dvtKey]; + await providerService.provider.send('evm_mine', []); + const newBlock = await providerService.provider.getBlock('latest'); + + const newMeta = mockMeta(newBlock, newBlock.hash); + // setup /v1/modules + keysApiMockGetModules(keysApiService, stakingModules, newMeta); + // setup /v1/keys + keysApiMockGetAllKeys(keysApiService, fixedKeys, newMeta); validateKeys.mockClear(); sendDepositMessage.mockClear(); @@ -382,18 +381,18 @@ describe('ganache e2e tests', () => { 1, expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: curatedModule.id, + stakingModuleId: 1, }), ); expect(sendDepositMessage).toHaveBeenNthCalledWith( 2, expect.objectContaining({ blockNumber: newBlock.number, - guardianAddress: wallet.address, + guardianAddress: walletAddress, guardianIndex: 7, - stakingModuleId: sdvtModule.id, + stakingModuleId: 2, }), ); From 0ff89a29202427a832bb198d8d8312dab42c1168 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 9 Sep 2024 15:28:01 +0200 Subject: [PATCH 45/94] feat: deposit tree refactor --- .../deposits-registry/crypto/utils.ts | 4 ++++ .../deposit-tree/deposit-tree.spec.ts | 12 +++++------ .../deposit-tree/deposit-tree.ts | 20 ++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/contracts/deposits-registry/crypto/utils.ts b/src/contracts/deposits-registry/crypto/utils.ts index 26060e8c..5c7505ed 100644 --- a/src/contracts/deposits-registry/crypto/utils.ts +++ b/src/contracts/deposits-registry/crypto/utils.ts @@ -10,3 +10,7 @@ export const parseLittleEndian64 = (str: string) => { export const toLittleEndian64 = (value: number): string => { return toHexString(UintNum64.serialize(value)); }; + +export const toLittleEndian64BigInt = (value: bigint): string => { + return toHexString(UintNum64.serialize(Number(value))); +}; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index d09f9ab9..51c8d6ca 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -24,7 +24,7 @@ describe('DepositTree', () => { const initialNodeCount = depositTree.nodeCount; const node = new Uint8Array(32).fill(1); // Example node hash depositTree.insert(node); - expect(depositTree.nodeCount).toBe(initialNodeCount + 1); + expect(depositTree.nodeCount).toBe(initialNodeCount + 1n); }); test('should handle detailed node data correctly', () => { @@ -36,7 +36,7 @@ describe('DepositTree', () => { amount: '0x0100000000000000', }; originalTree.insert(DepositTree.formDepositNode(nodeData)); - expect(originalTree.nodeCount).toBe(1); + expect(Number(originalTree.nodeCount)).toBe(1); const oldDepositRoot = originalTree.getRoot(); const cloned = originalTree.clone(); @@ -91,7 +91,7 @@ describe('DepositTree', () => { amount: '0x0100000000000000', }; depositTree.insert(DepositTree.formDepositNode(nodeData)); - expect(depositTree.nodeCount).toBe(1); + expect(Number(depositTree.nodeCount)).toBe(1); }); test('should clone the tree correctly', () => { @@ -149,7 +149,7 @@ describe('DepositTree', () => { depositTree.insert(fromHexString(ev)), ); - expect(depositTree.nodeCount).toEqual( + expect(Number(depositTree.nodeCount)).toEqual( depositDataRootsFixture10k.events.length, ); expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); @@ -160,7 +160,7 @@ describe('DepositTree', () => { depositTree.insert(fromHexString(ev)), ); - expect(depositTree.nodeCount).toEqual( + expect(Number(depositTree.nodeCount)).toEqual( depositDataRootsFixture10k.events.length, ); expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); @@ -168,7 +168,7 @@ describe('DepositTree', () => { depositDataRootsFixture20k.events.map((ev) => depositTree.insert(fromHexString(ev)), ); - expect(depositTree.nodeCount).toEqual( + expect(Number(depositTree.nodeCount)).toEqual( depositDataRootsFixture10k.events.length + depositDataRootsFixture20k.events.length, ); diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index ed40061f..9d29a7df 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -3,7 +3,7 @@ import { digest2Bytes32, fromHexString, parseLittleEndian64, - toLittleEndian64, + toLittleEndian64BigInt, } from '../../../crypto'; import { ethers } from 'ethers'; import { NodeData } from '../../../interfaces'; @@ -17,7 +17,7 @@ export class DepositTree { static ZERO_HASH = fromHexString(ZERO_HASH_HEX); zeroHashes: Uint8Array[] = new Array(DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH); branch: Uint8Array[] = []; - nodeCount = 0; + nodeCount = 0n; constructor() { this.formZeroHashes(); @@ -48,7 +48,7 @@ export class DepositTree { */ private formBranch( node: Uint8Array, - depositCount: number, + depositCount: bigint, ): Uint8Array[] | undefined { let size = depositCount; for ( @@ -56,18 +56,14 @@ export class DepositTree { height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; height++ ) { - if ((size & 1) == 1) { + if (size % 2n === 1n) { this.branch[height] = node; return this.branch; } node = digest2Bytes32(this.branch[height], node); - // Using size /= 2 is not a mistake. In JavaScript, when performing bitwise operations - // like & 1, floating-point numbers are implicitly converted to integers, discarding the fractional part. - // This ensures the algorithm works correctly and matches the logic of a Solidity smart contract. - // Solidity does not have floating-point numbers, and all division is performed as integer division, rounding down the result. - size /= 2; + size /= 2n; } } @@ -92,16 +88,16 @@ export class DepositTree { height < DepositTree.DEPOSIT_CONTRACT_TREE_DEPTH; height++ ) { - if ((size & 1) == 1) { + if (size % 2n === 1n) { node = digest2Bytes32(this.branch[height], node); } else { node = digest2Bytes32(node, this.zeroHashes[height]); } - size /= 2; + size /= 2n; } const finalRoot = ethers.utils.soliditySha256( ['bytes', 'bytes', 'bytes'], - [node, toLittleEndian64(this.nodeCount), ZERO_HASH_ROOT_HEX], + [node, toLittleEndian64BigInt(this.nodeCount), ZERO_HASH_ROOT_HEX], ); return finalRoot; } From 8d0d48759f6717ae49553699d0c6ba5a76311b59 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 9 Sep 2024 15:32:19 +0200 Subject: [PATCH 46/94] fix: ts-config bump target version to es2020 --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 042e8f28..4d726a82 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "es2020", "sourceMap": true, "outDir": "./dist", "baseUrl": "./src", From 7df5aeac81f403008161ad8561b1a55822e77272 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 9 Sep 2024 18:34:36 +0400 Subject: [PATCH 47/94] fix: get rid of spread operator --- .../keys-duplication-checker.service.ts | 8 +++----- src/guardian/guardian.service.ts | 18 ++++++++---------- .../keys-validation/keys-validation.service.ts | 4 ++-- src/guardian/unvetting/unvetting.service.ts | 9 ++++----- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index f728b3db..ea493776 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -100,11 +100,9 @@ export class KeysDuplicationCheckerService { } private getOperators(keys: RegistryKey[]): string[] { - return [ - ...new Set( - keys.map((key) => `${key.moduleAddress}-${key.operatorIndex}`), - ), - ]; + return Array.from( + new Set(keys.map((key) => `${key.moduleAddress}-${key.operatorIndex}`)), + ); } private handleSingleOperatorDuplicates( diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 3ca18b97..cd47f099 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -352,11 +352,10 @@ export class GuardianService implements OnModuleInit { } private hasInvalidKeys(moduleData: StakingModuleData): boolean { - const keys = [ - ...moduleData.invalidKeys, - ...moduleData.duplicatedKeys, - ...moduleData.frontRunKeys, - ]; + const keys = moduleData.invalidKeys.concat( + moduleData.duplicatedKeys, + moduleData.frontRunKeys, + ); return keys.length > 0; } @@ -402,11 +401,10 @@ export class GuardianService implements OnModuleInit { theftHappened: boolean, alreadyPausedDeposits: boolean, ): boolean { - const keysForUnvetting = [ - ...stakingModuleData.invalidKeys, - ...stakingModuleData.frontRunKeys, - ...stakingModuleData.duplicatedKeys, - ]; + const keysForUnvetting = stakingModuleData.invalidKeys.concat( + stakingModuleData.frontRunKeys, + stakingModuleData.duplicatedKeys, + ); // if neither of this conditions is true, deposits are allowed for module return ( diff --git a/src/guardian/keys-validation/keys-validation.service.ts b/src/guardian/keys-validation/keys-validation.service.ts index ed3c83ba..2abddced 100644 --- a/src/guardian/keys-validation/keys-validation.service.ts +++ b/src/guardian/keys-validation/keys-validation.service.ts @@ -64,10 +64,10 @@ export class KeysValidationService { const invalidKeys = this.filterInvalidKeys(validatedDepositKeyList); - return [...cachedInvalidKeyList, ...invalidKeys]; + return cachedInvalidKeyList.concat(invalidKeys); } - filterInvalidKeys( + private filterInvalidKeys( validatedKeys: [DepositKey & Key, boolean][], ): RegistryKey[] { return validatedKeys.reduce( diff --git a/src/guardian/unvetting/unvetting.service.ts b/src/guardian/unvetting/unvetting.service.ts index 2c1ed0e1..aee26607 100644 --- a/src/guardian/unvetting/unvetting.service.ts +++ b/src/guardian/unvetting/unvetting.service.ts @@ -52,11 +52,10 @@ export class UnvettingService { private collectInvalidKeys( stakingModuleData: StakingModuleData, ): RegistryKey[] { - return [ - ...stakingModuleData.invalidKeys, - ...stakingModuleData.duplicatedKeys, - ...stakingModuleData.frontRunKeys, - ]; + return stakingModuleData.invalidKeys.concat( + stakingModuleData.duplicatedKeys, + stakingModuleData.frontRunKeys, + ); } private logNoUnvettingNeeded( From adde748c67d4882cb848c8bba65cfa0b3dacfae2 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 9 Sep 2024 23:24:11 +0200 Subject: [PATCH 48/94] feat: log the last valid event for debugging purposes --- .../deposits-registry.service.ts | 29 ++++++++++++++++--- .../interfaces/cache.interface.ts | 1 + .../integrity-checker.service.ts | 2 +- .../sanity-checker/sanity-checker.service.ts | 4 +-- .../deposits-registry/store/store.service.ts | 22 ++++++++++++++ 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index fb66e3e3..7f14d2c5 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -9,6 +9,7 @@ import { import { VerifiedDepositEventsCache, VerifiedDepositedEventGroup, + VerifiedDepositEvent, } from './interfaces'; import { RepositoryService } from 'contracts/repository'; import { BlockTag } from 'provider'; @@ -87,6 +88,8 @@ export class DepositRegistryService { if (!isCacheValid) return; + let lastIndexedEvent: VerifiedDepositEvent | undefined = undefined; + for ( let block = firstNotCachedBlock; block <= finalizedBlockNumber; @@ -123,6 +126,11 @@ export class DepositRegistryService { newEventsCount += chunkEventGroup.events.length; + const lastEventFromGroup = + chunkEventGroup.events[chunkEventGroup.events.length - 1]; + + if (lastEventFromGroup) lastIndexedEvent = lastEventFromGroup; + this.logger.log('Historical events are fetched', { finalizedBlockNumber, startBlock: chunkStartBlock, @@ -132,12 +140,18 @@ export class DepositRegistryService { const fetchTimeEnd = performance.now(); const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; - // TODO: replace timer with metric const isRootValid = await this.sanityChecker.verifyUpdatedEvents( - finalizedBlockNumber, + finalizedBlockHash, ); + // Store the last event from the list of updated events separately + // Unfortunately, we cannot validate each event individually upon insertion + // because this would require an archival node + if (isRootValid && lastIndexedEvent) { + await this.store.insertLastValidEvent(lastIndexedEvent); + } + if (!isRootValid) { this.logger.error('Integrity check failed on block', { finalizedBlock, @@ -191,9 +205,15 @@ export class DepositRegistryService { ); if (!isValid) { + const { lastValidEvent } = cachedEvents; this.logger.warn('Integrity check failed on block', { - blockNumber, - blockHash, + currentBlockNumber: blockNumber, + currentBlockHash: blockHash, + lastValidBlockNumber: lastValidEvent?.blockNumber, + lastValidBlockHash: lastValidEvent?.blockHash, + lastValidEventIndex: lastValidEvent?.index, + lastValidEventDepositDataRoot: lastValidEvent?.depositDataRoot, + lastValidEventDepositCount: lastValidEvent?.depositCount, }); } @@ -214,6 +234,7 @@ export class DepositRegistryService { isValid, }; } + /** * Returns a deposit root */ diff --git a/src/contracts/deposits-registry/interfaces/cache.interface.ts b/src/contracts/deposits-registry/interfaces/cache.interface.ts index b42b7fcb..a421da53 100644 --- a/src/contracts/deposits-registry/interfaces/cache.interface.ts +++ b/src/contracts/deposits-registry/interfaces/cache.interface.ts @@ -8,4 +8,5 @@ export interface VerifiedDepositEventsCacheHeaders { export interface VerifiedDepositEventsCache { headers: VerifiedDepositEventsCacheHeaders; data: VerifiedDepositEvent[]; + lastValidEvent?: VerifiedDepositEvent; } diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 08368a04..4500aaf6 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -11,7 +11,7 @@ import { DEPOSIT_TREE_STEP_SYNC } from './constants'; @Injectable() export class DepositIntegrityCheckerService { - finalizedTree = new DepositTree(); + private finalizedTree = new DepositTree(); constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private repositoryService: RepositoryService, diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index d70a228a..2cb81fc1 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -141,7 +141,7 @@ export class DepositRegistrySanityCheckerService { return isDepositRootMatches; } - public async verifyUpdatedEvents(blockNumber: number) { - return this.depositsIntegrityChecker.checkFinalizedRoot(blockNumber); + public async verifyUpdatedEvents(tag: string | number) { + return this.depositsIntegrityChecker.checkFinalizedRoot(tag); } } diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts index b574aa82..bd715e52 100644 --- a/src/contracts/deposits-registry/store/store.service.ts +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -69,6 +69,7 @@ export class DepositsRegistryStoreService { public async getEventsCache(): Promise<{ data: VerifiedDepositEvent[]; headers: VerifiedDepositEventsCacheHeaders; + lastValidEvent?: VerifiedDepositEvent; }> { try { const stream = this.db.iterator({ gte: 'deposit:', lte: 'deposit:\xFF' }); @@ -82,6 +83,16 @@ export class DepositsRegistryStoreService { await this.db.get('headers'), ); + const lastValidEvent = await this.db.get('last-valid-event'); + + if (lastValidEvent) { + return { + data, + headers, + lastValidEvent: this.parseDepositEvent(lastValidEvent), + }; + } + return { data, headers }; } catch (error: any) { if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; @@ -169,6 +180,17 @@ export class DepositsRegistryStoreService { await this.db.batch(ops); } + /** + * Inserts a batch of deposit events and a header into the database. + * + * @param {VerifiedDepositEvent} event - Last valid and verified event. + * @returns {Promise} A promise that resolves when all operations have been successfully committed to the database. + * @public + */ + public async insertLastValidEvent(event: VerifiedDepositEvent) { + await this.db.put('last-valid-event', this.serializeDepositEvent(event)); + } + /** * Clears all entries from the database. * From 4ecb600052844651782a549941fa3cbb9aa6289e Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 00:02:02 +0200 Subject: [PATCH 49/94] fix: default value for last valid event --- .../deposits-registry/store/store.service.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts index bd715e52..0a337fbd 100644 --- a/src/contracts/deposits-registry/store/store.service.ts +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -83,23 +83,35 @@ export class DepositsRegistryStoreService { await this.db.get('headers'), ); - const lastValidEvent = await this.db.get('last-valid-event'); - - if (lastValidEvent) { - return { - data, - headers, - lastValidEvent: this.parseDepositEvent(lastValidEvent), - }; - } + const lastValidEvent = await this.getLastValidEvent(); - return { data, headers }; + return { data, headers, lastValidEvent }; } catch (error: any) { if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; throw error; } } + /** + * Retrieves the last valid deposit event from the database. + * This method queries the database for the 'last-valid-event' key to fetch the most recent + * valid event and parses it into a `VerifiedDepositEvent` object. + * + * @returns {Promise} A promise that resolves to the last valid `VerifiedDepositEvent` object + * or `undefined` if no event is found or if the event could not be retrieved (e.g., key does not exist). + * + * @throws {Error} Throws an error if there is a database access issue other than a 'LEVEL_NOT_FOUND' error code. + */ + public async getLastValidEvent(): Promise { + try { + const lastValidEvent = await this.db.get('last-valid-event'); + return this.parseDepositEvent(lastValidEvent); + } catch (error: any) { + if (error.code === 'LEVEL_NOT_FOUND') return undefined; + throw error; + } + } + /** * Generates a deposit key string based on a given number. * The number is checked to ensure it falls within a valid range (from 0 up to MAX_DEPOSIT_COUNT). From d8ebcecdaae16efe4dd2acbb1351e6e06670d334 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 10 Sep 2024 12:45:58 +0400 Subject: [PATCH 50/94] fix: check sign validation return duplicates for unvetting too --- .../keys-validation/keys-validation.spec.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/guardian/keys-validation/keys-validation.spec.ts b/src/guardian/keys-validation/keys-validation.spec.ts index 5e79ed9a..a5718d74 100644 --- a/src/guardian/keys-validation/keys-validation.spec.ts +++ b/src/guardian/keys-validation/keys-validation.spec.ts @@ -55,7 +55,14 @@ describe('KeysValidationService', () => { }); it('validate without use of cache', async () => { - const keysForValidation = [...validKeys, invalidKey1, invalidKey2]; + const duplicate = { ...invalidKey1, index: 102 }; + const keysForValidation = [ + ...validKeys, + invalidKey1, + // getInvalidKeys should return all invalid duplicates + duplicate, + invalidKey2, + ]; const result = await keysValidationService.getInvalidKeys( keysForValidation, wc, @@ -71,22 +78,24 @@ describe('KeysValidationService', () => { expect(validateKeysFun).toBeCalledTimes(1); expect(validateKeysFun).toBeCalledWith(depositKeyList); - expect(result).toEqual([invalidKey1, invalidKey2]); + expect(result).toEqual([invalidKey1, duplicate, invalidKey2]); }); it('validate with use of cache ', async () => { + const duplicate = { ...invalidKey1, index: 102 }; // Test scenario where one invalid key was removed from request's list const newResult = await keysValidationService.getInvalidKeys( - [...validKeys, invalidKey1, invalidKey2], + [...validKeys, invalidKey1, duplicate, invalidKey2], wc, ); expect(validateKeysFun).toBeCalledTimes(1); expect(validateKeysFun).toBeCalledWith([]); - expect(newResult).toEqual([invalidKey1, invalidKey2]); + expect(newResult).toEqual([invalidKey1, duplicate, invalidKey2]); }); it('validate without use of cache because of signature change', async () => { + const duplicate = { ...invalidKey1, index: 102 }; const invalidKey2Fix = { ...invalidKey2, depositSignature: invalidKey2GoodSign, @@ -94,6 +103,7 @@ describe('KeysValidationService', () => { const keyForValidation = [ ...validKeys, invalidKey1, + duplicate, // change signature on valid invalidKey2Fix, ]; @@ -109,7 +119,7 @@ describe('KeysValidationService', () => { expect(validateKeysFun).toBeCalledTimes(1); expect(validateKeysFun).toBeCalledWith(depositKeyList); - expect(newResult).toEqual([invalidKey1]); + expect(newResult).toEqual([invalidKey1, duplicate]); }); }); }); From 486ea53da6bc08c1075afb46f195c8df92cc23a4 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 10 Sep 2024 13:29:26 +0400 Subject: [PATCH 51/94] fix: security tests --- src/contracts/security/security.service.spec.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 57eff423..9d0ac94c 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -191,6 +191,7 @@ describe('SecurityService', () => { describe('signPauseDataV3', () => { it('should add prefix', async () => { const blockNumber = 1; + const blockHash = '0x'; const mockGetPauseMessagePrefix = jest .spyOn(securityService, 'getPauseMessagePrefix') @@ -198,7 +199,10 @@ describe('SecurityService', () => { const signPauseData = jest.spyOn(walletService, 'signPauseDataV3'); - const signature = await securityService.signPauseDataV3(blockNumber); + const signature = await securityService.signPauseDataV3( + blockNumber, + blockHash, + ); expect(mockGetPauseMessagePrefix).toBeCalledTimes(1); expect(signPauseData).toBeCalledWith({ blockNumber: 1, @@ -280,6 +284,7 @@ describe('SecurityService', () => { describe('pauseDepositsV3', () => { const hash = hexZeroPad('0x1', 32); const blockNumber = 10; + const blockHash = '0x'; let mockWait; let mockPauseDeposits; @@ -304,7 +309,7 @@ describe('SecurityService', () => { () => ({ pauseDeposits: mockPauseDeposits } as any), ); - signature = await securityService.signPauseDataV3(blockNumber); + signature = await securityService.signPauseDataV3(blockNumber, blockHash); }); it('should call contract method', async () => { @@ -467,6 +472,7 @@ describe('SecurityService', () => { describe('messages prefixes', () => { const blockNumber = 10; + const blockHash = '0x'; beforeEach(async () => { jest @@ -485,7 +491,7 @@ describe('SecurityService', () => { return iface.encodeFunctionResult('ATTEST_MESSAGE_PREFIX', result); }); - const prefix = await securityService.getAttestMessagePrefix(blockNumber); + const prefix = await securityService.getAttestMessagePrefix(blockHash); expect(prefix).toBe(expected); expect(mockProviderCall).toBeCalledTimes(1); }); @@ -501,7 +507,7 @@ describe('SecurityService', () => { return iface.encodeFunctionResult('PAUSE_MESSAGE_PREFIX', result); }); - const prefix = await securityService.getPauseMessagePrefix(blockNumber); + const prefix = await securityService.getPauseMessagePrefix(blockHash); expect(prefix).toBe(expected); expect(mockProviderCall).toBeCalledTimes(1); }); @@ -517,7 +523,7 @@ describe('SecurityService', () => { return iface.encodeFunctionResult('UNVET_MESSAGE_PREFIX', result); }); - const prefix = await securityService.getUnvetMessagePrefix(blockNumber); + const prefix = await securityService.getUnvetMessagePrefix(blockHash); expect(prefix).toBe(expected); expect(mockProviderCall).toBeCalledTimes(1); }); From 05df9484ed49a24e9db231bca9aa0af661a5a44a Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 10 Sep 2024 14:43:23 +0400 Subject: [PATCH 52/94] fix: bump 3.0.0 -> 3.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51e5343e..50057c7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lido-council-daemon", - "version": "3.0.0", + "version": "3.0.1", "description": "Lido Council Daemon", "author": "Lido team", "private": true, From 635b978401b65026598b69907c8c73f2fc4e94b9 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 10 Sep 2024 17:02:13 +0400 Subject: [PATCH 53/94] fix: operators -> uniqueOperatorIdentifiers --- .../keys-duplication-checker.service.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index f728b3db..2ecd3d36 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -81,9 +81,11 @@ export class KeysDuplicationCheckerService { suspectedDuplicateKeys: RegistryKey[], blockData: BlockData, ): Promise<{ duplicates: RegistryKey[]; unresolved: RegistryKey[] }> { - const operators = this.getOperators(suspectedDuplicateKeys); + const uniqueOperatorIdentifiers = this.getUniqueIdentifiersForOperators( + suspectedDuplicateKeys, + ); - if (operators.length === 1) { + if (uniqueOperatorIdentifiers.length === 1) { return this.handleSingleOperatorDuplicates(suspectedDuplicateKeys); } @@ -94,17 +96,17 @@ export class KeysDuplicationCheckerService { return await this.handleMultiOperatorDuplicates( key, suspectedDuplicateKeys, - operators, + uniqueOperatorIdentifiers, blockData, ); } - private getOperators(keys: RegistryKey[]): string[] { - return [ - ...new Set( - keys.map((key) => `${key.moduleAddress}-${key.operatorIndex}`), - ), - ]; + private getUniqueIdentifiersForOperators(keys: RegistryKey[]): string[] { + return [...new Set(keys.map((key) => this.getKeyOperatorIdentifier(key)))]; + } + + private getKeyOperatorIdentifier(key: RegistryKey): string { + return `${key.moduleAddress}-${key.operatorIndex}`; } private handleSingleOperatorDuplicates( @@ -130,14 +132,14 @@ export class KeysDuplicationCheckerService { private async handleMultiOperatorDuplicates( key: string, suspectedDuplicateKeys: RegistryKey[], - operators: string[], + uniqueOperatorIdentifiers: string[], blockData: BlockData, ) { const { duplicateKeys, unresolvedKeys } = await this.getDuplicatesAcrossOperators( key, suspectedDuplicateKeys, - operators, + uniqueOperatorIdentifiers, blockData, ); return { duplicates: duplicateKeys, unresolved: unresolvedKeys }; @@ -167,13 +169,13 @@ export class KeysDuplicationCheckerService { private async getDuplicatesAcrossOperators( key: string, suspectedDuplicateKeys: RegistryKey[], - operators: string[], + uniqueOperatorIdentifiers: string[], blockData: BlockData, ) { const events = await this.fetchSigningKeyEvents(key, blockData); const operatorsWithoutEvents = this.getOperatorsWithoutEvents( - operators, + uniqueOperatorIdentifiers, events, ); @@ -236,13 +238,15 @@ export class KeysDuplicationCheckerService { } private getOperatorsWithoutEvents( - operators: string[], + uniqueOperatorIdentifiers: string[], events: SigningKeyEvent[], ): string[] { const eventOperators = new Set( events.map((event) => `${event.moduleAddress}-${event.operatorIndex}`), ); - return operators.filter((op) => !eventOperators.has(op)); + return uniqueOperatorIdentifiers.filter( + (operatorIdentifier) => !eventOperators.has(operatorIdentifier), + ); } private filterNonEarliestKeys( @@ -259,11 +263,9 @@ export class KeysDuplicationCheckerService { const earliestKey = this.findEarliestKeyWithinOperator(operatorKeys); this.logger.log('Earliest key is', { - ...{ - earliestKey, - createBlockNumber: earliestEvent.blockNumber, - createBlockHash: earliestEvent.blockHash, - }, + earliestKey, + createBlockNumber: earliestEvent.blockNumber, + createBlockHash: earliestEvent.blockHash, currentBlockNumber: blockData.blockNumber, currentBlockHash: blockData.blockHash, }); From d35847973b155f0d62bdf71fff78b441ef3dafa1 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 16:00:01 +0200 Subject: [PATCH 54/94] refactor: code review --- src/contracts/deposits-registry/deposits-registry.module.ts | 2 +- src/contracts/deposits-registry/fetcher/fetcher.service.ts | 4 ++-- .../sanity-checker/sanity-checker.service.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.module.ts b/src/contracts/deposits-registry/deposits-registry.module.ts index caad9c70..f53689be 100644 --- a/src/contracts/deposits-registry/deposits-registry.module.ts +++ b/src/contracts/deposits-registry/deposits-registry.module.ts @@ -14,7 +14,7 @@ export class DepositsRegistryModule { /** * Registers the deposits module with a specific tag to handle block finality. * The `finalizedTag` is primarily used to address issues with the Ganache handling of the 'finalized' tag, - * where it needs to be substituted with 'latest' for end-to-end tests. This tag is necessary only on a full Ethereum node + * where it needs to be substituted with 'latest' for end-to-end tests. This tag is necessary only on a Ethereum node * to avoid issues with blockchain reorganizations. * In a production environment, this argument should either be empty or set to 'finalized'. * diff --git a/src/contracts/deposits-registry/fetcher/fetcher.service.ts b/src/contracts/deposits-registry/fetcher/fetcher.service.ts index 688f2edc..1c7ce144 100644 --- a/src/contracts/deposits-registry/fetcher/fetcher.service.ts +++ b/src/contracts/deposits-registry/fetcher/fetcher.service.ts @@ -18,7 +18,7 @@ export class DepositsRegistryFetcherService { ) {} /** - * Returns events in the block range + * Returns events in the block range and verify signature * If the request failed, it tries to repeat it or split it into two * @param startBlock - start of the range * @param endBlock - end of the range @@ -36,7 +36,7 @@ export class DepositsRegistryFetcherService { } /** - * Returns events in the block range + * Returns events in the block range and verify signature * @param startBlock - start of the range * @param endBlock - end of the range * @returns event group diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index 2cb81fc1..0b2e7e38 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -23,7 +23,7 @@ export class DepositRegistrySanityCheckerService { return await this.depositsIntegrityChecker.putFinalizedEvents(events); } // putLatestEvents - private async checkFreshEventsChunk( + private async checkFreshEvents( blockNumber: number, events: VerifiedDepositEvent[], ) { @@ -133,7 +133,7 @@ export class DepositRegistrySanityCheckerService { if (isReorgFound) return false; // Check if the deposit root of the events matches the expected values. - const isDepositRootMatches = await this.checkFreshEventsChunk( + const isDepositRootMatches = await this.checkFreshEvents( blockNumber, freshEvents, ); From 633b9ac7dd9ba4d2c4790ddc0854434620b4950b Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 16:24:01 +0200 Subject: [PATCH 55/94] fix: depositDataRoot logging --- src/contracts/deposits-registry/crypto/index.ts | 1 + src/contracts/deposits-registry/deposits-registry.service.ts | 5 ++++- .../sanity-checker/sanity-checker.service.ts | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/contracts/deposits-registry/crypto/index.ts b/src/contracts/deposits-registry/crypto/index.ts index 374b83cf..3400a40c 100644 --- a/src/contracts/deposits-registry/crypto/index.ts +++ b/src/contracts/deposits-registry/crypto/index.ts @@ -1,2 +1,3 @@ export * from './containers'; export * from './utils'; +export { toHexString } from '@chainsafe/ssz'; diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 7f14d2c5..f5f5ae20 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -16,6 +16,7 @@ import { BlockTag } from 'provider'; import { DepositsRegistryStoreService } from './store'; import { DepositsRegistryFetcherService } from './fetcher/fetcher.service'; import { DepositRegistrySanityCheckerService } from './sanity-checker/sanity-checker.service'; +import { toHexString } from './crypto'; @Injectable() export class DepositRegistryService { @@ -212,7 +213,9 @@ export class DepositRegistryService { lastValidBlockNumber: lastValidEvent?.blockNumber, lastValidBlockHash: lastValidEvent?.blockHash, lastValidEventIndex: lastValidEvent?.index, - lastValidEventDepositDataRoot: lastValidEvent?.depositDataRoot, + lastValidEventDepositDataRoot: lastValidEvent?.depositDataRoot + ? toHexString(lastValidEvent?.depositDataRoot) + : '', lastValidEventDepositCount: lastValidEvent?.depositCount, }); } diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index 0b2e7e38..c0822e7e 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -6,7 +6,7 @@ import { } from '../interfaces'; import { BlockchainCheckerService } from './blockchain-checker/blockchain-checker.service'; import { DepositIntegrityCheckerService } from './integrity-checker'; - +import { toHexString } from '../crypto'; @Injectable() export class DepositRegistrySanityCheckerService { constructor( @@ -48,7 +48,7 @@ export class DepositRegistrySanityCheckerService { this.logger.error('Reorganization found in deposit event', { blockHash: event.blockHash, blockNumber: event.blockNumber, - depositDataRoot: event.depositDataRoot, + depositDataRoot: toHexString(event.depositDataRoot), }); return true; } From 37d3db745c5e2ea1e2a00ff0e797aaa3db40b9a3 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 16:32:35 +0200 Subject: [PATCH 56/94] refactor: add comments to deposit-registry module --- src/contracts/deposits-registry/deposits-registry.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index f5f5ae20..4ec76b17 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -81,7 +81,8 @@ export class DepositRegistryService { const totalEventsCount = initialCache.data.length; let newEventsCount = 0; - // verify blockchain + // check that the cache is written to a block less than or equal to the current block + // otherwise we consider that the Ethereum node has started sending incorrect data const isCacheValid = this.sanityChecker.verifyCacheBlock( initialCache, finalizedBlockNumber, From 2775d558f9255a41f61bf4cdc300b645cc0fb452 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 10 Sep 2024 23:11:32 +0400 Subject: [PATCH 57/94] fix: use rangePromise for processing duplicates events --- .../keys-duplication-checker.service.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index 2ecd3d36..554a3fb0 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -4,6 +4,9 @@ import { SigningKeyEvent } from 'contracts/signing-key-events-cache/interfaces/e import { BlockData } from 'guardian/interfaces'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { rangePromise } from 'utils'; + +const BATCH_SIZE = 10; @Injectable() export class KeysDuplicationCheckerService { @@ -39,10 +42,20 @@ export class KeysDuplicationCheckerService { // First element of sub-arrays is a key, second - all it's occurrences const suspectedDuplicateKeyGroups = this.getDuplicateKeyGroups(keys); - const result = await Promise.all( - suspectedDuplicateKeyGroups.map(([key, suspectedDuplicateKeys]) => - this.processDuplicateKeyGroup(key, suspectedDuplicateKeys, blockData), - ), + const processDuplicateGroup = async (index) => { + const [key, suspectedDuplicateKeys] = suspectedDuplicateKeyGroups[index]; + return await this.processDuplicateKeyGroup( + key, + suspectedDuplicateKeys, + blockData, + ); + }; + + const result = await rangePromise( + processDuplicateGroup, + 0, + suspectedDuplicateKeyGroups.length, + BATCH_SIZE, ); const duplicates = result.flatMap(({ duplicates }) => duplicates); From d782a4b728daf1c3f46a07e67ab713a1fe147b0f Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 23:29:33 +0200 Subject: [PATCH 58/94] refactor: code review --- .../deposits-registry/deposits-registry.service.ts | 2 +- .../sanity-checker/sanity-checker.service.ts | 4 ++-- src/guardian/guardian.service.ts | 6 +++--- test/duplicates-v3.e2e-spec.ts | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 4ec76b17..0c015d42 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -108,7 +108,7 @@ export class DepositRegistryService { chunkToBlock, ); - await this.sanityChecker.verifyEventsChunk( + await this.sanityChecker.addEventGroupToIndex( chunkStartBlock, chunkToBlock, chunkEventGroup.events, diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index c0822e7e..399fa4f4 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -84,7 +84,7 @@ export class DepositRegistrySanityCheckerService { return isCacheValid; } - public async verifyEventsChunk( + public async addEventGroupToIndex( chunkStartBlock: number, chunkToBlock: number, events: VerifiedDepositEvent[], @@ -93,7 +93,7 @@ export class DepositRegistrySanityCheckerService { const tree = await this.indexEventsChunk(events); - this.logger.log('Deposit events chunk was verified', { + this.logger.log('Deposit events chunk was indexed', { chunkStartBlock, chunkToBlock, depositRoot: tree.getRoot(), diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 3ab25d1c..8ab39f87 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -367,7 +367,7 @@ export class GuardianService implements OnModuleInit { ]; // if neither of this conditions is true, deposits are allowed for module - const isCannot = + const ignoreDeposits = keysForUnvetting.length > 0 || stakingModuleData.unresolvedDuplicatedKeys.length > 0 || alreadyPausedDeposits || @@ -375,7 +375,7 @@ export class GuardianService implements OnModuleInit { stakingModuleData.isModuleDepositsPaused || !isDepositsCacheValid; - if (isCannot) { + if (ignoreDeposits) { this.logger.warn('Deposits are not available', { keysForUnvetting: keysForUnvetting.length, duplicates: stakingModuleData.unresolvedDuplicatedKeys.length, @@ -387,6 +387,6 @@ export class GuardianService implements OnModuleInit { }); } - return isCannot; + return ignoreDeposits; } } diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index 02436791..95e2a1dd 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -167,7 +167,6 @@ describe('Deposits in case of duplicates', () => { test( 'skip deposits for module if find duplicated key across operator', async () => { - // TODO: mine new block instead (? @Anna) const { depositData } = signDeposit(pk, sk); const { wallet } = await makeDeposit(depositData, providerService); From 8fb21a8ad0a4267c969e7dab1caab9e3a5f6195c Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 10 Sep 2024 23:37:27 +0200 Subject: [PATCH 59/94] fix: add correct endBlock to getAllDepositedEvents invariant --- src/contracts/deposits-registry/deposits-registry.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 0c015d42..4a8680b6 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -187,7 +187,7 @@ export class DepositRegistryService { return { events: cachedEvents.data, startBlock: cachedEvents.headers.startBlock, - endBlock, + endBlock: cachedEvents.headers.endBlock, isValid: false, }; } From 7b522273e16b9ff08f6e75dc522c6c935d619698 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 11 Sep 2024 12:00:08 +0200 Subject: [PATCH 60/94] refactor: comments in integrity checker --- .../integrity-checker/integrity-checker.service.ts | 6 ++++-- .../sanity-checker/sanity-checker.service.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 4500aaf6..dbeb9c8d 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -51,7 +51,8 @@ export class DepositIntegrityCheckerService { } /** - * Checks the integrity of the latest root against the blockchain deposit root for a given block number. + * Checks the integrity of the latest deposit root against the blockchain deposit root for a given block number. + * latest is the tag against which the state relative to the blockchain is stored * @param {number} blockNumber - Block number to check the deposit root against. * @param {VerifiedDepositEvent[]} eventsCache - Latest events to verify against the deposit root. * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. @@ -68,7 +69,8 @@ export class DepositIntegrityCheckerService { } /** - * Checks the integrity of the finalized root against the blockchain deposit root for a given block number. + * Checks the integrity of the finalized deposit root against the blockchain deposit root for a given block number. + * finalized is the tag against which the state relative to the blockchain is stored. * @param {number} blockNumber - Block number to check the deposit root against. * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. */ diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index 399fa4f4..cad8d269 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -115,7 +115,7 @@ export class DepositRegistrySanityCheckerService { ) { const lastEvent = freshEvents[freshEvents.length - 1]; - // If there is no last event, validate the finalized root for the current block hash. + // If events list is empty, there is no last event, so validate the finalized root for the current block hash. if (!lastEvent) { return this.depositsIntegrityChecker.checkFinalizedRoot(currentBlockHash); } From 27c5e5e2484c08e64ef1b5dbaba1d4788f1834a4 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 11 Sep 2024 13:05:03 +0200 Subject: [PATCH 61/94] refactor: code review --- .../integrity-checker/deposit-tree/deposit-tree.ts | 2 +- .../integrity-checker/integrity-checker.service.ts | 2 +- src/guardian/guardian.service.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index 9d29a7df..a53a6c72 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -43,7 +43,7 @@ export class DepositTree { /** * Forms the branch of the tree needed to update the root when a new node is inserted. * @param {Uint8Array} node - The node's data to be inserted. - * @param {number} depositCount - The sequential index of the deposit, representing the total deposits. + * @param {bigint} depositCount - The sequential index of the deposit, representing the total deposits. * @returns {Uint8Array[] | undefined} The updated branch of the tree after inserting the node. */ private formBranch( diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index dbeb9c8d..19b5d258 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -118,7 +118,7 @@ export class DepositIntegrityCheckerService { if (index % DEPOSIT_TREE_STEP_SYNC === 0) { await new Promise((res) => setTimeout(res, 1)); - this.logger.log('Checking integrity of saved deposit events', { + this.logger.log('Inserting verified deposit events', { processed: index, remaining: eventsCache.length - index, }); diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 8ab39f87..3328b1d7 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -334,7 +334,7 @@ export class GuardianService implements OnModuleInit { ); if ( - this.cannotDeposit( + this.ignoreDeposits( stakingModuleData, blockData.theftHappened, blockData.alreadyPausedDeposits, @@ -353,7 +353,7 @@ export class GuardianService implements OnModuleInit { ); } - cannotDeposit( + private ignoreDeposits( stakingModuleData: StakingModuleData, theftHappened: boolean, alreadyPausedDeposits: boolean, From cea79936174396b93efd28fe30a7740857973ea2 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 11 Sep 2024 13:09:28 +0200 Subject: [PATCH 62/94] refactor: jsdoc comments --- .../integrity-checker/integrity-checker.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 19b5d258..84b8e1d7 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -71,7 +71,7 @@ export class DepositIntegrityCheckerService { /** * Checks the integrity of the finalized deposit root against the blockchain deposit root for a given block number. * finalized is the tag against which the state relative to the blockchain is stored. - * @param {number} blockNumber - Block number to check the deposit root against. + * @param {string | number} tag - Block Tag to check the deposit root against. * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. */ public async checkFinalizedRoot(tag: string | number): Promise { @@ -80,7 +80,7 @@ export class DepositIntegrityCheckerService { /** * A private helper method to compare the local deposit tree root with the remote deposit root from the blockchain. - * @param {number} blockNumber - Block number associated with the deposit root to verify. + * @param {string | number} tag - Block Tag associated with the deposit root to verify. * @param {DepositTree} tree - Deposit tree to use for comparison. * @returns {Promise} A promise that resolves if the roots match, otherwise logs an error and throws. */ From 329f10ed6521ed32368759a19a430139483d6e00 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 11 Sep 2024 17:52:52 +0400 Subject: [PATCH 63/94] fix: test extand --- src/guardian/keys-validation/keys-validation.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/guardian/keys-validation/keys-validation.spec.ts b/src/guardian/keys-validation/keys-validation.spec.ts index a5718d74..eebc91ad 100644 --- a/src/guardian/keys-validation/keys-validation.spec.ts +++ b/src/guardian/keys-validation/keys-validation.spec.ts @@ -79,6 +79,11 @@ describe('KeysValidationService', () => { expect(validateKeysFun).toBeCalledTimes(1); expect(validateKeysFun).toBeCalledWith(depositKeyList); expect(result).toEqual([invalidKey1, duplicate, invalidKey2]); + + expect(result[0].index).toEqual(invalidKey1.index); + expect(result[0].operatorIndex).toEqual(invalidKey1.operatorIndex); + expect(result[0].used).toEqual(invalidKey1.used); + expect(result[0].moduleAddress).toEqual(invalidKey1.moduleAddress); }); it('validate with use of cache ', async () => { From 97bf271da3f961b5600b7221707997b1614d5709 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 11 Sep 2024 17:39:05 +0200 Subject: [PATCH 64/94] fix: e2e test --- test/front-run-v3.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index 5f14db59..f7f2eae9 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -622,7 +622,7 @@ describe('ganache e2e tests', () => { async () => { const currentBlock = await providerService.provider.getBlock('latest'); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -670,7 +670,7 @@ describe('ganache e2e tests', () => { mockedMeta(currentBlock, currentBlock.hash), ); - await depositService.setCachedEvents({ + await levelDBService.setCachedEvents({ data: [ { valid: true, From eacd4d211aaabaff4f8d0cf6b378cec4fb721016 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 12 Sep 2024 12:12:14 +0400 Subject: [PATCH 65/94] fix: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d013b7c..95fd1a9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lido-council-daemon", - "version": "3.0.1", + "version": "3.0.2", "description": "Lido Council Daemon", "author": "Lido team", "private": true, From fa55ae87fba4f9c41ca2114fc920eae050935bd1 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 12 Sep 2024 10:56:50 +0200 Subject: [PATCH 66/94] feat: add KEYS_API_URL configuration --- src/common/config/configuration.ts | 1 + src/common/config/in-memory-configuration.ts | 15 ++++++++++++--- src/keys-api/keys-api.service.ts | 13 ++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/common/config/configuration.ts b/src/common/config/configuration.ts index 349a6f51..5539d95a 100644 --- a/src/common/config/configuration.ts +++ b/src/common/config/configuration.ts @@ -31,6 +31,7 @@ export interface Configuration { REGISTRY_KEYS_QUERY_CONCURRENCY: number; KEYS_API_PORT: number; KEYS_API_HOST: string; + KEYS_API_URL: string; LOCATOR_DEVNET_ADDRESS: string; WALLET_MIN_BALANCE: ethers.BigNumber; WALLET_CRITICAL_BALANCE: ethers.BigNumber; diff --git a/src/common/config/in-memory-configuration.ts b/src/common/config/in-memory-configuration.ts index e17dc2bf..b69809cd 100644 --- a/src/common/config/in-memory-configuration.ts +++ b/src/common/config/in-memory-configuration.ts @@ -128,15 +128,24 @@ export class InMemoryConfiguration implements Configuration { @Transform(({ value }) => parseInt(value, 10), { toClassOnly: true }) REGISTRY_KEYS_QUERY_CONCURRENCY = 5; + @ValidateIf((conf) => !conf.KEYS_API_URL) @IsNotEmpty() @IsNumber() @Min(1) @Transform(({ value }) => parseInt(value, 10), { toClassOnly: true }) - KEYS_API_PORT = 3001; + KEYS_API_PORT = 0; - @IsOptional() + @ValidateIf((conf) => !conf.KEYS_API_URL) + @IsNotEmpty() + @IsString() + KEYS_API_HOST = ''; + + @ValidateIf((conf) => { + return !conf.KEYS_API_PORT && !conf.KEYS_API_HOST; + }) + @IsNotEmpty() @IsString() - KEYS_API_HOST = 'http://localhost'; + KEYS_API_URL = ''; @IsOptional() @IsString() diff --git a/src/keys-api/keys-api.service.ts b/src/keys-api/keys-api.service.ts index e79931ad..e035cbca 100644 --- a/src/keys-api/keys-api.service.ts +++ b/src/keys-api/keys-api.service.ts @@ -16,6 +16,13 @@ export class KeysApiService { protected readonly fetchService: FetchService, ) {} + private getBaseUrl() { + const baseUrl = + this.config.KEYS_API_URL || + `${this.config.KEYS_API_HOST}:${this.config.KEYS_API_PORT}`; + return baseUrl; + } + protected async fetch(url: string, requestInit?: RequestInit) { const controller = new AbortController(); const { signal } = controller; @@ -24,8 +31,7 @@ export class KeysApiService { controller.abort(); }, FETCH_REQUEST_TIMEOUT); - const baseUrl = `${this.config.KEYS_API_HOST}:${this.config.KEYS_API_PORT}`; - + const baseUrl = this.getBaseUrl(); try { const res: Response = await this.fetchService.fetchJson( `${baseUrl}${url}`, @@ -34,9 +40,10 @@ export class KeysApiService { ...requestInit, }, ); + clearTimeout(timer); return res; - } catch (error) { + } catch (error: any) { clearTimeout(timer); throw error; } From 0d6b5ca795c473f76d63869ef854bc3daca24f6e Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 12 Sep 2024 12:48:36 +0200 Subject: [PATCH 67/94] feat: config loader tests --- .../config/config-loader.service.spec.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/common/config/config-loader.service.spec.ts b/src/common/config/config-loader.service.spec.ts index 729caf92..9f33789c 100644 --- a/src/common/config/config-loader.service.spec.ts +++ b/src/common/config/config-loader.service.spec.ts @@ -14,6 +14,25 @@ const DEFAULTS = { RPC_URL: 'some-rpc-url', RABBITMQ_URL: 'some-rabbit-url', RABBITMQ_LOGIN: 'some-rabbit-login', + KEYS_API_URL: 'keys-api', +}; + +const extractError = async ( + fn: Promise, +): Promise<[ValidationError[], T]> => { + try { + return [[], await fn]; + } catch (error: any) { + return [error as ValidationError[], undefined as unknown as T]; + } +}; + +const toHaveProblemWithRecords = ( + recordsKeys: string[], + errors: ValidationError[], +) => { + const errorKeys = errors.map((error) => error.property); + expect(recordsKeys.sort()).toEqual(errorKeys.sort()); }; describe('ConfigLoaderService base spec', () => { @@ -106,6 +125,96 @@ describe('ConfigLoaderService base spec', () => { }); }); + describe('kapi url config', () => { + test('all invariants are empty', async () => { + const prepConfig = plainToClass(InMemoryConfiguration, { + RABBITMQ_PASSCODE: 'some-rabbit-passcode', + ...DEFAULTS, + KEYS_API_URL: undefined, + }); + const [validationErrors] = await extractError( + configLoaderService.loadSecrets(prepConfig), + ); + + toHaveProblemWithRecords( + ['KEYS_API_URL', 'KEYS_API_PORT', 'KEYS_API_HOST'], + validationErrors, + ); + }); + + test('KEYS_API_URL is set and the rest is default', async () => { + const KEYS_API_URL = 'kapi-url'; + const KEYS_API_HOST = ''; + const KEYS_API_PORT = 0; + const prepConfig = plainToClass(InMemoryConfiguration, { + RABBITMQ_PASSCODE: 'some-rabbit-passcode', + ...DEFAULTS, + KEYS_API_URL, + }); + const [validationErrors, result] = await extractError( + configLoaderService.loadSecrets(prepConfig), + ); + expect(validationErrors).toHaveLength(0); + expect(result.KEYS_API_URL).toBe(KEYS_API_URL); + expect(result.KEYS_API_HOST).toBe(KEYS_API_HOST); + expect(result.KEYS_API_PORT).toBe(KEYS_API_PORT); + }); + + test('KEYS_API_URL is empty and the rest is set', async () => { + const KEYS_API_URL = undefined; + const KEYS_API_HOST = 'kapi-host'; + const KEYS_API_PORT = 2222; + const prepConfig = plainToClass(InMemoryConfiguration, { + RABBITMQ_PASSCODE: 'some-rabbit-passcode', + ...DEFAULTS, + KEYS_API_URL, + KEYS_API_HOST, + KEYS_API_PORT, + }); + const [validationErrors, result] = await extractError( + configLoaderService.loadSecrets(prepConfig), + ); + expect(validationErrors).toHaveLength(0); + expect(result.KEYS_API_URL).toBe(KEYS_API_URL); + expect(result.KEYS_API_HOST).toBe(KEYS_API_HOST); + expect(result.KEYS_API_PORT).toBe(KEYS_API_PORT); + }); + + test('KEYS_API_URL and KEYS_API_PORT are empty and the KEYS_API_HOST is set', async () => { + const KEYS_API_URL = undefined; + const KEYS_API_HOST = 'kapi-host'; + const KEYS_API_PORT = 0; + const prepConfig = plainToClass(InMemoryConfiguration, { + RABBITMQ_PASSCODE: 'some-rabbit-passcode', + ...DEFAULTS, + KEYS_API_URL, + KEYS_API_HOST, + KEYS_API_PORT, + }); + const [validationErrors] = await extractError( + configLoaderService.loadSecrets(prepConfig), + ); + toHaveProblemWithRecords(['KEYS_API_PORT'], validationErrors); + }); + + test('KEYS_API_URL and KEYS_API_HOST are empty and the KEYS_API_PORT is set', async () => { + const KEYS_API_URL = undefined; + const KEYS_API_HOST = ''; + const KEYS_API_PORT = 2222; + const prepConfig = plainToClass(InMemoryConfiguration, { + RABBITMQ_PASSCODE: 'some-rabbit-passcode', + ...DEFAULTS, + KEYS_API_URL, + KEYS_API_HOST, + KEYS_API_PORT, + }); + const [validationErrors] = await extractError( + configLoaderService.loadSecrets(prepConfig), + ); + toHaveProblemWithRecords(['KEYS_API_HOST'], validationErrors); + }); + }); + describe('wallet', () => { let configLoaderService: ConfigLoaderService; const DEFAULTS_WITH_RABBIT = { From c94d24b438fc24ebe4f721a469422304f8a082f6 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 12 Sep 2024 16:09:35 +0400 Subject: [PATCH 68/94] fix: dockerfile typechain error --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 29c1187c..cfe3a6ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:14.18.1-alpine3.13 as building # needed for git dependencies RUN apk update && apk upgrade && \ -apk add --no-cache bash=5.1.16-r0 git=2.30.6-r0 openssh=8.4_p1-r4 python3=3.8.15-r0 make=4.3-r0 g++=10.2.1_pre1-r3 + apk add --no-cache bash=5.1.16-r0 git=2.30.6-r0 openssh=8.4_p1-r4 python3=3.8.15-r0 make=4.3-r0 g++=10.2.1_pre1-r3 RUN mkdir /council @@ -13,10 +13,10 @@ RUN npm i -g npm@7.19.0 COPY ./package*.json ./ COPY ./yarn*.lock ./ +COPY ./src ./src RUN yarn install --frozen-lockfile --non-interactive && yarn cache clean COPY ./tsconfig*.json ./ -COPY ./src ./src RUN yarn typechain && yarn build From a078e1525bb10a6ee162e01ed072a5ffc16ab3e3 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 12 Sep 2024 16:19:06 +0400 Subject: [PATCH 69/94] fix: copy tsconfig before yarn install in DOckerfile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cfe3a6ae..dd535b71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,11 +13,10 @@ RUN npm i -g npm@7.19.0 COPY ./package*.json ./ COPY ./yarn*.lock ./ +COPY ./tsconfig*.json ./ COPY ./src ./src RUN yarn install --frozen-lockfile --non-interactive && yarn cache clean -COPY ./tsconfig*.json ./ - RUN yarn typechain && yarn build FROM node:14.18.1-alpine3.13 From 57e0063bc1c14d981e70ba9c8625e6be45c466cb Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 15 Sep 2024 23:18:02 +0200 Subject: [PATCH 70/94] fix: correct block hash in block tag --- .../integrity-checker.service.ts | 21 +++++++++---------- .../sanity-checker/sanity-checker.service.ts | 10 ++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 84b8e1d7..957ff6d6 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -58,14 +58,14 @@ export class DepositIntegrityCheckerService { * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. */ public async checkLatestRoot( - blockNumber: number, + blockHash: string, eventsCache: VerifiedDepositEvent[], ): Promise { const tree = await this.putLatestEvents( eventsCache.sort((a, b) => a.depositCount - b.depositCount), ); - return this.checkRoot(blockNumber, tree); + return this.checkRoot(blockHash, tree); } /** @@ -74,8 +74,8 @@ export class DepositIntegrityCheckerService { * @param {string | number} tag - Block Tag to check the deposit root against. * @returns {Promise} A promise that resolves if the roots match, otherwise throws an error. */ - public async checkFinalizedRoot(tag: string | number): Promise { - return this.checkRoot(tag, this.finalizedTree); + public async checkFinalizedRoot(blockHash: string): Promise { + return this.checkRoot(blockHash, this.finalizedTree); } /** @@ -84,13 +84,13 @@ export class DepositIntegrityCheckerService { * @param {DepositTree} tree - Deposit tree to use for comparison. * @returns {Promise} A promise that resolves if the roots match, otherwise logs an error and throws. */ - private async checkRoot(tag: string | number, tree: DepositTree) { + private async checkRoot(blockHash: string, tree: DepositTree) { const localRoot = tree.getRoot(); - const remoteRoot = await this.getDepositRoot(tag); + const remoteRoot = await this.getDepositRoot(blockHash); if (localRoot === remoteRoot) { this.logger.log('Integrity check successfully completed', { - tag, + blockHash, }); return true; } @@ -131,11 +131,10 @@ export class DepositIntegrityCheckerService { * @param {BlockTag | undefined} blockTag - Specific block number or tag to retrieve the deposit root for. * @returns {Promise} Promise that resolves with the deposit root. */ - public async getDepositRoot(blockTag?: BlockTag): Promise { + public async getDepositRoot(blockHash?: BlockTag): Promise { const contract = await this.repositoryService.getCachedDepositContract(); - const depositRoot = await contract.get_deposit_root({ - blockTag: blockTag as any, - }); + const overrides = { blockTag: { blockHash } }; + const depositRoot = await contract.get_deposit_root(overrides as any); return depositRoot; } diff --git a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts index cad8d269..207ffd1b 100644 --- a/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/sanity-checker.service.ts @@ -24,11 +24,11 @@ export class DepositRegistrySanityCheckerService { } // putLatestEvents private async checkFreshEvents( - blockNumber: number, + blockHash: string, events: VerifiedDepositEvent[], ) { return await this.depositsIntegrityChecker.checkLatestRoot( - blockNumber, + blockHash, events, ); } @@ -134,14 +134,14 @@ export class DepositRegistrySanityCheckerService { // Check if the deposit root of the events matches the expected values. const isDepositRootMatches = await this.checkFreshEvents( - blockNumber, + blockHash, freshEvents, ); return isDepositRootMatches; } - public async verifyUpdatedEvents(tag: string | number) { - return this.depositsIntegrityChecker.checkFinalizedRoot(tag); + public async verifyUpdatedEvents(blockHash: string) { + return this.depositsIntegrityChecker.checkFinalizedRoot(blockHash); } } From 4c64ea7d0861d80c43f77967073c990354622bb6 Mon Sep 17 00:00:00 2001 From: Eddort Date: Sun, 15 Sep 2024 23:31:51 +0200 Subject: [PATCH 71/94] refactor: block hash type --- .../integrity-checker/integrity-checker.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 957ff6d6..87f50c6e 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -1,7 +1,6 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { RepositoryService } from 'contracts/repository'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { BlockTag } from 'provider'; import { DepositTree } from './deposit-tree'; import { VerifiedDepositEvent, @@ -131,7 +130,7 @@ export class DepositIntegrityCheckerService { * @param {BlockTag | undefined} blockTag - Specific block number or tag to retrieve the deposit root for. * @returns {Promise} Promise that resolves with the deposit root. */ - public async getDepositRoot(blockHash?: BlockTag): Promise { + public async getDepositRoot(blockHash: string): Promise { const contract = await this.repositoryService.getCachedDepositContract(); const overrides = { blockTag: { blockHash } }; const depositRoot = await contract.get_deposit_root(overrides as any); From e93b10eae4a2c0c5f9b6e6c6bbfefc9a3f690109 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 08:35:01 +0200 Subject: [PATCH 72/94] feat: base signing keys registry structure --- .../signing-keys-registry/constants.ts | 22 ++ .../fetcher/fetcher.module.ts | 10 + .../fetcher/fetcher.service.ts | 86 +++++ .../signing-keys-registry/fetcher/index.ts | 2 + src/contracts/signing-keys-registry/index.ts | 2 + .../interfaces/cache.interface.ts | 12 + .../interfaces/event.interface.ts | 18 + .../sanity-checker/index.ts | 0 .../sanity-checker/sanity-checker.module.ts | 8 + .../sanity-checker/sanity-checker.service.ts | 75 ++++ .../signing-keys-registry.module.ts | 15 + .../signing-keys-registry.service.ts | 356 ++++++++++++++++++ .../signing-keys-registry.spec.ts | 175 +++++++++ .../signing-keys-registry/store/index.ts | 3 + .../store/store.constants.ts | 3 + .../store/store.fixtures.ts | 71 ++++ .../store/store.module.ts | 34 ++ .../store/store.service.spec.ts | 67 ++++ .../store/store.service.ts | 249 ++++++++++++ 19 files changed, 1208 insertions(+) create mode 100644 src/contracts/signing-keys-registry/constants.ts create mode 100644 src/contracts/signing-keys-registry/fetcher/fetcher.module.ts create mode 100644 src/contracts/signing-keys-registry/fetcher/fetcher.service.ts create mode 100644 src/contracts/signing-keys-registry/fetcher/index.ts create mode 100644 src/contracts/signing-keys-registry/index.ts create mode 100644 src/contracts/signing-keys-registry/interfaces/cache.interface.ts create mode 100644 src/contracts/signing-keys-registry/interfaces/event.interface.ts create mode 100644 src/contracts/signing-keys-registry/sanity-checker/index.ts create mode 100644 src/contracts/signing-keys-registry/sanity-checker/sanity-checker.module.ts create mode 100644 src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts create mode 100644 src/contracts/signing-keys-registry/signing-keys-registry.module.ts create mode 100644 src/contracts/signing-keys-registry/signing-keys-registry.service.ts create mode 100644 src/contracts/signing-keys-registry/signing-keys-registry.spec.ts create mode 100644 src/contracts/signing-keys-registry/store/index.ts create mode 100644 src/contracts/signing-keys-registry/store/store.constants.ts create mode 100644 src/contracts/signing-keys-registry/store/store.fixtures.ts create mode 100644 src/contracts/signing-keys-registry/store/store.module.ts create mode 100644 src/contracts/signing-keys-registry/store/store.service.spec.ts create mode 100644 src/contracts/signing-keys-registry/store/store.service.ts diff --git a/src/contracts/signing-keys-registry/constants.ts b/src/contracts/signing-keys-registry/constants.ts new file mode 100644 index 00000000..e98d087c --- /dev/null +++ b/src/contracts/signing-keys-registry/constants.ts @@ -0,0 +1,22 @@ +import { CHAINS } from '@lido-sdk/constants'; + +export const SIGNING_KEYS_CACHE_DEFAULT = Object.freeze({ + headers: { + stakingModulesAddresses: [], + startBlock: 0, + endBlock: 0, + }, + data: [], +}); + +export const EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK: { + [key in CHAINS]?: number; +} = { + [CHAINS.Mainnet]: 11473216, + [CHAINS.Holesky]: 0, +}; + +// will make a gap in case of reorganization +export const SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS = 100; +export const SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; +export const FETCHING_EVENTS_STEP = 10_000; diff --git a/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts b/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts new file mode 100644 index 00000000..66338a99 --- /dev/null +++ b/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { BlsModule } from 'bls'; +import { SigningKeysRegistryFetcherService } from './fetcher.service'; + +@Module({ + imports: [BlsModule], + providers: [SigningKeysRegistryFetcherService], + exports: [SigningKeysRegistryFetcherService], +}) +export class SigningKeysRegistryFetcherModule {} diff --git a/src/contracts/signing-keys-registry/fetcher/fetcher.service.ts b/src/contracts/signing-keys-registry/fetcher/fetcher.service.ts new file mode 100644 index 00000000..78e6d994 --- /dev/null +++ b/src/contracts/signing-keys-registry/fetcher/fetcher.service.ts @@ -0,0 +1,86 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { StakingRouterService } from 'contracts/staking-router'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { ProviderService } from 'provider'; +import { + SigningKeyEvent, + SigningKeyEventsGroup, +} from '../interfaces/event.interface'; + +@Injectable() +export class SigningKeysRegistryFetcherService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private providerService: ProviderService, + private stakingRouterService: StakingRouterService, + ) {} + + /** + * Fetches signing key events within a specified block range, with fallback mechanisms. + * If the request failed, it tries to repeat it or split it into two + * + * @param {number} startBlock - The starting block number of the range. + * @param {number} endBlock - The ending block number of the range. + * @returns {Promise} Events fetched within the specified block range + */ + public async fetchEventsFallOver( + startBlock: number, + endBlock: number, + stakingModulesAddresses: string[], + ): Promise { + const fetcherWrapper = (start: number, end: number) => + this.fetchEvents(start, end, stakingModulesAddresses); + + return await this.providerService.fetchEventsFallOver( + startBlock, + endBlock, + fetcherWrapper, + ); + } + + /** + * Fetches signing key events within a specified block range from staking module contracts. + * + * @param {number} startBlock - The starting block number of the range. + * @param {number} endBlock - The ending block number of the range. + * @returns {Promise} Events fetched within the specified block range. + */ + public async fetchEvents( + startBlock: number, + endBlock: number, + stakingModulesAddresses: string[], + ): Promise { + const events: SigningKeyEvent[] = []; + + await Promise.all( + stakingModulesAddresses.map(async (address) => { + const rawEvents = + await this.stakingRouterService.getSigningKeyAddedEvents( + startBlock, + endBlock, + address, + ); + + const moduleEvents: SigningKeyEvent[] = rawEvents.map((rawEvent) => { + return { + operatorIndex: rawEvent.args[0].toNumber(), + key: rawEvent.args[1], + moduleAddress: address, + blockNumber: rawEvent.blockNumber, + logIndex: rawEvent.logIndex, + blockHash: rawEvent.blockHash, + }; + }); + + events.push(...moduleEvents); + + this.logger.log('Fetched signing keys add events for staking module', { + count: moduleEvents.length, + address, + }); + }), + ); + + return { events, startBlock, endBlock }; + } +} diff --git a/src/contracts/signing-keys-registry/fetcher/index.ts b/src/contracts/signing-keys-registry/fetcher/index.ts new file mode 100644 index 00000000..128136bc --- /dev/null +++ b/src/contracts/signing-keys-registry/fetcher/index.ts @@ -0,0 +1,2 @@ +export * from './fetcher.module'; +export * from './fetcher.service'; diff --git a/src/contracts/signing-keys-registry/index.ts b/src/contracts/signing-keys-registry/index.ts new file mode 100644 index 00000000..baafb04b --- /dev/null +++ b/src/contracts/signing-keys-registry/index.ts @@ -0,0 +1,2 @@ +export * from './signing-key-events-cache.module'; +export * from './signing-key-events-cache.service'; diff --git a/src/contracts/signing-keys-registry/interfaces/cache.interface.ts b/src/contracts/signing-keys-registry/interfaces/cache.interface.ts new file mode 100644 index 00000000..eac22b32 --- /dev/null +++ b/src/contracts/signing-keys-registry/interfaces/cache.interface.ts @@ -0,0 +1,12 @@ +import { SigningKeyEvent } from './event.interface'; + +export interface SigningKeyEventsCacheHeaders { + stakingModulesAddresses: string[]; + startBlock: number; + endBlock: number; +} + +export interface SigningKeyEventsCache { + headers: SigningKeyEventsCacheHeaders; + data: SigningKeyEvent[]; +} diff --git a/src/contracts/signing-keys-registry/interfaces/event.interface.ts b/src/contracts/signing-keys-registry/interfaces/event.interface.ts new file mode 100644 index 00000000..e84c01a0 --- /dev/null +++ b/src/contracts/signing-keys-registry/interfaces/event.interface.ts @@ -0,0 +1,18 @@ +export interface SigningKeyEvent { + operatorIndex: number; + key: string; + moduleAddress: string; + logIndex: number; + blockNumber: number; + blockHash: string; +} + +export interface SigningKeyEventsGroup { + events: SigningKeyEvent[]; + startBlock: number; + endBlock: number; +} +export interface SigningKeyEventsGroupWithStakingModules + extends SigningKeyEventsGroup { + stakingModulesAddresses: string[]; +} diff --git a/src/contracts/signing-keys-registry/sanity-checker/index.ts b/src/contracts/signing-keys-registry/sanity-checker/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.module.ts b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.module.ts new file mode 100644 index 00000000..8cbedd71 --- /dev/null +++ b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { SigningKeysRegistrySanityCheckerService } from './sanity-checker.service'; + +@Module({ + providers: [SigningKeysRegistrySanityCheckerService], + exports: [SigningKeysRegistrySanityCheckerService], +}) +export class SigningKeysRegistrySanityCheckerModule {} diff --git a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts new file mode 100644 index 00000000..258a33da --- /dev/null +++ b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts @@ -0,0 +1,75 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; + +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; + +import { SigningKeyEventsCache } from '../interfaces/cache.interface'; +import { SigningKeyEvent } from '../interfaces/event.interface'; + +@Injectable() +export class SigningKeysRegistrySanityCheckerService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + ) {} + + /** + * Validates the block number in the cached events against the current block number. + * + * This method checks if the cached events are up to date by comparing the current block number + * with the end block number in the cache. It logs a message if the cache is valid and a warning if it is not. + * + * @param {SigningKeyEventsCache} cachedEvents - The cached events containing block headers to validate. + * @param {number} currentBlock - The current block number to compare against the cached block. + * @returns {boolean} `true` if the cache is valid (i.e., the current block number is greater than or equal to the cached end block), `false` otherwise. + */ + public validateCacheBlock( + cachedEvents: SigningKeyEventsCache, + currentBlock: number, + ): boolean { + const isCacheValid = currentBlock >= cachedEvents.headers.endBlock; + + const blocks = { + cachedStartBlock: cachedEvents.headers.startBlock, + cachedEndBlock: cachedEvents.headers.endBlock, + currentBlock, + }; + + if (isCacheValid) { + this.logger.log('Signing keys events cache has valid age', blocks); + } + + if (!isCacheValid) { + this.logger.warn( + 'Signing key events cache is newer than the current block', + blocks, + ); + } + + return isCacheValid; + } + + /** + * Validates the block hash of signing key events. + * + * This method checks each event's block hash against the provided block hash, but only if the event's block number + * matches the given `blockNumber`. This ensures that the events are not from an alternate chain (e.g., due to a chain reorganization). + * If a block number match is found but the block hashes do not match, an error is thrown. + * + * @param {SigningKeyEvent[]} events - The list of signing key events to be checked. + * @param {number} blockNumber - The block number to match against the events' block numbers. + * @param {string} blockHash - The block hash to match against the events' block hashes. + * @throws {Error} If any event's block hash does not match the provided block hash for the specified block number. + */ + public checkEventsBlockHash( + events: SigningKeyEvent[], + blockNumber: number, + blockHash: string, + ): void { + events.forEach((event) => { + if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { + throw new Error( + 'Blockhash of the received events does not match the current blockhash', + ); + } + }); + } +} diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.module.ts b/src/contracts/signing-keys-registry/signing-keys-registry.module.ts new file mode 100644 index 00000000..47b42160 --- /dev/null +++ b/src/contracts/signing-keys-registry/signing-keys-registry.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { SigningKeysStoreModule } from './store'; +import { SigningKeysRegistryService } from './signing-keys-registry.service'; +import { SIGNING_KEYS_CACHE_DEFAULT } from './constants'; +import { StakingRouterModule } from 'contracts/staking-router'; + +@Module({ + imports: [ + StakingRouterModule, + SigningKeysStoreModule.register(SIGNING_KEYS_CACHE_DEFAULT), + ], + providers: [SigningKeysRegistryService], + exports: [SigningKeysRegistryService], +}) +export class SigningKeysRegistryModule {} diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts new file mode 100644 index 00000000..221860ed --- /dev/null +++ b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts @@ -0,0 +1,356 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { ProviderService } from 'provider'; +import { + SigningKeyEvent, + SigningKeyEventsGroupWithStakingModules, +} from './interfaces/event.interface'; +import { SigningKeysStoreService } from './store'; +import { SigningKeyEventsCache } from './interfaces/cache.interface'; +import { + EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, + FETCHING_EVENTS_STEP, + SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS, + SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE, +} from './constants'; +import { performance } from 'perf_hooks'; +import { SigningKeysRegistryFetcherService } from './fetcher'; + +@Injectable() +export class SigningKeysRegistryService { + constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, + private providerService: ProviderService, + private levelDBCacheService: SigningKeysStoreService, + private fetcher: SigningKeysRegistryFetcherService, + ) {} + + /** + * Handles the logic for processing a new block. + * + * This method checks if the staking module list has been updated and, if so, deletes the cache and updates the events cache. + * If the staking module list has not been updated, it checks whether the block number is divisible by the + * `SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE` and, if true, updates the events cache. + * + * @param {number} blockNumber - The block number of the newly processed block. + * @returns {Promise} + */ + public async handleNewBlock( + blockNumber: number, + currentstakingModulesAddresses: string[], + ): Promise { + const wasUpdated = await this.stakingModuleListWasUpdated( + currentstakingModulesAddresses, + ); + if (wasUpdated) { + this.logger.log('Staking module list was updated. Deleting cache'); + await this.levelDBCacheService.deleteCache(); + await this.updateEventsCache(currentstakingModulesAddresses); + } else if (blockNumber % SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE === 0) { + // update for every SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE block + await this.updateEventsCache(currentstakingModulesAddresses); + } + } + + /** + * Initialize or update cache + * @param {number} blockNumber - The block number to validate the cache against. + * @returns {Promise} + */ + public async initialize( + blockNumber: number, + currentstakingModulesAddresses: string[], + ) { + await this.levelDBCacheService.initialize(); + + const cachedEvents = await this.getCachedEvents(); + + // check age of cache + const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); + + if (!isCacheValid) { + process.exit(1); + } + + const wasUpdated = await this.stakingModuleListWasUpdated( + currentstakingModulesAddresses, + ); + if (wasUpdated) { + this.logger.log('Staking module list was updated. Deleting cache'); + await this.levelDBCacheService.deleteCache(); + } + + await this.updateEventsCache(currentstakingModulesAddresses); + } + + /** + * Updates the cache signing keys events + * The last N blocks are not stored, in order to avoid storing reorganized blocks + * + * @returns {Promise} The block number up to which the cache has been updated. + */ + public async updateEventsCache( + currentStakingModulesAddresses: string[], + ): Promise { + const fetchTimeStart = performance.now(); + + // TODO: maybe we should add blockNumber as argument of updateEventsCache method + // and use block number from initialized and handleNewBlock methods + const [latestBlock, initialCache] = await Promise.all([ + this.providerService.getBlockNumber(), + this.getCachedEvents(), + ]); + + const firstNotCachedBlock = initialCache.headers.endBlock + 1; + const toBlock = latestBlock - SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS; + + const totalEventsCount = initialCache.data.length; + let newEventsCount = 0; + + for ( + let block = firstNotCachedBlock; + block <= toBlock; + block += FETCHING_EVENTS_STEP + ) { + const chunkStartBlock = block; + const chunkToBlock = Math.min(toBlock, block + FETCHING_EVENTS_STEP - 1); + + const chunkEventGroup = await this.fetcher.fetchEventsFallOver( + chunkStartBlock, + chunkToBlock, + currentStakingModulesAddresses, + ); + + await this.levelDBCacheService.insertEventsCacheBatch({ + headers: { + ...initialCache.headers, + // as we update staking modules addresses always before run of this method, we can update value on every iteration + stakingModulesAddresses: currentStakingModulesAddresses, + endBlock: chunkEventGroup.endBlock, + }, + data: chunkEventGroup.events, + }); + + newEventsCount += chunkEventGroup.events.length; + + this.logger.log('Historical signing key add events are fetched', { + toBlock, + startBlock: chunkStartBlock, + endBlock: chunkToBlock, + }); + } + + const fetchTimeEnd = performance.now(); + const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; + // TODO: replace timer with metric + + this.logger.log('Signing key events cache is updated', { + newEventsCount, + totalEventsCount: totalEventsCount + newEventsCount, + fetchTime, + }); + + return toBlock; + } + + /** + * Checks if the list of staking modules has been updated. + * + * This method compares the current list of staking modules with the previously cached list. + * If the list has changed, it logs a warning and indicates that the cache needs to be cleared and updated. + * + * @returns {Promise} Return `true` if the staking modules list was updated, `false` otherwise. + */ + public async stakingModuleListWasUpdated( + currentModules: string[], + ): Promise { + const { + headers: { stakingModulesAddresses: previousModules }, + } = await this.levelDBCacheService.getHeader(); + + const wasUpdated = this.wasStakingModulesListUpdated( + previousModules, + currentModules, + ); + + if (wasUpdated) { + this.logger.warn( + 'Staking module list was changed. Need to clear and update cache', + { + previousModules, + currentModules, + }, + ); + } + + return wasUpdated; + } + + /** + * Compares the previous and current lists of staking modules to determine if any changes have occurred. + * + * This method checks if any staking modules were added or deleted by comparing the previous + * and current lists of staking modules. + * + * @param {string[]} previousModules - The list of staking modules from the previous cache. + * @param {string[]} currentModules - The current list of staking modules. + * @returns {boolean} `true` if the staking modules list was updated (modules were added or deleted), `false` otherwise. + */ + public wasStakingModulesListUpdated( + previousModules: string[], + currentModules: string[], + ) { + const modulesWereDeleted = previousModules.some( + (sm) => !currentModules.includes(sm), + ); + const modulesWereAdded = currentModules.some( + (module) => !previousModules.includes(module), + ); + + return modulesWereDeleted || modulesWereAdded; + } + + /** + * Retrieves signing key events data from the cache. + * + * This method fetches cached signing key events along with their associated headers. + * If the headers have default values (like 0 for the start and end block numbers), + * these values are updated to reflect the actual deployment block of the network. + * + * @returns {Promise} A promise that resolves to a `SigningKeyEventsCache` object, + * containing the cached signing key events and their metadata. + */ + public async getCachedEvents(): Promise { + const { headers, data } = await this.levelDBCacheService.getEventsCache(); + + // default values is startBlock: 0, endBlock: 0 + const deploymentBlock = await this.getDeploymentBlockByNetwork(); + + return { + headers: { + ...headers, + startBlock: Math.max(headers.startBlock, deploymentBlock), + endBlock: Math.max(headers.endBlock, deploymentBlock), + }, + data, + }; + } + + /** + * Retrieves signing key events from the cache for the specified operators' keys. + * + * This method takes a list of operators' keys, ensures the list contains unique keys, + * and then fetches the corresponding events from the cache. + * + * @param {string[]} keys - An array of operators' keys for which to retrieve events. + * @returns {Promise} Events associated with the specified keys. + */ + public async getEventsForOperatorsKeys( + keys: string[], + ): Promise { + const uniqueKeys = Array.from(new Set(keys)); + return await this.levelDBCacheService.getCachedEvents(uniqueKeys); + } + + /** + * Retrieves and returns all signing key events based on cached data and fresh data for a given key. + * + * This method combines cached signing key events with newly fetched events for a specific key, + * ensuring the cache is valid and updating the cache if necessary. + * + * @param {string} key - The specific signing key to retrieve events for. + * @param {number} blockNumber - The block number up to which the events should be retrieved. + * @param {string} blockHash - The block hash used to verify the integrity of the retrieved events. + * @returns {Promise} merged signing key events and associated staking module addresses. + */ + public async getUpdatedSigningKeyEvents( + key: string, + blockNumber: number, + blockHash: string, + ): Promise { + const endBlock = blockNumber; + const cachedEvents = await this.getEventsForOperatorsKeys([key]); + + const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); + if (!isCacheValid) process.exit(1); + + const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; + + const freshEventGroup = await this.fetcher.fetchEventsFallOver( + firstNotCachedBlock, + endBlock, + cachedEvents.headers.stakingModulesAddresses, + ); + const freshEvents = freshEventGroup.events; + const lastEvent = freshEvents[freshEvents.length - 1]; + const lastEventBlockHash = lastEvent?.blockHash; + + this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); + + this.logger.debug?.('Fresh signing key add events are fetched', { + events: freshEvents.length, + startBlock: firstNotCachedBlock, + endBlock, + blockHash, + lastEventBlockHash, + }); + + const keyFreshEvents = freshEventGroup.events.filter( + (event) => event.key == key, + ); + + const mergedEvents = cachedEvents.data.concat(keyFreshEvents); + + this.logger.debug?.('Merged signing key add events', { + events: mergedEvents.length, + startBlock: firstNotCachedBlock, + endBlock, + blockHash, + lastEventBlockHash, + }); + + return { + events: mergedEvents, + stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, + startBlock: cachedEvents.headers.startBlock, + endBlock, + }; + } + + /** + * Saves signing key events to the cache. + * + * This method first deletes the existing cache and then saves the provided signing key events + * and their associated headers to the cache. + * + * @param {SigningKeyEventsCache} cachedEvents - An object containing the signing key events and headers to be saved to the cache. + * @returns {Promise} + */ + public async setCachedEvents( + cachedEvents: SigningKeyEventsCache, + ): Promise { + await this.levelDBCacheService.deleteCache(); + await this.levelDBCacheService.insertEventsCacheBatch({ + data: cachedEvents.data, + headers: cachedEvents.headers, + }); + } + + /** + * Retrieves the block number when the curated module contract was deployed for the current network. + * + * This method determines the deployment block number based on the current network's chain ID. + * If the chain ID is not supported, an error is thrown. + * + * @returns {Promise} Block number where the curated module contract was deployed. + * @throws {Error} If the chain ID is not supported. + */ + public async getDeploymentBlockByNetwork(): Promise { + const chainId = await this.providerService.getChainId(); + + const block = EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK[chainId]; + if (block == null) throw new Error(`Chain ${chainId} is not supported`); + + return block; + } +} diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts new file mode 100644 index 00000000..f771e646 --- /dev/null +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -0,0 +1,175 @@ +import { Test } from '@nestjs/testing'; +import { MockProviderModule, ProviderService } from 'provider'; +import { ConfigModule } from 'common/config'; +import { LoggerModule } from 'common/logger'; +import { RepositoryModule, RepositoryService } from 'contracts/repository'; +import { SigningKeysStoreService, SigningKeysStoreModule } from './store'; +import { mockRepository } from 'contracts/repository/repository.mock'; +import { LocatorService } from 'contracts/repository/locator/locator.service'; +import { mockLocator } from 'contracts/repository/locator/locator.mock'; +import { cacheMock, newEvent } from './store/store.fixtures'; +import { SigningKeysRegistryModule } from './signing-keys-registry.module'; +import { SigningKeysRegistryService } from './signing-keys-registry.service'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; + +describe('SigningKeyEventsCacheService', () => { + const defaultCacheValue = { + headers: {}, + data: [] as any[], + }; + + let dbService: SigningKeysStoreService; + let repositoryService: RepositoryService; + let locatorService: LocatorService; + let signingkeyEventsCacheService: SigningKeysRegistryService; + let providerService: ProviderService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot(), + MockProviderModule.forRoot(), + RepositoryModule, + SigningKeysStoreModule.register( + defaultCacheValue, + 'leveldb-spec', + 'signing-keys-spec', + ), + LoggerModule, + SigningKeysRegistryModule, + ], + }).compile(); + + dbService = moduleRef.get(SigningKeysStoreService); + repositoryService = moduleRef.get(RepositoryService); + locatorService = moduleRef.get(LocatorService); + signingkeyEventsCacheService = moduleRef.get(SigningKeysRegistryService); + providerService = moduleRef.get(ProviderService); + + const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); + jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); + jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); + + mockLocator(locatorService); + await mockRepository(repositoryService); + await dbService.initialize(); + }); + + afterEach(async () => { + try { + await dbService.deleteCache(); + await dbService.close(); + } catch (error) {} + }); + + it('should clear cache and update if new module was added', async () => { + await dbService.insertEventsCacheBatch(cacheMock); + const result = await dbService.getEventsCache(); + const expected = cacheMock; + + expect(result.headers).toEqual(cacheMock.headers); + expect(result.data.length).toEqual(expected.data.length); + expect(result.data).toEqual(expect.arrayContaining(expected.data)); + + const endBlock = newEvent.blockNumber + 2000; // (10 - (newEvent.blockNumber % 10)); + + jest + .spyOn(signingkeyEventsCacheService, 'fetchEventsFallOver') + .mockImplementation(async () => { + return { + events: [...cacheMock.data, newEvent], + stakingModulesAddresses: [ + ...cacheMock.headers.stakingModulesAddresses, + newEvent.moduleAddress, + ], + startBlock: expected.headers.startBlock, + endBlock, + }; + }); + + jest + .spyOn(providerService, 'getBlockNumber') + .mockImplementation(async () => { + return endBlock; + }); + + jest + .spyOn(signingkeyEventsCacheService, 'getDeploymentBlockByNetwork') + .mockImplementation(async () => { + return expected.headers.startBlock; + }); + + const deleteCache = jest.spyOn(dbService, 'deleteCache'); + + await signingkeyEventsCacheService.handleNewBlock(endBlock, [ + ...cacheMock.headers.stakingModulesAddresses, + newEvent.moduleAddress, + ]); + + expect(deleteCache).toBeCalledTimes(1); + + const newResult = await dbService.getEventsCache(); + + expect(newResult.headers.stakingModulesAddresses).toEqual([ + ...cacheMock.headers.stakingModulesAddresses, + newEvent.moduleAddress, + ]); + expect(newResult.headers.startBlock).toEqual(result.headers.startBlock); + expect(newResult.headers.endBlock).toEqual(endBlock); + expect(newResult.data.length).toEqual([...cacheMock.data, newEvent].length); + expect(newResult.data).toEqual( + expect.arrayContaining([...cacheMock.data, newEvent]), + ); + }); + + describe('wasStakingModulesListUpdated', () => { + const testCases = [ + { previousModules: [], currentModules: [], expected: false }, + { previousModules: [], currentModules: ['1'], expected: true }, + { previousModules: ['1'], currentModules: [], expected: true }, + { previousModules: ['1'], currentModules: ['1'], expected: false }, + { previousModules: ['1'], currentModules: ['2'], expected: true }, + { + previousModules: ['1', '2', '3'], + currentModules: ['1', '2'], + expected: true, + }, + { + previousModules: ['1', '2'], + currentModules: ['1', '2', '3'], + expected: true, + }, + { + previousModules: ['1', '2', '3'], + currentModules: ['2', '3', '4'], + expected: true, + }, + { + previousModules: ['1', '2'], + currentModules: ['2', '3'], + expected: true, + }, + { + previousModules: ['1', '2', '3'], + currentModules: ['4', '5', '6'], + expected: true, + }, + ]; + + testCases.forEach((testCase, index) => { + it(`Test case ${index + 1}: previousModules = ${JSON.stringify( + testCase.previousModules, + )}, currentModules = ${JSON.stringify( + testCase.currentModules, + )}, expected = ${testCase.expected}`, () => { + const result = + signingkeyEventsCacheService.wasStakingModulesListUpdated( + testCase.previousModules, + testCase.currentModules, + ); + + expect(result).toEqual(testCase.expected); + }); + }); + }); +}); diff --git a/src/contracts/signing-keys-registry/store/index.ts b/src/contracts/signing-keys-registry/store/index.ts new file mode 100644 index 00000000..99322182 --- /dev/null +++ b/src/contracts/signing-keys-registry/store/index.ts @@ -0,0 +1,3 @@ +export * from './store.constants'; +export * from './store.module'; +export * from './store.service'; diff --git a/src/contracts/signing-keys-registry/store/store.constants.ts b/src/contracts/signing-keys-registry/store/store.constants.ts new file mode 100644 index 00000000..7ff41070 --- /dev/null +++ b/src/contracts/signing-keys-registry/store/store.constants.ts @@ -0,0 +1,3 @@ +export const DB_DIR = 'cache'; +export const DB_DEFAULT_VALUE = 'cacheDefaultValue'; +export const DB_LAYER_DIR = 'cache:layer'; diff --git a/src/contracts/signing-keys-registry/store/store.fixtures.ts b/src/contracts/signing-keys-registry/store/store.fixtures.ts new file mode 100644 index 00000000..cb744530 --- /dev/null +++ b/src/contracts/signing-keys-registry/store/store.fixtures.ts @@ -0,0 +1,71 @@ +import { SigningKeyEventsCacheHeaders } from '../interfaces/cache.interface'; +import { SigningKeyEvent } from '../interfaces/event.interface'; + +export const keyMock1 = + '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f'; +export const keys = [ + keyMock1, + '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', +]; + +export const eventsMock1 = [ + { + key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', + operatorIndex: 1, + moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', + logIndex: 1, + blockNumber: 1591260, + blockHash: '0x1', + }, + { + key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', + operatorIndex: 2, + moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', + logIndex: 2, + blockNumber: 1591260, + blockHash: '0x1', + }, + { + key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', + operatorIndex: 1, + moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', + logIndex: 2, + blockNumber: 1591261, + blockHash: '0x2', + }, +]; + +export const eventsMock = [ + ...eventsMock1, + { + key: '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', + operatorIndex: 1, + moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', + logIndex: 1, + blockNumber: 1591261, + blockHash: '0x2', + }, +]; + +export const headersMock: SigningKeyEventsCacheHeaders = { + stakingModulesAddresses: ['0x11a93807078f8BB880c1BD0ee4C387537de4b4b6'], + startBlock: 1591259, + endBlock: 1593259, +}; + +export const cacheMock: { + data: SigningKeyEvent[]; + headers: SigningKeyEventsCacheHeaders; +} = { + data: eventsMock, + headers: headersMock, +}; + +export const newEvent = { + key: '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', + operatorIndex: 1, + moduleAddress: '0x77b13807078f8BB880c1BD0ee4C387537de4b4b6', + logIndex: 1, + blockNumber: 1593261, + blockHash: '0x3', +}; diff --git a/src/contracts/signing-keys-registry/store/store.module.ts b/src/contracts/signing-keys-registry/store/store.module.ts new file mode 100644 index 00000000..8aef1fdc --- /dev/null +++ b/src/contracts/signing-keys-registry/store/store.module.ts @@ -0,0 +1,34 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { ProviderModule } from 'provider'; +import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './store.constants'; +import { SigningKeysStoreService } from './store.service'; + +@Module({}) +export class SigningKeysStoreModule { + static register( + defaultValue: unknown, + cacheDir = 'cache', + cacheLayerDir = 'add-sign-keys-cache', + ): DynamicModule { + return { + module: SigningKeysStoreModule, + imports: [ProviderModule], + providers: [ + SigningKeysStoreService, + { + provide: DB_DIR, + useValue: cacheDir, + }, + { + provide: DB_DEFAULT_VALUE, + useValue: defaultValue, + }, + { + provide: DB_LAYER_DIR, + useValue: cacheLayerDir, + }, + ], + exports: [SigningKeysStoreService], + }; + } +} diff --git a/src/contracts/signing-keys-registry/store/store.service.spec.ts b/src/contracts/signing-keys-registry/store/store.service.spec.ts new file mode 100644 index 00000000..08cad728 --- /dev/null +++ b/src/contracts/signing-keys-registry/store/store.service.spec.ts @@ -0,0 +1,67 @@ +import { Test } from '@nestjs/testing'; +import { MockProviderModule } from 'provider'; +import { ConfigModule } from 'common/config'; +import { LoggerModule } from 'common/logger'; +import { SigningKeysStoreModule } from './store.module'; +import { SigningKeysStoreService } from './store.service'; +import { cacheMock, eventsMock1, keyMock1 } from './store.fixtures'; + +describe('dbService', () => { + const defaultCacheValue = { + headers: {}, + data: [] as any[], + }; + + let dbService: SigningKeysStoreService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot(), + MockProviderModule.forRoot(), + SigningKeysStoreModule.register( + defaultCacheValue, + 'leveldb-spec', + 'signing-keys-spec', + ), + LoggerModule, + ], + }).compile(); + + dbService = moduleRef.get(SigningKeysStoreService); + await dbService.initialize(); + }); + + afterEach(async () => { + try { + await dbService.deleteCache(); + await dbService.close(); + } catch (error) {} + }); + + it('should return default cache', async () => { + const result = await dbService.getEventsCache(); + expect(result).toEqual(defaultCacheValue); + }); + + it('should return saved cache', async () => { + const expected = cacheMock; + + await dbService.insertEventsCacheBatch(expected); + const result = await dbService.getEventsCache(); + + expect(result.headers).toEqual(expected.headers); + expect(result.data.length).toEqual(expected.data.length); + expect(result.data).toEqual(expect.arrayContaining(expected.data)); + }); + + it('should return all values with the same key, node operator and module address', async () => { + await dbService.insertEventsCacheBatch(cacheMock); + const result = await dbService.getCachedEvents([keyMock1]); + const expected = eventsMock1; + + expect(result.headers).toEqual(cacheMock.headers); + expect(result.data.length).toEqual(expected.length); + expect(result.data).toEqual(expect.arrayContaining(expected)); + }); +}); diff --git a/src/contracts/signing-keys-registry/store/store.service.ts b/src/contracts/signing-keys-registry/store/store.service.ts new file mode 100644 index 00000000..4a2755dd --- /dev/null +++ b/src/contracts/signing-keys-registry/store/store.service.ts @@ -0,0 +1,249 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Level } from 'level'; +import { join } from 'path'; +import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './store.constants'; +import { ProviderService } from 'provider'; +import { SigningKeyEvent } from '../interfaces/event.interface'; +import { SigningKeyEventsCacheHeaders } from '../interfaces/cache.interface'; + +@Injectable() +export class SigningKeysStoreService { + private db!: Level; + constructor( + private providerService: ProviderService, + @Inject(DB_DIR) private cacheDir: string, + @Inject(DB_LAYER_DIR) private cacheLayerDir: string, + @Inject(DB_DEFAULT_VALUE) + private cacheDefaultValue: { + data: SigningKeyEvent[]; + headers: SigningKeyEventsCacheHeaders; + }, + ) {} + + public async initialize() { + await this.setupLevel(); + } + + /** + * Initializes LevelDB with JSON encoding at the cache directory path. + * + * @returns {Promise} A promise that resolves when the database is successfully initialized. + * @private + */ + private async setupLevel() { + this.db = new Level(await this.getDBDirPath(), { + valueEncoding: 'json', + }); + await this.db.open(); + } + + /** + * Fetches and constructs the cache directory path for the current blockchain network. + * + * @returns {Promise} A promise that resolves to the full path of the network-specific cache directory. + * @private + */ + private async getDBDirPath(): Promise { + const chainId = await this.providerService.getChainId(); + const networkDir = `chain-${chainId}`; + + return join(this.cacheDir, this.cacheLayerDir, networkDir); + } + + /** + * Asynchronously retrieves signing key events and headers from the database. + * Iterates through entries starting with 'signingKey:' to collect data and fetches headers stored under 'header'. + * Handles errors by logging and returning default cache values. + * + * @returns {Promise<{data: SigningKeyEvent[], headers: SigningKeyEventsCacheHeaders}>} Cache data and headers. + * @public + */ + public async getEventsCache(): Promise<{ + data: SigningKeyEvent[]; + headers: SigningKeyEventsCacheHeaders; + }> { + try { + const stream = this.db.iterator({ + gte: 'signingKey:', + lte: 'signingKey:\xFF', + }); + + const data: SigningKeyEvent[] = []; + + for await (const [, value] of stream) { + data.push(this.parseSigningKeyEvent(value)); + } + const headers: SigningKeyEventsCacheHeaders = JSON.parse( + await this.db.get('headers'), + ); + + return { data, headers }; + } catch (error: any) { + if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; + throw error; + } + } + + /** + * @param {string[]} keys - public keys list + * @returns {Promise<{data: SigningKeyEvent[], headers: SigningKeyEventsCacheHeaders}>} Cache data and headers. + * @public + */ + public async getCachedEvents(keys: string[]): Promise<{ + data: SigningKeyEvent[]; + headers: SigningKeyEventsCacheHeaders; + }> { + try { + const data: SigningKeyEvent[] = []; + for (const key of keys) { + const stream = this.db.iterator({ + gte: `signingKey:${key}`, + lte: `signingKey:${key}\xFF`, + }); + + for await (const [, value] of stream) { + data.push(this.parseSigningKeyEvent(value)); + } + } + + const headers: SigningKeyEventsCacheHeaders = JSON.parse( + await this.db.get('headers'), + ); + + return { + data, + headers, + }; + } catch (error: any) { + if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; + throw error; + } + } + + /** Get header + * @returns {Promise<{ headers: SigningKeyEventsCacheHeaders}>} Cache headers. + * @public + */ + public async getHeader(): Promise<{ + headers: SigningKeyEventsCacheHeaders; + }> { + try { + const headers: SigningKeyEventsCacheHeaders = JSON.parse( + await this.db.get('headers'), + ); + + return { + headers, + }; + } catch (error: any) { + if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; + throw error; + } + } + + /** + * Generates a signing key event key for storage. + */ + private generateSigningKeyEventStorageKey({ + key, + blockNumber, + logIndex, + }: SigningKeyEvent): string { + return `signingKey:${key}:${blockNumber}:${logIndex}`; + } + + /** + * Parses a JSON string to a SigningKeyEvent. + * + * @param {string} dataString - The JSON string representing a signing key event. + * @returns {SigningKeyEvent} The parsed signing key event. + * @private + */ + private parseSigningKeyEvent(dataString: string): SigningKeyEvent { + return JSON.parse(dataString); + } + + /** + * Serializes a SigningKeyEvent into a JSON string. + * + * @param {SigningKeyEvent} signingKeyEvent - The signing key event to serialize. + * @returns {string} The serialized JSON string of the signing key event. + * @public + */ + public serializeEventData(signingKeyEvent: SigningKeyEvent) { + return JSON.stringify(signingKeyEvent); + } + + /** + * Inserts a batch of signing key events and a header into the database. + * + * @param {SigningKeyEvent[]} events - An array of signing key events to be inserted into the database. + * @param {SigningKeyEventsCacheHeaders} header - The header information to be stored along with the events. + * @returns {Promise} A promise that resolves when all operations have been successfully committed to the database. + * @public + */ + public async insertEventsCacheBatch(records: { + data: SigningKeyEvent[]; + headers: SigningKeyEventsCacheHeaders; + }) { + if (!this.validateHeader(records.headers)) { + throw new Error( + 'Invalid headers: Headers must contain exactly all SigningKeyEventsCacheHeaders keys.', + ); + } + + const ops = records.data.map((event) => ({ + type: 'put' as const, + key: this.generateSigningKeyEventStorageKey(event), + value: this.serializeEventData(event), + })); + ops.push({ + type: 'put', + key: 'headers', + value: JSON.stringify(records.headers), + }); + await this.db.batch(ops); + } + + private validateHeader( + headers: any, + ): headers is SigningKeyEventsCacheHeaders { + const requiredHeaders: (keyof SigningKeyEventsCacheHeaders)[] = [ + 'stakingModulesAddresses', + 'startBlock', + 'endBlock', + ]; + + const headersKeys = Object.keys( + headers, + ) as (keyof SigningKeyEventsCacheHeaders)[]; + const hasNoExtraKey = headersKeys.every((key) => + requiredHeaders.includes(key), + ); + const hasAllRequiredKeys = requiredHeaders.every((key) => + headersKeys.includes(key), + ); + + return hasNoExtraKey && hasAllRequiredKeys; + } + + /** + * Clears all entries from the database. + * + * @returns {Promise} + * @public + */ + public async deleteCache(): Promise { + await this.db.clear(); + } + + /** + * Close the database connection. + * + * @returns {Promise} + * @public + */ + public async close(): Promise { + await this.db.close(); + } +} From 3cfae4150481bdbacb89b4d41782911182d2075a Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 08:35:29 +0200 Subject: [PATCH 73/94] feat: update loop signing keys registry --- .../signing-keys-registry/constants.ts | 1 + .../sanity-checker/sanity-checker.service.ts | 2 +- .../signing-keys-registry.service.ts | 34 +++++++++++++------ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/contracts/signing-keys-registry/constants.ts b/src/contracts/signing-keys-registry/constants.ts index e98d087c..97b2beb9 100644 --- a/src/contracts/signing-keys-registry/constants.ts +++ b/src/contracts/signing-keys-registry/constants.ts @@ -20,3 +20,4 @@ export const EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK: { export const SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS = 100; export const SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; export const FETCHING_EVENTS_STEP = 10_000; +export const SIGNING_KEYS_REGISTRY_FINALIZED_TAG = 'finalized'; diff --git a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts index 258a33da..ce5de7b9 100644 --- a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts @@ -21,7 +21,7 @@ export class SigningKeysRegistrySanityCheckerService { * @param {number} currentBlock - The current block number to compare against the cached block. * @returns {boolean} `true` if the cache is valid (i.e., the current block number is greater than or equal to the cached end block), `false` otherwise. */ - public validateCacheBlock( + public verifyCacheBlock( cachedEvents: SigningKeyEventsCache, currentBlock: number, ): boolean { diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts index 221860ed..f7d26b35 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts @@ -11,10 +11,12 @@ import { EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, FETCHING_EVENTS_STEP, SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS, + SIGNING_KEYS_REGISTRY_FINALIZED_TAG, SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE, } from './constants'; import { performance } from 'perf_hooks'; import { SigningKeysRegistryFetcherService } from './fetcher'; +import { SigningKeysRegistrySanityCheckerService } from './sanity-checker/sanity-checker.service'; @Injectable() export class SigningKeysRegistryService { @@ -23,6 +25,8 @@ export class SigningKeysRegistryService { private providerService: ProviderService, private levelDBCacheService: SigningKeysStoreService, private fetcher: SigningKeysRegistryFetcherService, + private sanityChecker: SigningKeysRegistrySanityCheckerService, + @Inject(SIGNING_KEYS_REGISTRY_FINALIZED_TAG) private finalizedTag: string, ) {} /** @@ -91,29 +95,39 @@ export class SigningKeysRegistryService { */ public async updateEventsCache( currentStakingModulesAddresses: string[], - ): Promise { + ): Promise { const fetchTimeStart = performance.now(); - // TODO: maybe we should add blockNumber as argument of updateEventsCache method - // and use block number from initialized and handleNewBlock methods - const [latestBlock, initialCache] = await Promise.all([ - this.providerService.getBlockNumber(), + const [finalizedBlock, initialCache] = await Promise.all([ + this.providerService.getBlock(this.finalizedTag), this.getCachedEvents(), ]); + const { number: finalizedBlockNumber } = finalizedBlock; const firstNotCachedBlock = initialCache.headers.endBlock + 1; - const toBlock = latestBlock - SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS; const totalEventsCount = initialCache.data.length; let newEventsCount = 0; + // check that the cache is written to a block less than or equal to the current block + // otherwise we consider that the Ethereum node has started sending incorrect data + const isCacheValid = this.sanityChecker.verifyCacheBlock( + initialCache, + finalizedBlockNumber, + ); + + if (!isCacheValid) return; + for ( let block = firstNotCachedBlock; - block <= toBlock; + block <= finalizedBlockNumber; block += FETCHING_EVENTS_STEP ) { const chunkStartBlock = block; - const chunkToBlock = Math.min(toBlock, block + FETCHING_EVENTS_STEP - 1); + const chunkToBlock = Math.min( + finalizedBlockNumber, + block + FETCHING_EVENTS_STEP - 1, + ); const chunkEventGroup = await this.fetcher.fetchEventsFallOver( chunkStartBlock, @@ -134,7 +148,7 @@ export class SigningKeysRegistryService { newEventsCount += chunkEventGroup.events.length; this.logger.log('Historical signing key add events are fetched', { - toBlock, + finalizedBlockNumber, startBlock: chunkStartBlock, endBlock: chunkToBlock, }); @@ -149,8 +163,6 @@ export class SigningKeysRegistryService { totalEventsCount: totalEventsCount + newEventsCount, fetchTime, }); - - return toBlock; } /** From 90b69e0d036e9e40074d249bee04222eb0ef5539 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 09:20:00 +0200 Subject: [PATCH 74/94] feat: signing keys registry checks --- .../signing-keys-registry/constants.ts | 1 - .../interfaces/event.interface.ts | 1 + .../sanity-checker/sanity-checker.service.ts | 37 ++++++-- .../signing-keys-registry.service.ts | 95 ++++++++----------- .../signing-keys-registry.spec.ts | 2 +- 5 files changed, 72 insertions(+), 64 deletions(-) diff --git a/src/contracts/signing-keys-registry/constants.ts b/src/contracts/signing-keys-registry/constants.ts index 97b2beb9..dd0829c6 100644 --- a/src/contracts/signing-keys-registry/constants.ts +++ b/src/contracts/signing-keys-registry/constants.ts @@ -18,6 +18,5 @@ export const EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK: { // will make a gap in case of reorganization export const SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS = 100; -export const SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; export const FETCHING_EVENTS_STEP = 10_000; export const SIGNING_KEYS_REGISTRY_FINALIZED_TAG = 'finalized'; diff --git a/src/contracts/signing-keys-registry/interfaces/event.interface.ts b/src/contracts/signing-keys-registry/interfaces/event.interface.ts index e84c01a0..64446a84 100644 --- a/src/contracts/signing-keys-registry/interfaces/event.interface.ts +++ b/src/contracts/signing-keys-registry/interfaces/event.interface.ts @@ -15,4 +15,5 @@ export interface SigningKeyEventsGroup { export interface SigningKeyEventsGroupWithStakingModules extends SigningKeyEventsGroup { stakingModulesAddresses: string[]; + isValid: boolean; } diff --git a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts index ce5de7b9..66c37f4b 100644 --- a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts @@ -57,19 +57,38 @@ export class SigningKeysRegistrySanityCheckerService { * @param {SigningKeyEvent[]} events - The list of signing key events to be checked. * @param {number} blockNumber - The block number to match against the events' block numbers. * @param {string} blockHash - The block hash to match against the events' block hashes. - * @throws {Error} If any event's block hash does not match the provided block hash for the specified block number. */ public checkEventsBlockHash( events: SigningKeyEvent[], blockNumber: number, blockHash: string, - ): void { - events.forEach((event) => { - if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { - throw new Error( - 'Blockhash of the received events does not match the current blockhash', - ); - } - }); + ): boolean { + const event = this.findReorganizedEvent(events, blockNumber, blockHash); + + if (event) { + this.logger.error('Reorganization found in signing key event', { + blockHash: event.blockHash, + blockNumber: event.blockNumber, + }); + return true; + } + return false; + } + + /** + * Checks events block hash + * An additional check to avoid events processing in an alternate chain + */ + private findReorganizedEvent( + events: SigningKeyEvent[], + blockNumber: number, + blockHash: string, + ): SigningKeyEvent | null { + return ( + events.find( + (event) => + event.blockNumber === blockNumber && event.blockHash !== blockHash, + ) || null + ); } } diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts index f7d26b35..f00c597e 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts @@ -1,18 +1,13 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { ProviderService } from 'provider'; -import { - SigningKeyEvent, - SigningKeyEventsGroupWithStakingModules, -} from './interfaces/event.interface'; +import { SigningKeyEventsGroupWithStakingModules } from './interfaces/event.interface'; import { SigningKeysStoreService } from './store'; import { SigningKeyEventsCache } from './interfaces/cache.interface'; import { EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, FETCHING_EVENTS_STEP, - SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS, SIGNING_KEYS_REGISTRY_FINALIZED_TAG, - SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE, } from './constants'; import { performance } from 'perf_hooks'; import { SigningKeysRegistryFetcherService } from './fetcher'; @@ -23,7 +18,7 @@ export class SigningKeysRegistryService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private providerService: ProviderService, - private levelDBCacheService: SigningKeysStoreService, + private store: SigningKeysStoreService, private fetcher: SigningKeysRegistryFetcherService, private sanityChecker: SigningKeysRegistrySanityCheckerService, @Inject(SIGNING_KEYS_REGISTRY_FINALIZED_TAG) private finalizedTag: string, @@ -40,20 +35,9 @@ export class SigningKeysRegistryService { * @returns {Promise} */ public async handleNewBlock( - blockNumber: number, - currentstakingModulesAddresses: string[], + currentStakingModulesAddresses: string[], ): Promise { - const wasUpdated = await this.stakingModuleListWasUpdated( - currentstakingModulesAddresses, - ); - if (wasUpdated) { - this.logger.log('Staking module list was updated. Deleting cache'); - await this.levelDBCacheService.deleteCache(); - await this.updateEventsCache(currentstakingModulesAddresses); - } else if (blockNumber % SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE === 0) { - // update for every SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE block - await this.updateEventsCache(currentstakingModulesAddresses); - } + await this.updateEventsCache(currentStakingModulesAddresses); } /** @@ -61,30 +45,9 @@ export class SigningKeysRegistryService { * @param {number} blockNumber - The block number to validate the cache against. * @returns {Promise} */ - public async initialize( - blockNumber: number, - currentstakingModulesAddresses: string[], - ) { - await this.levelDBCacheService.initialize(); - - const cachedEvents = await this.getCachedEvents(); - - // check age of cache - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - - if (!isCacheValid) { - process.exit(1); - } - - const wasUpdated = await this.stakingModuleListWasUpdated( - currentstakingModulesAddresses, - ); - if (wasUpdated) { - this.logger.log('Staking module list was updated. Deleting cache'); - await this.levelDBCacheService.deleteCache(); - } - - await this.updateEventsCache(currentstakingModulesAddresses); + public async initialize(currentStakingModulesAddresses: string[]) { + await this.store.initialize(); + await this.updateEventsCache(currentStakingModulesAddresses); } /** @@ -98,6 +61,15 @@ export class SigningKeysRegistryService { ): Promise { const fetchTimeStart = performance.now(); + const wasUpdated = await this.stakingModuleListWasUpdated( + currentStakingModulesAddresses, + ); + + if (wasUpdated) { + this.logger.log('Staking module list was updated. Deleting cache'); + await this.store.deleteCache(); + } + const [finalizedBlock, initialCache] = await Promise.all([ this.providerService.getBlock(this.finalizedTag), this.getCachedEvents(), @@ -135,7 +107,7 @@ export class SigningKeysRegistryService { currentStakingModulesAddresses, ); - await this.levelDBCacheService.insertEventsCacheBatch({ + await this.store.insertEventsCacheBatch({ headers: { ...initialCache.headers, // as we update staking modules addresses always before run of this method, we can update value on every iteration @@ -178,7 +150,7 @@ export class SigningKeysRegistryService { ): Promise { const { headers: { stakingModulesAddresses: previousModules }, - } = await this.levelDBCacheService.getHeader(); + } = await this.store.getHeader(); const wasUpdated = this.wasStakingModulesListUpdated( previousModules, @@ -233,7 +205,7 @@ export class SigningKeysRegistryService { * containing the cached signing key events and their metadata. */ public async getCachedEvents(): Promise { - const { headers, data } = await this.levelDBCacheService.getEventsCache(); + const { headers, data } = await this.store.getEventsCache(); // default values is startBlock: 0, endBlock: 0 const deploymentBlock = await this.getDeploymentBlockByNetwork(); @@ -261,7 +233,7 @@ export class SigningKeysRegistryService { keys: string[], ): Promise { const uniqueKeys = Array.from(new Set(keys)); - return await this.levelDBCacheService.getCachedEvents(uniqueKeys); + return await this.store.getCachedEvents(uniqueKeys); } /** @@ -283,8 +255,20 @@ export class SigningKeysRegistryService { const endBlock = blockNumber; const cachedEvents = await this.getEventsForOperatorsKeys([key]); - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - if (!isCacheValid) process.exit(1); + const isCacheValid = this.sanityChecker.verifyCacheBlock( + cachedEvents, + blockNumber, + ); + + if (!isCacheValid) { + return { + events: cachedEvents.data, + startBlock: cachedEvents.headers.startBlock, + endBlock: cachedEvents.headers.endBlock, + stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, + isValid: false, + }; + } const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; @@ -297,7 +281,11 @@ export class SigningKeysRegistryService { const lastEvent = freshEvents[freshEvents.length - 1]; const lastEventBlockHash = lastEvent?.blockHash; - this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); + const isValid = this.sanityChecker.checkEventsBlockHash( + freshEvents, + blockNumber, + blockHash, + ); this.logger.debug?.('Fresh signing key add events are fetched', { events: freshEvents.length, @@ -326,6 +314,7 @@ export class SigningKeysRegistryService { stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, startBlock: cachedEvents.headers.startBlock, endBlock, + isValid, }; } @@ -341,8 +330,8 @@ export class SigningKeysRegistryService { public async setCachedEvents( cachedEvents: SigningKeyEventsCache, ): Promise { - await this.levelDBCacheService.deleteCache(); - await this.levelDBCacheService.insertEventsCacheBatch({ + await this.store.deleteCache(); + await this.store.insertEventsCacheBatch({ data: cachedEvents.data, headers: cachedEvents.headers, }); diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts index f771e646..7c3875d5 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -101,7 +101,7 @@ describe('SigningKeyEventsCacheService', () => { const deleteCache = jest.spyOn(dbService, 'deleteCache'); - await signingkeyEventsCacheService.handleNewBlock(endBlock, [ + await signingkeyEventsCacheService.handleNewBlock([ ...cacheMock.headers.stakingModulesAddresses, newEvent.moduleAddress, ]); From d3766d4cf4d14d40073aedfe8f15996aae0bb923 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 09:21:47 +0200 Subject: [PATCH 75/94] feat: remove old service --- .../signing-key-events-cache/constants.ts | 22 - .../signing-key-events-cache/index.ts | 2 - .../interfaces/cache.interface.ts | 12 - .../interfaces/event.interface.ts | 18 - .../signing-key-events-cache/leveldb/index.ts | 3 - .../leveldb/leveldb.constants.ts | 3 - .../leveldb/leveldb.fixtures.ts | 71 --- .../leveldb/leveldb.module.ts | 34 -- .../leveldb/leveldb.service.spec.ts | 67 --- .../leveldb/leveldb.service.ts | 249 --------- .../signing-key-events-cache.module.ts | 15 - .../signing-key-events-cache.service.ts | 488 ------------------ .../signing-key-events-cache.spec.ts | 175 ------- 13 files changed, 1159 deletions(-) delete mode 100644 src/contracts/signing-key-events-cache/constants.ts delete mode 100644 src/contracts/signing-key-events-cache/index.ts delete mode 100644 src/contracts/signing-key-events-cache/interfaces/cache.interface.ts delete mode 100644 src/contracts/signing-key-events-cache/interfaces/event.interface.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/index.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/leveldb.constants.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/leveldb.fixtures.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/leveldb.module.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/leveldb.service.spec.ts delete mode 100644 src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts delete mode 100644 src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts delete mode 100644 src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts delete mode 100644 src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts diff --git a/src/contracts/signing-key-events-cache/constants.ts b/src/contracts/signing-key-events-cache/constants.ts deleted file mode 100644 index e98d087c..00000000 --- a/src/contracts/signing-key-events-cache/constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CHAINS } from '@lido-sdk/constants'; - -export const SIGNING_KEYS_CACHE_DEFAULT = Object.freeze({ - headers: { - stakingModulesAddresses: [], - startBlock: 0, - endBlock: 0, - }, - data: [], -}); - -export const EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK: { - [key in CHAINS]?: number; -} = { - [CHAINS.Mainnet]: 11473216, - [CHAINS.Holesky]: 0, -}; - -// will make a gap in case of reorganization -export const SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS = 100; -export const SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE = 10; -export const FETCHING_EVENTS_STEP = 10_000; diff --git a/src/contracts/signing-key-events-cache/index.ts b/src/contracts/signing-key-events-cache/index.ts deleted file mode 100644 index baafb04b..00000000 --- a/src/contracts/signing-key-events-cache/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './signing-key-events-cache.module'; -export * from './signing-key-events-cache.service'; diff --git a/src/contracts/signing-key-events-cache/interfaces/cache.interface.ts b/src/contracts/signing-key-events-cache/interfaces/cache.interface.ts deleted file mode 100644 index eac22b32..00000000 --- a/src/contracts/signing-key-events-cache/interfaces/cache.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SigningKeyEvent } from './event.interface'; - -export interface SigningKeyEventsCacheHeaders { - stakingModulesAddresses: string[]; - startBlock: number; - endBlock: number; -} - -export interface SigningKeyEventsCache { - headers: SigningKeyEventsCacheHeaders; - data: SigningKeyEvent[]; -} diff --git a/src/contracts/signing-key-events-cache/interfaces/event.interface.ts b/src/contracts/signing-key-events-cache/interfaces/event.interface.ts deleted file mode 100644 index e84c01a0..00000000 --- a/src/contracts/signing-key-events-cache/interfaces/event.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface SigningKeyEvent { - operatorIndex: number; - key: string; - moduleAddress: string; - logIndex: number; - blockNumber: number; - blockHash: string; -} - -export interface SigningKeyEventsGroup { - events: SigningKeyEvent[]; - startBlock: number; - endBlock: number; -} -export interface SigningKeyEventsGroupWithStakingModules - extends SigningKeyEventsGroup { - stakingModulesAddresses: string[]; -} diff --git a/src/contracts/signing-key-events-cache/leveldb/index.ts b/src/contracts/signing-key-events-cache/leveldb/index.ts deleted file mode 100644 index eed6aa71..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './leveldb.constants'; -export * from './leveldb.module'; -export * from './leveldb.service'; diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.constants.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.constants.ts deleted file mode 100644 index 7ff41070..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const DB_DIR = 'cache'; -export const DB_DEFAULT_VALUE = 'cacheDefaultValue'; -export const DB_LAYER_DIR = 'cache:layer'; diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.fixtures.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.fixtures.ts deleted file mode 100644 index cb744530..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.fixtures.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { SigningKeyEventsCacheHeaders } from '../interfaces/cache.interface'; -import { SigningKeyEvent } from '../interfaces/event.interface'; - -export const keyMock1 = - '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f'; -export const keys = [ - keyMock1, - '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', -]; - -export const eventsMock1 = [ - { - key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', - operatorIndex: 1, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - logIndex: 1, - blockNumber: 1591260, - blockHash: '0x1', - }, - { - key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', - operatorIndex: 2, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - logIndex: 2, - blockNumber: 1591260, - blockHash: '0x1', - }, - { - key: '0x80d12670ec69b62abd4d24c828136cbb1666a63374a66269031d6101973419b66711ed712d17da05d7ca6c0b28ecd21f', - operatorIndex: 1, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - logIndex: 2, - blockNumber: 1591261, - blockHash: '0x2', - }, -]; - -export const eventsMock = [ - ...eventsMock1, - { - key: '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', - operatorIndex: 1, - moduleAddress: '0x11a93807078f8BB880c1BD0ee4C387537de4b4b6', - logIndex: 1, - blockNumber: 1591261, - blockHash: '0x2', - }, -]; - -export const headersMock: SigningKeyEventsCacheHeaders = { - stakingModulesAddresses: ['0x11a93807078f8BB880c1BD0ee4C387537de4b4b6'], - startBlock: 1591259, - endBlock: 1593259, -}; - -export const cacheMock: { - data: SigningKeyEvent[]; - headers: SigningKeyEventsCacheHeaders; -} = { - data: eventsMock, - headers: headersMock, -}; - -export const newEvent = { - key: '0x81011ad6ebe5c7844e59b1799e12de769f785f66df3f63debb06149c1782d574c8c2cd9c923fa881e9dcf6d413159863', - operatorIndex: 1, - moduleAddress: '0x77b13807078f8BB880c1BD0ee4C387537de4b4b6', - logIndex: 1, - blockNumber: 1593261, - blockHash: '0x3', -}; diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.module.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.module.ts deleted file mode 100644 index 629a7a88..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { DynamicModule, Module } from '@nestjs/common'; -import { ProviderModule } from 'provider'; -import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './leveldb.constants'; -import { LevelDBService } from './leveldb.service'; - -@Module({}) -export class LevelDBModule { - static register( - defaultValue: unknown, - cacheDir = 'cache', - cacheLayerDir = 'add-sign-keys-cache', - ): DynamicModule { - return { - module: LevelDBModule, - imports: [ProviderModule], - providers: [ - LevelDBService, - { - provide: DB_DIR, - useValue: cacheDir, - }, - { - provide: DB_DEFAULT_VALUE, - useValue: defaultValue, - }, - { - provide: DB_LAYER_DIR, - useValue: cacheLayerDir, - }, - ], - exports: [LevelDBService], - }; - } -} diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.spec.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.service.spec.ts deleted file mode 100644 index 7f874fcd..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Test } from '@nestjs/testing'; -import { MockProviderModule } from 'provider'; -import { ConfigModule } from 'common/config'; -import { LoggerModule } from 'common/logger'; -import { LevelDBModule } from './leveldb.module'; -import { LevelDBService } from './leveldb.service'; -import { cacheMock, eventsMock1, keyMock1 } from './leveldb.fixtures'; - -describe('dbService', () => { - const defaultCacheValue = { - headers: {}, - data: [] as any[], - }; - - let dbService: LevelDBService; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot(), - MockProviderModule.forRoot(), - LevelDBModule.register( - defaultCacheValue, - 'leveldb-spec', - 'signing-keys-spec', - ), - LoggerModule, - ], - }).compile(); - - dbService = moduleRef.get(LevelDBService); - await dbService.initialize(); - }); - - afterEach(async () => { - try { - await dbService.deleteCache(); - await dbService.close(); - } catch (error) {} - }); - - it('should return default cache', async () => { - const result = await dbService.getEventsCache(); - expect(result).toEqual(defaultCacheValue); - }); - - it('should return saved cache', async () => { - const expected = cacheMock; - - await dbService.insertEventsCacheBatch(expected); - const result = await dbService.getEventsCache(); - - expect(result.headers).toEqual(expected.headers); - expect(result.data.length).toEqual(expected.data.length); - expect(result.data).toEqual(expect.arrayContaining(expected.data)); - }); - - it('should return all values with the same key, node operator and module address', async () => { - await dbService.insertEventsCacheBatch(cacheMock); - const result = await dbService.getCachedEvents([keyMock1]); - const expected = eventsMock1; - - expect(result.headers).toEqual(cacheMock.headers); - expect(result.data.length).toEqual(expected.length); - expect(result.data).toEqual(expect.arrayContaining(expected)); - }); -}); diff --git a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts b/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts deleted file mode 100644 index e898bff8..00000000 --- a/src/contracts/signing-key-events-cache/leveldb/leveldb.service.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { Level } from 'level'; -import { join } from 'path'; -import { DB_DIR, DB_DEFAULT_VALUE, DB_LAYER_DIR } from './leveldb.constants'; -import { ProviderService } from 'provider'; -import { SigningKeyEvent } from '../interfaces/event.interface'; -import { SigningKeyEventsCacheHeaders } from '../interfaces/cache.interface'; - -@Injectable() -export class LevelDBService { - private db!: Level; - constructor( - private providerService: ProviderService, - @Inject(DB_DIR) private cacheDir: string, - @Inject(DB_LAYER_DIR) private cacheLayerDir: string, - @Inject(DB_DEFAULT_VALUE) - private cacheDefaultValue: { - data: SigningKeyEvent[]; - headers: SigningKeyEventsCacheHeaders; - }, - ) {} - - public async initialize() { - await this.setupLevel(); - } - - /** - * Initializes LevelDB with JSON encoding at the cache directory path. - * - * @returns {Promise} A promise that resolves when the database is successfully initialized. - * @private - */ - private async setupLevel() { - this.db = new Level(await this.getDBDirPath(), { - valueEncoding: 'json', - }); - await this.db.open(); - } - - /** - * Fetches and constructs the cache directory path for the current blockchain network. - * - * @returns {Promise} A promise that resolves to the full path of the network-specific cache directory. - * @private - */ - private async getDBDirPath(): Promise { - const chainId = await this.providerService.getChainId(); - const networkDir = `chain-${chainId}`; - - return join(this.cacheDir, this.cacheLayerDir, networkDir); - } - - /** - * Asynchronously retrieves signing key events and headers from the database. - * Iterates through entries starting with 'signingKey:' to collect data and fetches headers stored under 'header'. - * Handles errors by logging and returning default cache values. - * - * @returns {Promise<{data: SigningKeyEvent[], headers: SigningKeyEventsCacheHeaders}>} Cache data and headers. - * @public - */ - public async getEventsCache(): Promise<{ - data: SigningKeyEvent[]; - headers: SigningKeyEventsCacheHeaders; - }> { - try { - const stream = this.db.iterator({ - gte: 'signingKey:', - lte: 'signingKey:\xFF', - }); - - const data: SigningKeyEvent[] = []; - - for await (const [, value] of stream) { - data.push(this.parseSigningKeyEvent(value)); - } - const headers: SigningKeyEventsCacheHeaders = JSON.parse( - await this.db.get('headers'), - ); - - return { data, headers }; - } catch (error: any) { - if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; - throw error; - } - } - - /** - * @param {string[]} keys - public keys list - * @returns {Promise<{data: SigningKeyEvent[], headers: SigningKeyEventsCacheHeaders}>} Cache data and headers. - * @public - */ - public async getCachedEvents(keys: string[]): Promise<{ - data: SigningKeyEvent[]; - headers: SigningKeyEventsCacheHeaders; - }> { - try { - const data: SigningKeyEvent[] = []; - for (const key of keys) { - const stream = this.db.iterator({ - gte: `signingKey:${key}`, - lte: `signingKey:${key}\xFF`, - }); - - for await (const [, value] of stream) { - data.push(this.parseSigningKeyEvent(value)); - } - } - - const headers: SigningKeyEventsCacheHeaders = JSON.parse( - await this.db.get('headers'), - ); - - return { - data, - headers, - }; - } catch (error: any) { - if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; - throw error; - } - } - - /** Get header - * @returns {Promise<{ headers: SigningKeyEventsCacheHeaders}>} Cache headers. - * @public - */ - public async getHeader(): Promise<{ - headers: SigningKeyEventsCacheHeaders; - }> { - try { - const headers: SigningKeyEventsCacheHeaders = JSON.parse( - await this.db.get('headers'), - ); - - return { - headers, - }; - } catch (error: any) { - if (error.code === 'LEVEL_NOT_FOUND') return this.cacheDefaultValue; - throw error; - } - } - - /** - * Generates a signing key event key for storage. - */ - private generateSigningKeyEventStorageKey({ - key, - blockNumber, - logIndex, - }: SigningKeyEvent): string { - return `signingKey:${key}:${blockNumber}:${logIndex}`; - } - - /** - * Parses a JSON string to a SigningKeyEvent. - * - * @param {string} dataString - The JSON string representing a signing key event. - * @returns {SigningKeyEvent} The parsed signing key event. - * @private - */ - private parseSigningKeyEvent(dataString: string): SigningKeyEvent { - return JSON.parse(dataString); - } - - /** - * Serializes a SigningKeyEvent into a JSON string. - * - * @param {SigningKeyEvent} signingKeyEvent - The signing key event to serialize. - * @returns {string} The serialized JSON string of the signing key event. - * @public - */ - public serializeEventData(signingKeyEvent: SigningKeyEvent) { - return JSON.stringify(signingKeyEvent); - } - - /** - * Inserts a batch of signing key events and a header into the database. - * - * @param {SigningKeyEvent[]} events - An array of signing key events to be inserted into the database. - * @param {SigningKeyEventsCacheHeaders} header - The header information to be stored along with the events. - * @returns {Promise} A promise that resolves when all operations have been successfully committed to the database. - * @public - */ - public async insertEventsCacheBatch(records: { - data: SigningKeyEvent[]; - headers: SigningKeyEventsCacheHeaders; - }) { - if (!this.validateHeader(records.headers)) { - throw new Error( - 'Invalid headers: Headers must contain exactly all SigningKeyEventsCacheHeaders keys.', - ); - } - - const ops = records.data.map((event) => ({ - type: 'put' as const, - key: this.generateSigningKeyEventStorageKey(event), - value: this.serializeEventData(event), - })); - ops.push({ - type: 'put', - key: 'headers', - value: JSON.stringify(records.headers), - }); - await this.db.batch(ops); - } - - private validateHeader( - headers: any, - ): headers is SigningKeyEventsCacheHeaders { - const requiredHeaders: (keyof SigningKeyEventsCacheHeaders)[] = [ - 'stakingModulesAddresses', - 'startBlock', - 'endBlock', - ]; - - const headersKeys = Object.keys( - headers, - ) as (keyof SigningKeyEventsCacheHeaders)[]; - const hasNoExtraKey = headersKeys.every((key) => - requiredHeaders.includes(key), - ); - const hasAllRequiredKeys = requiredHeaders.every((key) => - headersKeys.includes(key), - ); - - return hasNoExtraKey && hasAllRequiredKeys; - } - - /** - * Clears all entries from the database. - * - * @returns {Promise} - * @public - */ - public async deleteCache(): Promise { - await this.db.clear(); - } - - /** - * Close the database connection. - * - * @returns {Promise} - * @public - */ - public async close(): Promise { - await this.db.close(); - } -} diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts deleted file mode 100644 index 5e3471de..00000000 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Module } from '@nestjs/common'; -import { LevelDBModule } from './leveldb'; -import { SigningKeyEventsCacheService } from './signing-key-events-cache.service'; -import { SIGNING_KEYS_CACHE_DEFAULT } from './constants'; -import { StakingRouterModule } from 'contracts/staking-router'; - -@Module({ - imports: [ - StakingRouterModule, - LevelDBModule.register(SIGNING_KEYS_CACHE_DEFAULT), - ], - providers: [SigningKeyEventsCacheService], - exports: [SigningKeyEventsCacheService], -}) -export class SigningKeyEventsCacheModule {} diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts deleted file mode 100644 index 7a9d1feb..00000000 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.service.ts +++ /dev/null @@ -1,488 +0,0 @@ -import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { ProviderService } from 'provider'; -import { - SigningKeyEvent, - SigningKeyEventsGroup, - SigningKeyEventsGroupWithStakingModules, -} from './interfaces/event.interface'; -import { LevelDBService } from './leveldb'; -import { SigningKeyEventsCache } from './interfaces/cache.interface'; -import { - EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, - FETCHING_EVENTS_STEP, - SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS, - SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE, -} from './constants'; -import { performance } from 'perf_hooks'; -import { StakingRouterService } from 'contracts/staking-router'; - -@Injectable() -export class SigningKeyEventsCacheService { - constructor( - @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private providerService: ProviderService, - private levelDBCacheService: LevelDBService, - private stakingRouterService: StakingRouterService, - ) {} - - /** - * Handles the logic for processing a new block. - * - * This method checks if the staking module list has been updated and, if so, deletes the cache and updates the events cache. - * If the staking module list has not been updated, it checks whether the block number is divisible by the - * `SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE` and, if true, updates the events cache. - * - * @param {number} blockNumber - The block number of the newly processed block. - * @returns {Promise} - */ - public async handleNewBlock( - blockNumber: number, - currentstakingModulesAddresses: string[], - ): Promise { - const wasUpdated = await this.stakingModuleListWasUpdated( - currentstakingModulesAddresses, - ); - if (wasUpdated) { - this.logger.log('Staking module list was updated. Deleting cache'); - await this.levelDBCacheService.deleteCache(); - await this.updateEventsCache(currentstakingModulesAddresses); - } else if (blockNumber % SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE === 0) { - // update for every SIGNING_KEY_EVENTS_CACHE_UPDATE_BLOCK_RATE block - await this.updateEventsCache(currentstakingModulesAddresses); - } - } - - /** - * Initialize or update cache - * @param {number} blockNumber - The block number to validate the cache against. - * @returns {Promise} - */ - public async initialize( - blockNumber: number, - currentstakingModulesAddresses: string[], - ) { - await this.levelDBCacheService.initialize(); - - const cachedEvents = await this.getCachedEvents(); - - // check age of cache - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - - if (!isCacheValid) { - process.exit(1); - } - - const wasUpdated = await this.stakingModuleListWasUpdated( - currentstakingModulesAddresses, - ); - if (wasUpdated) { - this.logger.log('Staking module list was updated. Deleting cache'); - await this.levelDBCacheService.deleteCache(); - } - - await this.updateEventsCache(currentstakingModulesAddresses); - } - - /** - * Updates the cache signing keys events - * The last N blocks are not stored, in order to avoid storing reorganized blocks - * - * @returns {Promise} The block number up to which the cache has been updated. - */ - public async updateEventsCache( - currentStakingModulesAddresses: string[], - ): Promise { - const fetchTimeStart = performance.now(); - - // TODO: maybe we should add blockNumber as argument of updateEventsCache method - // and use block number from initialized and handleNewBlock methods - const [latestBlock, initialCache] = await Promise.all([ - this.providerService.getBlockNumber(), - this.getCachedEvents(), - ]); - - const firstNotCachedBlock = initialCache.headers.endBlock + 1; - const toBlock = latestBlock - SIGNING_KEYS_EVENTS_CACHE_LAG_BLOCKS; - - const totalEventsCount = initialCache.data.length; - let newEventsCount = 0; - - for ( - let block = firstNotCachedBlock; - block <= toBlock; - block += FETCHING_EVENTS_STEP - ) { - const chunkStartBlock = block; - const chunkToBlock = Math.min(toBlock, block + FETCHING_EVENTS_STEP - 1); - - const chunkEventGroup = await this.fetchEventsFallOver( - chunkStartBlock, - chunkToBlock, - currentStakingModulesAddresses, - ); - - await this.levelDBCacheService.insertEventsCacheBatch({ - headers: { - ...initialCache.headers, - // as we update staking modules addresses always before run of this method, we can update value on every iteration - stakingModulesAddresses: currentStakingModulesAddresses, - endBlock: chunkEventGroup.endBlock, - }, - data: chunkEventGroup.events, - }); - - newEventsCount += chunkEventGroup.events.length; - - this.logger.log('Historical signing key add events are fetched', { - toBlock, - startBlock: chunkStartBlock, - endBlock: chunkToBlock, - }); - } - - const fetchTimeEnd = performance.now(); - const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000; - // TODO: replace timer with metric - - this.logger.log('Signing key events cache is updated', { - newEventsCount, - totalEventsCount: totalEventsCount + newEventsCount, - fetchTime, - }); - - return toBlock; - } - - /** - * Checks if the list of staking modules has been updated. - * - * This method compares the current list of staking modules with the previously cached list. - * If the list has changed, it logs a warning and indicates that the cache needs to be cleared and updated. - * - * @returns {Promise} Return `true` if the staking modules list was updated, `false` otherwise. - */ - public async stakingModuleListWasUpdated( - currentModules: string[], - ): Promise { - const { - headers: { stakingModulesAddresses: previousModules }, - } = await this.levelDBCacheService.getHeader(); - - const wasUpdated = this.wasStakingModulesListUpdated( - previousModules, - currentModules, - ); - - if (wasUpdated) { - this.logger.warn( - 'Staking module list was changed. Need to clear and update cache', - { - previousModules, - currentModules, - }, - ); - } - - return wasUpdated; - } - - /** - * Compares the previous and current lists of staking modules to determine if any changes have occurred. - * - * This method checks if any staking modules were added or deleted by comparing the previous - * and current lists of staking modules. - * - * @param {string[]} previousModules - The list of staking modules from the previous cache. - * @param {string[]} currentModules - The current list of staking modules. - * @returns {boolean} `true` if the staking modules list was updated (modules were added or deleted), `false` otherwise. - */ - public wasStakingModulesListUpdated( - previousModules: string[], - currentModules: string[], - ) { - const modulesWereDeleted = previousModules.some( - (sm) => !currentModules.includes(sm), - ); - const modulesWereAdded = currentModules.some( - (module) => !previousModules.includes(module), - ); - - return modulesWereDeleted || modulesWereAdded; - } - - /** - * Fetches signing key events within a specified block range, with fallback mechanisms. - * If the request failed, it tries to repeat it or split it into two - * - * @param {number} startBlock - The starting block number of the range. - * @param {number} endBlock - The ending block number of the range. - * @returns {Promise} Events fetched within the specified block range - */ - public async fetchEventsFallOver( - startBlock: number, - endBlock: number, - stakingModulesAddresses: string[], - ): Promise { - const fetcherWrapper = (start: number, end: number) => - this.fetchEvents(start, end, stakingModulesAddresses); - - return await this.providerService.fetchEventsFallOver( - startBlock, - endBlock, - fetcherWrapper, - ); - } - - /** - * Fetches signing key events within a specified block range from staking module contracts. - * - * @param {number} startBlock - The starting block number of the range. - * @param {number} endBlock - The ending block number of the range. - * @returns {Promise} Events fetched within the specified block range. - */ - public async fetchEvents( - startBlock: number, - endBlock: number, - stakingModulesAddresses: string[], - ): Promise { - const events: SigningKeyEvent[] = []; - - await Promise.all( - stakingModulesAddresses.map(async (address) => { - const rawEvents = - await this.stakingRouterService.getSigningKeyAddedEvents( - startBlock, - endBlock, - address, - ); - - const moduleEvents: SigningKeyEvent[] = rawEvents.map((rawEvent) => { - return { - operatorIndex: rawEvent.args[0].toNumber(), - key: rawEvent.args[1], - moduleAddress: address, - blockNumber: rawEvent.blockNumber, - logIndex: rawEvent.logIndex, - blockHash: rawEvent.blockHash, - }; - }); - - events.push(...moduleEvents); - - this.logger.log('Fetched signing keys add events for staking module', { - count: moduleEvents.length, - address, - }); - }), - ); - - return { events, startBlock, endBlock }; - } - - /** - * Retrieves signing key events data from the cache. - * - * This method fetches cached signing key events along with their associated headers. - * If the headers have default values (like 0 for the start and end block numbers), - * these values are updated to reflect the actual deployment block of the network. - * - * @returns {Promise} A promise that resolves to a `SigningKeyEventsCache` object, - * containing the cached signing key events and their metadata. - */ - public async getCachedEvents(): Promise { - const { headers, data } = await this.levelDBCacheService.getEventsCache(); - - // default values is startBlock: 0, endBlock: 0 - const deploymentBlock = await this.getDeploymentBlockByNetwork(); - - return { - headers: { - ...headers, - startBlock: Math.max(headers.startBlock, deploymentBlock), - endBlock: Math.max(headers.endBlock, deploymentBlock), - }, - data, - }; - } - - /** - * Retrieves signing key events from the cache for the specified operators' keys. - * - * This method takes a list of operators' keys, ensures the list contains unique keys, - * and then fetches the corresponding events from the cache. - * - * @param {string[]} keys - An array of operators' keys for which to retrieve events. - * @returns {Promise} Events associated with the specified keys. - */ - public async getEventsForOperatorsKeys( - keys: string[], - ): Promise { - const uniqueKeys = Array.from(new Set(keys)); - return await this.levelDBCacheService.getCachedEvents(uniqueKeys); - } - - /** - * Retrieves and returns all signing key events based on cached data and fresh data for a given key. - * - * This method combines cached signing key events with newly fetched events for a specific key, - * ensuring the cache is valid and updating the cache if necessary. - * - * @param {string} key - The specific signing key to retrieve events for. - * @param {number} blockNumber - The block number up to which the events should be retrieved. - * @param {string} blockHash - The block hash used to verify the integrity of the retrieved events. - * @returns {Promise} merged signing key events and associated staking module addresses. - */ - public async getUpdatedSigningKeyEvents( - key: string, - blockNumber: number, - blockHash: string, - ): Promise { - const endBlock = blockNumber; - const cachedEvents = await this.getEventsForOperatorsKeys([key]); - - const isCacheValid = this.validateCacheBlock(cachedEvents, blockNumber); - if (!isCacheValid) process.exit(1); - - const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; - - const freshEventGroup = await this.fetchEventsFallOver( - firstNotCachedBlock, - endBlock, - cachedEvents.headers.stakingModulesAddresses, - ); - const freshEvents = freshEventGroup.events; - const lastEvent = freshEvents[freshEvents.length - 1]; - const lastEventBlockHash = lastEvent?.blockHash; - - this.checkEventsBlockHash(freshEvents, blockNumber, blockHash); - - this.logger.debug?.('Fresh signing key add events are fetched', { - events: freshEvents.length, - startBlock: firstNotCachedBlock, - endBlock, - blockHash, - lastEventBlockHash, - }); - - const keyFreshEvents = freshEventGroup.events.filter( - (event) => event.key == key, - ); - - const mergedEvents = cachedEvents.data.concat(keyFreshEvents); - - this.logger.debug?.('Merged signing key add events', { - events: mergedEvents.length, - startBlock: firstNotCachedBlock, - endBlock, - blockHash, - lastEventBlockHash, - }); - - return { - events: mergedEvents, - stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, - startBlock: cachedEvents.headers.startBlock, - endBlock, - }; - } - - /** - * Saves signing key events to the cache. - * - * This method first deletes the existing cache and then saves the provided signing key events - * and their associated headers to the cache. - * - * @param {SigningKeyEventsCache} cachedEvents - An object containing the signing key events and headers to be saved to the cache. - * @returns {Promise} - */ - public async setCachedEvents( - cachedEvents: SigningKeyEventsCache, - ): Promise { - await this.levelDBCacheService.deleteCache(); - await this.levelDBCacheService.insertEventsCacheBatch({ - data: cachedEvents.data, - headers: cachedEvents.headers, - }); - } - - /** - * Validates the block number in the cached events against the current block number. - * - * This method checks if the cached events are up to date by comparing the current block number - * with the end block number in the cache. It logs a message if the cache is valid and a warning if it is not. - * - * @param {SigningKeyEventsCache} cachedEvents - The cached events containing block headers to validate. - * @param {number} currentBlock - The current block number to compare against the cached block. - * @returns {boolean} `true` if the cache is valid (i.e., the current block number is greater than or equal to the cached end block), `false` otherwise. - */ - public validateCacheBlock( - cachedEvents: SigningKeyEventsCache, - currentBlock: number, - ): boolean { - const isCacheValid = currentBlock >= cachedEvents.headers.endBlock; - - const blocks = { - cachedStartBlock: cachedEvents.headers.startBlock, - cachedEndBlock: cachedEvents.headers.endBlock, - currentBlock, - }; - - if (isCacheValid) { - this.logger.log('Signing keys events cache has valid age', blocks); - } - - if (!isCacheValid) { - this.logger.warn( - 'Signing key events cache is newer than the current block', - blocks, - ); - } - - return isCacheValid; - } - - /** - * Validates the block hash of signing key events. - * - * This method checks each event's block hash against the provided block hash, but only if the event's block number - * matches the given `blockNumber`. This ensures that the events are not from an alternate chain (e.g., due to a chain reorganization). - * If a block number match is found but the block hashes do not match, an error is thrown. - * - * @param {SigningKeyEvent[]} events - The list of signing key events to be checked. - * @param {number} blockNumber - The block number to match against the events' block numbers. - * @param {string} blockHash - The block hash to match against the events' block hashes. - * @throws {Error} If any event's block hash does not match the provided block hash for the specified block number. - */ - public checkEventsBlockHash( - events: SigningKeyEvent[], - blockNumber: number, - blockHash: string, - ): void { - events.forEach((event) => { - if (event.blockNumber === blockNumber && event.blockHash !== blockHash) { - throw new Error( - 'Blockhash of the received events does not match the current blockhash', - ); - } - }); - } - - /** - * Retrieves the block number when the curated module contract was deployed for the current network. - * - * This method determines the deployment block number based on the current network's chain ID. - * If the chain ID is not supported, an error is thrown. - * - * @returns {Promise} Block number where the curated module contract was deployed. - * @throws {Error} If the chain ID is not supported. - */ - public async getDeploymentBlockByNetwork(): Promise { - const chainId = await this.providerService.getChainId(); - - const block = EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK[chainId]; - if (block == null) throw new Error(`Chain ${chainId} is not supported`); - - return block; - } -} diff --git a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts b/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts deleted file mode 100644 index e21693ef..00000000 --- a/src/contracts/signing-key-events-cache/signing-key-events-cache.spec.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Test } from '@nestjs/testing'; -import { MockProviderModule, ProviderService } from 'provider'; -import { ConfigModule } from 'common/config'; -import { LoggerModule } from 'common/logger'; -import { RepositoryModule, RepositoryService } from 'contracts/repository'; -import { LevelDBModule, LevelDBService } from './leveldb'; -import { mockRepository } from 'contracts/repository/repository.mock'; -import { LocatorService } from 'contracts/repository/locator/locator.service'; -import { mockLocator } from 'contracts/repository/locator/locator.mock'; -import { cacheMock, newEvent } from './leveldb/leveldb.fixtures'; -import { SigningKeyEventsCacheModule } from './signing-key-events-cache.module'; -import { SigningKeyEventsCacheService } from './signing-key-events-cache.service'; -import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; - -describe('SigningKeyEventsCacheService', () => { - const defaultCacheValue = { - headers: {}, - data: [] as any[], - }; - - let dbService: LevelDBService; - let repositoryService: RepositoryService; - let locatorService: LocatorService; - let signingkeyEventsCacheService: SigningKeyEventsCacheService; - let providerService: ProviderService; - - beforeEach(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot(), - MockProviderModule.forRoot(), - RepositoryModule, - LevelDBModule.register( - defaultCacheValue, - 'leveldb-spec', - 'signing-keys-spec', - ), - LoggerModule, - SigningKeyEventsCacheModule, - ], - }).compile(); - - dbService = moduleRef.get(LevelDBService); - repositoryService = moduleRef.get(RepositoryService); - locatorService = moduleRef.get(LocatorService); - signingkeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); - providerService = moduleRef.get(ProviderService); - - const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); - jest.spyOn(loggerService, 'warn').mockImplementation(() => undefined); - jest.spyOn(loggerService, 'log').mockImplementation(() => undefined); - - mockLocator(locatorService); - await mockRepository(repositoryService); - await dbService.initialize(); - }); - - afterEach(async () => { - try { - await dbService.deleteCache(); - await dbService.close(); - } catch (error) {} - }); - - it('should clear cache and update if new module was added', async () => { - await dbService.insertEventsCacheBatch(cacheMock); - const result = await dbService.getEventsCache(); - const expected = cacheMock; - - expect(result.headers).toEqual(cacheMock.headers); - expect(result.data.length).toEqual(expected.data.length); - expect(result.data).toEqual(expect.arrayContaining(expected.data)); - - const endBlock = newEvent.blockNumber + 2000; // (10 - (newEvent.blockNumber % 10)); - - jest - .spyOn(signingkeyEventsCacheService, 'fetchEventsFallOver') - .mockImplementation(async () => { - return { - events: [...cacheMock.data, newEvent], - stakingModulesAddresses: [ - ...cacheMock.headers.stakingModulesAddresses, - newEvent.moduleAddress, - ], - startBlock: expected.headers.startBlock, - endBlock, - }; - }); - - jest - .spyOn(providerService, 'getBlockNumber') - .mockImplementation(async () => { - return endBlock; - }); - - jest - .spyOn(signingkeyEventsCacheService, 'getDeploymentBlockByNetwork') - .mockImplementation(async () => { - return expected.headers.startBlock; - }); - - const deleteCache = jest.spyOn(dbService, 'deleteCache'); - - await signingkeyEventsCacheService.handleNewBlock(endBlock, [ - ...cacheMock.headers.stakingModulesAddresses, - newEvent.moduleAddress, - ]); - - expect(deleteCache).toBeCalledTimes(1); - - const newResult = await dbService.getEventsCache(); - - expect(newResult.headers.stakingModulesAddresses).toEqual([ - ...cacheMock.headers.stakingModulesAddresses, - newEvent.moduleAddress, - ]); - expect(newResult.headers.startBlock).toEqual(result.headers.startBlock); - expect(newResult.headers.endBlock).toEqual(endBlock); - expect(newResult.data.length).toEqual([...cacheMock.data, newEvent].length); - expect(newResult.data).toEqual( - expect.arrayContaining([...cacheMock.data, newEvent]), - ); - }); - - describe('wasStakingModulesListUpdated', () => { - const testCases = [ - { previousModules: [], currentModules: [], expected: false }, - { previousModules: [], currentModules: ['1'], expected: true }, - { previousModules: ['1'], currentModules: [], expected: true }, - { previousModules: ['1'], currentModules: ['1'], expected: false }, - { previousModules: ['1'], currentModules: ['2'], expected: true }, - { - previousModules: ['1', '2', '3'], - currentModules: ['1', '2'], - expected: true, - }, - { - previousModules: ['1', '2'], - currentModules: ['1', '2', '3'], - expected: true, - }, - { - previousModules: ['1', '2', '3'], - currentModules: ['2', '3', '4'], - expected: true, - }, - { - previousModules: ['1', '2'], - currentModules: ['2', '3'], - expected: true, - }, - { - previousModules: ['1', '2', '3'], - currentModules: ['4', '5', '6'], - expected: true, - }, - ]; - - testCases.forEach((testCase, index) => { - it(`Test case ${index + 1}: previousModules = ${JSON.stringify( - testCase.previousModules, - )}, currentModules = ${JSON.stringify( - testCase.currentModules, - )}, expected = ${testCase.expected}`, () => { - const result = - signingkeyEventsCacheService.wasStakingModulesListUpdated( - testCase.previousModules, - testCase.currentModules, - ); - - expect(result).toEqual(testCase.expected); - }); - }); - }); -}); From d2c12d286bd2a75de702aecbe5998ac800046a43 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 09:29:13 +0200 Subject: [PATCH 76/94] feat: connect new module --- src/contracts/signing-keys-registry/index.ts | 4 ++-- .../signing-keys-registry.spec.ts | 5 ++++- .../keys-duplication-checker.service.ts | 18 +++++++++++++----- src/guardian/guardian.service.ts | 6 ++---- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/contracts/signing-keys-registry/index.ts b/src/contracts/signing-keys-registry/index.ts index baafb04b..a5530e09 100644 --- a/src/contracts/signing-keys-registry/index.ts +++ b/src/contracts/signing-keys-registry/index.ts @@ -1,2 +1,2 @@ -export * from './signing-key-events-cache.module'; -export * from './signing-key-events-cache.service'; +export * from './signing-keys-registry.module'; +export * from './signing-keys-registry.service'; diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts index 7c3875d5..3106a086 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -11,6 +11,7 @@ import { cacheMock, newEvent } from './store/store.fixtures'; import { SigningKeysRegistryModule } from './signing-keys-registry.module'; import { SigningKeysRegistryService } from './signing-keys-registry.service'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { SigningKeysRegistryFetcherService } from './fetcher'; describe('SigningKeyEventsCacheService', () => { const defaultCacheValue = { @@ -22,6 +23,7 @@ describe('SigningKeyEventsCacheService', () => { let repositoryService: RepositoryService; let locatorService: LocatorService; let signingkeyEventsCacheService: SigningKeysRegistryService; + let signingKeysFetch: SigningKeysRegistryFetcherService; let providerService: ProviderService; beforeEach(async () => { @@ -44,6 +46,7 @@ describe('SigningKeyEventsCacheService', () => { repositoryService = moduleRef.get(RepositoryService); locatorService = moduleRef.get(LocatorService); signingkeyEventsCacheService = moduleRef.get(SigningKeysRegistryService); + signingKeysFetch = moduleRef.get(SigningKeysRegistryFetcherService); providerService = moduleRef.get(ProviderService); const loggerService = moduleRef.get(WINSTON_MODULE_NEST_PROVIDER); @@ -74,7 +77,7 @@ describe('SigningKeyEventsCacheService', () => { const endBlock = newEvent.blockNumber + 2000; // (10 - (newEvent.blockNumber % 10)); jest - .spyOn(signingkeyEventsCacheService, 'fetchEventsFallOver') + .spyOn(signingKeysFetch, 'fetchEventsFallOver') .mockImplementation(async () => { return { events: [...cacheMock.data, newEvent], diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index 554a3fb0..e3127e0a 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; -import { SigningKeyEvent } from 'contracts/signing-key-events-cache/interfaces/event.interface'; +import { SigningKeyEvent } from 'contracts/signing-keys-registry/interfaces/event.interface'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry/signing-keys-registry.service'; + import { BlockData } from 'guardian/interfaces'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; @@ -12,7 +13,7 @@ const BATCH_SIZE = 10; export class KeysDuplicationCheckerService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private signingKeyEventsCacheService: SigningKeyEventsCacheService, + private signingKeyEventsCacheService: SigningKeysRegistryService, ) {} /** @@ -241,13 +242,20 @@ export class KeysDuplicationCheckerService { key: string, blockData: BlockData, ): Promise { - const { events } = + const eventsGroup = await this.signingKeyEventsCacheService.getUpdatedSigningKeyEvents( key, blockData.blockNumber, blockData.blockHash, ); - return events; + + if (!eventsGroup.isValid) { + throw new Error( + `Signing keys events are not valid on the block ${blockData.blockHash}`, + ); + } + + return eventsGroup.events; } private getOperatorsWithoutEvents( diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index dc765449..903a6772 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -27,12 +27,12 @@ import { BlockData, StakingModuleData } from './interfaces'; import { ProviderService } from 'provider'; import { KeysApiService } from 'keys-api/keys-api.service'; import { MIN_KAPI_VERSION } from './guardian.constants'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { UnvettingService } from './unvetting/unvetting.service'; import { Meta } from 'keys-api/interfaces/Meta'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; import { StakingRouterService } from 'contracts/staking-router'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; @Injectable() export class GuardianService implements OnModuleInit { @@ -56,7 +56,7 @@ export class GuardianService implements OnModuleInit { private providerService: ProviderService, private keysApiService: KeysApiService, - private signingKeyEventsCacheService: SigningKeyEventsCacheService, + private signingKeyEventsCacheService: SigningKeysRegistryService, private unvettingService: UnvettingService, @@ -78,7 +78,6 @@ export class GuardianService implements OnModuleInit { this.depositService.initialize(), this.securityService.initialize({ blockHash }), this.signingKeyEventsCacheService.initialize( - block.number, stakingRouterModuleAddresses, ), ]); @@ -290,7 +289,6 @@ export class GuardianService implements OnModuleInit { ); // update cache if needs await this.signingKeyEventsCacheService.handleNewBlock( - blockData.blockNumber, stakingRouterModuleAddresses, ); From 3606de77ffb9c98f8f7c3e4da19761d2a027cfca Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 09:44:17 +0200 Subject: [PATCH 77/94] refactor: singing keys service tests --- .../signing-keys-registry.spec.ts | 19 +++++++------- .../keys-duplication-checker.module.ts | 4 +-- .../keys-duplication-checker.service.spec.ts | 26 +++++++++---------- .../keys-duplication-checker.service.ts | 4 +-- src/guardian/duplicates/keys.fixtures.ts | 2 +- src/guardian/guardian.module.ts | 4 +-- src/guardian/guardian.service.ts | 10 +++---- test/duplicates-v3.e2e-spec.ts | 20 +++++++------- test/duplicates.e2e-spec.ts | 16 ++++++------ test/front-run-v3.e2e-spec.ts | 20 +++++++------- test/front-run.e2e-spec.ts | 18 ++++++------- test/guardian-balance-monitoring.e2e-spec.ts | 10 +++---- test/helpers/test-setup.ts | 2 +- test/invalid-keys-v3.e2e-spec.ts | 12 ++++----- test/invalid-keys.e2e-spec.ts | 12 ++++----- 15 files changed, 89 insertions(+), 90 deletions(-) diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts index 3106a086..b402f918 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -13,7 +13,7 @@ import { SigningKeysRegistryService } from './signing-keys-registry.service'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { SigningKeysRegistryFetcherService } from './fetcher'; -describe('SigningKeyEventsCacheService', () => { +describe('SigningKeysRegistryService', () => { const defaultCacheValue = { headers: {}, data: [] as any[], @@ -22,7 +22,7 @@ describe('SigningKeyEventsCacheService', () => { let dbService: SigningKeysStoreService; let repositoryService: RepositoryService; let locatorService: LocatorService; - let signingkeyEventsCacheService: SigningKeysRegistryService; + let signingKeysRegistryService: SigningKeysRegistryService; let signingKeysFetch: SigningKeysRegistryFetcherService; let providerService: ProviderService; @@ -45,7 +45,7 @@ describe('SigningKeyEventsCacheService', () => { dbService = moduleRef.get(SigningKeysStoreService); repositoryService = moduleRef.get(RepositoryService); locatorService = moduleRef.get(LocatorService); - signingkeyEventsCacheService = moduleRef.get(SigningKeysRegistryService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); signingKeysFetch = moduleRef.get(SigningKeysRegistryFetcherService); providerService = moduleRef.get(ProviderService); @@ -97,14 +97,14 @@ describe('SigningKeyEventsCacheService', () => { }); jest - .spyOn(signingkeyEventsCacheService, 'getDeploymentBlockByNetwork') + .spyOn(signingKeysRegistryService, 'getDeploymentBlockByNetwork') .mockImplementation(async () => { return expected.headers.startBlock; }); const deleteCache = jest.spyOn(dbService, 'deleteCache'); - await signingkeyEventsCacheService.handleNewBlock([ + await signingKeysRegistryService.handleNewBlock([ ...cacheMock.headers.stakingModulesAddresses, newEvent.moduleAddress, ]); @@ -165,11 +165,10 @@ describe('SigningKeyEventsCacheService', () => { )}, currentModules = ${JSON.stringify( testCase.currentModules, )}, expected = ${testCase.expected}`, () => { - const result = - signingkeyEventsCacheService.wasStakingModulesListUpdated( - testCase.previousModules, - testCase.currentModules, - ); + const result = signingKeysRegistryService.wasStakingModulesListUpdated( + testCase.previousModules, + testCase.currentModules, + ); expect(result).toEqual(testCase.expected); }); diff --git a/src/guardian/duplicates/keys-duplication-checker.module.ts b/src/guardian/duplicates/keys-duplication-checker.module.ts index 3580014b..3b640e83 100644 --- a/src/guardian/duplicates/keys-duplication-checker.module.ts +++ b/src/guardian/duplicates/keys-duplication-checker.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; -import { SigningKeyEventsCacheModule } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryModule } from 'contracts/signing-keys-registry'; import { KeysDuplicationCheckerService } from './keys-duplication-checker.service'; @Module({ - imports: [SigningKeyEventsCacheModule], + imports: [SigningKeysRegistryModule], providers: [KeysDuplicationCheckerService], exports: [KeysDuplicationCheckerService], }) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts index 06745a4d..38651fff 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts @@ -1,9 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LoggerModule } from 'common/logger'; import { - SigningKeyEventsCacheModule, - SigningKeyEventsCacheService, -} from 'contracts/signing-key-events-cache'; + SigningKeysRegistryModule, + SigningKeysRegistryService, +} from 'contracts/signing-keys-registry'; import { KeysDuplicationCheckerModule } from './keys-duplication-checker.module'; import { KeysDuplicationCheckerService } from './keys-duplication-checker.service'; import { eventMock1, keyMock1, keyMock2 } from './keys.fixtures'; @@ -15,7 +15,7 @@ import { RepositoryModule } from 'contracts/repository'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; describe('KeysDuplicationCheckerService', () => { let service: KeysDuplicationCheckerService; - const mockSigningKeyEventsCacheService = { + const mockSigningKeysRegistryService = { getUpdatedSigningKeyEvents: jest.fn(), }; @@ -30,11 +30,11 @@ describe('KeysDuplicationCheckerService', () => { MockProviderModule.forRoot(), LoggerModule, KeysDuplicationCheckerModule, - SigningKeyEventsCacheModule, + SigningKeysRegistryModule, ], }) - .overrideProvider(SigningKeyEventsCacheService) - .useValue(mockSigningKeyEventsCacheService) + .overrideProvider(SigningKeysRegistryService) + .useValue(mockSigningKeysRegistryService) .compile(); service = moduleRef.get( @@ -138,7 +138,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 1, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event], @@ -180,7 +180,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 1, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event, keyMock2Event], @@ -228,7 +228,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 2, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event, keyMock2Event], @@ -296,7 +296,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 1, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event], @@ -346,7 +346,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 1, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event, keyMock2Event], @@ -398,7 +398,7 @@ describe('KeysDuplicationCheckerService', () => { blockNumber: 2, }; - mockSigningKeyEventsCacheService.getUpdatedSigningKeyEvents.mockImplementationOnce( + mockSigningKeysRegistryService.getUpdatedSigningKeyEvents.mockImplementationOnce( async () => { return { events: [keyMock1Event, keyMock2Event], diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index e3127e0a..d3fd3aa3 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -13,7 +13,7 @@ const BATCH_SIZE = 10; export class KeysDuplicationCheckerService { constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, - private signingKeyEventsCacheService: SigningKeysRegistryService, + private signingKeysRegistryService: SigningKeysRegistryService, ) {} /** @@ -243,7 +243,7 @@ export class KeysDuplicationCheckerService { blockData: BlockData, ): Promise { const eventsGroup = - await this.signingKeyEventsCacheService.getUpdatedSigningKeyEvents( + await this.signingKeysRegistryService.getUpdatedSigningKeyEvents( key, blockData.blockNumber, blockData.blockHash, diff --git a/src/guardian/duplicates/keys.fixtures.ts b/src/guardian/duplicates/keys.fixtures.ts index e045b0f9..83717c78 100644 --- a/src/guardian/duplicates/keys.fixtures.ts +++ b/src/guardian/duplicates/keys.fixtures.ts @@ -1,4 +1,4 @@ -import { SigningKeyEvent } from 'contracts/signing-key-events-cache/interfaces/event.interface'; +import { SigningKeyEvent } from 'contracts/signing-keys-registry/interfaces/event.interface'; export const keyMock1 = { key: '0xb3c90525010a5710d43acbea46047fc37ed55306d032527fa15dd7e8cd8a9a5fa490347cc5fce59936fb8300683cd9f3', diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index d9aec8a2..2367ba25 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -9,7 +9,7 @@ import { StakingModuleGuardModule } from './staking-module-guard'; import { GuardianMessageModule } from './guardian-message'; import { GuardianMetricsModule } from './guardian-metrics'; import { KeysApiModule } from 'keys-api/keys-api.module'; -import { SigningKeyEventsCacheModule } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryModule } from 'contracts/signing-keys-registry'; import { UnvettingModule } from './unvetting/unvetting.module'; import { StakingModuleDataCollectorModule } from 'staking-module-data-collector'; import { StakingRouterModule } from 'contracts/staking-router'; @@ -27,7 +27,7 @@ import { StakingRouterModule } from 'contracts/staking-router'; GuardianMessageModule, GuardianMetricsModule, KeysApiModule, - SigningKeyEventsCacheModule, + SigningKeysRegistryModule, StakingRouterModule, ], providers: [GuardianService], diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 903a6772..72e592b6 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -56,7 +56,7 @@ export class GuardianService implements OnModuleInit { private providerService: ProviderService, private keysApiService: KeysApiService, - private signingKeyEventsCacheService: SigningKeysRegistryService, + private signingKeysRegistryService: SigningKeysRegistryService, private unvettingService: UnvettingService, @@ -77,7 +77,7 @@ export class GuardianService implements OnModuleInit { await Promise.all([ this.depositService.initialize(), this.securityService.initialize({ blockHash }), - this.signingKeyEventsCacheService.initialize( + this.signingKeysRegistryService.initialize( stakingRouterModuleAddresses, ), ]); @@ -107,8 +107,8 @@ export class GuardianService implements OnModuleInit { // The event cache is stored with an N block lag to avoid caching data from uncle blocks // so we don't worry about blockHash here - // TODO: rewrite signingKeyEventsCacheService - await this.signingKeyEventsCacheService.updateEventsCache( + // TODO: rewrite SigningKeysRegistryService + await this.signingKeysRegistryService.updateEventsCache( stakingRouterModuleAddresses, ); @@ -288,7 +288,7 @@ export class GuardianService implements OnModuleInit { (stakingModule) => stakingModule.stakingModuleAddress, ); // update cache if needs - await this.signingKeyEventsCacheService.handleNewBlock( + await this.signingKeysRegistryService.handleNewBlock( stakingRouterModuleAddresses, ); diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index 197c8b4f..aa81692b 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -34,7 +34,7 @@ import { closeServer, initLevelDB, } from './helpers/test-setup'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { ProviderService } from 'provider'; @@ -43,7 +43,7 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { SecurityService } from 'contracts/security'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; @@ -63,7 +63,7 @@ describe('Deposits in case of duplicates', () => { let depositIntegrityCheckerService: DepositIntegrityCheckerService; let signKeyLevelDBService: SignKeyLevelDBService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let stakingModuleGuardService: StakingModuleGuardService; let guardianMessageService: GuardianMessageService; @@ -131,7 +131,7 @@ describe('Deposits in case of duplicates', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); @@ -204,7 +204,7 @@ describe('Deposits in case of duplicates', () => { ); // mock events cache to check - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], // dont need events in this test headers: { startBlock: currentBlock.number - 2, @@ -333,7 +333,7 @@ describe('Deposits in case of duplicates', () => { unusedKeys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [ mockKeyEvent, // key of second module was added later @@ -474,7 +474,7 @@ describe('Deposits in case of duplicates', () => { unusedKeys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [ { ...mockKeyEvent, operatorIndex: mockOperator1.index }, // key of second module was added later @@ -617,7 +617,7 @@ describe('Deposits in case of duplicates', () => { keys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number - 2, @@ -743,7 +743,7 @@ describe('Deposits in case of duplicates', () => { keys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number - 2, @@ -842,7 +842,7 @@ describe('Deposits in case of duplicates', () => { unusedKeys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [mockKeyEvent], headers: { startBlock: currentBlock.number - 2, diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 6c1f4494..37292f20 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -35,7 +35,7 @@ import { closeServer, initLevelDB, } from './helpers/test-setup'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; import { makeDeposit, signDeposit } from './helpers/deposit'; import { StakingModuleGuardService } from 'guardian/staking-module-guard'; @@ -44,7 +44,7 @@ import { GuardianService } from 'guardian'; import { KeysApiService } from 'keys-api/keys-api.service'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { StakingModuleDataCollectorService } from 'staking-module-data-collector'; import { addGuardians } from './helpers/dsm'; import { makeServer } from './server'; @@ -61,7 +61,7 @@ describe('ganache e2e tests', () => { let sendPauseMessage: jest.SpyInstance; let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let stakingModuleGuardService: StakingModuleGuardService; let guardianMessageService: GuardianMessageService; let stakingModuleDataCollectorService: StakingModuleDataCollectorService; @@ -116,7 +116,7 @@ describe('ganache e2e tests', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); @@ -161,7 +161,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -297,7 +297,7 @@ describe('ganache e2e tests', () => { unusedKeys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [ mockKeyEvent, { @@ -417,7 +417,7 @@ describe('ganache e2e tests', () => { keys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number - 2, @@ -516,7 +516,7 @@ describe('ganache e2e tests', () => { keys, ); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number - 2, diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index f7f2eae9..a03164ab 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -50,9 +50,9 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; @@ -75,7 +75,7 @@ describe('ganache e2e tests', () => { let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; // method mocks @@ -109,7 +109,7 @@ describe('ganache e2e tests', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); @@ -202,7 +202,7 @@ describe('ganache e2e tests', () => { }); // dont set events for keys as we check this cache only in case of duplicated keys - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -270,7 +270,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -348,7 +348,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -411,7 +411,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -525,7 +525,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -630,7 +630,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 252f4282..736ee891 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -50,9 +50,9 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { GuardianMessageService } from 'guardian/guardian-message'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { makeServer } from './server'; import { addGuardians } from './helpers/dsm'; import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; @@ -75,7 +75,7 @@ describe('ganache e2e tests', () => { let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; const setupServer = async () => { @@ -106,7 +106,7 @@ describe('ganache e2e tests', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); // keys api servies @@ -185,7 +185,7 @@ describe('ganache e2e tests', () => { }); // dont set events for keys as we check this cache only in case of duplicated keys - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -241,7 +241,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -320,7 +320,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -383,7 +383,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -507,7 +507,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index a7e1abde..ab6498a3 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -39,8 +39,8 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { ProviderService } from 'provider'; import { GuardianMessageService } from 'guardian/guardian-message'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { BlsService } from 'bls'; import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; @@ -56,7 +56,7 @@ describe('Guardian balance monitoring test', () => { let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; let securityService: SecurityService; @@ -127,7 +127,7 @@ describe('Guardian balance monitoring test', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: blockNumber, @@ -196,7 +196,7 @@ describe('Guardian balance monitoring test', () => { }; const initializeKeyEventServices = (moduleRef) => { - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); }; const initializeProviders = (moduleRef) => { diff --git a/test/helpers/test-setup.ts b/test/helpers/test-setup.ts index 8e37d623..3dee69ba 100644 --- a/test/helpers/test-setup.ts +++ b/test/helpers/test-setup.ts @@ -10,7 +10,7 @@ import { KeysApiModule } from 'keys-api/keys-api.module'; import { GanacheProviderModule } from 'provider'; import { WalletModule } from 'wallet'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; export const setupTestingModule = async () => { diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index a839f967..8e16ff4f 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -41,10 +41,10 @@ import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; import { makeDeposit, signDeposit } from './helpers/deposit'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; @@ -60,7 +60,7 @@ describe('ganache e2e tests', () => { let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; let securityService: SecurityService; @@ -96,7 +96,7 @@ describe('ganache e2e tests', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); @@ -175,7 +175,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -313,7 +313,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 6f2c1de7..eb737530 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -42,10 +42,10 @@ import { ProviderService } from 'provider'; import { Server } from 'ganache'; import { GuardianMessageService } from 'guardian/guardian-message'; import { DepositsRegistryStoreService } from 'contracts/deposits-registry/store'; -import { LevelDBService as SignKeyLevelDBService } from 'contracts/signing-key-events-cache/leveldb'; +import { SigningKeysStoreService as SignKeyLevelDBService } from 'contracts/signing-keys-registry/store'; import { KeyValidatorInterface } from '@lido-nestjs/key-validation'; import { makeDeposit, signDeposit } from './helpers/deposit'; -import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; +import { SigningKeysRegistryService } from 'contracts/signing-keys-registry'; import { addGuardians } from './helpers/dsm'; import { BlsService } from 'bls'; import { DepositIntegrityCheckerService } from 'contracts/deposits-registry/sanity-checker'; @@ -64,7 +64,7 @@ describe('ganache e2e tests', () => { let levelDBService: DepositsRegistryStoreService; let signKeyLevelDBService: SignKeyLevelDBService; let guardianMessageService: GuardianMessageService; - let signingKeyEventsCacheService: SigningKeyEventsCacheService; + let signingKeysRegistryService: SigningKeysRegistryService; let depositIntegrityCheckerService: DepositIntegrityCheckerService; const setupServer = async () => { @@ -95,7 +95,7 @@ describe('ganache e2e tests', () => { await blsService.onModuleInit(); // keys events service - signingKeyEventsCacheService = moduleRef.get(SigningKeyEventsCacheService); + signingKeysRegistryService = moduleRef.get(SigningKeysRegistryService); providerService = moduleRef.get(ProviderService); @@ -161,7 +161,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, @@ -272,7 +272,7 @@ describe('ganache e2e tests', () => { }, }); - await signingKeyEventsCacheService.setCachedEvents({ + await signingKeysRegistryService.setCachedEvents({ data: [], headers: { startBlock: currentBlock.number, From b2202844e1a0168adfddbd26580800d55db97b76 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 09:56:03 +0200 Subject: [PATCH 78/94] feat: connect singing keys module via method --- .../fetcher/fetcher.module.ts | 4 +- .../sanity-checker/index.ts | 2 + ....ts => signing-keys-registry.constants.ts} | 0 .../signing-keys-registry.module.ts | 50 ++++++++++++++----- .../signing-keys-registry.service.ts | 2 +- .../signing-keys-registry.spec.ts | 2 +- .../keys-duplication-checker.module.ts | 2 +- .../keys-duplication-checker.service.spec.ts | 8 ++- src/guardian/guardian.module.ts | 2 +- 9 files changed, 53 insertions(+), 19 deletions(-) rename src/contracts/signing-keys-registry/{constants.ts => signing-keys-registry.constants.ts} (100%) diff --git a/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts b/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts index 66338a99..9b3831eb 100644 --- a/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts +++ b/src/contracts/signing-keys-registry/fetcher/fetcher.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; -import { BlsModule } from 'bls'; +import { StakingRouterModule } from 'contracts/staking-router'; import { SigningKeysRegistryFetcherService } from './fetcher.service'; @Module({ - imports: [BlsModule], + imports: [StakingRouterModule], providers: [SigningKeysRegistryFetcherService], exports: [SigningKeysRegistryFetcherService], }) diff --git a/src/contracts/signing-keys-registry/sanity-checker/index.ts b/src/contracts/signing-keys-registry/sanity-checker/index.ts index e69de29b..04e24741 100644 --- a/src/contracts/signing-keys-registry/sanity-checker/index.ts +++ b/src/contracts/signing-keys-registry/sanity-checker/index.ts @@ -0,0 +1,2 @@ +export * from './sanity-checker.module'; +export * from './sanity-checker.service'; diff --git a/src/contracts/signing-keys-registry/constants.ts b/src/contracts/signing-keys-registry/signing-keys-registry.constants.ts similarity index 100% rename from src/contracts/signing-keys-registry/constants.ts rename to src/contracts/signing-keys-registry/signing-keys-registry.constants.ts diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.module.ts b/src/contracts/signing-keys-registry/signing-keys-registry.module.ts index 47b42160..1bbbdb93 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.module.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.module.ts @@ -1,15 +1,41 @@ -import { Module } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { SigningKeysStoreModule } from './store'; import { SigningKeysRegistryService } from './signing-keys-registry.service'; -import { SIGNING_KEYS_CACHE_DEFAULT } from './constants'; -import { StakingRouterModule } from 'contracts/staking-router'; +import { + SIGNING_KEYS_CACHE_DEFAULT, + SIGNING_KEYS_REGISTRY_FINALIZED_TAG, +} from './signing-keys-registry.constants'; +import { SigningKeysRegistryFetcherModule } from './fetcher'; +import { SigningKeysRegistrySanityCheckerModule } from './sanity-checker'; -@Module({ - imports: [ - StakingRouterModule, - SigningKeysStoreModule.register(SIGNING_KEYS_CACHE_DEFAULT), - ], - providers: [SigningKeysRegistryService], - exports: [SigningKeysRegistryService], -}) -export class SigningKeysRegistryModule {} +@Module({}) +export class SigningKeysRegistryModule { + /** + * Registers the signing keys module with a specific tag to handle block finality. + * The `finalizedTag` is primarily used to address issues with the Ganache handling of the 'finalized' tag, + * where it needs to be substituted with 'latest' for end-to-end tests. This tag is necessary only on a Ethereum node + * to avoid issues with blockchain reorganizations. + * In a production environment, this argument should either be empty or set to 'finalized'. + * + * @param {string} [finalizedTag='finalized'] - The tag to be used for identifying the status of blocks concerning finality. + * @returns {DynamicModule} - The dynamic module configuration for the Deposits Registry. + */ + static register(finalizedTag = 'finalized'): DynamicModule { + return { + module: SigningKeysRegistryModule, + imports: [ + SigningKeysRegistryFetcherModule, + SigningKeysRegistrySanityCheckerModule, + SigningKeysStoreModule.register(SIGNING_KEYS_CACHE_DEFAULT), + ], + providers: [ + SigningKeysRegistryService, + { + provide: SIGNING_KEYS_REGISTRY_FINALIZED_TAG, + useValue: finalizedTag, + }, + ], + exports: [SigningKeysRegistryService], + }; + } +} diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts index f00c597e..2392dfb3 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts @@ -8,7 +8,7 @@ import { EARLIEST_MODULE_DEPLOYMENT_BLOCK_NETWORK, FETCHING_EVENTS_STEP, SIGNING_KEYS_REGISTRY_FINALIZED_TAG, -} from './constants'; +} from './signing-keys-registry.constants'; import { performance } from 'perf_hooks'; import { SigningKeysRegistryFetcherService } from './fetcher'; import { SigningKeysRegistrySanityCheckerService } from './sanity-checker/sanity-checker.service'; diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts index b402f918..2f2324f2 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -38,7 +38,7 @@ describe('SigningKeysRegistryService', () => { 'signing-keys-spec', ), LoggerModule, - SigningKeysRegistryModule, + SigningKeysRegistryModule.register('latest'), ], }).compile(); diff --git a/src/guardian/duplicates/keys-duplication-checker.module.ts b/src/guardian/duplicates/keys-duplication-checker.module.ts index 3b640e83..a5ba4528 100644 --- a/src/guardian/duplicates/keys-duplication-checker.module.ts +++ b/src/guardian/duplicates/keys-duplication-checker.module.ts @@ -3,7 +3,7 @@ import { SigningKeysRegistryModule } from 'contracts/signing-keys-registry'; import { KeysDuplicationCheckerService } from './keys-duplication-checker.service'; @Module({ - imports: [SigningKeysRegistryModule], + imports: [SigningKeysRegistryModule.register()], providers: [KeysDuplicationCheckerService], exports: [KeysDuplicationCheckerService], }) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts index 38651fff..ccd25324 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.spec.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.spec.ts @@ -30,7 +30,7 @@ describe('KeysDuplicationCheckerService', () => { MockProviderModule.forRoot(), LoggerModule, KeysDuplicationCheckerModule, - SigningKeysRegistryModule, + SigningKeysRegistryModule.register('latest'), ], }) .overrideProvider(SigningKeysRegistryService) @@ -142,6 +142,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event], + isValid: true, }; }, ); @@ -184,6 +185,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event, keyMock2Event], + isValid: true, }; }, ); @@ -232,6 +234,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event, keyMock2Event], + isValid: true, }; }, ); @@ -300,6 +303,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event], + isValid: true, }; }, ); @@ -350,6 +354,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event, keyMock2Event], + isValid: true, }; }, ); @@ -402,6 +407,7 @@ describe('KeysDuplicationCheckerService', () => { async () => { return { events: [keyMock1Event, keyMock2Event], + isValid: true, }; }, ); diff --git a/src/guardian/guardian.module.ts b/src/guardian/guardian.module.ts index 2367ba25..509b4b66 100644 --- a/src/guardian/guardian.module.ts +++ b/src/guardian/guardian.module.ts @@ -27,7 +27,7 @@ import { StakingRouterModule } from 'contracts/staking-router'; GuardianMessageModule, GuardianMetricsModule, KeysApiModule, - SigningKeysRegistryModule, + SigningKeysRegistryModule.register(), StakingRouterModule, ], providers: [GuardianService], From 8bb45676c6e74fed2cade3ad0e344e7691bf9b6a Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 10:04:41 +0200 Subject: [PATCH 79/94] fix: signing keys test --- .../signing-keys-registry/signing-keys-registry.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts index 2f2324f2..9352293b 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.spec.ts @@ -1,4 +1,5 @@ import { Test } from '@nestjs/testing'; +import { Block } from '@ethersproject/abstract-provider'; import { MockProviderModule, ProviderService } from 'provider'; import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; @@ -90,11 +91,9 @@ describe('SigningKeysRegistryService', () => { }; }); - jest - .spyOn(providerService, 'getBlockNumber') - .mockImplementation(async () => { - return endBlock; - }); + jest.spyOn(providerService, 'getBlock').mockImplementation(async () => { + return { number: endBlock } as Block; + }); jest .spyOn(signingKeysRegistryService, 'getDeploymentBlockByNetwork') From b955dc8868df4b1ad7a14169c2d7dcdaccdfd1ff Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 10:41:22 +0200 Subject: [PATCH 80/94] fix: checkEventsBlockHash in signing keys --- .../sanity-checker/sanity-checker.service.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts index 66c37f4b..05f96ea0 100644 --- a/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts +++ b/src/contracts/signing-keys-registry/sanity-checker/sanity-checker.service.ts @@ -64,15 +64,14 @@ export class SigningKeysRegistrySanityCheckerService { blockHash: string, ): boolean { const event = this.findReorganizedEvent(events, blockNumber, blockHash); - if (event) { this.logger.error('Reorganization found in signing key event', { blockHash: event.blockHash, blockNumber: event.blockNumber, }); - return true; + return false; } - return false; + return true; } /** From d4b359b493207c6f0bd4cad67b6ddfc50b5ef1b5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 16 Sep 2024 13:10:04 +0200 Subject: [PATCH 81/94] fix: error handling --- .../keys-duplication-checker.service.ts | 29 ++++++++++++------- src/guardian/guardian.service.ts | 7 ----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index d3fd3aa3..bc694f71 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -1,5 +1,8 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { SigningKeyEvent } from 'contracts/signing-keys-registry/interfaces/event.interface'; +import { + SigningKeyEvent, + SigningKeyEventsGroupWithStakingModules, +} from 'contracts/signing-keys-registry/interfaces/event.interface'; import { SigningKeysRegistryService } from 'contracts/signing-keys-registry/signing-keys-registry.service'; import { BlockData } from 'guardian/interfaces'; @@ -186,7 +189,19 @@ export class KeysDuplicationCheckerService { uniqueOperatorIdentifiers: string[], blockData: BlockData, ) { - const events = await this.fetchSigningKeyEvents(key, blockData); + const { events, isValid } = await this.fetchSigningKeyEvents( + key, + blockData, + ); + + if (!isValid) { + this.logger.error('Signing keys events are not valid on the block', { + currentBlockNumber: blockData.blockNumber, + currentBlockHash: blockData.blockHash, + }); + // Return the entire list of duplicates as unresolved + return { duplicateKeys: [], unresolvedKeys: suspectedDuplicateKeys }; + } const operatorsWithoutEvents = this.getOperatorsWithoutEvents( uniqueOperatorIdentifiers, @@ -241,7 +256,7 @@ export class KeysDuplicationCheckerService { private async fetchSigningKeyEvents( key: string, blockData: BlockData, - ): Promise { + ): Promise { const eventsGroup = await this.signingKeysRegistryService.getUpdatedSigningKeyEvents( key, @@ -249,13 +264,7 @@ export class KeysDuplicationCheckerService { blockData.blockHash, ); - if (!eventsGroup.isValid) { - throw new Error( - `Signing keys events are not valid on the block ${blockData.blockHash}`, - ); - } - - return eventsGroup.events; + return eventsGroup; } private getOperatorsWithoutEvents( diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 72e592b6..4dbf3542 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -105,13 +105,6 @@ export class GuardianService implements OnModuleInit { ); } - // The event cache is stored with an N block lag to avoid caching data from uncle blocks - // so we don't worry about blockHash here - // TODO: rewrite SigningKeysRegistryService - await this.signingKeysRegistryService.updateEventsCache( - stakingRouterModuleAddresses, - ); - this.subscribeToModulesUpdates(); } catch (error) { this.logger.error(error); From 5fc7e80fe672b51de430837f9bcf927e203a21d8 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 16 Sep 2024 23:06:41 +0400 Subject: [PATCH 82/94] fix: lint --- src/contracts/security/security.service.spec.ts | 1 - src/guardian/guardian.service.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/contracts/security/security.service.spec.ts b/src/contracts/security/security.service.spec.ts index 9d0ac94c..8e7620d3 100644 --- a/src/contracts/security/security.service.spec.ts +++ b/src/contracts/security/security.service.spec.ts @@ -471,7 +471,6 @@ describe('SecurityService', () => { }); describe('messages prefixes', () => { - const blockNumber = 10; const blockHash = '0x'; beforeEach(async () => { diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index e05acb2a..c1ee1320 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -29,11 +29,8 @@ import { KeysApiService } from 'keys-api/keys-api.service'; import { MIN_KAPI_VERSION } from './guardian.constants'; import { SigningKeyEventsCacheService } from 'contracts/signing-key-events-cache'; import { UnvettingService } from './unvetting/unvetting.service'; -import { Meta } from 'keys-api/interfaces/Meta'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -import { SROperatorListWithModule } from 'keys-api/interfaces/SROperatorListWithModule'; import { StakingRouterService } from 'contracts/staking-router'; -import { SRModuleListResponse } from 'keys-api/interfaces/SRModuleListResponse'; import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; import { SRModule } from 'keys-api/interfaces'; From a994b983b3855f36045c1a296f9a98c1651bf801 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 10:11:45 +0200 Subject: [PATCH 83/94] fix: change error handling logic in signing keys events registry --- .../interfaces/event.interface.ts | 1 - .../signing-keys-registry.service.ts | 15 +++++++-------- .../keys-duplication-checker.service.ts | 14 +------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/contracts/signing-keys-registry/interfaces/event.interface.ts b/src/contracts/signing-keys-registry/interfaces/event.interface.ts index 64446a84..e84c01a0 100644 --- a/src/contracts/signing-keys-registry/interfaces/event.interface.ts +++ b/src/contracts/signing-keys-registry/interfaces/event.interface.ts @@ -15,5 +15,4 @@ export interface SigningKeyEventsGroup { export interface SigningKeyEventsGroupWithStakingModules extends SigningKeyEventsGroup { stakingModulesAddresses: string[]; - isValid: boolean; } diff --git a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts index 2392dfb3..67d4f697 100644 --- a/src/contracts/signing-keys-registry/signing-keys-registry.service.ts +++ b/src/contracts/signing-keys-registry/signing-keys-registry.service.ts @@ -261,13 +261,9 @@ export class SigningKeysRegistryService { ); if (!isCacheValid) { - return { - events: cachedEvents.data, - startBlock: cachedEvents.headers.startBlock, - endBlock: cachedEvents.headers.endBlock, - stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, - isValid: false, - }; + throw new Error( + `Signing key events cache is newer than the current block: ${blockNumber}`, + ); } const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; @@ -287,6 +283,10 @@ export class SigningKeysRegistryService { blockHash, ); + if (!isValid) { + throw new Error(`Reorganization found on block ${blockNumber}`); + } + this.logger.debug?.('Fresh signing key add events are fetched', { events: freshEvents.length, startBlock: firstNotCachedBlock, @@ -314,7 +314,6 @@ export class SigningKeysRegistryService { stakingModulesAddresses: cachedEvents.headers.stakingModulesAddresses, startBlock: cachedEvents.headers.startBlock, endBlock, - isValid, }; } diff --git a/src/guardian/duplicates/keys-duplication-checker.service.ts b/src/guardian/duplicates/keys-duplication-checker.service.ts index bc694f71..a909b850 100644 --- a/src/guardian/duplicates/keys-duplication-checker.service.ts +++ b/src/guardian/duplicates/keys-duplication-checker.service.ts @@ -189,19 +189,7 @@ export class KeysDuplicationCheckerService { uniqueOperatorIdentifiers: string[], blockData: BlockData, ) { - const { events, isValid } = await this.fetchSigningKeyEvents( - key, - blockData, - ); - - if (!isValid) { - this.logger.error('Signing keys events are not valid on the block', { - currentBlockNumber: blockData.blockNumber, - currentBlockHash: blockData.blockHash, - }); - // Return the entire list of duplicates as unresolved - return { duplicateKeys: [], unresolvedKeys: suspectedDuplicateKeys }; - } + const { events } = await this.fetchSigningKeyEvents(key, blockData); const operatorsWithoutEvents = this.getOperatorsWithoutEvents( uniqueOperatorIdentifiers, From 06ea1be8c797feda9048a3a7caae66f09950f8f4 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 11:54:00 +0200 Subject: [PATCH 84/94] feat: new deposit registry algorithm --- .../deposits-registry.service.ts | 18 +++---- .../interfaces/event.interface.ts | 5 +- .../deposits-registry/store/store.service.ts | 47 +++++++++++++++++++ src/guardian/guardian.service.ts | 6 +-- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index 4a8680b6..d4b6db1b 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -87,7 +87,7 @@ export class DepositRegistryService { initialCache, finalizedBlockNumber, ); - + // TODO: We can delete the cache if the node starts giving earlier blocks, but this functionality was not available in the earlier version and it is not known how the system will behave if (!isCacheValid) return; let lastIndexedEvent: VerifiedDepositEvent | undefined = undefined; @@ -159,6 +159,10 @@ export class DepositRegistryService { finalizedBlock, finalizedBlockHash, }); + + // Delete invalid cache only after full synchronization due to: + // - we cannot check root at arbitrary times, only if the backlog is less than 120 blocks + await this.store.clearFromLastValidEvent(); } this.logger.log('Deposit events cache is updated', { @@ -184,12 +188,9 @@ export class DepositRegistryService { ); if (!isCacheValid) { - return { - events: cachedEvents.data, - startBlock: cachedEvents.headers.startBlock, - endBlock: cachedEvents.headers.endBlock, - isValid: false, - }; + throw new Error( + `Deposit events cache is newer than the current block ${blockNumber}`, + ); } const firstNotCachedBlock = cachedEvents.headers.endBlock + 1; @@ -219,6 +220,8 @@ export class DepositRegistryService { : '', lastValidEventDepositCount: lastValidEvent?.depositCount, }); + + throw new Error(`Integrity check failed on block ${blockNumber}`); } this.logger.debug?.('Fresh deposit events are fetched', { @@ -235,7 +238,6 @@ export class DepositRegistryService { events: mergedEvents, startBlock: cachedEvents.headers.startBlock, endBlock, - isValid, }; } diff --git a/src/contracts/deposits-registry/interfaces/event.interface.ts b/src/contracts/deposits-registry/interfaces/event.interface.ts index a7322cab..470d453f 100644 --- a/src/contracts/deposits-registry/interfaces/event.interface.ts +++ b/src/contracts/deposits-registry/interfaces/event.interface.ts @@ -26,6 +26,5 @@ export interface VerifiedDepositEventGroup extends DepositEventGroup { events: VerifiedDepositEvent[]; } -export interface VerifiedDepositedEventGroup extends VerifiedDepositEventGroup { - isValid: boolean; -} +export interface VerifiedDepositedEventGroup + extends VerifiedDepositEventGroup {} diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts index 0a337fbd..88ffff40 100644 --- a/src/contracts/deposits-registry/store/store.service.ts +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -112,6 +112,53 @@ export class DepositsRegistryStoreService { } } + /** + * Clears all deposit records from the database starting from the deposit count of the last valid event. + * If no valid event is found, it will clear deposits greater than deposit count zero. + * This method leverages the `deleteDepositsGreaterThanNBatch` method for batch deletion. + * @returns {Promise} A promise that resolves when all appropriate deposits have been deleted. + */ + public async clearFromLastValidEvent(): Promise { + const lastValidEvent = await this.getLastValidEvent(); + + // Determine the starting index for deletion based on the last valid event's deposit count + const fromIndex = lastValidEvent ? lastValidEvent.depositCount : 0; + + // Delete all deposits from the determined index onwards + await this.deleteDepositsGreaterThanNBatch(fromIndex); + } + + /** + * Deletes all deposit records from the database with keys greater than a specified number. + * @param {number} depositCount - The number above which deposit keys will be deleted. + * @returns {Promise} A promise that resolves when the operation is complete. + */ + public async deleteDepositsGreaterThanNBatch( + depositCount: number, + ): Promise { + // Generate the upper boundary key for deletion + const upperBoundKey = this.generateDepositKey(depositCount); + + // Initialize the iterator starting from the upper boundary key + const stream = this.db.iterator({ gte: upperBoundKey }); + + // Initialize an array to hold batch operations + const ops: { type: 'del'; key: string }[] = []; + + // Populate the batch operations array with delete operations + for await (const [key] of stream) { + ops.push({ + type: 'del', + key: key, + }); + } + + // Execute the batch operation if there are any operations to perform + if (ops.length > 0) { + await this.db.batch(ops); + } + } + /** * Generates a deposit key string based on a given number. * The number is checked to ensure it falls within a valid range (from 0 up to MAX_DEPOSIT_COUNT). diff --git a/src/guardian/guardian.service.ts b/src/guardian/guardian.service.ts index 4dbf3542..440ffa4a 100644 --- a/src/guardian/guardian.service.ts +++ b/src/guardian/guardian.service.ts @@ -348,7 +348,6 @@ export class GuardianService implements OnModuleInit { stakingModuleData, blockData.theftHappened, blockData.alreadyPausedDeposits, - blockData.depositedEvents.isValid, stakingModuleData.stakingModuleId, ) ) { @@ -367,7 +366,6 @@ export class GuardianService implements OnModuleInit { stakingModuleData: StakingModuleData, theftHappened: boolean, alreadyPausedDeposits: boolean, - isDepositsCacheValid: boolean, stakingModuleId: number, ): boolean { const keysForUnvetting = stakingModuleData.invalidKeys.concat( @@ -381,8 +379,7 @@ export class GuardianService implements OnModuleInit { stakingModuleData.unresolvedDuplicatedKeys.length > 0 || alreadyPausedDeposits || theftHappened || - stakingModuleData.isModuleDepositsPaused || - !isDepositsCacheValid; + stakingModuleData.isModuleDepositsPaused; if (ignoreDeposits) { this.logger.warn('Deposits are not available', { @@ -391,7 +388,6 @@ export class GuardianService implements OnModuleInit { alreadyPausedDeposits, theftHappened, isModuleDepositsPaused: stakingModuleData.isModuleDepositsPaused, - isDepositsCacheValid, stakingModuleId, }); } From 3646a9a88dcda2336479707ad61c8baf000e4f6e Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 17 Sep 2024 13:59:59 +0400 Subject: [PATCH 85/94] fix: increase kapi version up to 2.0.0 to support vetted status in key --- src/guardian/guardian.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guardian/guardian.constants.ts b/src/guardian/guardian.constants.ts index 50cf550e..5c029200 100644 --- a/src/guardian/guardian.constants.ts +++ b/src/guardian/guardian.constants.ts @@ -3,4 +3,4 @@ import { CronExpression } from '@nestjs/schedule'; export const GUARDIAN_DEPOSIT_RESIGNING_BLOCKS = 10; export const GUARDIAN_DEPOSIT_JOB_NAME = 'guardian-deposit-job'; export const GUARDIAN_DEPOSIT_JOB_DURATION = CronExpression.EVERY_5_SECONDS; -export const MIN_KAPI_VERSION = '1.5.0'; +export const MIN_KAPI_VERSION = '2.0.0'; From 47a42765f92899f81ab9db287a31d41be9314437 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 12:38:28 +0200 Subject: [PATCH 86/94] feat: clearFromLastValidEvent tests --- .../store/store.service.spec.ts | 85 ++++++++++++++++++- .../deposits-registry/store/store.service.ts | 2 +- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/contracts/deposits-registry/store/store.service.spec.ts b/src/contracts/deposits-registry/store/store.service.spec.ts index 90e41ac5..99af6b8c 100644 --- a/src/contracts/deposits-registry/store/store.service.spec.ts +++ b/src/contracts/deposits-registry/store/store.service.spec.ts @@ -4,7 +4,15 @@ import { ConfigModule } from 'common/config'; import { LoggerModule } from 'common/logger'; import { DepositsRegistryStoreModule } from './store.module'; import { DepositsRegistryStoreService } from './store.service'; -import { cacheMock } from './store.fixtures'; +import { cacheMock, eventMock1 } from './store.fixtures'; + +const getEventsDepositCount = async ( + dbService: DepositsRegistryStoreService, +) => { + const result = await dbService.getEventsCache(); + const remainingDeposits = result.data.map((event) => event.depositCount); + return remainingDeposits; +}; describe('dbService', () => { const defaultCacheValue = { @@ -48,4 +56,79 @@ describe('dbService', () => { expect(result).toEqual(expected); }); + + describe('deleteDepositsGreaterThanNBatch', () => { + const testCases = [ + { N: 10, deposits: [9, 10, 11, 12], expectedRemaining: [9, 10] }, + { N: 5, deposits: [3, 4, 5, 6], expectedRemaining: [3, 4, 5] }, + { N: 0, deposits: [0, 1, 2], expectedRemaining: [0] }, + ]; + + it.each(testCases)( + 'should delete deposits where deposit count is greater than %s', + async ({ N, deposits, expectedRemaining }) => { + await dbService.insertEventsCacheBatch({ + headers: { startBlock: 1, endBlock: 100 }, + data: deposits.map((count) => ({ + ...eventMock1, + depositCount: count, + })), + }); + + const insertedDeposits = await getEventsDepositCount(dbService); + expect(insertedDeposits).toEqual(expect.arrayContaining(deposits)); + expect(insertedDeposits.length).toBe(deposits.length); + + await dbService.deleteDepositsGreaterThanNBatch(N); + + const remainingDeposits = await getEventsDepositCount(dbService); + expect(remainingDeposits).toEqual( + expect.arrayContaining(expectedRemaining), + ); + expect(remainingDeposits.length).toBe(expectedRemaining.length); + }, + ); + }); + + describe('clearFromLastValidEvent', () => { + const testCases = [ + { lastValidCount: 5, deposits: [4, 5, 6], expectedRemaining: [4, 5] }, + { lastValidCount: 1, deposits: [1, 2, 3], expectedRemaining: [1] }, + { lastValidCount: 0, deposits: [0, 1], expectedRemaining: [0] }, + ]; + + it.each(testCases)( + 'should clear deposits starting from depositCount %s', + async ({ lastValidCount, deposits, expectedRemaining }) => { + await dbService.insertLastValidEvent({ + ...eventMock1, + depositCount: lastValidCount, + }); + + const lastEvent = await dbService.getLastValidEvent(); + expect(lastEvent).toBeDefined(); + expect(lastEvent?.depositCount).toBe(lastValidCount); + + await dbService.insertEventsCacheBatch({ + headers: { startBlock: 1, endBlock: 100 }, + data: deposits.map((count) => ({ + ...eventMock1, + depositCount: count, + })), + }); + + const insertedDeposits = await getEventsDepositCount(dbService); + expect(insertedDeposits).toEqual(expect.arrayContaining(deposits)); + expect(insertedDeposits.length).toBe(deposits.length); + + await dbService.clearFromLastValidEvent(); + + const remainingDeposits = await getEventsDepositCount(dbService); + expect(remainingDeposits).toEqual( + expect.arrayContaining(expectedRemaining), + ); + expect(remainingDeposits.length).toBe(expectedRemaining.length); + }, + ); + }); }); diff --git a/src/contracts/deposits-registry/store/store.service.ts b/src/contracts/deposits-registry/store/store.service.ts index 88ffff40..17723d12 100644 --- a/src/contracts/deposits-registry/store/store.service.ts +++ b/src/contracts/deposits-registry/store/store.service.ts @@ -140,7 +140,7 @@ export class DepositsRegistryStoreService { const upperBoundKey = this.generateDepositKey(depositCount); // Initialize the iterator starting from the upper boundary key - const stream = this.db.iterator({ gte: upperBoundKey }); + const stream = this.db.iterator({ gt: upperBoundKey, lte: 'deposit:\xFF' }); // Initialize an array to hold batch operations const ops: { type: 'del'; key: string }[] = []; From e67083f95883c47f005cc311b2793a9b9accb7fe Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 12:42:41 +0200 Subject: [PATCH 87/94] refactor: remove todo --- src/contracts/deposits-registry/deposits-registry.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/deposits-registry/deposits-registry.service.ts b/src/contracts/deposits-registry/deposits-registry.service.ts index d4b6db1b..66fa09c5 100644 --- a/src/contracts/deposits-registry/deposits-registry.service.ts +++ b/src/contracts/deposits-registry/deposits-registry.service.ts @@ -87,7 +87,7 @@ export class DepositRegistryService { initialCache, finalizedBlockNumber, ); - // TODO: We can delete the cache if the node starts giving earlier blocks, but this functionality was not available in the earlier version and it is not known how the system will behave + if (!isCacheValid) return; let lastIndexedEvent: VerifiedDepositEvent | undefined = undefined; From 571dc751094e2f7f0734c1204203d2307f084712 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 13:21:28 +0200 Subject: [PATCH 88/94] feat: check deposit count while inserting node to deposit tree --- .../deposit-tree/deposit-tree.spec.ts | 41 ++++++++++++++----- .../deposit-tree/deposit-tree.ts | 14 +++++-- .../integrity-checker.service.ts | 29 ++++++++++++- 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index 51c8d6ca..3fa0862b 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -5,7 +5,7 @@ import { depositDataRootsFixture10k, dataTransformFixtures, } from './deposit-tree.fixture'; - +const MOCK_DEPOSIT_COUNT = 1n; describe('DepositTree', () => { let depositTree: DepositTree; @@ -23,10 +23,19 @@ describe('DepositTree', () => { test('should correctly insert a node and update the tree', () => { const initialNodeCount = depositTree.nodeCount; const node = new Uint8Array(32).fill(1); // Example node hash - depositTree.insert(node); + depositTree.insert(node, MOCK_DEPOSIT_COUNT); expect(depositTree.nodeCount).toBe(initialNodeCount + 1n); }); + test('should detect problem with deposit count while inserting new node', () => { + const initialNodeCount = depositTree.nodeCount; + const node = new Uint8Array(32).fill(1); + const SOME_UNREAL_DEPOSIT_COUNT = 100n; + const problemFound = depositTree.insert(node, SOME_UNREAL_DEPOSIT_COUNT); + expect(depositTree.nodeCount).toBe(initialNodeCount); + expect(problemFound).toBeTruthy(); + }); + test('should handle detailed node data correctly', () => { const originalTree = new DepositTree(); const nodeData = { @@ -35,7 +44,10 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - originalTree.insert(DepositTree.formDepositNode(nodeData)); + originalTree.insert( + DepositTree.formDepositNode(nodeData), + MOCK_DEPOSIT_COUNT, + ); expect(Number(originalTree.nodeCount)).toBe(1); const oldDepositRoot = originalTree.getRoot(); @@ -43,6 +55,7 @@ describe('DepositTree', () => { cloned.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + 1n, ); expect(cloned.getRoot()).not.toEqual(oldDepositRoot); @@ -51,9 +64,10 @@ describe('DepositTree', () => { const freshTree = new DepositTree(); - freshTree.insert(DepositTree.formDepositNode(nodeData)); + freshTree.insert(DepositTree.formDepositNode(nodeData), MOCK_DEPOSIT_COUNT); freshTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + 1n, ); expect(cloned.getRoot()).toEqual(freshTree.getRoot()); @@ -70,9 +84,11 @@ describe('DepositTree', () => { originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + 1n, ); originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), + 1n, ); originalTree.branch[0][0] = 1; @@ -90,12 +106,15 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - depositTree.insert(DepositTree.formDepositNode(nodeData)); + depositTree.insert( + DepositTree.formDepositNode(nodeData), + MOCK_DEPOSIT_COUNT, + ); expect(Number(depositTree.nodeCount)).toBe(1); }); test('should clone the tree correctly', () => { - depositTree.insert(new Uint8Array(32).fill(1)); + depositTree.insert(new Uint8Array(32).fill(1), MOCK_DEPOSIT_COUNT); const clonedTree = depositTree.clone(); expect(clonedTree.nodeCount).toEqual(depositTree.nodeCount); expect(clonedTree.branch).toEqual(depositTree.branch); @@ -104,12 +123,12 @@ describe('DepositTree', () => { test('branch updates correctly after multiple insertions', () => { const node1 = new Uint8Array(32).fill(1); // First example node - depositTree.insert(node1); // First insertion + depositTree.insert(node1, MOCK_DEPOSIT_COUNT); // First insertion expect(depositTree.branch[0]).toEqual(node1); const node2 = new Uint8Array(32).fill(2); // Second example node - depositTree.insert(node2); // Second insertion + depositTree.insert(node2, MOCK_DEPOSIT_COUNT); // Second insertion // Now, we need to check the second level of the branch // This should use the same hashing function as used in your actual code @@ -146,7 +165,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev) => - depositTree.insert(fromHexString(ev)), + depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -157,7 +176,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev) => - depositTree.insert(fromHexString(ev)), + depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -166,7 +185,7 @@ describe('DepositTree', () => { expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); depositDataRootsFixture20k.events.map((ev) => - depositTree.insert(fromHexString(ev)), + depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), ); expect(Number(depositTree.nodeCount)).toEqual( depositDataRootsFixture10k.events.length + diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index a53a6c72..362eebba 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -68,12 +68,20 @@ export class DepositTree { } /** - * Inserts a new node into the tree using already computed node hash. - * @param {Uint8Array} node - The node's hash to be inserted. + * Inserts a new node into the tree using an already computed hash. The insertion only proceeds + * if the deposit count provided is the next sequential number expected (one more than the current node count). + * @param {Uint8Array} node - The hash of the node to be inserted, represented as a Uint8Array. + * @param {bigint} depositCount - The sequential count of the deposit event from the blockchain, + * expected to be one more than the current highest node count. + * @returns {boolean} Returns true if the node was successfully inserted, false otherwise. */ - public insert(node: Uint8Array) { + public insert(node: Uint8Array, depositCount: bigint): boolean { + if (depositCount !== this.nodeCount + 1n) { + return false; + } this.nodeCount++; this.formBranch(node, this.nodeCount); + return true; } /** diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 87f50c6e..300ae8e5 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -112,7 +112,34 @@ export class DepositIntegrityCheckerService { eventsCache: VerifiedDepositEvent[], ) { for (const [index, event] of eventsCache.entries()) { - tree.insert(event.depositDataRoot); + const insertionIsMade = tree.insert( + event.depositDataRoot, + BigInt(event.depositCount), + ); + + if (!insertionIsMade) { + const { + depositCount, + depositDataRoot, + index: eventIndex, + blockHash, + blockNumber, + } = event; + + this.logger.warn( + 'Problem found while forming deposit tree with event', + { + depositCount, + depositDataRoot, + blockHash, + blockNumber, + eventIndex, + depositCountInTree: tree.nodeCount, + }, + ); + + throw new Error('Problem found while forming deposit tree with event'); + } if (index % DEPOSIT_TREE_STEP_SYNC === 0) { await new Promise((res) => setTimeout(res, 1)); From 31addcd62ec12d79177f7d394a10af9d4cc3a005 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 14:02:23 +0200 Subject: [PATCH 89/94] fix: deposit tree tests --- .../deposit-tree/deposit-tree.spec.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index 3fa0862b..be42eba7 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -31,9 +31,9 @@ describe('DepositTree', () => { const initialNodeCount = depositTree.nodeCount; const node = new Uint8Array(32).fill(1); const SOME_UNREAL_DEPOSIT_COUNT = 100n; - const problemFound = depositTree.insert(node, SOME_UNREAL_DEPOSIT_COUNT); + const isInserted = depositTree.insert(node, SOME_UNREAL_DEPOSIT_COUNT); expect(depositTree.nodeCount).toBe(initialNodeCount); - expect(problemFound).toBeTruthy(); + expect(isInserted).toBeFalsy(); }); test('should handle detailed node data correctly', () => { @@ -44,10 +44,7 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - originalTree.insert( - DepositTree.formDepositNode(nodeData), - MOCK_DEPOSIT_COUNT, - ); + originalTree.insert(DepositTree.formDepositNode(nodeData), 1n); expect(Number(originalTree.nodeCount)).toBe(1); const oldDepositRoot = originalTree.getRoot(); @@ -55,7 +52,7 @@ describe('DepositTree', () => { cloned.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), - 1n, + 2n, ); expect(cloned.getRoot()).not.toEqual(oldDepositRoot); @@ -64,10 +61,10 @@ describe('DepositTree', () => { const freshTree = new DepositTree(); - freshTree.insert(DepositTree.formDepositNode(nodeData), MOCK_DEPOSIT_COUNT); + freshTree.insert(DepositTree.formDepositNode(nodeData), 1n); freshTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), - 1n, + 2n, ); expect(cloned.getRoot()).toEqual(freshTree.getRoot()); @@ -128,7 +125,7 @@ describe('DepositTree', () => { expect(depositTree.branch[0]).toEqual(node1); const node2 = new Uint8Array(32).fill(2); // Second example node - depositTree.insert(node2, MOCK_DEPOSIT_COUNT); // Second insertion + depositTree.insert(node2, MOCK_DEPOSIT_COUNT + 1n); // Second insertion // Now, we need to check the second level of the branch // This should use the same hashing function as used in your actual code @@ -164,8 +161,8 @@ describe('DepositTree', () => { ); test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { - depositDataRootsFixture10k.events.map((ev) => - depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), + depositDataRootsFixture10k.events.map((ev, index) => + depositTree.insert(fromHexString(ev), BigInt(index + 1)), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -175,8 +172,8 @@ describe('DepositTree', () => { }); test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { - depositDataRootsFixture10k.events.map((ev) => - depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), + depositDataRootsFixture10k.events.map((ev, index) => + depositTree.insert(fromHexString(ev), BigInt(index + 1)), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -184,8 +181,11 @@ describe('DepositTree', () => { ); expect(depositTree.getRoot()).toEqual(depositDataRootsFixture10k.root); - depositDataRootsFixture20k.events.map((ev) => - depositTree.insert(fromHexString(ev), MOCK_DEPOSIT_COUNT), + depositDataRootsFixture20k.events.map((ev, index) => + depositTree.insert( + fromHexString(ev), + BigInt(depositDataRootsFixture10k.events.length + index + 1), + ), ); expect(Number(depositTree.nodeCount)).toEqual( depositDataRootsFixture10k.events.length + From 15e3b4c3ad77a19562b4ab3144a521f6b69408c0 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 17:40:10 +0200 Subject: [PATCH 90/94] fix: deposit tree insertion --- .../deposit-tree/deposit-tree.spec.ts | 18 +++++++++--------- .../deposit-tree/deposit-tree.ts | 2 +- .../integrity-checker.service.ts | 5 +++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts index be42eba7..67152621 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.spec.ts @@ -5,7 +5,7 @@ import { depositDataRootsFixture10k, dataTransformFixtures, } from './deposit-tree.fixture'; -const MOCK_DEPOSIT_COUNT = 1n; +const MOCK_DEPOSIT_COUNT = 0n; describe('DepositTree', () => { let depositTree: DepositTree; @@ -44,7 +44,7 @@ describe('DepositTree', () => { signature: '0x987654321fedcba0', amount: '0x0100000000000000', }; - originalTree.insert(DepositTree.formDepositNode(nodeData), 1n); + originalTree.insert(DepositTree.formDepositNode(nodeData), 0n); expect(Number(originalTree.nodeCount)).toBe(1); const oldDepositRoot = originalTree.getRoot(); @@ -52,7 +52,7 @@ describe('DepositTree', () => { cloned.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), - 2n, + 1n, ); expect(cloned.getRoot()).not.toEqual(oldDepositRoot); @@ -61,10 +61,10 @@ describe('DepositTree', () => { const freshTree = new DepositTree(); - freshTree.insert(DepositTree.formDepositNode(nodeData), 1n); + freshTree.insert(DepositTree.formDepositNode(nodeData), 0n); freshTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), - 2n, + 1n, ); expect(cloned.getRoot()).toEqual(freshTree.getRoot()); @@ -81,7 +81,7 @@ describe('DepositTree', () => { originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), - 1n, + 0n, ); originalTree.insert( DepositTree.formDepositNode({ ...nodeData, wc: '0x123456789abcdef1' }), @@ -162,7 +162,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (first 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev, index) => - depositTree.insert(fromHexString(ev), BigInt(index + 1)), + depositTree.insert(fromHexString(ev), BigInt(index)), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -173,7 +173,7 @@ describe('DepositTree', () => { test('hashes should matches with fixtures (second 10k blocks from holesky)', () => { depositDataRootsFixture10k.events.map((ev, index) => - depositTree.insert(fromHexString(ev), BigInt(index + 1)), + depositTree.insert(fromHexString(ev), BigInt(index)), ); expect(Number(depositTree.nodeCount)).toEqual( @@ -184,7 +184,7 @@ describe('DepositTree', () => { depositDataRootsFixture20k.events.map((ev, index) => depositTree.insert( fromHexString(ev), - BigInt(depositDataRootsFixture10k.events.length + index + 1), + BigInt(depositDataRootsFixture10k.events.length + index), ), ); expect(Number(depositTree.nodeCount)).toEqual( diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts index 362eebba..94394b4e 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/deposit-tree/deposit-tree.ts @@ -76,7 +76,7 @@ export class DepositTree { * @returns {boolean} Returns true if the node was successfully inserted, false otherwise. */ public insert(node: Uint8Array, depositCount: bigint): boolean { - if (depositCount !== this.nodeCount + 1n) { + if (depositCount !== this.nodeCount) { return false; } this.nodeCount++; diff --git a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts index 300ae8e5..752909c0 100644 --- a/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts +++ b/src/contracts/deposits-registry/sanity-checker/integrity-checker/integrity-checker.service.ts @@ -7,6 +7,7 @@ import { VerifiedDepositEventsCache, } from '../../interfaces'; import { DEPOSIT_TREE_STEP_SYNC } from './constants'; +import { toHexString } from 'contracts/deposits-registry/crypto'; @Injectable() export class DepositIntegrityCheckerService { @@ -130,11 +131,11 @@ export class DepositIntegrityCheckerService { 'Problem found while forming deposit tree with event', { depositCount, - depositDataRoot, + depositDataRoot: toHexString(depositDataRoot), blockHash, blockNumber, eventIndex, - depositCountInTree: tree.nodeCount, + depositCountInTree: Number(tree.nodeCount), }, ); From 351e5d75b1578d929e6307cb8e9a8af505ade857 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 19:02:54 +0200 Subject: [PATCH 91/94] fix: e2e --- test/duplicates-v3.e2e-spec.ts | 3 +++ test/duplicates.e2e-spec.ts | 3 +++ test/front-run-v3.e2e-spec.ts | 3 +++ test/front-run.e2e-spec.ts | 3 +++ test/guardian-balance-monitoring.e2e-spec.ts | 3 +++ test/invalid-keys-v3.e2e-spec.ts | 3 +++ test/invalid-keys.e2e-spec.ts | 3 +++ 7 files changed, 21 insertions(+) diff --git a/test/duplicates-v3.e2e-spec.ts b/test/duplicates-v3.e2e-spec.ts index aa81692b..14dc3027 100644 --- a/test/duplicates-v3.e2e-spec.ts +++ b/test/duplicates-v3.e2e-spec.ts @@ -100,6 +100,9 @@ describe('Deposits in case of duplicates', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/duplicates.e2e-spec.ts b/test/duplicates.e2e-spec.ts index 37292f20..2559d300 100644 --- a/test/duplicates.e2e-spec.ts +++ b/test/duplicates.e2e-spec.ts @@ -92,6 +92,9 @@ describe('ganache e2e tests', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/front-run-v3.e2e-spec.ts b/test/front-run-v3.e2e-spec.ts index a03164ab..ad98508e 100644 --- a/test/front-run-v3.e2e-spec.ts +++ b/test/front-run-v3.e2e-spec.ts @@ -142,6 +142,9 @@ describe('ganache e2e tests', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/front-run.e2e-spec.ts b/test/front-run.e2e-spec.ts index 736ee891..e050e7a6 100644 --- a/test/front-run.e2e-spec.ts +++ b/test/front-run.e2e-spec.ts @@ -132,6 +132,9 @@ describe('ganache e2e tests', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/guardian-balance-monitoring.e2e-spec.ts b/test/guardian-balance-monitoring.e2e-spec.ts index ab6498a3..5d3a3418 100644 --- a/test/guardian-balance-monitoring.e2e-spec.ts +++ b/test/guardian-balance-monitoring.e2e-spec.ts @@ -237,6 +237,9 @@ describe('Guardian balance monitoring test', () => { }; const mockDepositCacheMethods = () => { + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/invalid-keys-v3.e2e-spec.ts b/test/invalid-keys-v3.e2e-spec.ts index 8e16ff4f..bf4574af 100644 --- a/test/invalid-keys-v3.e2e-spec.ts +++ b/test/invalid-keys-v3.e2e-spec.ts @@ -132,6 +132,9 @@ describe('ganache e2e tests', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index eb737530..ba8e6f2d 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -125,6 +125,9 @@ describe('ganache e2e tests', () => { .mockImplementation(() => Promise.resolve()); // deposit cache mocks + jest + .spyOn(depositIntegrityCheckerService, 'putEventsToTree') + .mockImplementation(() => Promise.resolve()); jest .spyOn(depositIntegrityCheckerService, 'checkLatestRoot') .mockImplementation(() => Promise.resolve(true)); From b734f0e50dc7f47e15ef4362e2b9b2a7f87cb8d5 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 17 Sep 2024 19:10:50 +0200 Subject: [PATCH 92/94] refactor: naming in test --- .../store/store.service.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/contracts/deposits-registry/store/store.service.spec.ts b/src/contracts/deposits-registry/store/store.service.spec.ts index 99af6b8c..3751639b 100644 --- a/src/contracts/deposits-registry/store/store.service.spec.ts +++ b/src/contracts/deposits-registry/store/store.service.spec.ts @@ -10,8 +10,8 @@ const getEventsDepositCount = async ( dbService: DepositsRegistryStoreService, ) => { const result = await dbService.getEventsCache(); - const remainingDeposits = result.data.map((event) => event.depositCount); - return remainingDeposits; + const expectedDeposits = result.data.map((event) => event.depositCount); + return expectedDeposits; }; describe('dbService', () => { @@ -81,11 +81,11 @@ describe('dbService', () => { await dbService.deleteDepositsGreaterThanNBatch(N); - const remainingDeposits = await getEventsDepositCount(dbService); - expect(remainingDeposits).toEqual( + const expectedDeposits = await getEventsDepositCount(dbService); + expect(expectedDeposits).toEqual( expect.arrayContaining(expectedRemaining), ); - expect(remainingDeposits.length).toBe(expectedRemaining.length); + expect(expectedDeposits.length).toBe(expectedRemaining.length); }, ); }); @@ -123,11 +123,11 @@ describe('dbService', () => { await dbService.clearFromLastValidEvent(); - const remainingDeposits = await getEventsDepositCount(dbService); - expect(remainingDeposits).toEqual( + const expectedDeposits = await getEventsDepositCount(dbService); + expect(expectedDeposits).toEqual( expect.arrayContaining(expectedRemaining), ); - expect(remainingDeposits.length).toBe(expectedRemaining.length); + expect(expectedDeposits.length).toBe(expectedRemaining.length); }, ); }); From 505d905213e9552ead52eeb02e8eac0e85810a1a Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 18 Sep 2024 16:41:13 +0400 Subject: [PATCH 93/94] fix: removed comments --- test/helpers/mockKeysApi.ts | 96 ------------------------------------- 1 file changed, 96 deletions(-) diff --git a/test/helpers/mockKeysApi.ts b/test/helpers/mockKeysApi.ts index f0112ac2..7d2c0ec8 100644 --- a/test/helpers/mockKeysApi.ts +++ b/test/helpers/mockKeysApi.ts @@ -2,50 +2,10 @@ import ethers from 'ethers'; import { KeysApiService } from '../../src/keys-api/keys-api.service'; import { SIMPLE_DVT, NOP_REGISTRY } from './../constants'; -// import { RegistryOperator } from 'keys-api/interfaces/RegistryOperator'; import { SRModule } from 'keys-api/interfaces'; import { ELBlockSnapshot } from 'keys-api/interfaces/ELBlockSnapshot'; import { RegistryKey } from 'keys-api/interfaces/RegistryKey'; -// export const setupMockModules = ( -// currentBlock: ethers.providers.Block, -// keysApiService: KeysApiService, -// mockedOperators: RegistryOperator[], -// mockedDvtOperators: RegistryOperator[], -// unusedKeys: RegistryKey[], -// ) => { -// const curatedModule = mockedModule(currentBlock, currentBlock.hash); -// const sdvtModule = mockedModuleDvt(currentBlock, currentBlock.hash); -// const meta = mockedMeta(currentBlock, currentBlock.hash); - -// mockedKeysApiOperatorsMany( -// keysApiService, -// [ -// { operators: mockedOperators, module: curatedModule }, -// { operators: mockedDvtOperators, module: sdvtModule }, -// ], -// meta, -// ); - -// mockedKeysApiGetAllKeys(keysApiService, unusedKeys, meta); - -// return { curatedModule, sdvtModule, meta }; -// }; - -// export const setupKeysAPI = ( -// block: ethers.providers.Block, -// keysApiService: KeysApiService, -// modules: SRModule[], -// keys: RegistryKey[], -// lastChangedBlockHash = undefined, -// ) => { -// const meta = mockMeta(block, lastChangedBlockHash || block.hash); -// keysApiMockGetAllKeys(keysApiService, keys, meta); -// mod - -// return { curatedModule, sdvtModule, meta }; -// }; - export const mockedModuleCurated: SRModule = { type: 'curated-onchain-v1', id: 1, @@ -90,62 +50,6 @@ export const mockMeta = ( lastChangedBlockHash, }); -// export const mockOperator1 = { -// name: 'Dev team', -// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', -// stakingLimit: 12, -// stoppedValidators: 0, -// totalSigningKeys: 12, -// usedSigningKeys: 0, -// index: 0, -// active: true, -// moduleAddress: NOP_REGISTRY, -// }; - -// export const mockOperator2 = { -// name: 'Dev team', -// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', -// stakingLimit: 12, -// stoppedValidators: 0, -// totalSigningKeys: 12, -// usedSigningKeys: 0, -// index: 1, -// active: true, -// moduleAddress: NOP_REGISTRY, -// }; - -// export const mockedOperators: RegistryOperator[] = [ -// mockOperator1, -// mockOperator2, -// ]; - -// export const mockedDvtOperator: RegistryOperator = { -// name: 'Dev DVT team', -// rewardAddress: '0x6D725DAe055287f913661ee0b79dE6B21F12A459', -// stakingLimit: 12, -// stoppedValidators: 0, -// totalSigningKeys: 12, -// usedSigningKeys: 0, -// index: 0, -// active: true, -// moduleAddress: SIMPLE_DVT, -// }; - -// export const mockedKeysApiOperatorsMany = ( -// keysApiService: KeysApiService, -// data: { operators: RegistryOperator[]; module: SRModule }[], -// mockedMeta: ELBlockSnapshot, -// ) => { -// jest -// .spyOn(keysApiService, 'getOperatorListWithModule') -// .mockImplementation(async () => ({ -// data: data, -// meta: { -// elBlockSnapshot: mockedMeta, -// }, -// })); -// }; - export const keysApiMockGetModules = ( keysApiService: KeysApiService, modules: SRModule[], From e54edd524dda03d3f9e08703f8ff6bdd1d1f6065 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 18 Sep 2024 17:30:52 +0400 Subject: [PATCH 94/94] fix: comment --- test/invalid-keys.e2e-spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/invalid-keys.e2e-spec.ts b/test/invalid-keys.e2e-spec.ts index 10cb0987..791c2b1f 100644 --- a/test/invalid-keys.e2e-spec.ts +++ b/test/invalid-keys.e2e-spec.ts @@ -174,7 +174,6 @@ describe('ganache e2e tests', () => { }, }); - // const { depositData: depositData } = signDeposit(pk, sk, LIDO_WC); const walletAddress = await getWalletAddress(); const keyWithWrongSign = {