├── .editorconfig ├── .gitignore ├── .gitmodules ├── README.md ├── accounts └── keystore │ ├── 0b2f5e2f3cbd864eaa2c642e3769c1582361caf6.json │ ├── 312c230e7d6db05224f60208a656e3541c5c42ba.json │ ├── 32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json │ ├── 720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9.json │ ├── aa94b687d3f9552a453b81b2834ca53778980dc0.json │ ├── b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e.json │ ├── b916e7e1f4bcb13549602ed042d36746fd0d96c9.json │ ├── be69eb0968226a1808975e1a1f2127667f2bffb3.json │ ├── db9cb2478d917719c53862008672166808258577.json │ └── f67cc5231c5858ad6cc87b105217426e17b824bb.json ├── config ├── TxPriority1.json ├── TxPriority2.json ├── TxPriority3.json ├── node0.nethermind.json ├── node0.openethereum.toml ├── node1.nethermind.json ├── node1.openethereum.toml ├── node2.nethermind.json ├── node2.openethereum.toml ├── node3.nethermind.json ├── node3.openethereum.toml ├── node4.nethermind.json ├── node4.openethereum.toml ├── node5.nethermind.json ├── node5.openethereum.toml ├── node6.nethermind.json ├── node6.openethereum.toml └── password ├── data ├── node0 │ ├── .gitkeep │ └── keys │ │ └── DPoSChain │ │ └── address_book.json ├── node1 │ ├── .keep │ └── keys │ │ └── DPoSChain │ │ ├── 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json │ │ └── 0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78.json ├── node2 │ ├── .keep │ └── keys │ │ └── DPoSChain │ │ ├── 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json │ │ └── 0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441.json ├── node3 │ ├── .keep │ └── keys │ │ └── DPoSChain │ │ ├── 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json │ │ └── 0x522df396ae70a058bd69778408630fdb023389b2.json ├── node4 │ └── keys │ │ └── DPoSChain │ │ └── 0xf67cc5231c5858ad6cc87b105217426e17b824bb.json ├── node5 │ └── keys │ │ └── DPoSChain │ │ └── 0xbe69eb0968226a1808975e1a1f2127667f2bffb3.json └── node6 │ └── keys │ └── DPoSChain │ └── 0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9.json ├── package-lock.json ├── package.json ├── scripts ├── copy-spec.js ├── deploy-staking-token.js ├── getReservedPeer.js ├── mint-coins-to-candidates.js ├── network-spec ├── start-test-setup ├── stop-test-setup ├── watchOrdinaryNode.js ├── watchRandomSeed.js └── watcher.js ├── simulation ├── README.md └── dpos.nlogo ├── test ├── 00_tx_priority.js ├── 01_staking.js ├── 02_removing_pool.js ├── 03_ordinary_node.js └── 04_random_seed.js └── utils ├── calcMinGasPrice.js ├── constants.js ├── getContract.js ├── getLatestBlock.js ├── mintCoins.js ├── prettyPrint.js ├── sendInStakingWindow.js ├── sendRequest.js ├── sign2718Tx.js ├── signAndSendTx.js ├── waitForNextStakingEpoch.js └── waitForValidatorSetChange.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Emacs backups 2 | \#*# 3 | .#* 4 | *~ 5 | 6 | # Vim backups 7 | .*.sw[po] 8 | 9 | # Patch files 10 | *.orig 11 | *.rej 12 | *.diff 13 | *.patch 14 | 15 | # Account created during test 16 | accounts/keystore/* 17 | !accounts/keystore/0b2f5e2f3cbd864eaa2c642e3769c1582361caf6.json 18 | !accounts/keystore/b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e.json 19 | !accounts/keystore/312c230e7d6db05224f60208a656e3541c5c42ba.json 20 | !accounts/keystore/b916e7e1f4bcb13549602ed042d36746fd0d96c9.json 21 | !accounts/keystore/32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json 22 | !accounts/keystore/be69eb0968226a1808975e1a1f2127667f2bffb3.json 23 | !accounts/keystore/720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9.json 24 | !accounts/keystore/db9cb2478d917719c53862008672166808258577.json 25 | !accounts/keystore/aa94b687d3f9552a453b81b2834ca53778980dc0.json 26 | !accounts/keystore/f67cc5231c5858ad6cc87b105217426e17b824bb.json 27 | 28 | 29 | # Logs 30 | /data/node[0-9]*/network/ 31 | /data/node[0-9]*/chains/ 32 | /data/node[0-9]*/log 33 | /data/node0/blocks.log 34 | /data/node0/check.log 35 | /data/node1/checkRandomSeed.log 36 | /data/node1/checkRandomSeedDebug.log 37 | /data/reserved-peers 38 | /data/spec.json 39 | /data/StakingToken.json 40 | 41 | # Empty files 42 | /data/node*/keys/DPoSChain/address_book.json 43 | 44 | # NPM files 45 | /node_modules/ 46 | 47 | build 48 | 49 | #Mac OS specific 50 | .DS_Store 51 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "posdao-contracts"] 2 | path = posdao-contracts 3 | url = https://github.com/poanetwork/posdao-contracts 4 | branch = master 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POSDAO test setup 2 | 3 | This is an integration test of AuRa POSDAO with seven OpenEthereum (or Nethermind) nodes running locally from the genesis block. 4 | 5 | 6 | ## Ethereum client installation 7 | 8 | ### OpenEthereum 9 | 10 | To integrate with [OpenEthereum](https://github.com/openethereum/openethereum), the following structure of folders is assumed: 11 | ``` 12 | . 13 | ├── openethereum 14 | ├── posdao-test-setup 15 | ``` 16 | So there should be two folders on the same level and `posdao-test-setup` will use a binary from the `openethereum` folder, namely the binary is assumed to be at `../openethereum/target/release/openethereum` relative to `posdao-test-setup` root. 17 | 18 | If you want to compile a specific branch/version of `OpenEthereum`, you can clone it directly and build the binary 19 | ```bash 20 | # move up from posdao-test-setup root 21 | $ cd .. 22 | $ git clone https://github.com/openethereum/openethereum 23 | $ cd openethereum 24 | # 25 | # Next step assumes you have Rust and required dependencies installed, 26 | # for details please check https://github.com/openethereum/openethereum#readme 27 | # Note that you can instruct Rust to always use the latest stable version for this project by running 28 | # $ rustup override set stable 29 | # in `openethereum` folder. 30 | # 31 | # Build the binary 32 | $ cargo build --release --features final 33 | ``` 34 | 35 | To save time, you can download a pre-compiled binary from the [releases page](https://github.com/openethereum/openethereum/releases) (versions >= v3.3.5 are supported). But you still need to maintain directory structure and naming conventions: 36 | ```bash 37 | # move up from posdao-test-setup root 38 | $ cd .. 39 | $ mkdir -p openethereum/target/release/ 40 | # an example for macOS binary 41 | $ curl -SfL 'https://github.com/openethereum/openethereum/releases/download/v3.3.5/openethereum-macos-v3.3.5.zip' -o openethereum/target/release/openethereum.zip 42 | $ unzip openethereum/target/release/openethereum.zip -d openethereum/target/release 43 | $ chmod +x openethereum/target/release/openethereum 44 | # check that it works and the version is correct (compare the version from the binary with version on the release page) 45 | $ openethereum/target/release/openethereum --version 46 | ``` 47 | 48 | ### Nethermind 49 | 50 | To integrate with [Nethermind](https://github.com/NethermindEth/nethermind), the following structure of folders is assumed: 51 | ``` 52 | . 53 | ├── nethermind 54 | ├── posdao-test-setup 55 | ``` 56 | So there should be two folders on the same level and `posdao-test-setup` will use a binary from the `nethermind` folder, namely the binary is assumed to be at `../nethermind/bin/Nethermind.Runner` relative to `posdao-test-setup` root. 57 | 58 | A pre-compiled binary can be downloaded from the [releases page](https://github.com/NethermindEth/nethermind/releases) (versions >= v1.12.7 are supported). You need to maintain directory structure and naming conventions: 59 | ```bash 60 | # move up from posdao-test-setup root 61 | $ cd .. 62 | $ mkdir -p nethermind/bin 63 | # an example for Linux binary 64 | $ curl -SfL 'https://github.com/NethermindEth/nethermind/releases/download/1.12.7/nethermind-linux-amd64-1.12.7-3b419f1-20220407.zip' -o nethermind/bin/nethermind.zip 65 | $ unzip nethermind/bin/nethermind.zip -d nethermind/bin 66 | $ chmod +x nethermind/bin/Nethermind.Runner 67 | # check that it works and version is correct (compare the version from the binary with version on the release page) 68 | $ nethermind/bin/Nethermind.Runner --version 69 | ``` 70 | 71 | 72 | ## Usage 73 | 74 | After OpenEthereum client is downloaded or built (see above), the integration test can be launched with `npm run all` (in the root of `posdao-test-setup` working directory). To use Nethermind client instead, the integration test should be launched with `npm run all-nethermind`. 75 | 76 | To stop the tests, use `npm run stop-test-setup` (or just use `CTRL+C` in the console). 77 | 78 | To stop and clear directories, use `npm run cleanup` in a separate console. 79 | 80 | To restart the tests from scratch just run `npm run all` (or `npm run all-nethermind`) again. 81 | 82 | To monitor blocks and transactions, use `npm run watcher` in a separate console. 83 | 84 | 85 | ## Development 86 | 87 | ### Adding new validator nodes and their keys (for OpenEthereum client only) 88 | 89 | To add a new validator node, OpenEthereum should generate an account together with its 90 | secret key like so: 91 | 92 | ``` 93 | $ ./openethereum/target/release/openethereum account new --config config/nodeX.openethereum.toml --keys-path ./posdao-test-setup/data/nodeX/keys 94 | ``` 95 | 96 | given a node configuration file `config/nodeX.openethereum.toml` and a newly created 97 | directory `data/nodeX/keys`. `config/nodeX.openethereum.toml` should then be amended 98 | with the validator address output by the above command. Also, the keys directory 99 | `data/nodeX/keys` should be committed to the Git repository: it is part 100 | of persistent OpenEthereum state which should be kept across state resets which happen 101 | when you run `npm run all`. 102 | 103 | With this done, the node can be added to the list of started nodes in 104 | `scripts/start-test-setup` and to the list of stopped nodes in 105 | `scripts/stop-test-setup`. 106 | 107 | If the new node has to be an initial validator, the network spec should reflect 108 | that: add the node's address to `INITIAL_VALIDATORS` and `STAKING_ADDRESSES` in `scripts/network-spec`. 109 | 110 | ## Simulation 111 | 112 | We've created a [NetLogo model](./simulation/README.md) for simulating the 113 | staking and rewards computation on networks of various sizes and having 114 | different input parameters. 115 | -------------------------------------------------------------------------------- /accounts/keystore/0b2f5e2f3cbd864eaa2c642e3769c1582361caf6.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"e0147d0a-26ae-4826-a569-76d3ecaf7bda","address":"0b2f5e2f3cbd864eaa2c642e3769c1582361caf6","crypto":{"ciphertext":"2ce6163c7f98ff19fc6a48f65044116848ec42ccecc918145efc43d3a0d18bcf","cipherparams":{"iv":"8f362e8fa6ab9f06fd6307b525bea53a"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"2293014c67eac1300063746cd39b33da25ccbaf12afb1a3928b2e0aa8c8e5aa6","n":131072,"r":8,"p":1},"mac":"c5322dfcf2da7caa245a2cc423b8666b11e40d9d8020e14a77400fcf1d2576f2"}} -------------------------------------------------------------------------------- /accounts/keystore/312c230e7d6db05224f60208a656e3541c5c42ba.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"d107ddf1-0c6c-4719-9bde-0bee74c1be73","address":"312c230e7d6db05224f60208a656e3541c5c42ba","crypto":{"ciphertext":"68e2d360846f179095e33494f3d6874d97955256b71af81642738a60ef171945","cipherparams":{"iv":"f19b10288b0666e507a533e90e7ecb2b"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"4f85b549fe5a93cc5e76b73998bdfc4ccbba32bc50ea32bfa0ab9fd7bbee37d4","n":131072,"r":8,"p":1},"mac":"5d6b179bc81945dd58149c563b3aec4c7e82d77a644c5ec74b25de858fa00549"}} -------------------------------------------------------------------------------- /accounts/keystore/32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"be60c5ac-6c4a-44c4-a12f-9558131ab64a","address":"32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24","crypto":{"ciphertext":"68d1202b416408cb22ca60ba4f4deabcbddad7a262e014e1239dbe7354677ebb","cipherparams":{"iv":"a37afd65f955ab252340496defade99d"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"47fdc45755f6f75072fb8c1a1eea9ec3a06450754eb78f0cc726baef22fed0bf","n":8192,"r":8,"p":1},"mac":"00def76f42948515e1f498b433860ef4a0fca7f9ce7a81eee0abc470f365ceb7"}} 2 | -------------------------------------------------------------------------------- /accounts/keystore/720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9.json: -------------------------------------------------------------------------------- 1 | {"id":"a0a9df30-804e-520f-9951-472b28cdec6e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"8ad84ae9685669a060750fa28b9760b6"},"ciphertext":"e9d3ed5aa4951dead67136fea1b2bdf9d9df2624aff4bc3d4c8fc186d14f208f","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"64860db5b5a1cd65f3dc97e3f0d96f4c7ef8166377cecc7c4679529dd640bcee"},"mac":"e9646576f7de1059d1fca4b08a02ceb3ae915b27e34acf32716c5d8e67fcc631"},"address":"720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9","name":"","meta":"{}"} -------------------------------------------------------------------------------- /accounts/keystore/aa94b687d3f9552a453b81b2834ca53778980dc0.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"d7c26ad5-6e79-4464-8728-2fdb74c0b9b6","address":"aa94b687d3f9552a453b81b2834ca53778980dc0","crypto":{"ciphertext":"f8715ca28dbaa61ee4606c56be208a3852864f096ecac5637e26c60fa1b63c54","cipherparams":{"iv":"08a9f9cde974bfdb01bb4bfb4805de82"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"895638c901cc1b26d48b116e5de0fa3074b7fb3caa341f69953bece81e4af815","n":131072,"r":8,"p":1},"mac":"f791579e75ea2e1e75017a06367a2fcc121de14cc728ef6b11ef429bd5c30a74"}} -------------------------------------------------------------------------------- /accounts/keystore/b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"71e35b90-ce89-43b6-bb9d-1bece5905f95","address":"b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e","crypto":{"ciphertext":"15c428a7967e708a4e08e409500e9182019dfb36b4b318558c8e5a4c8a840b86","cipherparams":{"iv":"5bb12130bf46d5714fe67195df3ffa3b"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"798cc4c06273aa8bd4e401f6553f006f60540c57e9cbd99a6bb862677df7cf78","n":131072,"r":8,"p":1},"mac":"f7826af43bb2ff22143bf9d297497b4a51f901611f662dd6f4a408e216c89a15"}} -------------------------------------------------------------------------------- /accounts/keystore/b916e7e1f4bcb13549602ed042d36746fd0d96c9.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"b5d2f816-70f4-42fe-8741-e208408399d7","address":"b916e7e1f4bcb13549602ed042d36746fd0d96c9","crypto":{"ciphertext":"5df5ed8a5ca0c68973b49f3bf916a1e3284213b91d42960bb71581653ca90f71","cipherparams":{"iv":"04f548ff150e3036d58e3694d84f49d0"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"70de414a9de770718581bfa6b7d99b0b81ef9db622e7aa2d0f251cbd1adc8d82","n":131072,"r":8,"p":1},"mac":"6a4ac406f03294aa665f8693d8dc62bf711d0386ad7da023c27010f2f18eafc7"}} -------------------------------------------------------------------------------- /accounts/keystore/be69eb0968226a1808975e1a1f2127667f2bffb3.json: -------------------------------------------------------------------------------- 1 | {"id":"c22fc0ee-5788-9afa-f85d-b5bcccac6f99","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"224fa37102b43887e58fff0fb6054340"},"ciphertext":"be628355c8b1bd918c470f8dc1466abaa59e812c629574bb560da24601e3a4f3","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"7644636bdab757cda6927be5ff0d3882fd126bf1559190eec058f199283229cd"},"mac":"5431830b2b0b314f30f6fea718fc2c0909a1425be7650ac30ce93c4411a86011"},"address":"be69eb0968226a1808975e1a1f2127667f2bffb3","name":"","meta":"{}"} -------------------------------------------------------------------------------- /accounts/keystore/db9cb2478d917719c53862008672166808258577.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"b4e82c42-6dd4-43ca-a850-3ca9fc8b3947","address":"db9cb2478d917719c53862008672166808258577","crypto":{"ciphertext":"98b983ea051ceecba80dc771354ba0cb22909b2dd2cc57e0cdfdd40e30d60027","cipherparams":{"iv":"9ba905a5ebf1b3c17bf94308f4ca1292"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"b76f7bca5d8bd47c21c86ac1f1cec6e70c61fdc8929a8445b59df3c8e05302d2","n":131072,"r":8,"p":1},"mac":"d169550ed98250b33728000577f28224a19cadc506fde942a5ab1c399c7a8f2a"}} -------------------------------------------------------------------------------- /accounts/keystore/f67cc5231c5858ad6cc87b105217426e17b824bb.json: -------------------------------------------------------------------------------- 1 | {"id":"b3b5371c-7a49-ae31-01e0-1e827fd9fda6","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"75dcb9f5ea86ad12a85274f9f4a35ee7"},"ciphertext":"55e6b5dc8026c32b0d8dc92d12b807abe724598229143bce98a096fe6a9c7332","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"6e660fcb9ae4a0eaf6a7acc96acb7930e328645ce1b4544200388e24029db01d"},"mac":"d92be1116594290d4bd677b777c70dbfc233475502d65fe7e9850706aa3cf073"},"address":"f67cc5231c5858ad6cc87b105217426e17b824bb","name":"","meta":"{}"} -------------------------------------------------------------------------------- /config/TxPriority1.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": ["0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB"], 3 | "priorities": [ 4 | { 5 | "target": "0x1000000000000000000000000000000000000001", 6 | "fnSignature": "0x00000000", 7 | "value": "3" 8 | }, 9 | { 10 | "target": "0x2000000000000000000000000000000000000001", 11 | "fnSignature": "0x00000000", 12 | "value": "2" 13 | } 14 | ], 15 | "minGasPrices": [ 16 | { 17 | "target": "0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB", 18 | "fnSignature": "0x00000000", 19 | "value": "100000000000" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /config/TxPriority2.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": ["0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB"], 3 | "priorities": [ 4 | { 5 | "target": "0x1000000000000000000000000000000000000001", 6 | "fnSignature": "0x00000000", 7 | "value": "3" 8 | }, 9 | { 10 | "target": "0x2000000000000000000000000000000000000001", 11 | "fnSignature": "0x00000000", 12 | "value": "2" 13 | } 14 | ], 15 | "minGasPrices": [ 16 | { 17 | "target": "0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB", 18 | "fnSignature": "0x00000000", 19 | "value": "100000000000" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /config/TxPriority3.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": ["0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB"], 3 | "priorities": [ 4 | { 5 | "target": "0x1000000000000000000000000000000000000001", 6 | "fnSignature": "0x00000000", 7 | "value": "3" 8 | }, 9 | { 10 | "target": "0x2000000000000000000000000000000000000001", 11 | "fnSignature": "0x00000000", 12 | "value": "2" 13 | } 14 | ], 15 | "minGasPrices": [ 16 | { 17 | "target": "0x15B5c5A3D4bF2F2Dfc356A442f72Df372743d7cB", 18 | "fnSignature": "0x00000000", 19 | "value": "100000000000" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /config/node0.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node0", 8 | "LogDirectory": "./data/node0", 9 | "LogFileName": "nethermind.logs.txt" 10 | }, 11 | "Network": { 12 | "DiscoveryPort": 30300, 13 | "P2PPort": 30300 14 | }, 15 | "JsonRpc": { 16 | "Enabled": true, 17 | "Host": "127.0.0.1", 18 | "Port": 8540, 19 | "WebSocketsPort": 9540 20 | }, 21 | "Db": { 22 | "WriteBufferSize": 16000000, 23 | "WriteBufferNumber": 4, 24 | "BlockCacheSize": 128000000, 25 | "CacheIndexAndFilterBlocks": true, 26 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 27 | "HeadersDbCacheIndexAndFilterBlocks": false, 28 | "BlocksDbCacheIndexAndFilterBlocks": false 29 | }, 30 | "Sync": { 31 | "FastSync": false 32 | }, 33 | "KeyStore": 34 | { 35 | "KeyStoreDirectory": "./data/node0/keys/DPoSChain" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/node0.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node0" 4 | 5 | [network] 6 | port = 30300 7 | discovery = true 8 | nat = "none" 9 | interface = "local" 10 | 11 | [rpc] 12 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 13 | port = 8540 14 | 15 | [websockets] 16 | port = 9540 17 | 18 | [ipc] 19 | disable = true 20 | 21 | [secretstore] 22 | disable = true 23 | 24 | [misc] 25 | logging = "engine=trace,miner=trace,reward=trace" 26 | -------------------------------------------------------------------------------- /config/node1.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node1", 8 | "LogDirectory": "./data/node1", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30301, 14 | "P2PPort": 30301 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8541, 20 | "WebSocketsPort": 9541 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true, 38 | "TxPriorityContractAddress": "0x4100000000000000000000000000000000000000", 39 | "TxPriorityConfigFilePath": "./config/TxPriority1.json" 40 | }, 41 | "KeyStore": 42 | { 43 | "BlockAuthorAccount": "0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", 44 | "PasswordFiles": ["./config/password"], 45 | "UnlockAccounts": ["0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24"], 46 | "KeyStoreDirectory": "./data/node1/keys/DPoSChain" 47 | }, 48 | "Mining": 49 | { 50 | "MinGasPrice": "1000000000", 51 | "TargetBlockGasLimit": "12000000" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/node1.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node1" 4 | 5 | [network] 6 | port = 30301 7 | nat = "none" 8 | interface = "local" 9 | reserved_peers="data/reserved-peers" 10 | 11 | [rpc] 12 | cors = ["all"] 13 | hosts = ["all"] 14 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 15 | port = 8541 16 | 17 | [websockets] 18 | interface = "all" 19 | origins = ["all"] 20 | port = 9541 21 | 22 | [ipc] 23 | disable = true 24 | 25 | [secretstore] 26 | disable = true 27 | 28 | [account] 29 | unlock = ["0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24"] 30 | password = ["config/password"] 31 | 32 | [mining] 33 | force_sealing = true 34 | min_gas_price = 1000000000 35 | gas_floor_target = "12000000" 36 | engine_signer = "0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" 37 | reseal_on_txs = "none" 38 | extra_data = "Parity" 39 | tx_gas_limit="7500000" 40 | tx_time_limit=1000 41 | 42 | [misc] 43 | logging = "engine=trace,miner=trace,reward=trace" 44 | 45 | [footprint] 46 | tracing = "on" 47 | fat_db = "on" 48 | pruning = "archive" 49 | -------------------------------------------------------------------------------- /config/node2.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node2", 8 | "LogDirectory": "./data/node2", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30302, 14 | "P2PPort": 30302 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8542, 20 | "WebSocketsPort": 9542 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true, 38 | "TxPriorityContractAddress": "0x4100000000000000000000000000000000000000", 39 | "TxPriorityConfigFilePath": "./config/TxPriority2.json" 40 | }, 41 | "KeyStore": 42 | { 43 | "BlockAuthorAccount": "0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441", 44 | "PasswordFiles": ["./config/password"], 45 | "UnlockAccounts": ["0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441", "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24"], 46 | "KeyStoreDirectory": "./data/node2/keys/DPoSChain" 47 | }, 48 | "Mining": 49 | { 50 | "MinGasPrice": "1000000000", 51 | "TargetBlockGasLimit": "12000000" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/node2.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node2" 4 | 5 | [network] 6 | port = 30302 7 | discovery = true 8 | reserved_peers="data/reserved-peers" 9 | nat = "none" 10 | interface = "local" 11 | 12 | [rpc] 13 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 14 | port = 8542 15 | 16 | [websockets] 17 | port = 9542 18 | 19 | [ipc] 20 | disable = true 21 | 22 | [secretstore] 23 | disable = true 24 | 25 | [account] 26 | unlock = ["0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441"] 27 | password = ["config/password"] 28 | 29 | [mining] 30 | force_sealing = true 31 | min_gas_price = 1000000000 32 | gas_floor_target = "12000000" 33 | engine_signer = "0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441" 34 | reseal_on_txs = "none" 35 | extra_data = "Parity" 36 | tx_gas_limit="7500000" 37 | tx_time_limit=1000 38 | 39 | [misc] 40 | logging = "engine=trace,miner=trace,reward=trace" 41 | -------------------------------------------------------------------------------- /config/node3.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node3", 8 | "LogDirectory": "./data/node3", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30303, 14 | "P2PPort": 30303 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8543, 20 | "WebSocketsPort": 9543 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true, 38 | "TxPriorityContractAddress": "0x4100000000000000000000000000000000000000", 39 | "TxPriorityConfigFilePath": "./config/TxPriority3.json" 40 | }, 41 | "KeyStore": 42 | { 43 | "BlockAuthorAccount": "0x522df396ae70a058bd69778408630fdb023389b2", 44 | "PasswordFiles": ["./config/password"], 45 | "UnlockAccounts": ["0x522df396ae70a058bd69778408630fdb023389b2", "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24"], 46 | "KeyStoreDirectory": "./data/node3/keys/DPoSChain" 47 | }, 48 | "Mining": 49 | { 50 | "MinGasPrice": "1000000000", 51 | "TargetBlockGasLimit": "12000000" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/node3.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node3" 4 | 5 | [network] 6 | port = 30303 7 | discovery = true 8 | reserved_peers="data/reserved-peers" 9 | nat = "none" 10 | interface = "local" 11 | 12 | [rpc] 13 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 14 | port = 8543 15 | 16 | [websockets] 17 | port = 9543 18 | 19 | [ipc] 20 | disable = true 21 | 22 | [secretstore] 23 | disable = true 24 | 25 | [account] 26 | unlock = ["0x522df396ae70a058bd69778408630fdb023389b2"] 27 | password = ["config/password"] 28 | 29 | [mining] 30 | force_sealing = true 31 | min_gas_price = 1000000000 32 | gas_floor_target = "12000000" 33 | engine_signer = "0x522df396ae70a058bd69778408630fdb023389b2" 34 | reseal_on_txs = "none" 35 | extra_data = "Parity" 36 | tx_gas_limit="7500000" 37 | tx_time_limit=1000 38 | 39 | [misc] 40 | logging = "engine=trace,miner=trace,reward=trace" 41 | -------------------------------------------------------------------------------- /config/node4.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node4", 8 | "LogDirectory": "./data/node4", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30304, 14 | "P2PPort": 30304 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8544, 20 | "WebSocketsPort": 9544 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true 38 | }, 39 | "KeyStore": 40 | { 41 | "BlockAuthorAccount": "0xf67cc5231c5858ad6cc87b105217426e17b824bb", 42 | "PasswordFiles": ["./config/password"], 43 | "UnlockAccounts": ["0xf67cc5231c5858ad6cc87b105217426e17b824bb"], 44 | "KeyStoreDirectory": "./data/node4/keys/DPoSChain" 45 | }, 46 | "Mining": 47 | { 48 | "MinGasPrice": "1000000000", 49 | "TargetBlockGasLimit": "12000000" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/node4.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node4" 4 | 5 | [network] 6 | port = 30304 7 | discovery = true 8 | reserved_peers="data/reserved-peers" 9 | nat = "none" 10 | interface = "local" 11 | 12 | [rpc] 13 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 14 | port = 8544 15 | 16 | [websockets] 17 | port = 9544 18 | 19 | [ipc] 20 | disable = true 21 | 22 | [secretstore] 23 | disable = true 24 | 25 | [account] 26 | unlock = ["0xf67cc5231c5858ad6cc87b105217426e17b824bb"] 27 | password = ["config/password"] 28 | 29 | [mining] 30 | force_sealing = true 31 | min_gas_price = 1000000000 32 | gas_floor_target = "12000000" 33 | engine_signer = "0xf67cc5231c5858ad6cc87b105217426e17b824bb" 34 | reseal_on_txs = "none" 35 | extra_data = "Parity" 36 | tx_gas_limit="7500000" 37 | tx_time_limit=1000 38 | 39 | [misc] 40 | logging = "engine=trace,miner=trace,reward=trace" 41 | -------------------------------------------------------------------------------- /config/node5.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node5", 8 | "LogDirectory": "./data/node5", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30305, 14 | "P2PPort": 30305 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8545, 20 | "WebSocketsPort": 9545 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true 38 | }, 39 | "KeyStore": 40 | { 41 | "BlockAuthorAccount": "0xbe69eb0968226a1808975e1a1f2127667f2bffb3", 42 | "PasswordFiles": ["./config/password"], 43 | "UnlockAccounts": ["0xbe69eb0968226a1808975e1a1f2127667f2bffb3"], 44 | "KeyStoreDirectory": "./data/node5/keys/DPoSChain" 45 | }, 46 | "Mining": 47 | { 48 | "MinGasPrice": "1000000000", 49 | "TargetBlockGasLimit": "12000000" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/node5.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node5" 4 | 5 | [network] 6 | port = 30305 7 | discovery = true 8 | reserved_peers="data/reserved-peers" 9 | nat = "none" 10 | interface = "local" 11 | 12 | [rpc] 13 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 14 | port = 8545 15 | 16 | [websockets] 17 | port = 9545 18 | 19 | [ipc] 20 | disable = true 21 | 22 | [secretstore] 23 | disable = true 24 | 25 | [account] 26 | unlock = ["0xbe69eb0968226a1808975e1a1f2127667f2bffb3"] 27 | password = ["config/password"] 28 | 29 | [mining] 30 | force_sealing = true 31 | min_gas_price = 1000000000 32 | gas_floor_target = "12000000" 33 | engine_signer = "0xbe69eb0968226a1808975e1a1f2127667f2bffb3" 34 | reseal_on_txs = "none" 35 | extra_data = "Parity" 36 | tx_gas_limit="7500000" 37 | tx_time_limit=1000 38 | 39 | [misc] 40 | logging = "engine=trace,miner=trace,reward=trace" 41 | -------------------------------------------------------------------------------- /config/node6.nethermind.json: -------------------------------------------------------------------------------- 1 | { 2 | "Init": { 3 | "WebSocketsEnabled": true, 4 | "StoreReceipts" : true, 5 | "IsMining": true, 6 | "ChainSpecPath": "./data/spec.json", 7 | "BaseDbPath": "./data/node6", 8 | "LogDirectory": "./data/node6", 9 | "LogFileName": "nethermind.logs.txt", 10 | "StaticNodesPath": "./data/reserved-peers" 11 | }, 12 | "Network": { 13 | "DiscoveryPort": 30306, 14 | "P2PPort": 30306 15 | }, 16 | "JsonRpc": { 17 | "Enabled": true, 18 | "Host": "127.0.0.1", 19 | "Port": 8546, 20 | "WebSocketsPort": 9546 21 | }, 22 | "Db": { 23 | "WriteBufferSize": 16000000, 24 | "WriteBufferNumber": 4, 25 | "BlockCacheSize": 128000000, 26 | "CacheIndexAndFilterBlocks": true, 27 | "BlockInfosDbCacheIndexAndFilterBlocks": false, 28 | "HeadersDbCacheIndexAndFilterBlocks": false, 29 | "BlocksDbCacheIndexAndFilterBlocks": false 30 | }, 31 | "Sync": { 32 | "FastSync": false 33 | }, 34 | "Aura": 35 | { 36 | "ForceSealing": true, 37 | "AllowAuRaPrivateChains": true 38 | }, 39 | "KeyStore": 40 | { 41 | "BlockAuthorAccount": "0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9", 42 | "PasswordFiles": ["./config/password"], 43 | "UnlockAccounts": ["0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9"], 44 | "KeyStoreDirectory": "./data/node6/keys/DPoSChain" 45 | }, 46 | "Mining": 47 | { 48 | "MinGasPrice": "1000000000", 49 | "TargetBlockGasLimit": "12000000" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/node6.openethereum.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "./data/spec.json" 3 | base_path = "data/node6" 4 | 5 | [network] 6 | port = 30306 7 | discovery = true 8 | reserved_peers="data/reserved-peers" 9 | nat = "none" 10 | interface = "local" 11 | 12 | [rpc] 13 | apis = ["web3", "eth", "pubsub", "net", "parity", "parity_set", "parity_pubsub", "personal", "traces"] 14 | port = 8546 15 | 16 | [websockets] 17 | port = 9546 18 | 19 | [ipc] 20 | disable = true 21 | 22 | [secretstore] 23 | disable = true 24 | 25 | [account] 26 | unlock = ["0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9"] 27 | password = ["config/password"] 28 | 29 | [mining] 30 | force_sealing = true 31 | min_gas_price = 1000000000 32 | gas_floor_target = "12000000" 33 | engine_signer = "0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9" 34 | reseal_on_txs = "none" 35 | extra_data = "Parity" 36 | tx_gas_limit="7500000" 37 | tx_time_limit=1000 38 | 39 | [misc] 40 | logging = "engine=trace,miner=trace,reward=trace" 41 | -------------------------------------------------------------------------------- /config/password: -------------------------------------------------------------------------------- 1 | testnetpoa 2 | -------------------------------------------------------------------------------- /data/node0/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnosischain/posdao-test-setup/d2828623e551398330214d6a4a51705a6baa7503/data/node0/.gitkeep -------------------------------------------------------------------------------- /data/node0/keys/DPoSChain/address_book.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /data/node1/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnosischain/posdao-test-setup/d2828623e551398330214d6a4a51705a6baa7503/data/node1/.keep -------------------------------------------------------------------------------- /data/node1/keys/DPoSChain/0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"be60c5ac-6c4a-44c4-a12f-9558131ab64a","address":"32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24","Crypto":{"ciphertext":"68d1202b416408cb22ca60ba4f4deabcbddad7a262e014e1239dbe7354677ebb","cipherparams":{"iv":"a37afd65f955ab252340496defade99d"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"47fdc45755f6f75072fb8c1a1eea9ec3a06450754eb78f0cc726baef22fed0bf","n":8192,"r":8,"p":1},"mac":"00def76f42948515e1f498b433860ef4a0fca7f9ce7a81eee0abc470f365ceb7"}} 2 | -------------------------------------------------------------------------------- /data/node1/keys/DPoSChain/0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78.json: -------------------------------------------------------------------------------- 1 | {"id":"5afa8ed4-e6fc-00b0-ef3a-2cdaad1e614d","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"56f6fbe53417fbb6e655637773a606f3"},"ciphertext":"2efca20f19100bb6dbc1ec40b2ee65b136dbb466a93fc0729bd76fdf11de664b","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"4b6c770bc53f27174cb8a1cfced0c5d3bcd5779178f316d92dfb265ab2a9cd31"},"mac":"e5803846a2d967a91193dded01fc8a03d8cc27b8ba64ef50646dc24a8e575037"},"address":"bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78","name":"","meta":"{}"} -------------------------------------------------------------------------------- /data/node2/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnosischain/posdao-test-setup/d2828623e551398330214d6a4a51705a6baa7503/data/node2/.keep -------------------------------------------------------------------------------- /data/node2/keys/DPoSChain/0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"be60c5ac-6c4a-44c4-a12f-9558131ab64a","address":"32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24","Crypto":{"ciphertext":"68d1202b416408cb22ca60ba4f4deabcbddad7a262e014e1239dbe7354677ebb","cipherparams":{"iv":"a37afd65f955ab252340496defade99d"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"47fdc45755f6f75072fb8c1a1eea9ec3a06450754eb78f0cc726baef22fed0bf","n":8192,"r":8,"p":1},"mac":"00def76f42948515e1f498b433860ef4a0fca7f9ce7a81eee0abc470f365ceb7"}} 2 | -------------------------------------------------------------------------------- /data/node2/keys/DPoSChain/0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441.json: -------------------------------------------------------------------------------- 1 | {"id":"fa7fe28d-16ab-b6f5-7562-630fe7e2953c","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"3110d9e4f079a10aef3c26f017e537d8"},"ciphertext":"f2c8eacce3bdfb6d86d9aee5f15bd344133fa7e9b34ef0411cd29ef78869b8a0","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"5f641fbd64c0e126b250b374853f5d18bf17fd509a70bcbc302153c016faf7f6"},"mac":"ad77daf70f7bb8d607077c904fc40321183b1fe7405f1214ac3a23e1f718be2d"},"address":"75df42383afe6bf5194aa8fa0e9b3d5f9e869441","name":"","meta":"{}"} -------------------------------------------------------------------------------- /data/node3/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnosischain/posdao-test-setup/d2828623e551398330214d6a4a51705a6baa7503/data/node3/.keep -------------------------------------------------------------------------------- /data/node3/keys/DPoSChain/0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"be60c5ac-6c4a-44c4-a12f-9558131ab64a","address":"32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24","Crypto":{"ciphertext":"68d1202b416408cb22ca60ba4f4deabcbddad7a262e014e1239dbe7354677ebb","cipherparams":{"iv":"a37afd65f955ab252340496defade99d"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"47fdc45755f6f75072fb8c1a1eea9ec3a06450754eb78f0cc726baef22fed0bf","n":8192,"r":8,"p":1},"mac":"00def76f42948515e1f498b433860ef4a0fca7f9ce7a81eee0abc470f365ceb7"}} 2 | -------------------------------------------------------------------------------- /data/node3/keys/DPoSChain/0x522df396ae70a058bd69778408630fdb023389b2.json: -------------------------------------------------------------------------------- 1 | {"id":"35142060-03f5-78a0-fe4c-1d2545137da8","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"beeb869ca61be05c3e746e60b5d1a5c1"},"ciphertext":"35ff91c26c89cbe35e4071cfc7c87c2ec10a8b2d6b2fa1e942c1cd4f49ee4b37","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"0aa0176bf16d0d1340d20932faea5299c17b09f0f401e500545f92904d43301c"},"mac":"c4ce345c9f9eeac861bd2f8e6f41be14e221e4d374fee6d0625e682391af0d04"},"address":"522df396ae70a058bd69778408630fdb023389b2","name":"","meta":"{}"} -------------------------------------------------------------------------------- /data/node4/keys/DPoSChain/0xf67cc5231c5858ad6cc87b105217426e17b824bb.json: -------------------------------------------------------------------------------- 1 | {"id":"b3b5371c-7a49-ae31-01e0-1e827fd9fda6","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"75dcb9f5ea86ad12a85274f9f4a35ee7"},"ciphertext":"55e6b5dc8026c32b0d8dc92d12b807abe724598229143bce98a096fe6a9c7332","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"6e660fcb9ae4a0eaf6a7acc96acb7930e328645ce1b4544200388e24029db01d"},"mac":"d92be1116594290d4bd677b777c70dbfc233475502d65fe7e9850706aa3cf073"},"address":"f67cc5231c5858ad6cc87b105217426e17b824bb","name":"","meta":"{}"} -------------------------------------------------------------------------------- /data/node5/keys/DPoSChain/0xbe69eb0968226a1808975e1a1f2127667f2bffb3.json: -------------------------------------------------------------------------------- 1 | {"id":"c22fc0ee-5788-9afa-f85d-b5bcccac6f99","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"224fa37102b43887e58fff0fb6054340"},"ciphertext":"be628355c8b1bd918c470f8dc1466abaa59e812c629574bb560da24601e3a4f3","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"7644636bdab757cda6927be5ff0d3882fd126bf1559190eec058f199283229cd"},"mac":"5431830b2b0b314f30f6fea718fc2c0909a1425be7650ac30ce93c4411a86011"},"address":"be69eb0968226a1808975e1a1f2127667f2bffb3","name":"","meta":"{}"} -------------------------------------------------------------------------------- /data/node6/keys/DPoSChain/0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9.json: -------------------------------------------------------------------------------- 1 | {"id":"a0a9df30-804e-520f-9951-472b28cdec6e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"8ad84ae9685669a060750fa28b9760b6"},"ciphertext":"e9d3ed5aa4951dead67136fea1b2bdf9d9df2624aff4bc3d4c8fc186d14f208f","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"64860db5b5a1cd65f3dc97e3f0d96f4c7ef8166377cecc7c4679529dd640bcee"},"mac":"e9646576f7de1059d1fca4b08a02ceb3ae915b27e34acf32716c5d8e67fcc631"},"address":"720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9","name":"","meta":"{}"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posdao-test-setup", 3 | "version": "0.0.1", 4 | "description": "", 5 | "scripts": { 6 | "after-start": "npm run checkers && npm run test && npm run watcher && npm run stop-test-setup", 7 | "after-start-no-watcher": "npm run checkers && npm run test && npm run stop-test-setup", 8 | "all": "CLIENT=openethereum npm run before-start && npm run start-test-setup-openethereum && npm run after-start", 9 | "all-nethermind": "CLIENT=nethermind npm run before-start && npm run start-test-setup-nethermind && npm run after-start", 10 | "all-nethermind-no-watcher": "CLIENT=nethermind npm run before-start && npm run start-test-setup-nethermind && npm run after-start-no-watcher", 11 | "before-start": "npm i && npm run get-all-submodules && npm run cleanup && npm run compile-posdao-contracts && npm run make-spec", 12 | "checkers": "(node scripts/watchOrdinaryNode.js &) && node scripts/deploy-staking-token.js && (node scripts/watchRandomSeed &)", 13 | "cleanup": "bash scripts/stop-test-setup && rm -rf ./accounts ./config ./data && git checkout ./accounts && git checkout ./config && git checkout ./data", 14 | "get-all-submodules": "git submodule update --init --remote", 15 | "compile-posdao-contracts": "cd ./posdao-contracts && npm i && npm run compile", 16 | "make-spec": ". ./scripts/network-spec && cd ./posdao-contracts && npm i && node ./scripts/make_spec.js && node ../scripts/copy-spec.js", 17 | "start-test-setup-openethereum": "bash scripts/start-test-setup ../openethereum/target/release/openethereum", 18 | "start-test-setup-nethermind": "bash scripts/start-test-setup ../nethermind/bin/Nethermind.Runner", 19 | "test": "node_modules/.bin/mocha --bail --timeout 1200000", 20 | "stop-test-setup": "bash scripts/stop-test-setup", 21 | "watcher": "node scripts/watcher.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/poanetwork/posdao-test-setup.git" 26 | }, 27 | "author": "poa.network", 28 | "license": "ISC", 29 | "dependencies": { 30 | "chai": "^4.2.0", 31 | "chai-as-promised": "^7.1.1", 32 | "chai-bn": "^0.1.1", 33 | "ethereumjs-tx": "^1.3.7", 34 | "mocha": "^6.1.4", 35 | "rlp": "2.2.6", 36 | "secp256k1": "4.0.2", 37 | "solc": "0.5.10", 38 | "web3": "1.3.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/copy-spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { promisify } = require('util'); 3 | const readFile = promisify(fs.readFile); 4 | const assert = require('assert'); 5 | 6 | async function main() { 7 | let specFile = await readFile(__dirname + '/../posdao-contracts/spec.json', 'UTF-8'); 8 | assert(typeof specFile === 'string'); 9 | specFile = JSON.parse(specFile); 10 | assert(specFile.engine.authorityRound.params.stepDuration != null); 11 | 12 | // Set step duration map for testing purposes 13 | specFile.engine.authorityRound.params.stepDuration = { 14 | "0": 5 15 | }; 16 | // Switch to another duration in 120 seconds 17 | const newStepDurationTimestamp = Math.round((Date.now() / 1000 + 120) / 10) * 10; 18 | specFile.engine.authorityRound.params.stepDuration[newStepDurationTimestamp] = 4; 19 | console.log(); 20 | console.log(); 21 | console.log('STEP DURATION WILL BE CHANGED AT ', new Date(newStepDurationTimestamp * 1000).toLocaleTimeString('en-US')); 22 | console.log(); 23 | console.log(); 24 | 25 | // Activate London hard fork 26 | specFile.params.eip3198Transition = "0"; 27 | specFile.params.eip3529Transition = "0"; 28 | specFile.params.eip3541Transition = "0"; 29 | specFile.params.eip1559Transition = "8"; 30 | specFile.params.eip1559BaseFeeMaxChangeDenominator = "0x8"; 31 | specFile.params.eip1559ElasticityMultiplier = "0x2"; 32 | specFile.params.eip1559BaseFeeInitialValue = "0x3b9aca00"; 33 | specFile.params.eip1559BaseFeeMinValue = "0x1dcd6500"; 34 | specFile.params.eip1559BaseFeeMinValueTransition = "8"; 35 | specFile.params.eip1559FeeCollector = "0x1559000000000000000000000000000000000000"; 36 | specFile.params.eip1559FeeCollectorTransition = specFile.params.eip1559Transition; 37 | //specFile.genesis.baseFeePerGas = specFile.params.eip1559BaseFeeInitialValue 38 | 39 | //if (process.env.CLIENT == 'openethereum') { 40 | // specFile.params.validateServiceTransactionsTransition = "0"; // OpenEthereum specific 41 | //} 42 | 43 | await promisify(fs.writeFile)(__dirname + '/../data/spec.json', JSON.stringify(specFile, null, ' '), 'UTF-8'); 44 | } 45 | 46 | main(); 47 | -------------------------------------------------------------------------------- /scripts/deploy-staking-token.js: -------------------------------------------------------------------------------- 1 | // Assumes network is started 2 | 3 | const Web3 = require('web3'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const solc = require('solc'); 7 | const constants = require('../utils/constants'); 8 | const SnS = require('../utils/signAndSendTx.js'); 9 | const sendRequest = require('../utils/sendRequest.js'); 10 | const web3 = new Web3('http://localhost:8541'); 11 | web3.eth.transactionConfirmationBlocks = 1; 12 | web3.eth.transactionPollingTimeout = 30; 13 | const BN = web3.utils.BN; 14 | const BlockRewardAuRa = require(path.join(__dirname, '../utils/getContract'))('BlockRewardAuRa', web3); 15 | const StakingAuRa = require(path.join(__dirname, '../utils/getContract'))('StakingAuRa', web3); 16 | const OWNER = constants.OWNER; 17 | const expect = require('chai') 18 | .use(require('chai-bn')(BN)) 19 | .use(require('chai-as-promised')) 20 | .expect; 21 | const pp = require('../utils/prettyPrint'); 22 | const mintCoinsToCandidates = require('./mint-coins-to-candidates'); 23 | let tokenName = 'STAKE'; 24 | let tokenSymbol = 'STAKE'; 25 | let tokenDecimals = 18; 26 | 27 | function compileContract() { 28 | let input = { 29 | language: 'Solidity', 30 | sources: { 31 | 'token.sol': { 32 | content: fs.readFileSync(path.join(__dirname, '../posdao-contracts/contracts/ERC677BridgeTokenRewardable.sol'), 'utf8'), 33 | }, 34 | }, 35 | settings: { 36 | outputSelection: { 37 | '*': { 38 | '*': ['*'], 39 | }, 40 | }, 41 | }, 42 | }; 43 | let compiledContract = JSON.parse( solc.compile(JSON.stringify(input)) ); 44 | return compiledContract.contracts['token.sol']['ERC677BridgeTokenRewardable']; 45 | } 46 | 47 | async function main() { 48 | console.log('**** Check that StakingToken is already deployed in StakingAuRa'); 49 | let existingStakingTokenAddress = await StakingAuRa.instance.methods.erc677TokenContract().call(); 50 | if (existingStakingTokenAddress 51 | && existingStakingTokenAddress.toLowerCase() != '0x' 52 | && existingStakingTokenAddress.toLowerCase() != '0x0000000000000000000000000000000000000000' 53 | ) { 54 | console.log('***** StakingToken already deployed at ' + existingStakingTokenAddress + ', skipping deployment'); 55 | return; 56 | } 57 | 58 | let compiledContract = compileContract(); 59 | let abi = compiledContract.abi; 60 | let bytecode = compiledContract.evm.bytecode.object; 61 | const netId = await web3.eth.net.getId(); 62 | 63 | console.log(`**** Deploying StakingToken. netId = ${netId}`); 64 | const contract = new web3.eth.Contract(abi); 65 | 66 | // Deploy using eth_sendTransaction 67 | const data = await contract 68 | .deploy({ 69 | data: '0x' + bytecode, 70 | arguments: [tokenName, tokenSymbol, tokenDecimals, netId], 71 | }) 72 | .encodeABI(); 73 | let txParams; 74 | const latestBlock = await sendRequest(`curl --data '{"method":"eth_getBlockByNumber","params":["latest",false],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST ${web3.currentProvider.host} 2>/dev/null`); 75 | if (latestBlock.baseFeePerGas) { // EIP-1559 is activated, so we can use a new type of transactions 76 | txParams = { 77 | from: OWNER, 78 | type: '0x2', 79 | chainId: web3.utils.numberToHex(netId), 80 | maxPriorityFeePerGas: web3.utils.numberToHex('0'), 81 | maxFeePerGas: web3.utils.numberToHex('0'), 82 | gas: web3.utils.numberToHex('4700000'), 83 | data, 84 | accessList: [] 85 | }; 86 | const nodeInfo = await web3.eth.getNodeInfo(); 87 | if (nodeInfo.indexOf('OpenEthereum') >= 0) { 88 | delete txParams.chainId; 89 | } 90 | } else { // EIP-1559 is not activated. Use a legacy transaction 91 | txParams = { 92 | from: OWNER, 93 | gasPrice: web3.utils.numberToHex('0'), 94 | gas: web3.utils.numberToHex('4700000'), 95 | data 96 | }; 97 | } 98 | const txHash = await sendRequest(`curl --data '{"method":"eth_sendTransaction","params":[${JSON.stringify(txParams)}],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST ${web3.currentProvider.host} 2>/dev/null`); 99 | let stakingTokenDeployTxReceipt; 100 | while(!(stakingTokenDeployTxReceipt = await sendRequest(`curl --data '{"method":"eth_getTransactionReceipt","params":["${txHash}"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST ${web3.currentProvider.host} 2>/dev/null`))) { 101 | await sleep(500); 102 | } 103 | /* 104 | // Deploy using eth_sendRawTransaction 105 | const stakingTokenDeploy = await contract.deploy({ 106 | data: '0x' + bytecode, 107 | arguments: [tokenName, tokenSymbol, tokenDecimals, netId] 108 | }); 109 | const stakingTokenDeployTxReceipt = await SnS(web3, { 110 | from: OWNER, 111 | method: stakingTokenDeploy, 112 | gasLimit: '4700000', 113 | gasPrice: '0' 114 | }); 115 | */ 116 | const StakingTokenInstance = new web3.eth.Contract(abi, stakingTokenDeployTxReceipt.contractAddress); 117 | 118 | let address = StakingTokenInstance.options.address; 119 | console.log('**** StakingToken deployed at:', address); 120 | 121 | console.log('**** Saving output to ./data'); 122 | let runtimeData = { abi, address }; 123 | fs.writeFileSync(path.join(__dirname, '../data/StakingToken.json'), JSON.stringify(runtimeData, null, 4)); 124 | 125 | let tx; 126 | 127 | console.log('**** Set StakingAuRa address in StakingToken contract'); 128 | tx = await SnS(web3, { 129 | from: OWNER, 130 | to: address, 131 | method: StakingTokenInstance.methods.setStakingContract(StakingAuRa.address), 132 | gasPrice: '0', 133 | }); 134 | pp.tx(tx); 135 | expect(tx.status).to.equal(true); 136 | 137 | console.log('**** Set BlockRewardAuRa address in StakingToken contract'); 138 | tx = await SnS(web3, { 139 | from: OWNER, 140 | to: address, 141 | method: StakingTokenInstance.methods.setBlockRewardContract(BlockRewardAuRa.address), 142 | gasPrice: '0', 143 | }); 144 | pp.tx(tx); 145 | expect(tx.status).to.equal(true); 146 | 147 | console.log('**** Set StakingToken address in StakingAuRa'); 148 | tx = await SnS(web3, { 149 | from: OWNER, 150 | to: StakingAuRa.address, 151 | method: StakingAuRa.instance.methods.setErc677TokenContract(address), 152 | gasPrice: '0', 153 | }); 154 | pp.tx(tx); 155 | expect(tx.status).to.equal(true); 156 | 157 | let contractAddress; 158 | 159 | console.log('**** Check that StakingAuRa address in StakingToken contract is correct'); 160 | contractAddress = await StakingTokenInstance.methods.stakingContract().call(); 161 | expect(contractAddress).to.equal(StakingAuRa.address); 162 | 163 | console.log('**** Check that BlockRewardAuRa address in StakingToken contract is correct'); 164 | contractAddress = await StakingTokenInstance.methods.blockRewardContract().call(); 165 | expect(contractAddress).to.equal(BlockRewardAuRa.address); 166 | 167 | console.log('**** Check that StakingToken address in StakingAuRa is correct'); 168 | contractAddress = await StakingAuRa.instance.methods.erc677TokenContract().call(); 169 | expect(contractAddress).to.equal(address); 170 | 171 | console.log('**** Mint initial coins to candidates and unremovable validator'); 172 | await mintCoinsToCandidates(); 173 | } 174 | 175 | function sleep(millis) { 176 | return new Promise(resolve => setTimeout(resolve, millis)); 177 | } 178 | 179 | main(); 180 | -------------------------------------------------------------------------------- /scripts/getReservedPeer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { URL } = require('url'); 3 | const process = require('process'); 4 | var os = require("os"); 5 | 6 | main(); 7 | 8 | async function main() { 9 | const maxAttempts = 50; 10 | var node_index = process.argv[2].toString(); 11 | console.log("Registering node " + node_index + " as bootnode"); 12 | const cmd = `curl --data '{"method":"parity_enode","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:854` 13 | + node_index 14 | + ` 2>/dev/null`; 15 | console.log(`> ` + cmd); 16 | for (let i = 1; i <= maxAttempts; i++) { 17 | try { 18 | const enodeURL = await getEnodeURL(cmd); 19 | console.log("enode URL: " + enodeURL); 20 | fs.appendFileSync("data/reserved-peers", enodeURL + os.EOL); 21 | 22 | const bootnodes = fs.readFileSync("data/reserved-peers", 'utf-8').trim().split(os.EOL); 23 | 24 | const tomlFilepath = `config/node${node_index - 0 + 1}.openethereum.toml`; 25 | if (fs.existsSync(tomlFilepath)) { 26 | let toml = fs.readFileSync(tomlFilepath, 'utf-8'); 27 | toml = toml.replace('reserved_peers="data/reserved-peers"', `bootnodes = ${JSON.stringify(bootnodes)}`); 28 | fs.writeFileSync(tomlFilepath, toml, 'utf-8'); 29 | } 30 | const jsonFilepath = `config/node${node_index - 0 + 1}.nethermind.json`; 31 | if (fs.existsSync(jsonFilepath)) { 32 | let json = JSON.parse(fs.readFileSync(jsonFilepath, 'utf-8')); 33 | delete json.Init.StaticNodesPath; 34 | json.Discovery = {"Bootnodes" : bootnodes.join(',')}; 35 | fs.writeFileSync(jsonFilepath, JSON.stringify(json, null, 2), 'utf-8'); 36 | } 37 | 38 | break; 39 | } catch(e) { 40 | if (i <= maxAttempts) { 41 | await sleep(500); 42 | } else { 43 | console.log(e.message); 44 | } 45 | } 46 | } 47 | } 48 | 49 | function getEnodeURL(cmd) { 50 | return new Promise((resolve, reject) => { 51 | var exec = require('child_process').exec; 52 | exec(cmd, function (error, stdout, stderr) { 53 | if (error !== null) { 54 | reject(error); 55 | } 56 | let resp; 57 | try { 58 | resp = JSON.parse(stdout); 59 | } catch(e) { 60 | reject(e); 61 | } 62 | let result; 63 | try { 64 | if (resp.result) { 65 | result = new URL(resp.result); 66 | result.host = '127.0.0.1'; 67 | result = result.href; 68 | } else { 69 | throw new Error('result is undefined'); 70 | } 71 | } catch (e) { 72 | reject(e); 73 | } 74 | resolve(result); 75 | }); 76 | }) 77 | } 78 | 79 | function sleep(millis) { 80 | return new Promise(resolve => setTimeout(resolve, millis)); 81 | } 82 | -------------------------------------------------------------------------------- /scripts/mint-coins-to-candidates.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const path = require('path'); 3 | const SnS = require('../utils/signAndSendTx.js'); 4 | const web3 = new Web3('http://localhost:8541'); 5 | web3.eth.transactionConfirmationBlocks = 1; 6 | const BN = web3.utils.BN; 7 | const ValidatorSetAuRa = require(path.join(__dirname, '../utils/getContract'))('ValidatorSetAuRa', web3); 8 | const expect = require('chai') 9 | .use(require('chai-bn')(BN)) 10 | .use(require('chai-as-promised')) 11 | .expect; 12 | const mintCoins = require('../utils/mintCoins'); 13 | const constants = require('../utils/constants'); 14 | 15 | const coins = constants.CANDIDATE_INITIAL_BALANCE; 16 | 17 | module.exports = async function () { 18 | const unremovableValidator = await ValidatorSetAuRa.instance.methods.unremovableValidator().call(); 19 | const unremovableValidatorExists = unremovableValidator != '0'; 20 | const unremovableValidatorStakingAddress = await ValidatorSetAuRa.instance.methods.stakingAddressById(unremovableValidator).call(); 21 | let toWhom = [...constants.CANDIDATES.map(c => c.staking)]; 22 | if (unremovableValidatorExists) { 23 | toWhom.push(unremovableValidatorStakingAddress); 24 | } 25 | const txs = await mintCoins(web3, constants.OWNER, toWhom, coins); 26 | for (const tx of txs) { 27 | expect(tx.status, `Tx to mint inital balance failed: ${tx.transactionHash}`).to.equal(true); 28 | } 29 | for (let i = 0; i < constants.CANDIDATES.length; i++) { 30 | const candidate = constants.CANDIDATES[i].staking; 31 | const balanceBN = await web3.eth.getBalance(candidate); 32 | expect(balanceBN, 33 | `Amount initial coins minted to ${candidate} is incorrect: expected ${coins}, but got ${balanceBN.toString()}` 34 | ).to.be.equal(coins); 35 | } 36 | if (unremovableValidatorExists) { 37 | const balanceBN = await web3.eth.getBalance(unremovableValidatorStakingAddress); 38 | expect(balanceBN, 39 | `Amount initial coins minted to unremovable validator (${unremovableValidatorStakingAddress}) is incorrect: expected ${coins}, but got ${balanceBN.toString()}` 40 | ).to.be.equal(coins); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/network-spec: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Export all variables. 4 | set -a 5 | 6 | NETWORK_NAME=DPoSChain 7 | NETWORK_ID=101 8 | OWNER=0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24 9 | INITIAL_VALIDATORS=0xbbcaa8d48289bb1ffcf9808d9aa4b1d215054c78,0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441,0x522df396ae70a058bd69778408630fdb023389b2 10 | STAKING_ADDRESSES=0x0b2f5e2f3cbd864eaa2c642e3769c1582361caf6,0xaa94b687d3f9552a453b81b2834ca53778980dc0,0x312c230e7d6db05224f60208a656e3541c5c42ba 11 | FIRST_VALIDATOR_IS_UNREMOVABLE=true 12 | STAKING_EPOCH_DURATION=76 13 | STAKE_WITHDRAW_DISALLOW_PERIOD=10 14 | COLLECT_ROUND_LENGTH=38 15 | -------------------------------------------------------------------------------- /scripts/start-test-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on undefined variables. 4 | set -u 5 | set -e 6 | set -x 7 | 8 | CLIENT=$1 9 | if [[ $CLIENT == *"openethereum"* ]]; then 10 | extension="openethereum.toml" 11 | else 12 | extension="nethermind.json" 13 | fi 14 | for i in $(seq 0 6); do 15 | "$CLIENT" --config "./config/node${i}.${extension}" >> "./data/node${i}/log" 2>&1 & 16 | node ./scripts/getReservedPeer.js "$i" 17 | done 18 | -------------------------------------------------------------------------------- /scripts/stop-test-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function kill_at_port { 4 | PID=`lsof -t -i:${1}` 5 | if [ "$PID" != "" ]; then 6 | echo Killing pid $PID at port $1 7 | kill -9 $PID 8 | else 9 | echo Nothing to kill at port $1 10 | fi 11 | } 12 | 13 | # Kill test nodes. 14 | for i in $(seq 0 6); do 15 | kill_at_port 854$i 16 | done 17 | -------------------------------------------------------------------------------- /scripts/watchOrdinaryNode.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Web3 = require('web3'); 4 | const os = require('os'); 5 | 6 | // ordinary node 7 | const web3Ord = new Web3('http://localhost:8540'); 8 | // reference validator node 9 | const web3Val = new Web3('http://localhost:8541'); 10 | // block time 11 | const blockTimeMS = 2539; 12 | 13 | const node0Path = '../data/node0/'; 14 | const blocksLogFileName = path.join(__dirname, `${node0Path}blocks.log`); 15 | const checkLogFileName = path.join(__dirname, `${node0Path}check.log`); 16 | 17 | fs.writeFileSync(blocksLogFileName, '', 'utf8'); 18 | fs.writeFileSync(checkLogFileName, '', 'utf8'); 19 | 20 | function getLatestBlock(web3) { 21 | return web3.eth.getBlock('latest', false); 22 | } 23 | 24 | function reportBad(blockOrd, blockVal, reason) { 25 | fs.appendFileSync(checkLogFileName, JSON.stringify({ 26 | reason, 27 | ordinaryNodeBlock: { 28 | number: blockOrd.number, 29 | hash: blockOrd.hash, 30 | author: blockOrd.author, 31 | }, 32 | validatorNodeBlock: { 33 | number: blockVal.number, 34 | hash: blockVal.hash, 35 | author: blockVal.author, 36 | }, 37 | }) + os.EOL, 'utf8'); 38 | } 39 | 40 | function doCheck() { 41 | Promise.all([ 42 | getLatestBlock(web3Ord), 43 | getLatestBlock(web3Val) 44 | ]).then(blocks => { 45 | let blockOrd = blocks[0]; 46 | let blockVal = blocks[1]; 47 | 48 | fs.appendFileSync(blocksLogFileName, `${blockOrd.number} (${blockOrd.hash}) - ${blockVal.number} (${blockVal.hash})\n`, 'utf8'); 49 | 50 | if (Math.abs(blockOrd.number - blockVal.number) > 1) { 51 | reportBad(blockOrd, blockVal, 'Block numbers too far apart: ' + (blockOrd.number - blockVal.number)); 52 | return; 53 | } 54 | 55 | if (Math.abs(blockOrd.number - blockVal.number) == 1) { 56 | // maybe we just happen to be in the moment when blocks change, check next time 57 | return; 58 | } 59 | // here block numbers agree 60 | 61 | if (blockOrd.hash.toLowerCase() != blockVal.hash.toLowerCase()) { 62 | reportBad(blockOrd, blockVal, 'Block hashes disagree: ' + blockOrd.hash.toLowerCase() + ' vs ' + blockVal.hash.toLowerCase()); 63 | return; 64 | } 65 | }).catch(e => { 66 | reportBad({}, {}, 'Exception: ' + e); 67 | }); 68 | } 69 | 70 | setInterval(doCheck, blockTimeMS); 71 | -------------------------------------------------------------------------------- /scripts/watchRandomSeed.js: -------------------------------------------------------------------------------- 1 | /* 2 | - RandomAuRa.currentSeed (value should change every RandomAuRa.collectRoundLength() blocks) 3 | */ 4 | 5 | const path = require('path'); 6 | const Web3 = require('web3'); 7 | const os = require('os'); 8 | const fs = require('fs'); 9 | 10 | const web3 = new Web3('http://localhost:8541'); 11 | 12 | const checkIntervalMS = 2539; // should be less than block time 13 | 14 | const node1Path = '../data/node1/'; 15 | const checkLogFileName = path.join(__dirname, `${node1Path}/checkRandomSeed.log`); 16 | const checkDebugFileName = path.join(__dirname, `${node1Path}/checkRandomSeedDebug.log`); 17 | fs.writeFileSync(checkLogFileName, '', 'utf8'); 18 | 19 | const RandomAuRa = require('../utils/getContract')('RandomAuRa', web3).instance; 20 | const ValidatorSetAuRa = require('../utils/getContract')('ValidatorSetAuRa', web3).instance; 21 | let collectRoundLengthBN; 22 | let prevBlock; 23 | 24 | let seedState = (function () { 25 | let lastSeedBN; 26 | let lastChangeStart = 0; 27 | return { 28 | update: function (blockN, currentSeedBN, validatorsLength) { 29 | let err_reason; 30 | if (!lastSeedBN || lastSeedBN.isZero()) { 31 | // not initialized yet 32 | if (!currentSeedBN.isZero()) { 33 | // we start first round here 34 | lastChangeStart = blockN; 35 | } 36 | } 37 | else { 38 | if (blockN - lastChangeStart < validatorsLength) { 39 | // validators reveal their shares, so seed should change every block 40 | if (currentSeedBN.eq(lastSeedBN)) { 41 | err_reason = `seed didn't change in this block, seed value: ${currentSeedBN}, collectRoundLengthBN = ${collectRoundLengthBN}`; 42 | } 43 | } 44 | else if (blockN - lastChangeStart >= validatorsLength && collectRoundLengthBN.gt(new web3.utils.BN(blockN - lastChangeStart))) { 45 | // we are outside of revealing phase but new round has not yet started, so seed should not change 46 | if (!currentSeedBN.eq(lastSeedBN)) { 47 | err_reason = `seed changed outside of revealing phase, previous value: ${lastSeedBN}, current value: ${currentSeedBN}, collectRoundLengthBN = ${collectRoundLengthBN}, lastChangeStart = ${lastChangeStart}`; 48 | } 49 | } 50 | else if (collectRoundLengthBN.lte(new web3.utils.BN(blockN - lastChangeStart))) { 51 | // new round should start, so seed should change 52 | if (currentSeedBN.eq(lastSeedBN)) { 53 | err_reason = `seed didn't change in the beginning of a new round, seed value: ${currentSeedBN}, collectRoundLengthBN = ${collectRoundLengthBN}, lastChangeStart = ${lastChangeStart}`; 54 | } 55 | lastChangeStart = blockN; 56 | } 57 | 58 | } 59 | lastSeedBN = currentSeedBN; 60 | return { 61 | err: !!err_reason, 62 | reason: err_reason 63 | }; 64 | } 65 | } 66 | })(); 67 | 68 | // utility functions: 69 | function appendLine(str) { 70 | fs.appendFileSync(checkLogFileName, `${new Date().toISOString()} ${str}${os.EOL}`, 'utf8'); 71 | } 72 | 73 | function appendDebug(str) { 74 | fs.appendFileSync(checkDebugFileName, `${new Date().toISOString()} ${str}${os.EOL}`, 'utf8'); 75 | } 76 | 77 | async function wait(ms) { 78 | await new Promise(r => setTimeout(r, ms)); 79 | } 80 | 81 | function doCheck() { 82 | Promise.all([ 83 | web3.eth.getBlock('latest', false), 84 | RandomAuRa.methods.currentSeed().call(), 85 | ValidatorSetAuRa.methods.getValidators().call(), 86 | ]).then(results => { 87 | let block = results[0]; 88 | if (block.number == prevBlock) return; 89 | prevBlock = block.number; 90 | let seed = new web3.utils.BN(results[1]); 91 | let validatorsLength = results[2].length; 92 | let report = seedState.update(block.number, seed, validatorsLength); 93 | appendDebug(`[${block.number}]: seed=${seed} author=${block.author} validators=${results[2].join(',')} report=${report.reason||''}`); 94 | if (report.err) { 95 | appendLine(`[${block.number}]: report: ${report.reason}`); 96 | } 97 | }).catch(e => { 98 | appendLine(`exception occured: ${e}`); 99 | }); 100 | } 101 | 102 | 103 | async function main() { 104 | // initially wait until collectRoundLength is defined 105 | while (true) { 106 | let _collectRoundLengthBN = await RandomAuRa.methods.collectRoundLength().call(); 107 | if (_collectRoundLengthBN) { 108 | collectRoundLengthBN = new web3.utils.BN(_collectRoundLengthBN); 109 | break; 110 | } 111 | else { 112 | await wait(checkIntervalMS); 113 | } 114 | } 115 | 116 | setInterval(doCheck, checkIntervalMS); 117 | } 118 | 119 | main(); 120 | -------------------------------------------------------------------------------- /scripts/watcher.js: -------------------------------------------------------------------------------- 1 | console.log(''); 2 | console.log(''); 3 | 4 | const Web3 = require('web3'); 5 | const providerUrl = 'ws://localhost:9541'; 6 | const web3 = new Web3(new Web3.providers.WebsocketProvider(providerUrl)); 7 | 8 | const artifactsPath = '../posdao-contracts/build/contracts/'; 9 | const blockRewardContract = new web3.eth.Contract( 10 | require(`${artifactsPath}BlockRewardAuRa.json`).abi, 11 | '0x2000000000000000000000000000000000000001' 12 | ); 13 | const validatorSetContract = new web3.eth.Contract( 14 | require(`${artifactsPath}ValidatorSetAuRa.json`).abi, 15 | '0x1000000000000000000000000000000000000001' 16 | ); 17 | const stakingContract = new web3.eth.Contract( 18 | require(`${artifactsPath}StakingAuRa.json`).abi, 19 | '0x1100000000000000000000000000000000000001' 20 | ); 21 | const randomContract = new web3.eth.Contract( 22 | require(`${artifactsPath}RandomAuRa.json`).abi, 23 | '0x3000000000000000000000000000000000000001' 24 | ); 25 | 26 | const contractNameByAddress = {}; 27 | contractNameByAddress[validatorSetContract.options.address] = 'ValidatorSetAuRa'; 28 | contractNameByAddress[stakingContract.options.address] = 'StakingAuRa'; 29 | contractNameByAddress[randomContract.options.address] = 'RandomAuRa'; 30 | contractNameByAddress[blockRewardContract.options.address] = 'BlockRewardAuRa'; 31 | contractNameByAddress['0x4100000000000000000000000000000000000000'] = 'TxPriority'; 32 | 33 | var prevBlock = null; 34 | var prevConnected = false; 35 | var tryingReconnect = false; 36 | var subscription = null; 37 | var scanInterval = null; 38 | 39 | connect(); 40 | 41 | async function connect() { 42 | const connected = isConnected(); 43 | if (!connected) { 44 | if (prevConnected) { 45 | console.log('Lost connection'); 46 | } 47 | if (subscription) { 48 | await subscription.unsubscribe(); 49 | subscription = null; 50 | } 51 | if (scanInterval) { 52 | clearInterval(scanInterval); 53 | scanInterval = null; 54 | } 55 | prevBlock = null; 56 | web3.setProvider(new Web3.providers.WebsocketProvider(providerUrl)); 57 | if (!tryingReconnect) { 58 | console.log('Trying a new websocket connection...'); 59 | console.log(); 60 | tryingReconnect = true; 61 | } 62 | } else { 63 | tryingReconnect = false; 64 | if (!prevConnected) { 65 | subscription = web3.eth.subscribe('newBlockHeaders', function(error, result){ 66 | if (error && error.message.includes('not supported')) { 67 | scanInterval = setInterval(scanForNewBlock, 500); 68 | } 69 | }).on("data", blockHeader => onNewBlock(blockHeader.number)); 70 | } 71 | } 72 | prevConnected = connected; 73 | setTimeout(connect, 3000); 74 | } 75 | 76 | async function onNewBlock(blockNumber) { 77 | let block; 78 | if (isConnected()) { 79 | block = await web3.eth.getBlock(blockNumber, true); 80 | } else { 81 | return; 82 | } 83 | 84 | console.log(`Block ${block.number}`); 85 | console.log(` Gas used: ${block.gasUsed} [${block.transactions.length} txs]`); 86 | console.log(` Gas limit: ${block.gasLimit}`); 87 | console.log(` Validator: ${block.miner}`); 88 | console.log(` Base Fee: ${web3.utils.hexToNumber(block.baseFeePerGas)}`); 89 | if (prevBlock) { 90 | console.log(` Timestamp delta from prevBlock: ${block.timestamp - prevBlock.timestamp}`); 91 | } 92 | console.log(''); 93 | 94 | const stakingEpoch = await stakingContract.methods.stakingEpoch().call(); 95 | const stakingEpochStartBlock = await stakingContract.methods.stakingEpochStartBlock().call(); 96 | const validatorSetApplyBlock = await validatorSetContract.methods.validatorSetApplyBlock().call(); 97 | const stakingEpochEndBlock = await stakingContract.methods.stakingEpochEndBlock().call(); 98 | console.log(`stakingEpoch ${stakingEpoch}`); 99 | console.log(` startBlock ${stakingEpochStartBlock}`); 100 | console.log(` applyBlock ${validatorSetApplyBlock > 0 ? validatorSetApplyBlock : '-'}`); 101 | console.log(` endBlock ${stakingEpochEndBlock}`); 102 | console.log(''); 103 | 104 | const collectionRound = await randomContract.methods.currentCollectRound().call(); 105 | const isCommitPhase = await randomContract.methods.isCommitPhase().call(); 106 | console.log(`collectionRound ${collectionRound}`); 107 | console.log(` ${isCommitPhase ? 'COMMITS' : 'REVEALS'} PHASE`); 108 | console.log(''); 109 | 110 | let emptyList = true; 111 | let method; 112 | const validators = await validatorSetContract.methods.getValidators().call(); 113 | 114 | if (isCommitPhase) { 115 | console.log('isCommitted:'); 116 | method = randomContract.methods.isCommitted; 117 | } else { 118 | console.log('sentReveal:'); 119 | method = randomContract.methods.sentReveal; 120 | } 121 | for (let i = 0; i < validators.length; i++) { 122 | const result = await method(collectionRound, validators[i]).call(); 123 | if (result) { 124 | console.log(` ${validators[i]}`); 125 | emptyList = false; 126 | } 127 | } 128 | if (emptyList) { 129 | console.log('-'); 130 | } 131 | console.log(''); 132 | 133 | //console.log('currentRandom:'); 134 | //console.log(await randomContract.methods.currentRandom().call()); 135 | //console.log(''); 136 | 137 | const events = await validatorSetContract.getPastEvents('InitiateChange', {fromBlock: block.number, toBlock: block.number}); 138 | for (let i = 0; i < events.length; i++) { 139 | console.log('InitiateChange:'); 140 | console.log(events[i].returnValues.newSet); 141 | console.log(''); 142 | } 143 | 144 | let txSuccess = []; 145 | let txFail = []; 146 | 147 | for (let i = 0; i < block.transactions.length; i++) { 148 | const tx = block.transactions[i]; 149 | const txReceipt = await web3.eth.getTransactionReceipt(tx.hash); 150 | const txObject = {from: tx.from, to: tx.to, gasPrice: tx.gasPrice, gasLimit: tx.gas, nonce: tx.nonce, receipt: txReceipt, hash: tx.hash}; 151 | if (txReceipt.status) { 152 | txSuccess.push(txObject); 153 | } else { 154 | txFail.push(txObject); 155 | } 156 | } 157 | 158 | if (txSuccess.length > 0) { 159 | console.log('SUCCESS transactions:'); 160 | txSuccess.forEach((tx) => { 161 | let contractName = tx.to; 162 | if (contractNameByAddress.hasOwnProperty(tx.to)) { 163 | contractName = contractNameByAddress[tx.to] 164 | } 165 | console.log(` ${tx.from} => ${contractName}`); 166 | console.log(` gas used: ${tx.receipt.gasUsed}/${tx.gasLimit}, gas price: ${tx.gasPrice}, nonce: ${tx.nonce}, index: ${tx.receipt.transactionIndex}`); 167 | console.log(` hash: ${tx.hash}`); 168 | }); 169 | console.log(''); 170 | } 171 | 172 | if (txFail.length > 0) { 173 | console.log('FAILED transactions:'); 174 | txFail.forEach((tx) => { 175 | let contractName = tx.to; 176 | if (contractNameByAddress.hasOwnProperty(tx.to)) { 177 | contractName = contractNameByAddress[tx.to] 178 | } 179 | console.log(` ${tx.from} => ${contractName}`); 180 | console.log(` gas used: ${tx.receipt.gasUsed}/${tx.gasLimit}, gas price: ${tx.gasPrice}, nonce: ${tx.nonce}, index: ${tx.receipt.transactionIndex}`); 181 | console.log(` hash: ${tx.hash}`); 182 | }); 183 | console.log(''); 184 | } 185 | 186 | console.log(''); 187 | console.log('======================================================='); 188 | console.log(''); 189 | console.log(''); 190 | 191 | prevBlock = block; 192 | } 193 | 194 | function isConnected() { 195 | const connection = web3.currentProvider.connection; 196 | return connection.readyState == connection.OPEN; 197 | } 198 | 199 | async function scanForNewBlock() { 200 | if (isConnected()) { 201 | let blockNumber; 202 | try { 203 | blockNumber = await web3.eth.getBlockNumber(); 204 | } catch (e) { 205 | prevBlock = null; 206 | return; 207 | } 208 | if (!prevBlock || blockNumber > prevBlock.number) { 209 | await onNewBlock(blockNumber); 210 | } 211 | } else { 212 | prevBlock = null; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /simulation/README.md: -------------------------------------------------------------------------------- 1 | ## Delegated Proof of Stake Simulation 2 | 3 | ### Usage 4 | 5 | 1. Download NetLogo v6.0.4 or above from https://ccl.northwestern.edu/netlogo/. 6 | 7 | 2. Load the [model](./dpos.nlogo) and refer to instructions in the **Info** tab. 8 | -------------------------------------------------------------------------------- /simulation/dpos.nlogo: -------------------------------------------------------------------------------- 1 | extensions [vid] 2 | 3 | ;; Candidate nodes. 4 | breed [candidates candidate] 5 | ;; Staking relationships. 6 | directed-link-breed [stakes stake] 7 | 8 | ;; Relationship of having a stake on a candidate. 9 | stakes-own [ 10 | staked-tokens ;; Tokens staked on the target candidate. 11 | rewarded-tokens ;; Reward tokens paid for this staking relationship. 12 | ] 13 | 14 | ;; Candidates, including validators. 15 | candidates-own [ 16 | own-stake ;; The candidate's stake on itself. 17 | validator? ;; Whether the candidate is currently a validator. 18 | ] 19 | 20 | ;; Delegators. 21 | turtles-own [ 22 | age ;; Number of epochs in existence. 23 | staking-tokens ;; Staking tokens available to spend. 24 | reward-tokens ;; Reward tokens earned by this staker. 25 | ] 26 | 27 | globals [ 28 | staking-epoch# 29 | #validators 30 | #candidates 31 | #delegators 32 | ] 33 | 34 | to setup 35 | clear-all 36 | set staking-epoch# 0 37 | set #candidates 0 38 | 39 | set-default-shape turtles "triangle" 40 | set-default-shape candidates "circle" 41 | create-initial-validators 42 | reset-ticks 43 | ;; Video setup. 44 | if record-video [ 45 | vid:start-recorder 46 | vid:record-interface 47 | ] 48 | end 49 | 50 | to go 51 | set staking-epoch# (staking-epoch# + 1) 52 | let current-validators (candidates with [validator?]) 53 | set #validators (count current-validators) 54 | let pool-reward (block-reward / #validators) 55 | ask candidates with [validator?] [ 56 | become-candidate 57 | distribute-reward pool-reward 58 | ] 59 | increment-age 60 | add-delegators 61 | make-some-delegators-candidates 62 | make-stakes 63 | select-validators 64 | tick 65 | ;; Capture video. 66 | if record-video [ 67 | vid:record-interface 68 | ] 69 | end 70 | 71 | to create-initial-validators 72 | create-candidates #initial-validators [ 73 | become-validator 74 | set own-stake min-candidate-stake 75 | ] 76 | end 77 | 78 | to increment-age 79 | ask turtles [ 80 | set age (age + 1) 81 | ] 82 | end 83 | 84 | to distribute-reward [pool-reward] 85 | let pool staking-pool-tokens 86 | let min-validator-reward-ratio (min-validator-reward% / 100) 87 | ifelse (own-stake / pool) > min-validator-reward-ratio [ 88 | let reward-unit (pool-reward / staking-pool-tokens) 89 | 90 | ;; Award the due reward to the validator. 91 | let validator-reward (reward-unit * own-stake) 92 | set reward-tokens (reward-tokens + validator-reward) 93 | move-delegator 94 | 95 | ;; Award the due rewards to the delegators. 96 | ask my-in-stakes [ 97 | let delegator-reward (reward-unit * staked-tokens) 98 | ;; Update the reward for this staking relationship. 99 | set rewarded-tokens (rewarded-tokens + delegator-reward) 100 | ask end1 [ 101 | ;; Update the total reward of the staker. 102 | set reward-tokens (reward-tokens + delegator-reward) 103 | move-delegator 104 | ] 105 | ] 106 | ] [ 107 | let validator-reward (min-validator-reward-ratio * pool-reward) 108 | set reward-tokens (reward-tokens + validator-reward) 109 | move-delegator 110 | 111 | let delegator-staked-tokens (pool - own-stake) 112 | ask my-in-stakes [ 113 | let delegator-reward 114 | ((pool-reward - validator-reward) * staked-tokens / delegator-staked-tokens) 115 | set rewarded-tokens (rewarded-tokens + delegator-reward) 116 | ask end1 [ 117 | set reward-tokens (reward-tokens + delegator-reward) 118 | move-delegator 119 | ] 120 | ] 121 | ] 122 | end 123 | 124 | to move-delegator 125 | setxy (min (list staking-pool-tokens max-pxcor)) (min (list reward-tokens max-pycor)) 126 | end 127 | 128 | ;; Sorts the current candidates in the decreasing order of staking pool amounts. 129 | to-report ordered-candidates 130 | let order-of-stakes [[a b] -> [staking-pool-tokens] of a > [staking-pool-tokens] of b] 131 | report (sort-by order-of-stakes candidates) 132 | end 133 | 134 | to become-delegator 135 | set breed turtles 136 | set size 7 137 | set color sky 138 | end 139 | 140 | to become-candidate 141 | set breed candidates 142 | set validator? false 143 | set size 7 144 | set color green 145 | end 146 | 147 | to become-validator 148 | set breed candidates 149 | set validator? true 150 | set size 7 151 | set color red 152 | end 153 | 154 | to make-some-delegators-candidates 155 | let able-delegators (turtles with [breed != candidates and staking-tokens >= min-candidate-stake]) 156 | ;; Ensure that there are no more than `max-#candidates` after the new candidates are made. 157 | let #new-candidates 158 | (max (list 0 159 | (min (list (max-#candidates - count candidates) (random (sqrt (count able-delegators))))))) 160 | if #new-candidates > 0 [ 161 | ask n-of #new-candidates able-delegators [ 162 | become-candidate 163 | ;; Allocate a random stake between `min-candidate-stake` and `staking-tokens`. 164 | let the-stake ((random (staking-tokens - min-candidate-stake)) + min-candidate-stake + 1) 165 | set staking-tokens (staking-tokens - the-stake) 166 | set own-stake the-stake 167 | move-delegator 168 | ] 169 | ] 170 | end 171 | 172 | ;; Selects the maximum of first max-#validators from the ordered list of candidates. 173 | to select-validators 174 | ask n-of (min (list max-#validators (count candidates))) candidates [ 175 | become-validator 176 | ] 177 | end 178 | 179 | to add-delegators 180 | create-turtles (random max-#new-delegators) [ 181 | become-delegator 182 | set staking-tokens (random max-new-staking-tokens) 183 | ] 184 | end 185 | 186 | to make-stakes 187 | let the-ordered-candidates ordered-candidates 188 | ;; variance in the choice of the staking target 189 | ask turtles [ 190 | if random (rm-stake-chance-reciprocal + 1) = 0 [ 191 | ifelse any? my-out-stakes [ 192 | ;; Remove an existing stake from a candidate that brought less rewards among 193 | ;; all candidates on which this delegator currently has stakes. 194 | let withdrawn 0 195 | ask min-one-of my-out-stakes [rewarded-tokens] [ 196 | set withdrawn staked-tokens 197 | die 198 | ] 199 | set staking-tokens (staking-tokens + withdrawn) 200 | ] [ 201 | if breed = candidates [ 202 | ;; Remove own stake and become a delegator. 203 | set staking-tokens (staking-tokens + own-stake) 204 | set own-stake 0 205 | ask my-in-stakes [ 206 | let withdrawn staked-tokens 207 | ask end1 [ 208 | set staking-tokens (staking-tokens + withdrawn) 209 | move-delegator 210 | ] 211 | die 212 | ] 213 | become-delegator 214 | move-delegator 215 | ] 216 | ] 217 | ] 218 | ;; Stake at random on one of the candidates. 219 | if staking-tokens > 0 and random (add-stake-chance-reciprocal + 1) = 0 [ 220 | let the-stake ((random staking-tokens) + 1) 221 | set staking-tokens (staking-tokens - the-stake) 222 | let target (one-of candidates with [self != myself]) 223 | if target != nobody [ 224 | let existing-stake (stakes with [end1 = myself and end2 = target]) 225 | ifelse any? existing-stake [ 226 | ; FIXME: Ensure the chosen link is unique. 227 | ask min-one-of existing-stake [staked-tokens] [ 228 | set staked-tokens (staked-tokens + the-stake) 229 | ] 230 | ] [ 231 | create-stake-to target [ 232 | set staked-tokens the-stake 233 | ] 234 | ] 235 | ] 236 | ] 237 | ] 238 | end 239 | 240 | to-report staking-pool-tokens 241 | let delegator-stakes sum [staked-tokens] of my-in-stakes 242 | let maybe-own-stake (ifelse-value (breed = candidates) [own-stake] [0]) 243 | report maybe-own-stake + delegator-stakes 244 | end 245 | @#$#@#$#@ 246 | GRAPHICS-WINDOW 247 | 411 248 | 10 249 | 1430 250 | 490 251 | -1 252 | -1 253 | 1.0 254 | 1 255 | 10 256 | 1 257 | 1 258 | 1 259 | 0 260 | 0 261 | 0 262 | 1 263 | -10 264 | 1000 265 | -10 266 | 460 267 | 1 268 | 1 269 | 1 270 | ticks 271 | 30.0 272 | 273 | SLIDER 274 | 5 275 | 110 276 | 204 277 | 143 278 | #initial-validators 279 | #initial-validators 280 | 1 281 | 100 282 | 5.0 283 | 1 284 | 1 285 | NIL 286 | HORIZONTAL 287 | 288 | BUTTON 289 | 18 290 | 10 291 | 91 292 | 43 293 | NIL 294 | setup 295 | NIL 296 | 1 297 | T 298 | OBSERVER 299 | NIL 300 | NIL 301 | NIL 302 | NIL 303 | 1 304 | 305 | BUTTON 306 | 91 307 | 10 308 | 154 309 | 43 310 | NIL 311 | go 312 | T 313 | 1 314 | T 315 | OBSERVER 316 | NIL 317 | NIL 318 | NIL 319 | NIL 320 | 1 321 | 322 | SLIDER 323 | 5 324 | 142 325 | 204 326 | 175 327 | min-candidate-stake 328 | min-candidate-stake 329 | 0 330 | 500 331 | 98.0 332 | 1 333 | 1 334 | NIL 335 | HORIZONTAL 336 | 337 | SLIDER 338 | 5 339 | 45 340 | 204 341 | 78 342 | max-#validators 343 | max-#validators 344 | 0 345 | 100 346 | 21.0 347 | 1 348 | 1 349 | NIL 350 | HORIZONTAL 351 | 352 | SLIDER 353 | 5 354 | 78 355 | 204 356 | 111 357 | max-#candidates 358 | max-#candidates 359 | 0 360 | 1000 361 | 467.0 362 | 1 363 | 1 364 | NIL 365 | HORIZONTAL 366 | 367 | SLIDER 368 | 5 369 | 175 370 | 204 371 | 208 372 | block-reward 373 | block-reward 374 | 0 375 | 500 376 | 90.0 377 | 1 378 | 1 379 | NIL 380 | HORIZONTAL 381 | 382 | PLOT 383 | 207 384 | 45 385 | 407 386 | 201 387 | participants 388 | NIL 389 | NIL 390 | 0.0 391 | 10.0 392 | 0.0 393 | 10.0 394 | true 395 | false 396 | "" "" 397 | PENS 398 | "default" 1.0 0 -16777216 true "" "plot count turtles" 399 | "delegators" 1.0 0 -13791810 true "" "plot count turtles with [breed != candidates]" 400 | "candidates" 1.0 0 -10899396 true "" "plot count candidates with [not validator?]" 401 | "validators" 1.0 0 -2674135 true "" "plot count candidates with [validator?]" 402 | "candidates+validators" 1.0 0 -955883 true "" "plot count candidates" 403 | 404 | PLOT 405 | 207 406 | 200 407 | 407 408 | 350 409 | rewards 410 | NIL 411 | NIL 412 | 0.0 413 | 10.0 414 | 0.0 415 | 10.0 416 | true 417 | false 418 | "" "" 419 | PENS 420 | "all" 1.0 0 -16777216 true "" "plot sum [reward-tokens] of turtles" 421 | "delegators" 1.0 0 -13791810 true "" "plot sum [reward-tokens] of turtles with [breed != candidates]" 422 | "candidates" 1.0 0 -10899396 true "" "plot sum [reward-tokens] of candidates with [not validator?]" 423 | "validators" 1.0 0 -2674135 true "" "plot sum [reward-tokens] of candidates with [validator?]" 424 | "candidates+validators" 1.0 0 -955883 true "" "plot sum [reward-tokens] of candidates" 425 | 426 | SWITCH 427 | 154 428 | 10 429 | 301 430 | 43 431 | record-video 432 | record-video 433 | 1 434 | 1 435 | -1000 436 | 437 | BUTTON 438 | 301 439 | 10 440 | 391 441 | 43 442 | save video 443 | vid:save-recording \"dpos.mp4\" 444 | NIL 445 | 1 446 | T 447 | OBSERVER 448 | NIL 449 | NIL 450 | NIL 451 | NIL 452 | 1 453 | 454 | SLIDER 455 | 5 456 | 208 457 | 204 458 | 241 459 | min-validator-reward% 460 | min-validator-reward% 461 | 0 462 | 100 463 | 30.0 464 | 1 465 | 1 466 | NIL 467 | HORIZONTAL 468 | 469 | SLIDER 470 | 5 471 | 241 472 | 204 473 | 274 474 | max-#new-delegators 475 | max-#new-delegators 476 | 0 477 | 100 478 | 10.0 479 | 1 480 | 1 481 | NIL 482 | HORIZONTAL 483 | 484 | PLOT 485 | 207 486 | 350 487 | 407 488 | 500 489 | stakes 490 | NIL 491 | NIL 492 | 0.0 493 | 10.0 494 | 0.0 495 | 10.0 496 | true 497 | false 498 | "" "" 499 | PENS 500 | "all" 1.0 0 -16777216 true "" "plot sum [staked-tokens] of stakes" 501 | "validators" 1.0 0 -2674135 true "" "plot sum [staked-tokens] of stakes with [any? (turtle-set end1) with [breed = candidates and validator? = true]]" 502 | "candidates" 1.0 0 -10899396 true "" "plot sum [staked-tokens] of stakes with [any? (turtle-set end1) with [breed = candidates and validator? = false]]" 503 | "delegators" 1.0 0 -13791810 true "" "plot sum [staked-tokens] of stakes with [any? (turtle-set end1) with [breed != candidates]]" 504 | "candidates+validators" 1.0 0 -955883 true "" "plot sum [staked-tokens] of stakes with [any? (turtle-set end1) with [breed = candidates]]" 505 | 506 | SLIDER 507 | 5 508 | 274 509 | 204 510 | 307 511 | rm-stake-chance-reciprocal 512 | rm-stake-chance-reciprocal 513 | 0 514 | 20 515 | 10.0 516 | 1 517 | 1 518 | NIL 519 | HORIZONTAL 520 | 521 | SLIDER 522 | 5 523 | 307 524 | 204 525 | 340 526 | add-stake-chance-reciprocal 527 | add-stake-chance-reciprocal 528 | 0 529 | 20 530 | 10.0 531 | 1 532 | 1 533 | NIL 534 | HORIZONTAL 535 | 536 | SLIDER 537 | 5 538 | 340 539 | 204 540 | 373 541 | max-new-staking-tokens 542 | max-new-staking-tokens 543 | 0 544 | 1000 545 | 207.0 546 | 1 547 | 1 548 | NIL 549 | HORIZONTAL 550 | 551 | PLOT 552 | 4 553 | 500 554 | 707 555 | 655 556 | reward distribution 557 | reward 558 | count 559 | 0.0 560 | 400.0 561 | 0.0 562 | 10.0 563 | true 564 | false 565 | "" "" 566 | PENS 567 | "freq" 1.0 0 -16777216 true "" "histogram [reward-tokens] of turtles" 568 | 569 | PLOT 570 | 707 571 | 500 572 | 1411 573 | 655 574 | age vs rewards 575 | age / (rewards + 1) 576 | count 577 | 0.0 578 | 40.0 579 | 0.0 580 | 10.0 581 | true 582 | false 583 | "" "" 584 | PENS 585 | "ratio" 1.0 0 -16777216 true "" "histogram [age / (reward-tokens + 1)] of turtles" 586 | 587 | @#$#@#$#@ 588 | ## WHAT IS IT? 589 | 590 | This is a working simulator of a Delegated Proof of Stake (DPoS) network. The model allows to experience how the choice of values for initial parameters influences the network behavior. 591 | 592 | ## HOW IT WORKS 593 | 594 | The model contains participants (delegators, candidates and validators) and staking relations. It proceeds in steps, each of which corresponds to one staking epoch. When the simulation starts, the initial validators are created. Then, at every step, the following happens: 595 | 596 | - the pool rewards are distributed, 597 | - a random number of new delegators is added to the network, 598 | - a small number of delegators becomes candidates by making stakes on themselves, 599 | - current stakes are made or removed using a randomized greedy strategy, 600 | - validators are selected among candidates at random. 601 | 602 | ## HOW TO USE IT 603 | 604 | Adjust the parameters using the sliders, click **Setup**, and then **Go** to start the simulation. To stop the simulation, click **Go** the second time. 605 | 606 | The network view is an X-Y graph with the staking pool size increasing to the right along the X axis, and the participant reward amount increasing upwards along the Y axis. The pure delegators are blue, pure candidates are green and current validators are red. The links are directed and represent staking relations. There are also a few statistical charts with the same color legend plus additional colors: black for "all participants" and orange for "all candidates" (pure candidates and current validators). 607 | 608 | The graph output is trimmed at the maximum coordinates and the participants whose staking pool or rewards exceed the maximum displayable limit are pinned to the maximum coordinate instead of disappearing from view. 609 | 610 | There is an optional video recording feature that allows recording the whole interface window including the charts. To use it, turn **record-video** on before clicking **Setup**. After the simulation stops, click **save-video** to save the recording into a file. The file name is fixed as `dpos.mp4`. 611 | 612 | ## THINGS TO NOTICE 613 | 614 | Staking in the simulator is implemented using a simple greedy strategy. This has a few noticeable implications. Participants that join early amass more rewards and tend to have bigger staking pools. Staking relations are more likely to disappear early than late because the greedy strategy prefers keeping the staking relations that bring more rewards, and that is more likely to be the case with staking relations that have been around for longer. 615 | 616 | ## THINGS TO TRY 617 | 618 | Try increasing **min-candidate-stake** while still keeping it less than **max-new-staking-tokens** so that the participants can become candidates. This should make is more difficult to become a candidate and you should obtain higher total rewards for pure delegators than for candidates. 619 | 620 | Try decreasing **min-candidate-stake** and you should see the total pure delegator rewards decrease below the total candidate rewards. 621 | 622 | Try decreasing **block-reward** and increasing **rm-stake-chance-reciprocal** to see the age to reward ratio becoming more evenly distributed among participants. 623 | 624 | ## EXTENDING THE MODEL 625 | 626 | Adding more staking strategies is needed. 627 | 628 | ## NETLOGO FEATURES 629 | 630 | The simulation slows down soon due to the use of multiple list operations in NetLogo. It would be great to find a way to speed them up possibly by memoization or precomputation. 631 | 632 | ## RELATED MODELS 633 | 634 | TBD 635 | 636 | ## CREDITS AND REFERENCES 637 | 638 | For details about the DPoS, refer to the [POA Network DPoS whitepaper](https://forum.poa.network/t/posdao-white-paper/2208). 639 | @#$#@#$#@ 640 | default 641 | true 642 | 0 643 | Polygon -7500403 true true 150 5 40 250 150 205 260 250 644 | 645 | airplane 646 | true 647 | 0 648 | Polygon -7500403 true true 150 0 135 15 120 60 120 105 15 165 15 195 120 180 135 240 105 270 120 285 150 270 180 285 210 270 165 240 180 180 285 195 285 165 180 105 180 60 165 15 649 | 650 | arrow 651 | true 652 | 0 653 | Polygon -7500403 true true 150 0 0 150 105 150 105 293 195 293 195 150 300 150 654 | 655 | box 656 | false 657 | 0 658 | Polygon -7500403 true true 150 285 285 225 285 75 150 135 659 | Polygon -7500403 true true 150 135 15 75 150 15 285 75 660 | Polygon -7500403 true true 15 75 15 225 150 285 150 135 661 | Line -16777216 false 150 285 150 135 662 | Line -16777216 false 150 135 15 75 663 | Line -16777216 false 150 135 285 75 664 | 665 | bug 666 | true 667 | 0 668 | Circle -7500403 true true 96 182 108 669 | Circle -7500403 true true 110 127 80 670 | Circle -7500403 true true 110 75 80 671 | Line -7500403 true 150 100 80 30 672 | Line -7500403 true 150 100 220 30 673 | 674 | butterfly 675 | true 676 | 0 677 | Polygon -7500403 true true 150 165 209 199 225 225 225 255 195 270 165 255 150 240 678 | Polygon -7500403 true true 150 165 89 198 75 225 75 255 105 270 135 255 150 240 679 | Polygon -7500403 true true 139 148 100 105 55 90 25 90 10 105 10 135 25 180 40 195 85 194 139 163 680 | Polygon -7500403 true true 162 150 200 105 245 90 275 90 290 105 290 135 275 180 260 195 215 195 162 165 681 | Polygon -16777216 true false 150 255 135 225 120 150 135 120 150 105 165 120 180 150 165 225 682 | Circle -16777216 true false 135 90 30 683 | Line -16777216 false 150 105 195 60 684 | Line -16777216 false 150 105 105 60 685 | 686 | car 687 | false 688 | 0 689 | Polygon -7500403 true true 300 180 279 164 261 144 240 135 226 132 213 106 203 84 185 63 159 50 135 50 75 60 0 150 0 165 0 225 300 225 300 180 690 | Circle -16777216 true false 180 180 90 691 | Circle -16777216 true false 30 180 90 692 | Polygon -16777216 true false 162 80 132 78 134 135 209 135 194 105 189 96 180 89 693 | Circle -7500403 true true 47 195 58 694 | Circle -7500403 true true 195 195 58 695 | 696 | circle 697 | false 698 | 0 699 | Circle -7500403 true true 0 0 300 700 | 701 | circle 2 702 | false 703 | 0 704 | Circle -7500403 true true 0 0 300 705 | Circle -16777216 true false 30 30 240 706 | 707 | cow 708 | false 709 | 0 710 | Polygon -7500403 true true 200 193 197 249 179 249 177 196 166 187 140 189 93 191 78 179 72 211 49 209 48 181 37 149 25 120 25 89 45 72 103 84 179 75 198 76 252 64 272 81 293 103 285 121 255 121 242 118 224 167 711 | Polygon -7500403 true true 73 210 86 251 62 249 48 208 712 | Polygon -7500403 true true 25 114 16 195 9 204 23 213 25 200 39 123 713 | 714 | cylinder 715 | false 716 | 0 717 | Circle -7500403 true true 0 0 300 718 | 719 | dot 720 | false 721 | 0 722 | Circle -7500403 true true 90 90 120 723 | 724 | face happy 725 | false 726 | 0 727 | Circle -7500403 true true 8 8 285 728 | Circle -16777216 true false 60 75 60 729 | Circle -16777216 true false 180 75 60 730 | Polygon -16777216 true false 150 255 90 239 62 213 47 191 67 179 90 203 109 218 150 225 192 218 210 203 227 181 251 194 236 217 212 240 731 | 732 | face neutral 733 | false 734 | 0 735 | Circle -7500403 true true 8 7 285 736 | Circle -16777216 true false 60 75 60 737 | Circle -16777216 true false 180 75 60 738 | Rectangle -16777216 true false 60 195 240 225 739 | 740 | face sad 741 | false 742 | 0 743 | Circle -7500403 true true 8 8 285 744 | Circle -16777216 true false 60 75 60 745 | Circle -16777216 true false 180 75 60 746 | Polygon -16777216 true false 150 168 90 184 62 210 47 232 67 244 90 220 109 205 150 198 192 205 210 220 227 242 251 229 236 206 212 183 747 | 748 | fish 749 | false 750 | 0 751 | Polygon -1 true false 44 131 21 87 15 86 0 120 15 150 0 180 13 214 20 212 45 166 752 | Polygon -1 true false 135 195 119 235 95 218 76 210 46 204 60 165 753 | Polygon -1 true false 75 45 83 77 71 103 86 114 166 78 135 60 754 | Polygon -7500403 true true 30 136 151 77 226 81 280 119 292 146 292 160 287 170 270 195 195 210 151 212 30 166 755 | Circle -16777216 true false 215 106 30 756 | 757 | flag 758 | false 759 | 0 760 | Rectangle -7500403 true true 60 15 75 300 761 | Polygon -7500403 true true 90 150 270 90 90 30 762 | Line -7500403 true 75 135 90 135 763 | Line -7500403 true 75 45 90 45 764 | 765 | flower 766 | false 767 | 0 768 | Polygon -10899396 true false 135 120 165 165 180 210 180 240 150 300 165 300 195 240 195 195 165 135 769 | Circle -7500403 true true 85 132 38 770 | Circle -7500403 true true 130 147 38 771 | Circle -7500403 true true 192 85 38 772 | Circle -7500403 true true 85 40 38 773 | Circle -7500403 true true 177 40 38 774 | Circle -7500403 true true 177 132 38 775 | Circle -7500403 true true 70 85 38 776 | Circle -7500403 true true 130 25 38 777 | Circle -7500403 true true 96 51 108 778 | Circle -16777216 true false 113 68 74 779 | Polygon -10899396 true false 189 233 219 188 249 173 279 188 234 218 780 | Polygon -10899396 true false 180 255 150 210 105 210 75 240 135 240 781 | 782 | house 783 | false 784 | 0 785 | Rectangle -7500403 true true 45 120 255 285 786 | Rectangle -16777216 true false 120 210 180 285 787 | Polygon -7500403 true true 15 120 150 15 285 120 788 | Line -16777216 false 30 120 270 120 789 | 790 | leaf 791 | false 792 | 0 793 | Polygon -7500403 true true 150 210 135 195 120 210 60 210 30 195 60 180 60 165 15 135 30 120 15 105 40 104 45 90 60 90 90 105 105 120 120 120 105 60 120 60 135 30 150 15 165 30 180 60 195 60 180 120 195 120 210 105 240 90 255 90 263 104 285 105 270 120 285 135 240 165 240 180 270 195 240 210 180 210 165 195 794 | Polygon -7500403 true true 135 195 135 240 120 255 105 255 105 285 135 285 165 240 165 195 795 | 796 | line 797 | true 798 | 0 799 | Line -7500403 true 150 0 150 300 800 | 801 | line half 802 | true 803 | 0 804 | Line -7500403 true 150 0 150 150 805 | 806 | pentagon 807 | false 808 | 0 809 | Polygon -7500403 true true 150 15 15 120 60 285 240 285 285 120 810 | 811 | person 812 | false 813 | 0 814 | Circle -7500403 true true 110 5 80 815 | Polygon -7500403 true true 105 90 120 195 90 285 105 300 135 300 150 225 165 300 195 300 210 285 180 195 195 90 816 | Rectangle -7500403 true true 127 79 172 94 817 | Polygon -7500403 true true 195 90 240 150 225 180 165 105 818 | Polygon -7500403 true true 105 90 60 150 75 180 135 105 819 | 820 | plant 821 | false 822 | 0 823 | Rectangle -7500403 true true 135 90 165 300 824 | Polygon -7500403 true true 135 255 90 210 45 195 75 255 135 285 825 | Polygon -7500403 true true 165 255 210 210 255 195 225 255 165 285 826 | Polygon -7500403 true true 135 180 90 135 45 120 75 180 135 210 827 | Polygon -7500403 true true 165 180 165 210 225 180 255 120 210 135 828 | Polygon -7500403 true true 135 105 90 60 45 45 75 105 135 135 829 | Polygon -7500403 true true 165 105 165 135 225 105 255 45 210 60 830 | Polygon -7500403 true true 135 90 120 45 150 15 180 45 165 90 831 | 832 | sheep 833 | false 834 | 15 835 | Circle -1 true true 203 65 88 836 | Circle -1 true true 70 65 162 837 | Circle -1 true true 150 105 120 838 | Polygon -7500403 true false 218 120 240 165 255 165 278 120 839 | Circle -7500403 true false 214 72 67 840 | Rectangle -1 true true 164 223 179 298 841 | Polygon -1 true true 45 285 30 285 30 240 15 195 45 210 842 | Circle -1 true true 3 83 150 843 | Rectangle -1 true true 65 221 80 296 844 | Polygon -1 true true 195 285 210 285 210 240 240 210 195 210 845 | Polygon -7500403 true false 276 85 285 105 302 99 294 83 846 | Polygon -7500403 true false 219 85 210 105 193 99 201 83 847 | 848 | square 849 | false 850 | 0 851 | Rectangle -7500403 true true 30 30 270 270 852 | 853 | square 2 854 | false 855 | 0 856 | Rectangle -7500403 true true 30 30 270 270 857 | Rectangle -16777216 true false 60 60 240 240 858 | 859 | star 860 | false 861 | 0 862 | Polygon -7500403 true true 151 1 185 108 298 108 207 175 242 282 151 216 59 282 94 175 3 108 116 108 863 | 864 | target 865 | false 866 | 0 867 | Circle -7500403 true true 0 0 300 868 | Circle -16777216 true false 30 30 240 869 | Circle -7500403 true true 60 60 180 870 | Circle -16777216 true false 90 90 120 871 | Circle -7500403 true true 120 120 60 872 | 873 | tree 874 | false 875 | 0 876 | Circle -7500403 true true 118 3 94 877 | Rectangle -6459832 true false 120 195 180 300 878 | Circle -7500403 true true 65 21 108 879 | Circle -7500403 true true 116 41 127 880 | Circle -7500403 true true 45 90 120 881 | Circle -7500403 true true 104 74 152 882 | 883 | triangle 884 | false 885 | 0 886 | Polygon -7500403 true true 150 30 15 255 285 255 887 | 888 | triangle 2 889 | false 890 | 0 891 | Polygon -7500403 true true 150 30 15 255 285 255 892 | Polygon -16777216 true false 151 99 225 223 75 224 893 | 894 | truck 895 | false 896 | 0 897 | Rectangle -7500403 true true 4 45 195 187 898 | Polygon -7500403 true true 296 193 296 150 259 134 244 104 208 104 207 194 899 | Rectangle -1 true false 195 60 195 105 900 | Polygon -16777216 true false 238 112 252 141 219 141 218 112 901 | Circle -16777216 true false 234 174 42 902 | Rectangle -7500403 true true 181 185 214 194 903 | Circle -16777216 true false 144 174 42 904 | Circle -16777216 true false 24 174 42 905 | Circle -7500403 false true 24 174 42 906 | Circle -7500403 false true 144 174 42 907 | Circle -7500403 false true 234 174 42 908 | 909 | turtle 910 | true 911 | 0 912 | Polygon -10899396 true false 215 204 240 233 246 254 228 266 215 252 193 210 913 | Polygon -10899396 true false 195 90 225 75 245 75 260 89 269 108 261 124 240 105 225 105 210 105 914 | Polygon -10899396 true false 105 90 75 75 55 75 40 89 31 108 39 124 60 105 75 105 90 105 915 | Polygon -10899396 true false 132 85 134 64 107 51 108 17 150 2 192 18 192 52 169 65 172 87 916 | Polygon -10899396 true false 85 204 60 233 54 254 72 266 85 252 107 210 917 | Polygon -7500403 true true 119 75 179 75 209 101 224 135 220 225 175 261 128 261 81 224 74 135 88 99 918 | 919 | wheel 920 | false 921 | 0 922 | Circle -7500403 true true 3 3 294 923 | Circle -16777216 true false 30 30 240 924 | Line -7500403 true 150 285 150 15 925 | Line -7500403 true 15 150 285 150 926 | Circle -7500403 true true 120 120 60 927 | Line -7500403 true 216 40 79 269 928 | Line -7500403 true 40 84 269 221 929 | Line -7500403 true 40 216 269 79 930 | Line -7500403 true 84 40 221 269 931 | 932 | wolf 933 | false 934 | 0 935 | Polygon -16777216 true false 253 133 245 131 245 133 936 | Polygon -7500403 true true 2 194 13 197 30 191 38 193 38 205 20 226 20 257 27 265 38 266 40 260 31 253 31 230 60 206 68 198 75 209 66 228 65 243 82 261 84 268 100 267 103 261 77 239 79 231 100 207 98 196 119 201 143 202 160 195 166 210 172 213 173 238 167 251 160 248 154 265 169 264 178 247 186 240 198 260 200 271 217 271 219 262 207 258 195 230 192 198 210 184 227 164 242 144 259 145 284 151 277 141 293 140 299 134 297 127 273 119 270 105 937 | Polygon -7500403 true true -1 195 14 180 36 166 40 153 53 140 82 131 134 133 159 126 188 115 227 108 236 102 238 98 268 86 269 92 281 87 269 103 269 113 938 | 939 | x 940 | false 941 | 0 942 | Polygon -7500403 true true 270 75 225 30 30 225 75 270 943 | Polygon -7500403 true true 30 75 75 30 270 225 225 270 944 | @#$#@#$#@ 945 | NetLogo 6.0.4 946 | @#$#@#$#@ 947 | @#$#@#$#@ 948 | @#$#@#$#@ 949 | 950 | 951 | setup 952 | go 953 | count turtles 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | @#$#@#$#@ 990 | @#$#@#$#@ 991 | default 992 | 0.0 993 | -0.2 0 0.0 1.0 994 | 0.0 1 1.0 0.0 995 | 0.2 0 0.0 1.0 996 | link direction 997 | true 998 | 0 999 | Line -7500403 true 150 150 90 180 1000 | Line -7500403 true 150 150 210 180 1001 | @#$#@#$#@ 1002 | 0 1003 | @#$#@#$#@ 1004 | -------------------------------------------------------------------------------- /test/01_staking.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const constants = require('../utils/constants'); 5 | const SnS = require('../utils/signAndSendTx.js'); 6 | const sendRequest = require('../utils/sendRequest.js'); 7 | const web3 = new Web3('http://localhost:8541'); 8 | web3.eth.transactionConfirmationBlocks = 1; 9 | const BN = web3.utils.BN; 10 | const OWNER = constants.OWNER; 11 | const expect = require('chai') 12 | .use(require('chai-bn')(BN)) 13 | .use(require('chai-as-promised')) 14 | .expect; 15 | const BlockRewardAuRa = require('../utils/getContract')('BlockRewardAuRa', web3); 16 | const ValidatorSetAuRa = require('../utils/getContract')('ValidatorSetAuRa', web3); 17 | const StakingAuRa = require('../utils/getContract')('StakingAuRa', web3); 18 | const StakingTokenContract = require('../utils/getContract')('StakingToken', web3); 19 | const sendInStakingWindow = require('../utils/sendInStakingWindow'); 20 | const waitForValidatorSetChange = require('../utils/waitForValidatorSetChange'); 21 | const waitForNextStakingEpoch = require('../utils/waitForNextStakingEpoch'); 22 | const calcMinGasPrice = require('../utils/calcMinGasPrice'); 23 | const getLatestBlock = require('../utils/getLatestBlock'); 24 | const pp = require('../utils/prettyPrint'); 25 | const FAILED_EXCEPTION_MSG = 'The execution failed due to an exception'; 26 | const REVERT_EXCEPTION_MSG = 'revert'; 27 | 28 | describe('Candidates place stakes on themselves', () => { 29 | var minCandidateStake; 30 | var minCandidateStakeBN; 31 | var minDelegatorStake; 32 | var minDelegatorStakeBN; 33 | const delegatorsNumber = 10; 34 | var delegators = []; 35 | 36 | before(async () => { 37 | // this is min stake per a CANDIDATE 38 | minCandidateStake = await StakingAuRa.instance.methods.candidateMinStake().call(); 39 | minDelegatorStake = await StakingAuRa.instance.methods.delegatorMinStake().call(); 40 | minCandidateStakeBN = new BN(minCandidateStake.toString()); 41 | minDelegatorStakeBN = new BN(minDelegatorStake.toString()); 42 | 43 | console.log('**** Delegator addresses are generated'); 44 | for (let i = 0; i < delegatorsNumber; i++) { 45 | let acc = web3.eth.accounts.create(); 46 | let keystoreObj = web3.eth.accounts.encrypt(acc.privateKey, 'testnetpoa'); 47 | delegators.push(acc.address); 48 | fs.writeFileSync(path.join(__dirname, '../accounts/keystore', acc.address.substring(2).toLowerCase() + '.json'), JSON.stringify(keystoreObj), 'utf8'); 49 | } 50 | }); 51 | 52 | it('Owner mints (2x minStake) tokens to candidates', async () => { 53 | let candidateTokensBN = minCandidateStakeBN.mul(new BN('2')); 54 | for (candidate of constants.CANDIDATES) { 55 | console.log('**** candidate =', JSON.stringify(candidate)); 56 | let iTokenBalance = await StakingTokenContract.instance.methods.balanceOf(candidate.staking).call(); 57 | let iTokenBalanceBN = new BN(iTokenBalance.toString()); 58 | const latestBlock = await getLatestBlock(web3); 59 | 60 | if (latestBlock.baseFeePerGas) { 61 | const netId = await web3.eth.net.getId(); 62 | const nodeInfo = await web3.eth.getNodeInfo(); 63 | const txParams = { 64 | from: OWNER, 65 | to: StakingTokenContract.address, 66 | type: '0x2', 67 | chainId: web3.utils.numberToHex(netId), 68 | maxPriorityFeePerGas: web3.utils.numberToHex('0'), 69 | maxFeePerGas: web3.utils.numberToHex('0'), 70 | gas: web3.utils.numberToHex('1000000'), 71 | data: StakingTokenContract.instance.methods.mint(candidate.staking, candidateTokensBN.toString()).encodeABI(), 72 | accessList: [] 73 | }; 74 | if (nodeInfo.indexOf('OpenEthereum') >= 0) { 75 | delete txParams.chainId; 76 | } 77 | const txHash = await sendRequest(`curl --data '{"method":"eth_sendTransaction","params":[${JSON.stringify(txParams)}],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST ${web3.currentProvider.host} 2>/dev/null`); 78 | let txReceipt; 79 | while(!(txReceipt = await web3.eth.getTransactionReceipt(txHash))) { 80 | await sleep(500); 81 | } 82 | expect(txReceipt.status, `Failed tx: ${txReceipt.transactionHash}`).to.equal(true); 83 | } else { 84 | let tx = await SnS(web3, { 85 | from: OWNER, 86 | to: StakingTokenContract.address, 87 | method: StakingTokenContract.instance.methods.mint(candidate.staking, candidateTokensBN.toString()), 88 | gasPrice: '0', 89 | }); 90 | pp.tx(tx); 91 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 92 | } 93 | 94 | let fTokenBalance = await StakingTokenContract.instance.methods.balanceOf(candidate.staking).call(); 95 | let fTokenBalanceBN = new BN(fTokenBalance.toString()); 96 | expect(fTokenBalanceBN, `Amount of minted staking tokens is incorrect for ${candidate.staking}`).to.be.bignumber.equal(iTokenBalanceBN.add(candidateTokensBN)); 97 | } 98 | }); 99 | 100 | it('Owner emulates bridge token fee accrual', async () => { 101 | const stakeTokenInflationRate = await BlockRewardAuRa.instance.methods.STAKE_TOKEN_INFLATION_RATE().call(); 102 | if (stakeTokenInflationRate != 0) { 103 | console.log('Skipping this step because inflation is activated (the reward will be minted as a result of inflation, not a bridge fee)'); 104 | return; 105 | } 106 | const bridgeTokenFeeAmount = '1000000000000000000'; 107 | await SnS(web3, { 108 | from: OWNER, 109 | to: BlockRewardAuRa.address, 110 | method: BlockRewardAuRa.instance.methods.setErcToErcBridgesAllowed([OWNER]), 111 | gasPrice: '0' 112 | }); 113 | await SnS(web3, { 114 | from: OWNER, 115 | to: BlockRewardAuRa.address, 116 | method: BlockRewardAuRa.instance.methods.addBridgeTokenRewardReceivers(bridgeTokenFeeAmount), 117 | gasPrice: '0' 118 | }); 119 | const bridgeTokenFeeActual = new BN(await BlockRewardAuRa.instance.methods.bridgeTokenReward().call()); 120 | expect(bridgeTokenFeeActual, 'bridgeTokenReward amount is incorrect').to.be.bignumber.equal(new BN(bridgeTokenFeeAmount)); 121 | }); 122 | 123 | it('Candidates add pools for themselves', async () => { 124 | let stakeBN = minCandidateStakeBN.clone(); 125 | console.log('**** stake = ' + stakeBN.toString()); 126 | for (candidate of constants.CANDIDATES) { 127 | console.log('**** candidate =', JSON.stringify(candidate)); 128 | let poolId = (await ValidatorSetAuRa.instance.methods.lastPoolId().call()) - 0 + 1; 129 | let candidatePoolId = await ValidatorSetAuRa.instance.methods.idByStakingAddress(candidate.staking).call(); 130 | let iStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, '0x0000000000000000000000000000000000000000').call(); 131 | let iStakeBN = new BN(iStake.toString()); 132 | let poolName = `Pool ${poolId}`; 133 | let poolDescription = `Pool ${poolId} description`; 134 | let tx = await sendInStakingWindow(web3, async () => { 135 | const latestBlock = await getLatestBlock(web3); 136 | return SnS(web3, { 137 | from: candidate.staking, 138 | to: StakingAuRa.address, 139 | method: StakingAuRa.instance.methods.addPool(stakeBN.toString(), candidate.mining, poolName, poolDescription), 140 | gasPrice: '1000000000', // maxPriorityFeePerGas for EIP-1559, maxFeePerGas is calculated as baseFeePerGas + maxPriorityFeePerGas 141 | gasLimit: '700000', 142 | }, null, latestBlock.baseFeePerGas); 143 | }); 144 | pp.tx(tx); 145 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 146 | candidatePoolId = await ValidatorSetAuRa.instance.methods.idByStakingAddress(candidate.staking).call(); 147 | let fStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, '0x0000000000000000000000000000000000000000').call(); 148 | let fStakeBN = new BN(fStake.toString()); 149 | expect(fStakeBN, `Stake on candidate ${candidate.staking} didn't increase`).to.be.bignumber.equal(iStakeBN.add(stakeBN)); 150 | } 151 | }); 152 | 153 | it('Candidates place stakes on themselves', async () => { 154 | let stakeBN = minCandidateStakeBN.clone(); 155 | console.log('**** stake = ' + stakeBN.toString()); 156 | for (candidate of constants.CANDIDATES) { 157 | console.log('**** candidate =', JSON.stringify(candidate)); 158 | let candidatePoolId = await ValidatorSetAuRa.instance.methods.idByStakingAddress(candidate.staking).call(); 159 | let iStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, '0x0000000000000000000000000000000000000000').call(); 160 | let iStakeBN = new BN(iStake.toString()); 161 | let tx = await sendInStakingWindow(web3, async () => { 162 | const latestBlock = await getLatestBlock(web3); 163 | return SnS(web3, { 164 | from: candidate.staking, 165 | to: StakingAuRa.address, 166 | method: StakingAuRa.instance.methods.stake(candidate.staking, stakeBN.toString()), 167 | gasPrice: '1000000000', // maxPriorityFeePerGas for EIP-1559, maxFeePerGas is calculated as baseFeePerGas + maxPriorityFeePerGas 168 | gasLimit: '400000', 169 | }, null, latestBlock.baseFeePerGas, [ 170 | [ 171 | ValidatorSetAuRa.address, 172 | ["0x0000000000000000000000000000000000000000000000000000000000000016"] 173 | ], 174 | [ 175 | StakingAuRa.address, 176 | [ 177 | "0x0000000000000000000000000000000000000000000000000000000000000005", 178 | "0x0000000000000000000000000000000000000000000000000000000000000024", 179 | "0x000000000000000000000000000000000000000000000000000000000000003A", 180 | ] 181 | ] 182 | ]); // Use EIP-2930 here (and EIP-1559 if supported) 183 | }); 184 | pp.tx(tx); 185 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 186 | let fStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, '0x0000000000000000000000000000000000000000').call(); 187 | let fStakeBN = new BN(fStake.toString()); 188 | expect(fStakeBN, `Stake on candidate ${candidate.staking} didn't increase`).to.be.bignumber.equal(iStakeBN.add(stakeBN)); 189 | } 190 | }); 191 | 192 | it('Delegators place stakes into the second candidate\'s pool', async () => { 193 | const candidate = constants.CANDIDATES[1].staking; 194 | const candidatePoolId = await ValidatorSetAuRa.instance.methods.idByStakingAddress(candidate).call(); 195 | 196 | let promises; 197 | let nonce; 198 | let txs; 199 | 200 | console.log('**** Owner mints (3x minStake) tokens to delegators'); 201 | 202 | const delegatorTokensBN = minDelegatorStakeBN.mul(new BN('3')); 203 | let latestBlock = await getLatestBlock(web3); 204 | 205 | promises = []; 206 | nonce = await web3.eth.getTransactionCount(OWNER); 207 | for (let i = 0; i < delegatorsNumber; i++) { 208 | const delegator = delegators[i]; 209 | const prm = SnS(web3, { 210 | from: OWNER, 211 | to: StakingTokenContract.address, 212 | method: StakingTokenContract.instance.methods.mint(delegator, delegatorTokensBN.toString()), 213 | gasPrice: '0', 214 | nonce: nonce++ 215 | }, null, null, []); // EIP-2930 only 216 | promises.push(prm); 217 | } 218 | txs = await Promise.all(promises); 219 | for (const tx of txs) { 220 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 221 | } 222 | 223 | console.log('**** BlockReward mints native coins to delegators'); 224 | 225 | const newNativeBalance = '1000000000000000000'; 226 | 227 | await SnS(web3, { 228 | from: OWNER, 229 | to: BlockRewardAuRa.address, 230 | method: BlockRewardAuRa.instance.methods.setErcToNativeBridgesAllowed([OWNER]), 231 | gasPrice: '0' 232 | }); 233 | 234 | latestBlock = await getLatestBlock(web3); 235 | 236 | promises = []; 237 | nonce = await web3.eth.getTransactionCount(OWNER); 238 | for (let i = 0; i < delegatorsNumber; i++) { 239 | const delegator = delegators[i]; 240 | const prm = SnS(web3, { 241 | from: OWNER, 242 | to: BlockRewardAuRa.address, 243 | method: BlockRewardAuRa.instance.methods.addExtraReceiver(newNativeBalance, delegator), 244 | gasPrice: '0', 245 | nonce: nonce++ 246 | }, null, latestBlock.baseFeePerGas); 247 | promises.push(prm); 248 | } 249 | txs = await Promise.all(promises); 250 | for (const tx of txs) { 251 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 252 | } 253 | 254 | for (let i = 0; i < delegatorsNumber; i++) { 255 | const delegator = delegators[i]; 256 | const delegatorBalance = await web3.eth.getBalance(delegator); 257 | expect(delegatorBalance, `Amount of minted coins is incorrect for ${delegator}`).to.be.equal(newNativeBalance); 258 | } 259 | 260 | console.log('**** Delegators place stakes on the candidate'); 261 | 262 | latestBlock = await getLatestBlock(web3); 263 | 264 | promises = []; 265 | for (let i = 0; i < delegatorsNumber; i++) { 266 | const delegator = delegators[i]; 267 | const prm = SnS(web3, { 268 | from: delegator, 269 | to: StakingAuRa.address, 270 | method: StakingAuRa.instance.methods.stake(candidate, minDelegatorStakeBN.toString()), 271 | gasPrice: '1000000000', // maxPriorityFeePerGas for EIP-1559, maxFeePerGas is calculated as baseFeePerGas + maxPriorityFeePerGas 272 | gasLimit: '400000' 273 | }, null, latestBlock.baseFeePerGas); 274 | promises.push(prm); 275 | } 276 | txs = await Promise.all(promises); 277 | for (const tx of txs) { 278 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 279 | } 280 | 281 | for (let i = 0; i < delegatorsNumber; i++) { 282 | const fStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, delegators[i]).call(); 283 | const fStakeBN = new BN(fStake.toString()); 284 | expect(fStakeBN, `Stake on candidate ${candidate} didn't increase`).to.be.bignumber.equal(minDelegatorStakeBN); 285 | } 286 | 287 | // Test moving of stakes 288 | console.log('**** One of delegators moves their stake to another candidate'); 289 | let candidate_rec = constants.CANDIDATES[2].staking; 290 | let candidate_rec_id = await ValidatorSetAuRa.instance.methods.idByStakingAddress(constants.CANDIDATES[2].staking).call(); 291 | let delegator = delegators[0]; 292 | 293 | // initial stake on the initial candidate 294 | let iStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, delegator).call(); 295 | let iStakeBN = new BN(iStake.toString()); 296 | 297 | // initial stake on the target candidate 298 | let iStake_rec = await StakingAuRa.instance.methods.stakeAmount(candidate_rec_id, delegator).call(); 299 | let iStake_recBN = new BN(iStake_rec.toString()); 300 | 301 | let tx = await SnS(web3, { 302 | from: delegator, 303 | to: StakingAuRa.address, 304 | method: StakingAuRa.instance.methods.moveStake(candidate, candidate_rec, minDelegatorStakeBN.toString()), 305 | gasPrice: await calcMinGasPrice(web3), 306 | gasLimit: '500000', 307 | }); 308 | expect(tx.status, `Tx to move stake failed: ${tx.transactionHash}`).to.equal(true); 309 | 310 | // final stake on the initial candidate (should have decreased) 311 | let fStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, delegator).call(); 312 | let fStakeBN = new BN(fStake.toString()); 313 | let dStakeBN = fStakeBN.sub(iStakeBN); 314 | expect(dStakeBN, `Stake on initial candidate ${candidate} didn't decrease`).to.be.bignumber.equal(minDelegatorStakeBN.neg()); // x.neg() == -x 315 | 316 | // final stake on the target candidate (should have increased) 317 | let fStake_rec = await StakingAuRa.instance.methods.stakeAmount(candidate_rec_id, delegator).call(); 318 | let fStake_recBN = new BN(fStake_rec.toString()); 319 | let dStake_recBN = fStake_recBN.sub(iStake_recBN); 320 | expect(dStake_recBN, `Stake on target candidate ${candidate_rec} didn't increase`).to.be.bignumber.equal(minDelegatorStakeBN); 321 | 322 | console.log('**** Moving stake must fail if delegator tries to move their stake to the same candidate'); 323 | try { 324 | let tx2 = await SnS(web3, { 325 | from: delegator, 326 | to: StakingAuRa.address, 327 | method: StakingAuRa.instance.methods.moveStake(candidate_rec, candidate_rec, minDelegatorStakeBN.toString()), 328 | gasPrice: await calcMinGasPrice(web3), 329 | gasLimit: '500000', 330 | }); 331 | expect(false, `Tx didn't throw an exception: ${tx2.transactionHash}. Tx status: ${tx2.status}`).to.equal(true); 332 | } 333 | catch (e) { 334 | const eString = e ? e.toString() : ''; 335 | expect(e && (eString.includes(FAILED_EXCEPTION_MSG) || eString.includes(REVERT_EXCEPTION_MSG)), `Tx threw an unexpected exception: ` + eString).to.equal(true); 336 | } 337 | 338 | console.log('**** Delegator can\'t move more staking tokens than one has'); 339 | try { 340 | let tx3 = await SnS(web3, { 341 | from: delegator, 342 | to: StakingAuRa.address, 343 | method: StakingAuRa.instance.methods.moveStake(candidate, candidate_rec, minDelegatorStakeBN.toString()), 344 | gasPrice: await calcMinGasPrice(web3), 345 | gasLimit: '500000', 346 | }); 347 | expect(false, `Tx didn't throw an exception: ${tx3.transactionHash}. Tx status: ${tx3.status}`).to.equal(true); 348 | } 349 | catch (e) { 350 | const eString = e ? e.toString() : ''; 351 | expect(e && (eString.includes(FAILED_EXCEPTION_MSG) || eString.includes(REVERT_EXCEPTION_MSG)), `Tx threw an unexpected exception: ` + eString).to.equal(true); 352 | } 353 | }); 354 | 355 | it('Candidates are in validator set in the new staking epoch', async() => { 356 | console.log('***** Wait for staking epoch to change'); 357 | let validators = (await waitForValidatorSetChange(web3)).map(v => v.toLowerCase()); 358 | for (candidate of constants.CANDIDATES) { 359 | let validatorIndex = validators.indexOf(candidate.mining.toLowerCase()); 360 | expect(validatorIndex, `Candidate ${JSON.stringify(candidate)} 361 | is not in the validator set in the new epoch`) 362 | .to.not.equal(-1); 363 | } 364 | }); 365 | 366 | it('New tokens are minted and deposited to the BlockRewardAuRa contract; delegator claims ordered withdrawal', async () => { 367 | const miningAddresses = await ValidatorSetAuRa.instance.methods.getValidators().call(); 368 | const unremovableValidator = await ValidatorSetAuRa.instance.methods.unremovableValidator().call(); 369 | const candidate = constants.CANDIDATES[2].staking; 370 | const candidatePoolId = await ValidatorSetAuRa.instance.methods.idByStakingAddress(constants.CANDIDATES[2].staking).call(); 371 | const delegator = delegators[0]; 372 | 373 | const stakingEpoch = await StakingAuRa.instance.methods.stakingEpoch().call(); 374 | const iBlockRewardAuRaBalance = new BN(await StakingTokenContract.instance.methods.balanceOf(BlockRewardAuRa.address).call()); 375 | 376 | let validators = {}; 377 | for (mining of miningAddresses) { 378 | const poolId = await ValidatorSetAuRa.instance.methods.idByMiningAddress(mining).call(); 379 | const staking = (await ValidatorSetAuRa.instance.methods.stakingByMiningAddress(mining).call()).toLowerCase(); 380 | const balance = await BlockRewardAuRa.instance.methods.epochPoolTokenReward(stakingEpoch, poolId).call(); 381 | if (poolId == unremovableValidator) { 382 | // don't check unremovable validator because they didn't stake 383 | continue; 384 | } 385 | validators[mining] = { 386 | poolId, 387 | staking: staking, 388 | balance: new BN(balance.toString()) 389 | }; 390 | } 391 | 392 | // initial stake on the candidate 393 | const iStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, delegator).call(); 394 | const iStakeBN = new BN(iStake.toString()); 395 | console.log(`***** Initial stake of delegator ${delegator} on candidate ${candidate} is ${iStakeBN.toString()}, going to order withdrawal of ${minDelegatorStakeBN.toString()}`); 396 | const tx = await sendInStakingWindow(web3, async () => { 397 | return SnS(web3, { 398 | from: delegator, 399 | to: StakingAuRa.address, 400 | method: StakingAuRa.instance.methods.orderWithdraw(candidate, minDelegatorStakeBN.toString()), 401 | gasPrice: await calcMinGasPrice(web3) 402 | }); 403 | }); 404 | pp.tx(tx); 405 | expect(tx.status, `Tx to order withdrawal failed: ${tx.transactionHash}`).to.equal(true); 406 | 407 | const fStake = await StakingAuRa.instance.methods.stakeAmount(candidatePoolId, delegator).call(); 408 | const fStakeBN = new BN(fStake.toString()); 409 | 410 | expect(fStakeBN, `Delegator\'s stake didn\'t decrease correctly after they (${delegator}) ordered the withdrawal: ` + 411 | `initial = ${iStakeBN.toString()}, final = ${fStakeBN.toString()}, ordered amount = ${minDelegatorStakeBN.toString()}` 412 | ).to.be.bignumber.equal(iStakeBN.sub(minDelegatorStakeBN)); 413 | 414 | await waitForNextStakingEpoch(web3); 415 | 416 | await new Promise(r => setTimeout(r, 10000)); 417 | 418 | console.log('***** Check BlockRewardAuRa and pool balances changing'); 419 | const fBlockRewardAuRaBalance = new BN(await StakingTokenContract.instance.methods.balanceOf(BlockRewardAuRa.address).call()); 420 | expect(fBlockRewardAuRaBalance, `BlockRewardAuRa contract did not receive minted tokens`) 421 | .to.be.bignumber.above(iBlockRewardAuRaBalance); 422 | console.log(`**** BlockRewardAuRa had ${iBlockRewardAuRaBalance} tokens before and ${fBlockRewardAuRaBalance} tokens after.`); 423 | for (mining in validators) { 424 | const new_balance = new BN(await BlockRewardAuRa.instance.methods.epochPoolTokenReward(stakingEpoch, validators[mining].poolId).call()); 425 | expect(new_balance, `Pool with mining address ${mining} did not receive minted tokens`) 426 | .to.be.bignumber.above(validators[mining].balance); 427 | console.log(`**** the pool ${mining} had ${validators[mining].balance} tokens before and ${new_balance} tokens after.`); 428 | } 429 | 430 | const iOrdered = await StakingAuRa.instance.methods.orderedWithdrawAmount(candidatePoolId, delegator).call(); 431 | const iOrderedBN = new BN(iOrdered.toString()); 432 | 433 | console.log('***** Claiming ordered withdrawal'); 434 | const tx2 = await sendInStakingWindow(web3, async () => { 435 | return SnS(web3, { 436 | from: delegator, 437 | to: StakingAuRa.address, 438 | method: StakingAuRa.instance.methods.claimOrderedWithdraw(candidate), 439 | gasPrice: await calcMinGasPrice(web3) 440 | }); 441 | }); 442 | pp.tx(tx2); 443 | 444 | const fOrdered = await StakingAuRa.instance.methods.orderedWithdrawAmount(candidatePoolId, delegator).call(); 445 | const fOrderedBN = new BN(fOrdered.toString()); 446 | 447 | expect(fOrderedBN, `Delegator\'s ordered amount didn\'t decrease correctly after they (${delegator}) claimed the withdrawal: ` + 448 | `initial = ${iOrderedBN.toString()}, final = ${fOrderedBN.toString()}, claimed amount = ${minDelegatorStakeBN.toString()}` 449 | ).to.be.bignumber.equal(iOrderedBN.sub(minDelegatorStakeBN)); 450 | 451 | expect(fOrderedBN, `Delegator\'s ordered amount now should be zero: ` + 452 | `initial = ${iOrderedBN.toString()}, final = ${fOrderedBN.toString()}, claimed amount = ${minDelegatorStakeBN.toString()}` 453 | ).to.be.bignumber.equal(new BN(0)); 454 | }); 455 | }); 456 | 457 | function sleep(millis) { 458 | return new Promise(resolve => setTimeout(resolve, millis)); 459 | } 460 | -------------------------------------------------------------------------------- /test/02_removing_pool.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const constants = require('../utils/constants'); 5 | const SnS = require('../utils/signAndSendTx.js'); 6 | const web3 = new Web3('http://localhost:8541'); 7 | web3.eth.transactionConfirmationBlocks = 1; 8 | const BN = web3.utils.BN; 9 | const OWNER = constants.OWNER; 10 | const expect = require('chai') 11 | .use(require('chai-bn')(BN)) 12 | .use(require('chai-as-promised')) 13 | .expect; 14 | const ValidatorSetAuRa = require('../utils/getContract')('ValidatorSetAuRa', web3); 15 | const StakingAuRa = require('../utils/getContract')('StakingAuRa', web3); 16 | const waitForValidatorSetChange = require('../utils/waitForValidatorSetChange'); 17 | const calcMinGasPrice = require('../utils/calcMinGasPrice'); 18 | const pp = require('../utils/prettyPrint'); 19 | 20 | describe('Pool removal and validator set change', () => { 21 | let tiredValidator = {}; 22 | 23 | it('The last validator removes his pool', async () => { 24 | let validators = await ValidatorSetAuRa.instance.methods.getValidators().call(); 25 | console.log('***** Initial validator set = ' + JSON.stringify(validators)); 26 | if (!validators.length == 1) { 27 | throw new Error('This test cannot be performed because it requires at least 2 validators in the validatorSet'); 28 | } 29 | 30 | tiredValidator.mining = validators[validators.length - 1]; 31 | tiredValidator.staking = await ValidatorSetAuRa.instance.methods.stakingByMiningAddress(tiredValidator.mining).call(); 32 | console.log('***** Validator to be removed: ' + JSON.stringify(tiredValidator)); 33 | const tx = await SnS(web3, { 34 | from: tiredValidator.staking, 35 | to: StakingAuRa.address, 36 | method: StakingAuRa.instance.methods.removeMyPool(), 37 | gasPrice: await calcMinGasPrice(web3), 38 | gasLimit: '300000', 39 | }); 40 | pp.tx(tx); 41 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 42 | }); 43 | 44 | it('Validator is not present in the validator set in the next staking epoch', async () => { 45 | console.log('***** Wait for staking epoch to change'); 46 | let validators = (await waitForValidatorSetChange(web3)).map(v => v.toLowerCase()); 47 | let validatorIndex = validators.indexOf(tiredValidator.mining.toLowerCase()); 48 | expect(validatorIndex, `Validator ${JSON.stringify(tiredValidator)} 49 | removed his pool but still is in validator set`) 50 | .to.equal(-1); 51 | }); 52 | }); 53 | 54 | describe('Unremovable validator removes his pool', () => { 55 | let unremovableValidator = {}; 56 | 57 | it('Owner calls ValidatorSetAuRa.clearUnremovableValidator', async () => { 58 | unremovableValidator.poolId = await ValidatorSetAuRa.instance.methods.unremovableValidator().call(); 59 | unremovableValidator.staking = (await ValidatorSetAuRa.instance.methods.stakingAddressById(unremovableValidator.poolId).call()).toLowerCase(); 60 | unremovableValidator.mining = (await ValidatorSetAuRa.instance.methods.miningAddressById(unremovableValidator.poolId).call()).toLowerCase(); 61 | 62 | if (unremovableValidator.poolId == '0') { 63 | console.log('***** Unremovable validator doesn\'t exist. Skip this test'); 64 | return; 65 | } 66 | 67 | const validatorsList = await ValidatorSetAuRa.instance.methods.getValidators().call(); 68 | expect(validatorsList[0].toLowerCase() == unremovableValidator.mining, `Unexpected unremovable validator mining: ${validatorsList[0]}, actual mining: ${unremovableValidator.mining}`) 69 | .to.equal(true); 70 | 71 | console.log('***** Unremovable validator: ' + JSON.stringify(unremovableValidator)); 72 | console.log('***** OWNER calls clearUnremovableValidator'); 73 | const tx = await SnS(web3, { 74 | from: OWNER, 75 | to: ValidatorSetAuRa.address, 76 | method: ValidatorSetAuRa.instance.methods.clearUnremovableValidator(unremovableValidator.poolId), 77 | gasPrice: '0', 78 | gasLimit: '300000', 79 | }); 80 | pp.tx(tx); 81 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 82 | 83 | const check_unremovableValidator = await ValidatorSetAuRa.instance.methods.unremovableValidator().call(); 84 | console.log('***** ValidatorSetAuRa.unremovableValidator after the call: ' + check_unremovableValidator); 85 | expect(check_unremovableValidator == '0', 'Unremovable validator is not cleared') 86 | .to.equal(true); 87 | }); 88 | 89 | it('Ex unremovable validator removes his pool', async() => { 90 | if (unremovableValidator.poolId == '0') { 91 | console.log('***** Unremovable validator doesn\'t exist. Skip this test'); 92 | return; 93 | } 94 | 95 | console.log('***** Ex unremovable validator calls StakingAuRa.removeMyPool'); 96 | const tx = await SnS(web3, { 97 | from: unremovableValidator.staking, 98 | to: StakingAuRa.address, 99 | method: StakingAuRa.instance.methods.removeMyPool(), 100 | gasPrice: await calcMinGasPrice(web3), 101 | gasLimit: '300000', 102 | }); 103 | pp.tx(tx); 104 | expect(tx.status, `Failed tx: ${tx.transactionHash}`).to.equal(true); 105 | 106 | const pools = await StakingAuRa.instance.methods.getPools().call(); 107 | const poolFound = pools.filter(pool => pool == unremovableValidator.poolId).length > 0; 108 | expect(poolFound, 'Unremovable validator\'s pool still exists').to.equal(false); 109 | 110 | const validatorsList = await waitForValidatorSetChange(web3); 111 | console.log('***** Validators list after removal = ' + JSON.stringify(validatorsList)); 112 | expect(validatorsList[0].toLowerCase() != unremovableValidator.mining, `Unremovable validator still present in the validators list`) 113 | .to.equal(true); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/03_ordinary_node.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const expect = require('chai') 5 | .use(require('chai-as-promised')) 6 | .expect; 7 | 8 | const node0Path = '../data/node0'; 9 | const blocks_fname = path.join(__dirname, `${node0Path}/blocks.log`); 10 | const check_fname = path.join(__dirname, `${node0Path}/check.log`); 11 | 12 | describe('Check log of ordinary node to find block sync issues', () => { 13 | it(`file ${check_fname} should be empty`, async () => { 14 | const fcontent = fs.readFileSync(check_fname, 'utf8').trim(); 15 | expect(fcontent.length == 0, `Ordinary node had some block sync issue, check ${path.basename(check_fname)} for logs`).to.equal(true); 16 | }); 17 | it(`file ${blocks_fname} should not be empty`, async () => { 18 | const fcontent = fs.readFileSync(blocks_fname, 'utf8').trim(); 19 | expect(fcontent.length != 0, `${path.basename(blocks_fname)} file is empty. Seems the watchOrdinaryNode.js script did not work`).to.equal(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/04_random_seed.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const expect = require('chai') 5 | .use(require('chai-as-promised')) 6 | .expect; 7 | 8 | const node1Path = '../data/node1'; 9 | const seed_fname = path.join(__dirname, `${node1Path}/checkRandomSeed.log`); 10 | const seed_debug_fname = path.join(__dirname, `${node1Path}/checkRandomSeedDebug.log`); 11 | 12 | describe('Check log of random seeds to find incorrect seed values', () => { 13 | it(`file ${seed_fname} should be empty`, async () => { 14 | const fcontent = fs.readFileSync(seed_fname, 'utf8').trim(); 15 | expect(fcontent.length == 0, `There were errors in seed calculation, check ${path.basename(seed_fname)} for logs`).to.equal(true); 16 | }); 17 | it(`file ${seed_debug_fname} should not be empty`, async () => { 18 | const fcontent = fs.readFileSync(seed_debug_fname, 'utf8').trim(); 19 | expect(fcontent.length != 0, `${path.basename(seed_debug_fname)} file is empty. Seems the watchRandomSeed.js script did not work`).to.equal(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /utils/calcMinGasPrice.js: -------------------------------------------------------------------------------- 1 | const getLatestBlock = require('./getLatestBlock'); 2 | 3 | module.exports = async function (web3, configMinGasPrice) { 4 | let minGasPrice; 5 | if (configMinGasPrice !== undefined) { 6 | minGasPrice = new web3.utils.BN(configMinGasPrice); 7 | } else { 8 | const config = require('../config/node1.nethermind.json'); 9 | minGasPrice = new web3.utils.BN(config.Mining.MinGasPrice); 10 | } 11 | let gasPrice = minGasPrice; 12 | const latestBlock = await getLatestBlock(web3); 13 | if (latestBlock.baseFeePerGas) { 14 | // For EIP-1559 and legacy tx the gasPrice should satisfy the requirement: 15 | // require(legacyGasPrice - latestBlock.baseFeePerGas >= MinGasPrice) 16 | const baseFeePerGas = new web3.utils.BN(web3.utils.hexToNumberString(latestBlock.baseFeePerGas)); 17 | gasPrice = minGasPrice.add(baseFeePerGas); 18 | } 19 | return gasPrice; 20 | } 21 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const specParams = require("../posdao-contracts/spec").engine.authorityRound.params; 3 | const BLOCK_REWARD_ADDRESS = specParams.blockRewardContractAddress; 4 | const VALIDATOR_SET_ADDRESS = specParams.validators.multi[0].contract; 5 | const RANDOM_AURA_ADDRESS = specParams.randomnessContractAddress[0]; 6 | const STAKING_CONTRACT_ADDRESS = "0x1100000000000000000000000000000000000001"; 7 | const TX_PRIORITY_CONTRACT_ADDRESS = "0x4100000000000000000000000000000000000000"; 8 | const CERTIFIER_ADDRESS = "0x5000000000000000000000000000000000000001"; 9 | 10 | module.exports = { 11 | OWNER: "0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24", 12 | CANDIDATES: [ 13 | { mining: "0xf67cc5231c5858ad6cc87b105217426e17b824bb", staking: "0xb916e7e1f4bcb13549602ed042d36746fd0d96c9" }, 14 | { mining: "0xbe69eb0968226a1808975e1a1f2127667f2bffb3", staking: "0xdb9cb2478d917719c53862008672166808258577" }, 15 | { mining: "0x720e118ab1006cc97ed2ef6b4b49ac04bb3aa6d9", staking: "0xb6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e" } 16 | ], 17 | BLOCK_REWARD_ADDRESS, 18 | CERTIFIER_ADDRESS, 19 | VALIDATOR_SET_ADDRESS, 20 | STAKING_CONTRACT_ADDRESS, 21 | TX_PRIORITY_CONTRACT_ADDRESS, 22 | RANDOM_AURA_ADDRESS, 23 | CANDIDATE_INITIAL_BALANCE: '100000000000000000000', // 100 "ETH" 24 | }; 25 | -------------------------------------------------------------------------------- /utils/getContract.js: -------------------------------------------------------------------------------- 1 | const constants = require('./constants'); 2 | 3 | module.exports = function (contractName, web3) { 4 | var abi; 5 | var info; 6 | switch (contractName) { 7 | case 'RandomAuRa': 8 | abi = require('../posdao-contracts/build/contracts/RandomAuRa').abi; 9 | return { 10 | address: constants.RANDOM_AURA_ADDRESS, 11 | abi: abi, 12 | instance: new web3.eth.Contract(abi, constants.RANDOM_AURA_ADDRESS), 13 | }; 14 | 15 | case 'BlockRewardAuRa': 16 | abi = require('../posdao-contracts/build/contracts/BlockRewardAuRa').abi; 17 | return { 18 | address: constants.BLOCK_REWARD_ADDRESS, 19 | abi: abi, 20 | instance: new web3.eth.Contract(abi, constants.BLOCK_REWARD_ADDRESS), 21 | }; 22 | 23 | case 'Certifier': 24 | abi = require('../posdao-contracts/build/contracts/Certifier').abi; 25 | return { 26 | address: constants.CERTIFIER_ADDRESS, 27 | abi: abi, 28 | instance: new web3.eth.Contract(abi, constants.CERTIFIER_ADDRESS), 29 | }; 30 | 31 | case 'ValidatorSetAuRa': 32 | abi = require('../posdao-contracts/build/contracts/ValidatorSetAuRa').abi; 33 | return { 34 | address: constants.VALIDATOR_SET_ADDRESS, 35 | abi: abi, 36 | instance: new web3.eth.Contract(abi, constants.VALIDATOR_SET_ADDRESS), 37 | }; 38 | 39 | case 'StakingAuRa': 40 | abi = require('../posdao-contracts/build/contracts/StakingAuRa').abi; 41 | return { 42 | address: constants.STAKING_CONTRACT_ADDRESS, 43 | abi: abi, 44 | instance: new web3.eth.Contract(abi, constants.STAKING_CONTRACT_ADDRESS), 45 | }; 46 | 47 | case 'StakingToken': 48 | info = require('../data/StakingToken'); 49 | return { 50 | address: info.address, 51 | abi: info.abi, 52 | instance: new web3.eth.Contract(info.abi, info.address), 53 | }; 54 | 55 | case 'TxPriority': 56 | abi = require('../posdao-contracts/build/contracts/TxPriority').abi; 57 | return { 58 | address: constants.TX_PRIORITY_CONTRACT_ADDRESS, 59 | abi: abi, 60 | instance: new web3.eth.Contract(abi, constants.TX_PRIORITY_CONTRACT_ADDRESS), 61 | }; 62 | 63 | default: 64 | throw new Error('Unknown contract ' + contractName); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /utils/getLatestBlock.js: -------------------------------------------------------------------------------- 1 | // Nethermind sometimes returns null for eth_getBlockByNumber. 2 | // This is a workaround to do a few tries. 3 | module.exports = async function (web3) { 4 | let tries = 0; 5 | let latestBlock = await web3.eth.getBlock('latest'); 6 | while (!latestBlock && tries < 3) { 7 | await new Promise(r => setTimeout(r, 500)); 8 | latestBlock = await web3.eth.getBlock('latest'); 9 | tries++; 10 | } 11 | return latestBlock; 12 | } 13 | -------------------------------------------------------------------------------- /utils/mintCoins.js: -------------------------------------------------------------------------------- 1 | const constants = require('./constants'); 2 | const SnS = require('./signAndSendTx.js'); 3 | const OWNER = constants.OWNER; 4 | const getContract = require('./getContract'); 5 | 6 | module.exports = async function (web3, fromWhom, toWhom, howMuch) { 7 | const BlockRewardAuRa = getContract('BlockRewardAuRa', web3); 8 | if (!fromWhom) { 9 | fromWhom = OWNER; 10 | } 11 | if (typeof toWhom === 'string') { 12 | toWhom = [toWhom]; 13 | } 14 | 15 | // first - set allowed sender, this is always done from OWNER 16 | await SnS(web3, { 17 | from: OWNER, 18 | to: BlockRewardAuRa.address, 19 | method: BlockRewardAuRa.instance.methods.setErcToNativeBridgesAllowed([fromWhom]), 20 | gasPrice: '0', 21 | }); 22 | 23 | // send txs taking care about nonces 24 | let txsp = []; 25 | let nonce = await web3.eth.getTransactionCount(fromWhom); 26 | for (let i = 0; i < toWhom.length; i++) { 27 | const tx = SnS(web3, { 28 | from: fromWhom, 29 | to: BlockRewardAuRa.address, 30 | method: BlockRewardAuRa.instance.methods.addExtraReceiver(howMuch, toWhom[i]), 31 | gasPrice: '0', 32 | nonce: nonce, 33 | }); 34 | txsp.push(tx); 35 | nonce++; 36 | } 37 | // return all tx promises 38 | return Promise.all(txsp); 39 | } 40 | -------------------------------------------------------------------------------- /utils/prettyPrint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tx: function (tx, prefix) { 3 | prefix = prefix || '****'; 4 | console.log(prefix, 'tx: status =', tx.status, 'hash =', tx.transactionHash, 'blockNumber =', tx.blockNumber); 5 | }, 6 | } -------------------------------------------------------------------------------- /utils/sendInStakingWindow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getLatestBlock = require('./getLatestBlock'); 4 | 5 | const RETRY_INTERVAL_MS = 2499; 6 | 7 | async function getCurrentBlockNumber(web3) { 8 | const latestBlock = await getLatestBlock(web3); 9 | return parseInt(latestBlock.number); 10 | } 11 | 12 | module.exports = async function (web3, sendTx) { 13 | let StakingAuRa = require('../utils/getContract')('StakingAuRa', web3); 14 | // `8` is to account for possible period of snapshotting 15 | let stakeWithdrawDisallowPeriod = parseInt(await StakingAuRa.instance.methods.stakeWithdrawDisallowPeriod().call()); 16 | let maxRetriesBlocks = 8 + stakeWithdrawDisallowPeriod; 17 | 18 | let startBlock = await getCurrentBlockNumber(web3); 19 | let currentBlock = startBlock; 20 | let exc; 21 | 22 | while (currentBlock <= startBlock + maxRetriesBlocks) { 23 | currentBlock = await getCurrentBlockNumber(web3); 24 | if ( !(await StakingAuRa.instance.methods.areStakeAndWithdrawAllowed().call()) ) { 25 | console.log(`***** stake/withdraw not allowed now (block ${currentBlock})`); 26 | await new Promise(r => setTimeout(r, RETRY_INTERVAL_MS)); 27 | continue; 28 | } 29 | try { 30 | let tx = await sendTx(); 31 | return tx; 32 | } 33 | catch (e) { 34 | exc = e; 35 | let blocksPassed = (await getCurrentBlockNumber(web3)) - currentBlock; 36 | if (blocksPassed <= stakeWithdrawDisallowPeriod && !!(await StakingAuRa.instance.methods.areStakeAndWithdrawAllowed().call())) { 37 | throw new Error(`Tx failed yet it seems to be in the staking window, exception: ${exc}`); 38 | } 39 | else { 40 | console.log(`***** Tx execution failed, waiting for stake/withdraw window`); 41 | await new Promise(r => setTimeout(r, RETRY_INTERVAL_MS)); 42 | } 43 | } 44 | } 45 | throw new Error(`Tx didn't succeed after ${maxRetriesBlocks} blocks or areStakeAndWithdrawAllowed() didn't returned "true". Last exception: ${exc}`); 46 | } 47 | -------------------------------------------------------------------------------- /utils/sendRequest.js: -------------------------------------------------------------------------------- 1 | module.exports = function (cmd) { 2 | return new Promise((resolve, reject) => { 3 | var exec = require('child_process').exec; 4 | exec(cmd, function (error, stdout, stderr) { 5 | if (error !== null) { 6 | reject(error); 7 | } 8 | let resp; 9 | try { 10 | resp = JSON.parse(stdout); 11 | } catch(e) { 12 | reject(e); 13 | } 14 | if (resp.hasOwnProperty('result')) { 15 | resolve(resp.result); 16 | } else { 17 | reject(new Error(`JSON RPC result is undefined. Response text: ${stdout}`)); 18 | } 19 | }); 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /utils/sign2718Tx.js: -------------------------------------------------------------------------------- 1 | const secp256k1 = require("secp256k1"); 2 | const rlp = require('rlp'); 3 | const Web3 = require('web3'); 4 | const web3 = new Web3(); 5 | const BN = web3.utils.BN; 6 | 7 | function prepareTxDataField(data) { 8 | if (!data) { 9 | return ''; 10 | } else { 11 | if (!data.toLowerCase().startsWith('0x')) { 12 | throw "Data should have 0x prefix"; 13 | } 14 | } 15 | return data; 16 | } 17 | 18 | function prepareTxIntegerField(value, name) { 19 | if (value === undefined) { 20 | throw `${name} is not defined`; 21 | } 22 | if (!web3.utils.isHexStrict(value)) { 23 | value = web3.utils.toHex(value); 24 | } 25 | if ((new BN(web3.utils.stripHexPrefix(value), 16)).isZero()) { 26 | return ''; 27 | } 28 | return new BN(web3.utils.hexToNumberString(value)); 29 | } 30 | 31 | function prepareTxToField(to) { 32 | if (to === undefined) { 33 | throw "Destination address is not defined"; 34 | } 35 | to = to.toLowerCase(); 36 | if (web3.utils.isAddress(to)) { 37 | to = to.startsWith('0x') ? to : `0x${to}`; 38 | } else { 39 | throw "Invalid destination address"; 40 | } 41 | return to; 42 | } 43 | 44 | function signTransaction(txMessage, txType, privateKey) { 45 | const messageHash = web3.utils.keccak256('0x' + (txType > 0 ? `0${txType}` : '') + rlp.encode(txMessage).toString('hex')); 46 | 47 | let privateKeyBuffer; 48 | if (Buffer.isBuffer(privateKey)) { 49 | privateKeyBuffer = privateKey; 50 | } else { 51 | privateKeyBuffer = Buffer.from(privateKey.toLowerCase().startsWith('0x') ? privateKey.slice(2) : privateKey, "hex"); 52 | } 53 | 54 | const sigObj = secp256k1.ecdsaSign(Buffer.from(messageHash.slice(2), "hex"), privateKeyBuffer); 55 | const signature = Buffer.from(sigObj.signature).toString('hex'); 56 | 57 | const chainId = txType > 0 ? txMessage[0] : txMessage[6]; 58 | let v; 59 | if (txType > 0) { 60 | v = (sigObj.recid != 0) ? web3.utils.toHex(sigObj.recid) : ''; 61 | } else { 62 | v = web3.utils.toHex(sigObj.recid + 27 + chainId * 2 + 8); 63 | } 64 | const r = '0x' + signature.slice(0, 64).replace(/^0+/, ''); 65 | const s = '0x' + signature.slice(64, 128).replace(/^0+/, ''); 66 | 67 | const txMessageSigned = txType > 0 ? txMessage : txMessage.slice(0, 6); 68 | txMessageSigned.push(v); 69 | txMessageSigned.push(r); 70 | txMessageSigned.push(s); 71 | 72 | const rawTransaction = '0x' + (txType > 0 ? `0${txType}` : '') + rlp.encode(txMessageSigned).toString('hex'); 73 | const transactionHash = web3.utils.keccak256(rawTransaction); 74 | 75 | return { messageHash, v, r, s, rawTransaction, transactionHash }; 76 | } 77 | 78 | module.exports = function (transaction, privateKey, txType) { 79 | const chainId = prepareTxIntegerField(transaction.chainId, 'Chain id'); 80 | const nonce = prepareTxIntegerField(transaction.nonce, 'Nonce'); 81 | const gas = prepareTxIntegerField(transaction.gas, 'Gas limit'); 82 | const to = prepareTxToField(transaction.to); 83 | const value = prepareTxIntegerField(transaction.value, 'Value'); 84 | const data = prepareTxDataField(transaction.data); 85 | 86 | let txMessage = [chainId, nonce]; 87 | 88 | if (txType == 2) { // EIP-1559 89 | txMessage.push(prepareTxIntegerField(transaction.maxPriorityFeePerGas, 'maxPriorityFeePerGas')); 90 | txMessage.push(prepareTxIntegerField(transaction.maxFeePerGas, 'maxFeePerGas')); 91 | } else if (txType == 1) { // EIP-2930 92 | txMessage.push(prepareTxIntegerField(transaction.gasPrice, 'Gas price')); 93 | } else { 94 | throw "Unsupported transaction type"; 95 | } 96 | 97 | txMessage = txMessage.concat([ 98 | gas, 99 | to, 100 | value, 101 | data, 102 | transaction.accessList 103 | ]); 104 | 105 | return signTransaction(txMessage, txType, privateKey); 106 | } 107 | -------------------------------------------------------------------------------- /utils/signAndSendTx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EthereumTx = require('ethereumjs-tx'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const sign2718Transaction = require('./sign2718Tx.js'); 6 | /* 7 | * Expects the following structure for tx_details: 8 | { 9 | from: "0x...", 10 | to: "0x...", 11 | value: 1234, // defaults to 0 12 | gasPrice: 4321, // defaults to 1 gwei 13 | gasLimit: 1234, // runs estimateGas if empty 14 | method: myContract.myMethod(param1, param2, ...) // optional 15 | nonce: 1324, // auto-calculated if empty 16 | } 17 | * If privateKey is empty, it is recovered from json file in /accounts/keystore folder 18 | * Returns sendSignedTransaction promise. 19 | */ 20 | 21 | const DEBUG=false; 22 | const dbg = DEBUG? function dbg(...msg) { console.log(...msg) } : function () {}; 23 | 24 | const keysDir = path.join(__dirname, '../accounts/'); 25 | const keysPassword = fs.readFileSync( 26 | path.join(__dirname, '../config/password'), 27 | 'utf-8' 28 | ).trim(); 29 | 30 | function getPrivateKey(web3, address) { 31 | var fname = path.join(keysDir, './keystore/', address.substring(2).toLowerCase() + '.json'); 32 | var keystore = require(fname); 33 | var privateKey = web3.eth.accounts.decrypt(keystore, keysPassword).privateKey; 34 | var pkBuff = Buffer.from(privateKey.substring(2), "hex"); 35 | return pkBuff; 36 | } 37 | 38 | module.exports = async function (web3, tx_details, privateKey, eip1559BaseFee, eip2930AccessList) { 39 | let from = tx_details.from; 40 | let to = tx_details.to; 41 | let value = web3.utils.toHex(tx_details.value || 0); 42 | dbg(' **** from =', from); 43 | dbg(' **** to =', to); 44 | dbg(' **** value =', value); 45 | 46 | let gasPrice = web3.utils.toWei('1', 'gwei'); 47 | if (tx_details.gasPrice != null) { 48 | gasPrice = tx_details.gasPrice; 49 | } 50 | dbg(' **** gasPrice =', gasPrice); 51 | 52 | // defaults for plain eth-transfer transaction 53 | let data = '0x'; 54 | let egas = '21000'; 55 | if (tx_details.method != null) { 56 | data = tx_details.method.encodeABI(); 57 | } 58 | if (tx_details.gasLimit == null && tx_details.method != null) { 59 | egas = await tx_details.method.estimateGas({ from, gasPrice }); 60 | } 61 | else { 62 | egas = tx_details.gasLimit; 63 | } 64 | dbg(' **** data =', data); 65 | dbg(' **** egas =', egas); 66 | 67 | let nonce; 68 | if (tx_details.nonce == null) { 69 | nonce = await web3.eth.getTransactionCount(from); 70 | } 71 | else { 72 | nonce = tx_details.nonce; 73 | } 74 | dbg(' **** nonce =', nonce); 75 | 76 | let chainId = await web3.eth.net.getId(); 77 | dbg(' **** chainId =', chainId); 78 | 79 | if (privateKey == null) { 80 | privateKey = getPrivateKey(web3, from); 81 | } 82 | 83 | let _tx = { 84 | from: from, 85 | to: to, 86 | value: web3.utils.toHex(value), 87 | gasPrice: web3.utils.toHex(gasPrice), 88 | data: data, 89 | gasLimit: web3.utils.toHex(egas), 90 | nonce: web3.utils.toHex(nonce), 91 | chainId: chainId, 92 | }; 93 | 94 | if (eip1559BaseFee) { // EIP-1559 is active 95 | if (gasPrice == 0) { 96 | _tx.maxFeePerGas = '0'; 97 | } else { 98 | _tx.maxFeePerGas = web3.utils.toBN(eip1559BaseFee).add(web3.utils.toBN(gasPrice)).toString(); // maxFeePerGas = baseFee + maxPriorityFeePerGas 99 | } 100 | _tx.maxPriorityFeePerGas = gasPrice; 101 | } 102 | 103 | if (eip1559BaseFee || eip2930AccessList) { 104 | _tx.gas = _tx.gasLimit; 105 | _tx.accessList = eip2930AccessList || []; 106 | } 107 | 108 | dbg(' **** _tx =', _tx); 109 | 110 | if (eip1559BaseFee) { // EIP-1559 is active 111 | const signedTx = sign2718Transaction(_tx, privateKey, 2); // EIP-1559 112 | const serializedTx = signedTx.rawTransaction; 113 | return web3.eth.sendSignedTransaction(serializedTx); 114 | } else if (eip2930AccessList) { 115 | const signedTx = sign2718Transaction(_tx, privateKey, 1); // EIP-2930 116 | const serializedTx = signedTx.rawTransaction; 117 | return web3.eth.sendSignedTransaction(serializedTx); 118 | } else { 119 | let tx = new EthereumTx(_tx); 120 | dbg(' **** tx =', tx); 121 | tx.sign(privateKey); 122 | let serializedTx = tx.serialize(); 123 | return web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /utils/waitForNextStakingEpoch.js: -------------------------------------------------------------------------------- 1 | const getContract = require('./getContract'); 2 | 3 | const RETRY_INTERVAL_MS = 2499; 4 | 5 | module.exports = async (web3) => { 6 | let StakingAuRa = getContract('StakingAuRa', web3); 7 | let latestBlock = await StakingAuRa.instance.methods.stakingEpochEndBlock().call(); 8 | console.log('**** Waiting for next staking epoch to start (after block ' + latestBlock + ')') 9 | while ( 10 | parseInt( 11 | await web3.eth.getBlockNumber() 12 | ) <= latestBlock 13 | ) { 14 | await new Promise(r => setTimeout(r, RETRY_INTERVAL_MS)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/waitForValidatorSetChange.js: -------------------------------------------------------------------------------- 1 | const getContract = require('./getContract'); 2 | 3 | const RETRY_INTERVAL_MS = 2499; 4 | 5 | module.exports = async (web3) => { 6 | let ValidatorSetAuRaContract = getContract('ValidatorSetAuRa', web3).instance; 7 | let StakingAuRaContract = getContract('StakingAuRa', web3).instance; 8 | 9 | let initialStakingEpoch = parseInt(await StakingAuRaContract.methods.stakingEpoch().call()); 10 | // wait for the next staking epoch 11 | while (true) { 12 | await new Promise(r => setTimeout(r, RETRY_INTERVAL_MS)); 13 | let currentStakingEpoch = parseInt(await StakingAuRaContract.methods.stakingEpoch().call()); 14 | let currentBlock = parseInt((await web3.eth.getBlock('latest')).number); 15 | if (currentStakingEpoch > initialStakingEpoch) { 16 | console.log(`***** Staking epoch changed at block ${currentBlock} (new epoch: ${currentStakingEpoch})`); 17 | break; 18 | } 19 | } 20 | 21 | // wait until new validator set is applied 22 | while ( 23 | parseInt( 24 | await ValidatorSetAuRaContract.methods.validatorSetApplyBlock().call() 25 | ) === 0 26 | ) { 27 | await new Promise(r => setTimeout(r, RETRY_INTERVAL_MS)); 28 | } 29 | let currentBlock = parseInt((await web3.eth.getBlock('latest')).number); 30 | let validatorSet = await ValidatorSetAuRaContract.methods.getValidators().call(); 31 | console.log(`***** ValidatorSet change applied at block ${currentBlock} 32 | (new validator set: ${JSON.stringify(validatorSet)})`); 33 | return validatorSet; 34 | } 35 | --------------------------------------------------------------------------------