From bb6c367e705c60de9432e29f47523acb6531f972 Mon Sep 17 00:00:00 2001 From: Paul Noel Date: Mon, 9 Sep 2024 11:28:30 -0500 Subject: [PATCH] watcher: add findGaps script --- package-lock.json | 466 +++++++++++++++++++- watcher/package.json | 3 + watcher/scripts/findGaps.ts | 518 +++++++++++++++++++++++ watcher/scripts/storeAndPubSignedVAAs.ts | 82 ++++ 4 files changed, 1063 insertions(+), 6 deletions(-) create mode 100644 watcher/scripts/findGaps.ts create mode 100644 watcher/scripts/storeAndPubSignedVAAs.ts diff --git a/package-lock.json b/package-lock.json index d42064e6..9441aa1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3942,6 +3942,17 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -6565,9 +6576,9 @@ } }, "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -11314,6 +11325,20 @@ "follow-redirects": "^1.14.8" } }, + "node_modules/abitype": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.7.1.tgz", + "integrity": "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==", + "peerDependencies": { + "typescript": ">=4.9.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -13048,6 +13073,17 @@ "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz", "integrity": "sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -23094,6 +23130,422 @@ "defaults": "^1.0.3" } }, + "node_modules/web3": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-4.12.1.tgz", + "integrity": "sha512-zIFUPdgo2uG5Vbl7C4KrTv8dmWKN3sGnY/GundbiJzcaJZDxaCyu3a5HXAcgUM1VvvsVb1zaUQAFPceq05/q/Q==", + "dependencies": { + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.2.1", + "web3-eth-contract": "^4.7.0", + "web3-eth-ens": "^4.4.0", + "web3-eth-iban": "^4.0.7", + "web3-eth-personal": "^4.0.8", + "web3-net": "^4.1.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-rpc-providers": "^1.0.0-rc.2", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-core": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.5.1.tgz", + "integrity": "sha512-mFMOO/IWdKsLL1o2whh3oJ0LCG9P3l5c4lpiMoVsVln3QXh/B0Gf8gW3aY8S+Ixm0OHyzFDXJVc2CodxqmI4Gw==", + "dependencies": { + "web3-errors": "^1.3.0", + "web3-eth-accounts": "^4.2.0", + "web3-eth-iban": "^4.0.7", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + }, + "optionalDependencies": { + "web3-providers-ipc": "^4.0.7" + } + }, + "node_modules/web3-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.3.0.tgz", + "integrity": "sha512-j5JkAKCtuVMbY3F5PYXBqg1vWrtF4jcyyMY1rlw8a4PV67AkqlepjGgpzWJZd56Mt+TvHy6DA1F/3Id8LatDSQ==", + "dependencies": { + "web3-types": "^1.7.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.8.2.tgz", + "integrity": "sha512-DLV/fIMG6gBp/B0gv0+G4FzxZ4YCDQsY3lzqqv7avwh3uU7/O27aifCUcFd7Ye+3ixTqCjAvLEl9wYSeyG3zQw==", + "dependencies": { + "setimmediate": "^1.0.5", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.1", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.1.3", + "web3-net": "^4.1.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-abi": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.2.3.tgz", + "integrity": "sha512-rPVwTn0O1CzbtfXwEfIjUP0W5Y7u1OFjugwKpSqJzPQE6+REBg6OELjomTGZBu+GThxHnv0rp15SOxvqp+tyXA==", + "dependencies": { + "abitype": "0.7.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-accounts": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz", + "integrity": "sha512-aOlEZFzqAgKprKs7+DGArU4r9b+ILBjThpeq42aY7LAQcP+mSpsWcQgbIRK3r/n3OwTYZ3aLPk0Ih70O/LwnYA==", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "crc-32": "^1.2.2", + "ethereum-cryptography": "^2.0.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-accounts/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/web3-eth-contract": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz", + "integrity": "sha512-fdStoBOjFyMHwlyJmSUt/BTDL1ATwKGmG3zDXQ/zTKlkkW/F/074ut0Vry4GuwSBg9acMHc0ycOiZx9ZKjNHsw==", + "dependencies": { + "@ethereumjs/rlp": "^5.0.2", + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-contract/node_modules/@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web3-eth-ens": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz", + "integrity": "sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ==", + "dependencies": { + "@adraffy/ens-normalize": "^1.8.8", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.0", + "web3-eth": "^4.8.0", + "web3-eth-contract": "^4.5.0", + "web3-net": "^4.1.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-iban": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz", + "integrity": "sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==", + "dependencies": { + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-personal": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz", + "integrity": "sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==", + "dependencies": { + "web3-core": "^4.3.0", + "web3-eth": "^4.3.1", + "web3-rpc-methods": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-net": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-4.1.0.tgz", + "integrity": "sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA==", + "dependencies": { + "web3-core": "^4.4.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.6.0", + "web3-utils": "^4.3.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-http": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-4.2.0.tgz", + "integrity": "sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ==", + "dependencies": { + "cross-fetch": "^4.0.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-http/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/web3-providers-ipc": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz", + "integrity": "sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g==", + "optional": true, + "dependencies": { + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-ws": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz", + "integrity": "sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw==", + "dependencies": { + "@types/ws": "8.5.3", + "isomorphic-ws": "^5.0.0", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-ws/node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/web3-providers-ws/node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/web3-providers-ws/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/web3-rpc-methods": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz", + "integrity": "sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig==", + "dependencies": { + "web3-core": "^4.4.0", + "web3-types": "^1.6.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-rpc-providers": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.2.tgz", + "integrity": "sha512-ocFIEXcBx/DYQ90HhVepTBUVnL9pGsZw8wyPb1ZINSenwYus9SvcFkjU1Hfvd/fXjuhAv2bUVch9vxvMx1mXAQ==", + "dependencies": { + "web3-errors": "^1.3.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-types": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.7.0.tgz", + "integrity": "sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA==", + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-utils": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-4.3.1.tgz", + "integrity": "sha512-kGwOk8FxOLJ9DQC68yqNQc7AzN+k9YDLaW+ZjlAXs3qORhf8zXk5SxWAAGLbLykMs3vTeB0FTb1Exut4JEYfFA==", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "eventemitter3": "^5.0.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-utils/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/web3-utils/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/web3-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.6.tgz", + "integrity": "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "util": "^0.12.5", + "web3-errors": "^1.2.0", + "web3-types": "^1.6.0", + "zod": "^3.21.4" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-validator/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -23479,8 +23931,8 @@ "@solana/spl-token": "^0.4.6", "@solana/web3.js": "^1.73.0", "@types/bn.js": "^5.1.0", - "@wormhole-foundation/example-liquidity-layer-definitions": "file:./sdk/wormhole-foundation-example-liquidity-layer-definitions-0.0.1.tgz", - "@wormhole-foundation/example-liquidity-layer-evm": "file:./sdk/wormhole-foundation-example-liquidity-layer-evm-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-definitions": "file:sdk/wormhole-foundation-example-liquidity-layer-definitions-0.0.1.tgz", + "@wormhole-foundation/example-liquidity-layer-evm": "file:sdk/wormhole-foundation-example-liquidity-layer-evm-0.0.1.tgz", "@wormhole-foundation/example-liquidity-layer-solana": "file:sdk/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz", "@wormhole-foundation/wormhole-monitor-common": "^0.0.1", "algosdk": "^2.4.0", @@ -23493,6 +23945,7 @@ "knex": "^3.1.0", "near-api-js": "^1.1.0", "ora": "^5.4.1", + "web3": "^4.12.1", "winston": "^3.8.2", "winston-loki": "^6.0.7", "zod": "^3.20.2" @@ -23563,7 +24016,8 @@ }, "watcher/node_modules/@mysten/sui.js": { "version": "0.33.0", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/@mysten/sui.js/-/sui.js-0.33.0.tgz", + "integrity": "sha512-vPswzowPoxYmexN+jKrSQ5uKWv8AoX30c11t8VAODCCXeCziq7Y27TEcYFsQmWJBIMU/hYw75kGj9oorJyXyKw==", "dependencies": { "@mysten/bcs": "0.7.1", "@noble/curves": "^1.0.0", diff --git a/watcher/package.json b/watcher/package.json index 4002f168..4fede65d 100644 --- a/watcher/package.json +++ b/watcher/package.json @@ -19,10 +19,12 @@ "dump-messages": "ts-node scripts/dumpMessages.ts", "dump-vaas": "ts-node scripts/dumpVAAs.ts", "fetch-missing-vaas": "ts-node scripts/fetchMissingVAAs.ts", + "find-gaps": "ts-node scripts/findGaps.ts", "locate-message-gaps": "ts-node scripts/locateMessageGaps.ts", "read-bigtable": "ts-node scripts/readBigtable.ts", "read-firestore": "ts-node scripts/readFirestore.ts", "reconstruct-vaa": "ts-node scripts/reconstructVAA.ts", + "store-and-pub-signed-vaas": "ts-node scripts/storeAndPubSignedVAAs.ts", "update-found-vaas": "ts-node scripts/updateFoundVAAs.ts", "update-rows": "ts-node scripts/updateRows.ts", "watch-ft": "ts-node scripts/watchFt.ts" @@ -52,6 +54,7 @@ "knex": "^3.1.0", "near-api-js": "^1.1.0", "ora": "^5.4.1", + "web3": "^4.12.1", "winston": "^3.8.2", "winston-loki": "^6.0.7", "zod": "^3.20.2" diff --git a/watcher/scripts/findGaps.ts b/watcher/scripts/findGaps.ts new file mode 100644 index 00000000..6677e0a9 --- /dev/null +++ b/watcher/scripts/findGaps.ts @@ -0,0 +1,518 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import { padUint16, sleep, WormholescanRPC } from '@wormhole-foundation/wormhole-monitor-common'; +import * as fs from 'fs'; +import ora from 'ora'; +import { BigtableDatabase } from '../src/databases/BigtableDatabase'; +import { BigtableSignedVAAsResultRow } from '../src/databases/types'; +import { + Chain, + chainToChainId, + chainToPlatform, + contracts, + encoding, + rpc, +} from '@wormhole-foundation/sdk-base'; +import Web3, { core } from 'web3'; +import axios, { AxiosResponse } from 'axios'; +import { + getObjectFields, + JsonRpcProvider, + Connection as SuiConnection, + TransactionBlock, +} from '@mysten/sui.js'; +import { Connection, PublicKey } from '@solana/web3.js'; +// import { DynamicFieldPage, SuiClient } from '@mysten/sui.js/client'; +// import { TransactionBlock } from '@mysten/sui.js/dist/cjs/builder'; + +type EmitterToSequence = Map; +// type ChainToEmitterToSequence = Map; + +const LIMIT = 10000; +const evmEntries: Chain[] = [ + // 'Acala', + // 'Arbitrum', + // 'Aurora', + // 'Avalanche', + // 'Base', + // 'Bsc', + // 'Celo', + // 'Ethereum', + // 'Fantom', + // 'Gnosis', + // 'Karura', + // 'Klaytn', + // 'Moonbeam', + // 'Neon', + // 'Oasis', + // 'Optimism', + // 'Polygon', + // 'Rootstock', + // // 'Sepolia', + // // 'ArbitrumSepolia', + // // 'BaseSepolia', + // // 'OptimismSepolia', + // // 'Holesky', + // // 'PolygonSepolia', + // 'Mantle', + // 'Scroll', + // 'Blast', + // 'Xlayer', + // 'Linea', + // 'Berachain', + // 'Seievm', + // 'Snaxchain', +]; + +const moveEntries: Chain[] = [ + // 'Sui', + // 'Aptos', +]; +const solanaEntries: Chain[] = ['Solana']; + +const allEntries: Chain[] = [...evmEntries, ...moveEntries, ...solanaEntries]; + +(async () => { + try { + const bt = new BigtableDatabase(); + if (!bt.bigtable) { + throw new Error('bigtable is undefined'); + } + const instance = bt.bigtable.instance(bt.instanceId); + const vaaTable = instance.table(bt.signedVAAsTableId); + for (const chainName of allEntries) { + const emitterMap: EmitterToSequence = new Map(); + const missingVAAs: string[] = []; + const chainId = chainToChainId(chainName); + let total = 0; + let log = ora(`Fetching all ${chainName} VAAs...`).start(); + let start = `${padUint16(chainId.toString())}/`; + while (start) { + log.text = `Fetching ${LIMIT}/${total} ${chainName} VAAs starting at ${start}...`; + let vaaRows: BigtableSignedVAAsResultRow[] = ( + await vaaTable.getRows({ + start, + end: `${padUint16(chainId.toString())}/z`, + decode: false, + limit: LIMIT, + }) + )[0] as BigtableSignedVAAsResultRow[]; + start = vaaRows.length === LIMIT ? vaaRows[LIMIT - 1].id : ''; + vaaRows = vaaRows.filter((row) => row.id.toString() !== start.toString()); + total += vaaRows.length; + log.text = `Processing ${total} ${chainName} VAAs...`; + for (const row of vaaRows) { + try { + // console.log(`Processing ${row.id.toString()}`); + const vaaBytes = row.data.info.bytes?.[0].value; + if (vaaBytes) { + // Get the emitter and sequence from the row id + // Check if the emitter is already in the map + // Update the sequence if the new sequence is greater and check for gap + // If there is a gap, log the gap + // Update the map with the new sequence + const r_id = row.id.toString(); + const id: string[] = r_id.split('/'); + const chain = parseInt(id[0]); + if (chain !== chainId) { + start = ''; + break; + } + start = ''; + const emitter = id[1]; + const sequence = parseInt(id[2]); + // console.log(`Processing ${chainName} VAA ${emitter} ${sequence}`); + if (emitterMap.has(emitter)) { + const seq = emitterMap.get(emitter); + // console.log(`Emitter ${emitter} found in map, with seq ${seq} and new sequence ${sequence}`); + if (seq !== undefined && sequence > seq) { + if (sequence - seq > 1) { + console.log( + `Gap detected for emitter ${emitter} between ${seq} and ${sequence}` + ); + // Gap size needs to be <= 100 + if (sequence - seq > 100) { + console.log( + `Gap is too large to record for emitter ${emitter} between ${seq} and ${sequence}` + ); + continue; + } + for (let i = seq + 1; i < sequence; i++) { + missingVAAs.push(`${chainId}/${emitter}/${i}`); + } + } + emitterMap.set(emitter, sequence); + // console.log(`Emitter ${emitter} updated with sequence ${sequence}`); + } else { + console.log( + `Emitter ${emitter} has an old sequence ${sequence}. Expected a number > ${seq}` + ); + } + } else { + emitterMap.set(emitter, sequence); + // console.log(`Emitter ${emitter} added with sequence ${sequence}`); + } + } + } catch (e) { + console.error(e); + } + } + if (start === '') { + break; + } + } + log.succeed(`Processed ${total} ${chainName} VAAs...`); + console.log(`There are ${emitterMap.size} emitters`); + const mapArray = Array.from(emitterMap); + const csvContent = mapArray.map((row) => row.join(',')).join('\n'); + fs.writeFileSync(`emitter-sequence-${chainName}.csv`, csvContent); + // Walk all the emitters and get what should be the next sequence from the core contract + log = ora(`Fetching all VAAs...`).start(); + let i = 1; + const totalEmitters = emitterMap.size; + const coreContract = contracts.coreBridge.get('Mainnet', chainName); + if (!coreContract) { + throw new Error(`Core contract not found for ${chainName}`); + } + const RPC = rpc.rpcAddress('Mainnet', chainName); + if (!RPC) { + throw new Error(`RPC not found for ${chainName}`); + } + for (const [emitter, sequence] of emitterMap) { + log.text = `Processing ${i++}/${totalEmitters} emitter ${emitter}...`; + let nextSeq: number = 0; + if (chainToPlatform(chainName) === 'Evm') { + nextSeq = await abiCall(RPC, coreContract, emitter.slice(-40)); + // Check if any sequences are missing + if (nextSeq > sequence + 1) { + console.log(`Gap detected for emitter ${emitter} between ${sequence} and ${nextSeq}`); + for (let j = sequence + 1; j < nextSeq; j++) { + missingVAAs.push(`${chainId}/${emitter}/${j}`); + } + } + } else if (chainName === 'Sui') { + const vaaKey = `${chainId}/${emitter}/${sequence}`; + nextSeq = await suiCall(RPC, vaaKey, emitter.slice(-40)); + } else if (chainName === 'Aptos') { + const vaaKey = `${chainId}/${emitter}/${sequence}`; + console.log(`Processing ${vaaKey}`); + const localRPC: string = 'https://api.mainnet.aptoslabs.com'; + nextSeq = await aptosCall(localRPC, vaaKey); + } else if (chainName === 'Solana') { + nextSeq = await solanaCall(RPC, emitter); + } else { + console.log(`Chain ${chainName} not supported`); + continue; + } + // Check if any sequences are missing + if (nextSeq > sequence + 1) { + console.log(`Gap detected for emitter ${emitter} between ${sequence} and ${nextSeq}`); + for (let j = sequence + 1; j < nextSeq; j++) { + missingVAAs.push(`${chainId}/${emitter}/${j}`); + } + } + } + log.succeed(`Processed ${totalEmitters} emitters...`); + const missingContent = missingVAAs.join('\n'); + fs.writeFileSync(`missing-vaas-${chainName}.csv`, missingContent); + } + } catch (e) { + console.error(e); + } + process.exit(0); +})(); + +async function abiCall(rpc: string, coreContract: string, emitter: string): Promise { + let done: boolean = false; + let results; + while (!done) { + try { + let web3: Web3 = new Web3(rpc); + const callData = web3.eth.abi.encodeFunctionCall( + { + // this is the snippet from the ABI, you can grab that from the explorer + inputs: [{ internalType: 'address', name: 'emitter', type: 'address' }], + name: 'nextSequence', + outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], + stateMutability: 'view', + type: 'function', + }, + [emitter] // these are the parameters + ); + const payload = { + jsonrpc: '2.0', + method: 'eth_call', + params: [ + { + from: null, + to: coreContract, + data: callData, + }, + 'latest', + ], + id: 1, + }; + results = await axios.post(rpc, payload, { + headers: { + 'User-Agent': 'Mozilla/5.0', + 'Content-Type': 'application/json', + }, + }); + // console.log(results.data); + if (results && results.data && results.data.result) { + return parseInt(results.data.result); + } + } catch (e: any) { + if (e.response && e.response.status && e.response.status === 429) { + // sleep for 1 second and try again + console.log('Rate limited, sleeping for 1 second'); + await sleep(1000); + } else { + console.error('Non-Rate limited error', e); + console.log(`Error = ${e.response.data.error.message}`); + done = true; + } + } + } + return 0; +} + +async function suiCall(rpc: string, vaaKey: string, emitter: string): Promise { + // 1. Get the txHash from wormholescan + // 2. Get the txBlock from Sui + // 3. Get the emitter object + + const url: string = WormholescanRPC['Mainnet'] + 'api/v1/vaas/' + vaaKey; + console.log(`url = ${url}`); + try { + const response = await axios.get(url); + // console.log(response.data); + const txHash = response.data.data.txHash; + console.log(`txHash = ${txHash}`); + + const getTxPayload = { + jsonrpc: '2.0', + method: 'sui_getTransactionBlock', + params: [ + `${txHash}`, + { + showInput: true, + showRawInput: false, + showEffects: false, + showEvents: false, + showObjectChanges: true, + showBalanceChanges: false, + showRawEffects: false, + }, + ], + id: 1, + }; + + const results = await axios.post(rpc, getTxPayload, { + headers: { + 'User-Agent': 'Mozilla/5.0', + 'Content-Type': 'application/json', + }, + }); + // console.log(results.data); + let objectId: string = ''; + let gotEmitterCap: boolean = false; + let gotWHAdapter: boolean = false; + let gotState: boolean = false; + if (results && results.data && results.data.result) { + const changes = results.data.result.objectChanges; + // console.log(JSON.stringify(changes)); + for (const change of changes) { + if (change.type && change.type === 'mutated') { + if (change.owner) { + if (change.owner.Shared && change.owner.Shared.initial_shared_version > 64) { + // console.log( + // `\nFound Shared object with version = ${change.owner.Shared.initial_shared_version}` + // ); + // Grab this object's id + objectId = change.objectId; + if (change.objectType) { + gotEmitterCap = change.objectType.includes('EmitterCap'); + if (gotEmitterCap) break; + gotWHAdapter = change.objectType.includes('wormhole_adapter'); + if (gotWHAdapter) break; + gotState = change.objectType.includes('state::State'); + if (gotState) break; + } + } + } + } + } + if (gotEmitterCap) { + console.log(`\nFound EmitterCap object with id = ${objectId}`); + const objResults = await getObjectFromSui(rpc, objectId); + // console.log('objResults: ' + JSON.stringify(objResults.data)); + if ( + objResults && + objResults.data && + objResults.data.result && + objResults.data.result.data && + objResults.data.result.data.content + ) { + const fields = objResults.data.result.data.content.fields; + // console.log(`Fields = ${JSON.stringify(fields)}`); + if (fields.id && fields.sequence) { + console.log(`\nEmitter ${fields.id.id} has sequence ${fields.sequence}`); + return parseInt(fields.sequence); + } + } + } else if (gotWHAdapter) { + console.log(`\nFound wormhole_adapter object with id = ${objectId}`); + const objResults = await getObjectFromSui(rpc, objectId); + // console.log('objResults: ' + JSON.stringify(objResults.data)); + if ( + objResults && + objResults.data && + objResults.data.result && + objResults.data.result.data && + objResults.data.result.data.content + ) { + const fields = objResults.data.result.data.content.fields; + // console.log(`Fields = ${JSON.stringify(fields)}`); + if (fields.wormhole_emitter && fields.wormhole_emitter.fields) { + const innerFields = fields.wormhole_emitter.fields; + console.log(`\nEmitter ${innerFields.id.id} has sequence ${innerFields.sequence}`); + return parseInt(innerFields.sequence); + } + } + } else if (gotState) { + // console.log(`\nFound state object with id = ${objectId}`); + const objResults = await getObjectFromSui(rpc, objectId); + // console.log('objResults: ' + JSON.stringify(objResults.data)); + if ( + objResults && + objResults.data && + objResults.data.result && + objResults.data.result.data && + objResults.data.result.data.content + ) { + const fields = objResults.data.result.data.content.fields; + // console.log(`Fields = ${JSON.stringify(fields)}`); + if (fields.emitter_cap && fields.emitter_cap.fields) { + const innerFields = fields.emitter_cap.fields; + console.log(`\nEmitter ${innerFields.id.id} has sequence ${innerFields.sequence}`); + return parseInt(innerFields.sequence); + } + } + } + } + } catch (e) { + // console.error(e); + } + + return 0; +} + +async function getObjectFromSui(rpc: string, objectId: string): Promise> { + const objPayload = { + jsonrpc: '2.0', + method: 'sui_getObject', + params: [ + `${objectId}`, + { + showType: false, + showOwner: false, + showPreviousTransaction: false, + showDisplay: false, + showContent: true, + showBcs: false, + showStorageRebate: false, + }, + ], + id: 2, + }; + const objResults = await axios.post(rpc, objPayload, { + headers: { + 'User-Agent': 'Mozilla/5.0', + 'Content-Type': 'application/json', + }, + }); + return objResults; +} + +async function aptosCall(rpc: string, vaaKey: string): Promise { + // 1. Get the txHash from wormholescan + // 2. Get the txBlock from Sui + // 3. Get the emitter object + + const url: string = WormholescanRPC['Mainnet'] + 'api/v1/vaas/' + vaaKey; + // console.log(`url = ${url}`); + try { + const response = await axios.get(url); + // console.log(response.data); + const txHash = response.data.data.txHash; + if (!txHash) { + console.log(`No txHash found for ${vaaKey}`); + return 0; + } + // console.log(`txHash = ${txHash}`); + + const getTxUrl: string = `${rpc}/v1/transactions/by_hash/${txHash}`; + // console.log(`getTxUrl = ${getTxUrl}`); + const results = await axios.get(getTxUrl, { + headers: { + 'User-Agent': 'Mozilla/5.0', + 'Content-Type': 'application/json', + }, + }); + // console.log(results.data); + const changes = results.data.changes; + // Walk the changes + for (const change of changes) { + // console.log(`Change = ${JSON.stringify(change)}`); + if (!change.data || !change.data.data) { + continue; + } + // console.log(`\nChange data = ${JSON.stringify(change.data)}`); + // if ( + // change.data.type && + // (change.data.type.includes('state::State') || change.data.type.includes('sender::State')) + // ) { + const data = change.data.data; + if (data.emitter_cap && data.emitter_cap.sequence) { + console.log( + `\nEmitter ${data.emitter_cap.emitter} has sequence ${data.emitter_cap.sequence}` + ); + return parseInt(data.emitter_cap.sequence); + } + // } + } + } catch (e) { + console.error(e); + } + return 0; +} + +async function solanaCall(rpc: string, emitter: string): Promise { + console.log(`Processing ${emitter}`); + const coreContract = contracts.coreBridge.get('Mainnet', 'Solana'); + if (!coreContract) { + throw new Error(`Core contract not found for Solana`); + } + try { + const sequenceAcct = PublicKey.findProgramAddressSync( + [Buffer.from('Sequence'), new PublicKey(Buffer.from(emitter, 'hex')).toBytes()], + new PublicKey(coreContract) + )[0]; + console.log(`Sequence account = ${sequenceAcct.toString()}`); + const connection = new Connection(rpc); + const response = await connection.getAccountInfo(sequenceAcct); + // console.log(`Response = ${JSON.stringify(response)}`); + if (!response) { + console.log(`No sequence account found for ${emitter}`); + return 0; + } + const sequence = response.data.readBigUInt64LE(0); + console.log(`Sequence = ${sequence}`); + + return parseInt(sequence.toString()); + } catch (e) { + console.error(e); + } + return 0; +} diff --git a/watcher/scripts/storeAndPubSignedVAAs.ts b/watcher/scripts/storeAndPubSignedVAAs.ts new file mode 100644 index 00000000..1a0f4762 --- /dev/null +++ b/watcher/scripts/storeAndPubSignedVAAs.ts @@ -0,0 +1,82 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); +import { createReadStream } from 'fs'; +import { createInterface } from 'readline'; +import { assertEnvironmentVariable } from '@wormhole-foundation/wormhole-monitor-common/src/utils'; +import { BigtableDatabase } from '../src/databases/BigtableDatabase'; +import ora from 'ora'; +import { makeSignedVAAsRowKey } from '../src/databases/utils'; +import { ChainId } from '@wormhole-foundation/sdk-base'; +import { getSignedVAA } from '../src/utils/getSignedVAA'; + +// This script writes all VAAs from a csv file compatible with the guardian `sign-existing-vaas-csv` admin command to bigtable +// This script will: +// - Read a file containing VAAIds of missing VAAs in the format of `chainId/emitter/sequence` +// - It will call fetch the VAAs from the guardians +// - It will write the VAAs to the bigtable +// - It will publish the VAA keys to the signed-vaa PubSub topic + +const CHUNK_SIZE = 10000; + +interface SignedVAAsRowDefault { + key: string; + data: { + info: { + bytes: { value: Buffer; timestamp: '0' }; + }; + }; +} + +(async () => { + try { + const vaaIdFilename = assertEnvironmentVariable('VAA_ID_FILE'); + + const bt = new BigtableDatabase(); + if (!bt.bigtable) { + throw new Error('bigtable is undefined'); + } + const fileStream = createReadStream(vaaIdFilename, { encoding: 'utf8' }); + + const rl = createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + // Note: we use the crlfDelay option to recognize all instances of CR LF + // ('\r\n') in input.txt as a single line break. + + let rows: SignedVAAsRowDefault[] = []; + let log = ora('Getting VAAs from wormholescan...').start(); + for await (const vaaId of rl) { + log.text = `Getting VAA ${vaaId} from guardians...`; + const [chainIdStr, emitter, sequence] = vaaId.split('/'); + const chainId = parseInt(chainIdStr); + // Fetch the VAA from the guardians + const vaa = await getSignedVAA(chainId, emitter, sequence); + if (!vaa) { + console.error(`Failed to get VAA for ${vaaId}`); + continue; + } + const rowKey = makeSignedVAAsRowKey(chainId, emitter, sequence); + rows.push({ + key: rowKey, + data: { + info: { + bytes: { value: vaa, timestamp: '0' }, + }, + }, + }); + } + log.succeed(`Retrieved ${rows.length} VAAs`); + // Next, write the VAAs to the bigtable + log = ora(`Writing ${rows.length} VAAs to bigtable...`).start(); + await bt.storeSignedVAAs(rows); + log.succeed(`Wrote ${rows.length} VAAs to bigtable`); + log = ora(`Publishing ${rows.length} VAAs to to the signed-vaa topic...`).start(); + // Finally, publish the VAA keys to the signed-vaa PubSub topic + await bt.publishSignedVAAs(rows.map((r) => r.key)); + log.succeed(`Published ${rows.length} VAAs to to the signed-vaa topic...`); + } catch (e) { + console.error(e); + } + process.exit(0); +})();