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