diff --git a/.reuse/dep5 b/.reuse/dep5 index 3962301..a4374ee 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -10,3 +10,7 @@ License: Apache-2.0 Files: docs/assets/favicon.png docs/assets/logo.svg Copyright: © 2023 MLabs LTD License: LicenseRef-MLabs + +Files: modules/fixtures/* +Copyright: © 2023 MLabs LTD +License: LicenseRef-MLabs diff --git a/modules/default.nix b/modules/default.nix index 4d14e9e..59b7eaf 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -23,6 +23,14 @@ ./node.nix ]; }; + + private-testnet-node = { + imports = [ + config.flake.nixosModules.node + ./private-testnet-node.nix + ]; + }; + ogmios = { imports = [ ./services/ogmios.nix diff --git a/modules/fixtures/README.md b/modules/fixtures/README.md new file mode 100644 index 0000000..aedb11a --- /dev/null +++ b/modules/fixtures/README.md @@ -0,0 +1,3 @@ +# `./fixtures` + +This directory contains [_fixtures_](https://en.wikipedia.org/wiki/Test_fixture#Software) for testing. diff --git a/modules/fixtures/test-node/byron-delegate.key b/modules/fixtures/test-node/byron-delegate.key new file mode 100644 index 0000000..6693ec7 Binary files /dev/null and b/modules/fixtures/test-node/byron-delegate.key differ diff --git a/modules/fixtures/test-node/byron-delegation.cert b/modules/fixtures/test-node/byron-delegation.cert new file mode 100644 index 0000000..f29f3e5 --- /dev/null +++ b/modules/fixtures/test-node/byron-delegation.cert @@ -0,0 +1,8 @@ +{ "omega": 0 +, "issuerPk": + "NclXQiNNEpaaLdSxP3VFeOPIfSuFqBcNtmv8/7fftBKtgW1Aig7UqHJ/czsywkWFFVmBYPRnGjXspUl3wEMvuQ==" +, "delegatePk": + "24ejRK+kCDs1g4f3PcodFEUFVgNFWtfmuoEtVQf8/Ii2j2ruXHebJmZZPrwtAdbJYwDiSEvsHr95+BAF1ifGsA==" +, "cert": + "498c72e35ef30cd4657b48bfcc0a84a555a67981e3b6104a0d1708ab84510367d81e1ba3f47619565b1ee1098e31dcb8eb648d8030e061b568de113fdf3d6a09" +} \ No newline at end of file diff --git a/modules/fixtures/test-node/config.json b/modules/fixtures/test-node/config.json new file mode 100644 index 0000000..a70b964 --- /dev/null +++ b/modules/fixtures/test-node/config.json @@ -0,0 +1,81 @@ +{ + "Protocol": "Cardano", + + "ByronGenesisFile": "genesis-byron.json", + "ShelleyGenesisFile": "genesis-shelley.json", + "AlonzoGenesisFile": "genesis-alonzo.json", + "ConwayGenesisFile": "genesis-conway.json", + + "ApplicationName": "cardano-sl", + "ApplicationVersion": 1, + "MaxKnownMajorProtocolVersion": 2, + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 6, + "LastKnownBlockVersion-Minor": 0, + + "TestShelleyHardForkAtEpoch": 0, + "TestAllegraHardForkAtEpoch": 0, + "TestMaryHardForkAtEpoch": 0, + "TestAlonzoHardForkAtEpoch": 0, + "TestBabbageHardForkAtEpoch": 0, + "TestConwayHardForkAtEpoch": 0, + + "RequiresNetworkMagic": "RequiresNoMagic", + + "minSeverity": "Info", + "defaultBackends": ["KatipBK"], + "defaultScribes": [["StdoutSK", "stdout"]], + "setupBackends": ["KatipBK"], + "setupScribes": [ + { + "scFormat": "ScJson", + "scKind": "StdoutSK", + "scName": "stdout", + "scRotation": null + } + ], + + "TurnOnLogMetrics": true, + "TurnOnLogging": true, + + "TracingVerbosity": "NormalVerbosity", + "TraceBlockFetchClient": false, + "TraceBlockFetchDecisions": false, + "TraceBlockFetchProtocol": false, + "TraceBlockFetchProtocolSerialised": false, + "TraceBlockFetchServer": false, + "TraceChainDb": true, + "TraceChainSyncBlockServer": false, + "TraceChainSyncClient": false, + "TraceChainSyncHeaderServer": false, + "TraceChainSyncProtocol": false, + "TraceDNSResolver": false, + "TraceDNSSubscription": false, + "TraceErrorPolicy": false, + "TraceForge": true, + "TraceHandshake": false, + "TraceIpSubscription": false, + "TraceLocalChainSyncProtocol": true, + "TraceLocalErrorPolicy": false, + "TraceLocalHandshake": false, + "TraceLocalTxSubmissionProtocol": true, + "TraceLocalTxSubmissionServer": true, + "TraceMempool": true, + "TraceMux": false, + "TraceTxInbound": false, + "TraceTxOutbound": false, + "TraceTxSubmissionProtocol": false, + + "options": { + "mapBackends": { + "cardano.node.metrics": ["EKGViewBK"], + "cardano.node.resources": ["EKGViewBK"] + }, + "mapSubtrace": { + "cardano.node.metrics": { "subtrace": "Neutral" } + } + }, + + "ExperimentalHardForksEnabled": true, + "ExperimentalProtocolsEnabled": true +} diff --git a/modules/fixtures/test-node/faucet.skey b/modules/fixtures/test-node/faucet.skey new file mode 100644 index 0000000..cb6f8c5 --- /dev/null +++ b/modules/fixtures/test-node/faucet.skey @@ -0,0 +1,5 @@ +{ + "type": "PaymentSigningKeyShelley_ed25519", + "description": "Payment Signing Key", + "cborHex": "5820a5e4238b67ebb1108c52a01ac850bbce82c915d77bad94331892f3edf612883c" +} diff --git a/modules/fixtures/test-node/genesis-alonzo.json b/modules/fixtures/test-node/genesis-alonzo.json new file mode 100644 index 0000000..17d451a --- /dev/null +++ b/modules/fixtures/test-node/genesis-alonzo.json @@ -0,0 +1,51 @@ +{ + "collateralPercentage": 150, + "costModels": { + "PlutusV1": [ + 205665, 812, 1, 1, 1000, 571, 0, 1, 1000, 24177, 4, 1, 1000, 32, 117366, + 10475, 4, 23000, 100, 23000, 100, 23000, 100, 23000, 100, 23000, 100, + 23000, 100, 100, 100, 23000, 100, 19537, 32, 175354, 32, 46417, 4, 221973, + 511, 0, 1, 89141, 32, 497525, 14068, 4, 2, 196500, 453240, 220, 0, 1, 1, + 1000, 28662, 4, 2, 245000, 216773, 62, 1, 1060367, 12586, 1, 208512, 421, + 1, 187000, 1000, 52998, 1, 80436, 32, 43249, 32, 1000, 32, 80556, 1, + 57667, 4, 1000, 10, 197145, 156, 1, 197145, 156, 1, 204924, 473, 1, + 208896, 511, 1, 52467, 32, 64832, 32, 65493, 32, 22558, 32, 16563, 32, + 76511, 32, 196500, 453240, 220, 0, 1, 1, 69522, 11687, 0, 1, 60091, 32, + 196500, 453240, 220, 0, 1, 1, 196500, 453240, 220, 0, 1, 1, 806990, 30482, + 4, 1927926, 82523, 4, 265318, 0, 4, 0, 85931, 32, 205665, 812, 1, 1, + 41182, 32, 212342, 32, 31220, 32, 32696, 32, 43357, 32, 32247, 32, 38314, + 32, 57996947, 18975, 10 + ], + "PlutusV2": [ + 205665, 812, 1, 1, 1000, 571, 0, 1, 1000, 24177, 4, 1, 1000, 32, 117366, + 10475, 4, 23000, 100, 23000, 100, 23000, 100, 23000, 100, 23000, 100, + 23000, 100, 100, 100, 23000, 100, 19537, 32, 175354, 32, 46417, 4, 221973, + 511, 0, 1, 89141, 32, 497525, 14068, 4, 2, 196500, 453240, 220, 0, 1, 1, + 1000, 28662, 4, 2, 245000, 216773, 62, 1, 1060367, 12586, 1, 208512, 421, + 1, 187000, 1000, 52998, 1, 80436, 32, 43249, 32, 1000, 32, 80556, 1, + 57667, 4, 1000, 10, 197145, 156, 1, 197145, 156, 1, 204924, 473, 1, + 208896, 511, 1, 52467, 32, 64832, 32, 65493, 32, 22558, 32, 16563, 32, + 76511, 32, 196500, 453240, 220, 0, 1, 1, 69522, 11687, 0, 1, 60091, 32, + 196500, 453240, 220, 0, 1, 1, 196500, 453240, 220, 0, 1, 1, 1159724, + 392670, 0, 2, 806990, 30482, 4, 1927926, 82523, 4, 265318, 0, 4, 0, 85931, + 32, 205665, 812, 1, 1, 41182, 32, 212342, 32, 31220, 32, 32696, 32, 43357, + 32, 32247, 32, 38314, 32, 35892428, 10, 57996947, 18975, 10, 38887044, + 32947, 10 + ] + }, + "executionPrices": { + "prMem": 5.77e-2, + "prSteps": 7.21e-5 + }, + "lovelacePerUTxOWord": 34482, + "maxBlockExUnits": { + "exUnitsMem": 62000000, + "exUnitsSteps": 40000000000 + }, + "maxCollateralInputs": 3, + "maxTxExUnits": { + "exUnitsMem": 14000000, + "exUnitsSteps": 10000000000 + }, + "maxValueSize": 5000 +} diff --git a/modules/fixtures/test-node/genesis-byron.json b/modules/fixtures/test-node/genesis-byron.json new file mode 100644 index 0000000..24c4d2e --- /dev/null +++ b/modules/fixtures/test-node/genesis-byron.json @@ -0,0 +1,36 @@ +{ + "protocolConsts": { + "k": 2160, + "protocolMagic": 42 + }, + "startTime": 1657186415, + "blockVersionData": { + "scriptVersion": 0, + "slotDuration": "250", + "maxBlockSize": "2000000", + "maxHeaderSize": "2000000", + "maxTxSize": "4096", + "maxProposalSize": "700", + "mpcThd": "20000000000000", + "heavyDelThd": "300000000000", + "updateVoteThd": "1000000000000", + "updateProposalThd": "100000000000000", + "updateImplicit": "10000", + "softforkRule": { + "initThd": "900000000000000", + "minThd": "600000000000000", + "thdDecrement": "50000000000000" + }, + "txFeePolicy": { + "summand": "155381000000000", + "multiplier": "43000000000" + }, + "unlockStakeEpoch": "18446744073709551615" + }, + "bootStakeholders": { + "7a4519c93d7be4577dd85bd524c644e6b809e44eae0457b43128c1c7": 1 + }, + "heavyDelegation": {}, + "nonAvvmBalances": {}, + "avvmDistr": {} +} diff --git a/modules/fixtures/test-node/genesis-conway.json b/modules/fixtures/test-node/genesis-conway.json new file mode 100644 index 0000000..b0b2eaf --- /dev/null +++ b/modules/fixtures/test-node/genesis-conway.json @@ -0,0 +1,66 @@ +{ + "poolVotingThresholds": { + "committeeNormal": 0.51, + "committeeNoConfidence": 0.51, + "hardForkInitiation": 0.51, + "motionNoConfidence": 0.51, + "ppSecurityGroup": 0.51 + }, + "dRepVotingThresholds": { + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.6, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 + }, + "committeeMinSize": 0, + "committeeMaxTermLength": 365, + "govActionLifetime": 30, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, + "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, + 8356, 4, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, + 100, 100, 100, 16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, + 1, 22151, 32, 91189, 769, 4, 2, 85848, 123203, 7305, -900, 1716, 549, 57, + 85848, 0, 1, 1, 1000, 42921, 4, 2, 24548, 29498, 38, 1, 898148, 27279, 1, + 51775, 558, 1, 39184, 1000, 60594, 1, 141895, 32, 83150, 32, 15299, 32, + 76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1, 43285, 552, 1, + 44749, 541, 1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32, 11546, + 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 90434, 519, 0, 1, + 74433, 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 1, 85848, + 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 955506, 213312, 0, 2, + 270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0, 141992, 32, 100788, + 420, 1, 1, 81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32, 25933, 32, + 24623, 32, 43053543, 10, 53384111, 14333, 10, 43574283, 26308, 10, 16000, + 100, 16000, 100, 962335, 18, 2780678, 6, 442008, 1, 52538055, 3756, 18, + 267929, 18, 76433006, 8868, 18, 52948122, 18, 1995836, 36, 3227919, 12, + 901022, 1, 166917843, 4307, 36, 284546, 36, 158221314, 26549, 36, 74698472, + 36, 333849714, 1, 254006273, 72, 2174038, 72, 2261318, 64571, 4, 207616, + 8310, 4, 1293828, 28716, 63, 0, 1, 1006041, 43623, 251, 0, 1 + ], + "constitution": { + "anchor": { + "dataHash": "ca41a91f399259bcefe57f9858e91f6d00e1a38d6d9c63d4052914ea7bd70cb2", + "url": "ipfs://bafkreifnwj6zpu3ixa4siz2lndqybyc5wnnt3jkwyutci4e2tmbnj3xrdm" + }, + "script": "fa24fb305126805cf2164c161d852a0e7330cf988f1fe558cf7d4a64" + }, + "committee": { + "members": { + "scriptHash-ff9babf23fef3f54ec29132c07a8e23807d7b395b143ecd8ff79f4c7": 1000 + }, + "threshold": { + "numerator": 2, + "denominator": 3 + } + }, + "extraPraosEntropy": null +} diff --git a/modules/fixtures/test-node/genesis-shelley.json b/modules/fixtures/test-node/genesis-shelley.json new file mode 100644 index 0000000..153223b --- /dev/null +++ b/modules/fixtures/test-node/genesis-shelley.json @@ -0,0 +1,64 @@ +{ + "epochLength": 5, + "activeSlotsCoeff": 1.0, + "slotLength": 0.1, + "securityParam": 2160, + "genDelegs": {}, + "initialFunds": { + "609783be7d3c54f11377966dfabc9284cd6c32fca1cd42ef0a4f1cc45b": 1000000000000 + }, + "maxKESEvolutions": 60, + "maxLovelaceSupply": 2000000000000, + "networkId": "Testnet", + "networkMagic": 42, + "protocolParams": { + "a0": 0.0, + "decentralisationParam": 0, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "keyDeposit": 0, + "maxBlockBodySize": 65536, + "maxBlockHeaderSize": 1100, + "maxTxSize": 16384, + "minFeeA": 44, + "minFeeB": 155381, + "minPoolCost": 0, + "minUTxOValue": 0, + "nOpt": 100, + "poolDeposit": 0, + "protocolVersion": { + "major": 10, + "minor": 0 + }, + "rho": 0.1, + "tau": 0.1 + }, + "slotsPerKESPeriod": 129600, + "staking": { + "pools": { + "8a219b698d3b6e034391ae84cee62f1d76b6fbc45ddfe4e31e0d4b60": { + "cost": 0, + "margin": 0.0, + "metadata": null, + "owners": [], + "pledge": 0, + "publicKey": "8a219b698d3b6e034391ae84cee62f1d76b6fbc45ddfe4e31e0d4b60", + "relays": [], + "rewardAccount": { + "credential": { + "key hash": "b6ffb20cf821f9286802235841d4348a2c2bafd4f73092b7de6655ea" + }, + "network": "Testnet" + }, + "vrf": "fec17ed60cbf2ec5be3f061fb4de0b6ef1f20947cfbfce5fb2783d12f3f69ff5" + } + }, + "stake": { + "074a515f7f32bf31a4f41c7417a8136e8152bfb42f06d71b389a6896": "8a219b698d3b6e034391ae84cee62f1d76b6fbc45ddfe4e31e0d4b60" + } + }, + "systemStart": "2022-07-07T09:33:35Z", + "updateQuorum": 2 +} diff --git a/modules/fixtures/test-node/kes.skey b/modules/fixtures/test-node/kes.skey new file mode 100644 index 0000000..8711a55 --- /dev/null +++ b/modules/fixtures/test-node/kes.skey @@ -0,0 +1,5 @@ +{ + "type": "KesSigningKey_ed25519_kes_2^6", + "description": "KES Signing Key", + "cborHex": "590260a199f16b11da6c7f5c1e0f1eb0b9bbe278d3d8f35bfd50d0951c2ff94d0344cd57df5f64c9bac1dd60b4482f9c636168f40737d526625a2ec82f22ec0c72de0013f86ef743a7bba0286db6ddf3d85bf8e49ddbf14d9d3b7ee22f4857c77b740948f84f2e72f6bcf91f405e34ea50a2c53fa4876b43cfce2bcfe87c06a903de8bb33d968ca7930b67d0c23f5cb2d74e422d773ba80e388de384691000d6ba8a9b4dc7d3187f76048fbef9a52b72d80d835bb76eced7c0e0cdc5b58869b73c095dffa01db4ff51765afcead565395a5ed1cf74e5f2134d61076fece21aacd080bbbfaab94125401d7bbc74eafc7e7e3a2235f59dc03d6e332e53d558493a1e22213b92c77b1328ff1b83855da704fc366bf4415490602481d1939136eeaf252c65184912a779d9d94a90e32b72c1877ef60b6d79e707ce5a762acb4bed46436efe4fe62aae50b39068cc508a09427c92791cbcbea44318529cc68d297ca24e1b73b2394c385ec63fcd85ed56eec3de48860a1ec950aad4f91cbf741dbd7bf1d3c278875bd20e31ff5372339f6aa5280ad9b8bf3514889ac44600fe57ca0b535d6dc6b0b981e079595aad186ee0be9b07e837391ab165e4ca406601c876a86e246a3f53311e21199cccc0b080f28d18f4dc6987731e10e4ade00df7c6921c5ef3022b6f49a29ba307a2c8f4bd2ba42fcfa0aad68a2f0ad31fff69a99d3471f9036d3f5817a3edfeff7fc3c14e1151d767aaa043481cfd1a6ee55e8e5d7853ecdaf9da2bb36c716beae8d706bc648a790d4697e1d044a11a49f305ab8bc64a094bd81bda7395fe6f77dd5557c39919dd9bb9cf22a87fe47408ae3ec2247007d015a5" +} diff --git a/modules/fixtures/test-node/opcert.cert b/modules/fixtures/test-node/opcert.cert new file mode 100644 index 0000000..2ae24e0 --- /dev/null +++ b/modules/fixtures/test-node/opcert.cert @@ -0,0 +1,5 @@ +{ + "type": "NodeOperationalCertificate", + "description": "", + "cborHex": "828458204cd49bb05e9885142fe7af1481107995298771fd1a24e72b506a4d600ee2b3120000584089fc9e9f551b2ea873bf31643659d049152d5c8e8de86be4056370bccc5fa62dd12e3f152f1664e614763e46eaa7a17ed366b5cef19958773d1ab96941442e0b58205a3d778e76741a009e29d23093cfe046131808d34d7c864967b515e98dfc3583" +} diff --git a/modules/fixtures/test-node/topology.json b/modules/fixtures/test-node/topology.json new file mode 100644 index 0000000..e6f118b --- /dev/null +++ b/modules/fixtures/test-node/topology.json @@ -0,0 +1,3 @@ +{ + "Producers": [] +} diff --git a/modules/fixtures/test-node/vrf.skey b/modules/fixtures/test-node/vrf.skey new file mode 100644 index 0000000..5133967 --- /dev/null +++ b/modules/fixtures/test-node/vrf.skey @@ -0,0 +1,5 @@ +{ + "type": "VrfSigningKey_PraosVRF", + "description": "VRF Signing Key", + "cborHex": "5840899795b70e9f34b737159fe21a6170568d6031e187f0cc84555c712b7c29b45cb882007593ef70f86e5c0948561a3b8e8851529a4f98975f2b24e768dda38ce2" +} diff --git a/modules/node.nix b/modules/node.nix index 427853d..ee1e9c4 100644 --- a/modules/node.nix +++ b/modules/node.nix @@ -29,6 +29,12 @@ in default = "/etc/cardano-node/config.json"; }; + copyCardanoNodeConfigToEtc = lib.mkOption { + description = "If set to true, this will -- at Nix evaluation time -- copy the cardano node's config file to `/etc/cardano-node/config.json`"; + type = lib.types.bool; + default = true; + }; + prometheusExporter.enable = lib.mkEnableOption "prometheus exporter"; prometheusExporter.port = lib.mkOption { @@ -39,15 +45,29 @@ in }; config = lib.mkIf cfg.enable { - environment.etc."cardano-node/config.json" = { - # hack to get config file path - text = readFile (elemAt (match ".* --config ([^ ]+) .*" (replaceStrings [ "\n" ] [ " " ] config.services.cardano-node.script)) 0); - user = "cardano-node"; - group = "cardano-node"; + environment.etc = lib.mkIf cfg.copyCardanoNodeConfigToEtc { + "cardano-node/config.json" = { + # NOTE(jaredponn): This is a hack to get config file path + # This line forces the config file to be known at Nix + # evaluation time which causes trouble if you want to + # "dynamically create" the config which is desirable when -- + # for example -- creating a test node setup that sets the + # system start time to now. + + text = readFile (elemAt (match ".* --config ([^ ]+) .*" (replaceStrings [ "\n" ] [ " " ] config.services.cardano-node.script)) 0); + user = "cardano-node"; + group = "cardano-node"; + }; }; environment.variables = { + # Set convenience environment variables when interacting with the node + # via `cardano-cli` in the machine. + # In particular, see + # https://github.com/IntersectMBO/cardano-cli/blob/master/cardano-cli/src/Cardano/CLI/Environment.hs + # for details on the environment variables it reads. CARDANO_NODE_SOCKET_PATH = cfg.socketPath; + CARDANO_NODE_NETWORK_ID = if config.cardano.network == "mainnet" then "mainnet" else config.cardano.networkNumber; }; services.cardano-node = { diff --git a/modules/private-testnet-node.nix b/modules/private-testnet-node.nix new file mode 100644 index 0000000..1392087 --- /dev/null +++ b/modules/private-testnet-node.nix @@ -0,0 +1,392 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.cardano.private-testnet-node; +in +{ + + imports = [ + ./cardano.nix + ./node.nix + ]; + + options.cardano.private-testnet-node = { + enable = lib.mkEnableOption '' + cardano-devnet node (a private testnet node) which -- when enabled -- + switches the system's cardano-node to a private testnet node (with its + own testnet network magic) with the environment variable $FAUCET as an address + loaded with LOVELACE (approx. 1000000000000 LOVELACE) that can be + distributed with the CLI utility `request-from-faucet --address + addr_test1vztc80na8320zymhjekl40yjsnxkcvhu58x59mc2fuwvgkc332vxv + --amount 10000000`''; + + initialFunds = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + (lib.types.nonEmptyListOf lib.types.ints.unsigned) + lib.types.ints.unsigned + ] + ); + default = { }; + + example = { + addr_test1vzrv7az4xq620y20pyn44yhvl89r7nwa7ga5ftn9rleenxqharu33 = [ + 10000000 + 1000000 + ]; + addr_test1vr6ue2hmlnj8pzzqy7353lv3yj8xu7m24pgpctv7z3qhv8c3qdt46 = 1000000; + }; + description = '' + A mapping from bech32 encoded addresses to either a single LOVELACE + amount or a non-empty list of LOVELACE amounts which initializes the + addresses with UTxOs containing the provided LOVELACE amounts from the + FAUCET. + + When using the [NixOS + Tests](https://nixos.org/manual/nixos/stable/index.html#sec-call-nixos-test-outside-nixos), + it's good to wait for the unit `test-cardano-node-initial-funds.service` before + spending from these wallets i.e., having the following in the + `testScript` would be a good idea: + ``` + machine.wait_for_unit("test-cardano-node-initial-funds") + ``` + ''; + }; + + testNodeConfigDirectory = lib.mkOption { + internal = true; + type = lib.types.str; + default = "cardano-node"; + description = '' + The ConfigurationDirectory (see `man systemd.exec (5)`) to put the test + Cardano node's configuration in. Thus, the node's configuration + will be in `/etc/`. + ''; + }; + + # The following options are the genesis files for specific eras + genesisAlonzo = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/genesis-alonzo.json; + }; + + genesisConway = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/genesis-conway.json; + }; + + genesisByron = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/genesis-byron.json; + }; + + genesisShelley = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/genesis-shelley.json; + }; + + # The following options are wrappers for the options provided by the cardano-node + nodeConfigFile = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/config.json; + }; + + vrfKey = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/vrf.skey; + }; + + kesKey = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/kes.skey; + }; + + delegationCertificate = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/byron-delegation.cert; + }; + + operationalCertificate = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/opcert.cert; + }; + + signingKey = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/byron-delegate.key; + }; + + topology = lib.mkOption { + internal = true; + type = lib.types.path; + default = ./fixtures/test-node/topology.json; + }; + + }; + + config = lib.mkIf cfg.enable { + # Set the `cardano.network` option to `private` which has network magic 42. + # In particular, this matches the `networkMagic` value in + # `./fixtures/test-node/genesis-shelley.json` + cardano.network = lib.mkForce "private"; + + # Create a directory of the test node's config files + # We don't just link the Cardano node directly to the files in + # `./fixtures/test-node/`? This is because we need to dynamically fill some + # values in when the system is running e.g. the system start time. + systemd.services.cardano-node-config = { + enable = true; + wantedBy = [ "multi-user.target" ]; + before = [ "cardano-node.service" ]; + serviceConfig = { + Type = "oneshot"; + Restart = "on-failure"; + RemainAfterExit = true; + User = "cardano-node"; + Group = "cardano-node"; + ConfigurationDirectory = [ cfg.testNodeConfigDirectory ]; + }; + + path = [ pkgs.jq ]; + script = '' + # If we've built the configuration before, then don't do + # anything. + if test -f "$CONFIGURATION_DIRECTORY/done" # REMARK: we know that ConfigurationDirectory has only one element + then + exit 0 + fi + + # Copy most of the configuration files over + install -o cardano-node -g cardano-node -m 664 ${cfg.nodeConfigFile} "$CONFIGURATION_DIRECTORY/config.json" + install -o cardano-node -g cardano-node -m 664 ${cfg.genesisAlonzo} "$CONFIGURATION_DIRECTORY/genesis-alonzo.json" + install -o cardano-node -g cardano-node -m 664 ${cfg.genesisConway} "$CONFIGURATION_DIRECTORY/genesis-conway.json" + install -o cardano-node -g cardano-node -m 600 ${cfg.vrfKey} "$CONFIGURATION_DIRECTORY/vrf.skey" + install -o cardano-node -g cardano-node -m 600 ${cfg.kesKey} "$CONFIGURATION_DIRECTORY/kes.skey" + install -o cardano-node -g cardano-node -m 600 ${cfg.delegationCertificate} "$CONFIGURATION_DIRECTORY/byron-delegation.cert" + install -o cardano-node -g cardano-node -m 600 ${cfg.operationalCertificate} "$CONFIGURATION_DIRECTORY/opcert.cert" + install -o cardano-node -g cardano-node -m 600 ${cfg.signingKey} "$CONFIGURATION_DIRECTORY/byron-delegate.key" + install -o cardano-node -g cardano-node -m 600 ${cfg.topology} "$CONFIGURATION_DIRECTORY/topology.json" + + # Copy the configuration files that require additional initialization + # on boot + START_TIME="$(date -u)" + + jq '.startTime |= $start_time' \ + --argjson start_time "$(date -d "$START_TIME" +%s)" \ + < ${cfg.genesisByron} \ + > "$CONFIGURATION_DIRECTORY/genesis-byron.json" + + jq '.systemStart |= $start_time' \ + --arg start_time "$(date -d "$START_TIME" -u +%FT%TZ)" \ + < ${cfg.genesisShelley} \ + > "$CONFIGURATION_DIRECTORY/genesis-shelley.json" + + touch "$CONFIGURATION_DIRECTORY/done" + ''; + }; + + # Setup the cardano node + cardano.node.enable = true; + cardano.node.copyCardanoNodeConfigToEtc = lib.mkForce false; + + # Change the cardano node s.t. it uses a custom setup + services.cardano-node = { + nodeConfigFile = "/etc/${cfg.testNodeConfigDirectory}/config.json"; + topology = "/etc/${cfg.testNodeConfigDirectory}/topology.json"; + kesKey = "/etc/${cfg.testNodeConfigDirectory}/kes.skey"; + vrfKey = "/etc/${cfg.testNodeConfigDirectory}/vrf.skey"; + operationalCertificate = "/etc/${cfg.testNodeConfigDirectory}/opcert.cert"; + delegationCertificate = "/etc/${cfg.testNodeConfigDirectory}/byron-delegation.cert"; + signingKey = "/etc/${cfg.testNodeConfigDirectory}/byron-delegate.key"; + + # Override the `useSystemdReload` from the `./node.nix` defaults that + # messes with things. Note that if `useSystemdReload` is true, it makes + # the `cardano-node` go looking in `/etc/cardano-node/topology-0.yaml` + # instead of whatever value we provide. See + # https://github.com/IntersectMBO/cardano-node/blob/aec56982f99a3e94d6cde969f666133ff2f68890/nix/nixos/cardano-node-service.nix#L586-L599 + # for details. + useSystemdReload = lib.mkForce false; + }; + + systemd.services.test-cardano-node-initial-funds = { + description = "Pays LOVELACE to the initialFunds at most once."; + after = [ "cardano-node-socket.service" ]; + requires = [ "cardano-node.service" ]; + bindsTo = [ "cardano-node.service" ]; + requiredBy = [ "cardano-node.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + StateDirectory = [ "test-cardano-node-initial-funds" ]; + }; + environment = { + inherit (config.environment.variables) + FAUCET + FAUCET_SKEY + CARDANO_NODE_SOCKET_PATH + CARDANO_NODE_NETWORK_ID + ; + }; + path = [ pkgs.request-from-faucet ]; + script = '' + # Check if we've already initialized. If we have, then we're done. + if test -f /var/lib/test-cardano-node-initial-funds/done + then + 1>&2 echo "Initial funds have already been distributed, so doing nothing." + exit 0 + fi + + 1>&2 echo "Distributing initial funds." + ${lib.attrsets.foldlAttrs ( + acc: addr: amountOrAmounts: + # WARNING(jaredponn): probably terrible time complexity. + # NOTE(jaredponn): Loosely, this convoluted nix + # expression builds a shell script like + # ``` + # request-from-faucet --address --amount + # request-from-faucet --address --amount + # request-from-faucet --address --amount + # ``` + # when given an `initialFunds` like + # ``` + # { = [ ]; = ; } + # ``` + let + amounts = if builtins.typeOf amountOrAmounts == "int" then [ amountOrAmounts ] else amountOrAmounts; + in + '' + ${acc} + ${builtins.concatStringsSep "\n" (builtins.map (amount: ''request-from-faucet --address ${lib.escapeShellArg addr} --amount ${builtins.toString amount}'') amounts)} + '' + ) "" cfg.initialFunds} + + touch /var/lib/test-cardano-node-initial-funds/done + + 1>&2 echo "Finished distributing initial funds." + ''; + }; + + nixpkgs.overlays = [ + (_self: _super: { + # Add the package `request-from-faucet` + request-from-faucet = pkgs.writeShellApplication { + name = "request-from-faucet"; + runtimeInputs = [ + pkgs.jq + pkgs.cardano-cli + ]; + + text = '' + while test "$#" -gt 0; do + case "$1" in + --address) + shift + ADDRESS="$1" + shift + ;; + --amount) + shift + AMOUNT="$1" + shift + ;; + -h|--help) + 1>&2 echo "Usage: $0 --address --amount " + 1>&2 echo "Pays lovelace (a base 10 integer) to (human readable bech32 address) from the address specified by the \$FAUCET environment variable using the private key located in the file specified by the \$FAUCET_SKEY environment variable. This uses 'cardano-cli' internally, and hence requires the \$CARDANO_NODE_SOCKET_PATH and \$CARDANO_NODE_NETWORK_ID environment variables to be set appropriately." + exit 1 + ;; + *) + 1>&2 echo "$0: unrecognized option '$1'" + 1>&2 echo "Try '$0 --help' for more information." + exit 1 + ;; + esac + done + + 1>&2 echo "Creating a UTxO for $ADDRESS with $AMOUNT lovelace from $FAUCET" + + # Temporary working directory + TMP="$(mktemp -d)" + trap "rm -rf \$TMP" EXIT + + # Build and sign the tx from the faucet + + # NOTE(jaredponn) Most of the tx building follows from the following articles: + # - https://developers.cardano.org/docs/get-started/create-simple-transaction/ + # - https://github.com/cardano-scaling/hydra/blob/master/demo/seed-devnet.sh + + 1>&2 echo "Building and signing the transaction" + + # Create a tx which: + # - Uses the first largest in lovelace 64 UTxOs + # from the FAUCET address to finance the + # transaction. We limit it to using 64 UTxOs + # to help stay under transaction size limits. + # - Pay a single transaction output to ADDRESS + # with the specified AMOUNT + + # We ignore these shellcheck warnings (they + # arise from getting the tx-ins from the FAUCET + # address) because we know the form of the + # tx-ins is `#` + # shellcheck disable=SC2162 + # shellcheck disable=SC2046 + 1>&2 cardano-cli conway transaction build \ + --change-address "$FAUCET" \ + $(cardano-cli query utxo --output-json --address "$FAUCET" \ + | jq -r 'to_entries | sort_by(- .value.value.lovelace) | map(.key) | .[0:64] | .[]' \ + | while read FAUCET_TX_IN; do echo "--tx-in" "$FAUCET_TX_IN" ; done) \ + --tx-out "$ADDRESS"+"$AMOUNT" \ + --out-file tx.draft + + 1>&2 cardano-cli conway transaction sign \ + --tx-body-file tx.draft \ + --signing-key-file "$FAUCET_SKEY" \ + --out-file tx.signed + + TX_ID="$(cardano-cli conway transaction txid --tx-file tx.signed)" + TX_IN="$TX_ID#0" + + 1>&2 cardano-cli conway transaction submit --tx-file tx.signed + + 1>&2 echo "Finished building and signing transaction $TX_ID" + + # Await the tx + 1>&2 echo "Awaiting $TX_ID by waiting for tx-in $TX_IN" + + while test "$(cardano-cli query utxo --tx-in "$TX_IN" --output-json | jq length)" -eq 0; do + sleep 1 + 1>&2 echo -n "." + done + + 1>&2 echo "" + + 1>&2 echo "Done" + ''; + }; + }) + ]; + + environment = { + variables = { + FAUCET = "addr_test1vztc80na8320zymhjekl40yjsnxkcvhu58x59mc2fuwvgkc332vxv"; + FAUCET_SKEY = ./fixtures/test-node/faucet.skey; + }; + + systemPackages = [ pkgs.request-from-faucet ]; + }; + }; +} diff --git a/templates/default/flake.lock b/templates/default/flake.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/default.nix b/tests/default.nix index 6397b85..2e7c11c 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,7 +1,8 @@ { imports = [ ./cardano-cli.nix - ./node.nix + ./online-node.nix + ./private-testnet-node.nix ./ogmios.nix ./kupo.nix ./http.nix diff --git a/tests/node.nix b/tests/online-node.nix similarity index 95% rename from tests/node.nix rename to tests/online-node.nix index 8699e3f..9a64607 100644 --- a/tests/node.nix +++ b/tests/online-node.nix @@ -1,5 +1,5 @@ { - perSystem.vmTests.tests.cardano-node = { + perSystem.vmTests.tests.online-node = { impure = true; module = { nodes.machine = diff --git a/tests/private-testnet-node.nix b/tests/private-testnet-node.nix new file mode 100644 index 0000000..1480d13 --- /dev/null +++ b/tests/private-testnet-node.nix @@ -0,0 +1,82 @@ +{ + perSystem.vmTests.tests.node = { + impure = false; + module = { + nodes.machine = + { pkgs, ... }: + { + cardano = { + cli.enable = true; + private-testnet-node.enable = true; + private-testnet-node.initialFunds = { + addr_test1vzrv7az4xq620y20pyn44yhvl89r7nwa7ga5ftn9rleenxqharu33 = [ + 2000000 + 3000000 + ]; + addr_test1vr6ue2hmlnj8pzzqy7353lv3yj8xu7m24pgpctv7z3qhv8c3qdt46 = 1500000; + }; + }; + + environment.systemPackages = with pkgs; [ + jq + bc + ]; + }; + + testScript = + { nodes, ... }: + let + magic = toString nodes.machine.config.cardano.networkNumber; + in + '' + machine.wait_for_unit("cardano-node") + + # Check the test-node is working and syncing properly + machine.wait_until_succeeds("""[[ $(echo "$(cardano-cli query tip --testnet-magic ${magic} | jq '.syncProgress' --raw-output) > 0.001" | bc) == "1" ]]""", 10) + + # Check that the FAUCET address has a decent amount of ADA + # (note that because we have a non-zero amount of initial funds, some + # will be drained from the FAUCET initially) + machine.succeed(""" + test \ + "$(cardano-cli query utxo --output-json --address "$FAUCET" \ + | jq --arg faucet "$FAUCET" '.[] | (.address == $faucet and .value.lovelace >= 100000000000)')" \ + = \ + true + """) + + # Verify that the initial funds have been correctly distributed. + machine.wait_for_unit("test-cardano-node-initial-funds") + machine.succeed(""" + test \ + "$(cardano-cli query utxo --output-json --address addr_test1vzrv7az4xq620y20pyn44yhvl89r7nwa7ga5ftn9rleenxqharu33 \ + | jq 'to_entries | map(.value.value) | sort | . == [ {"lovelace" : 2000000}, {"lovelace" : 3000000} ]')" \ + = \ + true + """) + machine.succeed(""" + test \ + "$(cardano-cli query utxo --output-json --address addr_test1vr6ue2hmlnj8pzzqy7353lv3yj8xu7m24pgpctv7z3qhv8c3qdt46 \ + | jq 'to_entries | map(.value.value) | sort | . == [ {"lovelace" : 1500000} ]')" \ + = \ + true + """) + + # Verify that we can actually use the FAUCET to give ADA out + machine.succeed(""" + request-from-faucet --address addr_test1vq64jjlez93yz57ytlwtwsfz73n3elpty7e0w3z8l6yv3agc0e6jz --amount 10000000 + """) + machine.succeed(""" + test \ + "$(cardano-cli query utxo --output-json --address addr_test1vq64jjlez93yz57ytlwtwsfz73n3elpty7e0w3z8l6yv3agc0e6jz \ + | jq '.[] | (.value.lovelace >= 10000000)')" \ + = \ + true + """) + + print(machine.succeed("systemd-analyze security cardano-node")) + print('\nVM Test Succeeded.') + ''; + }; + }; +}