diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..894571b --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k=" + +use devenv diff --git a/.gitignore b/.gitignore index 3d558b6..9369bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,16 @@ docker/.env .venv pyrightconfig.json +test_env_data + # Nix result +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..477bb44 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,119 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1739444039, + "owner": "cachix", + "repo": "devenv", + "rev": "1235cd13f47df6ad19c8a183c6eabc1facb7c399", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1733328505, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1737465171, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1733477122, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1739451785, + "owner": "nixos", + "repo": "nixpkgs", + "rev": "1128e89fd5e11bb25aedbfc287733c6502202ea9", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "nixpkgs-unstable": "nixpkgs-unstable", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..a9cb935 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,99 @@ +{ + pkgs, + lib, + config, + inputs, + ... +}: let + dataDir = "$(pwd)/test_env_data"; + bitcoinConfigText = builtins.readFile ./scripts/bitcoin_regtest.conf; + bitcoinDataDir = "${dataDir}/bitcoin"; + bitcoinConfFilePath = "${bitcoinDataDir}/bitcoin.conf"; + lndDataDir = "${dataDir}/lnd"; + clnDataDir = "${dataDir}/cln"; + + pkgs-unstable = import inputs.nixpkgs-unstable {system = pkgs.stdenv.system;}; +in { + # https://devenv.sh/basics/ + env.GREET = "devenv"; + + # https://devenv.sh/packages/ + packages = with pkgs; [ + stdenv.cc.cc + poetry + pyright + alejandra + statix + ruff + ruff-lsp + redis + nushell + + pkgs-unstable.bitcoind + pkgs-unstable.lnd + pkgs-unstable.clightning + ]; + + # https://devenv.sh/processes/ + processes = { + bitcoind.exec = '' + if [ ! -d ${dataDir} ]; then + echo "Setup Script: Creating directory ${bitcoinDataDir}" + mkdir -p ${bitcoinDataDir} + echo "Setup Script: Creating directory ${lndDataDir}" + mkdir -p ${lndDataDir} + echo "Setup Script: Creating directory ${clnDataDir}" + mkdir -p ${clnDataDir} + fi + + if [ -f ${bitcoinConfFilePath} ]; then + echo "Setup Script: Deleting ${bitcoinConfFilePath}" + rm ${bitcoinConfFilePath} + fi + + touch ${bitcoinConfFilePath} + echo "${bitcoinConfigText}" >> ${bitcoinConfFilePath} + bitcoind -regtest -datadir=${bitcoinDataDir} -conf=${bitcoinConfFilePath} + ''; + lnd.exec = '' + sleep 3 + lnd --lnddir=${lndDataDir} \ + --bitcoin.node=bitcoind \ + --bitcoin.regtest \ + --bitcoind.dir=${bitcoinDataDir} + ''; + cln.exec = '' + sleep 3 + lightningd \ + --regtest \ + --lightning-dir=${clnDataDir} \ + --bitcoin-datadir=${bitcoinDataDir} + ''; + }; + + # https://devenv.sh/services/ + services = { + redis.enable = true; + }; + + # https://devenv.sh/tasks/ + # tasks = { + # "myproj:setup".exec = "mytool build"; + # "devenv:enterShell".after = [ "myproj:setup" ]; + # }; + + # enterShell = '' + # nu -e 'source scripts/test_env.nu; init_env' + # ''; + + # https://devenv.sh/tests/ + enterTest = '' + echo "Running tests" + git --version | grep --color=auto "${pkgs.git.version}" + ''; + + # https://devenv.sh/pre-commit-hooks/ + # pre-commit.hooks.shellcheck.enable = true; + + # See full reference at https://devenv.sh/reference/options/ +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..9c305db --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + nixpkgs-unstable: + url: github:nixos/nixpkgs/nixpkgs-unstable +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend diff --git a/scripts/bitcoin_regtest.conf b/scripts/bitcoin_regtest.conf new file mode 100644 index 0000000..da4b665 --- /dev/null +++ b/scripts/bitcoin_regtest.conf @@ -0,0 +1,32 @@ +# Generated by https://jlopp.github.io/bitcoin-core-config-generator/ + +# This config should be placed in following path: +# ~/.bitcoin/bitcoin.conf + +# [rpc] +# Accept command line and JSON-RPC commands. +server=1 +# Location of the RPC auth cookie +rpccookiefile=.cookie_ext +rpcuser=nixblitz +rpcpassword=nixlibtz + +# [chain] +# chain=regtest + +# [debug] +# Enable RPC logging +debug=rpc +# Log IP Addresses in debug output. +logips=1 + +# [zeromq] +# Enable publishing of raw block hex to
. +zmqpubrawblock=tcp://127.0.0.1:28332 +# Enable publishing of raw transaction hex to . +zmqpubrawtx=tcp://127.0.0.1:28333 + +[regtest] +# Bind to given address to listen for JSON-RPC connections. This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times. (default: 127.0.0.1 and ::1 i.e., localhost) +rpcbind=127.0.0.1 +rpcallowip=127.0.0.1 diff --git a/scripts/test_env.nu b/scripts/test_env.nu new file mode 100644 index 0000000..c5409b2 --- /dev/null +++ b/scripts/test_env.nu @@ -0,0 +1,144 @@ +const CORE_USER = "nixblitz" +const CORE_PASSWORD = "nixblitz" +const CORE_RAW_TX_ADDRESS = "tcp://127.0.0.1:28332" +const CORE_RAW_BLOCK_ADDRESS = "tcp://127.0.0.1:28333" + +const BASE_DIR = "test_env_data" +const CORE_DIR = $"($BASE_DIR)/bitcoin" +const LND_DIR = $"($BASE_DIR)/lnd" +const CLN_DIR = $"($BASE_DIR)/cln" + +# init the bitcon core daemon in regtest mode +def init_env [] { + print "Creating wallet \"testwallet\", if not created yet..." + bitcli createwallet testwallet out+err>| ignore + + print "Loading wallet \"testwallet\", if not loaded yet..." + bitcli loadwallet testwallet out+err>| ignore +} + +# regtest command for bitcoin core +def bitcli --wrapped [...args] { + let coreDir = $"(pwd)/($CORE_DIR)" + bitcoin-cli -regtest --datadir=($coreDir) ...$args +} + +# short for lncli with all connection settings applied +def lndcli --wrapped [...args] { + ( + lncli --lnddir=(pwd)/($LND_DIR) + --chain "bitcoin" + --network "regtest" + ...$args + ) +} + +# short for lightning-cli with all connection settings applied +def clncli --wrapped [...args] { + let cwd = readlink -f . + ( + lightning-cli + --regtest + --lightning-dir=(pwd)/($CLN_DIR) + --rpc-file=(pwd)/($CLN_DIR)/regtest/lightning-rpc + ...$args + ) +} + +# Sends a transaction using the default loaded wallet +def bitsend [ + address: string, # The address to send the funds to + amount: float = 15.0 # The amount of funds to send in Bitcoin + ] { + bitcli -named -regtest sendtoaddress address=($address) amount=($amount) fee_rate=100 +} + +# Generates regtest blocks +def bitgen [ + num_blocks: int=10 # The number of blocks to generate + ] { + bitcli -generate $num_blocks +} + +# Get the pubkey of the given node +def regpubkey [ + node: string # The node to get the pubkey of ("lnd" or "cln") +] { + match $node { + "cln" => { clncli getinfo | from json | echo $in.id } + "lnd" => { lndcli getinfo | from json | echo $in.identity_pubkey } + _ => { print "node must either be \"lnd\" or \"cln\"." } + } +} + +# Generate a new onchain address for the given node +def regaddress [ + node: string # The node to generate an address for ("lnd" or "cln") +] { + match $node { + "cln" => { clncli newaddr | from json | $in.bech32 } + "lnd" => { lndcli newaddress np2wkh | from json | $in.address } + _ => { print "node must either be \"lnd\" or \"cln\"." } + } +} + +# List funds in the given node +def reglistfunds [ + node: string # The node to list funds for ("lnd" or "cln") +] { + match $node { + "cln" => { clncli listfunds | from json | echo $in } + "lnd" => { + let channel_balance = lndcli channelbalance | from json + let wallet_balance = lndcli walletbalance | from json + echo { + channel: $channel_balance, + onchain: $wallet_balance + } + } + _ => { print "node must either be \"lnd\" or \"cln\"." } + } +} + +# open a channel `to` the other node +def regopenchannel [ + to: string, # The node to open a channel to ("lnd" or "cln"). From will be the "other" node. + local_amount: int = 10_000_000, # The amount in satoshis to commit to the channel + push_amount: int = 5_000_000 # The amount in satoshis to push to the remote node as part of the channel opening +] { + match $to { + "cln" => { + let k = (clnpubkey) + lndcli openchannel $k --local_amt $local_amount --push_amt $push_amount + } + "lnd" => { + let k = (lndpubkey) + clncli fundchannel $k $local_amount + } + _ => { print "to must either be \"lnd\" or \"cln\"." } + } +} + +# fund all nodes with onchain testnet coins +def regfundnodes [ + amount: float = 15.0 # The amount of funds to send to the nodes +] { + let cln = (regaddress "cln") + let lnd = (regaddress "lnd") + + bitsend $cln $amount + bitsend $lnd $amount +} + +# generate a syntetic history by making random payments between nodes +def regmakehistory [ + rounds: int = 100 # How many rounds of history you want to make +] { + for i in 0..$rounds { + let from = (random int 0..3) + mut to = (random int 0..3) + while $from == $to { + $to = (random int 0..3) + } + } +}