├── .dockerignore ├── .env.example ├── .gitattributes ├── .gitignore ├── .prettierrc ├── CONTRACTS.md ├── Dockerfile ├── Dockerfile.spark ├── LICENSE ├── README.md ├── Version ├── abis ├── BlockReward_abi.json ├── Consensus_abi.json ├── EternalStorageProxy_abi.json ├── ProxyStorage_abi.json └── Voting_abi.json ├── app ├── .dockerignore ├── Dockerfile ├── Dockerfile.spark ├── README.md ├── VERSION.FUSE.txt ├── VERSION.SPARK.txt ├── abi │ ├── blockReward.json │ └── consensus.json ├── index.js ├── package.json └── scripts │ └── run.sh ├── audits └── zokyo.pdf ├── commands ├── Version_legacy ├── abi.sh ├── backup.sh ├── buildContainers.sh ├── clean-docker.sh ├── flatten.sh ├── parity_wrapper.sh ├── parity_wrapper_spark.sh ├── quickstart.sh ├── setup.sh ├── test.sh └── update_and_run.sh ├── config ├── bootnodes.txt ├── config.toml ├── spark │ ├── bootnodes.txt │ ├── config.toml │ └── spec.json └── spec.json ├── contracts ├── BlockReward.sol ├── Consensus.sol ├── ConsensusUtils.sol ├── Migrations.sol ├── ProxyStorage.sol ├── Voting.sol ├── VotingUtils.sol ├── abstracts │ ├── BlockRewardBase.sol │ ├── ValidatorSet.sol │ └── VotingBase.sol ├── eternal-storage │ ├── EternalStorage.sol │ └── EternalStorageProxy.sol ├── interfaces │ ├── IBlockReward.sol │ ├── IConsensus.sol │ └── IVoting.sol └── test │ ├── BlockRewardMock.sol │ ├── ConsensusMock.sol │ ├── EternalStorageProxyMock.sol │ ├── ProxyStorageMock.sol │ └── VotingMock.sol ├── coverage.json ├── hardhat.config.js ├── nethermind ├── OE_Migration.md ├── README.md ├── docker │ ├── README.md │ ├── client │ │ ├── .archive.env │ │ ├── .bootnode.env │ │ ├── .node.env │ │ ├── .validator.env │ │ ├── README.md │ │ ├── docker-compose.archive.yaml │ │ ├── docker-compose.bootnode.yaml │ │ ├── docker-compose.node.yaml │ │ ├── docker-compose.validator.yaml │ │ └── processes.json │ └── monitoring │ │ ├── .monitoring.env │ │ ├── README.md │ │ ├── docker-compose.yaml │ │ ├── grafana │ │ ├── config.ini │ │ └── provisioning │ │ │ ├── dashboards │ │ │ ├── dashboard.yaml │ │ │ └── nethermind.json │ │ │ └── datasources │ │ │ └── datasource.yaml │ │ └── prometheus │ │ └── prometheus.yaml └── quickstart.sh ├── package.json ├── scripts ├── 1_initial_migration.js ├── 2_deploy_contract.js ├── 3_validator_stake.js ├── 4_verify_contracts.js ├── 5_validator_operations.js └── 6_deploy_consensus_open_vote.js ├── test ├── blockReward.test.js ├── consensus.test.js ├── helpers.js ├── proxyStorage.test.js └── voting.test.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PROVIDER_METHOD=keystore 2 | CREDENTIALS_ADDRESS=0x000000 3 | CREDENTIALS_KEYSTORE=~/Dev/io.parity.ethereum/config/keys/FuseNetwork/ 4 | CREDENTIALS_PASSWORD=~/Dev/io.parity.ethereum/config/pass.pwd 5 | 6 | #WALLET_PROVIDER_METHOD=mnemonic 7 | #MNEMONIC=sniff hour weekend august credit alley crumble process rubber select child eternal 8 | 9 | ### Deployment ### 10 | BLOCK_REWARD_IMPLEMENTATION=0x000000 11 | BLOCK_REWARD_PROXY=0x000000 12 | CONSENSUS_IMPLEMENTATION=0x000000 13 | CONSENSUS_PROXY=0x000000 14 | PROXY_STORAGE_IMPLEMENTATION=0x000000 15 | PROXY_STORAGE_PROXY=0x000000 16 | VOTING_IMPLEMENTATION=0x000000 17 | VOTING_PROXY=0x000000 18 | 19 | INITIAL_VALIDATOR_ADDRESS=0x000000 20 | INITIAL_SUPPLY_GWEI=300000000000000000 21 | DEBUG=false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | node_modules/ 11 | .env 12 | !.env*.default 13 | .vscode/* 14 | !.vscode/settings.json.default 15 | 16 | cache/ 17 | artifacts/ 18 | keystore/ 19 | 20 | .yalc 21 | yalc.lock 22 | 23 | # Nethermind - related files 24 | **/database/* 25 | **/keystore/* 26 | **/logs/* 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /CONTRACTS.md: -------------------------------------------------------------------------------- 1 | # Fuse Network Contracts 2 | 3 | - [Consensus](#consensus) 4 | - [Block Reward](#block-reward) 5 | - [Voting](#voting) 6 | - [Proxy Storage](#proxy-storage) 7 | 8 | ![Contracts Schema](https://storage.googleapis.com/sol2uml-storage/mainnet-0x970b9bb2c0444f5e81e9d0efb84c8ccdcdcaf84d.svg "Contracts Schema") 9 | 10 | ### Consensus 11 | 12 | This contract is responsible for handling the network DPos consensus. 13 | 14 | This contract is storing the current validator set and choosing a new validator set at the end of each cycle. 15 | 16 | The logic for updating the validator set is to select a random snapshot from the snapshots taken during the cycle. 17 | 18 | The snapshots taken, are of pending validators, who are those which staked more than the minimum stake needed to become a network validator. Therefore the contract is also responsible for staking, delegating and withdrawing those funds. 19 | 20 | This contract is based on `non-reporting ValidatorSet` [described in Parity Wiki](https://wiki.parity.io/Validator-Set.html#non-reporting-contract). 21 | 22 | ### Block Reward 23 | 24 | This contract is responsible for generating and distributing block rewards to the network validators according to the network specs (5% yearly inflation). 25 | 26 | Another role of this contract is to call the snapshot/cycle logic on the Consensus contract. 27 | 28 | This contract is based on `BlockReward` [described in Parity Wiki](https://wiki.parity.io/Block-Reward-Contract). 29 | 30 | ### Voting 31 | 32 | This contract is responsible for opening new ballots and voting to accept/reject them. 33 | 34 | Ballots are basically offers to change other network contracts implementation. 35 | 36 | Only network validators can open new ballots, eveyone can vote on them, but only validators votes count when the ballot is closed. 37 | 38 | Ballots are opened/closed on cycle end. 39 | 40 | ### Proxy Storage 41 | 42 | This contract is responsible for holding network contracts implementation addresses and upgrading them if necessary (via ballot approval). 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge AS builder 2 | 3 | ENV HOME=/home/parity 4 | ENV PARITY_HOME_DIR=$HOME/.local/share/io.parity.ethereum 5 | ENV PARITY_CONFIG_FILE_CHAIN=$PARITY_HOME_DIR/spec.json 6 | ENV PARITY_CONFIG_FILE_BOOTNODES=$PARITY_HOME_DIR/bootnodes.txt 7 | ENV PARITY_CONFIG_FILE_TEMPLATE=$PARITY_HOME_DIR/config_template.toml 8 | ENV PARITY_DATA_DIR=$PARITY_HOME_DIR/chains 9 | ENV PARITY_BIN=/usr/local/bin/parity 10 | ENV PARITY_WRAPPER_SCRIPT=$HOME/parity_wrapper.sh 11 | 12 | RUN mkdir -p $PARITY_HOME_DIR && ls -la $PARITY_HOME_DIR 13 | 14 | # add depends 15 | RUN apk add --no-cache \ 16 | libstdc++ \ 17 | eudev-libs \ 18 | libgcc \ 19 | curl \ 20 | jq \ 21 | bash 22 | 23 | COPY --from=openethereum/openethereum:v3.3.5 /home/openethereum/openethereum $PARITY_BIN 24 | 25 | ### Network RPC WebSocket 26 | EXPOSE 30300 8545 8546 27 | 28 | ### Default chain and node configuration files. 29 | COPY config/spec.json $PARITY_CONFIG_FILE_CHAIN 30 | COPY config/bootnodes.txt $PARITY_CONFIG_FILE_BOOTNODES 31 | COPY config/config.toml $PARITY_CONFIG_FILE_TEMPLATE 32 | 33 | ### Wrapper script for Parity. 34 | COPY scripts/parity_wrapper.sh $PARITY_WRAPPER_SCRIPT 35 | RUN chmod +x $PARITY_WRAPPER_SCRIPT 36 | 37 | ### Shorthand links 38 | RUN ln -s $PARITY_HOME_DIR /config && ln -s $PARITY_DATA_DIR /data 39 | 40 | # Start 41 | ENTRYPOINT ["/home/parity/parity_wrapper.sh"] -------------------------------------------------------------------------------- /Dockerfile.spark: -------------------------------------------------------------------------------- 1 | FROM alpine:edge AS builder 2 | 3 | ENV HOME=/home/parity 4 | ENV PARITY_HOME_DIR=$HOME/.local/share/io.parity.ethereum 5 | ENV PARITY_CONFIG_FILE_CHAIN=$PARITY_HOME_DIR/spec.json 6 | ENV PARITY_CONFIG_FILE_BOOTNODES=$PARITY_HOME_DIR/bootnodes.txt 7 | ENV PARITY_CONFIG_FILE_TEMPLATE=$PARITY_HOME_DIR/config_template.toml 8 | ENV PARITY_DATA_DIR=$PARITY_HOME_DIR/chains 9 | ENV PARITY_BIN=/usr/local/bin/parity 10 | ENV PARITY_WRAPPER_SCRIPT=$HOME/parity_wrapper.sh 11 | 12 | RUN mkdir -p $PARITY_HOME_DIR && ls -la $PARITY_HOME_DIR 13 | 14 | # add depends 15 | RUN apk add --no-cache \ 16 | libstdc++ \ 17 | eudev-libs \ 18 | libgcc \ 19 | curl \ 20 | jq \ 21 | bash 22 | 23 | COPY --from=openethereum/openethereum:v3.3.5 /home/openethereum/openethereum $PARITY_BIN 24 | 25 | ### Network RPC WebSocket 26 | EXPOSE 30300 8545 8546 27 | 28 | ### Default chain and node configuration files. 29 | COPY config/spark/spec.json $PARITY_CONFIG_FILE_CHAIN 30 | COPY config/spark/bootnodes.txt $PARITY_CONFIG_FILE_BOOTNODES 31 | COPY config/spark/config.toml $PARITY_CONFIG_FILE_TEMPLATE 32 | 33 | ### Wrapper script for Parity. 34 | COPY scripts/parity_wrapper_spark.sh $PARITY_WRAPPER_SCRIPT 35 | RUN chmod +x $PARITY_WRAPPER_SCRIPT 36 | 37 | ### Shorthand links 38 | RUN ln -s $PARITY_HOME_DIR /config && ln -s $PARITY_DATA_DIR /data 39 | 40 | # Start 41 | ENTRYPOINT ["/home/parity/parity_wrapper.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fuse Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuse Network 2 | 3 | - [Fuse Network](#fuse-network) 4 | - [General](#general) 5 | - [Clone Repository](#clone-repository) 6 | - [Install Dependencies](#install-dependencies) 7 | - [Run Unit Tests](#run-unit-tests) 8 | - [Contracts](#contracts) 9 | - [Compile](#compile) 10 | - [ABIs](#abis) 11 | - [Flatten](#flatten) 12 | - [Deploy](#deploy) 13 | - [Run Local Node](#run-local-node) 14 | - [Pre-Requisites](#pre-requisites) 15 | - [Hardware](#hardware) 16 | - [Networking](#networking) 17 | - [Using Quickstart](#using-quickstart) 18 | - [Nethermind](#nethermind) 19 | 20 | ## General 21 | 22 | ### Clone Repository 23 | 24 | ``` 25 | $ git clone https://github.com/fuseio/fuse-network.git ~/Dev/fuse-network 26 | ``` 27 | 28 | ### Install Dependencies 29 | 30 | ``` 31 | $ yarn install 32 | ``` 33 | 34 | ### Run Unit Tests 35 | 36 | ``` 37 | $ yarn test 38 | ``` 39 | 40 | ## Contracts 41 | 42 | ### Compile 43 | 44 | ``` 45 | $ yarn compile 46 | ``` 47 | 48 | ### Flatten 49 | 50 | ``` 51 | $ yarn flatten 52 | ``` 53 | 54 | ### Deploy 55 | 56 | Make sure `NETWORK_NAME` is defined in [`hardhat.config`](https://github.com/fuseio/fuse-network/blob/master/hardhat.config.js) 57 | 58 | Make sure you've created an `.env` using the template [`env.example`](https://github.com/fuseio/fuse-network/blob/master/.env.example) 59 | 60 | Run: 61 | 62 | ``` 63 | npx hardhat run scripts/ --network 64 | ``` 65 | 66 | ## Run Local Node 67 | 68 | Please make sure you have access to a continuously running machine, if you like to participate as a network validator. 69 | 70 | ### Pre-Requisites 71 | 72 | A complete [Docker](https://docs.docker.com) environment is needed to be installed on your system, as well as [Docker-Compose](https://docs.docker.com/compose/) 73 | 74 | Make sure that your user is added to the `docker` user-group on _Unix_ systems, if you can't access root permissions to run containers. 75 | 76 | ### Hardware 77 | 78 | > Note: 79 | > 80 | > - Specified for [AWS](https://console.aws.amazon.com), but similar on other providers as well 81 | > - Depending on your node purpose (shared RPC endpoint with hight load) system requirements could be different 82 | > - `-` in each column means that role has the same parameters like previous 83 | 84 | | Node role | Bootnode | Node | Validator | Archival | 85 | | ------------------ | --------------------------------------------------------- | ---- | --------- | -------------------------------------------------------- | 86 | | Operating system | Ubuntu (18.04 and higher) or any other Linux distribution | - | - | - | 87 | | Runtime | On - Premise, Docker, Kubernetes | - | - | - | 88 | | Compute | Minimal: 2vCPU, 8GB RAM; Recommended: 4vCPU, 16GB RAM | - | - | | 89 | | Disk type and size | 150GB SSD; Read/Write IOPS - 5000, Throughput - 125 MB/s | - | - | 2TB SSD; Read / Write IOPS - 5000, Throughput - 125 MB/s | 90 | 91 | ### Networking 92 | 93 | | Name | Port | Protocol | Action | Description | Notes | 94 | | ---- | ----- | -------- | ------------ | ------------------------------------------------------------- | ------------------------------ | 95 | | P2P | 30303 | TCP | Allow | Port used for communication with the network peers | Should be openned for everyone | 96 | | P2P | 30303 | UDP | Allow | - | - | 97 | | RPC | 8545 | TCP | Allow / Deny | Port used for communication with the node with HTTP JSON RPC | Please, see notes below | 98 | | WS | 8546 | TCP | Allow / Deny | Port used for communication with the node with HTTP WebSocket | Please, see notes below | 99 | 100 | > Note: 101 | > 102 | > - Outbound traffic should be opened for all IP addresses 103 | > - For Bootnode node role not necessary to open RPC and WebSocket ports, only P2P are required; for Validator node role WebSocket and RPC ports should be opened on `localhost` and granted restricted access through IP whitelists 104 | 105 | ### Snapshot 106 | 107 | To speed up node sync there are the snapshot download links. 108 | 109 | | Endpoint | Network | Type | Direct link (latest) | 110 | | ------------------------------ | ------- | --------- | --------------------------------------------------- | 111 | | https://snapshot.fuse.io | Fuse | FastSync | https://snapshot.fuse.io/openethereum/database.zip | 112 | 113 | The archive file contains `database` folder, blockchain ledger, with n blocks depending on the snapshot date. 114 | 115 | > Note: Fuse snapshot compatible with OpenEthereum v3.3.5, Docker image `fusenet/node:2.0.2`. 116 | 117 | ### Using Quickstart 118 | 119 | #### Nethermind 120 | 121 | Since **08.2022** Fuse is moving from OE client to [Nethermind](https://nethermind.io). To bootstrap your own Fuse (Spark) node on Nethermind client you could use [quickstart.sh](./nethermind/quickstart.sh) script. 122 | 123 | ```bash 124 | # Download 125 | wget -O quickstart.sh https://raw.githubusercontent.com/fuseio/fuse-network/master/nethermind/quickstart.sh 126 | 127 | # Gain needed permissions 128 | chmod 755 quickstart.sh 129 | 130 | # Run 131 | ./quickstart.sh -r [node_role] -n [network_name] -k [node_key] 132 | ``` 133 | 134 | Full example provided [here](./nethermind/README.md). 135 | 136 | --- 137 | -------------------------------------------------------------------------------- /Version: -------------------------------------------------------------------------------- 1 | DOCKER_IMAGE_ORACLE_VERSION="3.0.0" 2 | DOCKER_IMAGE_FUSE_APP_VERSION="2.0.1" 3 | DOCKER_IMAGE_FUSE_PARITY_VERSION="2.0.2" 4 | DOCKER_IMAGE_NET_STATS_VERSION="2.0.1" 5 | DOCKER_IMAGE_NM_CLIENT="nethermind-1.28.0-v6.0.3" 6 | SPARK_DOCKER_IMAGE_NM_CLIENT="nethermind-1.28.0-v6.0.3-alpha" -------------------------------------------------------------------------------- /abis/BlockReward_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "DECIMALS", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "isInitialized", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "bool" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | }, 30 | { 31 | "constant": true, 32 | "inputs": [], 33 | "name": "getRewardedOnCycle", 34 | "outputs": [ 35 | { 36 | "name": "", 37 | "type": "uint256" 38 | } 39 | ], 40 | "payable": false, 41 | "stateMutability": "view", 42 | "type": "function" 43 | }, 44 | { 45 | "constant": true, 46 | "inputs": [], 47 | "name": "shouldEmitRewardedOnCycle", 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "bool" 52 | } 53 | ], 54 | "payable": false, 55 | "stateMutability": "view", 56 | "type": "function" 57 | }, 58 | { 59 | "constant": true, 60 | "inputs": [], 61 | "name": "getBlockRewardAmount", 62 | "outputs": [ 63 | { 64 | "name": "", 65 | "type": "uint256" 66 | } 67 | ], 68 | "payable": false, 69 | "stateMutability": "view", 70 | "type": "function" 71 | }, 72 | { 73 | "constant": true, 74 | "inputs": [ 75 | { 76 | "name": "_validator", 77 | "type": "address" 78 | } 79 | ], 80 | "name": "getBlockRewardAmountPerValidator", 81 | "outputs": [ 82 | { 83 | "name": "", 84 | "type": "uint256" 85 | } 86 | ], 87 | "payable": false, 88 | "stateMutability": "view", 89 | "type": "function" 90 | }, 91 | { 92 | "constant": true, 93 | "inputs": [], 94 | "name": "INFLATION", 95 | "outputs": [ 96 | { 97 | "name": "", 98 | "type": "uint256" 99 | } 100 | ], 101 | "payable": false, 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "constant": true, 107 | "inputs": [], 108 | "name": "getBlocksPerYear", 109 | "outputs": [ 110 | { 111 | "name": "", 112 | "type": "uint256" 113 | } 114 | ], 115 | "payable": false, 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, 119 | { 120 | "constant": false, 121 | "inputs": [], 122 | "name": "onCycleEnd", 123 | "outputs": [], 124 | "payable": false, 125 | "stateMutability": "nonpayable", 126 | "type": "function" 127 | }, 128 | { 129 | "constant": true, 130 | "inputs": [], 131 | "name": "getTotalSupply", 132 | "outputs": [ 133 | { 134 | "name": "", 135 | "type": "uint256" 136 | } 137 | ], 138 | "payable": false, 139 | "stateMutability": "view", 140 | "type": "function" 141 | }, 142 | { 143 | "constant": true, 144 | "inputs": [], 145 | "name": "BLOCKS_PER_YEAR", 146 | "outputs": [ 147 | { 148 | "name": "", 149 | "type": "uint256" 150 | } 151 | ], 152 | "payable": false, 153 | "stateMutability": "view", 154 | "type": "function" 155 | }, 156 | { 157 | "constant": false, 158 | "inputs": [], 159 | "name": "emitRewardedOnCycle", 160 | "outputs": [], 161 | "payable": false, 162 | "stateMutability": "nonpayable", 163 | "type": "function" 164 | }, 165 | { 166 | "constant": true, 167 | "inputs": [], 168 | "name": "getInflation", 169 | "outputs": [ 170 | { 171 | "name": "", 172 | "type": "uint256" 173 | } 174 | ], 175 | "payable": false, 176 | "stateMutability": "view", 177 | "type": "function" 178 | }, 179 | { 180 | "constant": true, 181 | "inputs": [], 182 | "name": "getProxyStorage", 183 | "outputs": [ 184 | { 185 | "name": "", 186 | "type": "address" 187 | } 188 | ], 189 | "payable": false, 190 | "stateMutability": "view", 191 | "type": "function" 192 | }, 193 | { 194 | "constant": false, 195 | "inputs": [ 196 | { 197 | "name": "benefactors", 198 | "type": "address[]" 199 | }, 200 | { 201 | "name": "kind", 202 | "type": "uint16[]" 203 | } 204 | ], 205 | "name": "reward", 206 | "outputs": [ 207 | { 208 | "name": "", 209 | "type": "address[]" 210 | }, 211 | { 212 | "name": "", 213 | "type": "uint256[]" 214 | } 215 | ], 216 | "payable": false, 217 | "stateMutability": "nonpayable", 218 | "type": "function" 219 | }, 220 | { 221 | "constant": false, 222 | "inputs": [ 223 | { 224 | "name": "_supply", 225 | "type": "uint256" 226 | } 227 | ], 228 | "name": "initialize", 229 | "outputs": [], 230 | "payable": false, 231 | "stateMutability": "nonpayable", 232 | "type": "function" 233 | }, 234 | { 235 | "anonymous": false, 236 | "inputs": [ 237 | { 238 | "indexed": false, 239 | "name": "receivers", 240 | "type": "address[]" 241 | }, 242 | { 243 | "indexed": false, 244 | "name": "rewards", 245 | "type": "uint256[]" 246 | } 247 | ], 248 | "name": "Rewarded", 249 | "type": "event" 250 | }, 251 | { 252 | "anonymous": false, 253 | "inputs": [ 254 | { 255 | "indexed": false, 256 | "name": "amount", 257 | "type": "uint256" 258 | } 259 | ], 260 | "name": "RewardedOnCycle", 261 | "type": "event" 262 | } 263 | ] -------------------------------------------------------------------------------- /abis/EternalStorageProxy_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "isInitialized", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "bool" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "inputs": [ 18 | { 19 | "name": "_proxyStorage", 20 | "type": "address" 21 | }, 22 | { 23 | "name": "_implementation", 24 | "type": "address" 25 | } 26 | ], 27 | "payable": false, 28 | "stateMutability": "nonpayable", 29 | "type": "constructor" 30 | }, 31 | { 32 | "payable": true, 33 | "stateMutability": "payable", 34 | "type": "fallback" 35 | }, 36 | { 37 | "anonymous": false, 38 | "inputs": [ 39 | { 40 | "indexed": false, 41 | "name": "version", 42 | "type": "uint256" 43 | }, 44 | { 45 | "indexed": true, 46 | "name": "implementation", 47 | "type": "address" 48 | } 49 | ], 50 | "name": "Upgraded", 51 | "type": "event" 52 | }, 53 | { 54 | "anonymous": false, 55 | "inputs": [ 56 | { 57 | "indexed": true, 58 | "name": "previousOwner", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "OwnershipRenounced", 63 | "type": "event" 64 | }, 65 | { 66 | "anonymous": false, 67 | "inputs": [ 68 | { 69 | "indexed": true, 70 | "name": "previousOwner", 71 | "type": "address" 72 | }, 73 | { 74 | "indexed": true, 75 | "name": "newOwner", 76 | "type": "address" 77 | } 78 | ], 79 | "name": "OwnershipTransferred", 80 | "type": "event" 81 | }, 82 | { 83 | "constant": false, 84 | "inputs": [ 85 | { 86 | "name": "_newImplementation", 87 | "type": "address" 88 | } 89 | ], 90 | "name": "upgradeTo", 91 | "outputs": [ 92 | { 93 | "name": "", 94 | "type": "bool" 95 | } 96 | ], 97 | "payable": false, 98 | "stateMutability": "nonpayable", 99 | "type": "function" 100 | }, 101 | { 102 | "constant": false, 103 | "inputs": [], 104 | "name": "renounceOwnership", 105 | "outputs": [], 106 | "payable": false, 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | }, 110 | { 111 | "constant": false, 112 | "inputs": [ 113 | { 114 | "name": "_newOwner", 115 | "type": "address" 116 | } 117 | ], 118 | "name": "transferOwnership", 119 | "outputs": [], 120 | "payable": false, 121 | "stateMutability": "nonpayable", 122 | "type": "function" 123 | }, 124 | { 125 | "constant": true, 126 | "inputs": [], 127 | "name": "getOwner", 128 | "outputs": [ 129 | { 130 | "name": "", 131 | "type": "address" 132 | } 133 | ], 134 | "payable": false, 135 | "stateMutability": "view", 136 | "type": "function" 137 | }, 138 | { 139 | "constant": true, 140 | "inputs": [], 141 | "name": "getVersion", 142 | "outputs": [ 143 | { 144 | "name": "", 145 | "type": "uint256" 146 | } 147 | ], 148 | "payable": false, 149 | "stateMutability": "view", 150 | "type": "function" 151 | }, 152 | { 153 | "constant": true, 154 | "inputs": [], 155 | "name": "getImplementation", 156 | "outputs": [ 157 | { 158 | "name": "", 159 | "type": "address" 160 | } 161 | ], 162 | "payable": false, 163 | "stateMutability": "view", 164 | "type": "function" 165 | }, 166 | { 167 | "constant": true, 168 | "inputs": [], 169 | "name": "getProxyStorage", 170 | "outputs": [ 171 | { 172 | "name": "", 173 | "type": "address" 174 | } 175 | ], 176 | "payable": false, 177 | "stateMutability": "view", 178 | "type": "function" 179 | } 180 | ] 181 | -------------------------------------------------------------------------------- /abis/ProxyStorage_abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "isInitialized", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "bool" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "anonymous": false, 18 | "inputs": [ 19 | { 20 | "indexed": false, 21 | "name": "consensus", 22 | "type": "address" 23 | }, 24 | { 25 | "indexed": false, 26 | "name": "blockReward", 27 | "type": "address" 28 | }, 29 | { 30 | "indexed": false, 31 | "name": "voting", 32 | "type": "address" 33 | } 34 | ], 35 | "name": "ProxyInitialized", 36 | "type": "event" 37 | }, 38 | { 39 | "anonymous": false, 40 | "inputs": [ 41 | { 42 | "indexed": false, 43 | "name": "contractType", 44 | "type": "uint256" 45 | }, 46 | { 47 | "indexed": false, 48 | "name": "contractAddress", 49 | "type": "address" 50 | } 51 | ], 52 | "name": "AddressSet", 53 | "type": "event" 54 | }, 55 | { 56 | "constant": false, 57 | "inputs": [ 58 | { 59 | "name": "_consensus", 60 | "type": "address" 61 | } 62 | ], 63 | "name": "initialize", 64 | "outputs": [], 65 | "payable": false, 66 | "stateMutability": "nonpayable", 67 | "type": "function" 68 | }, 69 | { 70 | "constant": false, 71 | "inputs": [ 72 | { 73 | "name": "_blockReward", 74 | "type": "address" 75 | }, 76 | { 77 | "name": "_voting", 78 | "type": "address" 79 | } 80 | ], 81 | "name": "initializeAddresses", 82 | "outputs": [], 83 | "payable": false, 84 | "stateMutability": "nonpayable", 85 | "type": "function" 86 | }, 87 | { 88 | "constant": false, 89 | "inputs": [ 90 | { 91 | "name": "_contractType", 92 | "type": "uint256" 93 | }, 94 | { 95 | "name": "_contractAddress", 96 | "type": "address" 97 | } 98 | ], 99 | "name": "setContractAddress", 100 | "outputs": [ 101 | { 102 | "name": "", 103 | "type": "bool" 104 | } 105 | ], 106 | "payable": false, 107 | "stateMutability": "nonpayable", 108 | "type": "function" 109 | }, 110 | { 111 | "constant": true, 112 | "inputs": [ 113 | { 114 | "name": "_contractType", 115 | "type": "uint256" 116 | } 117 | ], 118 | "name": "isValidContractType", 119 | "outputs": [ 120 | { 121 | "name": "", 122 | "type": "bool" 123 | } 124 | ], 125 | "payable": false, 126 | "stateMutability": "pure", 127 | "type": "function" 128 | }, 129 | { 130 | "constant": true, 131 | "inputs": [], 132 | "name": "getConsensus", 133 | "outputs": [ 134 | { 135 | "name": "", 136 | "type": "address" 137 | } 138 | ], 139 | "payable": false, 140 | "stateMutability": "view", 141 | "type": "function" 142 | }, 143 | { 144 | "constant": true, 145 | "inputs": [], 146 | "name": "getBlockReward", 147 | "outputs": [ 148 | { 149 | "name": "", 150 | "type": "address" 151 | } 152 | ], 153 | "payable": false, 154 | "stateMutability": "view", 155 | "type": "function" 156 | }, 157 | { 158 | "constant": true, 159 | "inputs": [], 160 | "name": "getVoting", 161 | "outputs": [ 162 | { 163 | "name": "", 164 | "type": "address" 165 | } 166 | ], 167 | "payable": false, 168 | "stateMutability": "view", 169 | "type": "function" 170 | } 171 | ] 172 | -------------------------------------------------------------------------------- /app/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | node_modules -------------------------------------------------------------------------------- /app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | ENV POLLING_INTERVAL=5000 4 | ENV LOG_LEVEL=debug 5 | ENV RPC=https://rpc.fuse.io 6 | ENV CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 7 | ENV BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B 8 | ENV MIN_GAS_PRICE=10000000000 9 | 10 | COPY ./ ./ 11 | RUN npm install --only=prod 12 | 13 | CMD ["node", "index.js"] -------------------------------------------------------------------------------- /app/Dockerfile.spark: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | ENV POLLING_INTERVAL=5000 4 | ENV LOG_LEVEL=debug 5 | ENV RPC=https://rpc.fusespark.io 6 | ENV CONSENSUS_ADDRESS=0x8C682051D70301A0ca913Ce0A0e71539702E1122 7 | ENV BLOCK_REWARD_ADDRESS=0xEa2151b6095CB76ECc57A57DE166728dd9b53Ed9 8 | ENV MIN_GAS_PRICE=10000000000 9 | 10 | COPY ./ ./ 11 | RUN npm install --only=prod 12 | 13 | CMD ["node", "index.js"] 14 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # Fuse Network Validators App 2 | 3 | Network validators, as part of their validating responsibilities, need to run this app along side the Parity node. 4 | 5 | This app is responsible for calling the `emitInitiateChange` function on the `Consensus` contract. 6 | 7 | The function is responsible for emitting the `InitiateChange` event [described in Parity Wiki](https://wiki.parity.io/Validator-Set.html#non-reporting-contract). 8 | After this function is called successfully the validator set changes to a new one. 9 | 10 | This app is also responsible for calling the `emitRewardedOnCycle` function on the `BlockReward` contract. 11 | 12 | All the validators call those functions and only the first call is successful, but there's no loss of gas because they're called using a zero-gas transactions. 13 | 14 | When running the [quickstart script](https://github.com/fuseio/fuse-network/blob/master#quickstart) as valiadtor, this app is run automatically. 15 | 16 | It can be started manually as well: 17 | ``` 18 | $ docker run --detach --name fuseapp --volume /home/fuse/fusenet/config:/config --restart=always fusenet/validator-app 19 | ``` 20 | 21 | Note that `/home/fuse/fusenet/config` is the folder where the key file and `pass.pwd` of the validator account should be placed. This is the default location for the quickstart script so there shouldn't be any problems there. -------------------------------------------------------------------------------- /app/VERSION.FUSE.txt: -------------------------------------------------------------------------------- 1 | 2.0.1 -------------------------------------------------------------------------------- /app/VERSION.SPARK.txt: -------------------------------------------------------------------------------- 1 | 2.0.3 -------------------------------------------------------------------------------- /app/abi/blockReward.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "DECIMALS", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "isInitialized", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "bool" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | }, 30 | { 31 | "constant": true, 32 | "inputs": [], 33 | "name": "INFLATION", 34 | "outputs": [ 35 | { 36 | "name": "", 37 | "type": "uint256" 38 | } 39 | ], 40 | "payable": false, 41 | "stateMutability": "view", 42 | "type": "function" 43 | }, 44 | { 45 | "constant": true, 46 | "inputs": [], 47 | "name": "BLOCKS_PER_YEAR", 48 | "outputs": [ 49 | { 50 | "name": "", 51 | "type": "uint256" 52 | } 53 | ], 54 | "payable": false, 55 | "stateMutability": "view", 56 | "type": "function" 57 | }, 58 | { 59 | "anonymous": false, 60 | "inputs": [ 61 | { 62 | "indexed": false, 63 | "name": "receivers", 64 | "type": "address[]" 65 | }, 66 | { 67 | "indexed": false, 68 | "name": "rewards", 69 | "type": "uint256[]" 70 | } 71 | ], 72 | "name": "Rewarded", 73 | "type": "event" 74 | }, 75 | { 76 | "anonymous": false, 77 | "inputs": [ 78 | { 79 | "indexed": false, 80 | "name": "amount", 81 | "type": "uint256" 82 | } 83 | ], 84 | "name": "RewardedOnCycle", 85 | "type": "event" 86 | }, 87 | { 88 | "constant": false, 89 | "inputs": [ 90 | { 91 | "name": "_supply", 92 | "type": "uint256" 93 | } 94 | ], 95 | "name": "initialize", 96 | "outputs": [], 97 | "payable": false, 98 | "stateMutability": "nonpayable", 99 | "type": "function" 100 | }, 101 | { 102 | "constant": false, 103 | "inputs": [ 104 | { 105 | "name": "benefactors", 106 | "type": "address[]" 107 | }, 108 | { 109 | "name": "kind", 110 | "type": "uint16[]" 111 | } 112 | ], 113 | "name": "reward", 114 | "outputs": [ 115 | { 116 | "name": "", 117 | "type": "address[]" 118 | }, 119 | { 120 | "name": "", 121 | "type": "uint256[]" 122 | } 123 | ], 124 | "payable": false, 125 | "stateMutability": "nonpayable", 126 | "type": "function" 127 | }, 128 | { 129 | "constant": false, 130 | "inputs": [], 131 | "name": "onCycleEnd", 132 | "outputs": [], 133 | "payable": false, 134 | "stateMutability": "nonpayable", 135 | "type": "function" 136 | }, 137 | { 138 | "constant": false, 139 | "inputs": [], 140 | "name": "emitRewardedOnCycle", 141 | "outputs": [], 142 | "payable": false, 143 | "stateMutability": "nonpayable", 144 | "type": "function" 145 | }, 146 | { 147 | "constant": true, 148 | "inputs": [], 149 | "name": "getTotalSupply", 150 | "outputs": [ 151 | { 152 | "name": "", 153 | "type": "uint256" 154 | } 155 | ], 156 | "payable": false, 157 | "stateMutability": "view", 158 | "type": "function" 159 | }, 160 | { 161 | "constant": true, 162 | "inputs": [], 163 | "name": "getRewardedOnCycle", 164 | "outputs": [ 165 | { 166 | "name": "", 167 | "type": "uint256" 168 | } 169 | ], 170 | "payable": false, 171 | "stateMutability": "view", 172 | "type": "function" 173 | }, 174 | { 175 | "constant": true, 176 | "inputs": [], 177 | "name": "getInflation", 178 | "outputs": [ 179 | { 180 | "name": "", 181 | "type": "uint256" 182 | } 183 | ], 184 | "payable": false, 185 | "stateMutability": "pure", 186 | "type": "function" 187 | }, 188 | { 189 | "constant": true, 190 | "inputs": [], 191 | "name": "getBlocksPerYear", 192 | "outputs": [ 193 | { 194 | "name": "", 195 | "type": "uint256" 196 | } 197 | ], 198 | "payable": false, 199 | "stateMutability": "pure", 200 | "type": "function" 201 | }, 202 | { 203 | "constant": true, 204 | "inputs": [], 205 | "name": "getBlockRewardAmount", 206 | "outputs": [ 207 | { 208 | "name": "", 209 | "type": "uint256" 210 | } 211 | ], 212 | "payable": false, 213 | "stateMutability": "view", 214 | "type": "function" 215 | }, 216 | { 217 | "constant": true, 218 | "inputs": [], 219 | "name": "getProxyStorage", 220 | "outputs": [ 221 | { 222 | "name": "", 223 | "type": "address" 224 | } 225 | ], 226 | "payable": false, 227 | "stateMutability": "view", 228 | "type": "function" 229 | }, 230 | { 231 | "constant": true, 232 | "inputs": [], 233 | "name": "shouldEmitRewardedOnCycle", 234 | "outputs": [ 235 | { 236 | "name": "", 237 | "type": "bool" 238 | } 239 | ], 240 | "payable": false, 241 | "stateMutability": "view", 242 | "type": "function" 243 | } 244 | ] -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const cwd = process.cwd() 3 | const logger = require('pino')({ level: process.env.LOG_LEVEL || 'info', prettyPrint: { translateTime: true } }) 4 | const fs = require('fs') 5 | const HDWalletProvider = require('@truffle/hdwallet-provider') 6 | const EthWallet = require('ethereumjs-wallet') 7 | const Web3 = require('web3') 8 | 9 | const configDir = path.join(cwd, process.env.CONFIG_DIR || 'config/') 10 | 11 | let web3 12 | let walletProvider 13 | let account 14 | let consensus, blockReward 15 | 16 | function initWalletProvider() { 17 | logger.info(`initWalletProvider`) 18 | let keystoreDir = path.join(configDir, 'keys/FuseNetwork') 19 | let keystore 20 | fs.readdirSync(keystoreDir).forEach(file => { 21 | if (file.startsWith('UTC')) { 22 | keystore = fs.readFileSync(path.join(keystoreDir, file)).toString() 23 | } 24 | }) 25 | let password = fs.readFileSync(path.join(configDir, 'pass.pwd')).toString().trim() 26 | let wallet = EthWallet.fromV3(keystore, password) 27 | let pkey = wallet.getPrivateKeyString() 28 | walletProvider = new HDWalletProvider({ 29 | privateKeys: [pkey], 30 | providerOrUrl: process.env.RPC, 31 | pollingInterval: 0}) 32 | if (!walletProvider) { 33 | throw new Error(`Could not set walletProvider for unknown reason`) 34 | } else { 35 | account = wallet.getAddressString() 36 | logger.info(`account: ${account}`) 37 | web3 = new Web3(walletProvider) 38 | } 39 | } 40 | 41 | async function getNonce() { 42 | try { 43 | logger.debug(`getNonce for ${account}`) 44 | const transactionCount = await web3.eth.getTransactionCount(account) 45 | logger.debug(`transactionCount for ${account} is ${transactionCount}`) 46 | return transactionCount 47 | } catch (e) { 48 | throw new Error(`Could not get nonce`) 49 | } 50 | } 51 | 52 | async function getGasPrice() { 53 | try { 54 | logger.debug(`getGasPrice for ${account}`) 55 | const gasPrice = await web3.eth.getGasPrice() 56 | logger.debug(`current GasPrice is ${gasPrice}`) 57 | return Math.max(process.env.MIN_GAS_PRICE,gasPrice) 58 | } catch (e) { 59 | throw new Error(`Could not get gasPrice`) 60 | } 61 | } 62 | 63 | function initConsensusContract() { 64 | logger.info(`initConsensusContract`, process.env.CONSENSUS_ADDRESS) 65 | consensus = new web3.eth.Contract(require(path.join(cwd, 'abi/consensus')), process.env.CONSENSUS_ADDRESS) 66 | } 67 | 68 | function initBlockRewardContract() { 69 | logger.info(`initBlockRewardContract`, process.env.BLOCK_REWARD_ADDRESS) 70 | blockReward = new web3.eth.Contract(require(path.join(cwd, 'abi/blockReward')), process.env.BLOCK_REWARD_ADDRESS) 71 | } 72 | 73 | function emitInitiateChange() { 74 | return new Promise(async (resolve, reject) => { 75 | try { 76 | logger.info(`emitInitiateChange`) 77 | let currentBlockNumber = await web3.eth.getBlockNumber() 78 | let currentCycleEndBlock = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() 79 | let shouldEmitInitiateChange = await consensus.methods.shouldEmitInitiateChange.call() 80 | logger.info(`block #${currentBlockNumber}\n\tcurrentCycleEndBlock: ${currentCycleEndBlock}\n\tshouldEmitInitiateChange: ${shouldEmitInitiateChange}`) 81 | if (!shouldEmitInitiateChange) { 82 | return resolve() 83 | } 84 | logger.info(`${account} sending emitInitiateChange transaction`) 85 | let nonce = await getNonce() 86 | let gasPrice = await getGasPrice() 87 | consensus.methods.emitInitiateChange().send({ from: account, gas: process.env.GAS || 1000000, gasPrice: process.env.GAS_PRICE || gasPrice, nonce: nonce }) 88 | .on('transactionHash', hash => { 89 | logger.info(`transactionHash: ${hash}`) 90 | }) 91 | .on('confirmation', (confirmationNumber, receipt) => { 92 | if (confirmationNumber == 1) { 93 | logger.debug(`receipt: ${JSON.stringify(receipt)}`) 94 | } 95 | resolve() 96 | }) 97 | .on('error', error => { 98 | logger.error(error); resolve() 99 | }) 100 | } catch (e) { 101 | reject(e) 102 | } 103 | }) 104 | } 105 | 106 | function emitRewardedOnCycle() { 107 | return new Promise(async (resolve, reject) => { 108 | try { 109 | logger.info(`emitRewardedOnCycle`) 110 | let currentBlockNumber = await web3.eth.getBlockNumber() 111 | let currentCycleEndBlock = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() 112 | let shouldEmitRewardedOnCycle = await blockReward.methods.shouldEmitRewardedOnCycle.call() 113 | logger.info(`block #${currentBlockNumber}\n\tcurrentCycleEndBlock: ${currentCycleEndBlock}\n\tshouldEmitRewardedOnCycle: ${shouldEmitRewardedOnCycle}`) 114 | if (!shouldEmitRewardedOnCycle) { 115 | return resolve() 116 | } 117 | logger.info(`${account} sending emitRewardedOnCycle transaction`) 118 | let nonce = await getNonce() 119 | let gasPrice = await getGasPrice() 120 | blockReward.methods.emitRewardedOnCycle().send({ from: account, gas: process.env.GAS || 1000000, gasPrice: process.env.GAS_PRICE || gasPrice, nonce: nonce }) 121 | .on('transactionHash', hash => { 122 | logger.info(`transactionHash: ${hash}`) 123 | }) 124 | .on('confirmation', (confirmationNumber, receipt) => { 125 | if (confirmationNumber == 1) { 126 | logger.debug(`receipt: ${JSON.stringify(receipt)}`) 127 | } 128 | resolve() 129 | }) 130 | .on('error', error => { 131 | logger.error(error); resolve() 132 | }) 133 | } catch (e) { 134 | reject(e) 135 | } 136 | }) 137 | } 138 | 139 | async function runMain() { 140 | try { 141 | logger.info(`runMain`) 142 | if (!walletProvider) { 143 | initWalletProvider() 144 | } 145 | if (!consensus) { 146 | initConsensusContract() 147 | } 148 | if (!blockReward) { 149 | initBlockRewardContract() 150 | } 151 | const isValidator = await consensus.methods.isValidator(web3.utils.toChecksumAddress(account)).call() 152 | if (!isValidator) { 153 | logger.warn(`${account} is not a validator, skipping`) 154 | return 155 | } 156 | await emitInitiateChange() 157 | await emitRewardedOnCycle() 158 | } catch (e) { 159 | logger.error(e) 160 | process.exit(1) 161 | } 162 | 163 | setTimeout(() => { 164 | runMain() 165 | }, process.env.POLLING_INTERVAL || 2500) 166 | } 167 | 168 | runMain() -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuse-network-app", 3 | "version": "0.1.0", 4 | "description": "App for Fuse Network", 5 | "main": "", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "LiorRabin", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@truffle/hdwallet-provider": "^2.0.16", 13 | "dotenv": "^8.0.0", 14 | "ethereumjs-wallet": "^0.6.5", 15 | "pino": "^5.12.6", 16 | "pino-pretty": "^3.2.0", 17 | "web3": "1.0.0-beta.55" 18 | } 19 | } -------------------------------------------------------------------------------- /app/scripts/run.sh: -------------------------------------------------------------------------------- 1 | CONFIG_DIR=../../../io.parity.ethereum/MasterOfCeremony/fusenet/config \ 2 | LOG_LEVEL=debug \ 3 | POLLING_INTERVAL=5000 \ 4 | RPC=http://127.0.0.1:8545 \ 5 | CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 \ 6 | BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B \ 7 | node index.js -------------------------------------------------------------------------------- /audits/zokyo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fuseio/fuse-network/91c57a26880fdf97bd7eb9d0ee735be83e07a811/audits/zokyo.pdf -------------------------------------------------------------------------------- /commands/Version_legacy: -------------------------------------------------------------------------------- 1 | DOCKER_IMAGE_ORACLE_VERSION="3.0.0" 2 | DOCKER_IMAGE_FUSE_APP_VERSION="1.0.0" 3 | DOCKER_IMAGE_FUSE_PARITY_VERSION="1.0.0" 4 | DOCKER_IMAGE_NET_STATS_VERSION="1.0.0" -------------------------------------------------------------------------------- /commands/abi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -d abis ]; then 4 | rm -rf abis 5 | fi 6 | 7 | mkdir abis 8 | 9 | ./node_modules/node-jq/bin/jq '.abi' build/contracts/EternalStorageProxy.json > abis/EternalStorageProxy_abi.json 10 | ./node_modules/node-jq/bin/jq '.abi' build/contracts/Consensus.json > abis/Consensus_abi.json 11 | ./node_modules/node-jq/bin/jq '.abi' build/contracts/BlockReward.json > abis/BlockReward_abi.json 12 | ./node_modules/node-jq/bin/jq '.abi' build/contracts/ProxyStorage.json > abis/ProxyStorage_abi.json 13 | ./node_modules/node-jq/bin/jq '.abi' build/contracts/Voting.json > abis/Voting_abi.json 14 | -------------------------------------------------------------------------------- /commands/backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DATE=`date +%Y-%m-%d_%R` 4 | DATABASE_FOLDER="fusenet/database" 5 | BACKUP_FOLDER=fusenet_backup 6 | BACKUP_FILENAME="db_${DATE}" 7 | FILE="$BACKUP_FOLDER/$BACKUP_FILENAME.tar.gz" 8 | S3_BUCKET="s3://fusenet-backup" 9 | 10 | tar -czvf $FILE $DATABASE_FOLDER 11 | 12 | aws s3 cp $FILE $S3_BUCKET 13 | 14 | rm -rf $FILE 15 | -------------------------------------------------------------------------------- /commands/buildContainers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | DOCKER_IMAGE_APP="fusenet/validator-app" 5 | DOCKER_IMAGE_PARITY="fusenet/node" 6 | DOCKER_IMAGE_APP_SPARK="fusenet/spark-validator-app" 7 | DOCKER_IMAGE_PARITY_SPARK="fusenet/spark-node" 8 | 9 | PLATFORM="" 10 | PLATFORM_VARIENT="" 11 | 12 | function displayErrorAndExit { 13 | local arg1=$1 14 | if [[ $arg1 != "" ]]; 15 | then 16 | echo "$(tput setaf 1)ERROR: $arg1$(tput sgr 0)" 17 | else 18 | echo "${FUNCNAME[0]} No Argument supplied" 19 | fi 20 | 21 | exit 1 22 | } 23 | 24 | function setPlatform { 25 | case "$(uname -s)" in 26 | 27 | Darwin) 28 | echo -e '\nRunning on Mac OS X' 29 | PLATFORM="MAC" 30 | ;; 31 | 32 | Linux) 33 | echo -e '\nRunning on Linux' 34 | PLATFORM="LINUX" 35 | PLATFORM_VARIENT=$(lsb_release -si) 36 | echo "Linux varient $PLATFORM_VARIENT" 37 | ;; 38 | 39 | CYGWIN*|MINGW32*|MSYS*|MINGW*) 40 | echo -e '\nRunning on Windows' 41 | PLATFORM="WINDOWS" 42 | ;; 43 | *) 44 | displayErrorAndExit "UNKNOWN OS exiting the script here" 45 | ;; 46 | esac 47 | } 48 | 49 | function readVersion { 50 | export $(grep -v '^#' "../Version" | xargs) 51 | } 52 | 53 | function buildFuseApp { 54 | read -p "Please input your new Version number for FuseApp (currentVersion=$DOCKER_IMAGE_FUSE_APP_VERSION): " newVersion 55 | 56 | docker build -t "$DOCKER_IMAGE_APP" ../app 57 | docker tag "$DOCKER_IMAGE_APP" "$DOCKER_IMAGE_APP:$newVersion" 58 | docker push "$DOCKER_IMAGE_APP:$newVersion" 59 | 60 | sed -i "s/^DOCKER_IMAGE_FUSE_APP_VERSION.*/DOCKER_IMAGE_FUSE_APP_VERSION="\""${newVersion}"\""/" "../Version" 61 | } 62 | 63 | function buildFuseParity { 64 | read -p "Please input your new Version number for FuseParity (currentVersion=$DOCKER_IMAGE_FUSE_PARITY_VERSION): " newVersion 65 | 66 | docker build -t "$DOCKER_IMAGE_PARITY" ../ 67 | docker tag "$DOCKER_IMAGE_PARITY" "$DOCKER_IMAGE_PARITY:$newVersion" 68 | docker push "$DOCKER_IMAGE_PARITY:$newVersion" 69 | 70 | sed -i "s/^DOCKER_IMAGE_FUSE_PARITY_VERSION.*/DOCKER_IMAGE_FUSE_PARITY_VERSION="\""${newVersion}"\""/" "../Version" 71 | } 72 | 73 | function buildFuseAppSpark { 74 | read -p "Please input your new Version number for FuseApp (currentVersion=$DOCKER_IMAGE_FUSE_APP_VERSION): " newVersion 75 | 76 | docker build -t "$DOCKER_IMAGE_APP_SPARK" -f "../app/Dockerfile.spark" ../app 77 | docker tag "$DOCKER_IMAGE_APP_SPARK" "$DOCKER_IMAGE_APP_SPARK:$newVersion" 78 | docker push "$DOCKER_IMAGE_APP_SPARK:$newVersion" 79 | 80 | sed -i "s/^DOCKER_IMAGE_FUSE_APP_VERSION.*/DOCKER_IMAGE_FUSE_APP_VERSION="\""${newVersion}"\""/" "../Version" 81 | } 82 | 83 | function buildFuseParitySpark { 84 | read -p "Please input your new Version number for FuseParity (currentVersion=$DOCKER_IMAGE_FUSE_PARITY_VERSION): " newVersion 85 | 86 | docker build -t "$DOCKER_IMAGE_PARITY_SPARK" -f "../Dockerfile.spark" ../ 87 | docker tag "$DOCKER_IMAGE_PARITY_SPARK" "$DOCKER_IMAGE_PARITY_SPARK:$newVersion" 88 | docker push "$DOCKER_IMAGE_PARITY_SPARK:$newVersion" 89 | 90 | sed -i "s/^DOCKER_IMAGE_FUSE_PARITY_VERSION.*/DOCKER_IMAGE_FUSE_PARITY_VERSION="\""${newVersion}"\""/" "../Version" 91 | } 92 | 93 | function pushChanges { 94 | local appsChanged=$1 95 | dt=$(date '+%d_%m_%Y_%H_%M_%S') 96 | dt="_$dt" 97 | branchName="update_$appsChanged$dt" 98 | 99 | git branch -m "$branchName" 100 | git commit -m "$branchName" ../Version 101 | git push -u origin "$branchName" 102 | hub pull-request -m "$branchName" 103 | } 104 | 105 | function install_docker { 106 | echo -e "\nInstalling docker..." 107 | if [ $PLATFORM_VARIENT == "Ubuntu" ]; then 108 | apt-get update 109 | 110 | apt-get install -y \ 111 | apt-transport-https \ 112 | ca-certificates \ 113 | curl \ 114 | gnupg-agent \ 115 | software-properties-common 116 | 117 | curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | apt-key add - 118 | 119 | add-apt-repository \ 120 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 121 | $(lsb_release -cs) \ 122 | stable" 123 | 124 | apt-get update 125 | 126 | apt-get install -y docker-ce docker-ce-cli containerd.io 127 | elif [ $PLATFORM_VARIENT == "Debian" ]; then 128 | apt update 129 | 130 | apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common 131 | 132 | curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - 133 | 134 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" 135 | 136 | apt update 137 | apt-cache policy docker-ce 138 | apt install docker-ce docker-ce-cli containerd.io 139 | else 140 | displayErrorAndExit "UNKNOWN OS please install docker" 141 | fi 142 | } 143 | 144 | function install_hub { 145 | if [ $PLATFORM == "LINUX" ]; then 146 | if [ $PLATFORM_VARIENT == "Ubuntu" ]; then 147 | apt update 148 | apt install snapd 149 | snap install hub --classic 150 | elif [ $PLATFORM_VARIENT == "Debian" ]; then 151 | apt install hub 152 | else 153 | displayErrorAndExit "UNKNOWN OS please install hub manually" 154 | fi 155 | elif [ $PLATFORM == "MAC" ]; then 156 | brew install hub 157 | else 158 | displayErrorAndExit "UNKNOWN OS please install hub manually" 159 | fi 160 | } 161 | 162 | function installDeps { 163 | #assume git already installed.... 164 | install_docker 165 | install_hub 166 | 167 | docker login 168 | } 169 | 170 | setPlatform 171 | readVersion 172 | while true; do 173 | PS3='Please enter your choice: ' 174 | options=("Build Fuse APP container" "Build Fuse Parity container" "Build both" "Build Fuse APP container(SPARK)" "Build Fuse Parity container(SPARK)" "Build both(SPARK)" "First time configure" "Exit") 175 | select opt in "${options[@]}"; 176 | do 177 | case $opt in 178 | "${options[0]}") 179 | #Build Fuse APP container 180 | echo "building fuse APP (FUSENET)" 181 | buildFuseApp 182 | read -p "Do you want to push the new version file[Y/N]: " yn 183 | case $yn in 184 | [Y/y]* ) 185 | pushChanges "FuseApp"; break;; 186 | esac 187 | break 188 | ;; 189 | "${options[1]}") 190 | #Build Fuse Parity container 191 | echo "building Fuse Parity (FUSENET)" 192 | buildFuseParity 193 | read -p "Do you want to push the new version file[Y/N]: " yn 194 | case $yn in 195 | [Y/y]* ) 196 | pushChanges "FuseParity"; break;; 197 | esac 198 | break 199 | ;; 200 | "${options[2]}") 201 | #Build both 202 | echo "building fuse APP (FUSENET)" 203 | buildFuseApp 204 | echo "building Fuse Parity" 205 | buildFuseParity 206 | read -p "Do you want to push the new version file[Y/N]: " yn 207 | case $yn in 208 | [Y/y]* ) 209 | pushChanges "FuseAPP_And_FuseParity"; break;; 210 | esac 211 | break 212 | ;; 213 | "${options[3]}") 214 | #Build Fuse APP container spaark 215 | echo "building fuse APP (Spark)" 216 | buildFuseAppSpark 217 | read -p "Do you want to push the new version file[Y/N]: " yn 218 | case $yn in 219 | [Y/y]* ) 220 | pushChanges "FuseApp"; break;; 221 | esac 222 | break 223 | ;; 224 | "${options[4]}") 225 | #Build Fuse Parity container spark 226 | echo "building Fuse Parity (Spark)" 227 | buildFuseParitySpark 228 | read -p "Do you want to push the new version file[Y/N]: " yn 229 | case $yn in 230 | [Y/y]* ) 231 | pushChanges "FuseParity"; break;; 232 | esac 233 | break 234 | ;; 235 | "${options[5]}") 236 | #Build both 237 | echo "building fuse APP (Spark)" 238 | buildFuseAppSpark 239 | echo "building Fuse Parity (Spark)" 240 | buildFuseParitySpark 241 | read -p "Do you want to push the new version file[Y/N]: " yn 242 | case $yn in 243 | [Y/y]* ) 244 | pushChanges "FuseAPP_And_FuseParity"; break;; 245 | esac 246 | break 247 | ;; 248 | "${options[6]}") 249 | #Configure 250 | echo "Configure env" 251 | installDeps 252 | break 253 | ;; 254 | "${options[7]}") 255 | #Exit 256 | exit 0 257 | ;; 258 | *) echo "invalid option $REPLY";; 259 | esac 260 | done 261 | done -------------------------------------------------------------------------------- /commands/clean-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # in case "sudo" is needed 6 | PERMISSION_PREFIX="" 7 | 8 | # stop all processes 9 | $PERMISSION_PREFIX docker stop $($PERMISSION_PREFIX docker ps -aq) 10 | 11 | # remove all containers 12 | $PERMISSION_PREFIX docker rm $($PERMISSION_PREFIX docker ps -aq) 13 | 14 | # remove all images 15 | $PERMISSION_PREFIX docker rmi $($PERMISSION_PREFIX docker images -aq) 16 | 17 | # remove all stopped containers, all dangling images, all unused networks and all unused volumes 18 | $PERMISSION_PREFIX docker system prune --volumes -------------------------------------------------------------------------------- /commands/flatten.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -d flats ]; then 4 | rm -rf flats 5 | fi 6 | 7 | mkdir flats 8 | 9 | npx hardhat flatten contracts/eternal-storage/EternalStorageProxy.sol > flats/EternalStorageProxy_flat.sol 10 | npx hardhat flatten contracts/Consensus.sol > flats/Consensus_flat.sol 11 | npx hardhat flatten contracts/BlockReward.sol > flats/BlockReward_flat.sol 12 | npx hardhat flatten contracts/ProxyStorage.sol > flats/ProxyStorage_flat.sol 13 | npx hardhat flatten contracts/Voting.sol > flats/Voting_flat.sol 14 | -------------------------------------------------------------------------------- /commands/parity_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # NAME 4 | # Parity Wrapper 5 | # 6 | # SYNOPSIS 7 | # parity_wrapper.sh [-r] [role] [-a] [address] [-p] [arguments] 8 | # 9 | # DESCRIPTION 10 | # A wrapper for the actual Parity client to make the Docker image easy usable by preparing the Parity client for 11 | # a set of predefined list of roles the client can take without have to write lines of arguments on run Docker. 12 | # 13 | # OPTIONS 14 | # -r [--role] Role the Parity client should use. 15 | # Depending on the chosen role Parity gets prepared for that role. 16 | # Selecting a specific role can require further arguments. 17 | # Checkout ROLES for further information. 18 | # 19 | # -a [--address] The Ethereum address that parity should use. 20 | # Depending on the chosen role, the address gets inserted at the right place of the configuration, so Parity is aware of it. 21 | # Gets ignored if not necessary for the chosen role. 22 | # 23 | # -p [--parity-args] Additional arguments that should be forwarded to the Parity client. 24 | # Make sure this is the last argument, cause everything after is forwarded to Parity. 25 | # 26 | # ROLES 27 | # The list of available roles is: 28 | # 29 | # bootnode 30 | # - No mining. 31 | # - RPC ports open. 32 | # - Does not require the address argument. 33 | # - Does not need the password file and the key-set. (see FILES) 34 | # node 35 | # - No mining. 36 | # - RPC ports open. 37 | # - Does not require the address argument. 38 | # - Does not need the password file and the key-set. (see FILES) 39 | # 40 | # validator 41 | # - Connect as authority to the network for validating blocks. 42 | # - Miner. 43 | # - RPC ports open. 44 | # - Requires the address argument. 45 | # - Needs the password file and the key-set. (see FILES) 46 | # 47 | # explorer 48 | # - No mining. 49 | # - RPC ports open. 50 | # - Does not require the address argument. 51 | # - Does not need the password file and the key-set. (see FILES) 52 | # - Some of Parity's settings are configured specifically for the use of blockscout explorer. 53 | # 54 | # FILES 55 | # The configuration folder for Parity takes place at /home/parity/.local/share/io.parity.ethereum. 56 | # Alternately the shorthand symbolic link at /config can be used. 57 | # Parity's database is at /home/parity/.local/share/io.parity.ethereum/chains or available trough /data as well. 58 | # To provide custom files in addition bind a volume through Docker to the sub-folder called 'custom'. 59 | # The password file is expected to be placed in the custom configuration folder names 'pass.pwd'. 60 | # The key-set is expected to to be placed in the custom configuration folder under 'keys/FuseNetwork/' 61 | # Besides from using the pre-defined locations, it is possible to define them manually thought the parity arguments. Checkout their documentation to do so. 62 | 63 | # Create an array by the argument string. 64 | IFS=' ' read -r -a ARG_VEC <<< "$@" 65 | 66 | # Adjustable configuration values. 67 | ROLE="node" 68 | ADDRESS="" 69 | GENERATE_NEW_ACCOUNT=false 70 | PARITY_ARGS="--no-color" 71 | 72 | # Internal stuff. 73 | declare -a VALID_ROLE_LIST=( 74 | bootnode 75 | node 76 | validator 77 | explorer 78 | ) 79 | 80 | # Configuration snippets. 81 | CONFIG_SNIPPET_BOOTNODE=' 82 | [rpc] 83 | cors = ["all"] 84 | port = 8545 85 | interface = "all" 86 | hosts = ["all"] 87 | apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] 88 | 89 | [websockets] 90 | disable = false 91 | port = 8546 92 | interface = "all" 93 | origins = ["all"] 94 | hosts = ["all"] 95 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "rpc", "secretstore"] 96 | 97 | [network] 98 | port = 30300 99 | ' 100 | 101 | CONFIG_SNIPPET_NODE=' 102 | [rpc] 103 | cors = ["all"] 104 | port = 8545 105 | interface = "all" 106 | hosts = ["all"] 107 | apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] 108 | 109 | [websockets] 110 | disable = false 111 | port = 8546 112 | interface = "all" 113 | origins = ["all"] 114 | hosts = ["all"] 115 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "rpc", "secretstore"] 116 | 117 | [network] 118 | port = 30300 119 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 120 | ' 121 | 122 | CONFIG_SNIPPET_VALIDATOR=' 123 | [rpc] 124 | cors = ["all"] 125 | port = 8545 126 | interface = "all" 127 | hosts = ["all"] 128 | apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] 129 | 130 | [websockets] 131 | disable = true 132 | 133 | [network] 134 | port = 30300 135 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 136 | 137 | [account] 138 | password = ["/home/parity/.local/share/io.parity.ethereum/custom/pass.pwd"] 139 | 140 | [mining] 141 | reseal_on_txs = "none" 142 | force_sealing = true 143 | engine_signer = "%s" 144 | min_gas_price = 10000000000 145 | gas_floor_target = "20000000" 146 | ' 147 | 148 | CONFIG_SNIPPET_EXPLORER_NODE=' 149 | [rpc] 150 | cors = ["all"] 151 | port = 8545 152 | interface = "all" 153 | hosts = ["all"] 154 | apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"] 155 | 156 | [websockets] 157 | disable = false 158 | port = 8546 159 | interface = "all" 160 | origins = ["all"] 161 | hosts = ["all"] 162 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "rpc", "secretstore"] 163 | 164 | [footprint] 165 | tracing = "on" 166 | pruning = "archive" 167 | fat_db = "on" 168 | 169 | [network] 170 | port = 30300 171 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 172 | ' 173 | 174 | # Make sure some environment variables are defined. 175 | [[ -z "$PARITY_BIN" ]] && PARITY_BIN=/usr/local/bin/parity 176 | [[ -z "$PARITY_CONFIG_FILE_NODE" ]] && PARITY_CONFIG_FILE_NODE=/home/parity/.local/share/io.parity.ethereum/config-template.toml 177 | PARITY_CONFIG_FILE=/home/parity/.local/share/io.parity.ethereum/config.toml 178 | 179 | 180 | # Print the header of this script as help. 181 | # The header ends with the first empty line. 182 | # 183 | function printHelp { 184 | local file="${BASH_SOURCE[0]}" 185 | cat "$file" | sed -e '/^$/,$d; s/^#//; s/^\!\/bin\/bash//' 186 | } 187 | 188 | # Check if the defined role for the client is valid. 189 | # Use a list of predefined roles to check for. 190 | # In case the selected role is invalid, it prints our the error message and exits. 191 | # 192 | function checkRoleArgument { 193 | # Check each known role and end if it match. 194 | for i in "${VALID_ROLE_LIST[@]}" ; do 195 | [[ $i == $ROLE ]] && return 196 | done 197 | 198 | # Error report to the user with the correct usage. 199 | echo "The defined role ('$ROLE') is invalid." 200 | echo "Please choose of the following: ${VALID_ROLE_LIST[@]}" 201 | exit 1 202 | } 203 | 204 | # Parse the arguments, given to the script by the caller. 205 | # Not defined configuration values stay with their default values. 206 | # A not known argument leads to an exit with status code 1. 207 | # 208 | # Arguments: 209 | # $1 - all arguments by the caller 210 | # 211 | function parseArguments { 212 | for (( i=0; i<${#ARG_VEC[@]}; i++ )) ; do 213 | arg="${ARG_VEC[i]}" 214 | nextIndex=$((i + 1)) 215 | 216 | # Print help and exit if requested. 217 | if [[ $arg == --help ]] || [[ $arg == -h ]] ; then 218 | printHelp 219 | exit 0 220 | 221 | # Define the role for the client. 222 | elif [[ $arg == --role ]] || [[ $arg == -r ]] ; then 223 | ROLE="${ARG_VEC[$nextIndex]}" 224 | checkRoleArgument # Make sure to have a valid role. 225 | i=$nextIndex 226 | 227 | # Define the address to bind. 228 | elif [[ $arg == --address ]] || [[ $arg == -a ]] ; then 229 | # Take the next argument as the address and jump other it. 230 | ADDRESS="${ARG_VEC[$nextIndex]}" 231 | i=$nextIndex 232 | PARITY_ARGS="$PARITY_ARGS --node-key ${ADDRESS}" 233 | 234 | # Additional arguments for the Parity client. 235 | # Use all remain arguments for parity. 236 | elif [[ $arg == --parity-args ]] || [[ $arg == -p ]] ; then 237 | PARITY_ARGS="$PARITY_ARGS ${ARG_VEC[@]:$nextIndex}" 238 | GENERATE_NEW_ACCOUNT=true 239 | i=${#ARG_VEC[@]} 240 | 241 | # A not known argument. 242 | else 243 | echo Unkown argument: $arg 244 | exit 1 245 | fi 246 | done 247 | } 248 | 249 | 250 | # Adjust the configuration file for parity for the selected role. 251 | # Includes some checks of the arguments constellation and hints for the user. 252 | # Use the predefined configuration snippets filled with the users input. 253 | # 254 | function adjustConfiguration { 255 | # Make sure role is defined 256 | if [[ -z "$ROLE" ]] && [[ $GENERATE_NEW_ACCOUNT != "true" ]] ; then 257 | echo "Missing or empty role!" 258 | echo "Make sure the argument order is correct (parity arguments at the end)." 259 | exit 1 260 | fi 261 | 262 | # Make sure an address is given if needed. 263 | if ( [[ $ROLE = 'validator' ]] ) && [[ -z "$ADDRESS" ]] ; then 264 | echo "Missing or empty address but required by selected role!" 265 | echo "Make sure the argument order is correct (parity arguments at the end)." 266 | exit 1 267 | fi 268 | 269 | # Read in the template. 270 | local template=$(cat $PARITY_CONFIG_FILE_TEMPLATE) 271 | 272 | # Handle the different roles. 273 | # Append the respective configuration snippet with the necessary variable to the default configuration file. 274 | case $ROLE in 275 | "bootnode") 276 | echo "Run as bootnode" 277 | printf "$template\n$CONFIG_SNIPPET_BOOTNODE" > $PARITY_CONFIG_FILE 278 | ;; 279 | 280 | "node") 281 | echo "Run as node" 282 | printf "$template\n$CONFIG_SNIPPET_NODE" > $PARITY_CONFIG_FILE 283 | ;; 284 | 285 | "validator") 286 | echo "Run as validator with address ${ADDRESS}" 287 | printf "$template\n$CONFIG_SNIPPET_VALIDATOR" "$ADDRESS" > $PARITY_CONFIG_FILE 288 | ;; 289 | 290 | "explorer") 291 | echo "Run as explorer node" 292 | printf "$template\n$CONFIG_SNIPPET_EXPLORER_NODE" > $PARITY_CONFIG_FILE 293 | ;; 294 | esac 295 | } 296 | 297 | # Caller of the actual Parity client binaries. 298 | # The provided arguments by the user gets forwarded. 299 | # 300 | function runParity { 301 | echo "Start Parity with the following arguments: '${PARITY_ARGS}'" 302 | exec $PARITY_BIN $PARITY_ARGS 303 | } 304 | 305 | 306 | # Getting Started 307 | parseArguments 308 | adjustConfiguration 309 | runParity -------------------------------------------------------------------------------- /commands/parity_wrapper_spark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # NAME 4 | # Parity Wrapper 5 | # 6 | # SYNOPSIS 7 | # parity_wrapper.sh [-r] [role] [-a] [address] [-p] [arguments] 8 | # 9 | # DESCRIPTION 10 | # A wrapper for the actual Parity client to make the Docker image easy usable by preparing the Parity client for 11 | # a set of predefined list of roles the client can take without have to write lines of arguments on run Docker. 12 | # 13 | # OPTIONS 14 | # -r [--role] Role the Parity client should use. 15 | # Depending on the chosen role Parity gets prepared for that role. 16 | # Selecting a specific role can require further arguments. 17 | # Checkout ROLES for further information. 18 | # 19 | # -a [--address] The Ethereum address that parity should use. 20 | # Depending on the chosen role, the address gets inserted at the right place of the configuration, so Parity is aware of it. 21 | # Gets ignored if not necessary for the chosen role. 22 | # 23 | # -p [--parity-args] Additional arguments that should be forwarded to the Parity client. 24 | # Make sure this is the last argument, cause everything after is forwarded to Parity. 25 | # 26 | # ROLES 27 | # The list of available roles is: 28 | # 29 | # bootnode 30 | # - No mining. 31 | # - RPC ports open. 32 | # - Does not require the address argument. 33 | # - Does not need the password file and the key-set. (see FILES) 34 | # node 35 | # - No mining. 36 | # - RPC ports open. 37 | # - Does not require the address argument. 38 | # - Does not need the password file and the key-set. (see FILES) 39 | # 40 | # validator 41 | # - Connect as authority to the network for validating blocks. 42 | # - Miner. 43 | # - RPC ports open. 44 | # - Requires the address argument. 45 | # - Needs the password file and the key-set. (see FILES) 46 | # 47 | # explorer 48 | # - No mining. 49 | # - RPC ports open. 50 | # - Does not require the address argument. 51 | # - Does not need the password file and the key-set. (see FILES) 52 | # - Some of Parity's settings are configured specifically for the use of blockscout explorer. 53 | # 54 | # FILES 55 | # The configuration folder for Parity takes place at /home/parity/.local/share/io.parity.ethereum. 56 | # Alternately the shorthand symbolic link at /config can be used. 57 | # Parity's database is at /home/parity/.local/share/io.parity.ethereum/chains or available trough /data as well. 58 | # To provide custom files in addition bind a volume through Docker to the sub-folder called 'custom'. 59 | # The password file is expected to be placed in the custom configuration folder names 'pass.pwd'. 60 | # The key-set is expected to to be placed in the custom configuration folder under 'keys/FuseNetwork/' 61 | # Besides from using the pre-defined locations, it is possible to define them manually thought the parity arguments. Checkout their documentation to do so. 62 | 63 | # Create an array by the argument string. 64 | IFS=' ' read -r -a ARG_VEC <<< "$@" 65 | 66 | # Adjustable configuration values. 67 | ROLE="node" 68 | ADDRESS="" 69 | GENERATE_NEW_ACCOUNT=false 70 | PARITY_ARGS="--no-color" 71 | 72 | # Internal stuff. 73 | declare -a VALID_ROLE_LIST=( 74 | bootnode 75 | node 76 | validator 77 | explorer 78 | ) 79 | 80 | # Configuration snippets. 81 | CONFIG_SNIPPET_BOOTNODE=' 82 | [rpc] 83 | cors = ["all"] 84 | port = 8545 85 | interface = "all" 86 | hosts = ["all"] 87 | apis = ["web3", "eth", "net", "parity", "traces", "secretstore"] 88 | 89 | [websockets] 90 | disable = false 91 | port = 8546 92 | interface = "all" 93 | origins = ["all"] 94 | hosts = ["all"] 95 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "secretstore"] 96 | 97 | [network] 98 | port = 30300 99 | ' 100 | 101 | CONFIG_SNIPPET_NODE=' 102 | [rpc] 103 | cors = ["all"] 104 | port = 8545 105 | interface = "all" 106 | hosts = ["all"] 107 | apis = ["web3", "eth", "net", "parity", "traces", "secretstore"] 108 | 109 | [websockets] 110 | disable = false 111 | port = 8546 112 | interface = "all" 113 | origins = ["all"] 114 | hosts = ["all"] 115 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "secretstore"] 116 | 117 | [network] 118 | port = 30300 119 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 120 | ' 121 | 122 | CONFIG_SNIPPET_VALIDATOR=' 123 | [rpc] 124 | cors = ["all"] 125 | port = 8545 126 | interface = "all" 127 | hosts = ["all"] 128 | apis = ["web3", "eth", "net", "parity", "traces", "secretstore"] 129 | 130 | [websockets] 131 | disable = true 132 | 133 | [network] 134 | port = 30300 135 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 136 | 137 | [account] 138 | password = ["/home/parity/.local/share/io.parity.ethereum/custom/pass.pwd"] 139 | 140 | [mining] 141 | reseal_on_txs = "none" 142 | force_sealing = true 143 | engine_signer = "%s" 144 | min_gas_price = 1000000000 145 | gas_floor_target = "20000000" 146 | ' 147 | 148 | CONFIG_SNIPPET_EXPLORER_NODE=' 149 | [rpc] 150 | cors = ["all"] 151 | port = 8545 152 | interface = "all" 153 | hosts = ["all"] 154 | apis = ["web3", "eth", "net", "parity", "traces", "secretstore"] 155 | 156 | [websockets] 157 | disable = false 158 | port = 8546 159 | interface = "all" 160 | origins = ["all"] 161 | hosts = ["all"] 162 | apis = ["web3", "eth", "net", "parity", "pubsub", "traces", "secretstore"] 163 | 164 | [footprint] 165 | tracing = "on" 166 | pruning = "archive" 167 | fat_db = "on" 168 | 169 | [network] 170 | port = 30300 171 | reserved_peers="/home/parity/.local/share/io.parity.ethereum/bootnodes.txt" 172 | ' 173 | 174 | # Make sure some environment variables are defined. 175 | [[ -z "$PARITY_BIN" ]] && PARITY_BIN=/usr/local/bin/parity 176 | [[ -z "$PARITY_CONFIG_FILE_NODE" ]] && PARITY_CONFIG_FILE_NODE=/home/parity/.local/share/io.parity.ethereum/config-template.toml 177 | PARITY_CONFIG_FILE=/home/parity/.local/share/io.parity.ethereum/config.toml 178 | 179 | 180 | # Print the header of this script as help. 181 | # The header ends with the first empty line. 182 | # 183 | function printHelp { 184 | local file="${BASH_SOURCE[0]}" 185 | cat "$file" | sed -e '/^$/,$d; s/^#//; s/^\!\/bin\/bash//' 186 | } 187 | 188 | # Check if the defined role for the client is valid. 189 | # Use a list of predefined roles to check for. 190 | # In case the selected role is invalid, it prints our the error message and exits. 191 | # 192 | function checkRoleArgument { 193 | # Check each known role and end if it match. 194 | for i in "${VALID_ROLE_LIST[@]}" ; do 195 | [[ $i == $ROLE ]] && return 196 | done 197 | 198 | # Error report to the user with the correct usage. 199 | echo "The defined role ('$ROLE') is invalid." 200 | echo "Please choose of the following: ${VALID_ROLE_LIST[@]}" 201 | exit 1 202 | } 203 | 204 | # Parse the arguments, given to the script by the caller. 205 | # Not defined configuration values stay with their default values. 206 | # A not known argument leads to an exit with status code 1. 207 | # 208 | # Arguments: 209 | # $1 - all arguments by the caller 210 | # 211 | function parseArguments { 212 | for (( i=0; i<${#ARG_VEC[@]}; i++ )) ; do 213 | arg="${ARG_VEC[i]}" 214 | nextIndex=$((i + 1)) 215 | 216 | # Print help and exit if requested. 217 | if [[ $arg == --help ]] || [[ $arg == -h ]] ; then 218 | printHelp 219 | exit 0 220 | 221 | # Define the role for the client. 222 | elif [[ $arg == --role ]] || [[ $arg == -r ]] ; then 223 | ROLE="${ARG_VEC[$nextIndex]}" 224 | checkRoleArgument # Make sure to have a valid role. 225 | i=$nextIndex 226 | 227 | # Define the address to bind. 228 | elif [[ $arg == --address ]] || [[ $arg == -a ]] ; then 229 | # Take the next argument as the address and jump other it. 230 | ADDRESS="${ARG_VEC[$nextIndex]}" 231 | i=$nextIndex 232 | PARITY_ARGS="$PARITY_ARGS --node-key ${ADDRESS}" 233 | 234 | # Additional arguments for the Parity client. 235 | # Use all remain arguments for parity. 236 | elif [[ $arg == --parity-args ]] || [[ $arg == -p ]] ; then 237 | PARITY_ARGS="$PARITY_ARGS ${ARG_VEC[@]:$nextIndex}" 238 | GENERATE_NEW_ACCOUNT=true 239 | i=${#ARG_VEC[@]} 240 | 241 | # A not known argument. 242 | else 243 | echo Unkown argument: $arg 244 | exit 1 245 | fi 246 | done 247 | } 248 | 249 | 250 | # Adjust the configuration file for parity for the selected role. 251 | # Includes some checks of the arguments constellation and hints for the user. 252 | # Use the predefined configuration snippets filled with the users input. 253 | # 254 | function adjustConfiguration { 255 | # Make sure role is defined 256 | if [[ -z "$ROLE" ]] && [[ $GENERATE_NEW_ACCOUNT != "true" ]] ; then 257 | echo "Missing or empty role!" 258 | echo "Make sure the argument order is correct (parity arguments at the end)." 259 | exit 1 260 | fi 261 | 262 | # Make sure an address is given if needed. 263 | if ( [[ $ROLE = 'validator' ]] ) && [[ -z "$ADDRESS" ]] ; then 264 | echo "Missing or empty address but required by selected role!" 265 | echo "Make sure the argument order is correct (parity arguments at the end)." 266 | exit 1 267 | fi 268 | 269 | # Read in the template. 270 | local template=$(cat $PARITY_CONFIG_FILE_TEMPLATE) 271 | 272 | # Handle the different roles. 273 | # Append the respective configuration snippet with the necessary variable to the default configuration file. 274 | case $ROLE in 275 | "bootnode") 276 | echo "Run as bootnode" 277 | printf "$template\n$CONFIG_SNIPPET_BOOTNODE" > $PARITY_CONFIG_FILE 278 | ;; 279 | 280 | "node") 281 | echo "Run as node" 282 | printf "$template\n$CONFIG_SNIPPET_NODE" > $PARITY_CONFIG_FILE 283 | ;; 284 | 285 | "validator") 286 | echo "Run as validator with address ${ADDRESS}" 287 | printf "$template\n$CONFIG_SNIPPET_VALIDATOR" "$ADDRESS" > $PARITY_CONFIG_FILE 288 | ;; 289 | 290 | "explorer") 291 | echo "Run as explorer node" 292 | printf "$template\n$CONFIG_SNIPPET_EXPLORER_NODE" > $PARITY_CONFIG_FILE 293 | ;; 294 | esac 295 | } 296 | 297 | # Caller of the actual Parity client binaries. 298 | # The provided arguments by the user gets forwarded. 299 | # 300 | function runParity { 301 | echo "Start Parity with the following arguments: '${PARITY_ARGS}'" 302 | exec $PARITY_BIN $PARITY_ARGS 303 | } 304 | 305 | 306 | # Getting Started 307 | parseArguments 308 | adjustConfiguration 309 | runParity -------------------------------------------------------------------------------- /commands/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # in case "sudo" is needed 6 | PERMISSION_PREFIX="" 7 | 8 | function docker { 9 | echo -e "\nInstalling docker..." 10 | 11 | $PERMISSION_PREFIX apt-get update 12 | 13 | $PERMISSION_PREFIX apt-get install \ 14 | apt-transport-https \ 15 | ca-certificates \ 16 | curl \ 17 | gnupg-agent \ 18 | software-properties-common 19 | 20 | curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | $PERMISSION_PREFIX apt-key add - 21 | 22 | $PERMISSION_PREFIX add-apt-repository \ 23 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 24 | $(lsb_release -cs) \ 25 | stable" 26 | 27 | $PERMISSION_PREFIX apt-get update 28 | 29 | $PERMISSION_PREFIX apt-get install docker-ce docker-ce-cli containerd.io 30 | } 31 | 32 | function docker-compose { 33 | echo -e "\nInstalling docker-compose..." 34 | 35 | $PERMISSION_PREFIX curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 36 | 37 | $PERMISSION_PREFIX chmod +x /usr/local/bin/docker-compose 38 | 39 | $PERMISSION_PREFIX ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose 40 | } 41 | 42 | function config { 43 | echo -e "\nDowloading config files and scripts..." 44 | 45 | wget -O quickstart.sh "https://raw.githubusercontent.com/fuseio/fuse-network/master/scripts/quickstart.sh" 46 | 47 | chmod +x quickstart.sh 48 | 49 | wget -O .env "https://raw.githubusercontent.com/fuseio/fuse-network/master/scripts/examples/.env.validator.example" 50 | 51 | wget -O clean-docker.sh "https://raw.githubusercontent.com/fuseio/fuse-network/master/scripts/clean-docker.sh" 52 | 53 | chmod +x clean-docker.sh 54 | } 55 | 56 | # Go :) 57 | docker 58 | docker-compose 59 | config -------------------------------------------------------------------------------- /commands/update_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function update-quickstart { 6 | echo -e "\nDowloading latest quickstart from GitHub" 7 | wget -O quickstart.sh "https://raw.githubusercontent.com/fuseio/fuse-network/master/scripts/quickstart.sh" 8 | chmod 777 quickstart.sh 9 | } 10 | 11 | function run-quickstart { 12 | ./quickstart.sh 13 | } 14 | 15 | update-quickstart 16 | run-quickstart -------------------------------------------------------------------------------- /config/bootnodes.txt: -------------------------------------------------------------------------------- 1 | enode://60ca021ca7a60c5fedeb39344d6ef282c6c8574c87bf492dcef7ed8dd9611c5a33da2b286f12eb81554c403718565a749d5028c9bcfc1d5b90b8d105ac04da4b@35.205.73.124:30303 2 | enode://550041c1883866ee537ddf220c0ea84b614bce27e9adb8de85b3b86bd745d7ed9575043a78fabe192f1f0ceee71a343f1d5b35f09e6bb41f24ac69bfe214f414@34.76.228.61:30303 3 | -------------------------------------------------------------------------------- /config/config.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "/home/parity/.local/share/io.parity.ethereum/spec.json" 3 | keys_path = "/home/parity/.local/share/io.parity.ethereum/custom/keys" -------------------------------------------------------------------------------- /config/spark/bootnodes.txt: -------------------------------------------------------------------------------- 1 | enode://008870748a938f4a6599d83c1238f719b0a67e4ae2d32e7542628c43f8af4185bd54124a0a4b80ec54a95098f8ae08174c5bc7fadb0349c0b70332dff22af061@35.205.144.49:30303 2 | enode://d377e5a6fb2d0b44b98e1382c9c16633bdd407843f83f21d17b09fdfa5f79175eb55113c6789fa50b7717d08cb3b24b7485951afa0fe9ca696919202e35170a6@34.34.138.246:30303 3 | -------------------------------------------------------------------------------- /config/spark/config.toml: -------------------------------------------------------------------------------- 1 | [parity] 2 | chain = "/home/parity/.local/share/io.parity.ethereum/spec.json" 3 | keys_path = "/home/parity/.local/share/io.parity.ethereum/custom/keys" -------------------------------------------------------------------------------- /config/spark/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FuseNetwork", 3 | "engine": { 4 | "authorityRound": { 5 | "params": { 6 | "stepDuration": "5", 7 | "blockReward": "0x0", 8 | "blockRewardContractAddress": "0x52B9b9585e1b50DA5600f7dbD94E9fE68943162c", 9 | "blockRewardContractTransition": 1000, 10 | "validators": { 11 | "multi": { 12 | "0": { 13 | "list": [ "0xba7829b381f07cca0d186bdf619fdc6c7f756d0a" ] 14 | }, 15 | "1000": { 16 | "safeContract": "0xC8c3a332f9e4CE6bfFFcf967026cB006Db2311c7" 17 | }, 18 | "6905799": { 19 | "list": [ "0x379e81df609e8235c9026f25a379d49a27b10d30","0xba7829b381f07cca0d186bdf619fdc6c7f756d0a","0xe4cc9b2836ba373c3ccf473cbb15ed07007963ee","0xbc048d3064fd912b40a9aadcf67a14fd4601db77"] 20 | }, 21 | "6910120": { 22 | "safeContract": "0xC8c3a332f9e4CE6bfFFcf967026cB006Db2311c7" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | }, 29 | "params": { 30 | "gasLimitBoundDivisor": "0x400", 31 | "maximumExtraDataSize": "0x20", 32 | "minGasLimit": "0x1388", 33 | "networkID" : "0x07b", 34 | "eip155Transition": 0, 35 | "validateChainIdTransition": 0, 36 | "eip140Transition": 0, 37 | "eip211Transition": 0, 38 | "eip214Transition": 0, 39 | "eip658Transition": 0, 40 | "eip150Transition": "0x0", 41 | "eip160Transition": "0x0", 42 | "eip161abcTransition": "0x0", 43 | "eip161dTransition": "0x0", 44 | "eip98Transition": "0x7fffffffffffff", 45 | "eip145Transition": "0x6bf64", 46 | "eip1014Transition": "0x6bf64", 47 | "eip1052Transition": "0x6bf64", 48 | "eip1283Transition": "0x13d620", 49 | "eip1344Transition": "0x13d620", 50 | "eip1706Transition": "0x13d620", 51 | "eip1884Transition": "0x13d620", 52 | "eip2028Transition": "0x13d620", 53 | "eip2929Transition": "0x13d620", 54 | "eip2930Transition": "0x13d620", 55 | "maxCodeSize": 24576, 56 | "maxCodeSizeTransition": "0x0" 57 | }, 58 | "genesis": { 59 | "seal": { 60 | "authorityRound": { 61 | "step": "0x0", 62 | "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 63 | } 64 | }, 65 | "difficulty": "0x20000", 66 | "gasLimit": "0x5F5E100" 67 | }, 68 | "accounts": { 69 | "0x0000000000000000000000000000000000000001": { 70 | "balance": "1", 71 | "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } 72 | }, 73 | "0x0000000000000000000000000000000000000002": { 74 | "balance": "1", 75 | "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } 76 | }, 77 | "0x0000000000000000000000000000000000000003": { 78 | "balance": "1", 79 | "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } 80 | }, 81 | "0x0000000000000000000000000000000000000004": { 82 | "balance": "1", 83 | "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } 84 | }, 85 | "0x0000000000000000000000000000000000000005": { 86 | "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } 87 | }, 88 | "0x0000000000000000000000000000000000000006": { 89 | "builtin": { 90 | "name": "alt_bn128_add", 91 | "activate_at": 0, 92 | "pricing": { "linear": { "base": 500, "word": 0 } } 93 | } 94 | }, 95 | "0x0000000000000000000000000000000000000007": { 96 | "builtin": { 97 | "name": "alt_bn128_mul", 98 | "activate_at": 0, 99 | "pricing": { "linear": { "base": 40000, "word": 0 } } 100 | } 101 | }, 102 | "0x0000000000000000000000000000000000000008": { 103 | "builtin": { 104 | "name": "alt_bn128_pairing", 105 | "activate_at": 0, 106 | "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } 107 | } 108 | }, 109 | "0xba7829b381f07cca0d186bdf619fdc6c7f756d0a": { "balance": "300000000000000000000000000" } 110 | } 111 | } -------------------------------------------------------------------------------- /config/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FuseNetwork", 3 | "engine": { 4 | "authorityRound": { 5 | "params": { 6 | "stepDuration": "5", 7 | "blockReward": "0x0", 8 | "blockRewardContractAddress": "0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B", 9 | "blockRewardContractTransition": 100, 10 | "validators": { 11 | "multi": { 12 | "0": { 13 | "list": ["0xd9176e84898a0054680aec3f7c056b200c3d96c3"] 14 | }, 15 | "100": { 16 | "safeContract": "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | }, 23 | "params": { 24 | "gasLimitBoundDivisor": "0x400", 25 | "maximumExtraDataSize": "0x20", 26 | "minGasLimit": "0x1388", 27 | "networkID" : "0x07a", 28 | "eip155Transition": 0, 29 | "validateChainIdTransition": 0, 30 | "eip140Transition": 0, 31 | "eip211Transition": 0, 32 | "eip214Transition": 0, 33 | "eip658Transition": 0, 34 | "eip150Transition": "0x0", 35 | "eip160Transition": "0x0", 36 | "eip161abcTransition": "0x0", 37 | "eip161dTransition": "0x0", 38 | "eip98Transition": "0x7fffffffffffff", 39 | "eip145Transition": "0x38ada7", 40 | "eip1014Transition": "0x38ada7", 41 | "eip1052Transition": "0x38ada7", 42 | "eip1283Transition": "0xd29240", 43 | "eip1344Transition": "0xd29240", 44 | "eip1706Transition": "0xd29240", 45 | "eip1884Transition": "0xd29240", 46 | "eip2028Transition": "0xd29240", 47 | "eip2929Transition": "0xd29240", 48 | "eip2930Transition": "0xd29240", 49 | "maxCodeSize": 24576, 50 | "maxCodeSizeTransition": "0x0" 51 | }, 52 | "genesis": { 53 | "seal": { 54 | "authorityRound": { 55 | "step": "0x0", 56 | "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 57 | } 58 | }, 59 | "difficulty": "0x20000", 60 | "gasLimit": "0x989680" 61 | }, 62 | "accounts": { 63 | "0x0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, 64 | "0x0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, 65 | "0x0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, 66 | "0x0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, 67 | "0x0000000000000000000000000000000000000005": { "builtin": { "name": "modexp", "pricing": { "0": { "price": { "modexp": { "divisor": 20 } } }, "0xd29240": { "info": "EIP-2565: ModExp Gas Cost.", "price": { "modexp2565": {} } } } } }, 68 | "0x0000000000000000000000000000000000000006": { "builtin": { "name": "alt_bn128_add", "pricing": { "0": { "price": { "alt_bn128_const_operations": { "price": 500 } } }, "0xd29240": { "info": "EIP-1108 Istanbul HF", "price": { "alt_bn128_const_operations": { "price": 150 } } } } } }, 69 | "0x0000000000000000000000000000000000000007": { "builtin": { "name": "alt_bn128_mul", "pricing": { "0": { "price": { "alt_bn128_const_operations": { "price": 4000 } } }, "0xd29240": { "info": "EIP-1108 Istanbul HF", "price": { "alt_bn128_const_operations": { "price": 6000 } } } } } }, 70 | "0x0000000000000000000000000000000000000008": { "builtin": { "name": "alt_bn128_pairing", "pricing": { "0": { "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } }, "0xd29240": { "info": "EIP-1108 Istanbul HF", "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 } } } } } }, 71 | "0x0000000000000000000000000000000000000009": { "builtin": { "name": "blake2_f", "pricing": { "0xd29240": { "info": "EIP-152 Istanbul HF", "price": { "blake2_f": { "gas_per_round": 1 } } } } } }, 72 | "0xd9176e84898a0054680aec3f7c056b200c3d96c3": { "balance": "300000000000000000000000000" } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/BlockReward.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./abstracts/BlockRewardBase.sol"; 4 | import "./interfaces/IConsensus.sol"; 5 | import "./eternal-storage/EternalStorage.sol"; 6 | import "./ProxyStorage.sol"; 7 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | 9 | /** 10 | * @title Contract handling block reward logic 11 | * @author LiorRabin 12 | */ 13 | contract BlockReward is EternalStorage, BlockRewardBase { 14 | using SafeMath for uint256; 15 | 16 | uint256 public constant DECIMALS = 10 ** 18; 17 | uint256 public constant INFLATION = 5; 18 | uint256 public constant BLOCKS_PER_YEAR = 6307200; 19 | 20 | /** 21 | * @dev This event will be emitted every block, describing the rewards given 22 | * @param receivers array of addresses to reward 23 | * @param rewards array of balance increases corresponding to the receivers array 24 | */ 25 | event Rewarded(address[] receivers, uint256[] rewards); 26 | 27 | /** 28 | * @dev This event will be emitted on cycle end, describing the amount of rewards distributed on the cycle 29 | * @param amount total rewards distributed on this cycle 30 | */ 31 | event RewardedOnCycle(uint256 amount); 32 | 33 | /** 34 | * @dev This modifier verifies that msg.sender is the system address (EIP96) 35 | */ 36 | modifier onlySystem() { 37 | require(msg.sender == addressStorage[SYSTEM_ADDRESS]); 38 | _; 39 | } 40 | 41 | /** 42 | * @dev This modifier verifies that msg.sender is the owner of the contract 43 | */ 44 | modifier onlyOwner() { 45 | require(msg.sender == addressStorage[OWNER]); 46 | _; 47 | } 48 | 49 | /** 50 | * @dev This modifier verifies that msg.sender is the consensus contract 51 | */ 52 | modifier onlyConsensus() { 53 | require(msg.sender == ProxyStorage(getProxyStorage()).getConsensus()); 54 | _; 55 | } 56 | 57 | /** 58 | * @dev This modifier verifies that msg.sender is a validator 59 | */ 60 | modifier onlyValidator() { 61 | require(IConsensus(ProxyStorage(getProxyStorage()).getConsensus()).isValidator(msg.sender)); 62 | _; 63 | } 64 | 65 | /** 66 | * @dev Function to be called on contract initialization 67 | */ 68 | function initialize(uint256 _supply) external onlyOwner { 69 | require(!isInitialized()); 70 | _setSystemAddress(0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE); 71 | _setTotalSupply(_supply); 72 | _initRewardedOnCycle(); 73 | _setBlockRewardAmount(); 74 | setInitialized(true); 75 | } 76 | 77 | /** 78 | * @dev Function called to produce the reward on each block 79 | * @param benefactors array of addresses representing benefectors to be considered for reward 80 | * @param kind array of reward types. We support only arrays with one item and type = 0 (Author - Reward attributed to the block author) 81 | * See https://wiki.parity.io/Block-Reward-Contract.html 82 | */ 83 | function reward(address[] benefactors, uint16[] kind) external onlySystem returns (address[], uint256[]) { 84 | require(benefactors.length == kind.length); 85 | require(benefactors.length == 1); 86 | require(kind[0] == 0); 87 | 88 | uint256 blockRewardAmount = getBlockRewardAmountPerValidator(benefactors[0]); 89 | 90 | (address[] memory _delegators, uint256[] memory _rewards) = IConsensus(ProxyStorage(getProxyStorage()).getConsensus()).getDelegatorsForRewardDistribution(benefactors[0], blockRewardAmount); 91 | 92 | address[] memory receivers = new address[](_delegators.length + 1); 93 | uint256[] memory rewards = new uint256[](receivers.length); 94 | 95 | receivers[0] = benefactors[0]; 96 | rewards[0] = blockRewardAmount; 97 | for (uint256 i = 1; i <= _delegators.length; i++) { 98 | receivers[i] = _delegators[i - 1]; 99 | rewards[i] = _rewards[i - 1]; 100 | rewards[0] = rewards[0].sub(rewards[i]); 101 | } 102 | 103 | _setRewardedOnCycle(getRewardedOnCycle().add(blockRewardAmount)); 104 | _setTotalSupply(getTotalSupply().add(blockRewardAmount)); 105 | 106 | if ((block.number).mod(getBlocksPerYear()) == 0) { 107 | _setBlockRewardAmount(); 108 | } 109 | 110 | IConsensus(ProxyStorage(getProxyStorage()).getConsensus()).cycle(benefactors[0]); 111 | 112 | emit Rewarded(receivers, rewards); 113 | return (receivers, rewards); 114 | } 115 | 116 | function onCycleEnd() external onlyConsensus { 117 | _setShouldEmitRewardedOnCycle(true); 118 | } 119 | 120 | /** 121 | * @dev Function to be called by validators only to emit RewardedOnCycle event (only if `shouldEmitRewardedOnCycle` returns true) 122 | */ 123 | function emitRewardedOnCycle() external onlyValidator { 124 | require(shouldEmitRewardedOnCycle()); 125 | emit RewardedOnCycle(getRewardedOnCycle()); 126 | _setShouldEmitRewardedOnCycle(false); 127 | _setRewardedOnCycle(0); 128 | } 129 | 130 | bytes32 internal constant OWNER = keccak256(abi.encodePacked("owner")); 131 | bytes32 internal constant SYSTEM_ADDRESS = keccak256(abi.encodePacked("SYSTEM_ADDRESS")); 132 | bytes32 internal constant PROXY_STORAGE = keccak256(abi.encodePacked("proxyStorage")); 133 | bytes32 internal constant TOTAL_SUPPLY = keccak256(abi.encodePacked("totalSupply")); 134 | bytes32 internal constant REWARDED_THIS_CYCLE = keccak256(abi.encodePacked("rewardedOnCycle")); 135 | bytes32 internal constant BLOCK_REWARD_AMOUNT = keccak256(abi.encodePacked("blockRewardAmount")); 136 | bytes32 internal constant SHOULD_EMIT_REWARDED_ON_CYCLE = keccak256(abi.encodePacked("shouldEmitRewardedOnCycle")); 137 | 138 | function _setSystemAddress(address _newAddress) private { 139 | addressStorage[SYSTEM_ADDRESS] = _newAddress; 140 | } 141 | 142 | function _setTotalSupply(uint256 _supply) private { 143 | require(_supply >= 0); 144 | uintStorage[TOTAL_SUPPLY] = _supply; 145 | } 146 | 147 | function getTotalSupply() public view returns(uint256) { 148 | return uintStorage[TOTAL_SUPPLY]; 149 | } 150 | 151 | function _initRewardedOnCycle() private { 152 | _setRewardedOnCycle(0); 153 | } 154 | 155 | function _setRewardedOnCycle(uint256 _amount) private { 156 | require(_amount >= 0); 157 | uintStorage[REWARDED_THIS_CYCLE] = _amount; 158 | } 159 | 160 | function getRewardedOnCycle() public view returns(uint256) { 161 | return uintStorage[REWARDED_THIS_CYCLE]; 162 | } 163 | 164 | /** 165 | * returns yearly inflation rate (percentage) 166 | */ 167 | function getInflation() public pure returns(uint256) { 168 | return INFLATION; 169 | } 170 | 171 | /** 172 | * returns blocks per year (block time is 5 seconds) 173 | */ 174 | function getBlocksPerYear() public pure returns(uint256) { 175 | return BLOCKS_PER_YEAR; 176 | } 177 | 178 | function _setBlockRewardAmount() private { 179 | uintStorage[BLOCK_REWARD_AMOUNT] = (getTotalSupply().mul(getInflation().mul(DECIMALS).div(100))).div(getBlocksPerYear()).div(DECIMALS); 180 | } 181 | 182 | function getBlockRewardAmount() public view returns(uint256) { 183 | return uintStorage[BLOCK_REWARD_AMOUNT]; 184 | } 185 | 186 | function getBlockRewardAmountPerValidator(address _validator) public view returns(uint256) { 187 | IConsensus consensus = IConsensus(ProxyStorage(getProxyStorage()).getConsensus()); 188 | uint256 stakeAmount = consensus.stakeAmount(_validator); 189 | uint256 totalStakeAmount = consensus.totalStakeAmount(); 190 | uint256 currentValidatorsLength = consensus.currentValidatorsLength(); 191 | // this may arise in peculiar cases when the consensus totalStakeAmount wasn't calculated yet 192 | // for example at the first blocks after the contract was deployed 193 | if (totalStakeAmount == 0) { 194 | return getBlockRewardAmount(); 195 | } 196 | return getBlockRewardAmount().mul(stakeAmount).mul(currentValidatorsLength).div(totalStakeAmount); 197 | } 198 | 199 | 200 | function getProxyStorage() public view returns(address) { 201 | return addressStorage[PROXY_STORAGE]; 202 | } 203 | 204 | function shouldEmitRewardedOnCycle() public view returns(bool) { 205 | return IConsensus(ProxyStorage(getProxyStorage()).getConsensus()).isFinalized() && boolStorage[SHOULD_EMIT_REWARDED_ON_CYCLE]; 206 | } 207 | 208 | function _setShouldEmitRewardedOnCycle(bool _status) internal { 209 | boolStorage[SHOULD_EMIT_REWARDED_ON_CYCLE] = _status; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /contracts/Consensus.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./interfaces/IBlockReward.sol"; 4 | import "./interfaces/IVoting.sol"; 5 | import "./ConsensusUtils.sol"; 6 | 7 | /** 8 | * @title Contract handling consensus logic 9 | * @author LiorRabin 10 | */ 11 | contract Consensus is ConsensusUtils { 12 | /** 13 | * @dev Function to be called on contract initialization 14 | * @param _initialValidator address of the initial validator. If not set - msg.sender will be the initial validator 15 | */ 16 | function initialize(address _initialValidator) external onlyOwner { 17 | require(!isInitialized()); 18 | _setSystemAddress(0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE); 19 | _setCurrentCycle(); 20 | if (_initialValidator == address(0)) { 21 | _currentValidatorsAdd(msg.sender); 22 | } else { 23 | _currentValidatorsAdd(_initialValidator); 24 | } 25 | _setFinalized(true); 26 | setInitialized(true); 27 | } 28 | 29 | /** 30 | * @dev Function which returns the current validator addresses 31 | */ 32 | function getValidators() external view returns(address[]) { 33 | return currentValidators(); 34 | } 35 | 36 | /** 37 | * @dev See ValidatorSet.finalizeChange 38 | */ 39 | function finalizeChange() external onlySystem notFinalized { 40 | if (newValidatorSetLength() > 0) { 41 | _setCurrentValidators(newValidatorSet()); 42 | emit ChangeFinalized(currentValidators()); 43 | } 44 | _setFinalized(true); 45 | } 46 | 47 | /** 48 | * @dev Fallback function allowing to pay to this contract. Whoever sends funds is considered as "staking" and wanting to become a validator. 49 | */ 50 | function () external payable { 51 | _delegate(msg.sender, msg.value, msg.sender); 52 | } 53 | 54 | /** 55 | * @dev stake to become a validator. 56 | */ 57 | function stake() external payable { 58 | _delegate(msg.sender, msg.value, msg.sender); 59 | } 60 | 61 | /** 62 | * @dev delegate to a validator 63 | * @param _validator the address of the validator msg.sender is delegating to 64 | */ 65 | function delegate(address _validator) external payable { 66 | _delegate(msg.sender, msg.value, _validator); 67 | } 68 | 69 | /** 70 | * @dev Function to be called when a staker whishes to withdraw some of his staked funds 71 | * @param _amount the amount msg.sender wishes to withdraw from the contract 72 | */ 73 | function withdraw(uint256 _amount) external { 74 | _withdraw(msg.sender, _amount, msg.sender); 75 | } 76 | 77 | /** 78 | * @dev Function to be called when a delegator whishes to withdraw some of his staked funds for a validator 79 | * @param _validator the address of the validator msg.sender has delegating to 80 | * @param _amount the amount msg.sender wishes to withdraw from the contract 81 | */ 82 | function withdraw(address _validator, uint256 _amount) external { 83 | _withdraw(msg.sender, _amount, _validator); 84 | } 85 | 86 | /** 87 | * @dev Function to be called by the block reward contract each block to handle cycles and snapshots logic 88 | */ 89 | function cycle(address _validator) external onlyBlockReward { 90 | _incBlockCounter(_validator); 91 | if (_hasCycleEnded()) { 92 | IVoting(ProxyStorage(getProxyStorage()).getVoting()).onCycleEnd(currentValidators()); 93 | _setCurrentCycle(); 94 | _checkJail(currentValidators()); 95 | address[] memory newSet = pendingValidators(); 96 | if (newSet.length > 0) { 97 | _setNewValidatorSet(newSet); 98 | } 99 | if (newValidatorSetLength() > 0) { 100 | _setFinalized(false); 101 | _setShouldEmitInitiateChange(true); 102 | emit ShouldEmitInitiateChange(); 103 | } 104 | IBlockReward(ProxyStorage(getProxyStorage()).getBlockReward()).onCycleEnd(); 105 | } 106 | } 107 | 108 | /** 109 | * @dev Function to be called by validators only to emit InitiateChange event (only if `shouldEmitInitiateChange` returns true) 110 | */ 111 | function emitInitiateChange() external onlyValidator { 112 | require(shouldEmitInitiateChange()); 113 | require(newValidatorSetLength() > 0); 114 | emit InitiateChange(blockhash(block.number - 1), newValidatorSet()); 115 | _setShouldEmitInitiateChange(false); 116 | } 117 | 118 | 119 | /** 120 | * @dev Function to be called by validators to update the validator fee, that's the fee cut the validator takes from his delegatots. 121 | * @param _amount fee percentage when 1e18 represents 100%. 122 | */ 123 | function setValidatorFee(uint256 _amount) external onlyValidator { 124 | require (_amount <= 1 * DECIMALS); 125 | require(_amount >= getMinValidatorFee()); 126 | _setValidatorFee(msg.sender, _amount); 127 | } 128 | 129 | /** 130 | * @dev Function to be called by jailed validator, in order to be released from jail 131 | */ 132 | function unJail() external onlyJailedValidator { 133 | require(getReleaseBlock(msg.sender) <= getCurrentCycleEndBlock()); 134 | 135 | _removeFromJail(msg.sender); 136 | } 137 | 138 | /** 139 | * @dev Function to be called by current validators to be dropped from the next cycle in order to perform maintenance 140 | */ 141 | function maintenance() external onlyValidator { 142 | require(isJailed(msg.sender) == false); 143 | _maintenance(msg.sender); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; // solhint-disable-line 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address newAddress) public restricted { 20 | Migrations upgraded = Migrations(newAddress); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/ProxyStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./eternal-storage/EternalStorageProxy.sol"; 4 | import "./eternal-storage/EternalStorage.sol"; 5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 6 | 7 | /** 8 | * @title Contract used for access and upgradeability to all network contracts 9 | * @author LiorRabin 10 | */ 11 | contract ProxyStorage is EternalStorage { 12 | using SafeMath for uint256; 13 | 14 | /** 15 | * @dev Available contract types on the network 16 | */ 17 | enum ContractTypes { 18 | Invalid, 19 | Consensus, 20 | BlockReward, 21 | ProxyStorage, 22 | Voting 23 | } 24 | 25 | /** 26 | * @dev This event will be emitted when all contract addresses have been initialized by the contract owner 27 | */ 28 | event ProxyInitialized( 29 | address consensus, 30 | address blockReward, 31 | address voting 32 | ); 33 | 34 | /** 35 | * @dev This event will be emitted each time a contract address is updated 36 | * @param contractType contract type (See ContractTypes enum) 37 | * @param contractAddress contract address set for the contract type 38 | */ 39 | event AddressSet(uint256 contractType, address contractAddress); 40 | 41 | /** 42 | * @dev This modifier verifies that msg.sender is the owner of the contract 43 | */ 44 | modifier onlyOwner() { 45 | require(msg.sender == addressStorage[OWNER]); 46 | _; 47 | } 48 | 49 | /** 50 | * @dev This modifier verifies that msg.sender is the voting contract which implement proxy address change 51 | */ 52 | modifier onlyVoting() { 53 | require(msg.sender == getVoting()); 54 | _; 55 | } 56 | 57 | /** 58 | * @dev Function to be called on contract initialization 59 | * @param _consensus address of the network consensus contract 60 | */ 61 | function initialize(address _consensus) external onlyOwner { 62 | require(!isInitialized()); 63 | require(_consensus != address(0)); 64 | require(_consensus != address(this)); 65 | _setConsensus(_consensus); 66 | setInitialized(true); 67 | } 68 | 69 | /** 70 | * @dev Function to be called to initialize all available contract types addresses 71 | */ 72 | function initializeAddresses(address _blockReward, address _voting) external onlyOwner { 73 | require(!boolStorage[PROXY_STORAGE_ADDRESSES_INITIALIZED]); 74 | 75 | addressStorage[BLOCK_REWARD] = _blockReward; 76 | addressStorage[VOTING] = _voting; 77 | 78 | boolStorage[PROXY_STORAGE_ADDRESSES_INITIALIZED] = true; 79 | 80 | emit ProxyInitialized( 81 | getConsensus(), 82 | _blockReward, 83 | _voting 84 | ); 85 | } 86 | 87 | /** 88 | * @dev Function to be called to set specific contract type address 89 | * @param _contractType contract type (See ContractTypes enum) 90 | * @param _contractAddress contract address set for the contract type 91 | */ 92 | function setContractAddress(uint256 _contractType, address _contractAddress) external onlyVoting returns(bool) { 93 | if (!isInitialized()) return false; 94 | if (_contractAddress == address(0)) return false; 95 | 96 | bool success = false; 97 | 98 | if (_contractType == uint256(ContractTypes.Consensus)) { 99 | success = EternalStorageProxy(getConsensus()).upgradeTo(_contractAddress); 100 | } else if (_contractType == uint256(ContractTypes.BlockReward)) { 101 | success = EternalStorageProxy(getBlockReward()).upgradeTo(_contractAddress); 102 | } else if (_contractType == uint256(ContractTypes.ProxyStorage)) { 103 | success = EternalStorageProxy(this).upgradeTo(_contractAddress); 104 | } else if (_contractType == uint256(ContractTypes.Voting)) { 105 | success = EternalStorageProxy(getVoting()).upgradeTo(_contractAddress); 106 | } 107 | 108 | if (success) { 109 | emit AddressSet(_contractType, _contractAddress); 110 | } 111 | return success; 112 | } 113 | 114 | /** 115 | * @dev Function checking if a contract type is valid one for proxy usage 116 | * @param _contractType contract type to check if valid 117 | */ 118 | function isValidContractType(uint256 _contractType) external pure returns(bool) { 119 | return 120 | _contractType == uint256(ContractTypes.Consensus) || 121 | _contractType == uint256(ContractTypes.BlockReward) || 122 | _contractType == uint256(ContractTypes.ProxyStorage) || 123 | _contractType == uint256(ContractTypes.Voting); 124 | } 125 | 126 | bytes32 internal constant OWNER = keccak256(abi.encodePacked("owner")); 127 | bytes32 internal constant CONSENSUS = keccak256(abi.encodePacked("consensus")); 128 | bytes32 internal constant BLOCK_REWARD = keccak256(abi.encodePacked("blockReward")); 129 | bytes32 internal constant VOTING = keccak256(abi.encodePacked("voting")); 130 | bytes32 internal constant PROXY_STORAGE_ADDRESSES_INITIALIZED = keccak256(abi.encodePacked("proxyStorageAddressesInitialized")); 131 | 132 | function _setConsensus(address _consensus) private { 133 | addressStorage[CONSENSUS] = _consensus; 134 | } 135 | 136 | function getConsensus() public view returns(address){ 137 | return addressStorage[CONSENSUS]; 138 | } 139 | 140 | function getBlockReward() public view returns(address){ 141 | return addressStorage[BLOCK_REWARD]; 142 | } 143 | 144 | function getVoting() public view returns(address){ 145 | return addressStorage[VOTING]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /contracts/Voting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./VotingUtils.sol"; 4 | import "./interfaces/IConsensus.sol"; 5 | 6 | /** 7 | * @title Contract handling vote to change implementations network contracts 8 | * @author LiorRabin 9 | */ 10 | contract Voting is VotingUtils { 11 | /** 12 | * @dev Function to be called on contract initialization 13 | */ 14 | function initialize() external onlyOwner { 15 | require(!isInitialized()); 16 | setInitialized(true); 17 | } 18 | 19 | /** 20 | * @dev Function to create a new ballot 21 | * @param _startAfterNumberOfCycles number of cycles after which the ballot should open for voting 22 | * @param _cyclesDuration number of cycles the ballot will remain open for voting 23 | * @param _contractType contract type to change its address (See ProxyStorage.ContractTypes) 24 | * @param _proposedValue proposed address for the contract type 25 | * @param _description ballot text description 26 | */ 27 | function newBallot(uint256 _startAfterNumberOfCycles, uint256 _cyclesDuration, uint256 _contractType, address _proposedValue, string _description) external onlyValidVotingKey(msg.sender) onlyValidDuration(_startAfterNumberOfCycles, _cyclesDuration) returns(uint256) { 28 | require(_proposedValue != address(0)); 29 | require(validContractType(_contractType)); 30 | uint256 ballotId = _createBallot(_startAfterNumberOfCycles, _cyclesDuration, _description); 31 | _setProposedValue(ballotId, _proposedValue); 32 | _setContractType(ballotId, _contractType); 33 | return ballotId; 34 | } 35 | 36 | /** 37 | * @dev Function to get specific ballot info along with voters involvment on it 38 | * @param _id ballot id to get info of 39 | * @param _key voter key to get if voted already 40 | */ 41 | function getBallotInfo(uint256 _id, address _key) external view returns(uint256 startBlock, uint256 endBlock, bool isFinalized, address proposedValue, uint256 contractType, address creator, string description, bool canBeFinalizedNow, bool alreadyVoted, bool belowTurnOut, uint256 accepted, uint256 rejected, uint256 totalStake) { 42 | startBlock = getStartBlock(_id); 43 | endBlock = getEndBlock(_id); 44 | isFinalized = getIsFinalized(_id); 45 | proposedValue = getProposedValue(_id); 46 | contractType = getContractType(_id); 47 | creator = getCreator(_id); 48 | description = getDescription(_id); 49 | canBeFinalizedNow = canBeFinalized(_id); 50 | alreadyVoted = hasAlreadyVoted(_id, _key); 51 | belowTurnOut = getBelowTurnOut(_id); 52 | accepted = getAccepted(_id); 53 | rejected = getRejected(_id); 54 | totalStake = getTotalStake(_id); 55 | 56 | return (startBlock, endBlock, isFinalized, proposedValue, contractType, creator, description, canBeFinalizedNow, alreadyVoted, belowTurnOut, accepted, rejected, totalStake); 57 | } 58 | 59 | /** 60 | * @dev This function is used to vote on a ballot 61 | * @param _id ballot id to vote on 62 | * @param _choice voting decision on the ballot (see VotingBase.ActionChoices) 63 | */ 64 | function vote(uint256 _id, uint256 _choice) external { 65 | require(!getIsFinalized(_id)); 66 | address voter = msg.sender; 67 | require(isActiveBallot(_id)); 68 | require(!hasAlreadyVoted(_id, voter)); 69 | require(_choice == uint(ActionChoices.Accept) || _choice == uint(ActionChoices.Reject)); 70 | _setVoterChoice(_id, voter, _choice); 71 | emit Vote(_id, _choice, voter); 72 | } 73 | 74 | /** 75 | * @dev Function to be called by the consensus contract when a cycles ends 76 | * In this function, all active ballots votes will be counted and updated according to the current validators 77 | */ 78 | function onCycleEnd(address[] validators) external onlyConsensus { 79 | uint256 numOfValidators = validators.length; 80 | if (numOfValidators == 0) { 81 | return; 82 | } 83 | uint[] memory ballots = activeBallots(); 84 | for (uint256 i = 0; i < ballots.length; i++) { 85 | uint256 ballotId = ballots[i]; 86 | if (getStartBlock(ballotId) < block.number && !getFinalizeCalled(ballotId)) { 87 | 88 | if (canBeFinalized(ballotId)) { 89 | uint256 accepts = 0; 90 | uint256 rejects = 0; 91 | for (uint256 j = 0; j < numOfValidators; j++) { 92 | uint256 choice = getVoterChoice(ballotId, validators[j]); 93 | if (choice == uint(ActionChoices.Accept)) { 94 | accepts = accepts.add(getStake(validators[j])); 95 | } else if (choice == uint256(ActionChoices.Reject)) { 96 | rejects = rejects.add(getStake(validators[j])); 97 | } 98 | } 99 | 100 | _setAccepted(ballotId, accepts); 101 | _setRejected(ballotId, rejects); 102 | _setTotalStake(ballotId); 103 | _finalize(ballotId); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /contracts/abstracts/BlockRewardBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title Interface to be implemented by block reward contract 5 | * @author LiorRabin 6 | * @dev abstract contract 7 | */ 8 | contract BlockRewardBase { 9 | // Produce rewards for the given benefactors, with corresponding reward codes. 10 | // Only valid when msg.sender == SYSTEM_ADDRESS (EIP96, 2**160 - 2) 11 | function reward(address[] benefactors, uint16[] kind) external returns (address[], uint256[]); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/abstracts/ValidatorSet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title Interface to be implemented by consensus contract 5 | * @author LiorRabin 6 | * @dev abstract contract 7 | */ 8 | contract ValidatorSet { 9 | /// Issue this log event to signal a desired change in validator set. 10 | /// This will not lead to a change in active validator set until finalizeChange is called. 11 | /// 12 | /// Only the last log event of any block can take effect. 13 | /// If a signal is issued while another is being finalized it may never take effect. 14 | /// 15 | /// parentHash here should be the parent block hash, or the signal will not be recognized. 16 | event InitiateChange(bytes32 indexed parentHash, address[] newSet); 17 | 18 | /// Get current validator set (last enacted or initial if no changes ever made) 19 | function getValidators() external view returns(address[]); 20 | 21 | /// Called when an initiated change reaches finality and is activated. 22 | /// Only valid when msg.sender == SYSTEM_ADDRESS (EIP96, 2**160 - 2) 23 | /// 24 | /// Also called when the contract is first enabled for consensus. 25 | /// In this case, the "change" finalized is the activation of the initial set. 26 | function finalizeChange() external; 27 | } -------------------------------------------------------------------------------- /contracts/abstracts/VotingBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title Interface to be implemented by voting contract 5 | * @author LiorRabin 6 | * @dev abstract contract 7 | */ 8 | contract VotingBase { 9 | 10 | /** 11 | * @dev Possible states of quorum 12 | * @param InProgress - state while a ballot has not been finalized yet 13 | * @param Accepted - state after finalizing the ballot and majority have voted ActionChoices.Accept 14 | * @param Rejected - state after finalizing the ballot and majority have voted ActionChoices.Reject 15 | */ 16 | enum QuorumStates { 17 | Invalid, 18 | InProgress, 19 | Accepted, 20 | Rejected 21 | } 22 | 23 | /** 24 | * @dev Possible choices for a ballot 25 | */ 26 | enum ActionChoices { 27 | Invalid, 28 | Accept, 29 | Reject 30 | } 31 | 32 | /** 33 | * @dev This event will be emitted every time a new ballot is created 34 | * @param id ballot id 35 | * @param creator address of ballot creator 36 | */ 37 | event BallotCreated(uint256 indexed id, address indexed creator); 38 | 39 | /** 40 | * @dev This event will be emitted when a ballot if finalized 41 | * @param id ballot id 42 | */ 43 | event BallotFinalized(uint256 indexed id); 44 | 45 | /** 46 | * @dev This event will be emitted on each vote 47 | * @param id ballot id 48 | * @param decision voter decision (see VotingBase.ActionChoices) 49 | * @param voter address of the voter 50 | */ 51 | event Vote(uint256 indexed id, uint256 decision, address indexed voter); 52 | 53 | /** 54 | * @dev Function to be called when voting on a ballot 55 | * @param _id ballot id 56 | * @param _choice voter decision on the ballot (see VotingBase.ActionChoices) 57 | */ 58 | function vote(uint256 _id, uint256 _choice) external; 59 | } 60 | -------------------------------------------------------------------------------- /contracts/eternal-storage/EternalStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | * @title EternalStorage 6 | * @author LiorRabin 7 | * @dev This contract holds all the necessary state variables to carry out the storage of any contract and to support the upgrade functionality. 8 | */ 9 | contract EternalStorage { 10 | // Version number of the current implementation 11 | uint256 internal version; 12 | 13 | // Address of the current implementation 14 | address internal implementation; 15 | 16 | // Storage mappings 17 | mapping(bytes32 => uint256) internal uintStorage; 18 | mapping(bytes32 => string) internal stringStorage; 19 | mapping(bytes32 => address) internal addressStorage; 20 | mapping(bytes32 => bytes) internal bytesStorage; 21 | mapping(bytes32 => bool) internal boolStorage; 22 | mapping(bytes32 => int256) internal intStorage; 23 | 24 | mapping(bytes32 => uint256[]) internal uintArrayStorage; 25 | mapping(bytes32 => string[]) internal stringArrayStorage; 26 | mapping(bytes32 => address[]) internal addressArrayStorage; 27 | mapping(bytes32 => bytes[]) internal bytesArrayStorage; 28 | mapping(bytes32 => bool[]) internal boolArrayStorage; 29 | mapping(bytes32 => int256[]) internal intArrayStorage; 30 | mapping(bytes32 => bytes32[]) internal bytes32ArrayStorage; 31 | 32 | function isInitialized() public view returns(bool) { 33 | return boolStorage[keccak256(abi.encodePacked("isInitialized"))]; 34 | } 35 | 36 | function setInitialized(bool _status) internal { 37 | boolStorage[keccak256(abi.encodePacked("isInitialized"))] = _status; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/eternal-storage/EternalStorageProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./EternalStorage.sol"; 4 | 5 | /** 6 | * @title EternalStorageProxy 7 | * @author LiorRabin 8 | * @dev This proxy holds the storage of the token contract and delegates every call to the current implementation set. 9 | * Besides, it allows to upgrade the token's behaviour towards further implementations, and provides authorization control functionalities 10 | */ 11 | contract EternalStorageProxy is EternalStorage { 12 | /** 13 | * @dev This event will be emitted every time the implementation gets upgraded 14 | * @param version representing the version number of the upgraded implementation 15 | * @param implementation representing the address of the upgraded implementation 16 | */ 17 | event Upgraded(uint256 version, address indexed implementation); 18 | 19 | /** 20 | * @dev This event will be emitted when ownership is renounces 21 | * @param previousOwner address which is renounced from ownership 22 | */ 23 | event OwnershipRenounced(address indexed previousOwner); 24 | 25 | /** 26 | * @dev This event will be emitted when ownership is transferred 27 | * @param previousOwner address which represents the previous owner 28 | * @param newOwner address which represents the new owner 29 | */ 30 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 31 | 32 | /** 33 | * @dev This modifier verifies that msg.sender is the ProxyStorage contract 34 | */ 35 | modifier onlyProxyStorage() { 36 | require(msg.sender == getProxyStorage()); 37 | _; 38 | } 39 | 40 | /** 41 | * @dev This modifier verifies that msg.sender is the owner of the contract 42 | */ 43 | modifier onlyOwner() { 44 | require(msg.sender == getOwner()); 45 | _; 46 | } 47 | 48 | /** 49 | * @dev Constructor 50 | * @param _proxyStorage address representing the ProxyStorage contract 51 | * @param _implementation address representing the implementation contract 52 | */ 53 | constructor(address _proxyStorage, address _implementation) public { 54 | require(_implementation != address(0)); 55 | if (_proxyStorage != address(0)) { 56 | _setProxyStorage(_proxyStorage); 57 | } else { 58 | _setProxyStorage(address(this)); 59 | } 60 | _setImplementation(_implementation); 61 | _setOwner(msg.sender); 62 | } 63 | 64 | /** 65 | * @dev Fallback function allowing to perform a delegatecall to the given implementation. 66 | * This function will return whatever the implementation call returns 67 | */ 68 | // solhint-disable no-complex-fallback, no-inline-assembly 69 | function() payable public { 70 | address _impl = getImplementation(); 71 | require(_impl != address(0)); 72 | 73 | assembly { 74 | // Copy msg.data. We take full control of memory in this inline assembly 75 | // block because it will not return to Solidity code. We overwrite the 76 | // Solidity scratch pad at memory position 0 77 | calldatacopy(0, 0, calldatasize) 78 | 79 | // Call the implementation. 80 | // out and outsize are 0 because we don't know the size yet 81 | let result := delegatecall(gas, _impl, 0, calldatasize, 0, 0) 82 | 83 | // Copy the returned data 84 | returndatacopy(0, 0, returndatasize) 85 | 86 | switch result 87 | // delegatecall returns 0 on error 88 | case 0 { revert(0, returndatasize) } 89 | default { return(0, returndatasize) } 90 | } 91 | } 92 | // solhint-enable no-complex-fallback, no-inline-assembly 93 | 94 | /** 95 | * @dev Allows ProxyStorage contract (only) to upgrade the current implementation. 96 | * @param _newImplementation representing the address of the new implementation to be set. 97 | */ 98 | function upgradeTo(address _newImplementation) public onlyProxyStorage returns(bool) { 99 | if (_newImplementation == address(0)) return false; 100 | if (getImplementation() == _newImplementation) return false; 101 | uint256 _newVersion = getVersion() + 1; 102 | _setVersion(_newVersion); 103 | _setImplementation(_newImplementation); 104 | emit Upgraded(_newVersion, _newImplementation); 105 | return true; 106 | } 107 | 108 | /** 109 | * @dev Allows the current owner to relinquish ownership. 110 | */ 111 | function renounceOwnership() public onlyOwner { 112 | emit OwnershipRenounced(getOwner()); 113 | _setOwner(address(0)); 114 | } 115 | 116 | /** 117 | * @dev Allows the current owner to transfer control of the contract to a _newOwner. 118 | * @param _newOwner The address to transfer ownership to. 119 | */ 120 | function transferOwnership(address _newOwner) public onlyOwner { 121 | require(_newOwner != address(0)); 122 | emit OwnershipTransferred(getOwner(), _newOwner); 123 | _setOwner(_newOwner); 124 | } 125 | 126 | function getOwner() public view returns(address) { 127 | return addressStorage[keccak256(abi.encodePacked("owner"))]; 128 | } 129 | 130 | function _setOwner(address _owner) private { 131 | addressStorage[keccak256(abi.encodePacked("owner"))] = _owner; 132 | } 133 | 134 | function getVersion() public view returns(uint256) { 135 | return version; 136 | } 137 | 138 | function _setVersion(uint256 _newVersion) private { 139 | version = _newVersion; 140 | } 141 | 142 | function getImplementation() public view returns(address) { 143 | return implementation; 144 | } 145 | 146 | function _setImplementation(address _newImplementation) private { 147 | implementation = _newImplementation; 148 | } 149 | 150 | function getProxyStorage() public view returns(address) { 151 | return addressStorage[keccak256(abi.encodePacked("proxyStorage"))]; 152 | } 153 | 154 | function _setProxyStorage(address _proxyStorage) private { 155 | addressStorage[keccak256(abi.encodePacked("proxyStorage"))] = _proxyStorage; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /contracts/interfaces/IBlockReward.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | interface IBlockReward { 4 | function onCycleEnd() external; 5 | } 6 | -------------------------------------------------------------------------------- /contracts/interfaces/IConsensus.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | interface IConsensus { 4 | function currentValidatorsLength() external view returns(uint256); 5 | function currentValidatorsAtPosition(uint256 _p) external view returns(address); 6 | function getCycleDurationBlocks() external view returns(uint256); 7 | function getCurrentCycleEndBlock() external view returns(uint256); 8 | function cycle(address _validator) external; 9 | function isValidator(address _address) external view returns(bool); 10 | function getDelegatorsForRewardDistribution(address _validator, uint256 _rewardAmount) external view returns(address[], uint256[]); 11 | function isFinalized() external view returns(bool); 12 | function stakeAmount(address _address) external view returns(uint256); 13 | function totalStakeAmount() external view returns(uint256); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IVoting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | interface IVoting { 4 | function onCycleEnd(address[] validators) external; 5 | } 6 | -------------------------------------------------------------------------------- /contracts/test/BlockRewardMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../BlockReward.sol"; 4 | 5 | contract BlockRewardMock is BlockReward { 6 | function setSystemAddressMock(address _newAddress) public onlyOwner { 7 | addressStorage[SYSTEM_ADDRESS] = _newAddress; 8 | } 9 | 10 | function getSystemAddress() public view returns(address) { 11 | return addressStorage[SYSTEM_ADDRESS]; 12 | } 13 | 14 | function getBlocksPerYear() public pure returns(uint256) { 15 | return 100; 16 | } 17 | 18 | function setShouldEmitRewardedOnCycleMock(bool _status) public { 19 | boolStorage[SHOULD_EMIT_REWARDED_ON_CYCLE] = _status; 20 | } 21 | 22 | function cycleMock() public { 23 | IConsensus(ProxyStorage(getProxyStorage()).getConsensus()).cycle(getSystemAddress()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/ConsensusMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../Consensus.sol"; 4 | 5 | contract ConsensusMock is Consensus { 6 | uint256 currentValidatorsLengthMock = 0; 7 | 8 | function setSystemAddressMock(address _newAddress) public onlyOwner { 9 | addressStorage[SYSTEM_ADDRESS] = _newAddress; 10 | } 11 | 12 | function getSystemAddress() public view returns(address) { 13 | return addressStorage[SYSTEM_ADDRESS]; 14 | } 15 | 16 | function hasCycleEnded() public view returns(bool) { 17 | return _hasCycleEnded(); 18 | } 19 | 20 | // function shouldTakeSnapshot() public view returns(bool) { 21 | // return _shouldTakeSnapshot(); 22 | // } 23 | 24 | function getRandom(uint256 _from, uint256 _to) public view returns(uint256) { 25 | return _getRandom(_from, _to); 26 | } 27 | 28 | // function getBlocksToSnapshot() public pure returns(uint256) { 29 | // return _getBlocksToSnapshot(); 30 | // } 31 | 32 | function setNewValidatorSetMock(address[] _newSet) public { 33 | addressArrayStorage[NEW_VALIDATOR_SET] = _newSet; 34 | } 35 | 36 | function setStakeAmountMockGroup(address[] _newSet) public { 37 | for (uint256 i; i < _newSet.length; i++) { 38 | _stakeAmountAdd(_newSet[i],1000000000000000000); 39 | } 40 | } 41 | 42 | function addStakeAmountMock(address addr, uint256 amount) public { 43 | _stakeAmountAdd(addr, amount); 44 | } 45 | 46 | function setFinalizedMock(bool _status) public { 47 | boolStorage[IS_FINALIZED] = _status; 48 | } 49 | 50 | function setShouldEmitInitiateChangeMock(bool _status) public { 51 | boolStorage[SHOULD_EMIT_INITIATE_CHANGE] = _status; 52 | } 53 | 54 | function getMinStake() public pure returns(uint256) { 55 | return 1e22; 56 | } 57 | 58 | function getMaxStake() public pure returns(uint256) { 59 | return 5e22; 60 | } 61 | 62 | function getCycleDurationBlocks() public pure returns(uint256) { 63 | return 120; 64 | } 65 | 66 | function getSnapshotsPerCycle() public pure returns(uint256) { 67 | return 10; 68 | } 69 | 70 | function setCurrentValidatorsLengthMock(uint256 _currentValidatorsLengthMock) external { 71 | currentValidatorsLengthMock = _currentValidatorsLengthMock; 72 | } 73 | 74 | function currentValidatorsLength() public view returns(uint256) { 75 | if (currentValidatorsLengthMock != 0) { 76 | return currentValidatorsLengthMock; 77 | } 78 | return super.currentValidatorsLength(); 79 | } 80 | 81 | function setValidatorFeeMock(uint256 _amount) external { 82 | require (_amount <= 1 * DECIMALS); 83 | _setValidatorFee(msg.sender, _amount); 84 | } 85 | 86 | function setTotalStakeAmountMock(uint256 _totalStake) public { 87 | _setTotalStakeAmount(_totalStake); 88 | } 89 | 90 | function setBlockCounterMock(address _val, uint256 counter) public { 91 | uintStorage[keccak256(abi.encodePacked("blockCounter", _val))] = counter; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/test/EternalStorageProxyMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import '../eternal-storage/EternalStorageProxy.sol'; 4 | 5 | contract EternalStorageProxyMock is EternalStorageProxy { 6 | constructor(address _proxyStorage, address _implementation) EternalStorageProxy(_proxyStorage, _implementation) public {} 7 | 8 | function setProxyStorageMock(address _proxyStorage) public { 9 | addressStorage[keccak256(abi.encodePacked("proxyStorage"))] = _proxyStorage; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/test/ProxyStorageMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../ProxyStorage.sol"; 4 | 5 | contract ProxyStorageMock is ProxyStorage { 6 | function setBlockRewardMock(address _newAddress) public { 7 | addressStorage[BLOCK_REWARD] = _newAddress; 8 | } 9 | 10 | function setConsensusMock(address _newAddress) public { 11 | addressStorage[CONSENSUS] = _newAddress; 12 | } 13 | 14 | function upgradeBlockRewardMock(address _implementation) public { 15 | EternalStorageProxy(getBlockReward()).upgradeTo(_implementation); 16 | } 17 | 18 | function upgradeConsensusMock(address _implementation) public { 19 | EternalStorageProxy(getConsensus()).upgradeTo(_implementation); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/test/VotingMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../Voting.sol"; 4 | 5 | contract VotingMock is Voting { 6 | 7 | bytes32 internal constant CONSESNSUS_MOCK = keccak256(abi.encodePacked("consensusMock")); 8 | 9 | function setNextBallotIdMock(uint256 _id) public { 10 | uintStorage[NEXT_BALLOT_ID] = _id; 11 | } 12 | 13 | function setConsensusMock(address _consensus) public { 14 | addressStorage[CONSESNSUS_MOCK] = _consensus; 15 | } 16 | 17 | /** 18 | * @dev This modifier verifies that msg.sender is the consensus contract 19 | */ 20 | modifier onlyConsensus() { 21 | if (addressStorage[CONSESNSUS_MOCK] != address(0)) { 22 | require(msg.sender == addressStorage[CONSESNSUS_MOCK]); 23 | } else { 24 | require(msg.sender == ProxyStorage(getProxyStorage()).getConsensus()); 25 | } 26 | _; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("solidity-coverage"); 2 | require("@nomiclabs/hardhat-truffle5"); 3 | require("@nomiclabs/hardhat-ethers"); 4 | require("@nomicfoundation/hardhat-verify"); 5 | require("dotenv").config(); 6 | 7 | const { 8 | WALLET_PROVIDER_METHOD, 9 | CREDENTIALS_ADDRESS, 10 | CREDENTIALS_KEYSTORE, 11 | CREDENTIALS_PASSWORD, 12 | MNEMONIC, 13 | PRIVATE_KEY, 14 | } = process.env; 15 | 16 | module.exports = { 17 | defaultNetwork: "hardhat", 18 | networks: { 19 | hardhat: { 20 | chainId: 122, 21 | accounts: { 22 | mnemonic: "test test test test test test test test test test test fuse", 23 | path: "m/44'/60'/0'/0", 24 | initialIndex: 0, 25 | count: 20, 26 | passphrase: "", 27 | accountsBalance: "1000000000000000000000000", 28 | }, 29 | }, 30 | fuse: { 31 | url: "https://rpc.fuse.io", 32 | chainId: 122, 33 | accounts: getSigners(), 34 | }, 35 | spark: { 36 | url: "https://rpc.fusespark.io", 37 | chainId: 123, 38 | accounts: getSigners(), 39 | }, 40 | devnet: { 41 | url: "http://34.38.118.140:8545", 42 | chainId: 123, 43 | accounts: getSigners(), 44 | allowUnlimitedContractSize: true, 45 | }, 46 | }, 47 | solidity: { 48 | version: "0.4.24", 49 | settings: { 50 | optimizer: { 51 | enabled: true, 52 | runs: 200, 53 | }, 54 | }, 55 | }, 56 | paths: { 57 | sources: "./contracts", 58 | tests: "./test", 59 | cache: "./cache", 60 | artifacts: "./artifacts", 61 | }, 62 | mocha: { 63 | timeout: 40000, 64 | }, 65 | etherscan: { 66 | apiKey: { 67 | spark: "abc", 68 | fuse: "abc", 69 | }, 70 | customChains: [ 71 | { 72 | network: "spark", 73 | chainId: 123, 74 | urls: { 75 | apiURL: "https://explorer.fusespark.io/api/", 76 | browserURL: "https://explorer.fusespark.io", 77 | }, 78 | }, 79 | { 80 | network: "fuse", 81 | chainId: 122, 82 | urls: { 83 | apiURL: "https://explorer.fuse.io/api/", 84 | browserURL: "https://explorer.fuse.io", 85 | }, 86 | }, 87 | ], 88 | }, 89 | }; 90 | function getSigners() { 91 | let signers = []; 92 | if (WALLET_PROVIDER_METHOD === "keystore") { 93 | const fs = require("fs"); 94 | const os = require("os"); 95 | const path = require("path"); 96 | const keythereum = require("keythereum"); 97 | 98 | const keystore_dir = path.join(os.homedir(), CREDENTIALS_KEYSTORE); 99 | const password_dir = path.join(os.homedir(), CREDENTIALS_PASSWORD); 100 | const password = fs.readFileSync(password_dir, "utf8"); 101 | const keyobj = keythereum.importFromFile(CREDENTIALS_ADDRESS, keystore_dir); 102 | const privateKey = keythereum.recover(password, keyobj); 103 | 104 | signers.push(privateKey.toString("hex")); 105 | } else if (WALLET_PROVIDER_METHOD === "mnemonic") { 106 | const wallet = Wallet.fromMnemonic(MNEMONIC); 107 | const privateKey = wallet.getPrivateKeyString(); 108 | signers.push(privateKey); 109 | } else if (WALLET_PROVIDER_METHOD === "privateKey") { 110 | signers.push(PRIVATE_KEY); 111 | } 112 | return signers; 113 | } 114 | -------------------------------------------------------------------------------- /nethermind/OE_Migration.md: -------------------------------------------------------------------------------- 1 | Certainly! Here's the updated guide with the added step to remove the maintenance flag and verify that the node is starting to validate blocks again: 2 | 3 | # Migrating from OpenEthereum to Nethermind Client 4 | 5 | ## Index 6 | 7 | - [Introduction](#introduction) 8 | - [Prerequisites](#prerequisites) 9 | - [Step 0: Avoid having two nodes with the same key!](#step-0-avoid-having-two-nodes-with-the-same-key) 10 | - [Step 1: Flagging for Maintenance (Validator Nodes Only)](#step-1-flagging-for-maintenance-validator-nodes-only) 11 | - [Step 2: Backing Up Your Node Data](#step-2-backing-up-your-node-data) 12 | - [Step 3: Copying the Keystore Directory](#step-3-copying-the-keystore-directory) 13 | - [Step 4: Installing Nethermind](#step-4-installing-nethermind) 14 | - [Step 5: Syncing Nethermind](#step-5-syncing-nethermind) 15 | - [Step 6: Verifying the Migration](#step-6-verifying-the-migration) 16 | - [Step 7: Removing Maintenance Flag (Validator Nodes Only)](#step-7-removing-maintenance-flag-validator-nodes-only) 17 | - [Support and Issues](#support-and-issues) 18 | 19 | ## Introduction 20 | 21 | This guide provides step-by-step instructions for node operators looking to migrate their Fuse nodes from the OpenEthereum client to the Nethermind client. The migration process involves flagging the node for maintenance, backing up data, installing the Nethermind client, configuring it, and finally starting and verifying the node's operation. 22 | 23 | ## Prerequisites 24 | 25 | - An operational Ethereum node running the OpenEthereum client. 26 | - Sufficient storage space for blockchain data. 27 | - Basic command-line interface (CLI) knowledge. 28 | 29 | ### In this guide, we will assume the containers are named fusenet, netstats, and **fuseapp** (Validator Nodes Only). 30 | 31 | ## Step 0: Avoid having two nodes with the same key! 32 | 33 | > **⚠️ Important: Only One Instance of OpenEthereum or Nethermind Client Can Be Running with the Same Key. ⚠️** 34 | > 35 | > You must make sure that the OpenEthereum node is not running before starting the Nethermind node. Failure to do this will result in double signing, which can cause reorgs, potentially lead to consensus failure, and forking. 36 | 37 | ## Step 1: Flagging for Maintenance (Validator Nodes Only) 38 | 39 | Before starting the migration, flag your node for maintenance to ensure it is removed from the active set. 40 | 41 | 1. **Flag the node for maintenance** using the following command: 42 | 43 | ```bash 44 | ./quickstart.sh -m 45 | ``` 46 | 47 | 2. **Wait for the validator node to be out of the active set**, which will take effect on the next cycle. To get the current cycle end block, use the following command: 48 | 49 | ```bash 50 | curl --location 'https://rpc.fuse.io/' \ 51 | --header 'Content-Type: application/json' \ 52 | --data '{ 53 | "jsonrpc": "2.0", 54 | "id": 1, 55 | "method": "eth_call", 56 | "params": [ 57 | { 58 | "to": "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79", 59 | "data": "0xaf295181" 60 | }, 61 | "latest" 62 | ] 63 | }' 64 | ``` 65 | 66 | 3. **To check if the validator node is no longer in the validation set**, use the following command: 67 | ```bash 68 | curl --location 'https://rpc.fuse.io/' \ 69 | --header 'Content-Type: application/json' \ 70 | --data '{ 71 | "jsonrpc": "2.0", 72 | "id": 1, 73 | "method": "eth_call", 74 | "params": [ 75 | { 76 | "to": "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79", 77 | "data": "0xfacd743b000000000000000000000000" 78 | }, 79 | "latest" 80 | ] 81 | }' 82 | ``` 83 | 84 | > **Important:** Please only proceed if the node is out of the active set. 85 | 86 | ## Step 2: Backing Up Your Node Data 87 | 88 | This backup step is to revert to OpenEthereum in case of migration failure. 89 | 90 | 1. **Navigate to the [health](https://health.fuse.io/)** dashboard and verify that the node is healthy. 91 | 2. **Check the logs** to verify the node's health: 92 | 93 | ```bash 94 | docker logs fusenet -f 95 | ``` 96 | 97 | - **Things to look for:** 98 | - Not preparing block (Validator Nodes Only) 99 | - Syncing #[Block-Number] is the latest. 100 | 101 | 3. **Identify running containers** by executing the command below: 102 | 103 | ```bash 104 | docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 105 | ``` 106 | 107 | 4. **Stop your OpenEthereum node** to ensure data integrity during the backup process: 108 | 109 | ```bash 110 | docker stop fusenet netstats fuseapp 111 | ``` 112 | 113 | > **Important**: The OpenEthereum node must be stopped to allow migration. 114 | 115 | 5. **Backup your data directory**. This directory contains the blockchain data and keys. The folder structure should be similar to the below: 116 | 117 | > **Note**: Please be aware that the names of the folders may differ depending on your initial setup. 118 | 119 | - fusenet/database 120 | - fusenet/config/keystore 121 | - `node.key.plain` 122 | - `pass.pwd` 123 | - `UTC--[Date]--xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx` 124 | 125 | > **Note**: The database backup from the OpenEthereum node cannot be used for Nethermind. It is to revert to OpenEthereum in case of migration failure. 126 | 127 | ## Step 3: Copying the Keystore Directory 128 | 129 | Create a new folder for Nethermind and copy the keystore directory from the OpenEthereum node. This step is crucial to ensure your keys are correctly transferred. 130 | 131 | 1. **Create a new folder for Nethermind** in your home directory: 132 | 133 | ```bash 134 | mkdir ~/nethermind && cd ~/nethermind 135 | ``` 136 | 137 | 2. **Copy the keystore directory** from the OpenEthereum node to the Nethermind directory: 138 | 139 | ```bash 140 | cp -r /path/to/openethereum/config/keystore ~/nethermind/config/keystore 141 | ``` 142 | 143 | > **Note**: Ensure you replace `/path/to/openethereum/config/keystore` with the actual path to your OpenEthereum keystore directory. 144 | 145 | 3. **Optional**: To protect the keystore, you may want to change the permissions: 146 | ```bash 147 | chmod -R 700 ~/nethermind/config/keystore 148 | ``` 149 | 150 | ## Step 4: Installing Nethermind 151 | 152 | With your data backed up and the keystore directory copied, the next step is to install the Nethermind client. Please check the Nethermind [system requirements](https://docs.nethermind.io/get-started/system-requirements/) before continuing. 153 | 154 | 1. **Download the Nethermind quickstart.sh script**. Please refer to the [quickstart.sh guide](https://github.com/fuseio/fuse-network/tree/master/nethermind) for more details: 155 | 156 | ```bash 157 | wget -O quickstart.sh https://raw.githubusercontent.com/fuseio/fuse-network/master/nethermind/quickstart.sh 158 | chmod 755 quickstart.sh 159 | ``` 160 | 161 | 2. **Install the Nethermind client** by following the guide above. 162 | > **Important:** Please ensure the OpenEthereum node is not running and only one key is active at any time. 163 | ```bash 164 | ./quickstart.sh -r [node/validator] -n [fuse/spark] -k [node_name] 165 | ``` 166 | 167 | - **Things to look for:** 168 | - Running Docker container for the Fuse network. Role - ..... 169 | - **Monitor** the command line while running the quickstart.sh script and verify that the public address matches the node address. 170 | 171 | ## Step 5: Syncing Nethermind 172 | 173 | Syncing the Nethermind client from scratch can take several hours. Optionally, to speed up this process, you can use a database snapshot. 174 | 175 | 1. **Download the Nethermind DB Snapshot** from the link provided: 176 | 177 | | Network | Type | Location | 178 | | ------- | -------- | --------------------------------------------------------------------------- | 179 | | Fuse | FastSync | https://storage.cloud.google.com/fuse-node-snapshot/nethermind/database.zip | 180 | 181 | 2. **Extract the snapshot** to the Nethermind database directory: 182 | 183 | ```bash 184 | unzip database.zip -d ~/nethermind/database 185 | ``` 186 | 187 | 3. **Start the Nethermind client**: 188 | ```bash 189 | ./quickstart.sh -r [node/validator] -n [fuse/spark] -k [node_name] 190 | ``` 191 | 192 | ## Step 6: Verifying the Migration 193 | 194 | Once Nethermind is up and running, perform checks to ensure everything is working as expected. 195 | 196 | 1. **Check the keystore folder**. The keystore structure differs between the OE client and the Nethermind client. The quickstart.sh script will handle the keystore migration from the old structure to the new one supported by Nethermind. 197 | - nethermind/database 198 | - nethermind/logs 199 | - nethermind/config/keystore 200 | - `UTC--{yyyy-MM-dd}T{HH-mm-ss.ffffff}000Z--{address}` 201 | - Verify that you have only one key file. 202 | 203 | > **Note**: Please verify the node address matches the address in the UTC file name above. 204 | 205 | 2. **Check the logs** by running the following commands: 206 | ```bash 207 | docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 208 | sudo docker logs fuse -f 209 | ``` 210 | 211 | - **Things to look for:** 212 | 213 | - Nethermind initialization completed. 214 | - Node address: `0x..........` 215 | - Address `0x0..........` is configured for signing blocks. (Validator Nodes Only) 216 | - Skipping the creation of a new private key... 217 | - Verify that the public address matches the node address. 218 | - The node is sealing/syncing new blocks. 219 | 220 | - **For validator app logs** (Validator Nodes Only): 221 | 222 | ```bash 223 | docker logs fuseapp 224 | ``` 225 | 226 | - **For netstats client logs**: 227 | ```bash 228 | docker logs netstats 229 | ``` 230 | 231 | ## Step 7: Removing Maintenance Flag (Validator Nodes Only) 232 | 233 | After verifying that the Nethermind client is functioning correctly, remove the maintenance flag to re-enable validation. 234 | 235 | 1. **Remove the maintenance flag** using the following command: 236 | 237 | ```bash 238 | ./quickstart.sh -m 239 | ``` 240 | 241 | 2. **Wait for the next cycle** for the node to rejoin the active validation set. Verify that the node is starting 242 | 243 | to validate blocks again. 244 | 245 | > **Note**: To check if the validator node is back in the validation set, use the commands mentioned in Step 1. 246 | 247 | ## Support and Issues 248 | 249 | If you encounter any issues during the migration or have suggestions to improve this guide, please post an [issue](<(https://github.com/fuseio/fuse-network/issues)>) or create a pull request on this GitHub repo, using `[OE Migration]` in the title. This will help us promptly identify and address migration-related concerns. 250 | -------------------------------------------------------------------------------- /nethermind/README.md: -------------------------------------------------------------------------------- 1 | # Fuse Network - Nethermind Node Bootstrap Script 2 | 3 | This custom script is designed to help you easily bootstrap your own node for either the Fuse mainnet or the Spark testnet. 4 | 5 | Before proceeding, please ensure you have checked the minimum system requirements for Nethermind [here](https://docs.nethermind.io/validators/#hardware-configurations) and the required disk speed [here](https://docs.nethermind.io/get-started/system-requirements/#disk-speed). 6 | 7 | Additionally, it is crucial to review the [Security Considerations](https://docs.nethermind.io/fundamentals/security) for Nethermind nodes, if you plan to run a **validator** using the Nethermind client. 8 | 9 | ## Description 10 | 11 | > **Note:** Currently, the script supports the following roles: `node`, `bootnode`, `explorer` and `validator`. 12 | 13 | ```bash 14 | ./quickstart.sh 15 | 16 | The Fuse Client - Bootstrap Your Own Node 17 | 18 | Description: 19 | This script allows you to run your own Fuse node locally based on a specified role. 20 | 21 | Note: 22 | quickstart.sh supports the following Linux/Unix-based distributions: Ubuntu, Debian, Fedora, CentOS, RHEL. 23 | 24 | Usage: 25 | ./quickstart.sh [-r|-n|-k||-v|-h|-u|-m] 26 | 27 | Options: 28 | -r Specify the node role. Available roles: 'node', 'bootnode', 'explorer', 'validator' 29 | -n Network (mainnet or testnet). Available values: 'fuse' and 'spark' 30 | -k Node key name for https://health.fuse.io. Example: 'my-own-fuse-node' 31 | -v Script version 32 | -u Unjail a node (Validator only) 33 | -m Flag a node for maintenance (Validator only) 34 | -h Help page 35 | ``` 36 | 37 | ## How to run 38 | 39 | ```bash 40 | wget -O quickstart.sh https://raw.githubusercontent.com/fuseio/fuse-network/master/nethermind/quickstart.sh 41 | chmod 755 quickstart.sh 42 | ``` 43 | 44 | ```bash 45 | ./quickstart.sh -r [node_role] -n [network_name] -k [node_key] 46 | ``` 47 | 48 | > **Note:** If the node is already configured, repeating this step with the same arguments will update the client if a new version is available. 49 | 50 | ### Examples: 51 | 52 | ```bash 53 | # Run node for Fuse (Mainnet) 54 | ./quickstart.sh -r node -n fuse -k fusenet-node 55 | 56 | # Run bootnode for Spark (Testnet) 57 | ./quickstart.sh -r bootnode -n spark -k fusenet-spark-bootnode 58 | 59 | # Unjail a node 60 | ./quickstart.sh -u 61 | 62 | # Flag a node for maintenance 63 | ./quickstart.sh -m 64 | ``` 65 | 66 | ## Health Dashboard 67 | 68 | The node should appear on the [health dashboard](https://health.fuse.io) and can be monitored there. 69 | 70 | > For testnet: [Spark health dashboard](https://health.fusespark.io/) 71 | 72 | Additionally, configure Nethermind monitoring by following the instructions [here](https://docs.nethermind.io/monitoring/metrics/grafana-and-prometheus). 73 | 74 | ## Nethermind DB Snapshot 75 | 76 | To speed up node sync there are the snapshot download links. 77 | 78 | | Endpoint | Network | Type | Direct link (latest) | 79 | | ------------------------------ | ------- | --------- | ------------------------------------------------------ | 80 | | https://snapshot.fusespark.io | Spark | FastSync | https://snapshot.fusespark.io/nethermind/database.zip | 81 | | https://snapshot.fuse.io | Fuse | FastSync | https://snapshot.fuse.io/nethermind/database.zip | 82 | 83 | The archive file contains `database` folder, blockchain ledger, with `n` blocks depending on the snapshot date. 84 | 85 | --- 86 | -------------------------------------------------------------------------------- /nethermind/docker/README.md: -------------------------------------------------------------------------------- 1 | # Nethermind - Docker 2 | 3 | This folder contains the Docker setup to run a Nethermind-based blockchain node. The project is organized into sub-folders for easy management of the Nethermind client and a monitoring stack. 4 | 5 | 6 | ## Folder Structure 7 | 8 | - **client/** 9 | Contains the configuration and files needed to run the Nethermind blockchain client in various node roles, including standard nodes, bootnodes, archive nodes, and validator nodes. 10 | 11 | - **monitoring/** 12 | Contains the necessary files for running a monitoring stack based on Prometheus, Grafana, and Seq to monitor the performance, logs and health of the Nethermind nodes. 13 | 14 | Each sub-folder contains Docker configuration files to quickly spin up the required services and node roles. You can easily run the desired Nethermind client configuration and monitoring stack using Docker Compose. 15 | 16 | Refer to the individual `README.md` files in each sub-folder for specific setup instructions and configuration details. 17 | 18 | 19 | ## Requirements 20 | 21 | Before you begin, ensure your environment meets the following requirements: 22 | 23 | - **Docker** and **Docker Compose** installed. 24 | Supports both Docker Compose v1 and v2. You can install them from the official Docker documentation: 25 | [Install Docker](https://docs.docker.com/get-docker/) 26 | [Install Docker Compose](https://docs.docker.com/compose/install/) 27 | 28 | - Your server should be compatible with the minimal [Nethermind system requirements](https://docs.nethermind.io/get-started/system-requirements). Ensure that your hardware and OS configurations align with these requirements for optimal performance. 29 | 30 | - For running a `validator` node, a JSON-based wallet and a wallet password file are required to sign and validate blocks. 31 | 32 | 33 | ## Move setup from quickstart.sh to the Docker Compose stack 34 | 35 | There is the description how to migrate the node from `quickstart.sh` to the Docker Compose stack. Let's imagine that you have `validator` node role. Your folder's structure is: 36 | 37 | - `[root_folder]/quickstart.sh` - quickstart bash file to run the blockchain node; 38 | 39 | - `[root_folder]/fusenet/database` - blockchain node ledger; 40 | 41 | - `[root_folder]/fusenet/keystore` - blockchain node private key (wallet); 42 | 43 | - `[root_folder]/fusenet/logs` - blockchain client logs. 44 | 45 | --- 46 | 47 | There are the next steps to migrate everything smoothly: 48 | 49 | - Login on the server and stop your existing setup; 50 | 51 | - Clone GitHub repository: 52 | 53 | ```bash 54 | git clone https://github.com/fuseio/fuse-network.git 55 | ``` 56 | 57 | - As a convention each optional packages should be stored in `/opt` folder. Create the folder `/opt/nethermind/[network]`: 58 | 59 | ```bash 60 | mkdir -p /opt/nethermind/fuse 61 | ``` 62 | 63 | - Copy (enough disk space is required) the folders `[root_folder]/fusenet/database` and `[root_folder]/fusenet/keystore` to the new directory; 64 | 65 | - From cloned repository copy `docker-compose.validator.yaml`, `.validator.env` and `processes.json` files to the new directory; 66 | 67 | - The new folder structure should be: 68 | 69 | ```bash 70 | ls -a /opt/nethermind/fuse 71 | . database docker-compose.validator.yaml 72 | .. keystore processes.json .validator.env 73 | ``` 74 | 75 | - In the .validator.env file specify the next environment variables (variables already have specified, just need to provide values compatible with your setup): 76 | 77 | ```bash 78 | # Netstats instance name. Example: 'fuse-nethermind-validator-1_[wallet_address]' 79 | INSTANCE_NAME=fuse-nethermind-validator-1_[wallet_address] 80 | 81 | # Netstats contact details. Example: 'hello@nethermind.io' 82 | CONTACT_DETAILS=[contact_details] 83 | 84 | # Keystore (required for 'validator' node role, for empty variables specify wallet address) 85 | NETHERMIND_KEYSTORECONFIG_BLOCKAUTHORACCOUNT=[wallet_address] 86 | NETHERMIND_KEYSTORECONFIG_ENODEACCOUNT=[wallet_address] 87 | NETHERMIND_KEYSTORECONFIG_PASSWORDFILES=keystore/pass.pwd 88 | NETHERMIND_KEYSTORECONFIG_UNLOCKACCOUNTS=[wallet_address] 89 | ``` 90 | 91 | - Run everything: 92 | 93 | ```bash 94 | docker-compose -f docker-compose.validator.yaml --env-file .validator.env up -d 95 | ``` 96 | 97 | There are 3 Docker containers should be up and running: `nethermind`, `netstats` and `validator`. 98 | 99 | Almost the same approach looks for the other node roles. The difference is no need to specify the private key (wallet) parameters. 100 | -------------------------------------------------------------------------------- /nethermind/docker/client/.archive.env: -------------------------------------------------------------------------------- 1 | # --------------------------------- 2 | # - Generic environment variables - 3 | # --------------------------------- 4 | 5 | # Docker (image & tag) - Nethermind 6 | NETHERMIND_DOCKER_IMAGE_REPOSITORY=fusenet/node 7 | NETHERMIND_DOCKER_IMAGE_TAG=nethermind-1.25.4-v6.0.2 8 | 9 | # Docker (image & tag) - Netstats 10 | NETSTATS_DOCKER_IMAGE_REPOSITORY=fusenet/netstat 11 | NETSTATS_DOCKER_IMAGE_TAG=2.0.1 12 | 13 | # Network. Allowed values: 'fuse', 'spark' 14 | NETWORK=fuse 15 | 16 | # Netstats instance name. Example: 'fuse-nethermind-archive-1' 17 | INSTANCE_NAME=fuse-nethermind-archive-1 18 | 19 | # Netstats node role 20 | ROLE=Archive 21 | 22 | # Netstats contact details. Example: 'hello@nethermind.io' 23 | CONTACT_DETAILS=hello@example.com 24 | 25 | # Netstats version. Should be the same as NETSTATS_DOCKER_IMAGE_TAG variable 26 | NETSTATS_VERSION=2.0.1 27 | 28 | # Netstats server. Allowed values: 'https://health.fuse.io/ws', 'https://health.fusespark.io/ws' 29 | WS_SERVER=https://health.fuse.io/ws 30 | 31 | # Netstats secret. For both Fuse & Spark networks secret is the same 32 | WS_SECRET=i5WsUJWaMUHOS2CwvTRy 33 | 34 | # -------------------------------------------------------------------------------------------------------------------- 35 | # - Nethermind - related environment variables - 36 | # - - 37 | # - Full list of environment variables - https://docs.nethermind.io/fundamentals/configuration#options-by-namespaces - 38 | # -------------------------------------------------------------------------------------------------------------------- 39 | 40 | # Config. Allowed values: 'fuse', 'fuse_archive', 'fuse_validator', 'spark', 'spark_archive', 'spark_validator' 41 | NETHERMIND_CONFIG=fuse_archive 42 | 43 | # Folders (by default used the Docker Compose file root path) 44 | # NETHERMIND_DATABASE_FOLDER= 45 | # NETHERMIND_KEYSTORE_FOLDER= 46 | # NETHERMIND_LOGS_FOLDER= 47 | 48 | # Network (bootnodes, static peers) 49 | NETHERMIND_NETWORKCONFIG_BOOTNODES=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 50 | NETHERMIND_NETWORKCONFIG_MAXACTIVEPEERS=50 51 | NETHERMIND_NETWORKCONFIG_ONLYSTATICPEERS=false 52 | NETHERMIND_NETWORKCONFIG_STATICPEERS=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 53 | 54 | # Metrics (required for 'monitoring' stack, uncomment variables below to expose Nethermind metrics) 55 | # NETHERMIND_METRICSCONFIG_COUNTERSENABLED=false 56 | # NETHERMIND_METRICSCONFIG_ENABLED=true 57 | # NETHERMIND_METRICSCONFIG_ENABLEDBSIZEMETRICS=true 58 | # NETHERMIND_METRICSCONFIG_EXPOSEHOST=+ 59 | # NETHERMIND_METRICSCONFIG_EXPOSEPORT=9091 60 | # NETHERMIND_METRICSCONFIG_INTERVALSECONDS=5 61 | # NETHERMIND_METRICSCONFIG_NODENAME=Nethermind 62 | # NETHERMIND_METRICSCONFIG_PUSHGATEWAYURL= 63 | 64 | # Seq (optional, required for 'monitoring' stack, uncomment variables below to store logs on Seq instance) 65 | # NETHERMIND_SEQCONFIG_APIKEY= 66 | # NETHERMIND_SEQCONFIG_MINLEVEL=Info 67 | # NETHERMIND_SEQCONFIG_SERVERURL=http://seq 68 | 69 | # -------------------------------------------- 70 | # - Netstats - related environment variables - 71 | # -------------------------------------------- 72 | 73 | # Node environment 74 | NODE_ENV=production 75 | 76 | # Container host, RPC & P2P ports 77 | RPC_HOST=nethermind 78 | RPC_PORT=8545 79 | LISTENING_PORT=30303 80 | 81 | # Other Netstats variables (should be empty for this node role) 82 | BRIDGE_VERSION= 83 | FUSE_APP_VERSION= 84 | PARITY_VERSION= 85 | 86 | # Verbosity 87 | VERBOSITY=2 88 | -------------------------------------------------------------------------------- /nethermind/docker/client/.bootnode.env: -------------------------------------------------------------------------------- 1 | # --------------------------------- 2 | # - Generic environment variables - 3 | # --------------------------------- 4 | 5 | # Docker (image & tag) - Nethermind 6 | NETHERMIND_DOCKER_IMAGE_REPOSITORY=fusenet/node 7 | NETHERMIND_DOCKER_IMAGE_TAG=nethermind-1.25.4-v6.0.2 8 | 9 | # Docker (image & tag) - Netstats 10 | NETSTATS_DOCKER_IMAGE_REPOSITORY=fusenet/netstat 11 | NETSTATS_DOCKER_IMAGE_TAG=2.0.1 12 | 13 | # Network. Allowed values: 'fuse', 'spark' 14 | NETWORK=fuse 15 | 16 | # Netstats instance name. Example: 'fuse-nethermind-bootnode-1' 17 | INSTANCE_NAME=fuse-nethermind-bootnode-1 18 | 19 | # Netstats node role 20 | ROLE=Bootode 21 | 22 | # Netstats contact details. Example: 'hello@nethermind.io' 23 | CONTACT_DETAILS=hello@example.com 24 | 25 | # Netstats version. Should be the same as NETSTATS_DOCKER_IMAGE_TAG variable 26 | NETSTATS_VERSION=2.0.1 27 | 28 | # Netstats server. Allowed values: 'https://health.fuse.io/ws', 'https://health.fusespark.io/ws' 29 | WS_SERVER=https://health.fuse.io/ws 30 | 31 | # Netstats secret. For both Fuse & Spark networks secret is the same 32 | WS_SECRET=i5WsUJWaMUHOS2CwvTRy 33 | 34 | # -------------------------------------------------------------------------------------------------------------------- 35 | # - Nethermind - related environment variables - 36 | # - - 37 | # - Full list of environment variables - https://docs.nethermind.io/fundamentals/configuration#options-by-namespaces - 38 | # -------------------------------------------------------------------------------------------------------------------- 39 | 40 | # Config. Allowed values: 'fuse', 'fuse_archive', 'fuse_validator', 'spark', 'spark_archive', 'spark_validator' 41 | NETHERMIND_CONFIG=fuse 42 | 43 | # Folders (optional, by default used the Docker Compose file root path) 44 | # NETHERMIND_DATABASE_FOLDER= 45 | # NETHERMIND_KEYSTORE_FOLDER= 46 | # NETHERMIND_LOGS_FOLDER= 47 | 48 | # Network (bootnodes, static peers) 49 | NETHERMIND_NETWORKCONFIG_BOOTNODES=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 50 | NETHERMIND_NETWORKCONFIG_MAXACTIVEPEERS=50 51 | NETHERMIND_NETWORKCONFIG_ONLYSTATICPEERS=false 52 | NETHERMIND_NETWORKCONFIG_STATICPEERS=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 53 | 54 | # Metrics (optional, required for 'monitoring' stack, uncomment variables below to expose Nethermind metrics) 55 | # NETHERMIND_METRICSCONFIG_COUNTERSENABLED=false 56 | # NETHERMIND_METRICSCONFIG_ENABLED=true 57 | # NETHERMIND_METRICSCONFIG_ENABLEDBSIZEMETRICS=true 58 | # NETHERMIND_METRICSCONFIG_EXPOSEHOST=+ 59 | # NETHERMIND_METRICSCONFIG_EXPOSEPORT=9091 60 | # NETHERMIND_METRICSCONFIG_INTERVALSECONDS=5 61 | # NETHERMIND_METRICSCONFIG_NODENAME=Nethermind 62 | # NETHERMIND_METRICSCONFIG_PUSHGATEWAYURL= 63 | 64 | # Seq (optional, required for 'monitoring' stack, uncomment variables below to store logs on Seq instance) 65 | # NETHERMIND_SEQCONFIG_APIKEY= 66 | # NETHERMIND_SEQCONFIG_MINLEVEL=Info 67 | # NETHERMIND_SEQCONFIG_SERVERURL=http://seq 68 | 69 | # -------------------------------------------- 70 | # - Netstats - related environment variables - 71 | # -------------------------------------------- 72 | 73 | # Node environment 74 | NODE_ENV=production 75 | 76 | # Container host, RPC & P2P ports 77 | RPC_HOST=nethermind 78 | RPC_PORT=8545 79 | LISTENING_PORT=30303 80 | 81 | # Other Netstats variables (should be empty for this node role) 82 | BRIDGE_VERSION= 83 | FUSE_APP_VERSION= 84 | PARITY_VERSION= 85 | 86 | # Verbosity 87 | VERBOSITY=2 88 | -------------------------------------------------------------------------------- /nethermind/docker/client/.node.env: -------------------------------------------------------------------------------- 1 | # --------------------------------- 2 | # - Generic environment variables - 3 | # --------------------------------- 4 | 5 | # Docker (image & tag) - Nethermind 6 | NETHERMIND_DOCKER_IMAGE_REPOSITORY=fusenet/node 7 | NETHERMIND_DOCKER_IMAGE_TAG=nethermind-1.25.4-v6.0.2 8 | 9 | # Docker (image & tag) - Netstats 10 | NETSTATS_DOCKER_IMAGE_REPOSITORY=fusenet/netstat 11 | NETSTATS_DOCKER_IMAGE_TAG=2.0.1 12 | 13 | # Network. Allowed values: 'fuse', 'spark' 14 | NETWORK=fuse 15 | 16 | # Netstats instance name. Example: 'fuse-nethermind-node-1' 17 | INSTANCE_NAME=fuse-nethermind-node-1 18 | 19 | # Netstats node role 20 | ROLE=Node 21 | 22 | # Netstats contact details. Example: 'hello@nethermind.io' 23 | CONTACT_DETAILS=hello@example.com 24 | 25 | # Netstats version. Should be the same as NETSTATS_DOCKER_IMAGE_TAG variable 26 | NETSTATS_VERSION=2.0.1 27 | 28 | # Netstats server. Allowed values: 'https://health.fuse.io/ws', 'https://health.fusespark.io/ws' 29 | WS_SERVER=https://health.fuse.io/ws 30 | 31 | # Netstats secret. For both Fuse & Spark networks secret is the same 32 | WS_SECRET=i5WsUJWaMUHOS2CwvTRy 33 | 34 | # -------------------------------------------------------------------------------------------------------------------- 35 | # - Nethermind - related environment variables - 36 | # - - 37 | # - Full list of environment variables - https://docs.nethermind.io/fundamentals/configuration#options-by-namespaces - 38 | # -------------------------------------------------------------------------------------------------------------------- 39 | 40 | # Config. Allowed values: 'fuse', 'fuse_archive', 'fuse_validator', 'spark', 'spark_archive', 'spark_validator' 41 | NETHERMIND_CONFIG=fuse 42 | 43 | # Folders (optional, by default used the Docker Compose file root path) 44 | # NETHERMIND_DATABASE_FOLDER= 45 | # NETHERMIND_KEYSTORE_FOLDER= 46 | # NETHERMIND_LOGS_FOLDER= 47 | 48 | # Network (bootnodes, static peers) 49 | NETHERMIND_NETWORKCONFIG_BOOTNODES=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 50 | NETHERMIND_NETWORKCONFIG_MAXACTIVEPEERS=50 51 | NETHERMIND_NETWORKCONFIG_ONLYSTATICPEERS=false 52 | NETHERMIND_NETWORKCONFIG_STATICPEERS=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 53 | 54 | # Metrics (optional, required for 'monitoring' stack, uncomment variables below to expose Nethermind metrics) 55 | # NETHERMIND_METRICSCONFIG_COUNTERSENABLED=false 56 | # NETHERMIND_METRICSCONFIG_ENABLED=true 57 | # NETHERMIND_METRICSCONFIG_ENABLEDBSIZEMETRICS=true 58 | # NETHERMIND_METRICSCONFIG_EXPOSEHOST=+ 59 | # NETHERMIND_METRICSCONFIG_EXPOSEPORT=9091 60 | # NETHERMIND_METRICSCONFIG_INTERVALSECONDS=5 61 | # NETHERMIND_METRICSCONFIG_NODENAME=fuse-nethermind-node-1 62 | # NETHERMIND_METRICSCONFIG_PUSHGATEWAYURL= 63 | 64 | # Seq (optional, required for 'monitoring' stack, uncomment variables below to store logs on Seq instance) 65 | # NETHERMIND_SEQCONFIG_APIKEY= 66 | # NETHERMIND_SEQCONFIG_MINLEVEL=Info 67 | # NETHERMIND_SEQCONFIG_SERVERURL=http://seq 68 | 69 | # -------------------------------------------- 70 | # - Netstats - related environment variables - 71 | # -------------------------------------------- 72 | 73 | # Node environment 74 | NODE_ENV=production 75 | 76 | # Container host, RPC & P2P ports 77 | RPC_HOST=nethermind 78 | RPC_PORT=8545 79 | LISTENING_PORT=30303 80 | 81 | # Other Netstats variables (should be empty for this node role) 82 | BRIDGE_VERSION= 83 | FUSE_APP_VERSION= 84 | PARITY_VERSION= 85 | 86 | # Verbosity 87 | VERBOSITY=2 88 | -------------------------------------------------------------------------------- /nethermind/docker/client/.validator.env: -------------------------------------------------------------------------------- 1 | # --------------------------------- 2 | # - Generic environment variables - 3 | # --------------------------------- 4 | 5 | # Docker (image & tag) - Nethermind 6 | NETHERMIND_DOCKER_IMAGE_REPOSITORY=fusenet/node 7 | NETHERMIND_DOCKER_IMAGE_TAG=nethermind-1.25.4-v6.0.2 8 | 9 | # Docker (image & tag) - Netstats 10 | NETSTATS_DOCKER_IMAGE_REPOSITORY=fusenet/netstat 11 | NETSTATS_DOCKER_IMAGE_TAG=2.0.1 12 | 13 | # Docker (image & tag) - Validator. Note: for Fuse and Spark are different Docker images 14 | 15 | # - Fuse - 16 | VALIDATOR_DOCKER_IMAGE_REPOSITORY=fusenet/validator-app 17 | VALIDATOR_DOCKER_IMAGE_TAG=2.0.1 18 | 19 | # - Spark - 20 | # VALIDATOR_DOCKER_IMAGE_REPOSITORY=fusenet/spark-validator-app 21 | # VALIDATOR_DOCKER_IMAGE_TAG=2.0.3 22 | 23 | # Network. Allowed values: 'fuse', 'spark' 24 | NETWORK=fuse 25 | 26 | # Netstats instance name. Example: 'fuse-nethermind-validator-1_[wallet_address]' 27 | INSTANCE_NAME=fuse-nethermind-validator-1_0x0000000000000000000000000000000000000000 28 | 29 | # Netstats node role 30 | ROLE=Validator 31 | 32 | # Netstats contact details. Example: 'hello@nethermind.io' 33 | CONTACT_DETAILS=hello@example.com 34 | 35 | # Netstats version. Should be the same as NETSTATS_DOCKER_IMAGE_TAG variable 36 | NETSTATS_VERSION=2.0.1 37 | 38 | # Netstats server. Allowed values: 'https://health.fuse.io/ws', 'https://health.fusespark.io/ws' 39 | WS_SERVER=https://health.fuse.io/ws 40 | 41 | # Netstats secret. For both Fuse & Spark networks secret is the same 42 | WS_SECRET=i5WsUJWaMUHOS2CwvTRy 43 | 44 | # -------------------------------------------------------------------------------------------------------------------- 45 | # - Nethermind - related environment variables - 46 | # - - 47 | # - Full list of environment variables - https://docs.nethermind.io/fundamentals/configuration#options-by-namespaces - 48 | # -------------------------------------------------------------------------------------------------------------------- 49 | 50 | # Config. Allowed values: 'fuse', 'fuse_archive', 'fuse_validator', 'spark', 'spark_archive', 'spark_validator' 51 | NETHERMIND_CONFIG=fuse_validator 52 | 53 | # Folders (optional, by default used the Docker Compose file root path) 54 | # NETHERMIND_DATABASE_FOLDER= 55 | # NETHERMIND_KEYSTORE_FOLDER= 56 | # NETHERMIND_LOGS_FOLDER= 57 | 58 | # Keystore (required for 'validator' node role, for empty variables specify wallet address) 59 | # NETHERMIND_KEYSTORECONFIG_BLOCKAUTHORACCOUNT= 60 | # NETHERMIND_KEYSTORECONFIG_ENODEACCOUNT= 61 | NETHERMIND_KEYSTORECONFIG_PASSWORDFILES=keystore/pass.pwd 62 | # NETHERMIND_KEYSTORECONFIG_UNLOCKACCOUNTS= 63 | 64 | # Network (bootnodes, static peers) 65 | NETHERMIND_NETWORKCONFIG_BOOTNODES=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 66 | NETHERMIND_NETWORKCONFIG_MAXACTIVEPEERS=50 67 | NETHERMIND_NETWORKCONFIG_ONLYSTATICPEERS=false 68 | NETHERMIND_NETWORKCONFIG_STATICPEERS=enode://57ab1850bbd6cbdf48835d19ccf046efd1228e96c5a5db3a3cdbea3036838a99bd9fb9ff1cb708f34443766cf056e15a5d86d46adf431c15dbfe92af9ec65cf0@135.148.233.9:30303,enode://9001cf3b321c4c6035b95cf326b7b3524f238aa7bdcdd62f45cf51c4f5e3d0bce0cd5a714c109ebbe4a8806f2017bfd68902ab24e15ab1a2612a120923e31ae9@135.148.232.105:30303 69 | 70 | # JsonRpc (required for Netstats agent and to have an ability to interact with the node internally) 71 | NETHERMIND_JSONRPCCONFIG_ENABLED=true 72 | NETHERMIND_JSONRPCCONFIG_ENABLEDMODULES=[Eth,Subscribe,Trace,TxPool,Web3,Personal,Proof,Net,Parity,Health,Rpc] 73 | NETHERMIND_JSONRPCCONFIG_HOST=0.0.0.0 74 | NETHERMIND_JSONRPCCONFIG_PORT=8545 75 | 76 | # Metrics (optional, required for 'monitoring' stack, uncomment variables below to expose Nethermind metrics) 77 | # NETHERMIND_METRICSCONFIG_COUNTERSENABLED=false 78 | # NETHERMIND_METRICSCONFIG_ENABLED=true 79 | # NETHERMIND_METRICSCONFIG_ENABLEDBSIZEMETRICS=true 80 | # NETHERMIND_METRICSCONFIG_EXPOSEHOST=+ 81 | # NETHERMIND_METRICSCONFIG_EXPOSEPORT=9091 82 | # NETHERMIND_METRICSCONFIG_INTERVALSECONDS=5 83 | # NETHERMIND_METRICSCONFIG_NODENAME=Nethermind 84 | # NETHERMIND_METRICSCONFIG_PUSHGATEWAYURL= 85 | 86 | # Seq (optional, required for 'monitoring' stack, uncomment variables below to store logs on Seq instance) 87 | # NETHERMIND_SEQCONFIG_APIKEY= 88 | # NETHERMIND_SEQCONFIG_MINLEVEL=Info 89 | # NETHERMIND_SEQCONFIG_SERVERURL=http://seq 90 | 91 | # -------------------------------------------- 92 | # - Netstats - related environment variables - 93 | # -------------------------------------------- 94 | 95 | # Node environment 96 | NODE_ENV=production 97 | 98 | # Container host, RPC & P2P ports 99 | RPC_HOST=nethermind 100 | RPC_PORT=8545 101 | LISTENING_PORT=30303 102 | FUSE_APP_VERSION=2.0.1 103 | 104 | # Other Netstats variables (should be empty for this node role) 105 | BRIDGE_VERSION= 106 | PARITY_VERSION= 107 | 108 | # Verbosity 109 | VERBOSITY=2 110 | -------------------------------------------------------------------------------- /nethermind/docker/client/README.md: -------------------------------------------------------------------------------- 1 | # Nethermind - Docker - Client 2 | 3 | Folder contains the Docker Compose files to run the Nethermind node for Fuse or Spark networks. 4 | 5 | 6 | ## Usage 7 | 8 | Was used the simple approach with Nethermind configuration based on `docker-compose.[node_role].yaml` file and environment variables file `.[node_role].env`. 9 | 10 | To run the specific Nethermind configuration depending on the node role: 11 | 12 | ```bash 13 | docker-compose -f docker-compose.[node_role].yaml --env-file .[node_role].env up -d 14 | ``` 15 | 16 | or 17 | 18 | ```bash 19 | docker compose -f docker-compose.[node_role].yaml --env-file .[node_role].env up -d 20 | ``` 21 | 22 | depending on Docker Compose version. 23 | 24 | 25 | ## How-To 26 | 27 | How-To tutorials used to provide detailed info for Docker Compose setup usage. 28 | 29 | ### Update Docker image version 30 | 31 | There are the next steps to update Docker image version: 32 | 33 | 1. Go to the `[node_role].env` file; 34 | 35 | 2. Find the needed section `[APP]_DOCKER_IMAGE_TAG`; 36 | 37 | 3. Update the Docker image tag; 38 | 39 | 4. Run the command provided in `README.md` file, `Usage` section. 40 | -------------------------------------------------------------------------------- /nethermind/docker/client/docker-compose.archive.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | nethermind: 4 | # Official Docker image with nethermind application 5 | image: "${NETHERMIND_DOCKER_IMAGE_REPOSITORY}:${NETHERMIND_DOCKER_IMAGE_TAG}" 6 | 7 | # Pull image if not present locally 8 | pull_policy: if_not_present 9 | 10 | # Docker container name & hostname 11 | container_name: nethermind 12 | hostname: nethermind 13 | 14 | # Always restart if container go down 15 | restart: always 16 | 17 | # Entrypoint 18 | entrypoint: ./Nethermind.Runner --config ${NETHERMIND_CONFIG} 19 | 20 | # Docker logging parameters to avoid full storage issue 21 | logging: 22 | driver: json-file 23 | options: 24 | max-size: 10m 25 | max-file: 3 26 | compress: "true" 27 | 28 | # Ulimits parameters advised by Nethermind team 29 | ulimits: 30 | nofile: 31 | soft: 1000000 32 | hard: 1000000 33 | 34 | # Resource limits, specify 35 | deploy: 36 | resources: 37 | limits: 38 | cpus: "2.0" 39 | memory: "8GiB" 40 | 41 | # Container volumes 42 | volumes: 43 | - ${NETHERMIND_DATABASE_FOLDER:-./database}:/nethermind/nethermind_db/${NETWORK}_archive 44 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}:/nethermind/keystore 45 | - ${NETHERMIND_LOGS_FOLDER:-./logs}:/nethermind/logs 46 | 47 | # Environment file location, file provided in the 'docker' root directory 48 | env_file: 49 | - .archive.env 50 | 51 | # Docker container ports 52 | ports: 53 | - 30303:30303/tcp 54 | - 30303:30303/udp 55 | - 8545:8545/tcp 56 | - 8546:8546/tcp 57 | 58 | # Note: for 'Metrics' module add metrics port like '[port]:[port]/tcp' specified in 'NETHERMIND_METRICSCONFIG_EXPOSEPORT' environment variable 59 | # - 9091:9091/tcp 60 | 61 | # Docker network to dedicate the blockchain node infrastructure 62 | networks: 63 | - nethermind 64 | 65 | netstats: 66 | # Official Docker image with netstats-agent application 67 | image: "${NETSTATS_DOCKER_IMAGE_REPOSITORY}:${NETSTATS_DOCKER_IMAGE_TAG}" 68 | 69 | # Pull image if not present locally 70 | pull_policy: if_not_present 71 | 72 | # Container name 73 | container_name: netstats 74 | 75 | # Always restart if container go down 76 | restart: always 77 | 78 | # Entrypoint 79 | entrypoint: pm2 start processes.json --no-daemon 80 | 81 | # Environment file location, file provided in the 'docker' root directory 82 | env_file: 83 | - .archive.env 84 | 85 | # Volumes 86 | volumes: 87 | - ./processes.json:/app/processes.json 88 | 89 | # Logging 90 | logging: 91 | driver: json-file 92 | options: 93 | max-size: 10m 94 | max-file: 3 95 | compress: "true" 96 | 97 | # Networks 98 | networks: 99 | - nethermind 100 | 101 | # Dependency 102 | depends_on: 103 | - nethermind 104 | 105 | # Networks 106 | networks: 107 | nethermind: 108 | name: nethermind 109 | driver: bridge 110 | -------------------------------------------------------------------------------- /nethermind/docker/client/docker-compose.bootnode.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | nethermind: 4 | # Official Docker image with nethermind application 5 | image: "${NETHERMIND_DOCKER_IMAGE_REPOSITORY}:${NETHERMIND_DOCKER_IMAGE_TAG}" 6 | 7 | # Pull image if not present locally 8 | pull_policy: if_not_present 9 | 10 | # Docker container name & hostname 11 | container_name: nethermind 12 | hostname: nethermind 13 | 14 | # Always restart if container go down 15 | restart: always 16 | 17 | # Entrypoint 18 | entrypoint: ./Nethermind.Runner --config ${NETHERMIND_CONFIG} 19 | 20 | # Docker logging parameters to avoid full storage issue 21 | logging: 22 | driver: json-file 23 | options: 24 | max-size: 10m 25 | max-file: 3 26 | compress: "true" 27 | 28 | # Ulimits parameters advised by Nethermind team 29 | ulimits: 30 | nofile: 31 | soft: 1000000 32 | hard: 1000000 33 | 34 | # Resource limits, specify 35 | deploy: 36 | resources: 37 | limits: 38 | cpus: "2.0" 39 | memory: "8GiB" 40 | 41 | # Container volumes 42 | volumes: 43 | - ${NETHERMIND_DATABASE_FOLDER:-./database}:/nethermind/nethermind_db/${NETWORK} 44 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}:/nethermind/keystore 45 | - ${NETHERMIND_LOGS_FOLDER:-./logs}:/nethermind/logs 46 | 47 | # Environment file location, file provided in the 'docker' root directory 48 | env_file: 49 | - .bootnode.env 50 | 51 | # Docker container ports 52 | ports: 53 | - 30303:30303/tcp 54 | - 30303:30303/udp 55 | 56 | # Note: for 'Metrics' module add metrics port like '[port]:[port]/tcp' specified in 'NETHERMIND_METRICSCONFIG_EXPOSEPORT' environment variable 57 | # - 9091:9091/tcp 58 | 59 | # Docker network to dedicate the blockchain node infrastructure 60 | networks: 61 | - nethermind 62 | 63 | netstats: 64 | # Official Docker image with netstats-agent application 65 | image: "${NETSTATS_DOCKER_IMAGE_REPOSITORY}:${NETSTATS_DOCKER_IMAGE_TAG}" 66 | 67 | # Pull image if not present locally 68 | pull_policy: if_not_present 69 | 70 | # Container name 71 | container_name: netstats 72 | 73 | # Always restart if container go down 74 | restart: always 75 | 76 | # Entrypoint 77 | entrypoint: pm2 start processes.json --no-daemon 78 | 79 | # Environment file location, file provided in the 'docker' root directory 80 | env_file: 81 | - .bootnode.env 82 | 83 | # Volumes 84 | volumes: 85 | - ./processes.json:/app/processes.json 86 | 87 | # Logging 88 | logging: 89 | driver: json-file 90 | options: 91 | max-size: 10m 92 | max-file: 3 93 | compress: "true" 94 | 95 | # Networks 96 | networks: 97 | - nethermind 98 | 99 | # Dependency 100 | depends_on: 101 | - nethermind 102 | 103 | # Networks 104 | networks: 105 | nethermind: 106 | name: nethermind 107 | driver: bridge 108 | -------------------------------------------------------------------------------- /nethermind/docker/client/docker-compose.node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | nethermind: 4 | # Official Docker image with nethermind application 5 | image: "${NETHERMIND_DOCKER_IMAGE_REPOSITORY}:${NETHERMIND_DOCKER_IMAGE_TAG}" 6 | 7 | # Pull image if not present locally 8 | pull_policy: if_not_present 9 | 10 | # Docker container name & hostname 11 | container_name: nethermind 12 | hostname: nethermind 13 | 14 | # Always restart if container go down 15 | restart: always 16 | 17 | # Entrypoint 18 | entrypoint: ./Nethermind.Runner --config ${NETHERMIND_CONFIG} 19 | 20 | # Docker logging parameters to avoid full storage issue 21 | logging: 22 | driver: json-file 23 | options: 24 | max-size: 10m 25 | max-file: 3 26 | compress: "true" 27 | 28 | # Ulimits parameters advised by Nethermind team 29 | ulimits: 30 | nofile: 31 | soft: 1000000 32 | hard: 1000000 33 | 34 | # Resource limits, specify 35 | deploy: 36 | resources: 37 | limits: 38 | cpus: "2.0" 39 | memory: "8GiB" 40 | 41 | # Container volumes 42 | volumes: 43 | - ${NETHERMIND_DATABASE_FOLDER:-./database}:/nethermind/nethermind_db/${NETWORK} 44 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}:/nethermind/keystore 45 | - ${NETHERMIND_LOGS_FOLDER:-./logs}:/nethermind/logs 46 | 47 | # Environment file location, file provided in the 'docker' root directory 48 | env_file: 49 | - .node.env 50 | 51 | # Docker container ports 52 | ports: 53 | - 30303:30303/tcp 54 | - 30303:30303/udp 55 | - 8545:8545/tcp 56 | 57 | # Note: for 'Metrics' module add metrics port like '[port]:[port]/tcp' specified in 'NETHERMIND_METRICSCONFIG_EXPOSEPORT' environment variable 58 | # - 9091:9091/tcp 59 | 60 | # Docker network to dedicate the blockchain node infrastructure 61 | networks: 62 | - nethermind 63 | 64 | netstats: 65 | # Official Docker image with netstats-agent application 66 | image: "${NETSTATS_DOCKER_IMAGE_REPOSITORY}:${NETSTATS_DOCKER_IMAGE_TAG}" 67 | 68 | # Pull image if not present locally 69 | pull_policy: if_not_present 70 | 71 | # Container name 72 | container_name: netstats 73 | 74 | # Always restart if container go down 75 | restart: always 76 | 77 | # Entrypoint 78 | entrypoint: pm2 start processes.json --no-daemon 79 | 80 | # Environment file location, file provided in the 'docker' root directory 81 | env_file: 82 | - .node.env 83 | 84 | # Volumes 85 | volumes: 86 | - ./processes.json:/app/processes.json 87 | 88 | # Logging 89 | logging: 90 | driver: json-file 91 | options: 92 | max-size: 10m 93 | max-file: 3 94 | compress: "true" 95 | 96 | # Networks 97 | networks: 98 | - nethermind 99 | 100 | # Dependency 101 | depends_on: 102 | - nethermind 103 | 104 | # Networks 105 | networks: 106 | nethermind: 107 | name: nethermind 108 | driver: bridge 109 | -------------------------------------------------------------------------------- /nethermind/docker/client/docker-compose.validator.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | nethermind: 4 | # Official Docker image with nethermind application 5 | image: "${NETHERMIND_DOCKER_IMAGE_REPOSITORY}:${NETHERMIND_DOCKER_IMAGE_TAG}" 6 | 7 | # Pull image if not present locally 8 | pull_policy: if_not_present 9 | 10 | # Container name & hostname 11 | container_name: nethermind 12 | hostname: nethermind 13 | 14 | # Always restart if container go down 15 | restart: always 16 | 17 | # Entrypoint 18 | entrypoint: ./Nethermind.Runner --config ${NETHERMIND_CONFIG} 19 | 20 | # Docker logging parameters to avoid full storage issue 21 | logging: 22 | driver: json-file 23 | options: 24 | max-size: 10m 25 | max-file: 3 26 | compress: "true" 27 | 28 | # Ulimits parameters advised by Nethermind team 29 | ulimits: 30 | nofile: 31 | soft: 1000000 32 | hard: 1000000 33 | 34 | # Resource limits, specify 35 | deploy: 36 | resources: 37 | limits: 38 | cpus: "2.0" 39 | memory: "8GiB" 40 | 41 | # Container volumes 42 | volumes: 43 | - ${NETHERMIND_DATABASE_FOLDER:-./database}:/nethermind/nethermind_db/${NETWORK} 44 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}:/nethermind/keystore 45 | - ${NETHERMIND_LOGS_FOLDER:-./logs}:/nethermind/logs 46 | 47 | # Environment file location, file provided in the 'docker' root directory 48 | env_file: 49 | - .validator.env 50 | 51 | # Docker container ports 52 | ports: 53 | - 30303:30303/tcp 54 | - 30303:30303/udp 55 | 56 | # Note: for 'validator' node role do not recommend to expose RPC port in public 57 | # - 8545:8545/tcp 58 | 59 | # Note: for 'Metrics' module add metrics port like '[port]:[port]/tcp' specified in 'NETHERMIND_METRICSCONFIG_EXPOSEPORT' environment variable 60 | # - 9091:9091/tcp 61 | 62 | # Docker network to dedicate the blockchain node infrastructure 63 | networks: 64 | - nethermind 65 | 66 | netstats: 67 | # Official Docker image with netstats-agent application 68 | image: "${NETSTATS_DOCKER_IMAGE_REPOSITORY}:${NETSTATS_DOCKER_IMAGE_TAG}" 69 | 70 | # Pull image if not present locally 71 | pull_policy: if_not_present 72 | 73 | # Container name 74 | container_name: netstats 75 | 76 | # Always restart if container go down 77 | restart: always 78 | 79 | # Entrypoint 80 | entrypoint: pm2 start processes.json --no-daemon 81 | 82 | # Environment file location, file provided in the 'docker' root directory 83 | env_file: 84 | - .validator.env 85 | 86 | # Volumes 87 | volumes: 88 | - ./processes.json:/app/processes.json 89 | 90 | # Logging 91 | logging: 92 | driver: json-file 93 | options: 94 | max-size: 10m 95 | max-file: 3 96 | compress: "true" 97 | 98 | # Networks 99 | networks: 100 | - nethermind 101 | 102 | # Dependency 103 | depends_on: 104 | - nethermind 105 | 106 | validator: 107 | # Fuse self-managed Docker image - Validator application 108 | image: "${VALIDATOR_DOCKER_IMAGE_REPOSITORY}:${VALIDATOR_DOCKER_IMAGE_TAG}" 109 | 110 | # Always pull the image even if presents 111 | pull_policy: always 112 | 113 | # Container name 114 | container_name: validator 115 | 116 | # Always restart if there are some agent issues 117 | restart: always 118 | 119 | # Environment file location, file provided in the 'docker' root directory 120 | env_file: 121 | - .validator.env 122 | 123 | # Volumes 124 | volumes: 125 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}:/config/keys/FuseNetwork 126 | - ${NETHERMIND_KEYSTORE_FOLDER:-./keystore}/pass.pwd:/config/pass.pwd 127 | 128 | # Networks 129 | networks: 130 | - nethermind 131 | 132 | # Dependency 133 | depends_on: 134 | - nethermind 135 | 136 | # Networks 137 | networks: 138 | nethermind: 139 | name: nethermind 140 | driver: bridge 141 | -------------------------------------------------------------------------------- /nethermind/docker/client/processes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "netstats-agent", 4 | "script": "app.js", 5 | "log_date_format": "YYYY-MM-DD HH:mm Z", 6 | "merge_logs": false, 7 | "watch": false, 8 | "max_restarts": 10, 9 | "exec_interpreter": "node", 10 | "exec_mode": "fork_mode" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/.monitoring.env: -------------------------------------------------------------------------------- 1 | # -------------------------- 2 | # - Prometheus - variables - 3 | # -------------------------- 4 | 5 | PROMETHEUS_DOCKER_IMAGE_REPOSITORY=prom/prometheus 6 | PROMETHEUS_DOCKER_IMAGE_TAG=v2.53.2 7 | 8 | # ----------------------- 9 | # - Grafana - variables - 10 | # ----------------------- 11 | 12 | GRAFANA_DOCKER_IMAGE_REPOSITORY=grafana/grafana 13 | GRAFANA_DOCKER_IMAGE_TAG=11.1.2 14 | 15 | # ------------------- 16 | # - Seq - variables - 17 | # ------------------- 18 | 19 | SEQ_DOCKER_IMAGE_REPOSITORY=datalust/seq 20 | SEQ_DOCKER_IMAGE_TAG=2024.3 21 | ACCEPT_EULA=Y 22 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/README.md: -------------------------------------------------------------------------------- 1 | # Nethermind - Docker - Monitoring 2 | 3 | Folder contains the Docker Compose files to run the monitoring stack to monitor Nethermind client health and performance. Monitoring stack contains the next applications running near with the Nethermind client: 4 | 5 | - Prometheus - collect the metrics from Nethermind client exposed port (by default :9091/tcp); 6 | 7 | - Grafana - visualize Nethermind metrics; 8 | 9 | - Seq - collect Nethermind client logs. 10 | 11 | There is a table with exposed endpoints: 12 | 13 | | Application | Endpoint | 14 | | ------------ | --------------------- | 15 | | Prometheus | http://localhost:9090 | 16 | | Grafana | http://localhost:3000 | 17 | | Seq | http://localhost:5341 | 18 | 19 | 20 | > **Note**: if you want to expose it in public please take care about HTTPS connection and proper service authentication. 21 | 22 | 23 | ## Usage 24 | 25 | Was used the simple approach with initial `docker-compose.yaml` file and environment variables file `.monitoring.env`. 26 | 27 | To run the specific Nethermind configuration depending on the node role: 28 | 29 | ```bash 30 | docker-compose --env-file .monitoring.env up -d 31 | ``` 32 | 33 | or 34 | 35 | ```bash 36 | docker compose -f docker-compose.[node_role].yaml --env-file .[node_role].env up -d 37 | ``` 38 | 39 | depending on Docker Compose version. 40 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | prometheus: 4 | # Official Docker image with Prometheus application 5 | image: ${PROMETHEUS_DOCKER_IMAGE_REPOSITORY}:${PROMETHEUS_DOCKER_IMAGE_TAG} 6 | 7 | # Container name & hostname 8 | container_name: prometheus 9 | hostname: prometheus 10 | 11 | # Volumes 12 | volumes: 13 | - ./prometheus/:/etc/prometheus/ 14 | - prometheus_data:/prometheus 15 | 16 | # Command 17 | command: 18 | - "--config.file=/etc/prometheus/prometheus.yaml" 19 | - "--storage.tsdb.path=/prometheus" 20 | - "--web.console.libraries=/etc/prometheus/console_libraries" 21 | - "--web.console.templates=/etc/prometheus/consoles" 22 | - "--storage.tsdb.retention=200h" 23 | - "--web.enable-lifecycle" 24 | 25 | # Always restart if container go down 26 | restart: always 27 | 28 | # Expose Prometheus API port 29 | expose: 30 | - 9090 31 | 32 | # Ports 33 | ports: 34 | - "9090:9090/tcp" 35 | 36 | # Networks 37 | networks: 38 | - metrics 39 | 40 | # Use .env file for the variables 41 | env_file: 42 | - .monitoring.env 43 | 44 | grafana: 45 | # Official Docker image with Grafana application 46 | image: ${GRAFANA_DOCKER_IMAGE_REPOSITORY}:${GRAFANA_DOCKER_IMAGE_TAG} 47 | 48 | # Container name & hostname 49 | container_name: grafana 50 | hostname: grafana 51 | 52 | # Volumes 53 | volumes: 54 | - ./grafana/config.ini:/etc/grafana/config.ini 55 | - ./grafana/provisioning/:/etc/grafana/provisioning/ 56 | 57 | # Command (use grafana.ini file as a configuration) 58 | command: > 59 | --config /etc/grafana/config.ini 60 | 61 | # Always restart if container go down 62 | restart: always 63 | 64 | # Expose Grafana UI & API port 65 | expose: 66 | - 3000 67 | 68 | # Ports 69 | ports: 70 | - 3000:3000/tcp 71 | 72 | # Networks 73 | networks: 74 | - metrics 75 | 76 | # Use .env file for the variables 77 | env_file: 78 | - .monitoring.env 79 | 80 | seq: 81 | # Official Docker image with Seq application 82 | image: ${SEQ_DOCKER_IMAGE_REPOSITORY}:${SEQ_DOCKER_IMAGE_TAG} 83 | 84 | # Container name & hostname 85 | container_name: seq 86 | hostname: seq 87 | 88 | # Volumes 89 | volumes: 90 | - seq_data:/data 91 | 92 | # Always restart if container go down 93 | restart: always 94 | 95 | # Expose Seq UI & API port 96 | expose: 97 | - 5341 98 | 99 | # Ports 100 | ports: 101 | - 5341:80/tcp 102 | 103 | # Networks 104 | networks: 105 | - metrics 106 | 107 | # Use .env file for the variables 108 | env_file: 109 | - .monitoring.env 110 | 111 | # Use external - managed network 112 | networks: 113 | metrics: 114 | name: nethermind 115 | external: true 116 | 117 | # Volumes for persistent data 118 | volumes: 119 | prometheus_data: {} 120 | seq_data: {} 121 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/grafana/config.ini: -------------------------------------------------------------------------------- 1 | instance_name="nethermind-grafana" 2 | 3 | [server] 4 | protocol=http 5 | http_addr="0.0.0.0" 6 | http_port="3000" 7 | 8 | [database] 9 | type=sqlite3 10 | 11 | [security] 12 | admin_user="admin" 13 | admin_password="admin" # Edit here for a different password 14 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/grafana/provisioning/dashboards/dashboard.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | providers: 5 | - name: "Prometheus" 6 | orgId: 1 7 | folder: "" 8 | type: file 9 | disableDeletion: false 10 | editable: true 11 | options: 12 | path: /etc/grafana/provisioning/dashboards/ 13 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/grafana/provisioning/datasources/datasource.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | deleteDatasources: 5 | - name: Prometheus 6 | orgId: 1 7 | 8 | datasources: 9 | - name: Prometheus 10 | type: prometheus 11 | access: proxy 12 | orgId: 1 13 | url: http://prometheus:9090 14 | uid: "prometheus_ds" 15 | # database password, if used 16 | password: 17 | # database user, if used 18 | user: 19 | # database name, if used 20 | database: 21 | # enable/disable basic auth 22 | basicAuth: false 23 | # basic auth username 24 | basicAuthUser: 25 | # basic auth password 26 | basicAuthPassword: 27 | jsonData: 28 | timeInterval: "5s" 29 | # enable/disable with credentials headers 30 | withCredentials: 31 | # mark as default datasource. Max one per org 32 | isDefault: true 33 | # fields that will be converted to json and stored in json_data 34 | version: 1 35 | # allow users to edit datasources from the UI. 36 | editable: true 37 | -------------------------------------------------------------------------------- /nethermind/docker/monitoring/prometheus/prometheus.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | global: 3 | scrape_interval: 5s 4 | evaluation_interval: 5s 5 | 6 | scrape_configs: 7 | - job_name: "Nethermind" 8 | honor_labels: true 9 | static_configs: 10 | - targets: ["nethermind:9091"] 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuse-network", 3 | "version": "0.1.0", 4 | "description": "Fuse Network", 5 | "main": "", 6 | "scripts": { 7 | "test": "npx hardhat test", 8 | "compile": "npx hardhat compile", 9 | "flatten": "commands/flatten.sh", 10 | "app": "node app/index.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/fuseio/fuse-network.git" 15 | }, 16 | "standard": { 17 | "env": { 18 | "mocha": true, 19 | "truffle/globals": true 20 | }, 21 | "plugins": [ 22 | "truffle" 23 | ] 24 | }, 25 | "author": "Lior Rabin", 26 | "license": "MIT", 27 | "dependencies": { 28 | "dotenv": "^8.0.0", 29 | "ethers": "5.6.2", 30 | "keythereum": "^2.0.0", 31 | "openzeppelin-solidity": "2.1.0" 32 | }, 33 | "devDependencies": { 34 | "@nomicfoundation/hardhat-verify": "^2.0.3", 35 | "@nomiclabs/hardhat-ethers": "^2.2.3", 36 | "@nomiclabs/hardhat-truffle5": "^2.0.7", 37 | "@nomiclabs/hardhat-web3": "^2.0.0", 38 | "chai": "^4.2.0", 39 | "chai-as-promised": "^7.1.1", 40 | "chai-bn": "^0.1.1", 41 | "eth-gas-reporter": "^0.2.1", 42 | "hardhat": "^2.19.1", 43 | "node-jq": "^1.9.0", 44 | "solc": "0.4.24", 45 | "solidity-coverage": "^0.8.5", 46 | "web3": "^1.0.0-beta.36" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | const [deployer] = await ethers.getSigners(); 3 | 4 | console.log("Deploying contracts with the account:", deployer.address); 5 | 6 | const Migrations = await ethers.getContractFactory("Migrations"); 7 | const migrations = await Migrations.deploy(); 8 | 9 | console.log("Migrations deployed to:", migrations.address); 10 | } 11 | 12 | main() 13 | .then(() => process.exit(0)) 14 | .catch((error) => { 15 | console.error(error); 16 | process.exit(1); 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/2_deploy_contract.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const fs = require("fs"); 3 | const hre = require("hardhat"); 4 | const ethers = hre.ethers; 5 | const { assert } = require("chai"); 6 | 7 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 8 | 9 | const { INITIAL_VALIDATOR_ADDRESS, INITIAL_SUPPLY_GWEI, DEBUG } = process.env; 10 | 11 | const debug = (msg) => { 12 | if (DEBUG) console.log(msg); 13 | }; 14 | 15 | async function main() { 16 | const [deployer] = await ethers.getSigners(); 17 | 18 | console.log(`Deploying contracts with the account: ${deployer.address}`); 19 | 20 | let initialValidatorAddress = INITIAL_VALIDATOR_ADDRESS || ZERO_ADDRESS; 21 | initialValidatorAddress = ethers.utils.getAddress(initialValidatorAddress); 22 | let initialSupply = ethers.utils.parseUnits( 23 | INITIAL_SUPPLY_GWEI || "0", 24 | "gwei" 25 | ); 26 | 27 | // Contracts Factory 28 | const ConsensusFactory = await ethers.getContractFactory("Consensus"); 29 | const ProxyStorageFactory = await ethers.getContractFactory("ProxyStorage"); 30 | const BlockRewardFactory = await ethers.getContractFactory("BlockReward"); 31 | const VotingFactory = await ethers.getContractFactory("Voting"); 32 | const EternalStorageProxyFactory = await ethers.getContractFactory( 33 | "EternalStorageProxy" 34 | ); 35 | 36 | // Consensus 37 | const consensusImpl = await ConsensusFactory.deploy(); 38 | await consensusImpl.deployed(); 39 | debug(`Consensus Impl: ${consensusImpl.address}`); 40 | 41 | const consensusProxy = await EternalStorageProxyFactory.deploy( 42 | ZERO_ADDRESS, 43 | consensusImpl.address 44 | ); 45 | await consensusProxy.deployed(); 46 | debug(`Consensus Proxy: ${consensusProxy.address}`); 47 | 48 | const consensus = ConsensusFactory.attach(consensusProxy.address); 49 | debug(`Consensus : ${consensus.address}`); 50 | 51 | const tx = await consensus.initialize(initialValidatorAddress); 52 | await tx.wait(); 53 | 54 | assert.equal( 55 | initialValidatorAddress, 56 | (await consensus.getValidators())[0], 57 | "InitialValidatorAddress Mismatch" 58 | ); 59 | debug(`Initial Validator Address: ${initialValidatorAddress}`); 60 | 61 | // ProxyStorage 62 | const proxyStorageImpl = await ProxyStorageFactory.deploy(); 63 | await proxyStorageImpl.deployed(); 64 | debug(`ProxyStorage Impl: ${proxyStorageImpl.address}`); 65 | 66 | const storageProxy = await EternalStorageProxyFactory.deploy( 67 | ZERO_ADDRESS, 68 | proxyStorageImpl.address 69 | ); 70 | await storageProxy.deployed(); 71 | debug(`ProxyStorage Proxy: ${storageProxy.address}`); 72 | 73 | const proxyStorage = ProxyStorageFactory.attach(storageProxy.address); 74 | debug(`ProxyStorage: ${proxyStorage.address}`); 75 | 76 | const tx2 = await proxyStorage.initialize(consensus.address); 77 | await tx2.wait(); 78 | debug(`ProxyStorage - initialize: ${tx2.hash}`); 79 | assert.equal( 80 | consensus.address, 81 | await proxyStorage.getConsensus(), 82 | "Consensus Mismatch" 83 | ); 84 | 85 | const tx3 = await consensus.setProxyStorage(proxyStorage.address); 86 | await tx3.wait(); 87 | debug(`Consensus - setProxyStorage: ${tx3.hash}`); 88 | assert.equal( 89 | proxyStorage.address, 90 | await consensus.getProxyStorage(), 91 | "ProxyStorage Mismatch" 92 | ); 93 | 94 | // BlockReward 95 | const blockRewardImpl = await BlockRewardFactory.deploy(); 96 | await blockRewardImpl.deployed(); 97 | debug(`BlockReward Impl: ${blockRewardImpl.address}`); 98 | 99 | const blockRewardProxy = await EternalStorageProxyFactory.deploy( 100 | proxyStorage.address, 101 | blockRewardImpl.address 102 | ); 103 | await blockRewardProxy.deployed(); 104 | debug(`BlockReward Proxy: ${blockRewardProxy.address}`); 105 | 106 | const blockReward = BlockRewardFactory.attach(blockRewardProxy.address); 107 | debug(`BlockReward : ${blockReward.address}`); 108 | 109 | const tx4 = await blockReward.initialize(initialSupply); 110 | await tx4.wait(); 111 | debug(`BlockReward - initialize: ${tx4.hash}`); 112 | 113 | // Voting 114 | const votingImpl = await VotingFactory.deploy(); 115 | await votingImpl.deployed(); 116 | debug(`Voting Impl: ${votingImpl.address}`); 117 | 118 | const votingProxy = await EternalStorageProxyFactory.deploy( 119 | proxyStorage.address, 120 | votingImpl.address 121 | ); 122 | await votingProxy.deployed(); 123 | debug(`Voting Proxy: ${votingProxy.address}`); 124 | 125 | const voting = VotingFactory.attach(votingProxy.address); 126 | debug(`Voting: ${voting.address}`); 127 | const tx5 = await voting.initialize(); 128 | await tx5.wait(); 129 | debug(`Voting - initialize ${tx5.hash}`); 130 | 131 | // Check ProxyStorage 132 | assert.equal( 133 | proxyStorage.address, 134 | await blockReward.getProxyStorage(), 135 | "BlockReward ProxyStorage Mismatch" 136 | ); 137 | 138 | assert.equal( 139 | proxyStorage.address, 140 | await voting.getProxyStorage(), 141 | "Voting ProxyStorage Mismatch" 142 | ); 143 | 144 | assert.equal( 145 | await blockReward.getProxyStorage(), 146 | await voting.getProxyStorage(), 147 | "Voting.BlockReward ProxyStorage Mismatch" 148 | ); 149 | 150 | assert.equal( 151 | await blockReward.getProxyStorage(), 152 | await consensus.getProxyStorage(), 153 | "Consensus.BlockReward ProxyStorage Mismatch" 154 | ); 155 | 156 | // Initialize ProxyStorage 157 | const tx6 = await proxyStorage.initializeAddresses( 158 | blockReward.address, 159 | voting.address 160 | ); 161 | await tx6.wait(); 162 | assert.equal( 163 | blockReward.address, 164 | await proxyStorage.getBlockReward(), 165 | "BlockReward Mismatch" 166 | ); 167 | assert.equal( 168 | voting.address, 169 | await proxyStorage.getVoting(), 170 | "Voting Mismatch" 171 | ); 172 | debug( 173 | `ProxyStorage - initializeAddresses: ${blockReward.address}, ${voting.address}, ${tx6.hash}` 174 | ); 175 | 176 | console.log( 177 | ` 178 | Deploying contracts with the account ............. ${deployer.address} 179 | Block Reward Implementation ...................... ${blockRewardImpl.address} 180 | Block Reward Proxy ............................... ${blockReward.address} 181 | Consensus Implementation ......................... ${consensusImpl.address} 182 | Consensus Proxy .................................. ${consensus.address} 183 | ProxyStorage Implementation ...................... ${proxyStorageImpl.address} 184 | ProxyStorage Proxy ............................... ${proxyStorage.address} 185 | Voting Implementation ............................ ${votingImpl.address} 186 | Voting Proxy ..................................... ${voting.address} 187 | ` 188 | ); 189 | } 190 | 191 | main() 192 | .then(() => process.exit(0)) 193 | .catch((error) => { 194 | console.error(error); 195 | process.exit(1); 196 | }); 197 | -------------------------------------------------------------------------------- /scripts/3_validator_stake.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const hre = require("hardhat"); 3 | const ethers = hre.ethers; 4 | const { assert } = require("chai"); 5 | 6 | const { CONSENSUS_PROXY, CREDENTIALS_ADDRESS } = process.env; 7 | 8 | async function main() { 9 | let consensusAddress = ethers.utils.getAddress(CONSENSUS_PROXY); 10 | let credentialsAddress = ethers.utils.getAddress(CREDENTIALS_ADDRESS); 11 | 12 | let stakeAmount = ethers.utils.parseUnits("100000", "ether"); 13 | console.log(`Consensus Contract: ${consensusAddress}`); 14 | console.log(`Staking Amount: ${stakeAmount}`); 15 | console.log(`Staking Address: ${credentialsAddress}`); 16 | 17 | const [validator] = await ethers.getSigners(); 18 | assert.equal( 19 | credentialsAddress, 20 | validator.address, 21 | "Staking Account Mismatch" 22 | ); 23 | 24 | const ConsensusFactory = await ethers.getContractFactory("Consensus"); 25 | const consensus = ConsensusFactory.attach(CONSENSUS_PROXY); 26 | 27 | const tx = await consensus.stake({ value: stakeAmount }); 28 | await tx.wait(); 29 | assert.equal( 30 | (await consensus.stakeAmount(validator.address)).toString(), 31 | stakeAmount.toString(), 32 | "Stake Amount Mismatch" 33 | ); 34 | console.log( 35 | `Validator: ${validator.address}, Staked: ${stakeAmount}, tx: ${tx.hash}` 36 | ); 37 | } 38 | 39 | main() 40 | .then(() => process.exit(0)) 41 | .catch((error) => { 42 | console.error(error); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /scripts/4_verify_contracts.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | require("dotenv").config(); 3 | 4 | const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 5 | 6 | const { 7 | BLOCK_REWARD_IMPLEMENTATION, 8 | BLOCK_REWARD_PROXY, 9 | CONSENSUS_IMPLEMENTATION, 10 | CONSENSUS_PROXY, 11 | PROXY_STORAGE_IMPLEMENTATION, 12 | PROXY_STORAGE_PROXY, 13 | VOTING_IMPLEMENTATION, 14 | VOTING_PROXY, 15 | } = process.env; 16 | 17 | const contractAddresses = { 18 | blockRewardImplementation: BLOCK_REWARD_IMPLEMENTATION, 19 | blockRewardProxy: BLOCK_REWARD_PROXY, 20 | consensusImplementation: CONSENSUS_IMPLEMENTATION, 21 | consensusProxy: CONSENSUS_PROXY, 22 | proxyStorageImplementation: PROXY_STORAGE_IMPLEMENTATION, 23 | proxyStorageProxy: PROXY_STORAGE_PROXY, 24 | votingImplementation: VOTING_IMPLEMENTATION, 25 | votingProxy: VOTING_PROXY, 26 | }; 27 | 28 | async function main() { 29 | // Consensus Implementation 30 | await hre.run("verify:verify", { 31 | address: contractAddresses.consensusImplementation, 32 | }); 33 | // Consensus Proxy contract address 34 | await hre.run("verify:verify", { 35 | address: contractAddresses.consensusProxy, 36 | constructorArguments: [ 37 | ZERO_ADDRESS, 38 | contractAddresses.consensusImplementation, 39 | ], 40 | }); 41 | 42 | // Proxy Storage Implementation 43 | await hre.run("verify:verify", { 44 | address: contractAddresses.proxyStorageImplementation, 45 | }); 46 | // Proxy Storage Proxy contract address 47 | await hre.run("verify:verify", { 48 | address: contractAddresses.proxyStorageProxy, 49 | constructorArguments: [ 50 | ZERO_ADDRESS, 51 | contractAddresses.proxyStorageImplementation, 52 | ], 53 | }); 54 | 55 | // Block Reward Implementation 56 | await hre.run("verify:verify", { 57 | address: contractAddresses.blockRewardImplementation, 58 | }); 59 | // Block Reward Proxy contract address 60 | await hre.run("verify:verify", { 61 | address: contractAddresses.blockRewardProxy, 62 | constructorArguments: [ 63 | contractAddresses.proxyStorageProxy, 64 | contractAddresses.blockRewardImplementation, 65 | ], 66 | }); 67 | 68 | // Voting Implementation 69 | await hre.run("verify:verify", { 70 | address: contractAddresses.votingImplementation, 71 | }); 72 | // Voting Proxy contract address 73 | await hre.run("verify:verify", { 74 | address: contractAddresses.votingProxy, 75 | constructorArguments: [ 76 | contractAddresses.proxyStorageProxy, 77 | contractAddresses.votingImplementation, 78 | ], 79 | }); 80 | } 81 | 82 | main() 83 | .then(() => process.exit(0)) 84 | .catch((error) => { 85 | console.error(error); 86 | process.exit(1); 87 | }); 88 | -------------------------------------------------------------------------------- /scripts/5_validator_operations.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const hre = require("hardhat"); 3 | const ethers = hre.ethers; 4 | const { assert } = require("chai"); 5 | 6 | const { CONSENSUS_PROXY, CREDENTIALS_ADDRESS, VOTING_PROXY } = process.env; 7 | 8 | async function main() { 9 | let consensusAddress = ethers.utils.getAddress(CONSENSUS_PROXY); 10 | let votingAddress = ethers.utils.getAddress(VOTING_PROXY); 11 | let credentialsAddress = ethers.utils.getAddress(CREDENTIALS_ADDRESS); 12 | 13 | const ConsensusFactory = await ethers.getContractFactory("Consensus"); 14 | const consensus = ConsensusFactory.attach(CONSENSUS_PROXY); 15 | const VotingFactory = await ethers.getContractFactory("Voting"); 16 | const voting = VotingFactory.attach(VOTING_PROXY); 17 | 18 | console.log(`Consensus Contract: ${consensusAddress}`); 19 | console.log(`Voting Contract: ${votingAddress}`); 20 | console.log(`Credentials Address: ${credentialsAddress}`); 21 | 22 | const [validator] = await ethers.getSigners(); 23 | console.log(`validator Address: ${validator.address}`); 24 | assert.equal(credentialsAddress, validator.address, "Account Mismatch"); 25 | 26 | const action = "unJail"; 27 | 28 | if (action === "unJail") { 29 | const tx = await consensus.unJail(); 30 | await tx.wait(); 31 | 32 | console.log(`Validator - unJail: ${validator.address}, tx: ${tx.hash}`); 33 | } else if (action === "vote") { 34 | const ballotId = 1; 35 | const tx = await voting.vote(ballotId, 1); //(id - the ballot id, choice - 1 is accept, 2 is reject) 36 | const receipt = await tx.wait(); 37 | 38 | const voteEvent = receipt.events.find((event) => event.event === "Vote"); 39 | const id = voteEvent.args[0]; 40 | const choice = voteEvent.args[1]; 41 | const voter = voteEvent.args[2]; 42 | 43 | console.log( 44 | `Validator - voted: ${validator.address}, tx: ${tx.hash}, vote_id: ${id}, choice: ${choice}, voter: ${voter}` 45 | ); 46 | } else if (action === "maintenance") { 47 | const tx = await consensus.maintenance(); 48 | await tx.wait(); 49 | 50 | console.log( 51 | `Validator - maintenance: ${validator.address}, tx: ${tx.hash}` 52 | ); 53 | } else { 54 | console.log("Invalid action"); 55 | } 56 | } 57 | 58 | main() 59 | .then(() => process.exit(0)) 60 | .catch((error) => { 61 | console.error(error); 62 | process.exit(1); 63 | }); 64 | -------------------------------------------------------------------------------- /scripts/6_deploy_consensus_open_vote.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const hre = require("hardhat"); 3 | const ethers = hre.ethers; 4 | const { assert } = require("chai"); 5 | 6 | const { VOTING_PROXY, CREDENTIALS_ADDRESS } = process.env; 7 | 8 | async function main() { 9 | const [deployer] = await ethers.getSigners(); 10 | console.log(`Deploying contracts with the account: ${deployer.address}`); 11 | 12 | let credentialsAddress = ethers.utils.getAddress(CREDENTIALS_ADDRESS); 13 | 14 | // Contracts Factory 15 | const ConsensusFactory = await ethers.getContractFactory("Consensus"); 16 | const VotingFactory = await ethers.getContractFactory("Voting"); 17 | 18 | // Consensus 19 | const consensusImpl = await ConsensusFactory.deploy(); 20 | await consensusImpl.deployed(); 21 | console.log(`New Consensus Impl: ${consensusImpl.address}`); 22 | 23 | // Verify Consensus 24 | await hre.run("verify:verify", { 25 | address: consensusImpl.address, 26 | }); 27 | 28 | // Open Vote 29 | let votingAddress = ethers.utils.getAddress(VOTING_PROXY); 30 | console.log(`Voting Contract: ${votingAddress}`); 31 | 32 | const [validator] = await ethers.getSigners(); 33 | console.log(`validator Address: ${validator.address}`); 34 | console.log(`Credentials Address: ${credentialsAddress}`); 35 | assert.equal(credentialsAddress, validator.address, "Account Mismatch"); 36 | 37 | let voting = VotingFactory.attach(votingAddress); 38 | 39 | let newConcensusAddress = ethers.utils.getAddress(consensusImpl.address); 40 | 41 | const tx = await voting.newBallot( 42 | 1, // startAfterNumberOfCycles - number of cycles (minimum 1) after which the ballot is open for voting 43 | 2, // cyclesDuration - number of cycles (minimum 2) for the ballot to remain open for voting 44 | 1, // contractType: 1 - Consensus, 2 - BlockReward, 3 - ProxyStorage, 4 - Voting 45 | newConcensusAddress, 46 | `double jail fix` 47 | ); 48 | const receipt = await tx.wait(); 49 | const newBallotEvent = receipt.events.find( 50 | (event) => event.event === "BallotCreated" 51 | ); 52 | const ballotId = newBallotEvent.args[0]; 53 | const ballotAddress = newBallotEvent.args[1]; 54 | 55 | console.log( 56 | `newBallot - ballotId: ${ballotId}, address: ${ballotAddress}, tx: ${tx.hash}` 57 | ); 58 | } 59 | 60 | main() 61 | .then(() => process.exit(0)) 62 | .catch((error) => { 63 | console.error(error); 64 | process.exit(1); 65 | }); 66 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | const { BN, toBN, toWei } = web3.utils; 2 | 3 | require("chai") 4 | .use(require("chai-as-promised")) 5 | .use(require("chai-bn")(BN)) 6 | .should(); 7 | 8 | exports.SYSTEM_ADDRESS = "0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"; 9 | exports.ZERO_AMOUNT = toWei(toBN(0), "ether"); 10 | exports.ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 11 | exports.ERROR_MSG = "Transaction reverted without a reason string"; 12 | exports.ERROR_MSG_OPCODE = 13 | "Transaction reverted without a reason: invalid opcode"; 14 | exports.INVALID_ARGUMENTS = "Invalid number of arguments to Solidity function"; 15 | exports.RANDOM_ADDRESS = "0xc0ffee254729296a45a3885639AC7E10F9d54979"; 16 | 17 | exports.ZERO = toBN(0); 18 | exports.ONE = toBN(1); 19 | exports.TWO = toBN(2); 20 | exports.THREE = toBN(3); 21 | exports.FOUR = toBN(4); 22 | exports.FIVE = toBN(5); 23 | exports.TEN = toBN(10); 24 | 25 | exports.advanceTime = (seconds) => { 26 | return new Promise((resolve, reject) => { 27 | web3.currentProvider.send( 28 | { 29 | jsonrpc: "2.0", 30 | method: "evm_increaseTime", 31 | params: [seconds], 32 | id: new Date().getTime(), 33 | }, 34 | (err, result) => { 35 | if (err) { 36 | return reject(err); 37 | } 38 | return resolve(result); 39 | } 40 | ); 41 | }); 42 | }; 43 | exports.advanceBlocks = (n) => { 44 | return new Promise((resolve, reject) => { 45 | const tasks = []; 46 | for (let i = 0; i < n; i++) { 47 | tasks.push( 48 | new Promise((resolve, reject) => { 49 | web3.currentProvider.send( 50 | { 51 | jsonrpc: "2.0", 52 | method: "evm_mine", 53 | id: new Date().getTime(), 54 | }, 55 | async (err, result) => { 56 | if (err) { 57 | return reject(err); 58 | } 59 | const newBlock = await web3.eth.getBlock("latest"); 60 | const newBlockNumber = newBlock.number; 61 | return resolve(newBlockNumber); 62 | } 63 | ); 64 | }) 65 | ); 66 | } 67 | return tasks 68 | .reduce((promiseChain, currentTask) => { 69 | return promiseChain.then((chainResults) => 70 | currentTask.then((currentResult) => [...chainResults, currentResult]) 71 | ); 72 | }, Promise.resolve([])) 73 | .then((results) => { 74 | resolve(results[results.length - 1]); 75 | }); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /test/proxyStorage.test.js: -------------------------------------------------------------------------------- 1 | const Consensus = artifacts.require("ConsensusMock.sol"); 2 | const ProxyStorage = artifacts.require("ProxyStorageMock.sol"); 3 | const EternalStorageProxy = artifacts.require("EternalStorageProxyMock.sol"); 4 | const BlockReward = artifacts.require("BlockReward.sol"); 5 | const Voting = artifacts.require("Voting.sol"); 6 | const { ERROR_MSG, ZERO_ADDRESS } = require("./helpers"); 7 | const { toBN, toWei } = web3.utils; 8 | 9 | contract("ProxyStorage", async (accounts) => { 10 | let proxyStorageImpl, proxy, proxyStorage; 11 | let owner = accounts[0]; 12 | let nonOwner = accounts[1]; 13 | let blockReward, voting; 14 | 15 | beforeEach(async () => { 16 | // Consensus 17 | consensusImpl = await Consensus.new(); 18 | proxy = await EternalStorageProxy.new(ZERO_ADDRESS, consensusImpl.address); 19 | consensus = await Consensus.at(proxy.address); 20 | await consensus.initialize(owner); 21 | 22 | // ProxyStorage 23 | proxyStorageImpl = await ProxyStorage.new(); 24 | proxy = await EternalStorageProxy.new( 25 | ZERO_ADDRESS, 26 | proxyStorageImpl.address 27 | ); 28 | proxyStorage = await ProxyStorage.at(proxy.address); 29 | 30 | // BlockReward 31 | blockRewardImpl = await BlockReward.new(); 32 | proxy = await EternalStorageProxy.new( 33 | proxyStorage.address, 34 | blockRewardImpl.address 35 | ); 36 | blockReward = await BlockReward.at(proxy.address); 37 | 38 | // Voting 39 | votingImpl = await Voting.new(); 40 | proxy = await EternalStorageProxy.new( 41 | proxyStorage.address, 42 | votingImpl.address 43 | ); 44 | voting = await Voting.at(proxy.address); 45 | }); 46 | 47 | describe("initialize", async () => { 48 | it("should be successful", async () => { 49 | await proxyStorage.initialize(consensus.address).should.be.fulfilled; 50 | true.should.be.equal(await proxyStorage.isInitialized()); 51 | consensus.address.should.be.equal(await proxyStorage.getConsensus()); 52 | }); 53 | }); 54 | 55 | describe("initializeAddresses", async () => { 56 | beforeEach(async () => { 57 | await proxyStorage.initialize(consensus.address); 58 | }); 59 | it("should fail if not called from owner", async () => { 60 | await proxyStorage 61 | .initializeAddresses(blockReward.address, voting.address, { 62 | from: nonOwner, 63 | }) 64 | .should.be.rejectedWith(ERROR_MSG); 65 | }); 66 | it("should be successful", async () => { 67 | let { logs } = await proxyStorage.initializeAddresses( 68 | blockReward.address, 69 | voting.address, 70 | { from: owner } 71 | ).should.be.fulfilled; 72 | logs.length.should.be.equal(1); 73 | logs[0].event.should.be.equal("ProxyInitialized"); 74 | logs[0].args.consensus.should.be.equal(consensus.address); 75 | logs[0].args.blockReward.should.be.equal(blockReward.address); 76 | logs[0].args.voting.should.be.equal(voting.address); 77 | 78 | consensus.address.should.be.equal(await proxyStorage.getConsensus()); 79 | blockReward.address.should.be.equal(await proxyStorage.getBlockReward()); 80 | voting.address.should.be.equal(await proxyStorage.getVoting()); 81 | }); 82 | it("should not be called twice", async () => { 83 | await proxyStorage.initializeAddresses( 84 | blockReward.address, 85 | voting.address, 86 | { from: owner } 87 | ).should.be.fulfilled; 88 | 89 | await proxyStorage 90 | .initializeAddresses(blockReward.address, voting.address, { 91 | from: owner, 92 | }) 93 | .should.be.rejectedWith(ERROR_MSG); 94 | }); 95 | }); 96 | 97 | describe("upgradeTo", async () => { 98 | let proxyStorageNew; 99 | let proxyStorageStub = accounts[8]; 100 | beforeEach(async () => { 101 | proxyStorageNew = await ProxyStorage.new(); 102 | await proxy.setProxyStorageMock(proxyStorageStub); 103 | }); 104 | it("should only be called by ProxyStorage (this)", async () => { 105 | await proxy 106 | .upgradeTo(proxyStorageNew.address, { from: owner }) 107 | .should.be.rejectedWith(ERROR_MSG); 108 | let { logs } = await proxy.upgradeTo(proxyStorageNew.address, { 109 | from: proxyStorageStub, 110 | }); 111 | logs[0].event.should.be.equal("Upgraded"); 112 | await proxy.setProxyStorageMock(proxyStorage.address); 113 | }); 114 | it("should change implementation address", async () => { 115 | await proxy.upgradeTo(proxyStorageNew.address, { 116 | from: proxyStorageStub, 117 | }); 118 | await proxy.setProxyStorageMock(proxyStorage.address); 119 | proxyStorageNew.address.should.be.equal(await proxy.getImplementation()); 120 | }); 121 | it("should increment implementation version", async () => { 122 | let proxyStorageOldVersion = await proxy.getVersion(); 123 | let proxyStorageNewVersion = proxyStorageOldVersion.add(toBN(1)); 124 | await proxy.upgradeTo(proxyStorageNew.address, { 125 | from: proxyStorageStub, 126 | }); 127 | await proxy.setProxyStorageMock(proxyStorage.address); 128 | proxyStorageNewVersion.should.be.bignumber.equal( 129 | await proxy.getVersion() 130 | ); 131 | }); 132 | it("should work after upgrade", async () => { 133 | await proxy.upgradeTo(proxyStorageNew.address, { 134 | from: proxyStorageStub, 135 | }); 136 | await proxy.setProxyStorageMock(proxyStorage.address); 137 | proxyStorageNew = await ProxyStorage.at(proxy.address); 138 | false.should.be.equal(await proxyStorageNew.isInitialized()); 139 | await proxyStorageNew.initialize(consensus.address).should.be.fulfilled; 140 | true.should.be.equal(await proxyStorageNew.isInitialized()); 141 | }); 142 | }); 143 | }); 144 | --------------------------------------------------------------------------------