at 21.11-pre 7.5 kB view raw
1{ config, pkgs, lib, ... }: 2let 3 4 inherit (lib) mkEnableOption mkIf mkOption literalExample types optionalString; 5 6 cfg = config.services.quorum; 7 dataDir = "/var/lib/quorum"; 8 genesisFile = pkgs.writeText "genesis.json" (builtins.toJSON cfg.genesis); 9 staticNodesFile = pkgs.writeText "static-nodes.json" (builtins.toJSON cfg.staticNodes); 10 11in { 12 options = { 13 14 services.quorum = { 15 enable = mkEnableOption "Quorum blockchain daemon"; 16 17 user = mkOption { 18 type = types.str; 19 default = "quorum"; 20 description = "The user as which to run quorum."; 21 }; 22 23 group = mkOption { 24 type = types.str; 25 default = cfg.user; 26 description = "The group as which to run quorum."; 27 }; 28 29 port = mkOption { 30 type = types.port; 31 default = 21000; 32 description = "Override the default port on which to listen for connections."; 33 }; 34 35 nodekeyFile = mkOption { 36 type = types.path; 37 default = "${dataDir}/nodekey"; 38 description = "Path to the nodekey."; 39 }; 40 41 staticNodes = mkOption { 42 type = types.listOf types.str; 43 default = []; 44 example = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ]; 45 description = "List of validator nodes."; 46 }; 47 48 privateconfig = mkOption { 49 type = types.str; 50 default = "ignore"; 51 description = "Configuration of privacy transaction manager."; 52 }; 53 54 syncmode = mkOption { 55 type = types.enum [ "fast" "full" "light" ]; 56 default = "full"; 57 description = "Blockchain sync mode."; 58 }; 59 60 blockperiod = mkOption { 61 type = types.int; 62 default = 5; 63 description = "Default minimum difference between two consecutive block's timestamps in seconds."; 64 }; 65 66 permissioned = mkOption { 67 type = types.bool; 68 default = true; 69 description = "Allow only a defined list of nodes to connect."; 70 }; 71 72 rpc = { 73 enable = mkOption { 74 type = types.bool; 75 default = true; 76 description = "Enable RPC interface."; 77 }; 78 79 address = mkOption { 80 type = types.str; 81 default = "0.0.0.0"; 82 description = "Listening address for RPC connections."; 83 }; 84 85 port = mkOption { 86 type = types.port; 87 default = 22004; 88 description = "Override the default port on which to listen for RPC connections."; 89 }; 90 91 api = mkOption { 92 type = types.str; 93 default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul"; 94 description = "API's offered over the HTTP-RPC interface."; 95 }; 96 }; 97 98 ws = { 99 enable = mkOption { 100 type = types.bool; 101 default = true; 102 description = "Enable WS-RPC interface."; 103 }; 104 105 address = mkOption { 106 type = types.str; 107 default = "0.0.0.0"; 108 description = "Listening address for WS-RPC connections."; 109 }; 110 111 port = mkOption { 112 type = types.port; 113 default = 8546; 114 description = "Override the default port on which to listen for WS-RPC connections."; 115 }; 116 117 api = mkOption { 118 type = types.str; 119 default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul"; 120 description = "API's offered over the WS-RPC interface."; 121 }; 122 123 origins = mkOption { 124 type = types.str; 125 default = "*"; 126 description = "Origins from which to accept websockets requests"; 127 }; 128 }; 129 130 genesis = mkOption { 131 type = types.nullOr types.attrs; 132 default = null; 133 example = literalExample '' { 134 alloc = { 135 a47385db68718bdcbddc2d2bb7c54018066ec111 = { 136 balance = "1000000000000000000000000000"; 137 }; 138 }; 139 coinbase = "0x0000000000000000000000000000000000000000"; 140 config = { 141 byzantiumBlock = 4; 142 chainId = 494702925; 143 eip150Block = 2; 144 eip155Block = 3; 145 eip158Block = 3; 146 homesteadBlock = 1; 147 isQuorum = true; 148 istanbul = { 149 epoch = 30000; 150 policy = 0; 151 }; 152 }; 153 difficulty = "0x1"; 154 extraData = "0x0000000000000000000000000000000000000000000000000000000000000000f85ad59438f0508111273d8e482f49410ca4078afc86a961b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"; 155 gasLimit = "0x2FEFD800"; 156 mixHash = "0x63746963616c2062797a616e74696e65201111756c7420746f6c6572616e6365"; 157 nonce = "0x0"; 158 parentHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; 159 timestamp = "0x00"; 160 }''; 161 description = "Blockchain genesis settings."; 162 }; 163 }; 164 }; 165 166 config = mkIf cfg.enable { 167 environment.systemPackages = [ pkgs.quorum ]; 168 systemd.tmpfiles.rules = [ 169 "d '${dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -" 170 ]; 171 systemd.services.quorum = { 172 description = "Quorum daemon"; 173 after = [ "network.target" ]; 174 wantedBy = [ "multi-user.target" ]; 175 environment = { 176 PRIVATE_CONFIG = "${cfg.privateconfig}"; 177 }; 178 preStart = '' 179 if [ ! -d ${dataDir}/geth ]; then 180 if [ ! -d ${dataDir}/keystore ]; then 181 echo ERROR: You need to create a wallet before initializing your genesis file, run: 182 echo # su -s /bin/sh - quorum 183 echo $ geth --datadir ${dataDir} account new 184 echo and configure your genesis file accordingly. 185 exit 1; 186 fi 187 ln -s ${staticNodesFile} ${dataDir}/static-nodes.json 188 ${pkgs.quorum}/bin/geth --datadir ${dataDir} init ${genesisFile} 189 fi 190 ''; 191 serviceConfig = { 192 User = cfg.user; 193 Group = cfg.group; 194 ExecStart = ''${pkgs.quorum}/bin/geth \ 195 --nodiscover \ 196 --verbosity 5 \ 197 --nodekey ${cfg.nodekeyFile} \ 198 --istanbul.blockperiod ${toString cfg.blockperiod} \ 199 --syncmode ${cfg.syncmode} \ 200 ${optionalString (cfg.permissioned) 201 "--permissioned"} \ 202 --mine --minerthreads 1 \ 203 ${optionalString (cfg.rpc.enable) 204 "--rpc --rpcaddr ${cfg.rpc.address} --rpcport ${toString cfg.rpc.port} --rpcapi ${cfg.rpc.api}"} \ 205 ${optionalString (cfg.ws.enable) 206 "--ws --wsaddr ${cfg.ws.address} --wsport ${toString cfg.ws.port} --wsapi ${cfg.ws.api} --wsorigins ${cfg.ws.origins}"} \ 207 --emitcheckpoints \ 208 --datadir ${dataDir} \ 209 --port ${toString cfg.port}''; 210 Restart = "on-failure"; 211 212 # Hardening measures 213 PrivateTmp = "true"; 214 ProtectSystem = "full"; 215 NoNewPrivileges = "true"; 216 PrivateDevices = "true"; 217 MemoryDenyWriteExecute = "true"; 218 }; 219 }; 220 users.users.${cfg.user} = { 221 name = cfg.user; 222 group = cfg.group; 223 description = "Quorum daemon user"; 224 home = dataDir; 225 isSystemUser = true; 226 }; 227 users.groups.${cfg.group} = {}; 228 }; 229}