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