diff --git a/CHANGELOG.md b/CHANGELOG.md index 140c2b61b..90b7709c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ The changelog format is based on [Keep a Changelog](https://keepachangelog.com/e ## [1.0.7] - NEXT **Milestone**: Mainnet(1.0.1.0) +- Added multi voting key file support. +- Added `updateVotingKeys` command. + | Package | Version | Link | | ---------------- | ------- | ------------------------------------------------------------------ | | Symbol Bootstrap | v1.0.3 | [symbol-bootstrap](https://www.npmjs.com/package/symbol-bootstrap) | diff --git a/README.md b/README.md index 741876407..1bfff8368 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ General users should install this tool like any other node module. * [`symbol-bootstrap run`](docs/run.md) - It boots the network via docker using the generated `docker-compose.yml` file and configuration. The config and compose methods/commands need to be called before this method. This is just a wrapper for the `docker-compose up` bash call. * [`symbol-bootstrap start`](docs/start.md) - Single command that aggregates config, compose and run in one line! * [`symbol-bootstrap stop`](docs/stop.md) - It stops the docker-compose network if running (symbol-bootstrap started with --detached). This is just a wrapper for the `docker-compose down` bash call. +* [`symbol-bootstrap updateVotingKeys`](docs/updateVotingKeys.md) - It updates the voting files containing the voting keys when required. * [`symbol-bootstrap verify`](docs/verify.md) - It tests the installed software in the current computer reporting if there is any missing dependency, invalid version, or software related issue. diff --git a/cmds/link-testnet-supernode-external-node.sh b/cmds/link-testnet-supernode-external-node.sh index c50203e61..5cbf55758 100755 --- a/cmds/link-testnet-supernode-external-node.sh +++ b/cmds/link-testnet-supernode-external-node.sh @@ -1,2 +1,2 @@ #!/bin/bash -symbol-bootstrap link -t target/testnet-supernode --useKnownRestGateways $1 $2 $3 $4 $5 $6 $7 +symbol-bootstrap link -t target/testnet-supernode --useKnownRestGateways --password 1111 $1 $2 $3 $4 $5 $6 $7 diff --git a/cmds/stop-testnet-supernode.sh b/cmds/stop-testnet-supernode.sh new file mode 100755 index 000000000..89faa5781 --- /dev/null +++ b/cmds/stop-testnet-supernode.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +# docker rm -f $(docker ps -aq) +symbol-bootstrap stop -t target/testnet-supernode diff --git a/cmds/upgradeVotingKeys-testnet-supernode.sh b/cmds/upgradeVotingKeys-testnet-supernode.sh new file mode 100755 index 000000000..d46a00759 --- /dev/null +++ b/cmds/upgradeVotingKeys-testnet-supernode.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -e +symbol-bootstrap upgradeVotingKeys -t target/testnet-supernode $1 $2 $3 diff --git a/docs/config.md b/docs/config.md index cbfb6a493..293434eec 100644 --- a/docs/config.md +++ b/docs/config.md @@ -41,9 +41,6 @@ OPTIONS can be provided in the command line (--password=XXXX) or disabled in the command line (--noPassword). - --pullImages It pulls the utility images from DockerHub when running the configuration. - It only affects alpha/dev docker images. - --report It generates reStructuredText (.rst) reports describing the configuration of each node. diff --git a/docs/start.md b/docs/start.md index 88164e83d..2db170e70 100644 --- a/docs/start.md +++ b/docs/start.md @@ -66,7 +66,7 @@ OPTIONS line (--noPassword). --pullImages - It pulls the utility images from DockerHub when running the configuration. It only affects alpha/dev docker images. + It pulls the images from DockerHub when running the configuration. It only affects alpha/dev docker images. --report It generates reStructuredText (.rst) reports describing the configuration of each node. diff --git a/docs/updateVotingKeys.md b/docs/updateVotingKeys.md new file mode 100644 index 000000000..ff5c96a60 --- /dev/null +++ b/docs/updateVotingKeys.md @@ -0,0 +1,47 @@ +`symbol-bootstrap updateVotingKeys` +=================================== + +It updates the voting files containing the voting keys when required. + +If the node's current voting file has an end epoch close to the current network epoch, this command will create a new 'private_key_treeX.dat' that continues the current file. + +By default, bootstrap creates a new voting file once the current file reaches its last month. The current network epoch is resolved from the network or you can provide it with the `finalizationEpoch` param. + +When a new voting file is created, Bootstrap will advise running the `link` command again. + +* [`symbol-bootstrap updateVotingKeys`](#symbol-bootstrap-updatevotingkeys) + +## `symbol-bootstrap updateVotingKeys` + +It updates the voting files containing the voting keys when required. + +``` +USAGE + $ symbol-bootstrap updateVotingKeys + +OPTIONS + -h, --help It shows the help of this command. + + -t, --target=target [default: target] The target folder where the symbol-bootstrap network is + generated + + -u, --user=user [default: current] User used to run docker images when creating the the voting + key files. "current" means the current user. + + --finalizationEpoch=finalizationEpoch The network's finalization epoch. It can be retrieved from the /chain/info rest + endpoint. If not provided, the bootstrap known epoch is used. + +DESCRIPTION + If the node's current voting file has an end epoch close to the current network epoch, this command will create a new + 'private_key_treeX.dat' that continues the current file. + + By default, bootstrap creates a new voting file once the current file reaches its last month. The current network + epoch is resolved from the network or you can provide it with the `finalizationEpoch` param. + + When a new voting file is created, Bootstrap will advise running the `link` command again. + +EXAMPLE + $ symbol-bootstrap updateVotingKeys +``` + +_See code: [src/commands/updateVotingKeys.ts](https://github.com/nemtech/symbol-bootstrap/blob/v1.0.7/src/commands/updateVotingKeys.ts)_ diff --git a/package-lock.json b/package-lock.json index d6eb21a2d..0cebf68fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3919,6 +3919,11 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, + "noble-ed25519": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/noble-ed25519/-/noble-ed25519-1.0.3.tgz", + "integrity": "sha512-6pOngnpa/GVYURFvE7igoRKm3RrNwQykVLjSCjTvmUdAyOokes75trVDYoi+CGwF8/jh9hWl2zF4ix9OlB9OIQ==" + }, "nock": { "version": "13.0.11", "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.11.tgz", diff --git a/package.json b/package.json index 827e51a35..bbb0e566d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "js-yaml": "^3.14.0", "lodash": "^4.17.21", "memorystream": "^0.3.1", + "noble-ed25519": "^1.0.3", "node-forge": "^0.10.0", "rxjs": "^6.6.3", "semver": "^7.3.5", @@ -100,8 +101,8 @@ "posttest": "eslint src/ test/ --ext .ts", "oclif-doc": "oclif-dev manifest && oclif-dev readme --multi", "prepack": "shx rm -rf lib && tsc -b && npm run oclif-doc", - "test": "nyc --reporter=lcov --extension .ts mocha -r ts-node/register --timeout 600000 --forbid-only \"test/**/*.test.ts\"", - "e2e": "nyc --reporter=lcov --extension .ts mocha -r ts-node/register --timeout 600000 --forbid-only \"test/**/*.e2e.ts\"", + "test": "nyc --reporter=lcov --extension .ts mocha -r ts-node/register --timeout 900000 --forbid-only \"test/**/*.test.ts\"", + "e2e": "nyc --reporter=lcov --extension .ts mocha -r ts-node/register --timeout 900000 --forbid-only \"test/**/*.e2e.ts\"", "coveralls-report": "cat ./coverage/lcov.info | coveralls", "version": "echo $npm_package_version", "install-cli": "npm pack && npm i -g", diff --git a/presets/bootstrap/network.yml b/presets/bootstrap/network.yml index 445f96946..e99b05c17 100644 --- a/presets/bootstrap/network.yml +++ b/presets/bootstrap/network.yml @@ -11,6 +11,9 @@ harvestingName: 'harvest' explorerUrl: http://localhost:90/ faucetUrl: http://localhost:100/ beneficiaryAddress: '' +votingKeyDesiredLifetime: 720 +votingKeyDesiredFutureLifetime: 120 +lastKnownNetworkEpoch: 1 restExtensions: 'accountLink, aggregate, lockHash, lockSecret, mosaic, metadata, multisig, namespace, receipts, restrictions, transfer' nemesis: mosaics: diff --git a/presets/mainnet/network.yml b/presets/mainnet/network.yml index 4f571ad1e..07ce09426 100644 --- a/presets/mainnet/network.yml +++ b/presets/mainnet/network.yml @@ -1,4 +1,4 @@ -assemblies: api, dual, peer +assemblies: 'api, dual, peer' baseNamespace: symbol batchVerificationRandomSource: /dev/urandom currencyMosaicId: 6BED913FA20223F8 @@ -18,7 +18,8 @@ namespaceRentalFeeSinkAddress: NBDTBUD6R32ZYJWDEWLJM4YMOX3OOILHGDUMTSA mosaicRentalFeeSinkAddress: NC733XE7DF46Q7QYLIIZBBSCJN2BEEP5FQ6PAYA nemesisSignerPublicKey: BE0B4CF546B7B4F4BBFCFF9F574FDA527C07A53D3FC76F8BB7DB746F8E8E0A9F networkType: 104 -rewardProgramCaFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJZVENDQVJNQ0ZHVWJMZ1FuUEU4azc2VUczNWNGME9yNWh6M1RNQVVHQXl0bGNEQlRNUm93R0FZRFZRUUsKREJGT1JVMGdSM0p2ZFhBZ1RHbHRhWFJsWkRFYk1Ca0dBMVVFQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dApNUmd3RmdZRFZRUUxEQTlTWlhkaGNtUnpJRkJ5YjJkeVlXMHdIaGNOTWpFd05ESTJNVFV4TlRVMFdoY05OREV3Ck5ESXhNVFV4TlRVMFdqQlRNUm93R0FZRFZRUUtEQkZPUlUwZ1IzSnZkWEFnVEdsdGFYUmxaREViTUJrR0ExVUUKQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dE1SZ3dGZ1lEVlFRTERBOVNaWGRoY21SeklGQnliMmR5WVcwdwpLakFGQmdNclpYQURJUUNGMEJ5NENlTTV3MWxoZWU3YVlzb0YvbEJMT29kZDI0ZWhqa3k0NnV4djREQUZCZ01yClpYQURRUURneUhSeU1zbVpCV3pFZWlDczJWMVZ1dHFaNWxzZ2tWN0w4K2dxZ3NQTmpCajRlaUpWaDdHUWV0SEcKZXlFaWZoUXFwdnpqWmQycW9MWUNrYWV3TUNRRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== +rewardProgramCaFile: >- + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJZVENDQVJNQ0ZHVWJMZ1FuUEU4azc2VUczNWNGME9yNWh6M1RNQVVHQXl0bGNEQlRNUm93R0FZRFZRUUsKREJGT1JVMGdSM0p2ZFhBZ1RHbHRhWFJsWkRFYk1Ca0dBMVVFQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dApNUmd3RmdZRFZRUUxEQTlTWlhkaGNtUnpJRkJ5YjJkeVlXMHdIaGNOTWpFd05ESTJNVFV4TlRVMFdoY05OREV3Ck5ESXhNVFV4TlRVMFdqQlRNUm93R0FZRFZRUUtEQkZPUlUwZ1IzSnZkWEFnVEdsdGFYUmxaREViTUJrR0ExVUUKQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dE1SZ3dGZ1lEVlFRTERBOVNaWGRoY21SeklGQnliMmR5WVcwdwpLakFGQmdNclpYQURJUUNGMEJ5NENlTTV3MWxoZWU3YVlzb0YvbEJMT29kZDI0ZWhqa3k0NnV4djREQUZCZ01yClpYQURRUURneUhSeU1zbVpCV3pFZWlDczJWMVZ1dHFaNWxzZ2tWN0w4K2dxZ3NQTmpCajRlaUpWaDdHUWV0SEcKZXlFaWZoUXFwdnpqWmQycW9MWUNrYWV3TUNRRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== rewardProgramEnrollmentAddress: NDG2F6IHON7EDOXZCHSTSJ2YMUHDFXAQ2EUZHFA rewardProgramControllerApiUrl: 'http://node-monitoring.symbolblockchain.io:7890' totalChainImportance: 7842928625000000 @@ -27,6 +28,9 @@ importanceGrouping: 720 votingSetGrouping: 1440 minVotingKeyLifetime: 112 maxVotingKeyLifetime: 360 +votingKeyDesiredLifetime: 360 +votingKeyDesiredFutureLifetime: 60 +lastKnownNetworkEpoch: 181 stepDuration: 5m maxBlockFutureTime: 300ms maxAccountRestrictionValues: 100 @@ -99,707 +103,807 @@ knownRestGateways: - 'http://ngl-api-601.symbolblockchain.io:3000' knownPeers: api-node: - - publicKey: C7BEA9036ECFA79CB081184CFA0E524E7D567A5127C55360D9FF1D2FC1AC4FDD - endpoint: - host: ngl-dual-001.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-001 - roles: 'Api,Peer,Voting' - - publicKey: CE0AC3FC879B190220A95A67CAC855251EA599F11FA05A77CC02BC32F8228610 - endpoint: - host: ngl-dual-002.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-002 - roles: 'Api,Peer,Voting' - - publicKey: B36DE7C3B39B225FEF7FC37D40863A2C9AD90EAFDFD79C630BBFF3883CABD929 - endpoint: - host: ngl-dual-003.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-003 - roles: 'Api,Peer,Voting' - - publicKey: 86C31F2136FB5EB07147E883ACECB891D2D4734E363B26033B1230D8ACF8BBBE - endpoint: - host: ngl-dual-004.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-004 - roles: 'Api,Peer,Voting' - - publicKey: 6E81088A2E2504C4F91E3DC9C2517FAB99574D71EF5D9F74031FB394CCA7AF3B - endpoint: - host: ngl-dual-101.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-101 - roles: 'Api,Peer,Voting' - - publicKey: D1BD3577079DD8CFA5073615B7053712C7B4D4B12899C31D1FB34219145298A2 - endpoint: - host: ngl-dual-102.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-102 - roles: 'Api,Peer,Voting' - - publicKey: DCC6C941EE95967B47433EA3241D53594364A34DB33A9BDC0D668C457002E0F1 - endpoint: - host: ngl-dual-103.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-103 - roles: 'Api,Peer,Voting' - - publicKey: 3A98603E293DCE7CBF5EE860A00670E3025D3E880D3CC339C926217C36FE6436 - endpoint: - host: ngl-dual-104.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-104 - roles: 'Api,Peer,Voting' - - publicKey: 7E1101CFD5E64D6CA4A55B70E24412365BD1658E767F23AF9E9777410C116D89 - endpoint: - host: ngl-dual-201.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-201 - roles: 'Api,Peer,Voting' - - publicKey: 3820C4CFB4BA94F62A982991CB2618EC5E1CFF2C11AD34EFA2254F4305013C1D - endpoint: - host: ngl-dual-202.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-202 - roles: 'Api,Peer,Voting' - - publicKey: 256AA91283F23168C538F3155DEE946A2B0E7207FBDC369E5C5415F7EE1DE592 - endpoint: - host: ngl-dual-203.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-203 - roles: 'Api,Peer,Voting' - - publicKey: 6830127294F7C50FE91625076A0D1CCE910A084B032252CDD005C4148ED11A3D - endpoint: - host: ngl-dual-204.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-204 - roles: 'Api,Peer,Voting' - - publicKey: 92D05B749D6300C4CDCD13A58BF37B7C82B471AB2DFD1A7A6E8C1584620594CA - endpoint: - host: ngl-dual-301.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-301 - roles: 'Api,Peer,Voting' - - publicKey: 5446DA4D21D5809A3C8492C9083743F4349690A21CB5FF0D6621CC78A6FEE979 - endpoint: - host: ngl-dual-302.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-302 - roles: 'Api,Peer,Voting' - - publicKey: 4C48C2A708CC1857405C9C48C4D7A69AA5F0FBB7FB8323C967E1BC644A4417E9 - endpoint: - host: ngl-dual-303.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-303 - roles: 'Api,Peer,Voting' - - publicKey: DEC3292B6ABFAC6687C37FA8963B84715C6E736F6CA27DD50249638D9DF967B1 - endpoint: - host: ngl-dual-304.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-304 - roles: 'Api,Peer,Voting' - - publicKey: 9D50238B795818044CB330DC5FBA485C7B20EBD38E1A7DBE248555AD70AB4F19 - endpoint: - host: ngl-dual-401.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-401 - roles: 'Api,Peer,Voting' - - publicKey: 474C5CD5F0B24BCD769DBE99B37C80F4D60B89E28946E126BE23E1EFD5AAA369 - endpoint: - host: ngl-dual-402.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-402 - roles: 'Api,Peer,Voting' - - publicKey: EF1209DC3C42B6450BEF658D404252DF2E26784CECD35FAEB19D929AE030A198 - endpoint: - host: ngl-dual-403.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-403 - roles: 'Api,Peer,Voting' - - publicKey: E335B45C9FA2666121D81ACDC8007DF0C958D48A0E1ADD34D22894D85B3EA3BC - endpoint: - host: ngl-dual-404.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-404 - roles: 'Api,Peer,Voting' - - publicKey: 606BF668BBD9EA1AFCD9E37A2C18C05BE39CC00752AAD99A0E22A79071CCAA7D - endpoint: - host: ngl-dual-501.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-501 - roles: 'Api,Peer,Voting' - - publicKey: 428F68595322EADDCC0722F024FB0FAD7D35F40C50F1E99AE8FBC8B7EC68E7F2 - endpoint: - host: ngl-dual-502.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-502 - roles: 'Api,Peer,Voting' - - publicKey: C5B4653E6EE56B7E9C1F87D4F0C712B00378D2828CF7AC73F6BE19F8B1B04B4B - endpoint: - host: ngl-dual-503.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-503 - roles: 'Api,Peer,Voting' - - publicKey: E74C655E5A4E72E13EE86B8AE7051EEAAF9AD871D5750A04AB360A3FEFD03440 - endpoint: - host: ngl-dual-504.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-504 - roles: 'Api,Peer,Voting' - - publicKey: 90009F2C2D396A6B788D6DBAB8F075CB20549A50BBA5259D382618FD86F1419A - endpoint: - host: ngl-dual-601.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-601 - roles: 'Api,Peer,Voting' - - publicKey: E0AB078AC2F363DE0D6823D2A9B5229C092FFB8CB2735D72BE63B8AF94367CB6 - endpoint: - host: ngl-dual-602.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-602 - roles: 'Api,Peer,Voting' - - publicKey: F8C70778B37A989480969CB44F509509D1372049E821CA6F0A759FC50E03ADBF - endpoint: - host: ngl-dual-603.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-603 - roles: 'Api,Peer,Voting' - - publicKey: 9F62586518D1707CA57004EF38135509EAE9E9D2933A73041B6171EC0B3F50DF - endpoint: - host: ngl-dual-604.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-604 - roles: 'Api,Peer,Voting' - - publicKey: BA3E425DA3D32458F8A36C8FCBD6DEDBF5E7A9D8E56A7962EA55AD5125C425B1 - endpoint: - host: ngl-dual-005.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-005 - roles: 'Api,Peer' - - publicKey: 3FDFCB4F510E81A4A44B044298974DAA34F3B6FC9D24B6D5CE90FFA933C11F51 - endpoint: - host: ngl-dual-006.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-006 - roles: 'Api,Peer' - - publicKey: 9266C64CDEE62F2AF54806F0CBF2A53D9144CB02AA10B7F7DDEAEAF483E87591 - endpoint: - host: ngl-dual-105.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-105 - roles: 'Api,Peer' - - publicKey: AC63FD2AAA53B73B4E515F73077522925C8502BFC13B073A520B4BA2F3026BC8 - endpoint: - host: ngl-dual-106.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-106 - roles: 'Api,Peer' - - publicKey: 7811A94C2BF3CE9E7E1933DF9E9D0C3A7C09DAEF5A3CAC23960B252BDB8D52D3 - endpoint: - host: ngl-dual-205.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-205 - roles: 'Api,Peer' - - publicKey: 02A55829A1A10542C460A87C4D2C39D03DCCA3F53692B5095C424CCDC21E64A6 - endpoint: - host: ngl-dual-206.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-206 - roles: 'Api,Peer' - - publicKey: 36CAF133D574DB99B4392D76959488D5B91BD71545039DC80249CFD1FC536795 - endpoint: - host: ngl-dual-305.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-305 - roles: 'Api,Peer' - - publicKey: 9B302ABCE3CD5783D79AD522B1769DA9AAA10A69E1930B998C2675AA105356FA - endpoint: - host: ngl-dual-306.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-306 - roles: 'Api,Peer' - - publicKey: 4C85111B10EFB6A25FB77C8A2675C5A92DA5FF36686C4CCE24F887B16342CC95 - endpoint: - host: ngl-dual-405.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-405 - roles: 'Api,Peer' - - publicKey: 0AF73C5CAC76624DB9B51560820F8DFC88120994143C6DB344A035EBFF707A2F - endpoint: - host: ngl-dual-406.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-406 - roles: 'Api,Peer' - - publicKey: E060CA383C533273DB4B3CE7AD964E0E3A2B17018B19ABF830590D5FB9338B29 - endpoint: - host: ngl-dual-505.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-505 - roles: 'Api,Peer' - - publicKey: B1D2AA01E51A81E84CBB2919C0E6AFBBC1383A2760370E60A93C335245A6CB9C - endpoint: - host: ngl-dual-506.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-506 - roles: 'Api,Peer' - - publicKey: 4F014A430E69EFC422AD8B440FA4D312CBDE9803BB857A0736F38E62CE4CF571 - endpoint: - host: ngl-dual-605.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-605 - roles: 'Api,Peer' - - publicKey: E32504B5590B9927CA5509FAA0C0CA63DB3479095C2DB73D0150FB862AA0E1FC - endpoint: - host: ngl-dual-606.symbolblockchain.io - port: 7900 - metadata: - name: ngl-dual-606 - roles: 'Api,Peer' - - publicKey: 47AC3041A5CA98EEA123C64CB7420ADA7F45355CF1993ECA8639891536958011 - endpoint: - host: ngl-api-001.symbolblockchain.io - port: 7900 - metadata: - name: ngl-api-001 - roles: Api - - publicKey: FE0186777D17DD57CAE5E3D9531EC8E70F1F452ED777910E96A9F44D889D9684 - endpoint: - host: ngl-api-301.symbolblockchain.io - port: 7900 - metadata: - name: ngl-api-301 - roles: Api - - publicKey: 642197D9D5B283252BFCBC2FEF8B2EE1A7B3383DCD964D159CBF135CA31DC22B - endpoint: - host: ngl-api-401.symbolblockchain.io - port: 7900 - metadata: - name: ngl-api-401 - roles: Api - - publicKey: E1640F016401E1A8644614B1707D878992DB01B480B55AEDE0EA1A986067ED79 - endpoint: - host: ngl-api-501.symbolblockchain.io - port: 7900 - metadata: - name: ngl-api-501 - roles: Api - - publicKey: 0282991F51E91F8994F1B3FD4A7D1CDE39F6B828A5D419150D6E07925CFFD27C - endpoint: - host: ngl-api-601.symbolblockchain.io - port: 7900 - metadata: - name: ngl-api-601 - roles: Api + - + publicKey: C7BEA9036ECFA79CB081184CFA0E524E7D567A5127C55360D9FF1D2FC1AC4FDD + endpoint: + host: ngl-dual-001.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-001 + roles: 'Api,Peer,Voting' + - + publicKey: CE0AC3FC879B190220A95A67CAC855251EA599F11FA05A77CC02BC32F8228610 + endpoint: + host: ngl-dual-002.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-002 + roles: 'Api,Peer,Voting' + - + publicKey: B36DE7C3B39B225FEF7FC37D40863A2C9AD90EAFDFD79C630BBFF3883CABD929 + endpoint: + host: ngl-dual-003.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-003 + roles: 'Api,Peer,Voting' + - + publicKey: 86C31F2136FB5EB07147E883ACECB891D2D4734E363B26033B1230D8ACF8BBBE + endpoint: + host: ngl-dual-004.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-004 + roles: 'Api,Peer,Voting' + - + publicKey: 6E81088A2E2504C4F91E3DC9C2517FAB99574D71EF5D9F74031FB394CCA7AF3B + endpoint: + host: ngl-dual-101.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-101 + roles: 'Api,Peer,Voting' + - + publicKey: D1BD3577079DD8CFA5073615B7053712C7B4D4B12899C31D1FB34219145298A2 + endpoint: + host: ngl-dual-102.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-102 + roles: 'Api,Peer,Voting' + - + publicKey: DCC6C941EE95967B47433EA3241D53594364A34DB33A9BDC0D668C457002E0F1 + endpoint: + host: ngl-dual-103.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-103 + roles: 'Api,Peer,Voting' + - + publicKey: 3A98603E293DCE7CBF5EE860A00670E3025D3E880D3CC339C926217C36FE6436 + endpoint: + host: ngl-dual-104.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-104 + roles: 'Api,Peer,Voting' + - + publicKey: 7E1101CFD5E64D6CA4A55B70E24412365BD1658E767F23AF9E9777410C116D89 + endpoint: + host: ngl-dual-201.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-201 + roles: 'Api,Peer,Voting' + - + publicKey: 3820C4CFB4BA94F62A982991CB2618EC5E1CFF2C11AD34EFA2254F4305013C1D + endpoint: + host: ngl-dual-202.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-202 + roles: 'Api,Peer,Voting' + - + publicKey: 256AA91283F23168C538F3155DEE946A2B0E7207FBDC369E5C5415F7EE1DE592 + endpoint: + host: ngl-dual-203.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-203 + roles: 'Api,Peer,Voting' + - + publicKey: 6830127294F7C50FE91625076A0D1CCE910A084B032252CDD005C4148ED11A3D + endpoint: + host: ngl-dual-204.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-204 + roles: 'Api,Peer,Voting' + - + publicKey: 92D05B749D6300C4CDCD13A58BF37B7C82B471AB2DFD1A7A6E8C1584620594CA + endpoint: + host: ngl-dual-301.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-301 + roles: 'Api,Peer,Voting' + - + publicKey: 5446DA4D21D5809A3C8492C9083743F4349690A21CB5FF0D6621CC78A6FEE979 + endpoint: + host: ngl-dual-302.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-302 + roles: 'Api,Peer,Voting' + - + publicKey: 4C48C2A708CC1857405C9C48C4D7A69AA5F0FBB7FB8323C967E1BC644A4417E9 + endpoint: + host: ngl-dual-303.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-303 + roles: 'Api,Peer,Voting' + - + publicKey: DEC3292B6ABFAC6687C37FA8963B84715C6E736F6CA27DD50249638D9DF967B1 + endpoint: + host: ngl-dual-304.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-304 + roles: 'Api,Peer,Voting' + - + publicKey: 9D50238B795818044CB330DC5FBA485C7B20EBD38E1A7DBE248555AD70AB4F19 + endpoint: + host: ngl-dual-401.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-401 + roles: 'Api,Peer,Voting' + - + publicKey: 474C5CD5F0B24BCD769DBE99B37C80F4D60B89E28946E126BE23E1EFD5AAA369 + endpoint: + host: ngl-dual-402.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-402 + roles: 'Api,Peer,Voting' + - + publicKey: EF1209DC3C42B6450BEF658D404252DF2E26784CECD35FAEB19D929AE030A198 + endpoint: + host: ngl-dual-403.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-403 + roles: 'Api,Peer,Voting' + - + publicKey: E335B45C9FA2666121D81ACDC8007DF0C958D48A0E1ADD34D22894D85B3EA3BC + endpoint: + host: ngl-dual-404.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-404 + roles: 'Api,Peer,Voting' + - + publicKey: 606BF668BBD9EA1AFCD9E37A2C18C05BE39CC00752AAD99A0E22A79071CCAA7D + endpoint: + host: ngl-dual-501.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-501 + roles: 'Api,Peer,Voting' + - + publicKey: 428F68595322EADDCC0722F024FB0FAD7D35F40C50F1E99AE8FBC8B7EC68E7F2 + endpoint: + host: ngl-dual-502.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-502 + roles: 'Api,Peer,Voting' + - + publicKey: C5B4653E6EE56B7E9C1F87D4F0C712B00378D2828CF7AC73F6BE19F8B1B04B4B + endpoint: + host: ngl-dual-503.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-503 + roles: 'Api,Peer,Voting' + - + publicKey: E74C655E5A4E72E13EE86B8AE7051EEAAF9AD871D5750A04AB360A3FEFD03440 + endpoint: + host: ngl-dual-504.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-504 + roles: 'Api,Peer,Voting' + - + publicKey: 90009F2C2D396A6B788D6DBAB8F075CB20549A50BBA5259D382618FD86F1419A + endpoint: + host: ngl-dual-601.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-601 + roles: 'Api,Peer,Voting' + - + publicKey: E0AB078AC2F363DE0D6823D2A9B5229C092FFB8CB2735D72BE63B8AF94367CB6 + endpoint: + host: ngl-dual-602.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-602 + roles: 'Api,Peer,Voting' + - + publicKey: F8C70778B37A989480969CB44F509509D1372049E821CA6F0A759FC50E03ADBF + endpoint: + host: ngl-dual-603.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-603 + roles: 'Api,Peer,Voting' + - + publicKey: 9F62586518D1707CA57004EF38135509EAE9E9D2933A73041B6171EC0B3F50DF + endpoint: + host: ngl-dual-604.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-604 + roles: 'Api,Peer,Voting' + - + publicKey: BA3E425DA3D32458F8A36C8FCBD6DEDBF5E7A9D8E56A7962EA55AD5125C425B1 + endpoint: + host: ngl-dual-005.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-005 + roles: 'Api,Peer' + - + publicKey: 3FDFCB4F510E81A4A44B044298974DAA34F3B6FC9D24B6D5CE90FFA933C11F51 + endpoint: + host: ngl-dual-006.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-006 + roles: 'Api,Peer' + - + publicKey: 9266C64CDEE62F2AF54806F0CBF2A53D9144CB02AA10B7F7DDEAEAF483E87591 + endpoint: + host: ngl-dual-105.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-105 + roles: 'Api,Peer' + - + publicKey: AC63FD2AAA53B73B4E515F73077522925C8502BFC13B073A520B4BA2F3026BC8 + endpoint: + host: ngl-dual-106.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-106 + roles: 'Api,Peer' + - + publicKey: 7811A94C2BF3CE9E7E1933DF9E9D0C3A7C09DAEF5A3CAC23960B252BDB8D52D3 + endpoint: + host: ngl-dual-205.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-205 + roles: 'Api,Peer' + - + publicKey: 02A55829A1A10542C460A87C4D2C39D03DCCA3F53692B5095C424CCDC21E64A6 + endpoint: + host: ngl-dual-206.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-206 + roles: 'Api,Peer' + - + publicKey: 36CAF133D574DB99B4392D76959488D5B91BD71545039DC80249CFD1FC536795 + endpoint: + host: ngl-dual-305.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-305 + roles: 'Api,Peer' + - + publicKey: 9B302ABCE3CD5783D79AD522B1769DA9AAA10A69E1930B998C2675AA105356FA + endpoint: + host: ngl-dual-306.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-306 + roles: 'Api,Peer' + - + publicKey: 4C85111B10EFB6A25FB77C8A2675C5A92DA5FF36686C4CCE24F887B16342CC95 + endpoint: + host: ngl-dual-405.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-405 + roles: 'Api,Peer' + - + publicKey: 0AF73C5CAC76624DB9B51560820F8DFC88120994143C6DB344A035EBFF707A2F + endpoint: + host: ngl-dual-406.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-406 + roles: 'Api,Peer' + - + publicKey: E060CA383C533273DB4B3CE7AD964E0E3A2B17018B19ABF830590D5FB9338B29 + endpoint: + host: ngl-dual-505.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-505 + roles: 'Api,Peer' + - + publicKey: B1D2AA01E51A81E84CBB2919C0E6AFBBC1383A2760370E60A93C335245A6CB9C + endpoint: + host: ngl-dual-506.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-506 + roles: 'Api,Peer' + - + publicKey: 4F014A430E69EFC422AD8B440FA4D312CBDE9803BB857A0736F38E62CE4CF571 + endpoint: + host: ngl-dual-605.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-605 + roles: 'Api,Peer' + - + publicKey: E32504B5590B9927CA5509FAA0C0CA63DB3479095C2DB73D0150FB862AA0E1FC + endpoint: + host: ngl-dual-606.symbolblockchain.io + port: 7900 + metadata: + name: ngl-dual-606 + roles: 'Api,Peer' + - + publicKey: 47AC3041A5CA98EEA123C64CB7420ADA7F45355CF1993ECA8639891536958011 + endpoint: + host: ngl-api-001.symbolblockchain.io + port: 7900 + metadata: + name: ngl-api-001 + roles: Api + - + publicKey: FE0186777D17DD57CAE5E3D9531EC8E70F1F452ED777910E96A9F44D889D9684 + endpoint: + host: ngl-api-301.symbolblockchain.io + port: 7900 + metadata: + name: ngl-api-301 + roles: Api + - + publicKey: 642197D9D5B283252BFCBC2FEF8B2EE1A7B3383DCD964D159CBF135CA31DC22B + endpoint: + host: ngl-api-401.symbolblockchain.io + port: 7900 + metadata: + name: ngl-api-401 + roles: Api + - + publicKey: E1640F016401E1A8644614B1707D878992DB01B480B55AEDE0EA1A986067ED79 + endpoint: + host: ngl-api-501.symbolblockchain.io + port: 7900 + metadata: + name: ngl-api-501 + roles: Api + - + publicKey: 0282991F51E91F8994F1B3FD4A7D1CDE39F6B828A5D419150D6E07925CFFD27C + endpoint: + host: ngl-api-601.symbolblockchain.io + port: 7900 + metadata: + name: ngl-api-601 + roles: Api peer-node: - - publicKey: FA9F3974FE3B15585E6B72672C7D8BEAE27D1EDF6C4752BAFDB8B2FEA601C0CF - endpoint: - host: ngl-beacon-001.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-001 - roles: 'Peer,Voting' - - publicKey: AFF16052217A847A6A71B326FEA9073CFF70D07FC5BA9026B3E05FB453C950DF - endpoint: - host: ngl-beacon-101.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-101 - roles: 'Peer,Voting' - - publicKey: D237B4C5964183141868F90ABEF557F751FFB973E7CFF59CCC1B00C504E048F0 - endpoint: - host: ngl-beacon-201.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-201 - roles: 'Peer,Voting' - - publicKey: 78480D48CC2AA6988E2FD05C884EE0525ACF6DC78766492034EA08CC1ADF788C - endpoint: - host: ngl-beacon-301.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-301 - roles: 'Peer,Voting' - - publicKey: 9261DB223A28A3DB05315235DF2186260951B66515B17A6B890BBCE3EE9E3FE7 - endpoint: - host: ngl-beacon-401.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-401 - roles: 'Peer,Voting' - - publicKey: B1B60E982CF0DE72584BED82EDCFAF5F187D24B21DECFA814F69605B5DE4A7C1 - endpoint: - host: ngl-beacon-501.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-501 - roles: 'Peer,Voting' - - publicKey: 9785B471C23994341159DB2E66853F6B0EAEA1E6E2A838A020965F4B8E29D03A - endpoint: - host: ngl-beacon-601.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-601 - roles: 'Peer,Voting' - - publicKey: EA77478068E3266CFA478E39AF1FDCD67EA512381889717AA8B66BFC4D01701F - endpoint: - host: ngl-beacon-002.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-002 - roles: 'Peer,Voting' - - publicKey: D5A40927DDCCD657F52F4C53FE682B07FC2E375040E0CAC50D8FE060024BB775 - endpoint: - host: ngl-beacon-102.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-102 - roles: 'Peer,Voting' - - publicKey: AF42867433DCAAA560A8A83A923822D55751F360BA385EDF21A932C33F3AEDD0 - endpoint: - host: ngl-beacon-202.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-202 - roles: 'Peer,Voting' - - publicKey: EF14DAE981FBD1E697CCD551DB9146B379CA4FD153C84016E7DA7A1930AD2395 - endpoint: - host: ngl-beacon-302.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-302 - roles: 'Peer,Voting' - - publicKey: 3F880DE47D4EB16D1871B1DED95F081151C9BF910EF682EDF202E8CA904B0CB9 - endpoint: - host: ngl-beacon-402.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-402 - roles: 'Peer,Voting' - - publicKey: 47704C18AC5F0C50A92B3763049EA91D84B17FAC0D86D9A9340AF98E3F610058 - endpoint: - host: ngl-beacon-502.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-502 - roles: 'Peer,Voting' - - publicKey: DE26F36D5426EC7B91200CB8C0DB70F4D82CD15F7FAFCE48A2174781D72CE6AD - endpoint: - host: ngl-beacon-602.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-602 - roles: 'Peer,Voting' - - publicKey: 533F75316C28E09B652232D1F48468D19EC88AB5125E947873AA3E150E2DD217 - endpoint: - host: ngl-beacon-003.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-003 - roles: Peer - - publicKey: DEE480BD3CA0B2E928543F087A28234E565FF6EDBBBC5B961F8302C16ACEB202 - endpoint: - host: ngl-beacon-004.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-004 - roles: Peer - - publicKey: F925F965D66E2928DDF2C27BB3AB69781C92D0E414C84AE963A505686C66445A - endpoint: - host: ngl-beacon-103.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-103 - roles: Peer - - publicKey: 4D0AAE4583FDA3BA3CFFA00F9C684A701C360E59DDD14EF3992233D20C59585A - endpoint: - host: ngl-beacon-104.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-104 - roles: Peer - - publicKey: EEA3B0D28CAEF4A6354F65D7D7F4947145D6FD9FF9F36CA87856CB44A7B1531E - endpoint: - host: ngl-beacon-203.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-203 - roles: Peer - - publicKey: 2D310E377C0CC84A945E8C9CFDB799F7FDE5F88EE07BBF90D5CED2BFCE27445B - endpoint: - host: ngl-beacon-204.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-204 - roles: Peer - - publicKey: C1BD10D1320BB22F66692AE002129028BEE7D78E3AA991B12525AD5D3E1BE7DA - endpoint: - host: ngl-beacon-303.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-303 - roles: Peer - - publicKey: 082F46AEAD35E6C3E443542616F41AAA992389035CAC2DD8807B8321C585E6BB - endpoint: - host: ngl-beacon-304.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-304 - roles: Peer - - publicKey: 11D6421BC726D684614038B5A0E48D6723CA7C53CDF3A7F9305582227988B3A1 - endpoint: - host: ngl-beacon-403.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-403 - roles: Peer - - publicKey: 66C2A930320B7A00D0A2653588F65967C73222A8356781DD5AB98F9E759AEF27 - endpoint: - host: ngl-beacon-404.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-404 - roles: Peer - - publicKey: C08E5DD1539AA29F1408B288550BCBC27BEA447E97E31C5976E58067CD69F655 - endpoint: - host: ngl-beacon-503.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-503 - roles: Peer - - publicKey: BADFA53EDBFC6B57511A836057DABAE9FF1669027EC37A5A188CCFB81A033C07 - endpoint: - host: ngl-beacon-504.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-504 - roles: Peer - - publicKey: 5A85331AB54F8B296EC69F7ABF05E133907D6B87F658E43D09E5E9F6E7FDFB6A - endpoint: - host: ngl-beacon-603.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-603 - roles: Peer - - publicKey: 00FB938646A8A458C55036682AEFAD6733DAEB86DDF19404DEED680FE79B1C39 - endpoint: - host: ngl-beacon-604.symbolblockchain.io - port: 7900 - metadata: - name: ngl-beacon-604 - roles: Peer - - publicKey: AA91F88DF6D80A9FA097E2FD2A6FCC9CC937177FE0D8933D64B16C5C442B2691 - endpoint: - host: ngl-peer-001.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-001 - roles: Peer - - publicKey: 990B2E1DD730964E62250A7DB1EC2EFCAEE8EE0FA9B2B5368CB4102964C4A822 - endpoint: - host: ngl-peer-301.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-301 - roles: Peer - - publicKey: 1F39352BDBFDBDE4FAE4C58A51140C0843A1B8EE1D62B4297BCC7F6ECE47F489 - endpoint: - host: ngl-peer-401.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-401 - roles: Peer - - publicKey: D12440291B4BD458FE6BAFAA67C502A638777462654C59C7070BE0B5215FF510 - endpoint: - host: ngl-peer-601.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-601 - roles: Peer - - publicKey: 92AD7B4E370A7499F1449C1BA2863C5E484B584B59B148724F5970C6EFD6F806 - endpoint: - host: ngl-peer-501.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-501 - roles: Peer - - publicKey: DEE184EB07DD52A171AA3381F5E7047AE3B830D2A99B7A9027461EA5CB6B878D - endpoint: - host: ngl-peer-002.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-002 - roles: 'Peer,Voting' - - publicKey: 7080531438D8E76B732D76ECB936E74AEAAF3F9491A85691791BB41462EC2DD0 - endpoint: - host: ngl-peer-003.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-003 - roles: 'Peer,Voting' - - publicKey: E4797E4362B7DE5E4EE834DE4EAAA1036B8F035F6A5352FD5BB85664B41633AE - endpoint: - host: ngl-peer-004.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-004 - roles: 'Peer,Voting' - - publicKey: FEC90947D0AF30CB32BC6018928AA6CEC96ED9861DD0608DAFC69131BFDD971A - endpoint: - host: ngl-peer-101.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-101 - roles: 'Peer,Voting' - - publicKey: E6C6D041D77590295432EDC5DC8D1019D4F1F0C002C37909EB4837FF03B139CD - endpoint: - host: ngl-peer-102.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-102 - roles: 'Peer,Voting' - - publicKey: FC8C66547D7C20CD6CBF7F31DC5657247351AF8C12188E56F885FF012431B8C1 - endpoint: - host: ngl-peer-103.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-103 - roles: 'Peer,Voting' - - publicKey: 0E8D31F20B4119D8AE8176970727A37247764CCCE966C72A5809685717FDEA0F - endpoint: - host: ngl-peer-201.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-201 - roles: 'Peer,Voting' - - publicKey: ABCB18959666D248D8B3677D6E673F3EB7A217DEFC5C31B4EDAEB5D111321DDF - endpoint: - host: ngl-peer-202.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-202 - roles: 'Peer,Voting' - - publicKey: 5F32F1AAE8934112A9627EFF43414D9E5DA93CFDB5B5149864FBF76BC2EAA09C - endpoint: - host: ngl-peer-302.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-302 - roles: 'Peer,Voting' - - publicKey: 504699E3C142DD465C5B46BCD7F43ABE232741F4AD82DFB66808E2A338B77EEB - endpoint: - host: ngl-peer-303.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-303 - roles: 'Peer,Voting' - - publicKey: D2C9A5ECDF0FFBDAC5F9D54EDE06848450798086206C3B5E8D9C7737AC5BF4E7 - endpoint: - host: ngl-peer-304.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-304 - roles: 'Peer,Voting' - - publicKey: 729073C51675055556F67BC1C4FD065A12ABDEEBB9455B835382BEA701B6B644 - endpoint: - host: ngl-peer-402.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-402 - roles: 'Peer,Voting' - - publicKey: 8C63F371ECBEC388A14117662F3CB7456D5EA7449779D20DDC259F084203B9DD - endpoint: - host: ngl-peer-403.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-403 - roles: 'Peer,Voting' - - publicKey: 22719E8A14770A076A81A3BD9A02847E0E198AA0D8E8D8E7CC0B359CA80CD73D - endpoint: - host: ngl-peer-404.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-404 - roles: 'Peer,Voting' - - publicKey: 00794D10A9BDEC3E431A8165782D59D54A3EDA682FC50EA31B03BE46E3508D86 - endpoint: - host: ngl-peer-502.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-502 - roles: 'Peer,Voting' - - publicKey: E6051F40218A770C56B170F27E2C031B61C19EB7EAF6D17CEE7854D3A3F264BF - endpoint: - host: ngl-peer-503.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-503 - roles: 'Peer,Voting' - - publicKey: 4EF71A6C97ADD6A2C07568C38130A7CD7768F3E0F4E5E651A72119DDFF1F74A2 - endpoint: - host: ngl-peer-504.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-504 - roles: 'Peer,Voting' - - publicKey: 98ECC6FFF03B4AB7C687A27C0DB120E3B0ECDFEAFECC54E10D8AA59409890170 - endpoint: - host: ngl-peer-602.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-602 - roles: 'Peer,Voting' - - publicKey: 8EA3F1718C0C8DF68FB3B53702C8B3C44FEB88FA7018CAF0C52D163974CAECD2 - endpoint: - host: ngl-peer-603.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-603 - roles: 'Peer,Voting' - - publicKey: 9C59D2235888E6FE638A9F4E3F6977C230836A9902929F1884220CF2F1A40B85 - endpoint: - host: ngl-peer-604.symbolblockchain.io - port: 7900 - metadata: - name: ngl-peer-604 - roles: 'Peer,Voting' + - + publicKey: FA9F3974FE3B15585E6B72672C7D8BEAE27D1EDF6C4752BAFDB8B2FEA601C0CF + endpoint: + host: ngl-beacon-001.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-001 + roles: 'Peer,Voting' + - + publicKey: AFF16052217A847A6A71B326FEA9073CFF70D07FC5BA9026B3E05FB453C950DF + endpoint: + host: ngl-beacon-101.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-101 + roles: 'Peer,Voting' + - + publicKey: D237B4C5964183141868F90ABEF557F751FFB973E7CFF59CCC1B00C504E048F0 + endpoint: + host: ngl-beacon-201.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-201 + roles: 'Peer,Voting' + - + publicKey: 78480D48CC2AA6988E2FD05C884EE0525ACF6DC78766492034EA08CC1ADF788C + endpoint: + host: ngl-beacon-301.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-301 + roles: 'Peer,Voting' + - + publicKey: 9261DB223A28A3DB05315235DF2186260951B66515B17A6B890BBCE3EE9E3FE7 + endpoint: + host: ngl-beacon-401.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-401 + roles: 'Peer,Voting' + - + publicKey: B1B60E982CF0DE72584BED82EDCFAF5F187D24B21DECFA814F69605B5DE4A7C1 + endpoint: + host: ngl-beacon-501.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-501 + roles: 'Peer,Voting' + - + publicKey: 9785B471C23994341159DB2E66853F6B0EAEA1E6E2A838A020965F4B8E29D03A + endpoint: + host: ngl-beacon-601.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-601 + roles: 'Peer,Voting' + - + publicKey: EA77478068E3266CFA478E39AF1FDCD67EA512381889717AA8B66BFC4D01701F + endpoint: + host: ngl-beacon-002.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-002 + roles: 'Peer,Voting' + - + publicKey: D5A40927DDCCD657F52F4C53FE682B07FC2E375040E0CAC50D8FE060024BB775 + endpoint: + host: ngl-beacon-102.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-102 + roles: 'Peer,Voting' + - + publicKey: AF42867433DCAAA560A8A83A923822D55751F360BA385EDF21A932C33F3AEDD0 + endpoint: + host: ngl-beacon-202.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-202 + roles: 'Peer,Voting' + - + publicKey: EF14DAE981FBD1E697CCD551DB9146B379CA4FD153C84016E7DA7A1930AD2395 + endpoint: + host: ngl-beacon-302.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-302 + roles: 'Peer,Voting' + - + publicKey: 3F880DE47D4EB16D1871B1DED95F081151C9BF910EF682EDF202E8CA904B0CB9 + endpoint: + host: ngl-beacon-402.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-402 + roles: 'Peer,Voting' + - + publicKey: 47704C18AC5F0C50A92B3763049EA91D84B17FAC0D86D9A9340AF98E3F610058 + endpoint: + host: ngl-beacon-502.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-502 + roles: 'Peer,Voting' + - + publicKey: DE26F36D5426EC7B91200CB8C0DB70F4D82CD15F7FAFCE48A2174781D72CE6AD + endpoint: + host: ngl-beacon-602.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-602 + roles: 'Peer,Voting' + - + publicKey: 533F75316C28E09B652232D1F48468D19EC88AB5125E947873AA3E150E2DD217 + endpoint: + host: ngl-beacon-003.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-003 + roles: Peer + - + publicKey: DEE480BD3CA0B2E928543F087A28234E565FF6EDBBBC5B961F8302C16ACEB202 + endpoint: + host: ngl-beacon-004.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-004 + roles: Peer + - + publicKey: F925F965D66E2928DDF2C27BB3AB69781C92D0E414C84AE963A505686C66445A + endpoint: + host: ngl-beacon-103.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-103 + roles: Peer + - + publicKey: 4D0AAE4583FDA3BA3CFFA00F9C684A701C360E59DDD14EF3992233D20C59585A + endpoint: + host: ngl-beacon-104.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-104 + roles: Peer + - + publicKey: EEA3B0D28CAEF4A6354F65D7D7F4947145D6FD9FF9F36CA87856CB44A7B1531E + endpoint: + host: ngl-beacon-203.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-203 + roles: Peer + - + publicKey: 2D310E377C0CC84A945E8C9CFDB799F7FDE5F88EE07BBF90D5CED2BFCE27445B + endpoint: + host: ngl-beacon-204.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-204 + roles: Peer + - + publicKey: C1BD10D1320BB22F66692AE002129028BEE7D78E3AA991B12525AD5D3E1BE7DA + endpoint: + host: ngl-beacon-303.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-303 + roles: Peer + - + publicKey: 082F46AEAD35E6C3E443542616F41AAA992389035CAC2DD8807B8321C585E6BB + endpoint: + host: ngl-beacon-304.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-304 + roles: Peer + - + publicKey: 11D6421BC726D684614038B5A0E48D6723CA7C53CDF3A7F9305582227988B3A1 + endpoint: + host: ngl-beacon-403.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-403 + roles: Peer + - + publicKey: 66C2A930320B7A00D0A2653588F65967C73222A8356781DD5AB98F9E759AEF27 + endpoint: + host: ngl-beacon-404.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-404 + roles: Peer + - + publicKey: C08E5DD1539AA29F1408B288550BCBC27BEA447E97E31C5976E58067CD69F655 + endpoint: + host: ngl-beacon-503.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-503 + roles: Peer + - + publicKey: BADFA53EDBFC6B57511A836057DABAE9FF1669027EC37A5A188CCFB81A033C07 + endpoint: + host: ngl-beacon-504.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-504 + roles: Peer + - + publicKey: 5A85331AB54F8B296EC69F7ABF05E133907D6B87F658E43D09E5E9F6E7FDFB6A + endpoint: + host: ngl-beacon-603.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-603 + roles: Peer + - + publicKey: 00FB938646A8A458C55036682AEFAD6733DAEB86DDF19404DEED680FE79B1C39 + endpoint: + host: ngl-beacon-604.symbolblockchain.io + port: 7900 + metadata: + name: ngl-beacon-604 + roles: Peer + - + publicKey: AA91F88DF6D80A9FA097E2FD2A6FCC9CC937177FE0D8933D64B16C5C442B2691 + endpoint: + host: ngl-peer-001.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-001 + roles: Peer + - + publicKey: 990B2E1DD730964E62250A7DB1EC2EFCAEE8EE0FA9B2B5368CB4102964C4A822 + endpoint: + host: ngl-peer-301.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-301 + roles: Peer + - + publicKey: 1F39352BDBFDBDE4FAE4C58A51140C0843A1B8EE1D62B4297BCC7F6ECE47F489 + endpoint: + host: ngl-peer-401.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-401 + roles: Peer + - + publicKey: D12440291B4BD458FE6BAFAA67C502A638777462654C59C7070BE0B5215FF510 + endpoint: + host: ngl-peer-601.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-601 + roles: Peer + - + publicKey: 92AD7B4E370A7499F1449C1BA2863C5E484B584B59B148724F5970C6EFD6F806 + endpoint: + host: ngl-peer-501.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-501 + roles: Peer + - + publicKey: DEE184EB07DD52A171AA3381F5E7047AE3B830D2A99B7A9027461EA5CB6B878D + endpoint: + host: ngl-peer-002.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-002 + roles: 'Peer,Voting' + - + publicKey: 7080531438D8E76B732D76ECB936E74AEAAF3F9491A85691791BB41462EC2DD0 + endpoint: + host: ngl-peer-003.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-003 + roles: 'Peer,Voting' + - + publicKey: E4797E4362B7DE5E4EE834DE4EAAA1036B8F035F6A5352FD5BB85664B41633AE + endpoint: + host: ngl-peer-004.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-004 + roles: 'Peer,Voting' + - + publicKey: FEC90947D0AF30CB32BC6018928AA6CEC96ED9861DD0608DAFC69131BFDD971A + endpoint: + host: ngl-peer-101.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-101 + roles: 'Peer,Voting' + - + publicKey: E6C6D041D77590295432EDC5DC8D1019D4F1F0C002C37909EB4837FF03B139CD + endpoint: + host: ngl-peer-102.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-102 + roles: 'Peer,Voting' + - + publicKey: FC8C66547D7C20CD6CBF7F31DC5657247351AF8C12188E56F885FF012431B8C1 + endpoint: + host: ngl-peer-103.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-103 + roles: 'Peer,Voting' + - + publicKey: 0E8D31F20B4119D8AE8176970727A37247764CCCE966C72A5809685717FDEA0F + endpoint: + host: ngl-peer-201.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-201 + roles: 'Peer,Voting' + - + publicKey: ABCB18959666D248D8B3677D6E673F3EB7A217DEFC5C31B4EDAEB5D111321DDF + endpoint: + host: ngl-peer-202.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-202 + roles: 'Peer,Voting' + - + publicKey: 5F32F1AAE8934112A9627EFF43414D9E5DA93CFDB5B5149864FBF76BC2EAA09C + endpoint: + host: ngl-peer-302.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-302 + roles: 'Peer,Voting' + - + publicKey: 504699E3C142DD465C5B46BCD7F43ABE232741F4AD82DFB66808E2A338B77EEB + endpoint: + host: ngl-peer-303.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-303 + roles: 'Peer,Voting' + - + publicKey: D2C9A5ECDF0FFBDAC5F9D54EDE06848450798086206C3B5E8D9C7737AC5BF4E7 + endpoint: + host: ngl-peer-304.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-304 + roles: 'Peer,Voting' + - + publicKey: 729073C51675055556F67BC1C4FD065A12ABDEEBB9455B835382BEA701B6B644 + endpoint: + host: ngl-peer-402.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-402 + roles: 'Peer,Voting' + - + publicKey: 8C63F371ECBEC388A14117662F3CB7456D5EA7449779D20DDC259F084203B9DD + endpoint: + host: ngl-peer-403.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-403 + roles: 'Peer,Voting' + - + publicKey: 22719E8A14770A076A81A3BD9A02847E0E198AA0D8E8D8E7CC0B359CA80CD73D + endpoint: + host: ngl-peer-404.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-404 + roles: 'Peer,Voting' + - + publicKey: 00794D10A9BDEC3E431A8165782D59D54A3EDA682FC50EA31B03BE46E3508D86 + endpoint: + host: ngl-peer-502.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-502 + roles: 'Peer,Voting' + - + publicKey: E6051F40218A770C56B170F27E2C031B61C19EB7EAF6D17CEE7854D3A3F264BF + endpoint: + host: ngl-peer-503.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-503 + roles: 'Peer,Voting' + - + publicKey: 4EF71A6C97ADD6A2C07568C38130A7CD7768F3E0F4E5E651A72119DDFF1F74A2 + endpoint: + host: ngl-peer-504.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-504 + roles: 'Peer,Voting' + - + publicKey: 98ECC6FFF03B4AB7C687A27C0DB120E3B0ECDFEAFECC54E10D8AA59409890170 + endpoint: + host: ngl-peer-602.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-602 + roles: 'Peer,Voting' + - + publicKey: 8EA3F1718C0C8DF68FB3B53702C8B3C44FEB88FA7018CAF0C52D163974CAECD2 + endpoint: + host: ngl-peer-603.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-603 + roles: 'Peer,Voting' + - + publicKey: 9C59D2235888E6FE638A9F4E3F6977C230836A9902929F1884220CF2F1A40B85 + endpoint: + host: ngl-peer-604.symbolblockchain.io + port: 7900 + metadata: + name: ngl-peer-604 + roles: 'Peer,Voting' inflation: starting-at-height-2: 0 starting-at-height-5760: 191997042 diff --git a/presets/shared.yml b/presets/shared.yml index 70fab1e45..9cb07a454 100644 --- a/presets/shared.yml +++ b/presets/shared.yml @@ -33,6 +33,7 @@ minVotingKeyLifetime: 28 mosaicRentalFee: 500 votingSetGrouping: 180 maxVotingKeysPerAccount: 3 +autoUpdateVotingKeys: false maxTransactionsPerAggregate: 100 maxCosignaturesPerAggregate: 25 harvestBeneficiaryPercentage: 10 @@ -84,6 +85,7 @@ votingKeysDirectory: ./votingkeys enableDelegatedHarvestersAutoDetection: true catapultAppFolder: /usr/catapult rewardProgramAgentPort: 7881 +enableRevoteOnBoot: true # config database databaseName: catapult @@ -210,9 +212,5 @@ maxConnectionAttempts: 15 baseRetryDelay: 750 connectionPoolSize: 10 maxSubscriptions: 300 +restExtensions: 'accountLink, aggregate, lockHash, lockSecret, mosaic, metadata, multisig, namespace, receipts, restrictions, transfer, cmc' restDeploymentTool: symbol-bootstrap - -#voting -votingKeyStartEpoch: 1 -votingKeyEndEpoch: 360 -enableRevoteOnBoot: true diff --git a/presets/testnet/network.yml b/presets/testnet/network.yml index 20aa494c6..0f781aac9 100644 --- a/presets/testnet/network.yml +++ b/presets/testnet/network.yml @@ -1,4 +1,4 @@ -assemblies: api, dual, peer +assemblies: 'api, dual, peer' baseNamespace: symbol batchVerificationRandomSource: /dev/urandom currencyMosaicId: 091F837E059AE13C @@ -18,13 +18,17 @@ namespaceRentalFeeSinkAddress: TATBDUEWS2X2BKKBPVB7SY4Z626YCAERGA3IF5A mosaicRentalFeeSinkAddress: TAFNXW3VXVFTGTVGATKQAR75ALQX7DQXQJRWWTA nemesisSignerPublicKey: 2267B24107405779DDF0D8FBEABD8142B97105F356F3737B1FC02220E8F90FF2 networkType: 152 -rewardProgramCaFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJZVENDQVJNQ0ZHTGt2L1dUM2J0NmltLytTckdrSUJuRnhBMW5NQVVHQXl0bGNEQlRNUm93R0FZRFZRUUsKREJGT1JVMGdSM0p2ZFhBZ1RHbHRhWFJsWkRFYk1Ca0dBMVVFQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dApNUmd3RmdZRFZRUUxEQTlTWlhkaGNtUnpJRkJ5YjJkeVlXMHdIaGNOTWpFd05ESTJNVFEwTXpBMVdoY05OREV3Ck5ESXhNVFEwTXpBMVdqQlRNUm93R0FZRFZRUUtEQkZPUlUwZ1IzSnZkWEFnVEdsdGFYUmxaREViTUJrR0ExVUUKQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dE1SZ3dGZ1lEVlFRTERBOVNaWGRoY21SeklGQnliMmR5WVcwdwpLakFGQmdNclpYQURJUUFnTU5jWGpILzM2SmswRVFQODNGeEsrcUljcUZ6U29HdHNxN3diR1B2RmZ6QUZCZ01yClpYQURRUUR0RU5uTXFUS2M3eUp1MFN5SzZoalBLUDZWVjVvMHFjWTFXR3VVK3REOWxwbFBkWkRRSnhEZG5aWEMKV1JJQlROR0hYWjZNa1p5LzFIdTZpOEp6V0FVSQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== +rewardProgramCaFile: >- + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJZVENDQVJNQ0ZHTGt2L1dUM2J0NmltLytTckdrSUJuRnhBMW5NQVVHQXl0bGNEQlRNUm93R0FZRFZRUUsKREJGT1JVMGdSM0p2ZFhBZ1RHbHRhWFJsWkRFYk1Ca0dBMVVFQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dApNUmd3RmdZRFZRUUxEQTlTWlhkaGNtUnpJRkJ5YjJkeVlXMHdIaGNOTWpFd05ESTJNVFEwTXpBMVdoY05OREV3Ck5ESXhNVFEwTXpBMVdqQlRNUm93R0FZRFZRUUtEQkZPUlUwZ1IzSnZkWEFnVEdsdGFYUmxaREViTUJrR0ExVUUKQXd3U2MzbHRZbTlzY0d4aGRHWnZjbTB1WTI5dE1SZ3dGZ1lEVlFRTERBOVNaWGRoY21SeklGQnliMmR5WVcwdwpLakFGQmdNclpYQURJUUFnTU5jWGpILzM2SmswRVFQODNGeEsrcUljcUZ6U29HdHNxN3diR1B2RmZ6QUZCZ01yClpYQURRUUR0RU5uTXFUS2M3eUp1MFN5SzZoalBLUDZWVjVvMHFjWTFXR3VVK3REOWxwbFBkWkRRSnhEZG5aWEMKV1JJQlROR0hYWjZNa1p5LzFIdTZpOEp6V0FVSQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== rewardProgramEnrollmentAddress: TDL73SDUMPDK7EOF7H3O4F5WB5WHG2SX7XUSFZQ rewardProgramControllerApiUrl: 'http://node-monitoring.testnet.symboldev.network:7890' totalChainImportance: 7842928625000000 initialCurrencyAtomicUnits: 7842928625000000 importanceGrouping: 180 votingSetGrouping: 720 +votingKeyDesiredLifetime: 720 +votingKeyDesiredFutureLifetime: 120 +lastKnownNetworkEpoch: 235 minVotingKeyLifetime: 28 maxVotingKeyLifetime: 720 stepDuration: 4m @@ -65,182 +69,207 @@ knownRestGateways: - 'http://ngl-api-101.testnet.symboldev.network:3000' knownPeers: api-node: - - publicKey: 1B8491EE121B865E733A0AFC2B0B5A0C762B57A7CDDDBDE6608943EAE135BE44 - endpoint: - host: ngl-dual-501.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-501 - roles: 'Api,Peer,Voting' - - publicKey: 41F38D9C02F7D3D460A4CB0311E3D6E67CB063351434A6A00BB6F7AE64CD4FD7 - endpoint: - host: ngl-dual-601.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-601 - roles: 'Api,Peer,Voting' - - publicKey: E3FC28889BDE31406465167F1D9D6A16DCA1FF67A3BABFA5E5A8596478848F78 - endpoint: - host: ngl-dual-001.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-001 - roles: 'Api,Peer' - - publicKey: C4348215B4C417D3E4B52ACAA3D370D29DE3A5F482CAED3C9F1BE257DD2B4079 - endpoint: - host: ngl-dual-101.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-101 - roles: 'Api,Peer' - - publicKey: C084A7B93E7B2B04A95328A39FD0851086BEE4389987654315F02F4DE32634F8 - endpoint: - host: ngl-dual-201.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-201 - roles: 'Api,Peer' - - publicKey: 4AD462EFDB312769AB0F146E287C4216E0858992F8181B32A232EDF94E4D4A7C - endpoint: - host: ngl-dual-301.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-301 - roles: 'Api,Peer' - - publicKey: EBB821CFAE5B8FBE76FAD81D4A6A2135EB6E2603E083AE9A726132B6AE8A1089 - endpoint: - host: ngl-dual-401.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-401 - roles: 'Api,Peer' - - publicKey: 093894ECBD487A2601E9DFEF100C40238F39EA29E9DF2DB244BFD4F3E7B64142 - endpoint: - host: ngl-dual-502.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-502 - roles: 'Api,Peer' - - publicKey: 53100549CE67E4B94867B461DC489747862A0CB34ADA701C6A974134F1DCC197 - endpoint: - host: ngl-dual-602.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-dual-602 - roles: 'Api,Peer' - - publicKey: 3BD4CECB2C82DE809CE6AD285ECF8687B4594EAE7D9220C2064E56812977EAE6 - endpoint: - host: ngl-api-001.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-api-001 - roles: Api - - publicKey: 904303E0DAF19BA69CD6A1AB4CA3E2E80525BD9D814342B67C850A0CBB3ECC0F - endpoint: - host: ngl-api-101.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-api-101 - roles: Api + - + publicKey: 1B8491EE121B865E733A0AFC2B0B5A0C762B57A7CDDDBDE6608943EAE135BE44 + endpoint: + host: ngl-dual-501.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-501 + roles: 'Api,Peer,Voting' + - + publicKey: 41F38D9C02F7D3D460A4CB0311E3D6E67CB063351434A6A00BB6F7AE64CD4FD7 + endpoint: + host: ngl-dual-601.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-601 + roles: 'Api,Peer,Voting' + - + publicKey: E3FC28889BDE31406465167F1D9D6A16DCA1FF67A3BABFA5E5A8596478848F78 + endpoint: + host: ngl-dual-001.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-001 + roles: 'Api,Peer' + - + publicKey: C4348215B4C417D3E4B52ACAA3D370D29DE3A5F482CAED3C9F1BE257DD2B4079 + endpoint: + host: ngl-dual-101.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-101 + roles: 'Api,Peer' + - + publicKey: C084A7B93E7B2B04A95328A39FD0851086BEE4389987654315F02F4DE32634F8 + endpoint: + host: ngl-dual-201.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-201 + roles: 'Api,Peer' + - + publicKey: 4AD462EFDB312769AB0F146E287C4216E0858992F8181B32A232EDF94E4D4A7C + endpoint: + host: ngl-dual-301.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-301 + roles: 'Api,Peer' + - + publicKey: EBB821CFAE5B8FBE76FAD81D4A6A2135EB6E2603E083AE9A726132B6AE8A1089 + endpoint: + host: ngl-dual-401.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-401 + roles: 'Api,Peer' + - + publicKey: 093894ECBD487A2601E9DFEF100C40238F39EA29E9DF2DB244BFD4F3E7B64142 + endpoint: + host: ngl-dual-502.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-502 + roles: 'Api,Peer' + - + publicKey: 53100549CE67E4B94867B461DC489747862A0CB34ADA701C6A974134F1DCC197 + endpoint: + host: ngl-dual-602.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-dual-602 + roles: 'Api,Peer' + - + publicKey: 3BD4CECB2C82DE809CE6AD285ECF8687B4594EAE7D9220C2064E56812977EAE6 + endpoint: + host: ngl-api-001.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-api-001 + roles: Api + - + publicKey: 904303E0DAF19BA69CD6A1AB4CA3E2E80525BD9D814342B67C850A0CBB3ECC0F + endpoint: + host: ngl-api-101.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-api-101 + roles: Api peer-node: - - publicKey: 3CA5B729F46A56928C4971674B641C9535B93B560FC789F1134314786FC5766F - endpoint: - host: ngl-beacon-501.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-beacon-501 - roles: 'Peer,Voting' - - publicKey: CFC25E8E5C7348C075B8189BD2FEF16E9AE26A1E4814A1002B562B95F5538435 - endpoint: - host: ngl-beacon-601.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-beacon-601 - roles: 'Peer,Voting' - - publicKey: 5E5BF270398818DC58107C599238D00247DA33446BC1CE334CAD5ED44057184D - endpoint: - host: ngl-beacon-001.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-beacon-001 - roles: 'Peer,Voting' - - publicKey: 3BE809A65B98E8AF7E5835A9C12F90B29BEDFFFF97E11C1160D8136F15B66AD1 - endpoint: - host: ngl-beacon-101.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-beacon-101 - roles: 'Peer,Voting' - - publicKey: A33EF3ED8CFBF16C2C3468FCAEE78B36F6F0E737820CE9F48D349309515316F8 - endpoint: - host: ngl-beacon-401.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-beacon-401 - roles: Peer - - publicKey: 723C4D540D0796C998D62258DF16AEE8D55A403F716211DEAB479FCE18640B2C - endpoint: - host: ngl-peer-201.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-201 - roles: Peer - - publicKey: DC7A90D0676DB3A2D963768276F606AF76541A59588B23C6C6B48D98E0AC3837 - endpoint: - host: ngl-peer-301.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-301 - roles: Peer - - publicKey: 22F6D59D56E17619B8F8B7323A500693892092268122AF5E2A02F6EE29FA2F11 - endpoint: - host: ngl-peer-001.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-001 - roles: 'Peer,Voting' - - publicKey: 78EFF533B504F49054F8CACEA9E5751E88EDCBBB057DE7410161B9398FEB9FCE - endpoint: - host: ngl-peer-101.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-101 - roles: 'Peer,Voting' - - publicKey: BA8CA590E44FE53FAAF8E98ED6E98A53C5E911CE2E6D0ACCAB22A979DA151824 - endpoint: - host: ngl-peer-202.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-202 - roles: 'Peer,Voting' - - publicKey: 356854C754039984F94C37C26CF58CD282AC3BCAC99F9549367702823A50D37C - endpoint: - host: ngl-peer-302.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-302 - roles: 'Peer,Voting' - - publicKey: FE5D4350784BF72E9D36B6E7BE7BB8F3DF26D501614BB707A4C0DFB8DB541BEB - endpoint: - host: ngl-peer-401.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-401 - roles: 'Peer,Voting' - - publicKey: FB1B701426A29A22A0711E351117AAFB9449B69CD0D6F310663DED02CD29F5CE - endpoint: - host: ngl-peer-501.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-501 - roles: 'Peer,Voting' - - publicKey: 2489946E49B03D9BE040E3FD42FEBC705D001A746BD25399E2796D615B35B732 - endpoint: - host: ngl-peer-601.testnet.symboldev.network - port: 7900 - metadata: - name: ngl-peer-601 - roles: 'Peer,Voting' + - + publicKey: 3CA5B729F46A56928C4971674B641C9535B93B560FC789F1134314786FC5766F + endpoint: + host: ngl-beacon-501.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-beacon-501 + roles: 'Peer,Voting' + - + publicKey: CFC25E8E5C7348C075B8189BD2FEF16E9AE26A1E4814A1002B562B95F5538435 + endpoint: + host: ngl-beacon-601.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-beacon-601 + roles: 'Peer,Voting' + - + publicKey: 5E5BF270398818DC58107C599238D00247DA33446BC1CE334CAD5ED44057184D + endpoint: + host: ngl-beacon-001.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-beacon-001 + roles: 'Peer,Voting' + - + publicKey: 3BE809A65B98E8AF7E5835A9C12F90B29BEDFFFF97E11C1160D8136F15B66AD1 + endpoint: + host: ngl-beacon-101.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-beacon-101 + roles: 'Peer,Voting' + - + publicKey: A33EF3ED8CFBF16C2C3468FCAEE78B36F6F0E737820CE9F48D349309515316F8 + endpoint: + host: ngl-beacon-401.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-beacon-401 + roles: Peer + - + publicKey: 723C4D540D0796C998D62258DF16AEE8D55A403F716211DEAB479FCE18640B2C + endpoint: + host: ngl-peer-201.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-201 + roles: Peer + - + publicKey: DC7A90D0676DB3A2D963768276F606AF76541A59588B23C6C6B48D98E0AC3837 + endpoint: + host: ngl-peer-301.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-301 + roles: Peer + - + publicKey: 22F6D59D56E17619B8F8B7323A500693892092268122AF5E2A02F6EE29FA2F11 + endpoint: + host: ngl-peer-001.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-001 + roles: 'Peer,Voting' + - + publicKey: 78EFF533B504F49054F8CACEA9E5751E88EDCBBB057DE7410161B9398FEB9FCE + endpoint: + host: ngl-peer-101.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-101 + roles: 'Peer,Voting' + - + publicKey: BA8CA590E44FE53FAAF8E98ED6E98A53C5E911CE2E6D0ACCAB22A979DA151824 + endpoint: + host: ngl-peer-202.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-202 + roles: 'Peer,Voting' + - + publicKey: 356854C754039984F94C37C26CF58CD282AC3BCAC99F9549367702823A50D37C + endpoint: + host: ngl-peer-302.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-302 + roles: 'Peer,Voting' + - + publicKey: FE5D4350784BF72E9D36B6E7BE7BB8F3DF26D501614BB707A4C0DFB8DB541BEB + endpoint: + host: ngl-peer-401.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-401 + roles: 'Peer,Voting' + - + publicKey: FB1B701426A29A22A0711E351117AAFB9449B69CD0D6F310663DED02CD29F5CE + endpoint: + host: ngl-peer-501.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-501 + roles: 'Peer,Voting' + - + publicKey: 2489946E49B03D9BE040E3FD42FEBC705D001A746BD25399E2796D615B35B732 + endpoint: + host: ngl-peer-601.testnet.symboldev.network + port: 7900 + metadata: + name: ngl-peer-601 + roles: 'Peer,Voting' inflation: starting-at-height-2: 0 starting-at-height-5760: 191997042 diff --git a/src/commands/config.ts b/src/commands/config.ts index 00c0a1bb0..5adc7e805 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -15,8 +15,7 @@ */ import { Command, flags } from '@oclif/command'; -import { BootstrapService, BootstrapUtils, ConfigService, Preset } from '../service'; -import { CommandUtils } from '../service/CommandUtils'; +import { BootstrapService, BootstrapUtils, CommandUtils, ConfigService, Preset } from '../service'; export default class Config extends Command { static description = 'Command used to set up the configuration files and the nemesis block for the current network'; @@ -26,7 +25,7 @@ export default class Config extends Command { `$ symbol-bootstrap config -p testnet -a dual --password 1234`, `$ echo "$MY_ENV_VAR_PASSWORD" | symbol-bootstrap config -p testnet -a dual`, ]; - + //@typescript-eslint/explicit-module-boundary-types static resolveFlags = (required: boolean) => ({ help: CommandUtils.helpFlag, target: CommandUtils.targetFlag, @@ -68,12 +67,6 @@ export default class Config extends Command { default: ConfigService.defaultParams.report, }), - pullImages: flags.boolean({ - description: - 'It pulls the utility images from DockerHub when running the configuration. It only affects alpha/dev docker images.', - default: ConfigService.defaultParams.pullImages, - }), - user: flags.string({ char: 'u', description: `User used to run docker images when creating configuration files like certificates or nemesis block. "${BootstrapUtils.CURRENT_USER}" means the current user.`, diff --git a/src/commands/updateVotingKeys.ts b/src/commands/updateVotingKeys.ts new file mode 100644 index 000000000..a7d402edc --- /dev/null +++ b/src/commands/updateVotingKeys.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2020 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command, flags } from '@oclif/command'; +import { LogType } from '../logger'; +import Logger from '../logger/Logger'; +import LoggerFactory from '../logger/LoggerFactory'; +import { BootstrapUtils, CommandUtils, ConfigLoader, CryptoUtils, RemoteNodeService, VotingService } from '../service'; +const logger: Logger = LoggerFactory.getLogger(LogType.System); + +export default class UpdateVotingKeys extends Command { + static description = `It updates the voting files containing the voting keys when required. + +If the node's current voting file has an end epoch close to the current network epoch, this command will create a new 'private_key_treeX.dat' that continues the current file. + +By default, bootstrap creates a new voting file once the current file reaches its last month. The current network epoch is resolved from the network or you can provide it with the \`finalizationEpoch\` param. + +When a new voting file is created, Bootstrap will advise running the \`link\` command again. + +`; + + static examples = [`$ symbol-bootstrap updateVotingKeys`]; + + static flags = { + help: CommandUtils.helpFlag, + target: CommandUtils.targetFlag, + user: flags.string({ + char: 'u', + description: `User used to run docker images when creating the the voting key files. "${BootstrapUtils.CURRENT_USER}" means the current user.`, + default: BootstrapUtils.CURRENT_USER, + }), + finalizationEpoch: flags.integer({ + description: `The network's finalization epoch. It can be retrieved from the /chain/info rest endpoint. If not provided, the bootstrap known epoch is used.`, + }), + }; + + public async run(): Promise { + const { flags } = this.parse(UpdateVotingKeys); + BootstrapUtils.showBanner(); + const password = false; + const target = flags.target; + const configLoader = new ConfigLoader(); + const addressesLocation = configLoader.getGeneratedAddressLocation(target); + const presetData = configLoader.loadExistingPresetData(target, password); + const addresses = configLoader.loadExistingAddresses(target, password); + const privateKeySecurityMode = CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode); + + const finalizationEpoch = flags.finalizationEpoch || (await new RemoteNodeService().resolveCurrentFinalizationEpoch(presetData)); + + const votingKeyUpgrade = ( + await Promise.all( + (presetData.nodes || []).map((nodePreset, index) => { + const nodeAccount = addresses.nodes?.[index]; + if (!nodeAccount) { + throw new Error(`There is not node in addresses at index ${index}`); + } + return new VotingService({ + target, + user: flags.user, + }).run(presetData, nodeAccount, nodePreset, finalizationEpoch, true, false); + }), + ) + ).find((f) => f); + if (votingKeyUpgrade) { + await BootstrapUtils.writeYaml( + addressesLocation, + CryptoUtils.removePrivateKeysAccordingToSecurityMode(addresses, privateKeySecurityMode), + undefined, + ); + logger.warn('Bootstrap has created new voting file(s). Review the logs!'); + logger.warn(''); + } else { + logger.info(''); + logger.info('Voting files are up-to-date. There is nothing to upgrade'); + logger.info(''); + } + } +} diff --git a/src/model/Addresses.ts b/src/model/Addresses.ts index 6eb047536..e3d75c6e6 100644 --- a/src/model/Addresses.ts +++ b/src/model/Addresses.ts @@ -15,6 +15,7 @@ */ import { NetworkType } from 'symbol-sdk'; +import { VotingKeyFile } from '../service'; export interface CertificatePair { privateKey?: string; @@ -35,8 +36,8 @@ export interface NodeAccount { remote?: ConfigAccount; // VRF key is produced if node is peer vrf?: ConfigAccount; - // Voting key is produced if node is voting - voting?: ConfigAccount; + // Voting keys are produced if node is voting + voting?: VotingKeyFile[]; // Agent private key is produced if node is supernode agent?: ConfigAccount; roles: string; diff --git a/src/model/ConfigPreset.ts b/src/model/ConfigPreset.ts index 694d114dd..bd6e3a9b7 100644 --- a/src/model/ConfigPreset.ts +++ b/src/model/ConfigPreset.ts @@ -398,8 +398,6 @@ export interface CommonConfigPreset extends NodeConfigPreset, GatewayConfigPrese dockerComposeVersion: number | string; dockerComposeServiceRestart: string; dockerComposeDebugMode: boolean; - votingKeyEndEpoch: number; - votingKeyStartEpoch: number; mongoComposeRunParam: string; peersP2PListLimit: number; peersApiListLimit: number; @@ -418,8 +416,12 @@ export interface CommonConfigPreset extends NodeConfigPreset, GatewayConfigPrese baseNamespace: string; rewardProgramEnrollmentAddress?: string; networkType: NetworkType; + votingKeyDesiredLifetime: number; + votingKeyDesiredFutureLifetime: number; // How in the future voting key files need to be generated. By default, 1 months before expiring.. + useExperimentalNativeVotingKeyGeneration?: boolean; + lastKnownNetworkEpoch: number; + autoUpdateVotingKeys: boolean; //Nested Objects - knownRestGateways?: string[]; inflation?: Record; knownPeers?: Record; diff --git a/src/service/AnnounceService.ts b/src/service/AnnounceService.ts index 37326666e..9af4c34cd 100644 --- a/src/service/AnnounceService.ts +++ b/src/service/AnnounceService.ts @@ -20,7 +20,6 @@ import { AccountInfo, Address, AggregateTransaction, - ChainInfo, Convert, Deadline, LockFundsTransaction, @@ -29,7 +28,6 @@ import { NetworkType, PublicAccount, RepositoryFactory, - RepositoryFactoryHttp, SignedTransaction, Transaction, TransactionService, @@ -41,6 +39,7 @@ import LoggerFactory from '../logger/LoggerFactory'; import { Addresses, ConfigPreset, NodeAccount, NodePreset } from '../model'; import { CommandUtils } from './CommandUtils'; import { KeyName } from './ConfigService'; +import { RemoteNodeService } from './RemoteNodeService'; const logger: Logger = LoggerFactory.getLogger(LogType.System); @@ -51,20 +50,15 @@ export interface TransactionFactoryParams { mainAccountInfo: AccountInfo; mainAccount: PublicAccount; deadline: Deadline; + target: string; maxFee: UInt64; + latestFinalizedBlockEpoch: number; } export interface TransactionFactory { createTransactions(params: TransactionFactoryParams): Promise; } -export interface RepositoryInfo { - repositoryFactory: RepositoryFactory; - restGatewayUrl: string; - generationHash?: string; - chainInfo?: ChainInfo; -} - export class AnnounceService { private static onProcessListener = () => { process.on('SIGINT', () => { @@ -83,7 +77,6 @@ export class AnnounceService { description: 'Use the best NEM node available when announcing. Otherwise the command will use the node provided by the --url parameter.', }), - ready: flags.boolean({ description: 'If --ready is provided, the command will not ask for confirmation when announcing transactions.', }), @@ -101,6 +94,7 @@ export class AnnounceService { providedMaxFee: number | undefined, useKnownRestGateways: boolean, ready: boolean | undefined, + target: string, presetData: ConfigPreset, addresses: Addresses, transactionFactory: TransactionFactory, @@ -111,22 +105,10 @@ export class AnnounceService { logger.info(`There are no transactions to announce...`); return; } - const url = providedUrl.replace(/\/$/, ''); - let repositoryFactory: RepositoryFactory; - const urls = (useKnownRestGateways && presetData.knownRestGateways) || []; - if (urls.length) { - const repositoryInfo = this.sortByHeight(await this.getKnownNodeRepositoryInfos(urls))[0]; - if (!repositoryInfo) { - throw new Error(`No up and running node could be found of out: ${urls.join(', ')}`); - } - repositoryFactory = repositoryInfo.repositoryFactory; - logger.info(`Connecting to node ${repositoryInfo.restGatewayUrl}`); - } else { - repositoryFactory = new RepositoryFactoryHttp(url); - logger.info(`Connecting to node ${url}`); - } - + const urls = (useKnownRestGateways && presetData.knownRestGateways) || [url]; + const repositoryInfo = await new RemoteNodeService().getBestRepositoryInfo(urls); + const repositoryFactory = repositoryInfo.repositoryFactory; const networkType = await repositoryFactory.getNetworkType().toPromise(); const transactionRepository = repositoryFactory.createTransactionRepository(); const transactionService = new TransactionService(transactionRepository, repositoryFactory.createReceiptRepository()); @@ -138,6 +120,8 @@ export class AnnounceService { const currencyMosaicId = currency.mosaicId; const deadline = Deadline.create(epochAdjustment); const minFeeMultiplier = (await repositoryFactory.createNetworkRepository().getTransactionFees().toPromise()).minFeeMultiplier; + const latestFinalizedBlockEpoch = (await repositoryFactory.createChainRepository().getChainInfo().toPromise()).latestFinalizedBlock + .finalizationEpoch; if (providedMaxFee) { logger.info(`MaxFee is ${providedMaxFee / Math.pow(10, currency.divisibility)}`); } else { @@ -179,6 +163,8 @@ export class AnnounceService { nodePreset, nodeAccount, mainAccountInfo, + latestFinalizedBlockEpoch, + target, mainAccount, deadline, maxFee: defaultMaxFee, @@ -477,48 +463,6 @@ export class AnnounceService { } } - private getKnownNodeRepositoryInfos(knownUrls: string[]): Promise { - logger.info(`Looking for the best node out of: ${knownUrls.join(', ')}`); - return Promise.all( - knownUrls.map( - async (restGatewayUrl): Promise => { - const repositoryFactory = new RepositoryFactoryHttp(restGatewayUrl); - try { - const generationHash = await repositoryFactory.getGenerationHash().toPromise(); - const chainInfo = await repositoryFactory.createChainRepository().getChainInfo().toPromise(); - return { - restGatewayUrl, - repositoryFactory, - generationHash, - chainInfo, - }; - } catch (e) { - const message = `There has been an error talking to node ${restGatewayUrl}. Error: ${e.message}}`; - logger.warn(message); - return { - restGatewayUrl: restGatewayUrl, - repositoryFactory, - }; - } - }, - ), - ); - } - - private sortByHeight(repos: RepositoryInfo[]): RepositoryInfo[] { - return repos - .filter((b) => b.chainInfo) - .sort((a, b) => { - if (!a.chainInfo) { - return 1; - } - if (!b.chainInfo) { - return -1; - } - return b.chainInfo.height.compare(a.chainInfo.height); - }); - } - private async getBestCosigner( repositoryFactory: RepositoryFactory, cosigners: Account[], diff --git a/src/service/BootstrapUtils.ts b/src/service/BootstrapUtils.ts index c5f9567e1..a02bb43cd 100644 --- a/src/service/BootstrapUtils.ts +++ b/src/service/BootstrapUtils.ts @@ -33,7 +33,7 @@ import { get } from 'https'; import * as _ from 'lodash'; import { platform, totalmem } from 'os'; import { basename, dirname, join, resolve } from 'path'; -import { Convert, Deadline, DtoMapping, LinkAction, NetworkType, Transaction, UInt64, VotingKeyLinkTransaction } from 'symbol-sdk'; +import { Convert, DtoMapping, NetworkType } from 'symbol-sdk'; import * as util from 'util'; import { LogType } from '../logger'; import Logger from '../logger/Logger'; @@ -309,25 +309,6 @@ export class BootstrapUtils { }); } - public static createVotingKeyTransaction( - shortPublicKey: string, - action: LinkAction, - presetData: { networkType: NetworkType; votingKeyStartEpoch: number; votingKeyEndEpoch: number }, - deadline: Deadline, - maxFee: UInt64, - ): Transaction { - return VotingKeyLinkTransaction.create( - deadline, - shortPublicKey, - presetData.votingKeyStartEpoch, - presetData.votingKeyEndEpoch, - action, - presetData.networkType, - 1, - maxFee, - ); - } - public static poll(promiseFunction: () => Promise, totalPollingTime: number, pollIntervalMs: number): Promise { const startTime = new Date().getMilliseconds(); return promiseFunction().then(async (result) => { diff --git a/src/service/ConfigLoader.ts b/src/service/ConfigLoader.ts index b5f659795..ab679df3e 100644 --- a/src/service/ConfigLoader.ts +++ b/src/service/ConfigLoader.ts @@ -202,22 +202,24 @@ export class ConfigLoader { oldStoredAccount?.privateKey?.toUpperCase(), ); const newAccount = this.getAccount(networkType, publicKey?.toUpperCase(), privateKey?.toUpperCase()); + + const getAccountLog = (account: Account | PublicAccount) => + `${keyName} Account ${account.address.plain()} Public Ley ${account.publicKey} `; + if (oldAccount && !newAccount) { - logger.info(`Reusing ${keyName} account ${oldAccount.address.plain()}`); + logger.info(`Reusing ${getAccountLog(oldAccount)}...`); return this.toConfig(oldAccount); } if (!oldAccount && newAccount) { - logger.info(`${keyName} Account ${newAccount.address.plain()} has been provided`); + logger.info(`${getAccountLog(newAccount)} has been provided`); return this.toConfig(newAccount); } if (oldAccount && newAccount) { if (oldAccount.address.equals(newAccount.address)) { - logger.info(`Reusing ${keyName} account ${oldAccount.address.plain()}`); + logger.info(`Reusing ${getAccountLog(newAccount)}`); return { ...this.toConfig(oldAccount), ...this.toConfig(newAccount) }; } - logger.info( - `Old ${keyName} Account ${oldAccount.address.plain()} has been changed. New ${keyName} Account is ${newAccount.address.plain()}`, - ); + logger.info(`Old ${getAccountLog(oldAccount)} has been changed. New ${getAccountLog(newAccount)} replaces it.`); return this.toConfig(newAccount); } @@ -282,7 +284,7 @@ export class ConfigLoader { const nodeAccount: NodeAccount = { name, friendlyName, - roles: nodePreset.roles || '', + roles: ConfigLoader.resolveRoles(nodePreset), main: main, transport: transport, }; @@ -298,12 +300,6 @@ export class ConfigLoader { nodePreset.remotePrivateKey, nodePreset.remotePublicKey, ); - if (nodePreset.voting) - nodeAccount.voting = this.toConfig( - oldNodeAccount?.voting - ? PublicAccount.createFromPublicKey(oldNodeAccount.voting.publicKey, networkType) - : Account.generateNewAccount(networkType), - ); if (nodePreset.harvesting) nodeAccount.vrf = this.generateAccount( networkType, @@ -569,7 +565,11 @@ export class ConfigLoader { public loadExistingPresetData(target: string, password: Password): ConfigPreset { const presetData = this.loadExistingPresetDataIfPreset(target, password); if (!presetData) { - throw new Error(`The file ${this.getGeneratedPresetLocation(target)} doesn't exist. Have you executed the 'config' command?`); + throw new Error( + `The file ${this.getGeneratedPresetLocation( + target, + )} doesn't exist. Have you executed the 'config' command? Have you provided the right --target param?`, + ); } return presetData; } @@ -628,7 +628,11 @@ export class ConfigLoader { public loadExistingAddresses(target: string, password: Password): Addresses { const addresses = this.loadExistingAddressesIfPreset(target, password); if (!addresses) { - throw new Error(`The file ${this.getGeneratedAddressLocation(target)} doesn't exist. Have you executed the 'config' command?`); + throw new Error( + `The file ${this.getGeneratedAddressLocation( + target, + )} doesn't exist. Have you executed the 'config' command? Have you provided the right --target param?`, + ); } return addresses; } diff --git a/src/service/ConfigService.ts b/src/service/ConfigService.ts index 650116cec..50555f1e8 100644 --- a/src/service/ConfigService.ts +++ b/src/service/ConfigService.ts @@ -27,6 +27,7 @@ import { Transaction, TransactionMapping, UInt64, + VotingKeyLinkTransaction, VrfKeyLinkTransaction, } from 'symbol-sdk'; import { LogType } from '../logger'; @@ -40,6 +41,7 @@ import { CommandUtils } from './CommandUtils'; import { ConfigLoader } from './ConfigLoader'; import { CryptoUtils } from './CryptoUtils'; import { NemgenService } from './NemgenService'; +import { RemoteNodeService } from './RemoteNodeService'; import { ReportService } from './ReportService'; import { RewardProgramService } from './RewardProgramService'; import { VotingService } from './VotingService'; @@ -68,11 +70,11 @@ export interface ConfigParams { report: boolean; reset: boolean; upgrade: boolean; + offline?: boolean; preset?: Preset; target: string; password?: string; user: string; - pullImages?: boolean; assembly?: string; customPreset?: string; customPresetObject?: CustomPreset; @@ -89,9 +91,9 @@ export class ConfigService { public static defaultParams: ConfigParams = { target: BootstrapUtils.defaultTargetFolder, report: false, + offline: false, reset: false, upgrade: false, - pullImages: false, user: BootstrapUtils.CURRENT_USER, }; private readonly configLoader: ConfigLoader; @@ -139,7 +141,6 @@ export class ConfigService { const presetData: ConfigPreset = this.resolveCurrentPresetData(oldPresetData, password); const addresses = await this.configLoader.generateRandomConfiguration(oldAddresses, presetData); - if (this.params.pullImages) await BootstrapUtils.pullImage(presetData.symbolServerImage); const privateKeySecurityMode = CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode); await BootstrapUtils.mkdir(target); @@ -246,12 +247,17 @@ export class ConfigService { } private async generateNodes(presetData: ConfigPreset, addresses: Addresses): Promise { + const currentFinalizationEpoch = this.params.offline + ? presetData.lastKnownNetworkEpoch + : await new RemoteNodeService().resolveCurrentFinalizationEpoch(presetData); await Promise.all( (addresses.nodes || []).map( - async (account, index) => await this.generateNodeConfiguration(account, index, presetData, addresses), + async (account, index) => + await this.generateNodeConfiguration(account, index, presetData, addresses, currentFinalizationEpoch), ), ); } + private async generateNodeCertificates(presetData: ConfigPreset, addresses: Addresses): Promise { await Promise.all( (addresses.nodes || []).map(async (account) => { @@ -285,7 +291,13 @@ export class ConfigService { ); } - private async generateNodeConfiguration(account: NodeAccount, index: number, presetData: ConfigPreset, addresses: Addresses) { + private async generateNodeConfiguration( + account: NodeAccount, + index: number, + presetData: ConfigPreset, + addresses: Addresses, + currentFinalizationEpoch: number | undefined, + ) { const copyFrom = join(this.root, 'config', 'node'); const name = account.name; @@ -405,7 +417,14 @@ export class ConfigService { copyFileSync(peersApiFile, join(join(brokerConfig, 'resources', 'peers-api.json'))); } - await new VotingService(this.params).run(presetData, account, nodePreset); + await new VotingService(this.params).run( + presetData, + account, + nodePreset, + currentFinalizationEpoch, + undefined, + presetData.nemesis != undefined, + ); } private async generateP2PFile( @@ -468,11 +487,7 @@ export class ConfigService { .map((n) => this.createAccountKeyLinkTransaction(transactionsDirectory, presetData, n)), ); - await Promise.all( - (addresses.nodes || []) - .filter((n) => n.voting) - .map((n) => this.createVotingKeyTransaction(transactionsDirectory, presetData, n)), - ); + await Promise.all((addresses.nodes || []).map((n) => this.createVotingKeyTransactions(transactionsDirectory, presetData, n))); if (presetData.nemesis.mosaics && (presetData.nemesis.transactions || presetData.nemesis.balances)) { logger.info('Opt In mode is ON!!! balances or transactions have been provided'); @@ -594,25 +609,12 @@ export class ConfigService { return await this.storeTransaction(transactionsDirectory, `remote_${node.name}`, signedTransaction.payload); } - private async createVotingKeyTransaction( + private async createVotingKeyTransactions( transactionsDirectory: string, presetData: ConfigPreset, node: NodeAccount, - ): Promise { - if (!node.voting) { - throw new Error('Voting keys should have been generated!!'); - } - - if (!node.main) { - throw new Error('Main keys should have been generated!!'); - } - const voting = BootstrapUtils.createVotingKeyTransaction( - node.voting.publicKey, - LinkAction.Link, - presetData, - Deadline.createFromDTO('1'), - UInt64.fromUint(0), - ); + ): Promise { + const votingFiles = node.voting || []; const mainPrivateKey = await CommandUtils.resolvePrivateKey( presetData.networkType, node.main, @@ -620,9 +622,23 @@ export class ConfigService { node.name, 'creating the voting key link transactions', ); - const account = Account.createFromPrivateKey(mainPrivateKey, presetData.networkType); - const signedTransaction = account.sign(voting, presetData.nemesisGenerationHashSeed); - return await this.storeTransaction(transactionsDirectory, `voting_${node.name}`, signedTransaction.payload); + return Promise.all( + votingFiles.map(async (votingFile) => { + const voting = VotingKeyLinkTransaction.create( + Deadline.createFromDTO('1'), + votingFile.publicKey, + votingFile.startEpoch, + votingFile.endEpoch, + LinkAction.Link, + presetData.networkType, + 1, + UInt64.fromUint(0), + ); + const account = Account.createFromPrivateKey(mainPrivateKey, presetData.networkType); + const signedTransaction = account.sign(voting, presetData.nemesisGenerationHashSeed); + return this.storeTransaction(transactionsDirectory, `voting_${node.name}`, signedTransaction.payload); + }), + ); } private async storeTransaction(transactionsDirectory: string, name: string, payload: string): Promise { diff --git a/src/service/LinkService.ts b/src/service/LinkService.ts index 6b0f3a4ab..d7435ddba 100644 --- a/src/service/LinkService.ts +++ b/src/service/LinkService.ts @@ -18,7 +18,6 @@ import { prompt } from 'inquirer'; import { AccountInfo, AccountKeyLinkTransaction, - AccountLinkVotingKey, Deadline, LinkAction, Transaction, @@ -33,6 +32,7 @@ import { Addresses, ConfigPreset, NodeAccount } from '../model'; import { AnnounceService, TransactionFactory } from './AnnounceService'; import { BootstrapUtils } from './BootstrapUtils'; import { ConfigLoader } from './ConfigLoader'; +import { VotingKeyAccount } from './VotingUtils'; /** * params necessary to announce link transactions network. @@ -46,8 +46,11 @@ export type LinkParams = { useKnownRestGateways: boolean; ready?: boolean; customPreset?: string; + removeOldLinked?: boolean; //TEST ONLY! }; +export type KeyAccount = { publicKey: string }; + const logger: Logger = LoggerFactory.getLogger(LogType.System); export interface LinkServiceTransactionFactoryParams { @@ -56,7 +59,13 @@ export interface LinkServiceTransactionFactoryParams { mainAccountInfo: AccountInfo; deadline: Deadline; maxFee: UInt64; - removeOldLinked?: boolean; + latestFinalizedBlockEpoch?: number; +} + +export interface GenericNodeAccount { + remote?: KeyAccount; + vrf?: KeyAccount; + voting?: VotingKeyAccount[]; } export class LinkService implements TransactionFactory { @@ -86,141 +95,253 @@ export class LinkService implements TransactionFactory { this.params.maxFee, this.params.useKnownRestGateways, this.params.ready, + this.params.target, this.configLoader.mergePresets(presetData, customPreset), addresses, this, ); } - - async createTransactions({ + public async createTransactions({ presetData, nodeAccount, mainAccountInfo, deadline, maxFee, - removeOldLinked, + latestFinalizedBlockEpoch, }: LinkServiceTransactionFactoryParams): Promise { - const transactions: Transaction[] = []; - const unlink = this.params.unlink; const networkType = presetData.networkType; + const nodeName = nodeAccount.name; - logger.info(''); - logger.info(`Creating transactions for node: ${nodeAccount.name}, ca/main account: ${mainAccountInfo.address.plain()}`); + const remoteTransactionFactory = ({ publicKey }: KeyAccount, action: LinkAction): AccountKeyLinkTransaction => + AccountKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee); + const vrfTransactionFactory = ({ publicKey }: KeyAccount, action: LinkAction): VrfKeyLinkTransaction => + VrfKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee); + const votingKeyTransactionFactory = (account: VotingKeyAccount, action: LinkAction): VotingKeyLinkTransaction => { + return VotingKeyLinkTransaction.create( + deadline, + account.publicKey, + account.startEpoch, + account.endEpoch, + action, + networkType, + 1, + maxFee, + ); + }; + logger.info(`Creating transactions for node: ${nodeName}, ca/main account: ${mainAccountInfo.address.plain()}`); + const transactions = await new LinkTransactionGenericFactory(this.params).createGenericTransactions( + nodeName, + { + vrf: mainAccountInfo.supplementalPublicKeys.vrf, + remote: mainAccountInfo.supplementalPublicKeys.linked, + voting: mainAccountInfo.supplementalPublicKeys.voting, + }, + nodeAccount, + latestFinalizedBlockEpoch || presetData.lastKnownNetworkEpoch, + remoteTransactionFactory, + vrfTransactionFactory, + votingKeyTransactionFactory, + ); + //Unlink transactions go first. + return transactions.sort((t1, t2) => t1.linkAction - t2.linkAction); + } +} + +export class LinkTransactionGenericFactory { + constructor(private readonly params: { unlink: boolean; ready?: boolean; removeOldLinked?: boolean }) {} + + public async createGenericTransactions( + nodeName: string, + currentMainAccountKeys: GenericNodeAccount, + nodeAccount: GenericNodeAccount, + latestFinalizedBlockEpoch: number, + remoteTransactionFactory: (keyAccount: KeyAccount, action: LinkAction) => AccountKL, + vrfTransactionFactory: (keyAccount: KeyAccount, action: LinkAction) => VRFKL, + votingKeyTransactionFactory: (account: VotingKeyAccount, action: LinkAction) => VotingKL, + ): Promise<(AccountKL | VRFKL | VotingKL)[]> { + const transactions: (AccountKL | VRFKL | VotingKL)[] = []; + const print = (account: { publicKey: string }) => `public key ${account.publicKey}`; if (nodeAccount.remote) { - const accountTobeLinked = nodeAccount.remote; - const alreadyLinkedAccount = mainAccountInfo.supplementalPublicKeys.linked; - const isAlreadyLinkedSameAccount = alreadyLinkedAccount?.publicKey.toUpperCase() === accountTobeLinked.publicKey.toUpperCase(); - await this.addTransaction( - alreadyLinkedAccount, - isAlreadyLinkedSameAccount, - unlink, - ({ publicKey }, action) => AccountKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee), - nodeAccount, - 'Remote', - nodeAccount.remote, - transactions, - removeOldLinked, - (account) => `public key ${account.publicKey}`, + transactions.push( + ...(await this.addTransaction( + currentMainAccountKeys.remote, + remoteTransactionFactory, + nodeName, + 'Remote', + nodeAccount.remote, + print, + )), ); } if (nodeAccount.vrf) { - const accountTobeLinked = nodeAccount.vrf; - const alreadyLinkedAccount = mainAccountInfo.supplementalPublicKeys.vrf; - const isAlreadyLinkedSameAccount = alreadyLinkedAccount?.publicKey.toUpperCase() === accountTobeLinked.publicKey.toUpperCase(); - await this.addTransaction( - alreadyLinkedAccount, - isAlreadyLinkedSameAccount, - unlink, - ({ publicKey }, action) => VrfKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee), - nodeAccount, - 'VRF', - accountTobeLinked, - transactions, - removeOldLinked, - (account) => `public key ${account.publicKey}`, + transactions.push( + ...(await this.addTransaction(currentMainAccountKeys.vrf, vrfTransactionFactory, nodeName, 'VRF', nodeAccount.vrf, print)), ); } - if (nodeAccount.voting) { - const accountTobeLinked = { - publicKey: nodeAccount.voting.publicKey, - startEpoch: presetData.votingKeyStartEpoch, - endEpoch: presetData.votingKeyEndEpoch, - }; - const alreadyLinkedAccount = (mainAccountInfo.supplementalPublicKeys?.voting || []).find((a) => - LinkService.overlapsVotingAccounts(accountTobeLinked, a), + const votingPrint = (account: VotingKeyAccount) => + `public key ${account.publicKey}, start epoch ${account.startEpoch}, end epoch ${account.endEpoch}`; + if (this.params.unlink) { + transactions.push( + ...(await this.addVotingKeyUnlinkTransactions( + currentMainAccountKeys?.voting || [], + nodeAccount.voting || [], + nodeName, + votingKeyTransactionFactory, + votingPrint, + )), ); + } else { + transactions.push( + ...(await this.addVotingKeyLinkTransactions( + currentMainAccountKeys?.voting || [], + nodeAccount.voting || [], + nodeName, + latestFinalizedBlockEpoch, + votingKeyTransactionFactory, + votingPrint, + )), + ); + } + return transactions; + } + public async addVotingKeyLinkTransactions( + linkedVotingKeyAccounts: VotingKeyAccount[], + votingKeyFiles: VotingKeyAccount[], + nodeName: string, + lastKnownNetworkEpoch: number, + transactionFactory: (transaction: VotingKeyAccount, action: LinkAction) => T, + print: (account: VotingKeyAccount) => string, + ): Promise { + const transactions: T[] = []; + const accountName = 'Voting'; + let remainingVotingKeys: VotingKeyAccount[] = linkedVotingKeyAccounts; + for (const alreadyLinkedAccount of linkedVotingKeyAccounts) { + if ( + alreadyLinkedAccount.endEpoch < lastKnownNetworkEpoch && + (await this.confirmUnlink(accountName, alreadyLinkedAccount, print)) + ) { + const unlinkTransaction = transactionFactory(alreadyLinkedAccount, LinkAction.Unlink); + logger.info( + `Creating Unlink ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print(alreadyLinkedAccount)}.`, + ); + remainingVotingKeys = remainingVotingKeys.filter((a) => a != alreadyLinkedAccount); + transactions.push(unlinkTransaction); + } + } + const activeVotingKeyFiles = votingKeyFiles.filter((a) => a.endEpoch >= lastKnownNetworkEpoch); + for (const accountTobeLinked of activeVotingKeyFiles) { + const alreadyLinkedAccount = remainingVotingKeys.find((a) => + LinkTransactionGenericFactory.overlapsVotingAccounts(accountTobeLinked, a), + ); const isAlreadyLinkedSameAccount = alreadyLinkedAccount?.publicKey.toUpperCase() === accountTobeLinked.publicKey.toUpperCase() && alreadyLinkedAccount?.startEpoch === accountTobeLinked.startEpoch && alreadyLinkedAccount?.endEpoch === accountTobeLinked.endEpoch; - await this.addTransaction( - alreadyLinkedAccount, - isAlreadyLinkedSameAccount, - unlink, - (votingKeyAccount, action) => { - return VotingKeyLinkTransaction.create( - deadline, - votingKeyAccount.publicKey, - votingKeyAccount.startEpoch, - votingKeyAccount.endEpoch, - action, - presetData.networkType, - 1, - maxFee, + let addTransaction = !isAlreadyLinkedSameAccount; + if (alreadyLinkedAccount && !isAlreadyLinkedSameAccount) { + logger.warn( + `Node ${nodeName} is already linked to ${accountName} ${print( + alreadyLinkedAccount, + )} which is different from the configured ${print(accountTobeLinked)}.`, + ); + if (await this.confirmUnlink(accountName, alreadyLinkedAccount, print)) { + const unlinkTransaction = transactionFactory(alreadyLinkedAccount, LinkAction.Unlink); + logger.info( + `Creating Unlink ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print( + alreadyLinkedAccount, + )}.`, ); - }, - nodeAccount, - 'Voting', - accountTobeLinked, - transactions, - removeOldLinked, - (account) => `public key ${account.publicKey}, start epoch ${account.startEpoch}, end epoch ${account.endEpoch}`, + remainingVotingKeys = remainingVotingKeys.filter((a) => a != alreadyLinkedAccount); + transactions.push(unlinkTransaction); + } else { + addTransaction = false; + } + } + + if (remainingVotingKeys.length < 3 && addTransaction) { + const transaction = transactionFactory(accountTobeLinked, LinkAction.Link); + logger.info( + `Creating Link ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print(accountTobeLinked)}.`, + ); + transactions.push(transaction); + remainingVotingKeys.push(accountTobeLinked); + } + } + return transactions; + } + + public async addVotingKeyUnlinkTransactions( + linkedVotingKeyAccounts: VotingKeyAccount[], + votingKeyFiles: VotingKeyAccount[], + nodeName: string, + transactionFactory: (transaction: VotingKeyAccount, action: LinkAction) => T, + print: (account: VotingKeyAccount) => string, + ): Promise { + const transactions: T[] = []; + const accountName = 'Voting'; + let remainingVotingKeys: VotingKeyAccount[] = linkedVotingKeyAccounts; + for (const accountTobeLinked of votingKeyFiles) { + const alreadyLinkedAccount = remainingVotingKeys.find((a) => + LinkTransactionGenericFactory.overlapsVotingAccounts(accountTobeLinked, a), ); + const isAlreadyLinkedSameAccount = + alreadyLinkedAccount?.publicKey.toUpperCase() === accountTobeLinked.publicKey.toUpperCase() && + alreadyLinkedAccount?.startEpoch === accountTobeLinked.startEpoch && + alreadyLinkedAccount?.endEpoch === accountTobeLinked.endEpoch; + + if (alreadyLinkedAccount && isAlreadyLinkedSameAccount) { + if (await this.confirmUnlink(accountName, alreadyLinkedAccount, print)) { + const unlinkTransaction = transactionFactory(alreadyLinkedAccount, LinkAction.Unlink); + logger.info( + `Creating Unlink ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print( + alreadyLinkedAccount, + )}.`, + ); + remainingVotingKeys = remainingVotingKeys.filter((a) => a != alreadyLinkedAccount); + transactions.push(unlinkTransaction); + } + } } return transactions; } - public static overlapsVotingAccounts(x: AccountLinkVotingKey, y: AccountLinkVotingKey): boolean { + public static overlapsVotingAccounts(x: VotingKeyAccount, y: VotingKeyAccount): boolean { return x.endEpoch >= y.startEpoch && x.startEpoch <= y.endEpoch; } - private async addTransaction( - alreadyLinkedAccount: T | undefined, - isAlreadyLinkedSameAccount: boolean, - unlink: boolean, - transactionFactory: (transaction: T, action: LinkAction) => Transaction, - nodeAccount: NodeAccount, + private async addTransaction( + alreadyLinkedAccount: A | undefined, + transactionFactory: (transaction: A, action: LinkAction) => T, + nodeName: string, accountName: string, - accountTobeLinked: T, - transactions: Transaction[], - removeOldLinked: boolean | undefined, - print: (account: T) => string, - ): Promise { - if (unlink) { + accountTobeLinked: A, + print: (account: A) => string, + ): Promise { + const transactions: T[] = []; + const isAlreadyLinkedSameAccount = accountTobeLinked.publicKey.toUpperCase() === alreadyLinkedAccount?.publicKey.toUpperCase(); + if (this.params.unlink) { if (alreadyLinkedAccount) { if (isAlreadyLinkedSameAccount) { const transaction = transactionFactory(accountTobeLinked, LinkAction.Unlink); logger.info( - `Creating Unlink ${transaction.constructor.name} for node ${nodeAccount.name} to ${accountName} ${print( - accountTobeLinked, - )}.`, + `Creating Unlink ${accountName} Transaction for node ${nodeName} to ${accountName} ${print(accountTobeLinked)}.`, ); transactions.push(transaction); } else { logger.warn( - `Node ${nodeAccount.name} is linked to a different ${accountName} ${print( + `Node ${nodeName} is linked to a different ${accountName} ${print( alreadyLinkedAccount, )} and not the configured ${print(accountTobeLinked)}.`, ); - if (await this.confirmUnlink(removeOldLinked, accountName, alreadyLinkedAccount, print)) { + if (await this.confirmUnlink(accountName, alreadyLinkedAccount, print)) { const transaction = transactionFactory(alreadyLinkedAccount, LinkAction.Unlink); logger.info( - `Creating Unlink ${transaction.constructor.name} for node ${nodeAccount.name} to ${accountName} ${print( + `Creating Unlink ${accountName} Transaction for node ${nodeName} to ${accountName} ${print( alreadyLinkedAccount, )}.`, ); @@ -228,24 +349,23 @@ export class LinkService implements TransactionFactory { } } } else { - logger.info(`Node ${nodeAccount.name} is not linked to ${accountName} ${print(accountTobeLinked)}.`); - return; + logger.info(`Node ${nodeName} is not linked to ${accountName} ${print(accountTobeLinked)}.`); } } else { if (alreadyLinkedAccount) { if (isAlreadyLinkedSameAccount) { - logger.info(`Node ${nodeAccount.name} is already linked to ${accountName} ${print(alreadyLinkedAccount)}.`); + logger.info(`Node ${nodeName} is already linked to ${accountName} ${print(alreadyLinkedAccount)}.`); } else { logger.warn( - `Node ${nodeAccount.name} is already linked to ${accountName} ${print( + `Node ${nodeName} is already linked to ${accountName} ${print( alreadyLinkedAccount, )} which is different from the configured ${print(accountTobeLinked)}.`, ); - if (await this.confirmUnlink(removeOldLinked, accountName, alreadyLinkedAccount, print)) { + if (await this.confirmUnlink(accountName, alreadyLinkedAccount, print)) { const unlinkTransaction = transactionFactory(alreadyLinkedAccount, LinkAction.Unlink); logger.info( - `Creating Unlink ${unlinkTransaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print( + `Creating Unlink ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print( alreadyLinkedAccount, )}.`, ); @@ -253,9 +373,7 @@ export class LinkService implements TransactionFactory { const linkTransaction = transactionFactory(accountTobeLinked, LinkAction.Link); logger.info( - `Creating Link ${linkTransaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print( - accountTobeLinked, - )}.`, + `Creating Link ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print(accountTobeLinked)}.`, ); transactions.push(linkTransaction); } @@ -263,22 +381,16 @@ export class LinkService implements TransactionFactory { } else { const transaction = transactionFactory(accountTobeLinked, LinkAction.Link); logger.info( - `Creating Link ${transaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print( - accountTobeLinked, - )}.`, + `Creating Link ${accountName} Transaction from Node ${nodeName} to ${accountName} ${print(accountTobeLinked)}.`, ); transactions.push(transaction); } } + return transactions; } - private async confirmUnlink( - removeOldLinked: boolean | undefined, - accountName: string, - alreadyLinkedAccount: T, - print: (account: T) => string, - ): Promise { - if (removeOldLinked === undefined) { + private async confirmUnlink(accountName: string, alreadyLinkedAccount: T, print: (account: T) => string): Promise { + if (this.params.removeOldLinked === undefined) { return ( this.params.ready || ( @@ -293,6 +405,6 @@ export class LinkService implements TransactionFactory { ).value ); } - return removeOldLinked; + return this.params.removeOldLinked; } } diff --git a/src/service/RemoteNodeService.ts b/src/service/RemoteNodeService.ts new file mode 100644 index 000000000..c52fb5f96 --- /dev/null +++ b/src/service/RemoteNodeService.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { lookup } from 'dns'; +import { ChainInfo, RepositoryFactory, RepositoryFactoryHttp } from 'symbol-sdk'; +import { LogType } from '../logger'; +import Logger from '../logger/Logger'; +import LoggerFactory from '../logger/LoggerFactory'; +import { ConfigPreset } from '../model'; + +const logger: Logger = LoggerFactory.getLogger(LogType.System); + +export interface RepositoryInfo { + repositoryFactory: RepositoryFactory; + restGatewayUrl: string; + chainInfo: ChainInfo; +} +export class RemoteNodeService { + public async resolveCurrentFinalizationEpoch(presetData: ConfigPreset): Promise { + const votingNode = presetData.nodes?.find((n) => n.voting); + if (!votingNode) { + return presetData.lastKnownNetworkEpoch; + } + const remoteNodeService = new RemoteNodeService(); + if (!(await remoteNodeService.isConnectedToInternet())) { + return presetData.lastKnownNetworkEpoch; + } + return (await remoteNodeService.getBestFinalizationEpoch(presetData.knownRestGateways)) || presetData.lastKnownNetworkEpoch; + } + + public async getBestFinalizationEpoch(urls: string[] | undefined): Promise { + if (!urls || !urls.length) { + return undefined; + } + const repositoryInfo = this.sortByHeight(await this.getKnownNodeRepositoryInfos(urls)).find((i) => i); + const finalizationEpoch = repositoryInfo?.chainInfo.latestFinalizedBlock.finalizationEpoch; + if (finalizationEpoch) { + logger.info(`The current network finalization epoch is ${finalizationEpoch}`); + } + return finalizationEpoch; + } + + public async getBestRepositoryInfo(urls: string[]): Promise { + const repositoryInfo = this.sortByHeight(await this.getKnownNodeRepositoryInfos(urls)).find((i) => i); + if (!repositoryInfo) { + throw new Error(`No up and running node could be found out of: \n - ${urls.join('\n - ')}`); + } + logger.info(`Connecting to node ${repositoryInfo.restGatewayUrl}`); + return repositoryInfo; + } + + private sortByHeight(repos: RepositoryInfo[]): RepositoryInfo[] { + return repos + .filter((b) => b.chainInfo) + .sort((a, b) => { + if (!a.chainInfo) { + return 1; + } + if (!b.chainInfo) { + return -1; + } + return b.chainInfo.height.compare(a.chainInfo.height); + }); + } + + public isConnectedToInternet(): Promise { + return new Promise((resolve) => { + lookup('google.com', (err) => { + if (err && err.code == 'ENOTFOUND') { + resolve(false); + } else { + resolve(true); + } + }); + }); + } + + private async getKnownNodeRepositoryInfos(urls: string[]): Promise { + logger.info(`Looking for the best node out of: \n - ${urls.join('\n - ')}`); + return ( + await Promise.all( + urls.map( + async (restGatewayUrl): Promise => { + const repositoryFactory = new RepositoryFactoryHttp(restGatewayUrl); + try { + const chainInfo = await repositoryFactory.createChainRepository().getChainInfo().toPromise(); + return { + restGatewayUrl, + repositoryFactory, + chainInfo, + }; + } catch (e) { + const message = `There has been an error talking to node ${restGatewayUrl}. Error: ${e.message}}`; + logger.warn(message); + return undefined; + } + }, + ), + ) + ) + .filter((i) => i) + .map((i) => i as RepositoryInfo); + } +} diff --git a/src/service/RewardProgramService.ts b/src/service/RewardProgramService.ts index 115c38338..bc55f6840 100644 --- a/src/service/RewardProgramService.ts +++ b/src/service/RewardProgramService.ts @@ -85,6 +85,7 @@ export class RewardProgramService implements TransactionFactory { this.params.maxFee, this.params.useKnownRestGateways, this.params.ready, + this.params.target, this.configLoader.mergePresets(presetData, customPreset), addresses, this, diff --git a/src/service/VotingService.ts b/src/service/VotingService.ts index d422b39b9..54179b400 100644 --- a/src/service/VotingService.ts +++ b/src/service/VotingService.ts @@ -14,64 +14,86 @@ * limitations under the License. */ +import { writeFileSync } from 'fs'; import { join } from 'path'; +import { Account } from 'symbol-sdk'; import { LogType } from '../logger'; import Logger from '../logger/Logger'; import LoggerFactory from '../logger/LoggerFactory'; -import { ConfigAccount, ConfigPreset, NodeAccount, NodePreset } from '../model'; +import { ConfigPreset, NodeAccount, NodePreset } from '../model'; import { BootstrapUtils } from './BootstrapUtils'; -import { ConfigParams } from './ConfigService'; +import { VotingUtils } from './VotingUtils'; -type VotingParams = ConfigParams; - -export interface VotingMetadata { - readonly votingKeyStartEpoch: number; - readonly votingKeyEndEpoch: number; - readonly votingPublicKey: string; - readonly version: number; -} +type VotingParams = { target: string; user: string }; const logger: Logger = LoggerFactory.getLogger(LogType.System); export class VotingService { - private static readonly METADATA_VERSION = 1; - constructor(protected readonly params: VotingParams) {} - public async run(presetData: ConfigPreset, nodeAccount: NodeAccount, nodePreset: NodePreset | undefined): Promise { + public async run( + presetData: ConfigPreset, + nodeAccount: NodeAccount, + nodePreset: NodePreset, + currentNetworkEpoch: number | undefined, + updateVotingKey: boolean | undefined, + nemesisBlock: boolean, + ): Promise { const symbolServerImage = presetData.symbolServerImage; + const networkEpoch = currentNetworkEpoch || presetData.lastKnownNetworkEpoch || 1; + const update = updateVotingKey === undefined ? presetData.autoUpdateVotingKeys : updateVotingKey; + if (!nodePreset?.voting) { + logger.info(`Node ${nodeAccount.name} is not voting.`); + return false; + } + const target = this.params.target; + const votingKeysFolder = join(BootstrapUtils.getTargetNodesFolder(target, true, nodeAccount.name), presetData.votingKeysDirectory); + const votingKeyDesiredFutureLifetime = presetData.votingKeyDesiredFutureLifetime; + const votingKeyDesiredLifetime = presetData.votingKeyDesiredLifetime; + if (votingKeyDesiredFutureLifetime > votingKeyDesiredLifetime) { + throw new Error('votingKeyDesiredFutureLifetime cannot be greater than votingKeyDesiredLifetime'); + } + await BootstrapUtils.mkdir(votingKeysFolder); + const votingUtils = new VotingUtils(); + await BootstrapUtils.deleteFile(join(votingKeysFolder, 'metadata.yml')); + const currentVotingFiles = votingUtils.loadVotingFiles(votingKeysFolder); + const maxVotingKeyEndEpoch = Math.max(currentVotingFiles[currentVotingFiles.length - 1]?.endEpoch || 0, networkEpoch - 1); - if (nodePreset?.voting && nodeAccount.voting) { - const privateKeyTreeFileName = 'private_key_tree1.dat'; - const target = this.params.target; - const votingKeysFolder = join( - BootstrapUtils.getTargetNodesFolder(target, true, nodeAccount.name), - presetData.votingKeysDirectory, - ); - const metadataFile = join(votingKeysFolder, 'metadata.yml'); - if (!(await this.shouldGenerateVoting(presetData, metadataFile, nodeAccount.voting))) { - logger.info(`Voting File for node ${nodePreset.name} has been previously generated. Reusing...`); - return; - } - const votingPrivateKey = nodeAccount?.voting.privateKey; - - if (!votingPrivateKey) { - throw new Error( - 'Voting key should have been previously generated!!! You need to reset your target folder. Please run --reset using your original custom preset.', - ); - } + //This updates the addresses.yml data about existing voting files. If a user puts a manual file into the voting folder, this will update the yml file automatically. + nodeAccount.voting = currentVotingFiles; + if (maxVotingKeyEndEpoch > networkEpoch + votingKeyDesiredFutureLifetime) { + logger.info(`Node ${nodeAccount.name}'s voting files are up-to-date.`); + return false; + } + // First file is created automatically on start, second file may or may not. + if (!update && currentVotingFiles.length > 0) { + logger.warn(''); + logger.warn(`Voting key files are close to EXPIRATION or have EXPIRED!. Run the 'symbol-bootstrap updateVotingKeys' command!`); + logger.warn(''); + return false; + } + const votingKeyStartEpoch = maxVotingKeyEndEpoch + 1; + const votingKeyEndEpoch = maxVotingKeyEndEpoch + votingKeyDesiredLifetime; + const votingAccount = Account.generateNewAccount(presetData.networkType); + const votingPrivateKey = votingAccount.privateKey; + const epochs = votingKeyEndEpoch - votingKeyStartEpoch + 1; + logger.info(`Creating Voting key file of ${epochs} epochs for node ${nodeAccount.name}. This could take a while!`); + const privateKeyTreeFileName = `private_key_tree${currentVotingFiles.length + 1}.dat`; + if (presetData.useExperimentalNativeVotingKeyGeneration) { + logger.info('Voting file is created using the native typescript voting key file generator!'); + const votingFile = await votingUtils.createVotingFile(votingPrivateKey, votingKeyStartEpoch, votingKeyEndEpoch); + writeFileSync(join(votingKeysFolder, privateKeyTreeFileName), votingFile); + } else { + logger.info(`Voting file is created using docker and the default's catapult.tools.votingkey`); + const binds = [`${votingKeysFolder}:/votingKeys:rw`]; const cmd = [ `${presetData.catapultAppFolder}/bin/catapult.tools.votingkey`, `--secret=${votingPrivateKey}`, - `--startEpoch=${presetData.votingKeyStartEpoch}`, - `--endEpoch=${presetData.votingKeyEndEpoch}`, + `--startEpoch=${votingKeyStartEpoch}`, + `--endEpoch=${votingKeyEndEpoch}`, `--output=/votingKeys/${privateKeyTreeFileName}`, ]; - await BootstrapUtils.deleteFolder(votingKeysFolder); - await BootstrapUtils.mkdir(votingKeysFolder); - const binds = [`${votingKeysFolder}:/votingKeys:rw`]; - const userId = await BootstrapUtils.resolveDockerUserFromParam(this.params.user); const { stdout, stderr } = await BootstrapUtils.runImageUsingExec({ catapultAppFolder: presetData.catapultAppFolder, @@ -86,36 +108,26 @@ export class VotingService { logger.error(stderr); throw new Error('Voting key failed. Check the logs!'); } - logger.warn(`A new Voting File for the node ${nodeAccount.name} has been regenerated! `); - logger.warn( - `Remember to send a Voting Key Link transaction from main ${nodeAccount.main.address} using the Voting Public Key ${nodeAccount.voting.publicKey} with startEpoch ${presetData.votingKeyStartEpoch} and endEpoch: ${presetData.votingKeyEndEpoch}`, + } + if (nemesisBlock) { + // For a new private network, voting keys are in the nemesisBlock. + logger.info(''); + logger.info( + `A new Voting File for the node ${nodeAccount.name} has been generated. The link transaction will be included in the nemesis block.`, ); - logger.warn('For linking, you can use symbol-bootstrap link command, the symbol cli, or the symbol desktop wallet. '); - logger.warn('The voting public key is stored in the target`s addresses.yml for reference'); - - const metadata: VotingMetadata = { - votingKeyStartEpoch: presetData.votingKeyStartEpoch, - votingKeyEndEpoch: presetData.votingKeyEndEpoch, - version: VotingService.METADATA_VERSION, - votingPublicKey: nodeAccount.voting.publicKey, - }; - await BootstrapUtils.writeYaml(metadataFile, metadata, undefined); + logger.info(''); } else { - logger.info(`Non-voting node ${nodeAccount.name}.`); - } - } + // For a running network. + logger.warn(''); + logger.warn(`A new Voting File for the node ${nodeAccount.name} has been generated! `); - private async shouldGenerateVoting(presetData: ConfigPreset, metadataFile: string, votingAccount: ConfigAccount): Promise { - try { - const metadata = BootstrapUtils.loadYaml(metadataFile, false) as VotingMetadata; - return ( - metadata.votingPublicKey !== votingAccount.publicKey || - metadata.version !== VotingService.METADATA_VERSION || - metadata.votingKeyStartEpoch !== presetData.votingKeyStartEpoch || - metadata.votingKeyEndEpoch !== presetData.votingKeyEndEpoch + logger.warn( + `Remember to send a Voting Key Link transaction from main ${nodeAccount.main.address} using the Voting Public Key: ${votingAccount.publicKey} with startEpoch: ${votingKeyStartEpoch} and endEpoch: ${votingKeyEndEpoch}`, ); - } catch (e) { - return true; + logger.warn(`For linking, you can use 'symbol-bootstrap link' command, the symbol cli, or the symbol desktop wallet.`); + logger.warn(''); } + nodeAccount.voting = votingUtils.loadVotingFiles(votingKeysFolder); + return true; } } diff --git a/src/service/VotingUtils.ts b/src/service/VotingUtils.ts new file mode 100644 index 000000000..2e436fc36 --- /dev/null +++ b/src/service/VotingUtils.ts @@ -0,0 +1,203 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs'; +import * as noble from 'noble-ed25519'; +import * as forge from 'node-forge'; +import { join } from 'path'; +import { Convert, Crypto } from 'symbol-sdk'; +import * as nacl from 'tweetnacl'; + +const ed25519 = forge.pki.ed25519; +export interface KeyPair { + privateKey: Uint8Array; + publicKey: Uint8Array; +} +export interface CryptoImplementation { + name: string; + createKeyPairFromPrivateKey: (privateKey: Uint8Array) => Promise; + sign: (keyPair: KeyPair, data: Uint8Array) => Promise; +} + +export interface VotingKeyAccount { + readonly startEpoch: number; + readonly endEpoch: number; + readonly publicKey: string; +} + +export type VotingKeyFile = VotingKeyAccount & { filename: string }; + +export class VotingUtils { + public static nobleImplementation: CryptoImplementation = { + name: 'Noble', + createKeyPairFromPrivateKey: async (privateKey: Uint8Array): Promise => { + const publicKey = await noble.getPublicKey(privateKey); + return { privateKey, publicKey: publicKey }; + }, + sign: async (keyPair: KeyPair, data: Uint8Array): Promise => { + return await noble.sign(data, keyPair.privateKey); + }, + }; + + public static forgeImplementation: CryptoImplementation = { + name: 'Forge', + createKeyPairFromPrivateKey: async (privateKey: Uint8Array): Promise => { + const keyPair = ed25519.generateKeyPair({ seed: privateKey }); + return { privateKey, publicKey: keyPair.publicKey }; + }, + + sign: async (keyPair: KeyPair, data: Uint8Array): Promise => { + const secretKey = new Uint8Array(64); + secretKey.set(keyPair.privateKey); + secretKey.set(keyPair.publicKey, 32); + const signature = ed25519.sign({ + // also accepts a forge ByteBuffer or Uint8Array + message: data, + privateKey: secretKey, + }); + return new Uint8Array(signature); + }, + }; + + public static tweetNaClImplementation: CryptoImplementation = { + name: 'TweetNaCl', + createKeyPairFromPrivateKey: async (privateKey: Uint8Array): Promise => { + const { publicKey } = nacl.sign.keyPair.fromSeed(privateKey); + return { privateKey, publicKey }; + }, + + sign: async (keyPair: KeyPair, data: Uint8Array): Promise => { + const secretKey = new Uint8Array(64); + secretKey.set(keyPair.privateKey); + secretKey.set(keyPair.publicKey, 32); + return nacl.sign.detached(data, secretKey); + }, + }; + + public static implementations = [VotingUtils.nobleImplementation, VotingUtils.tweetNaClImplementation, VotingUtils.forgeImplementation]; + + constructor(private readonly implementation: CryptoImplementation = VotingUtils.nobleImplementation) {} + public insert(result: Uint8Array, value: Uint8Array, index: number): number { + result.set(value, index); + return index + value.length; + } + + public async createVotingFile( + secret: string, + votingKeyStartEpoch: number, + votingKeyEndEpoch: number, + unitTestPrivateKeys: Uint8Array[] | undefined = undefined, + ): Promise { + const items = votingKeyEndEpoch - votingKeyStartEpoch + 1; + const headerSize = 64 + 16; + const itemSize = 32 + 64; + const totalSize = headerSize + items * itemSize; + const rootPrivateKey = await this.implementation.createKeyPairFromPrivateKey(Convert.hexToUint8(secret)); + const result = new Uint8Array(totalSize); + //start-epoch (8b), + let index = 0; + index = this.insert(result, Convert.numberToUint8Array(votingKeyStartEpoch, 8), index); + + //end-epoch (8b), + index = this.insert(result, Convert.numberToUint8Array(votingKeyEndEpoch, 8), index); + + // could it have other values???? + //last key identifier (8b) - for fresh file this is 0xFFFF'FFFF'FFFF'FFFF (a.k.a. Invalid_Id) + index = this.insert(result, Convert.hexToUint8('FFFFFFFFFFFFFFFF'), index); + + //last wipe key identifier (8b) - again, for fresh file this is 0xFFFF'FFFF'FFFF'FFFF (Invalid_Id) + index = this.insert(result, Convert.hexToUint8('FFFFFFFFFFFFFFFF'), index); + + // root public key (32b) - this is root public key that is getting announced via vote link tx + index = this.insert(result, rootPrivateKey.publicKey, index); + // start-epoch (8b), \ those two are exactly same one, as top level, reason is this was earlier a tree, + index = this.insert(result, Convert.numberToUint8Array(votingKeyStartEpoch, 8), index); + + //end-epoch (8b), / and each level holds this separately, so we left it as is + index = this.insert(result, Convert.numberToUint8Array(votingKeyEndEpoch, 8), index); + /// what follows are bound keys, there are (end - start + 1) of them. + + // each key is: + for (let i = 0; i < items; i++) { + // random PRIVATE key (32b) + const randomPrivateKey = unitTestPrivateKeys ? unitTestPrivateKeys[i] : Crypto.randomBytes(32); + if (randomPrivateKey.length != 32) { + throw new Error(`Invalid private key size ${randomPrivateKey.length}!`); + } + const randomKeyPar = await this.implementation.createKeyPairFromPrivateKey(randomPrivateKey); + index = this.insert(result, randomPrivateKey, index); + // signature (64b) + // now the signature is usual signature done using ROOT private key on a following data: + // (public key (32b), identifier (8b)) + // + // identifier is simply epoch, but, most importantly keys are written in REVERSE order. + // + // i.e. say your start-epoch = 2, end-epoch = 42 + const identifier = Convert.numberToUint8Array(votingKeyEndEpoch - i, 8); + const signature = await this.implementation.sign(rootPrivateKey, Uint8Array.from([...randomKeyPar.publicKey, ...identifier])); + index = this.insert(result, signature, index); + } + // + // root private key is discarded after file is created. + // header: + // 2, 42, ff.., ff..., (root pub), 2, 42 + // keys: + // (priv key 42, sig 42), (priv key 41, sig 31), ..., (priv key 2, sig 2) + // + // every priv key should be cryptographically random, + + return result; + } + + public readVotingFile(file: Uint8Array): VotingKeyAccount { + //start-epoch (8b), + const votingKeyStartEpoch = Convert.uintArray8ToNumber(file.slice(0, 8)); + //end-epoch (8b), + const votingKeyEndEpoch = Convert.uintArray8ToNumber(file.slice(8, 16)); + const votingPublicKey = Convert.uint8ToHex(file.slice(32, 64)); + + const items = votingKeyEndEpoch - votingKeyStartEpoch + 1; + const headerSize = 64 + 16; + const itemSize = 32 + 64; + const totalSize = headerSize + items * itemSize; + + if (file.length != totalSize) { + throw new Error(`Unexpected voting key file. Expected ${totalSize} but got ${file.length}`); + } + return { + publicKey: votingPublicKey, + startEpoch: votingKeyStartEpoch, + endEpoch: votingKeyEndEpoch, + }; + } + + public loadVotingFiles(folder: string): VotingKeyFile[] { + if (!existsSync(folder)) { + return []; + } + return readdirSync(folder) + .map((filename: string) => { + const currentPath = join(folder, filename); + if (lstatSync(currentPath).isFile() && filename.startsWith('private_key_tree') && filename.endsWith('.dat')) { + return { ...this.readVotingFile(readFileSync(currentPath)), filename }; + } else { + return undefined; + } + }) + .filter((i) => i) + .map((i) => i as VotingKeyFile) + .sort((a, b) => a.startEpoch - b.startEpoch); + } +} diff --git a/src/service/index.ts b/src/service/index.ts index 1e47308c8..dcd0c07fa 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -14,9 +14,11 @@ export * from './ForgeCertificateService'; export * from './LinkService'; export * from './NemgenService'; export * from './PortService'; +export * from './RemoteNodeService'; export * from './ReportService'; export * from './RewardProgramService'; export * from './RunService'; export * from './SshpkService'; export * from './VerifyService'; export * from './VotingService'; +export * from './VotingUtils'; diff --git a/test/service/BootstrapUtils.test.ts b/test/service/BootstrapUtils.test.ts index 41505ffd8..2925b6d15 100644 --- a/test/service/BootstrapUtils.test.ts +++ b/test/service/BootstrapUtils.test.ts @@ -20,7 +20,7 @@ import * as _ from 'lodash'; import 'mocha'; import { it } from 'mocha'; import { totalmem } from 'os'; -import { Account, Deadline, LinkAction, NetworkType, UInt64, VotingKeyLinkTransaction } from 'symbol-sdk'; +import { Account, NetworkType } from 'symbol-sdk'; import { ConfigAccount } from '../../src/model'; import { BootstrapUtils, ConfigLoader, CryptoUtils } from '../../src/service'; import assert = require('assert'); @@ -173,32 +173,6 @@ describe('BootstrapUtils', () => { expect(_.merge(a, b, c)).deep.equals(expected); }); - it('createVotingKeyTransaction v1 short key', async () => { - const networkType = NetworkType.PRIVATE; - const deadline = Deadline.createFromDTO('1'); - const voting = Account.generateNewAccount(networkType); - const presetData = { - networkType, - votingKeyStartEpoch: 1, - votingKeyEndEpoch: 3, - }; - const maxFee = UInt64.fromUint(20); - - const transaction = BootstrapUtils.createVotingKeyTransaction( - voting.publicKey, - LinkAction.Link, - presetData, - deadline, - maxFee, - ) as VotingKeyLinkTransaction; - expect(transaction.version).to.be.eq(1); - expect(transaction.linkedPublicKey).to.be.eq(voting.publicKey); - expect(transaction.startEpoch).to.be.eq(presetData.votingKeyStartEpoch); - expect(transaction.endEpoch).to.be.eq(presetData.votingKeyEndEpoch); - expect(transaction.maxFee).to.be.deep.eq(maxFee); - expect(transaction.deadline).to.be.deep.eq(deadline); - }); - it('should remove null values', () => { const compose = { version: '2.4', diff --git a/test/service/LinkService.test.ts b/test/service/LinkService.test.ts index 732e91779..1f2834dac 100644 --- a/test/service/LinkService.test.ts +++ b/test/service/LinkService.test.ts @@ -110,12 +110,12 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual', password, reset: false, preset: Preset.testnet, assembly: 'dual', - customPresetObject: { nodeUseRemoteAccount: true, }, @@ -124,7 +124,8 @@ describe('LinkService', () => { await new BootstrapService().config(params); await new BootstrapService().link(params); } catch (e) { - expect(e.message.indexOf('ECONNREFUSED'), `Not a connection error: ${e.message}`).to.be.greaterThan(-1); + expect(e.message.indexOf('No up and running node could be found out of:'), e.message).to.be.greaterThan(-1); + expect(e.message.indexOf('http://localhost:3000'), e.message).to.be.greaterThan(-1); } }); @@ -132,12 +133,57 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual-voting', password, reset: false, preset: Preset.testnet, + offline: true, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + nodeUseRemoteAccount: true, + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const maxFee = UInt64.fromUint(10); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(3); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + expect(addresses!.nodes![0].voting?.length).eq(1); + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + }); + + it('LinkService create transactions when dual + voting + lastKnownNetworkEpoch1', async () => { + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + target: 'target/tests/testnet-dual-voting-network-1', + password, + reset: false, + offline: true, + preset: Preset.testnet, customPreset: './test/unit-test-profiles/voting_preset.yml', customPresetObject: { + lastKnownNetworkEpoch: 1, nodeUseRemoteAccount: true, }, assembly: 'dual', @@ -145,27 +191,311 @@ describe('LinkService', () => { const { addresses, presetData } = await new BootstrapService().config(params); const maxFee = UInt64.fromUint(10); const nodeAccount = addresses.nodes![0]; - const notLinkedAcccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); const transactionFactoryParams: LinkServiceTransactionFactoryParams = { presetData, deadline: Deadline.create(1), nodeAccount: nodeAccount, maxFee: maxFee, - mainAccountInfo: notLinkedAcccountInfo, - removeOldLinked: true, + mainAccountInfo: notLinkedAccountInfo, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); expect(transactions.length).eq(3); assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); - assertVotingTransaction(transactions[2], LinkAction.Link, nodeAccount.voting!.publicKey, 1, 360); + expect(addresses!.nodes![0].voting?.length).eq(1); + expect(presetData.lastKnownNetworkEpoch).eq(1); + expect(presetData.votingKeyDesiredLifetime).eq(720); + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + }); + + it('LinkService create transactions when dual + voting + upgrade (manual)', async () => { + const target = 'target/tests/testnet-dual-voting-upgrade-manual'; + const maxFee = UInt64.fromUint(10); + { + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + offline: true, + target: target, + password, + reset: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + nodeUseRemoteAccount: true, + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(3); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + expect(addresses!.nodes![0].voting?.length).eq(1); + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + } + + { + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + target: target, + password, + upgrade: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + nodeUseRemoteAccount: true, + lastKnownNetworkEpoch: 323 + 720 - 10, //in the future, last known Network Epoch is pretty high! + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + expect(addresses!.nodes![0].voting?.length).eq(1); + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(2); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + } + }); + + it('LinkService create transactions when dual + voting + upgrade with gap (automatic)', async () => { + const target = 'target/tests/testnet-dual-voting-upgrade-automatic-gap'; + const maxFee = UInt64.fromUint(10); + let originalLasKnownNetworkEpoch: number; + { + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + offline: true, + target: target, + password, + reset: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + autoUpdateVotingKeys: true, + nodeUseRemoteAccount: true, + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(3); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + expect(addresses!.nodes![0].voting?.length).eq(1); + originalLasKnownNetworkEpoch = presetData.lastKnownNetworkEpoch; + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + } + + { + const lastKnownNetworkEpoch = 323 + 720 - 10; + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + target: target, + password, + upgrade: true, + offline: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + nodeUseRemoteAccount: true, + autoUpdateVotingKeys: true, + lastKnownNetworkEpoch: lastKnownNetworkEpoch, //in the future, last known Network Epoch is pretty high! + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + expect(addresses!.nodes![0].voting?.length).eq(2); + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(3); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + + expect(lastKnownNetworkEpoch).gt(originalLasKnownNetworkEpoch + presetData.votingKeyDesiredLifetime); + console.log(lastKnownNetworkEpoch); + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![1].publicKey, + lastKnownNetworkEpoch, + lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + } + }); + + it('LinkService create transactions when dual + voting + upgrade continue (automatic)', async () => { + const target = 'target/tests/testnet-dual-voting-upgrade-automatic-continue'; + const maxFee = UInt64.fromUint(10); + let originalLasKnownNetworkEpoch: number; + { + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + offline: true, + target: target, + password, + reset: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + autoUpdateVotingKeys: true, + nodeUseRemoteAccount: true, + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + expect(transactions.length).eq(3); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + expect(addresses!.nodes![0].voting?.length).eq(1); + originalLasKnownNetworkEpoch = presetData.lastKnownNetworkEpoch; + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + } + + { + // in the future, but before current files expires. + const lastKnownNetworkEpoch = 917 - 10; + const params = { + ...ConfigService.defaultParams, + ...LinkService.defaultParams, + ready: true, + target: target, + password, + upgrade: true, + offline: true, + preset: Preset.testnet, + customPreset: './test/unit-test-profiles/voting_preset.yml', + customPresetObject: { + nodeUseRemoteAccount: true, + autoUpdateVotingKeys: true, + lastKnownNetworkEpoch: lastKnownNetworkEpoch, //in the future, last known Network Epoch is pretty high! + }, + assembly: 'dual', + }; + const { addresses, presetData } = await new BootstrapService().config(params); + const nodeAccount = addresses.nodes![0]; + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const transactionFactoryParams: LinkServiceTransactionFactoryParams = { + presetData, + deadline: Deadline.create(1), + nodeAccount: nodeAccount, + maxFee: maxFee, + mainAccountInfo: notLinkedAccountInfo, + }; + expect(addresses!.nodes![0].voting?.length).eq(2); + const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); + // The original link needs to still be linked as it's current! + expect(transactions.length).eq(4); + assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + expect(lastKnownNetworkEpoch).lt(originalLasKnownNetworkEpoch + presetData.votingKeyDesiredLifetime); + console.log(lastKnownNetworkEpoch); + assertVotingTransaction( + transactions[2], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + originalLasKnownNetworkEpoch, + originalLasKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + assertVotingTransaction( + transactions[3], + LinkAction.Link, + addresses!.nodes![0].voting![1].publicKey, + originalLasKnownNetworkEpoch + presetData.votingKeyDesiredLifetime, + originalLasKnownNetworkEpoch + presetData.votingKeyDesiredLifetime + presetData.votingKeyDesiredLifetime - 1, + ); + } }); it('LinkService create transactions when dual + voting and already linked (different voting key)', async () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, + offline: true, target: 'target/tests/testnet-dual-voting', password, reset: false, @@ -186,7 +516,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: true, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -198,30 +527,41 @@ describe('LinkService', () => { LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.linked!.publicKey, ); - assertTransaction(transactions[1], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); assertTransaction( - transactions[2], + transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.vrf!.publicKey, ); - assertTransaction(transactions[3], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); assertVotingTransaction( - transactions[4], + transactions[2], LinkAction.Unlink, alreadyLinkedAccountInfoDto?.account.supplementalPublicKeys.voting!.publicKeys[0].publicKey, 1, 360, ); - assertVotingTransaction(transactions[5], LinkAction.Link, nodeAccount.voting!.publicKey, 1, 360); + assertTransaction(transactions[3], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[4], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + + assertVotingTransaction( + transactions[5], + LinkAction.Link, + addresses!.nodes![0].voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); + + expect(addresses!.nodes![0].voting?.length).eq(1); }); it('LinkService create transactions when dual + voting and already linked (same voting key)', async () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, + offline: true, target: 'target/tests/testnet-dual-voting', password, reset: false, @@ -252,13 +592,7 @@ describe('LinkService', () => { publicKey: 'AEC02D888F268EBDAD038E19BF2EE182E36F207F29BF05ABFC5E20EAF2D4F719', }, voting: { - publicKeys: [ - { - publicKey: addresses!.nodes![0].voting!.publicKey, - startEpoch: 1, - endEpoch: 360, - }, - ], + publicKeys: [addresses!.nodes![0].voting![0]], }, }, activityBuckets: [], @@ -277,7 +611,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: true, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -289,14 +622,14 @@ describe('LinkService', () => { LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.linked!.publicKey, ); - assertTransaction(transactions[1], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); - assertTransaction( - transactions[2], + transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.vrf!.publicKey, ); + assertTransaction(transactions[2], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[3], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); }); @@ -304,6 +637,8 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, + offline: true, target: 'target/tests/testnet-dual-voting', password, reset: false, @@ -334,13 +669,7 @@ describe('LinkService', () => { publicKey: 'AEC02D888F268EBDAD038E19BF2EE182E36F207F29BF05ABFC5E20EAF2D4F719', }, voting: { - publicKeys: [ - { - publicKey: addresses!.nodes![0].voting!.publicKey, - startEpoch: 2, - endEpoch: 400, - }, - ], + publicKeys: [{ ...addresses!.nodes![0].voting![0], startEpoch: 400, endEpoch: 500 }], }, }, activityBuckets: [], @@ -359,7 +688,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: true, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -371,29 +699,38 @@ describe('LinkService', () => { LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.linked!.publicKey, ); - assertTransaction(transactions[1], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); assertTransaction( - transactions[2], + transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.vrf!.publicKey, ); - assertTransaction(transactions[3], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); assertVotingTransaction( - transactions[4], + transactions[2], LinkAction.Unlink, alreadyLinkedAccountInfoDto?.account.supplementalPublicKeys.voting!.publicKeys[0].publicKey, - 2, 400, + 500, + ); + assertTransaction(transactions[3], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); + assertTransaction(transactions[4], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); + + assertVotingTransaction( + transactions[5], + LinkAction.Link, + nodeAccount.voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, ); - assertVotingTransaction(transactions[5], LinkAction.Link, nodeAccount.voting!.publicKey, 1, 360); }); it('LinkService create transactions when dual + voting and already linked not removed', async () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, + offline: true, target: 'target/tests/testnet-dual-voting', password, reset: false, @@ -403,6 +740,7 @@ describe('LinkService', () => { nodeUseRemoteAccount: true, }, assembly: 'dual', + removeOldLinked: false, }; const alreadyLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](alreadyLinkedAccountInfoDto); const { addresses, presetData } = await new BootstrapService().config(params); @@ -414,7 +752,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: false, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -425,6 +762,7 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual', password, reset: true, @@ -437,14 +775,13 @@ describe('LinkService', () => { const { addresses, presetData } = await new BootstrapService().config(params); const maxFee = UInt64.fromUint(10); const nodeAccount = addresses.nodes![0]; - const notLinkedAcccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); const transactionFactoryParams: LinkServiceTransactionFactoryParams = { presetData, deadline: Deadline.create(1), nodeAccount: nodeAccount, maxFee: maxFee, - mainAccountInfo: notLinkedAcccountInfo, - removeOldLinked: true, + mainAccountInfo: notLinkedAccountInfo, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -457,6 +794,7 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual', password, reset: true, @@ -476,7 +814,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: true, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -487,14 +824,13 @@ describe('LinkService', () => { LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.linked!.publicKey, ); - assertTransaction(transactions[1], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); - assertTransaction( - transactions[2], + transactions[1], TransactionType.VRF_KEY_LINK, LinkAction.Unlink, alreadyLinkedAccountInfo.supplementalPublicKeys.vrf!.publicKey, ); + assertTransaction(transactions[2], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); assertTransaction(transactions[3], TransactionType.VRF_KEY_LINK, LinkAction.Link, nodeAccount.vrf!.publicKey); }); @@ -502,6 +838,7 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual', password, reset: true, @@ -510,6 +847,7 @@ describe('LinkService', () => { nodeUseRemoteAccount: true, }, assembly: 'dual', + removeOldLinked: false, }; const alreadyLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](alreadyLinkedAccountInfoDto); const { addresses, presetData } = await new BootstrapService().config(params); @@ -521,7 +859,6 @@ describe('LinkService', () => { nodeAccount: nodeAccount, maxFee: maxFee, mainAccountInfo: alreadyLinkedAccountInfo, - removeOldLinked: false, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -532,6 +869,7 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-dual-not-remote', password, reset: false, @@ -545,14 +883,13 @@ describe('LinkService', () => { const { addresses, presetData } = await new BootstrapService().config(params); const maxFee = UInt64.fromUint(10); const nodeAccount = addresses.nodes![0]; - const notLinkedAcccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); const transactionFactoryParams: LinkServiceTransactionFactoryParams = { presetData, deadline: Deadline.create(1), nodeAccount: nodeAccount, maxFee: maxFee, - mainAccountInfo: notLinkedAcccountInfo, - removeOldLinked: true, + mainAccountInfo: notLinkedAccountInfo, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); @@ -564,6 +901,7 @@ describe('LinkService', () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, target: 'target/tests/testnet-api', password, reset: false, @@ -576,64 +914,28 @@ describe('LinkService', () => { const { addresses, presetData } = await new BootstrapService().config(params); const maxFee = UInt64.fromUint(10); - const notLinkedAcccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); const transactionFactoryParams: LinkServiceTransactionFactoryParams = { presetData, deadline: Deadline.create(1), nodeAccount: addresses.nodes![0], maxFee: maxFee, - mainAccountInfo: notLinkedAcccountInfo, - removeOldLinked: true, + mainAccountInfo: notLinkedAccountInfo, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); expect(transactions.length).eq(0); }); - it('should test overlaps', function () { - expect( - LinkService.overlapsVotingAccounts( - { startEpoch: 1, endEpoch: 10, publicKey: 'A' }, - { startEpoch: 1, endEpoch: 2, publicKey: 'A' }, - ), - ).true; - - expect( - LinkService.overlapsVotingAccounts( - { startEpoch: 1, endEpoch: 4, publicKey: 'A' }, - { startEpoch: 4, endEpoch: 10, publicKey: 'A' }, - ), - ).true; - - expect( - LinkService.overlapsVotingAccounts( - { startEpoch: 1, endEpoch: 4, publicKey: 'A' }, - { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, - ), - ).false; - - expect( - LinkService.overlapsVotingAccounts( - { startEpoch: 11, endEpoch: 20, publicKey: 'A' }, - { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, - ), - ).false; - - expect( - LinkService.overlapsVotingAccounts( - { startEpoch: 10, endEpoch: 20, publicKey: 'A' }, - { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, - ), - ).true; - }); - it('LinkService create transactions when api and voting', async () => { const params = { ...ConfigService.defaultParams, ...LinkService.defaultParams, + ready: true, + offline: true, target: 'target/tests/testnet-api-voting', password, - reset: false, + reset: true, preset: Preset.testnet, customPreset: './test/unit-test-profiles/voting_preset.yml', customPresetObject: { @@ -644,19 +946,24 @@ describe('LinkService', () => { const { addresses, presetData } = await new BootstrapService().config(params); const maxFee = UInt64.fromUint(10); const nodeAccount = addresses.nodes![0]; - const notLinkedAcccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); + const notLinkedAccountInfo: AccountInfo = (AccountHttp as any)['toAccountInfo'](notLinkedAccountDto); const transactionFactoryParams: LinkServiceTransactionFactoryParams = { presetData, deadline: Deadline.create(1), nodeAccount: nodeAccount, maxFee: maxFee, - mainAccountInfo: notLinkedAcccountInfo, - removeOldLinked: true, + mainAccountInfo: notLinkedAccountInfo, }; const transactions = await new LinkService(params).createTransactions(transactionFactoryParams); expect(transactions.length).eq(2); assertTransaction(transactions[0], TransactionType.ACCOUNT_KEY_LINK, LinkAction.Link, nodeAccount.remote!.publicKey); - assertVotingTransaction(transactions[1], LinkAction.Link, nodeAccount.voting!.publicKey, 1, 360); + assertVotingTransaction( + transactions[1], + LinkAction.Link, + nodeAccount.voting![0].publicKey, + presetData.lastKnownNetworkEpoch, + presetData.lastKnownNetworkEpoch + presetData.votingKeyDesiredLifetime - 1, + ); }); }); diff --git a/test/service/LinkTransactionGenericFactory.test.ts b/test/service/LinkTransactionGenericFactory.test.ts new file mode 100644 index 000000000..88501e841 --- /dev/null +++ b/test/service/LinkTransactionGenericFactory.test.ts @@ -0,0 +1,348 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@oclif/test'; +import { it } from 'mocha'; +import { LinkAction } from 'symbol-sdk'; +import { GenericNodeAccount, KeyAccount, LinkTransactionGenericFactory, VotingKeyAccount } from '../../src/service'; + +type GenericTransaction = + | { name: string; action: LinkAction; publicKey: string } + | { readonly endEpoch: number; name: string; action: LinkAction; readonly publicKey: string; readonly startEpoch: number }; + +describe('LinkTransactionGenericFactory', () => { + it('should test overlaps', () => { + expect( + LinkTransactionGenericFactory.overlapsVotingAccounts( + { startEpoch: 1, endEpoch: 10, publicKey: 'A' }, + { startEpoch: 1, endEpoch: 2, publicKey: 'A' }, + ), + ).true; + + expect( + LinkTransactionGenericFactory.overlapsVotingAccounts( + { startEpoch: 1, endEpoch: 4, publicKey: 'A' }, + { startEpoch: 4, endEpoch: 10, publicKey: 'A' }, + ), + ).true; + + expect( + LinkTransactionGenericFactory.overlapsVotingAccounts( + { startEpoch: 1, endEpoch: 4, publicKey: 'A' }, + { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, + ), + ).false; + + expect( + LinkTransactionGenericFactory.overlapsVotingAccounts( + { startEpoch: 11, endEpoch: 20, publicKey: 'A' }, + { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, + ), + ).false; + + expect( + LinkTransactionGenericFactory.overlapsVotingAccounts( + { startEpoch: 10, endEpoch: 20, publicKey: 'A' }, + { startEpoch: 5, endEpoch: 10, publicKey: 'A' }, + ), + ).true; + }); + const remoteTransactionFactory = (account: KeyAccount, action: LinkAction) => ({ ...account, action, name: 'remote' }); + const vrfTransactionFactory = (account: KeyAccount, action: LinkAction) => ({ ...account, action, name: 'vrf' }); + const votingKeyTransactionFactory = (account: VotingKeyAccount, action: LinkAction) => ({ ...account, action, name: 'voting' }); + + it('creates generic transactions when empty', async () => { + const currentLinkedAccounts: GenericNodeAccount = {}; + const toLinkNodeAccounts: GenericNodeAccount = {}; + const transactions = await new LinkTransactionGenericFactory({ + unlink: false, + ready: true, + }).createGenericTransactions( + 'SomeName', + currentLinkedAccounts, + toLinkNodeAccounts, + 1, + remoteTransactionFactory, + vrfTransactionFactory, + votingKeyTransactionFactory, + ); + expect(transactions).deep.equals([]); + }); + + const basicTest = async ( + currentLinkedAccounts: GenericNodeAccount, + toLinkNodeAccounts: GenericNodeAccount, + latestFinalizedBlockEpoch: number, + unlink: boolean, + expectedTransactions: GenericTransaction[], + ) => { + const transactions: GenericTransaction[] = await new LinkTransactionGenericFactory({ + unlink: unlink, + ready: true, + }).createGenericTransactions( + 'SomeName', + currentLinkedAccounts, + toLinkNodeAccounts, + latestFinalizedBlockEpoch, + remoteTransactionFactory, + vrfTransactionFactory, + votingKeyTransactionFactory, + ); + console.log(JSON.stringify(transactions)); + expect(transactions).deep.equals(expectedTransactions); + }; + + it('creates generic transactions when VRF and Remote', async () => { + const currentLinkedAccounts: GenericNodeAccount = {}; + const toLinkNodeAccounts: GenericNodeAccount = { + remote: { + publicKey: 'remote1', + }, + vrf: { + publicKey: 'vrf1', + }, + }; + const expectedTransactions = [ + { + action: LinkAction.Link, + publicKey: 'remote1', + name: 'remote', + }, + { + action: LinkAction.Link, + publicKey: 'vrf1', + name: 'vrf', + }, + ]; + await basicTest(currentLinkedAccounts, toLinkNodeAccounts, 1, false, expectedTransactions); + }); + + it('creates generic transactions when VRF and different Remote', async () => { + const currentLinkedAccounts: GenericNodeAccount = { + remote: { + publicKey: 'remote1', + }, + vrf: { + publicKey: 'vrf1', + }, + }; + const toLinkNodeAccounts: GenericNodeAccount = { + remote: { + publicKey: 'remote2', + }, + vrf: { + publicKey: 'vrf1', + }, + }; + + const expectedTransactions = [ + { publicKey: 'remote1', action: 0, name: 'remote' }, + { publicKey: 'remote2', action: 1, name: 'remote' }, + ]; + await basicTest(currentLinkedAccounts, toLinkNodeAccounts, 1, false, expectedTransactions); + }); + it('creates generic transactions when different VRF no remote', async () => { + const currentLinkedAccounts: GenericNodeAccount = { + remote: { + publicKey: 'remote1', + }, + vrf: { + publicKey: 'vrf1', + }, + }; + const toLinkNodeAccounts: GenericNodeAccount = { + vrf: { + publicKey: 'vrf2', + }, + }; + const expectedTransactions = [ + { publicKey: 'vrf1', action: 0, name: 'vrf' }, + { publicKey: 'vrf2', action: 1, name: 'vrf' }, + ]; + await basicTest(currentLinkedAccounts, toLinkNodeAccounts, 1, false, expectedTransactions); + }); + + it('creates generic transactions when new vrf, remote and voting', async () => { + const currentLinkedAccounts: GenericNodeAccount = {}; + const toLinkNodeAccounts: GenericNodeAccount = { + vrf: { + publicKey: 'vrf1', + }, + remote: { + publicKey: 'remote1', + }, + + voting: [ + { + publicKey: 'V1', + startEpoch: 70, + endEpoch: 97, + }, + { + publicKey: 'V2', + startEpoch: 98, + endEpoch: 125, + }, + { + publicKey: 'V3', + startEpoch: 126, + endEpoch: 153, + }, + { + publicKey: 'V4', + startEpoch: 154, + endEpoch: 181, + }, + { + publicKey: 'V5', + startEpoch: 182, + endEpoch: 209, + }, + { + publicKey: 'V6', + startEpoch: 210, + endEpoch: 237, + }, + { + publicKey: 'V7', + startEpoch: 238, + endEpoch: 265, + }, + { + publicKey: 'V8', + startEpoch: 266, + endEpoch: 293, + }, + { + publicKey: 'V9', + startEpoch: 294, + endEpoch: 321, + }, + { + publicKey: 'V10', + startEpoch: 322, + endEpoch: 349, + }, + ], + }; + const expectedTransactions = [ + { publicKey: 'remote1', action: 1, name: 'remote' }, + { publicKey: 'vrf1', action: 1, name: 'vrf' }, + { publicKey: 'V1', startEpoch: 70, endEpoch: 97, action: 1, name: 'voting' }, + { publicKey: 'V2', startEpoch: 98, endEpoch: 125, action: 1, name: 'voting' }, + { publicKey: 'V3', startEpoch: 126, endEpoch: 153, action: 1, name: 'voting' }, + ]; + await basicTest(currentLinkedAccounts, toLinkNodeAccounts, 1, false, expectedTransactions); + }); + + it('creates generic transactions voting old and different voting', async () => { + const currentLinkedAccounts: GenericNodeAccount = { + vrf: { + publicKey: 'vrf1', + }, + remote: { + publicKey: 'remoteOLD', + }, + voting: [ + { + publicKey: 'V1', + startEpoch: 70, + endEpoch: 97, // remove is old + }, + { + publicKey: 'V2', + startEpoch: 98, + endEpoch: 125, + }, + { + publicKey: 'V3', + startEpoch: 126, + endEpoch: 200, // remove is different + }, + ], + }; + const toLinkNodeAccounts: GenericNodeAccount = { + vrf: { + publicKey: 'vrf1', + }, + remote: { + publicKey: 'remote1', + }, + + voting: [ + { + publicKey: 'V1', + startEpoch: 70, + endEpoch: 97, + }, + { + publicKey: 'V2', + startEpoch: 98, + endEpoch: 125, + }, + { + publicKey: 'V3', + startEpoch: 126, + endEpoch: 153, + }, + { + publicKey: 'V4', + startEpoch: 154, + endEpoch: 181, + }, + { + publicKey: 'V5', + startEpoch: 182, + endEpoch: 209, + }, + { + publicKey: 'V6', + startEpoch: 210, + endEpoch: 237, + }, + { + publicKey: 'V7', + startEpoch: 238, + endEpoch: 265, + }, + { + publicKey: 'V8', + startEpoch: 266, + endEpoch: 293, + }, + { + publicKey: 'V9', + startEpoch: 294, + endEpoch: 321, + }, + { + publicKey: 'V10', + startEpoch: 322, + endEpoch: 349, + }, + ], + }; + const expectedTransactions = [ + { publicKey: 'remoteOLD', action: 0, name: 'remote' }, + { publicKey: 'remote1', action: 1, name: 'remote' }, + { publicKey: 'V1', startEpoch: 70, endEpoch: 97, action: 0, name: 'voting' }, + { publicKey: 'V3', startEpoch: 126, endEpoch: 200, action: 0, name: 'voting' }, + { publicKey: 'V3', startEpoch: 126, endEpoch: 153, action: 1, name: 'voting' }, + { publicKey: 'V4', startEpoch: 154, endEpoch: 181, action: 1, name: 'voting' }, + ]; + await basicTest(currentLinkedAccounts, toLinkNodeAccounts, 99, false, expectedTransactions); + }); +}); diff --git a/test/service/NetworkPresetUpgrade.test.ts b/test/service/NetworkPresetUpgrade.test.ts new file mode 100644 index 000000000..77a246a85 --- /dev/null +++ b/test/service/NetworkPresetUpgrade.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { it } from 'mocha'; +import { ConfigPreset } from '../../src/model'; +import { BootstrapUtils, Preset, RemoteNodeService } from '../../src/service'; + +describe('NetworkPresetUpgrade', () => { + const patchNetworkPreset = async (preset: Preset): Promise => { + const root = './'; + const presetLocation = `${root}/presets/${preset}/network.yml`; + const networkPreset: ConfigPreset = BootstrapUtils.loadYaml(presetLocation, false); + + const epoch = await new RemoteNodeService().getBestFinalizationEpoch(networkPreset.knownRestGateways); + if (!epoch) { + throw new Error('Epoch could not be resolved!!'); + } + networkPreset.lastKnownNetworkEpoch = epoch; + await BootstrapUtils.writeYaml(presetLocation, networkPreset, undefined); + }; + it('should patch testnet lastKnownNetworkEpoch', () => { + return patchNetworkPreset(Preset.testnet); + }); + it('should patch mainnet lastKnownNetworkEpoch', () => { + return patchNetworkPreset(Preset.mainnet); + }); +}); diff --git a/test/service/VotingUtils.test.ts b/test/service/VotingUtils.test.ts new file mode 100644 index 000000000..e0aa7423d --- /dev/null +++ b/test/service/VotingUtils.test.ts @@ -0,0 +1,168 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@oclif/test'; +import { readFileSync } from 'fs'; +import 'mocha'; +import { Convert, KeyPair } from 'symbol-sdk'; +import { VotingKeyAccount, VotingUtils } from '../../src/service'; +describe('VotingUtils', () => { + async function assertVotingKey( + expectedVotingKeyFile: Uint8Array, + privateKey: string, + votingKeyStartEpoch: number, + votingKeyEndEpoch: number, + ) { + const headerSize = 64 + 16; + const itemSize = 32 + 64; + //This files have been created from the original catapult tools's votingkey + + const votingPublicKey = Convert.uint8ToHex(KeyPair.createKeyPairFromPrivateKeyString(privateKey).publicKey); + const items = (expectedVotingKeyFile.length - headerSize) / itemSize; + + const unitTestPrivateKeys: Uint8Array[] = []; + // each key is: + for (let i = 0; i < items; i++) { + // random PRIVATE key (32b) + const start = headerSize + i * itemSize; + const end = start + 32; + const unitTestPrivateKey = expectedVotingKeyFile.slice(start, end); + unitTestPrivateKeys.push(unitTestPrivateKey); + } + + const service = new VotingUtils(); + const votingKeyFile = await service.createVotingFile(privateKey, votingKeyStartEpoch, votingKeyEndEpoch, unitTestPrivateKeys); + expect(votingKeyFile.length).eq(expectedVotingKeyFile.length); + const header = votingKeyFile.subarray(0, headerSize); + const expectedHeader = expectedVotingKeyFile.subarray(0, headerSize); + expect(header).deep.eq(expectedHeader); + expect(Convert.uint8ToHex(votingKeyFile)).eq(Convert.uint8ToHex(expectedVotingKeyFile)); + const expected: VotingKeyAccount = { + startEpoch: votingKeyStartEpoch, + endEpoch: votingKeyEndEpoch, + publicKey: votingPublicKey, + }; + expect(service.readVotingFile(votingKeyFile)).deep.eq(expected); + } + + it('createVotingFile voting key 1', async () => { + const votingKeyStartEpoch = 5; + const votingKeyEndEpoch = 10; + const privateKey = 'EFE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1'; + const testFile = new Uint8Array(readFileSync('./test/votingkeys/private_key_tree1.dat')); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + it.skip('createVotingFile voting key 2', async () => { + // TOO SLOW! I had to disable it. + const votingKeyStartEpoch = 1; + const votingKeyEndEpoch = 26280; + const privateKey = 'EFE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1'; + const testFile = new Uint8Array(readFileSync('./test/votingkeys/private_key_tree2.dat')); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + it('createVotingFile voting key 3', async () => { + const votingKeyStartEpoch = 10; + const votingKeyEndEpoch = 10; + const privateKey = 'EFE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1'; + const testFile = new Uint8Array(readFileSync('./test/votingkeys/private_key_tree3.dat')); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + it('createVotingFile voting key 4', async () => { + const votingKeyStartEpoch = 1; + const votingKeyEndEpoch = 1000; + const privateKey = 'EFE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1'; + const testFile = new Uint8Array(readFileSync('./test/votingkeys/private_key_tree4.dat')); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + it('createVotingFile voting key 5', async () => { + const votingKeyStartEpoch = 1; + const votingKeyEndEpoch = 10000; + const privateKey = 'AAAAF0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1'; + const testFile = new Uint8Array(readFileSync('./test/votingkeys/private_key_tree5.dat')); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + it('createVotingFile voting key gimre 1', async () => { + const votingKeyStartEpoch = 13; + const votingKeyEndEpoch = 34; + const privateKey = '0000000000000000000000000000000000000000000000000000000000000001'; + const testFile = Convert.hexToUint8( + '0d000000000000002200000000000000ffffffffffffffffffffffffffffffff4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba290d0000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000003745e8481844ef3e0cb6b23b3b55a95ee0dd233d4472efb591ba6210e758e0577fb3c487c9c9c92b1d8b0893364aad0c98167713b739fbb2caa8e01ca1e7cf600000000000000000000000000000000000000000000000000000000000000000536d91ac8eb78b1af20b2576be6e267b6ce48a69121937346d785745862881ed21c2fdb43d00a8dd5c517c5c78cb5a6f36a27dc88e45a1c12f7855ea3e9af9408000000000000000000000000000000000000000000000000000000000000000870f396b7f93e7a221501e21dfd27a4843e87ba779b0029e540a36f89755b51aa405b8fe50456745e86c7c814276d245aaedd2cf419118d305c92f03402869402000000000000000000000000000000000000000000000000000000000000000d34ffc8bce0520ac55966b354930feed16d517bef17c14c340cf3dd967748db2963edd6658bb28d7a797ac25d94582b30d5a9f5fb98e44673a763ddd77b58ff0700000000000000000000000000000000000000000000000000000000000000150761ef44b6f00d9b49f9a056c35b65aad81f708498c2892b28c870a9b8f5a1812ee7913b5d864edc6960b2ea576f2576909e0992fa75c83aa785853c25a47c0e0000000000000000000000000000000000000000000000000000000000000022f75d71a44e9deb35d919d691349cc31df2931ed679313838e827a3410dc1d252d0b08fa725fa366e0e0e2079d8866b267be7bfe7a9ee715a2379f34767dbe60d00000000000000000000000000000000000000000000000000000000000000378912c364cd475c6e5ba59dcc4c9a35004e4e902748f9724c145725c051869f2e6b7e8aa9b3e305558d2891227e365f780b11b2e692a68173fac7895378beff03000000000000000000000000000000000000000000000000000000000000005928fca21c7fabf3e194e8435c41b9352e24ef2ea6f45d08a6e4133734941c2a521e0382fdce9aec7987f52f80d3bca92337a445262c4aaee7c5c97e7a76aeba0000000000000000000000000000000000000000000000000000000000000000903b4efbba4a926aed1db317b6de0fbbc5e70b99e885b4937a3aae63252a3e4164b4a98bac77890460611af6b38aaa7ac67168cc473c5b763aea1b32f2036dd70800000000000000000000000000000000000000000000000000000000000000e97a7f0ecfe7fb752d2b97345b437a14232b70f5ec93a9413225dce4c634b60991dec0ef49e53e3561569709ddce244185d2ef676d69e4d5f380bd0aed884c8504000000000000000000000000000000000000000000000000000000000000007968ecc24af8312888e7fdd358c55dbe70523304e5a2723d3c6627ad0525f07b24cc586626e3add8f0d96f7e9b3ef2c18da53d1fd4e637ac6fe7e9a99df234f00d0000000000000000000000000000000000000000000000000000000000000062f3430b40025bb4fdd57e6ef557cd340a2edb9d2cec26fc61d9427b22bbcdd3ea6584ddd3b71d38bd0a5cc42d9d131bdcf982fef21bff521d889456f40c9cce0400000000000000000000000000000000000000000000000000000000000000db16e586d65cf3253ce72f57a720885e4f71bcb6f66662b853b14e7a3f9b4713c2d91a3408dd6a8ced8876c781f5155cc5647c2bbba4bca869ccde14abc0668002000000000000000000000000000000000000000000000000000000000000003dce7be3b0d78a155f0c8c1a7198c620a4e8b23839b3c55f2559053945a3667f63896785b45626a060e91c25be0f1cdbf2dd739a8c4b6be8d753081cb1e449c30b000000000000000000000000000000000000000000000000000000000000001817170bf187a5e6972baf4858c4bcb58ee698f3722bd5d9403af7bf93dc380062e79c0cda9af08154990f15f3a97fa237e760efd456c1c309f94348ccbe140a0e00000000000000000000000000000000000000000000000000000000000000559bae65ac572c4e034b9a0f11a9e7ded83abb7e1549bf0563781a7602e2cb9b3d5b5aa784df1f9b898896f3e3d5bfc7993934316156d2c41ed1176c2bd2397d0e000000000000000000000000000000000000000000000000000000000000006de2314a2d851afac51fb455bba223355feb9292d8c1b8f4b8adc59d8a6b827b776dd6061d891d93e8c924f42da6ec430fcb158b557180d2af1d02d27224022f0d00000000000000000000000000000000000000000000000000000000000000c23cc4de002506742812d45ae0343586aa816f2cd6c118b76ec3fa3c2d4273840424295981b808bf1cab489e8bb1fba6ab70d7c35a9b68d7db19dc9ee2bf7f9206000000000000000000000000000000000000000000000000000000000000002f4801ea571ac8c324a4b77922e3484f33b8a47f18301260a3bdc4e1d25eb66b042e12d922787c081ba382bf00d61e3ec88a5972625f6363921c165ab37a58c60200000000000000000000000000000000000000000000000000000000000000f194dc9d492b6e1e810a9931d588490b8ff216c26609241c7becc5744b702ea36715571399d69c7db4cc02a482af37b853a6a5fee9761688e61e33db4029ea700f000000000000000000000000000000000000000000000000000000000000002019c8643b2eaeb25f0e92744804647378855e72fdb6db471c52fcf0e99f8bf7c0ee611a2d4d0802fd18cd54874fa0e01eb939ba214c8e716653db27800dfa0b0f0000000000000000000000000000000000000000000000000000000000000011a4710bfce396facb91adf88ffeb079a3d5ce83b63e15b535b7c296ae0ac0645f52d2c0576a88f2c376ca61614cb75cdd5b45de7ca72f6ab8c77f8af5f0e36700', + ); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + it('createVotingFile voting key gimre 2', async () => { + const votingKeyStartEpoch = 1482; + const votingKeyEndEpoch = 1500; + const privateKey = '4CB5ABF6AD79FBF5ABBCCAFCC269D85CD2651ED4B885B5869F241AEDF0A5BA29'; + const testFile = Convert.hexToUint8( + 'ca05000000000000dc05000000000000ffffffffffffffffffffffffffffffffcff1a82f51efef1c1718ba68b9ba3aa89532eb4521b134c19dbec1978b7bdfdbca05000000000000dc05000000000000030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021224c47f7117ad5788ffadaa69a2f61ad40841a594692fa803d295dc6873fd8e0fed324a52726d47827af42181d40db98baa9f7e73053ab492576fac599f2e6eb0005060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223248aeb5f9c5cbcf898d3a5a5aee0c1c1271dc5b04bc3cd442193b4545968fde1c0c0155214306c031596635bdafaa2dc48a90ae6565678bc71ef9a68a22991cd0c08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627d1a06bf2c59efcc00fac9fc03e6249f08e1d149f08a2a6d8807c5b492a5665afeb742080fa0128e7f02905e3cf067e26c64330e94eada239075f5cbee1a2860b0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c89e5f9c466ae80ed0a45963e629f1daf927fdcb638be81d8ff157390bf4a669a9fc2bb8de8d4153ef5487150fda31e9079756638e2cd1debb3647e497955c70315161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334042fa8d4a86cdc3c14ece0a785383f7ed6b1de70397f37b3aa9a254c04321987ed5626e59333f9f1a5d0845ceaad1acced1934de4ad45fe95989aac3503ce40b22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404133dcb0c7755c62c00035ebfc783d783034f741b1e7bef0630d038d5eed9196076939bc585af25bbb898cb8441205a4da089e5a2f4c8e6d3f9d98a8519709920c3738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f5051525354555637b88b9631358ac8ddf9ab3e349f84527ced5d4b780b56975861f55f7920a32cfefb23d8685ad808a820621b858b932bdb9c4d088e6ca4e30e5c76b7996bee05595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778b3df77acd72732ca035fbef2befdb98428859d3225ca66eeb4a00d1ecfdd582872e99192acdb38e139928f36a0622d4f9cc39e1f2fe8df51ac33942d34f9fa0b909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf5f83915097d2a1b4891e4f7addd549476f2354c4aa34015f53dc28b8760b95cb2283ea6f352e975d463eaaa6f0fa3a2dd05fbf6e0ff89c2137811d3d0ce3ce01e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff00010203040506070858bb18e3f31c9415c725955799c5733f47fddfbad344ced76aa3351c70ee2ca1935a7d9ef54edb2041d78dfd683551a3c2e3bac8e004bdab2ca11639f7612b0f797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90919293949596979817a1f6459e0c3d9f7bd8dede08fcc5ec842b84735f830a86f464ee6359da4c984c5d0a96a594f04bbfe18e933af4adf5262101d4fbaf26d0f58b76894b925f0862636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80816cb0ca1bb406e7c4cb2b11e16a481527f7628af11ce56173f861473ac9ea54106f2f2c7378f7ef92deec95c1b47ae80bcd3828225d36c87d8ac8fee8d09f660edbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9faf0b4d53d015b5b1f4543871edc23144f1e39105fc995e298d9359bc8f97f11b4210d942003391189cd47d71a1513824da980c79ae4dfa7eb06e2fb142ec1c60b3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c246976894bd286cbd03bded94c17f46cf06424c7100f626c75a1349648518a85e367bb13ec2cd730290a2c36e4f3be97437798789f8508da77fc2353416a480018191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637820d7e0b4cbe6ea18c51646d5812d5466b6d117c159bddbe9ba80ce380d1986785876cae05b84576296ee86d46ec39ec8af5aaa480942aeddb1ec0e3b5c29b0155565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737420e0afef1ca2e914f90f14de387e98d01995bda1d603df4a08c8e4669c3f5eb9d2ff06ba0407375ccf9864926b89b25c46c24fef76d392c34ef9e63c3076f0006d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c231cbc08c42c7827cd35e80fb632a951f17211aeb7d32a9aee10ee96218722129ea4d4d3ca4d452b417e7a0cdef64b3ed68786c4b92846fa2db2f456af55ad0bc2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e16243e13e89dfbace1b04ff8f65121760fb7f2d2af76c2af19f338c5e53ad96e5eb3c23d4115bf2cb300e71102430010eeebf0049cd755069a90aaaefa4c7640c2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4ebec6a949ce9b25f0d793865f8f49ce74c571586fa1e872141d21a3223a442811250c4c5fe74cac3a729be52d33ba34212c203ef20b380fb72dd81cb825cd880a', + ); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + it('createVotingFile voting key gimre 3', async () => { + const votingKeyStartEpoch = 4294967293; + const votingKeyEndEpoch = 4294967295; + const privateKey = '201552D1EC8F3FD62B3D03C09CD083E381BE6C3AED72FB21852298913CAAAFB1'; + const testFile = Convert.hexToUint8( + 'fdffffff00000000ffffffff00000000ffffffffffffffffffffffffffffffff089b5ce0f9f9c951c783751b0511cafa1270b68590a0e491a9a3f520d3602090fdffffff00000000ffffffff0000000012f98b7cb64a6d840931a2b624fb1eacafa2c25c3ef0018cd67e8d470a248b2f7c4b52452002d535ca1450ec03d42a0f401a2b5313acb86388a757651490773aaaf16c39ad78d5dd4c1f1c8d8cf7341e20dbe2b1b851f73c3e86c75536758305b5593870940f28daee262b26367b69143ad85e43048d23e624f4ed8008c0427f2a63a3224589dd9611b8a156a584946ff76e9cecb5b2e59bf038253730fea8bf40735253838b4a017a0b8a755214d4ebde7e9b15a9be271551554f14b6fe1c056cfc879abcca78f5a4c9739852c7c643aec3990e93bf4c6f685eb58224b16a5902bc0c6deb7fdfc86aa4293246182a66b277af4247d9f26e581d3dd8ad6e3309c12d261319c5798075daaa43cf9067c7aa2bb5028698bfd65779611e71de7505', + ); + await assertVotingKey(testFile, privateKey, votingKeyStartEpoch, votingKeyEndEpoch); + }); + + type VectorData = { + privateKey: string; + publicKey: string; + length: number; + data: string; + signature: string; + }; + + const testSignVectorList: VectorData[] = JSON.parse(readFileSync('./test/votingkeys/2.test-sign.json', 'utf8')); + VotingUtils.implementations.forEach((implementation) => + it(`2.test-sign.json ${implementation.name} `, async () => { + for (const vector of testSignVectorList) { + const keyPair = { + privateKey: Convert.hexToUint8(vector.privateKey), + publicKey: Convert.hexToUint8(vector.publicKey), + }; + const privateKey = keyPair.privateKey; + expect(await implementation.createKeyPairFromPrivateKey(privateKey)).deep.eq(keyPair); + const resolvedSignature = await implementation.sign(keyPair, Convert.hexToUint8(vector.data)); + expect(Convert.uint8ToHex(resolvedSignature)).eq(vector.signature); + } + }), + ); +}); diff --git a/test/supernode.yml b/test/supernode.yml index 7a41a903d..1d021f4ff 100644 --- a/test/supernode.yml +++ b/test/supernode.yml @@ -1,8 +1,14 @@ #privateKeySecurityMode: PROMPT_MAIN -nodeUseRemoteAccount: false +nodeUseRemoteAccount: true logLevel: 'Debug' +#lastKnownNetworkEpoch: 70 +votingKeyDesiredLifetime: 28 # create short voting key files, 28 is the minimun +#votingKeyDesiredFutureLifetime: 300 # create files for at least 60 epochs in the future, this will create voting key file 2 and file 3 +useExperimentalNativeVotingKeyGeneration: true # Use new native ts generation +symbolServerImage: symbolplatform/symbol-server-private:gcc-voting-key-unlink-rollback-f2633f4a4c +symbolRestImage: 'symbolplatform/symbol-rest:2.3.6-alpha' nodes: - - voting: false + - voting: true rewardProgram: 'supernode' host: fboucquez-agent-symbollocal.ngrok.io agentUrl: https://fboucquez-agent-symbollocal.ngrok.io diff --git a/test/votingkeys/2.test-sign.json b/test/votingkeys/2.test-sign.json new file mode 100644 index 000000000..0851307cd --- /dev/null +++ b/test/votingkeys/2.test-sign.json @@ -0,0 +1,464 @@ +[ + { + "privateKey": "ABF4CF55A2B3F742D7543D9CC17F50447B969E6E06F5EA9195D428AB12B7318D", + "publicKey": "4DB881D07086498C3626F1F84EF89D7E08E5D8293298400F27CA98C92AB2D271", + "length": 41, + "data": "8CE03CD60514233B86789729102EA09E867FC6D964DEA8C2018EF7D0A2E0E24BF7E348E917116690B9", + "signature": "31D272F0662915CAC43AB7D721CAF65D8601F52B2E793EA1533E7BC20E04EA97B74859D9209A7B18DFECFD2C4A42D6957628F5357E3FB8B87CF6A888BAB4280E" + }, + { + "privateKey": "6AA6DAD25D3ACB3385D5643293133936CDDDD7F7E11818771DB1FF2F9D3F9215", + "publicKey": "F7BBE3BB4DBF9698122DA02EB8A6EDE55F1EF90D0C64819E8A792231A2A0B143", + "length": 49, + "data": "E4A92208A6FC52282B620699191EE6FB9CF04DAF48B48FD542C5E43DAA9897763A199AAA4B6F10546109F47AC3564FADE0", + "signature": "F21E4BE0A914C0C023F724E1EAB9071A3743887BB8824CB170404475873A827B301464261E93700725E8D4427A3E39D365AFB2C9191F75D33C6BE55896E0CC00" + }, + { + "privateKey": "8E32BC030A4C53DE782EC75BA7D5E25E64A2A072A56E5170B77A4924EF3C32A9", + "publicKey": "41C7467803C694DC7CB1D11384AD35BF63873E21EC04454E434FE64934942621", + "length": 40, + "data": "13ED795344C4448A3B256F23665336645A853C5C44DBFF6DB1B9224B5303B6447FBF8240A2249C55", + "signature": "939CD8932093571E24B21EA53F1359279BA5CFC32CE99BB020E676CF82B0AA1DD4BC76FCDE41EF784C06D122B3D018135352C057F079C926B3EFFA7E73CF1D06" + }, + { + "privateKey": "C83CE30FCB5B81A51BA58FF827CCBC0142D61C13E2ED39E78E876605DA16D8D7", + "publicKey": "4CD65AE31B90557EA0F80BCA0748AE1C91C9A1FB53666E8DCCC176774B94E52A", + "length": 49, + "data": "A2704638434E9F7340F22D08019C4C8E3DBEE0DF8DD4454A1D70844DE11694F4C8CA67FDCB08FED0CEC9ABB2112B5E5F89", + "signature": "9B4AFBB7B96CAD7726389C2A4F31115940E6EEE3EA29B3293C82EC8C03B9555C183ED1C55CA89A58C17729EFBA76A505C79AA40EC618D83124BC1134B887D305" + }, + { + "privateKey": "2DA2A0AAE0F37235957B51D15843EDDE348A559692D8FA87B94848459899FC27", + "publicKey": "37C877158F0BCCEF264475AF113494A0A385CB01CDA2ABCEC93C76A8EFC537A8", + "length": 40, + "data": "D2488E854DBCDFDB2C9D16C8C0B2FDBC0ABB6BAC991BFE2B14D359A6BC99D66C00FD60D731AE06D0", + "signature": "7AF2F0D9B30DE3B6C40605FDD4EBA93ECE39FA7458B300D538EC8D0ABAC1756DEFC0CA84C8A599954313E58CE36EFBA4C24A82FD6BB8127023A58EFC52A8410A" + }, + { + "privateKey": "0C066261FB1B18EBF2A9BCDEDA81EB47D5A3745438B3D0B9D19B75885AD0A154", + "publicKey": "F5328D4F30DE46CF43F09C228AFC47CF358FAE6A69057C6298E7356047C056F4", + "length": 41, + "data": "F15CB706E29FCFBCB324E38CBAC62BB355DEDDB845C142E970F0C029EA4D05E59FD6ADF85573CF1775", + "signature": "1D660056554F8AF19DE1F6774A4434B092CE3AAF68CAA302CCF77DC7AB4735C135545B1861297387E8902DBA7FCCE69D4A25DF996E3F0C33938B4B89EECF6B03" + }, + { + "privateKey": "EF3D8E22A592F04C3A31AA736E10901757A821D053F1A49A525B4EC91EACDEE3", + "publicKey": "6FBC2C208318E49104E25A34434CD5F55849D9C9F53D4B26C2049634B93B28FA", + "length": 50, + "data": "6C3E4387345740B8D62CF0C9DEC48F98C292539431B2B54020D8072D9CB55F0197F7D99FF066AFCF9E41EA8B7AEA78EB082D", + "signature": "032F9FCA99BE7B72D4F76792DD5ADFEED94FCCC75E4B311292534C5CCA64205BBC4B1E92E734B94B99995A186ECD0B1768F24DF9CEF744DC9A922FEF96533509" + }, + { + "privateKey": "F7FB79743E9BA957D2A4F1BD95CEB1299552ABECAF758BF840D2DC2C09F3E3CB", + "publicKey": "39AC2ABE9AD7A73E0ED1B238AF32C1E9CC051236161CDAA1D2A7FD67AC3C7FE0", + "length": 42, + "data": "55D8E60C307EE533B1AF9FF677A2DE40A6EACE722BCC9EB5D79907B420E533BC06DB674DAFBD9F43D672", + "signature": "DE32F224B6A4C8C3761D3F4904F03D8ED15D9B13AEE5C5671E03131560819FC9DF23F61CA5984B19C287DE63C7410A081B6E2F772D33193A4B48DED5056D6C0A" + }, + { + "privateKey": "8CC9A2469A77FAD18B44B871B2B6932CD354641D2D1E84403F746C4FFF829791", + "publicKey": "8D6756F82F2CAA567A08663784852D066815132157AAE94948E81AD0B14DFFE6", + "length": 42, + "data": "D9B8BE2F71B83261304E333D6E35563DC3C36C2EB5A23E1461B6E95AA7C6F381F9C3BD39DEAA1B6DF2F9", + "signature": "1A6499428B2E8BEA6370B31363E93552BA597746EE074A276867A3889F9CB50BB85E4D17C1F4140555C9862CCD80300830D2517AE6BE308B8FAB8C6412F2B205" + }, + { + "privateKey": "A247ABBEF0C1AFFBF021D1AFF128888550532FC0EDD77BC39F6EF5312317EC47", + "publicKey": "C23BFD8E37575D392F5A8B5F1EC7F0E1A228BFA33941566A33A692C6A6B7A70A", + "length": 47, + "data": "4A5F07EB713932532FC3132C96EFDC45862FE7A954C1D2AE4640AFDF4728FB58C65E8A4EBFE0D53D5797D5146442B9", + "signature": "96E67B41AED22D8D77D3E7F2212DB5324A27D83FC5974C7F6B497E0A70C216C40066F78D0E68E4BB36490246C327B85236BAC9BA2057ADC8D25869F33759D009" + }, + { + "privateKey": "163D69079DDAD1F16695C47D81C3B72F869B2FDD50E6E47113DB6C85051A6EDE", + "publicKey": "EB344803F7BF51AC9238E8670686A4EDD90960F3682B29F0F4CAE7C2D265E132", + "length": 41, + "data": "65FE5C1A0214A59644892E5AC4216F09FBB4E191B89BFB63D6540177D25EF9E3714850B8453BD6B2B6", + "signature": "D0441615DE0A07DB213D18DC97BFF8857C629D1E1AE3EE6E3CDAA3DA0221ED36415BFE5E8C2D2F9848B8139A031158ED748EE315B8A8B18C7C2D365F99C1FE0B" + }, + { + "privateKey": "7B061BF90EB760971B9EC66A96FD6609635CA4B531F33E3C126B9AE6FDB3D491", + "publicKey": "3E26D45143A5DAF42ACC54B05A26F95F33F99BFB63FDB6F3E17F22B8362B3579", + "length": 45, + "data": "A17F5CE39B9BA7B7CF1147E515D6AA84B22FD0E2D8323A91367198FC6C3AFF04EBB21FC2BDBE7BC0364E8040A9", + "signature": "8E2F0CED0546D6D2F0C59DCFCF2110A0200D331ABBE5A3B6867594EC2A7E3BA98D18DDA2B63CF7BFE6252F50B82823FC62B66F5E29AC71BBDD0A3A5B412C600E" + }, + { + "privateKey": "C9F8CCBF761CEC00AB236C52651E76B5F46D90F8936D44D40561ED5C277104DE", + "publicKey": "03447511451D7052D526094FD59BB9C6270863911C6B657448353FBA55B8ED4A", + "length": 47, + "data": "3D7E33B0ECEAD8269966E9DCD192B73EB8A12573FC8A5FDFBE5753541026EF2E49F5280CBA9BC2515A049B3A1C1B49", + "signature": "E68AAA2D854D411492EA26FFDBF69E6236531D2BBEA6B1015BDCF08A31F0E0030D2A177F2F375F2591A49E745B54EBEFA44D71C6B27B780C195E6C9045C68F06" + }, + { + "privateKey": "EBFA409AC6F987DF476858DD35310879BF564EEB62984A52115D2E6C24590124", + "publicKey": "F09832C0F3470136F34466DF2C12312C36D51B923260092FBB71188C74623F7A", + "length": 52, + "data": "0C37564F718EDA683AA6F3E9AB2487620B1A8B5C8F20ADB3B2D7550AF0D635371E531F27CEBE76A2ABCC96DE0875BDAE987A45AC", + "signature": "DC9A32E94379B259FE77E53762AD759250747162BBF9832B25AA502E9D5BBA23AABB74BA39470F111ADCE67D7647342E502342499B2AC34D2E19A1BB49D1BA0F" + }, + { + "privateKey": "F993F61902B7DA332F2BB001BAA7ACCAF764D824EB0CD073315F7EC43158B8FB", + "publicKey": "ABCD89B9740576399D85CE6EA630A2F887B1AA22EF50059178224C64EB56CE19", + "length": 42, + "data": "B7DD613BC9C364D9EEB9A52636D72BC881DFC81A836B6537BBB928BFF5B73831358947EA9EDEA1570550", + "signature": "D770890819CE6B117F4E9CE754C70716116880FAF67C34DFAE789A4131AA9A9EEDAD713EA363AC12073FE36D224CE37C15B7D92068C3C5CE06BEAABE9EDCAE0A" + }, + { + "privateKey": "05188C09C31B4BB63F0D49B47CCC1654C2ABA907B8C6C0A82EE403E950169167", + "publicKey": "603C051831C696E3F9B83574D75CE73B44DEF4002DDDEB6128ECA42DA902D928", + "length": 44, + "data": "BB8E22469D1C7F1D5418563E8781F69ECCB56678BD36D8919F358C2778562FF6B50DE916C12D44F1A778A7F3", + "signature": "0BE56B9D1C7509070EADE0CB817DC7D7B89B46158E917AF8544AE3441802D071F7A2FEA9293D76869EABD0CB2BB30CA7043D2A9B7F07B49EC06B9D0E8907C40B" + }, + { + "privateKey": "EABE57E1A916EBBFFA4BA7ABC7F23E83D4DEB1338816CC1784D7495D92E98D0B", + "publicKey": "388DED7692931BA9621E08DCD0F4C3F9F3E5F2F26A2A5B456EF99509390705A9", + "length": 44, + "data": "3F2C2D6682EE597F2A92D7E560AC53D5623550311A4939D68ADFB904045ED8D215A9FDB757A2368EA4D89F5F", + "signature": "2D56049F2C92173FADE821CF3BABFBE78BE249C72664C528A59998899AE83CF2E8B17EF71C5D2B42C1E81FE1ADE2558C16FC308AF19641E7EEC6FFF46788170D" + }, + { + "privateKey": "FEF7B893B4B517FAB68CA12D36B603BC00826BF3C9B31A05149642AE10BB3F55", + "publicKey": "6C5E1C025D03D1C8492D2BE99B263D746D2B33C4E7BF8ED670F76B556293E8F8", + "length": 47, + "data": "38C69F884045CDBEEBE4478FDBD1CCC6CF00A08D8A3120C74E7167D3A2E26A67A043B8E5BD198F7B0CE0358CEF7CF9", + "signature": "0176BC5AC41091F5F26FFB906A0AD59CA6BF09A1CC93AAB944706656752E9AF953DCA16482E7DFB836264394699A00DCEA0ED32B28280B5A6047E193CB805407" + }, + { + "privateKey": "16228BEC9B724300A37E88E535FC1C58548D34D7148B57C226F2B3AF974C1822", + "publicKey": "B0BE6C2B4882E9F2D199C867121990F887F31A1D09C1FF2EC9386AB4F21D812E", + "length": 44, + "data": "A3D7B122CD4431B396B20D8CC46CC73ED4A5253A44A76FC83DB62CDC845A2BF7081D069A857955A161CCCF84", + "signature": "8405727BFE8EA916BD2B9F169026528DED0A8D913DF217AD3AF0A9F10D0ABDFBFC5187A9C2AE2CB49DD0D76388C1A1E7CFF7D1E938134C1ED8E407CBECF01F0F" + }, + { + "privateKey": "2DC3F5F0A0BC32C6632534E1E8F27E59CBE0BF7617D31AFF98098E974C828BE7", + "publicKey": "9AF057D0C28A517A13EDDD6861FF68BAB5862915CE1DCA12FF4E1C2EED7E4C66", + "length": 42, + "data": "BDAE276D738B9758EA3D322B54FD12FE82B767E8D817D8EF3D41F78705748E28D15E9C506962A1B85901", + "signature": "7340B72E36115DC472871C149EE74D8030766960FD62FAC35A38B2198D1836AA9538F253CE048891AF2199DE224CB6C0586696140B6977433E6BBA5DC5F3D10A" + }, + { + "privateKey": "3ECB9CE7A3C4477B8324D787E94475CBE018A95140447AB7EDC4E87980171C92", + "publicKey": "15278A71B893798D5FBBCCCAB31DD860EDE563E9D3B9227DB5E80EA25A981EAD", + "length": 40, + "data": "19C3797889404967A54C91A737738400FCD4F4FD1C5EBEFCE1A5694B7CEDEB6BEA395088ED38F83A", + "signature": "12856F010456E647BA799D142FF5908EA14322B54AB22F8EC39FFAC8712F9AABB259014E49CBB56E2A8001E5FA69CCDDE3EEAC2434298160B3CA3478E6ABCD05" + }, + { + "privateKey": "3898C88D44091FE30A6E58D67BF5641E9D7CB3186D085EDB9EE05F6E7A79EA50", + "publicKey": "8B2F1438CFAC26E414EE376ED31FC7088F4687BAEC13F7E556C8BE7704F655D0", + "length": 51, + "data": "FCB7A3B57B2E22069F22ABDB6DB6DD467BEEFFAD0F3B74A999BAEE14831AD517E0B536152151B80C8D3D7C407F9CB889A52C06", + "signature": "D576A5D0E713C72E5D048CE38B15753E3E3F2C23825F471D69C599A40CA2B2F28A02127AAEFD1E028A08DB91A640F3DD50420349D0C477F11B3CDC8C6C79FE09" + }, + { + "privateKey": "5DB7FE111A7B068ED616D82FE649CA2CC08825960F2FA1D941B4347B344E8622", + "publicKey": "620B6072065B23ECDBC3EFE28CCA40E81FC410711FE199570AD44D88604AE014", + "length": 50, + "data": "10E6253361F0102A1090EB861F35F6764AE71FA0ACC46E3CA0632C5B7C65017AC3C3E05894F22CEA18B37E6FA28D219874C9", + "signature": "C873CF9357A46CB0BA91DC0FC83E65BA7194E7B347B2771DAA3EABCFC672F02F9FCD55F5738D3CBD69872731C0A3AC954064E9BF802131ED08477478A13F9E06" + }, + { + "privateKey": "5854F9B2A283AE951945CA5E151E46DE7F54B3612D5D590AF7C097D842DB399D", + "publicKey": "688A67B337DB39AADF31B10653761236FF7FA8712845A22872D735A4B03C7F84", + "length": 52, + "data": "2AC0A5C801F0DF0A7D9C66DAA3EB6F1231298700D628585D9295505408E799B3EF7B5313E9AB26554CCFBFDAD4EA7BAB31F46B50", + "signature": "A9CCE10862E910071D0A1974E65578E5536AACB9E1D90B1274A30D8E4CB437BCD06A221902B59306B825A43C6BAB021A9ECE238C136E2A966897D2594141350D" + }, + { + "privateKey": "0C3ED7A3E226754E03A90CE8E2918BD2EC622E0B258E69F98C8B7A09543A4D5B", + "publicKey": "16FB59F907524009730BCB9F860C8C5A1109A9E8F194275DA0B9F5A2085E2D02", + "length": 40, + "data": "FF60983E0C5D21D2FB83C67598D560F3CF0E28AE667B5616AAA58A059666CD8CF826B026243C92CF", + "signature": "2E32A8A934C2B8BC54A1594643A866CCDB3166BD41B6DE3E0C9FC779E7F3F421A0BCC798408ACCC92F47A3A45EF237D5CB7473D768991EE79AC659E1DA8CBB0C" + }, + { + "privateKey": "C90C193DAC26E6172462F212B5D3FA11406434496C7BF72FDF71FB4E9840BA44", + "publicKey": "AC515DCD6284495D569A374B40768F86D963604B68AB671E155A37A443FD8EAB", + "length": 45, + "data": "337E7CDBFF240042C778A8E6DAAD5690A47BC852087735CBDC3E0321ED056BE3EE4F6979BEF147BF59F9227AFB", + "signature": "E57C65FD7C9C445CA7C7723B506E569F278FBCF02E572E30365668EFBF764A4303BD43105B1840FB57C6342ABA8771ED42A20867049AC0C9DD356775AF2FDB01" + }, + { + "privateKey": "E3653151EC7882D6C6CB3DF83717374DF447DAE66544842AD354615B37D7B134", + "publicKey": "298F22B868882CCE041BC3614C6752124BBAD3C1189E85CEEF4BE2AB359D59A4", + "length": 51, + "data": "17448B36DF681C62A79C2D8C95B7AF3BBE4C8951DF3E47A940C5D343B2A81BAA589F63B6D00D82918B5B3B0BC80447F4E2B39F", + "signature": "0C1C9BC4C96F71656293DE75394C2C698519A5762FFD0478BD31D3CA137014E68C85682EF77F5F6FE2BDC66C78183AEA5DCCB2FA06CF61B8B08E8E2EAA01F90A" + }, + { + "privateKey": "43C74F8169896510718540B6D90C02EADAC289EE44A6EAEBD213DE9D951667A9", + "publicKey": "452869F7A0B507DF296DF39799E47E9D9A647CCEE95CE914A4DA502A461B83D2", + "length": 41, + "data": "F8D4D3CA7EEA8F20E24D155FB00D45639887F014D8F0A5D557AFEBA99625F360921167072B6C3A1078", + "signature": "D5D3E033F51244BD65255AC558B8BFF06C311F7AC534542277945DB3B247633BE264458D116DD5A5FD5EDADF95F561B03511727E5579F1112C70E38217D6FF0D" + }, + { + "privateKey": "DAB6E9D455178FAA8531518A30565AD0ED5C0E1E00029A572DB990B4E922A5D8", + "publicKey": "3B73F90F676F140004F8FD7BED292DF952B661E4D099F3EC5149A881773D1C40", + "length": 41, + "data": "6C0611A6F2C768F84B2D95C4DC6487EC7D73AAA804B5DF50719B86A26AA8CAF9AA71C624240FACFE22", + "signature": "74C62500E24E71054C12AD44ADFCC64F938B8BECD4C494A718EC7B6343F5F3DD4299280EC95FBA1B5ECAB0B4FA0017AA84B05A042422249255231C0CC0A80D0C" + }, + { + "privateKey": "97E825A1780F32227539EBF3D032E390035B50A6E13CF6174D259B33F1F327E5", + "publicKey": "E2BE362795B77D0C19FC4AC15BBA09E4449104D05DC7F75E4E7F3B3F9BCF1CEC", + "length": 42, + "data": "23F47836AB60E6258A5B64909B422D7BB305745D3D01DE7BA8D0A167F134AE61A4E08E11C5DF327EEF1D", + "signature": "8E514CD7AE01ADB20D6C4CA551255C1DFABC4E41531EF23539E29D27626BBD1F11BFD5030B6973981D515C627C3623F87926FAE4FC5CF15EB3C6DB75F7C26A03" + }, + { + "privateKey": "8230263C82F79A5DE3E309C084C369A13DBE7E66540A6974435080606B9004C9", + "publicKey": "076C56322569282D625CB5094746711AB95BA78179D4BC2F40B3A95CF21AAF8F", + "length": 43, + "data": "AF962C1BC29FF028C8B8FD89827219536D90D60F6E427C3E6E3BD5E7712B8D0A8052F32A7986B249E1331D", + "signature": "AC347785DC36B2259D84F879CFC8FAF25615DB80288CC594A87759CD611E8B065AD5A57AD95C96085527C4D3E65CEE36BA7EE911097FCCD3625FAC07968B3E04" + }, + { + "privateKey": "43D74BB830C268519BF5C72FB5EBBEBC7CDA14B3D787B005FC2038EE6F292497", + "publicKey": "02ADFC1A0581E48999AD182A1FC051092C2148880BFEFCEAA2A69E3363A172FB", + "length": 42, + "data": "F88D70F62102CE63AE2921EB8F930B849F7D4732C512E03EE1C8B05AB3E2EFED5DEE2B1893CB0A3FC13D", + "signature": "69E609B0DB38EEC29758BA5AF310B52390E4D6AB22E261C326BDF26ABFDF1A485E7D8F074F786B306C49D193BA0553FE7BAA7C27419DF1515BA7B8F8E77D6D07" + }, + { + "privateKey": "AA43BC3208F01D29E954DE910B69BE07A33AC3F97424EBF51F246774BACEDE8D", + "publicKey": "D49C9ECC9E55728F8F493349C7DFDE31B90C4A1E834342BFCC6BCF207A87295D", + "length": 52, + "data": "87D576C693E5528DC1FBD1CB96ACBE88D2BB71D9B8B73E7928BD16A8A71F0AE246BAE69F1241C6D7A009B7C691E5ECF639F4DAFD", + "signature": "7D7803920DC48D8D681E46571F5501E6B139800D7AEB464EF732FAD469AB0E451D6F31391E74340A03A2B69DB0346B7E727ABF637B70267EAEE9509FA41B5E0C" + }, + { + "privateKey": "E657D1D5E7789967B9366E1D3D1386CDEBBD8650EA0D6184D8E36B8853960B09", + "publicKey": "4A6141E3781E25E794210B6CA9922839EE0E81AAC69764360121775C95721BAD", + "length": 49, + "data": "8017A5CD334DB855016599C74F7BA4BF8B9226CD343FF0134EDAEE2F10ACA0BE407B233986F0A6F02C633844578EAC3D28", + "signature": "531B8436475C77AA90B891B7BED2A0E13FEAA5A603D8A53C27E64645690B9102AC48CDA7238F1CDA635634BDCEAEFC451569A57F0AA72D956B60ED455463BE0C" + }, + { + "privateKey": "9D3F468B66925D1F5941F81CEB845F2BD75FBA1F1F3E510EB4C5C9C787181D01", + "publicKey": "E90A9C4982BE531FE7E99BCA0A820BA950795859AE55EA6468D0EB3EF792E106", + "length": 46, + "data": "6645AAFAD0AFD4D5B524EC278807E67F7C39529188D311515A1DC101CB46EEC0847D44870F473CC123244447B853", + "signature": "AA627DCD21F2A9F35FD1E32E945DD24CC74035DCEC32CF3082B5C7FA1D9FA6B1F99D67CFCE41D54F2461B380956EBFF36CBD21712D8AF3E50B17D14FE2A29F02" + }, + { + "privateKey": "42FED01BA1A2CD66368E1B04BF3C9A2585A8E9E0DA7EF41351F347F84B4588BD", + "publicKey": "74C235585AB72A7465B24A3F7A2F8DCDE2A4CA8F844C46F57CA22C09C5C85731", + "length": 41, + "data": "E5BC834E803D8F55F9621DB8E79B94CD736C08BFC77155A6D3B1A8DE2AB73799665CD71BC7A1837CEB", + "signature": "CC009F182DB0E92262DFC28D9A65EC1546F9A825EA58642D38AACE7B7DFE12189308C3DF71A0A22F8E7FD01AAE11BCCC1DDB1B2F048D7C6C01F10D8D610C830A" + }, + { + "privateKey": "EA97113FD54FFA5E89B2623898A9399488D5215BEB325F0D91ED6FEBBFB492D2", + "publicKey": "613EA6630B9409C46E7B60ADCA75F1F27923EA41C5DFE0178416FF6064E87BCD", + "length": 52, + "data": "9C248DF832A6F7A2E96776CB2CC0DDED477705790F32778023753541ED13BFF187C5168FD52535BF9B919F6144592BCEFD707946", + "signature": "1D8A36B56141025771F0816ADBC44046FFB3C13FDE22C67616BE5574959C936CC4D71D29F9CA7E136C54C3708C8426A9B9C7ADA5137CCA9A2DFD4BFFABAAE303" + }, + { + "privateKey": "4F9084EEBEEFABCCCFAA51504ADC698405738922BB1BD99CF9C4E5273CFC3A79", + "publicKey": "4301C97DC20FB6A0C893497565AACD40904139D6D973C617A2311A31B7D0CE5F", + "length": 50, + "data": "712F4DC51E20995F2DDB160FBAE4BEADCC916E1D54CF46EF4E2FA80EE44E781765919E2AC103E5110AF899FCC3466C72E9D6", + "signature": "7F7022FD153FBE0D01D174F1B1C0A515AD5A5E92B801F7F9C060789AA87DECEEB6CE11078836A75C54C3BD2313FF9C81C26E4EFD29E4FB388CA86EE2A217C60C" + }, + { + "privateKey": "4F9845AB197ADE1CE0C616380327A2FE1F39A29AC15CAB803299F54AC2C66B66", + "publicKey": "D2BEAE369749522EC60D2FD5533DEDDE4234037E685C0E62FD5632139977F769", + "length": 40, + "data": "DC8C44894D5CC7A138A70C4326C82628C740A288C65247A213B410743E1725F58F4E1B97248BA372", + "signature": "08AFCC9CE11473F49DF7115B20B820101136248DFFA9A5F84F9A41B56280192B40C43DD3D416FA8B99CFFC608331A623FC2D691C9CEA4DA073368B8CB93E470F" + }, + { + "privateKey": "7C0C9AC3B6B1FFA2BD7BFE6238499C7662D3B9508A629F8953C127AB64DFE063", + "publicKey": "E32D3999D74F0E04ECD1F7120291A645DC4BC77BE8CA776D2E43A601F7029E80", + "length": 42, + "data": "89D8CB99858E7FCA07479CABA3B0BE76C7E1BD536F1B8AC6828CBA6894F5AE3F83D69DAC4B48A1701257", + "signature": "B752F24584C13AEBB6C253F33C8B5D2AA1B3471607F6D92AA1BD20AA8EA526918F08F4E55B4A6A7FCBB07563949BDC12F16E9C75E564931FA190A55A4CD64608" + }, + { + "privateKey": "E52F4833CFDDAD84065F8B60991111A1B8DF29EAB5C3D1AE0F76B7A29C1429F2", + "publicKey": "8CB39CD61AB2580182FFD625FB52BC2F2CEE3912AE43CCA74C067E2FC8C5E31A", + "length": 49, + "data": "6450318F4E7655EC8A8559D812A9621109498E83D0223800A2F51F25E3F7D6042CF11DDCFD34CDEBC2B1A52E140CCDD21A", + "signature": "63D5E6937F926729BC25556DDFEF1308D8DEA3476B1240FE0B43D43F14322F654198631E940603468E4D57922CCC1A9F28BF879C737244AD9F408F6D389AA10F" + }, + { + "privateKey": "223CB09792B627A0DCB3A2844EEC76E739926EEAE51A54AC3D1B87B236DF7CB7", + "publicKey": "A93B264145571E4768DD1C59E340328095483A9D552602E9CEADFA03648AE97C", + "length": 45, + "data": "50FB500789E1C6878D6796C4EFF83609BAD0A4D36CA236C9D6BDBCC9215BA298792788B2E031E43C0EE661DC5E", + "signature": "4971948AAC210159C1E806A615932FDF06AE37A6F06A4368160CEFAA5C2CFA6B35AA254C279AE57D448B910448C13331C23AD4F031A661E5DBFA23227C7E4507" + }, + { + "privateKey": "F08D123A754845A3FB735F3742F75C701B9A42F348CAE9FC59C540F52106A04A", + "publicKey": "16F59FB9DC7E30D2C8463C5FBFF014F0A43B8F58D3D0F3141860F71CBE597C47", + "length": 44, + "data": "E20CDE9087154A91BF18029C532449BBFD5083B932E3AC5853A712C8DBA2335EE028C017C55EE78A6819BED1", + "signature": "E937149644EF70B2F1546E0D5B8FA986E82B40D944A3490DD50B3F051827CC136D7289C50ED1C64364242339FDAF31A7E36F2242BE9E29AB65404CCAF127150D" + }, + { + "privateKey": "3CFC0D47DC90A51AE1830E23A64811827C7614F2839FF04A7FFB0FE41ACFE87C", + "publicKey": "F533228996724D57DEF40E19E0CAF32B0160B435A3446DAF03733C3C7D53BA88", + "length": 47, + "data": "8B029AFBA7A9F0718DFAC31A322C2B04898A2C93513C7CE31DE57D8F4C756D5FAE841E38DFA49FA4A7795CE6DB1654", + "signature": "F8BCACCA577BC24FE37155AB4111CA0F9C9CDC9BEEEEFF4551126DBF90CCA4CFBFD35500B319F0EAE449583D84A42CF272404D477E6DE08713B50BBB74B3A808" + }, + { + "privateKey": "4AC719DE0CB49B4D25795651ECDF64653A145CC0106602D97A33B7ABAA53E162", + "publicKey": "2C77836502DBFFCC202FC7DF4B30C41D0E8355CD085B53FC12F46B72ADF5DEA6", + "length": 49, + "data": "F807FE986F54D6CDF987E3F200FA6E9D3F257CCD5BA8E5B06291907B3F640FEDA52D8264387E46EA6D18FF9D1110CBC967", + "signature": "C45F6C30CFC167926C5BC3C34F2B29C57FA2C8DB8E67D2DA86345BA02C2013A8C09BF0B1901071C6DCB8FDF74E79F508BC2F4B3B28FE132A06DCF53F6D53990B" + }, + { + "privateKey": "9EC60BA468F1A537774E10F8BAB3909627D7A09D5EBCA86FB28903F4D740EC2C", + "publicKey": "968240B4A9846DE2DE85AA5C80A66412123A74B77CE1E98DF0ED93A7FF38B765", + "length": 44, + "data": "7283F37C2D42D621673875D529D36F755BB0149B54DA0D9DB45E1D703166E801C55DADCD72558C295660FF31", + "signature": "87DDAF5F0AB3B43A898332E423F096863DAA97B13E598EC2E0448E927119114DC933C655C0BCB6921F72BF8374207D730BA9FB8C5A8A891E3A5D8BC9E7E0F707" + }, + { + "privateKey": "836958918397D87CB8C261BC79B6D6B6C2C0812DD43BAFDACECC21FF91B55FA4", + "publicKey": "BB097BEF47B294374B7281EB61BF4FA1B1422353B714E6FDD3748D7805C2416C", + "length": 40, + "data": "955F1735E70128BEEBE64CA8903480C6DA19CC2BAF3E6BDD3FC7041A692A045CA102B059548FC57F", + "signature": "C37C4906FEF387022ADFCD664319DCA282893C6BF66BC41581B9402C36F537ED20A683BC0F52B7C079B18317F7B35B7E70BBB792541956E48206B44149B26501" + }, + { + "privateKey": "1530BE5A7BBA1B0F0BC3FB4CB0B019542D47DF89ECB3996D01BC280E4017ACD8", + "publicKey": "5E4CAA36812F2DB20CAB3EBD10697242D6AAF27A1C1615B899957BAB26866B8C", + "length": 51, + "data": "132CE7708E19068CECA35F8FC1F7384C4FDFD585C6E7953F22AE34659053C4716250434F666695497A7077BC9D63AAC495950B", + "signature": "F546F9F56CFE6B976E46E1B71709E4FB7563F4B52570B07DA9CE40B4BDABC1A332CA7C64EE9FE3DBCC6137D0A3DAD6C99F93F466A93A0596CB06494F4BA3CB0A" + }, + { + "privateKey": "F281A5B2D1C7F4CE628392E75902F38614CBB3AF4ACCCE263CBA860AFB2387EF", + "publicKey": "2FBC4FA9EA2B714FFA2902A68AB9E6EE6BFE213319FAE449FAF69C15A5B0E060", + "length": 47, + "data": "332FD70780B5FFAF3C571B6D3ECD048C065C0AFB05C1BE0F5DC7C80A5797180B6D886372A9B8B1DBD2EE1B904152FE", + "signature": "E7325CC1B7F4F67A704B9CC87C918777823071CD3BF888C1857DE273D0421BD5699DFAB857A7C90BE2907AFE0C361CC7002D0353C719A96ED3EE0A04B67A7007" + }, + { + "privateKey": "8B0A47A1ED6044E03234A517B0C508C24B6900F94546296472F1D6C380CA34BF", + "publicKey": "6E79131A0A48B7D6A729C4CFB83C337DEE288921299D0909BDD2B234101ABAC5", + "length": 51, + "data": "8918CAD2C4D10E8222865BD04A3AA492FC246A6BA477440FFCA26A2C21230456BE236D2B1D8F95AA01CD8EE8E01DCDB5CA89CA", + "signature": "726E867D23D6DDAC3C69B4B313F7951057197083427B38ED82178BC09DF98B6DF71C59D51649822D618111AB4A5E378A1A9418084DA59787F38B713B2EB59E05" + }, + { + "privateKey": "09DDD185A0A2B62760CA35567B83EAD845ACEF97AD2BCA6BFEA381FF8E806C1D", + "publicKey": "6EAF3A025A604E7A47DE2BA81FB13FC28B819D2C849CCB0D460ECE0F11D1BE6A", + "length": 40, + "data": "55DD33145CB8CBFBD21264C2FF786066B21A2DB7FE47F6E1410D20CC9E50FB9D6462188E4602A041", + "signature": "A440B334D5D3853F96BF910EEFA5285C9EF8702DC1036D94DF08C7B428BEE796C57033081D0AF0A640DD20A10937628934B1F7A291E88CDC018292BC787C6B08" + }, + { + "privateKey": "00665342A5BEE2DD5BD63C4C7996D102070A1CC7278CE10524715A8DBFC99DC8", + "publicKey": "1B0AB95A7417DB3CFCDEC24033CB9E01BB81F30DEDC892AA8AC619E4CF713E41", + "length": 51, + "data": "CF13D2EF24FE41E094A95D3619C80B2B5176349DCCE7EB3588D63AD5C1116597338C23D171D3DCE3A6DAC0CC311692D46AEEC1", + "signature": "6B3D9033B7B0E926D70FD4991DD1D9B40045E0E27097186FC50860A811C3E659E443F5027ADA8B29D80E31AC75520D68C36431E196F9F691E182481DDBA66B04" + }, + { + "privateKey": "81B4183684CFCA61BAC4F9114363E0A87DD1DF88191D4D4CD29762E9FCAC67FC", + "publicKey": "0879AC6FE51ED7199FB25D6C85D6B906F97462DF18705BEBFED17AD35742D01C", + "length": 50, + "data": "22FA266659FB38312725A4A8C6D2972E44C96C4BFF2FC7A8B7FDCA4D8AB2F5368A21E424C602E4C8759C7A7107A7E13722BD", + "signature": "751422EBABB558FCEE300027413083F7AC206DA8589AABB966204DD22AB4A29825C3ADBA1F9B985E2E4E0C0CE7BF2059CDFA1DDE6FD715C8218E87055C805608" + }, + { + "privateKey": "030C0B139705180A44D5A2406FBDB92AFB16F93C2D168DD39D2F5F43516295EC", + "publicKey": "2221D02D760C42B7793FB154B92259DC1EBB312C95DEB971F1EAA6F2D7E4DB19", + "length": 43, + "data": "60A3512E600236957F271A5B47EC35AB8991A1F0A0B76564D0549B77357779D0B1502365DD83DE78191265", + "signature": "CBDD3036A9964F2B000FB43F2A420F235B0FC8AF2A21A3017DA45547AC91C4F322E3BA0822DF6982EBAFA60BFCE41CC52FFA8E3BE7148044F70805F84E585D08" + }, + { + "privateKey": "E6C1685E7AC64EFB63B77D55E05D7A1D1036B21A2831A250564653DC0A137CB7", + "publicKey": "2E71772EA2A8216A6489235DC630E2948B1320E5BCAFE9F9D624F1B9515F5871", + "length": 44, + "data": "A50E6D1BCF638075E64C9E1B17C4DF9D21EA977878E2852628A485A29D8456FC7073921DE0A972E9DA4AC861", + "signature": "16B8630CE5268C4AF312EDF8791DB70FBB1F1ACDA6E1D468A3A266C221574D9E3C666373B4CCB68FDE86270A8EC7C1228A0E4E4073CFE9807364FED3CE231304" + }, + { + "privateKey": "51758D3BBC03772AC3047018FA472EF980B20ABC38212A63B442861BEAC83904", + "publicKey": "34F5E8C2D9315188E66A41E56524CB1BDA5E8123D965F3E9598906023BAC6014", + "length": 43, + "data": "5547AB3B1B7A38BA5CA4E7208F405D0B9D3B8FDD04841C4AFAA49D704A7BADE6533F369FF1D9F87CBB70D6", + "signature": "E37836858C29ECF256586D303F5D09C0A576FA20EA8DB77F026B5372D6C23BD5D2B82F12A3C9D321E38BE3B048F7B43BEAE5D81218DDAED2284058A372CF6904" + }, + { + "privateKey": "2900D96D21B7ACE794558A432C1F28B806280AD93C31482E5B40A117E1219F21", + "publicKey": "73B456E0CB23D8C7C4DB03DCF3AC2E095056D066D5E1A38DA5C38C9386536CB0", + "length": 45, + "data": "3B68A2DE1C8808566EB969C032F5A10DF91AB7AE2D3D57C5A6A3376572876E0D94A759EAB785D943AD1DA35689", + "signature": "C43B4FF7453A8063321AA877D4F4B2EC70A087D5B50464BCF63FA9270C717FB567A4034F56661F269A0310E07B190493393C7221D94549E33C0C61335A1D9B0D" + }, + { + "privateKey": "D7DA4E94FB9925B6AB01370F26D45D1649A47B8601EC7CB8AB8E410FE8205D21", + "publicKey": "A0E86E5C7D04C31F8E56D4B4288A0E5ECF34DF2F566A6A576009D9FDF708D14F", + "length": 46, + "data": "2DEE16A97075FDF715819FA1E88A49F96C7345F414330AB6BD5CB12A81412969920F0483961DA4EA6ADCBC01FDF5", + "signature": "00175568E98362C66EA8EEED9E1594F104952C02CDE4E2836C637C26D402E52C0934842390E63CEB166D56BB43723F52E58B36E2A644772D16379DE40CFBC10B" + }, + { + "privateKey": "3837B66CD409334733027AF1F4F89F9F8654FEB50C57DA4208BF60D32DD441B3", + "publicKey": "B71D5686982299078B3CCEDB1DE58DC4D44C01D8195AC38D13F96B35EBEB6320", + "length": 42, + "data": "E7F642BCCE878038090F520A6337C06F6ADB55050F3AEF6CEA392B6AD230B0DBA62E7C3AA524E84A6EBE", + "signature": "2D7595F8470BB2F94D52A7DEE30B345E3C8036B034A9DE366714782CC802368B3E9E32883D2A9E6D91816B28C108682499FDF154F912486F0C35B635913F5402" + }, + { + "privateKey": "59212CC95F202C2482122ACDCE15C0491C4DFD3C239423953CEA71B31C8ABE0D", + "publicKey": "4DB9A56DE7FB460989134919A126160CEE5BF28AFC4A4F7A1E4133F6D261B454", + "length": 41, + "data": "E6275ECFF25A50C01FCE080C1E657ED04301DB84F455CD462F8829EC909E4DC873EB31B90CC8381973", + "signature": "4C236E60A00A7DE0E362BCD35B1455104617ADCED5E1BF96BA71D6DC80A7324A097FA3D6F3E91596D58CCD4FF26B5335CB3D4A1622B370ACB4DEB69759408301" + }, + { + "privateKey": "6C89B77896B8109821B0F0CA372563476FE4102E4E77B5EA57A263C4EB9E1A6F", + "publicKey": "1293209FFCA524BA8F2FDD919E675CD22050EE8FE3CC8012BDC5F5BA7E94E3E2", + "length": 49, + "data": "6F1CD9AAC157734245E8688C7117092929030FE3A4499983481FE92E29B171C848C2E40E67183E9AED506F536CDB605D21", + "signature": "8D0E034D9880FD9D5A256A39C329AFFB588289524A9C4BC76711B00B03C5367C1D5B24DD75C1AF87386AE35933B7F0925378A7CD3E07237541285657129EA601" + }, + { + "privateKey": "3C3408D03B2C01FA5547375F73CA9734858DA32BC6CFD7C62C184EEB49652F69", + "publicKey": "B6DE0F193F7DA826B7A512DC8D4FE359825B5572580376E5BCE1A93ED68A46C1", + "length": 45, + "data": "51D01978437B34277EF148297E09197F5F03D434E57E1DD61B975BB2ED290B87B3DBA4425B0B705BE211426236", + "signature": "EF883C9D6D496CE8A476EEA7C399B78C164DB2CED1809D4DE36BD9354488675A8F4771F8CF33E6CE58C79F9D9DCA1485142340C5E0DE194BFB5A5D226A83CD04" + }, + { + "privateKey": "4D3300355854397FDAA96467254DB2EC04C18D165E5194D009EEDE13FD8B0273", + "publicKey": "1B7B4F851B6D374D6D38EF76BE4F2D900F9F8E1A8BA047F63B26CF6F608DD377", + "length": 41, + "data": "9E52B4BE735C44483CB6CC78BA9E8D9A84AAC17F14096D47D8067A36A2027892BCDD5B9F5AC466E564", + "signature": "64EF79C0BCFDFFE8B51FD2CBA90DE31ECED82FFE6CF4EA9A8F2E39E2833C642288AD4A09E501FC151E45D4219211C3A4E79706D852CBC97F8971443DB3415D0E" + }, + { + "privateKey": "9606BF951F3F28AEA0B7536DDE26C5AF8807B6D0BD54B2EA165645FA9890ED9D", + "publicKey": "5DF39A0FBE3D43F86C85791D82330F2C05905A832F56C61827E4BD110A490347", + "length": 42, + "data": "112BAAD15429D6F30E8A81637315A6CBCB959A48A1FBBC3844E5427274FFD486D039DC6D27D7BABB21F5", + "signature": "0602F3C8CA10DE8364AC80D3B6B484CA18FC87AE07F6ADA4ADBE79CC8813370FC5BF10CEA9FEED676F990DDE74529E78A188FA85A0B47EDCF51B6DC3DFB8F30D" + }, + { + "privateKey": "08720F4471BA2EC5DF3AB0A6B9C704B2302AF9A398F3AD3F56AF42FFE4F2A4CF", + "publicKey": "EDA0E18533AAEE4DDD57F68F36C34B75D282E649FDAF325DC68530B27052945F", + "length": 52, + "data": "42F96AB0BCAE227C6CAE23E76C967B8CD87450B21D894C98394F2896ECFB79807418C27DFF5EFF0B7439F522ED82EC3A8D48B927", + "signature": "1613FD614AE6B562AF159D3348A21E1961F76E6F9B96AC0D2BE9E713978048E066023CF08BC7DBEAF16F27E7C280B9F0264272E488BF7A4702C261F2677CA204" + }, + { + "privateKey": "725A04A1BA670B678B0C9162B53B550E9C0E0768DFEA503D33951C1BCD22E6A1", + "publicKey": "D83B6913FF47529492304829DCFEEE5E0866EADEC87A24057511F3D05B373A39", + "length": 51, + "data": "C610347D5FD368C7BE675B8B225CC8C8F92444797A0DFAD09E222D8D503EBE561E4DBEFA5D5E1F6F71229F9955A2692527F6E8", + "signature": "B48CAF93B64B987E4E5A5F03F436B02AC4AA0EEE456E81AD4AE19B5AF58F0D2E5DBAFBBD6C08775FCE01B7109D8DB1F3ED5E58BB7198EC09412696726FD68607" + } +] diff --git a/test/votingkeys/private_key_tree1.dat b/test/votingkeys/private_key_tree1.dat new file mode 100644 index 000000000..b8c5bb242 Binary files /dev/null and b/test/votingkeys/private_key_tree1.dat differ diff --git a/test/votingkeys/private_key_tree2.dat b/test/votingkeys/private_key_tree2.dat new file mode 100644 index 000000000..fdad001ba Binary files /dev/null and b/test/votingkeys/private_key_tree2.dat differ diff --git a/test/votingkeys/private_key_tree3.dat b/test/votingkeys/private_key_tree3.dat new file mode 100644 index 000000000..273e9f6fb Binary files /dev/null and b/test/votingkeys/private_key_tree3.dat differ diff --git a/test/votingkeys/private_key_tree4.dat b/test/votingkeys/private_key_tree4.dat new file mode 100644 index 000000000..f158a7953 Binary files /dev/null and b/test/votingkeys/private_key_tree4.dat differ diff --git a/test/votingkeys/private_key_tree5.dat b/test/votingkeys/private_key_tree5.dat new file mode 100644 index 000000000..42deadab5 Binary files /dev/null and b/test/votingkeys/private_key_tree5.dat differ