├── .env.mutinynet ├── .env.sample ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── README.md ├── bitcoind-signet.md ├── docker-compose.yml ├── docker-entrypoint.sh ├── gen-bitcoind-conf.sh ├── gen-signet-keys.sh ├── install.sh ├── lndk └── Dockerfile ├── mine-genesis.sh ├── mine.sh ├── miner ├── miner_imports └── test_framework │ ├── __init__.py │ ├── address.py │ ├── authproxy.py │ ├── bdb.py │ ├── bip340_test_vectors.csv │ ├── blocktools.py │ ├── coverage.py │ ├── descriptors.py │ ├── key.py │ ├── messages.py │ ├── muhash.py │ ├── netutil.py │ ├── p2p.py │ ├── ripemd160.py │ ├── script.py │ ├── script_util.py │ ├── segwit_addr.py │ ├── siphash.py │ ├── socks5.py │ ├── test_framework.py │ ├── test_node.py │ ├── test_shell.py │ ├── util.py │ ├── wallet.py │ └── wallet_util.py ├── nginx ├── cashu.mutinynet.com ├── faucet.mutinynet.com ├── lnurl.mutinynet.com ├── mutinynet.com ├── rgs.mutinynet.com ├── rpc.mutinynet.com └── www.mutinynet.com ├── rpcauth.py ├── run.sh └── setup-signet.sh /.env.mutinynet: -------------------------------------------------------------------------------- 1 | EXTERNAL_IP="127.0.0.1" 2 | RPCPASSWORD="my_rpc_password" 3 | RPCUSER="my_rpc_user" 4 | 5 | UACOMMENT="MutinyNet" 6 | SIGNETCHALLENGE="512102f7561d208dd9ae99bf497273e16f389bdbd6c4742ddb8e6b216e64fa2928ad8f51ae" 7 | ADDNODE=45.79.52.207:38333 -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PRIVKEY="my_priv_key" 2 | SIGNETCHALLENGE="my_signet_challenge" 3 | EXTERNAL_IP="127.0.0.1" 4 | 5 | NBITS="1e0377ae" # min difficulty 6 | BLOCKPRODUCTIONDELAY=30 7 | 8 | UACOMMENT="MutinyNet" 9 | 10 | RPCPASSWORD="my_rpc_password" 11 | 12 | NSEC="my-nsec" 13 | 14 | MINT_PRIVATE_KEY="my_mint_priv_key" 15 | 16 | JWT_SECRET="my_jwt_secret" 17 | GITHUB_CLIENT_ID="my_github_client_id" 18 | GITHUB_CLIENT_SECRET="my_github_client_secret" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | banned_users.txt 3 | banned_domains.txt 4 | /faucet_config 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "electrs"] 2 | path = electrs 3 | url = https://github.com/MutinyWallet/electrs.git 4 | branch = new-index 5 | [submodule "rapid-gossip-sync-server"] 6 | path = rapid-gossip-sync-server 7 | url = https://github.com/lightningdevkit/rapid-gossip-sync-server.git 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim as builder 2 | 3 | ARG BITCOIN_VERSION="d4a86277ed8a" 4 | ARG TRIPLET=${TRIPLET:-"x86_64-linux-gnu"} 5 | 6 | RUN apt-get update && \ 7 | apt-get install -qq --no-install-recommends ca-certificates dirmngr gosu wget libc6 procps python3 8 | WORKDIR /tmp 9 | 10 | # install bitcoin binaries 11 | RUN BITCOIN_URL="https://github.com/benthecarman/bitcoin/releases/download/paircommit/bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \ 12 | BITCOIN_FILE="bitcoin-${BITCOIN_VERSION}-${TRIPLET}.tar.gz" && \ 13 | wget -qO "${BITCOIN_FILE}" "${BITCOIN_URL}" && \ 14 | mkdir -p bin && \ 15 | tar -xzvf "${BITCOIN_FILE}" -C /tmp/bin --strip-components=2 "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli" "bitcoin-${BITCOIN_VERSION}/bin/bitcoind" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-wallet" "bitcoin-${BITCOIN_VERSION}/bin/bitcoin-util" 16 | FROM debian:buster-slim as custom-signet-bitcoin 17 | 18 | LABEL org.opencontainers.image.authors="NBD" 19 | LABEL org.opencontainers.image.licenses=MIT 20 | LABEL org.opencontainers.image.source="https://github.com/nbd-wtf/bitcoin_signet" 21 | 22 | ENV BITCOIN_DIR /root/.bitcoin 23 | 24 | ENV NBITS=${NBITS} 25 | ENV SIGNETCHALLENGE=${SIGNETCHALLENGE} 26 | ENV PRIVKEY=${PRIVKEY} 27 | 28 | ENV RPCUSER=${RPCUSER:-"bitcoin"} 29 | ENV RPCPASSWORD=${RPCPASSWORD:-"bitcoin"} 30 | ENV COOKIEFILE=${COOKIEFILE:-"false"} 31 | ENV ONIONPROXY=${ONIONPROXY:-""} 32 | ENV TORPASSWORD=${TORPASSWORD:-""} 33 | ENV TORCONTROL=${TORCONTROL:-""} 34 | ENV I2PSAM=${I2PSAM:-""} 35 | 36 | ENV UACOMMENT=${UACOMMENT:-"CustomSignet"} 37 | ENV ZMQPUBRAWBLOCK=${ZMQPUBRAWBLOCK:-"tcp://0.0.0.0:28332"} 38 | ENV ZMQPUBRAWTX=${ZMQPUBRAWTX:-"tcp://0.0.0.0:28333"} 39 | ENV ZMQPUBHASHBLOCK=${ZMQPUBHASHBLOCK:-"tcp://0.0.0.0:28334"} 40 | 41 | ENV RPCBIND=${RPCBIND:-"0.0.0.0:38332"} 42 | ENV RPCALLOWIP=${RPCALLOWIP:-"0.0.0.0/0"} 43 | ENV WHITELIST=${WHITELIST:-"0.0.0.0/0"} 44 | ENV ADDNODE=${ADDNODE:-""} 45 | ENV BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:-""} 46 | ENV MINERENABLED=${MINERENABLED:-"1"} 47 | ENV MINETO=${MINETO:-""} 48 | ENV EXTERNAL_IP=${EXTERNAL_IP:-""} 49 | 50 | VOLUME $BITCOIN_DIR 51 | EXPOSE 28332 28333 28334 38332 38333 38334 52 | RUN apt-get update && \ 53 | apt-get install -qq --no-install-recommends procps python3 python3-pip jq && \ 54 | apt-get clean 55 | COPY --from=builder "/tmp/bin" /usr/local/bin 56 | COPY docker-entrypoint.sh /usr/local/bin/entrypoint.sh 57 | COPY miner_imports /usr/local/bin 58 | COPY miner /usr/local/bin/miner 59 | COPY *.sh /usr/local/bin/ 60 | COPY rpcauth.py /usr/local/bin/rpcauth.py 61 | RUN pip3 install setuptools 62 | 63 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 64 | 65 | CMD ["run.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 nbd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mutinynet 2 | 3 | This repo contains most of the deployment for [Mutinynet](https://mutinynet.com). It originally is a fork 4 | of [Plebnet](https://github.com/nbd-wtf/bitcoin_signet) but has grown to include a lot more. 5 | 6 | The main deployment is done with docker-compose. It contains various services: 7 | 8 | * [bitcoind](https://github.com/bitcoin/bitcoin) 9 | * [lnd](https://github.com/lightningnetwork/lnd) 10 | * [rgs server](https://github.com/lightningdevkit/rapid-gossip-sync-server) 11 | * faucet ([frontend](https://github.com/MutinyWallet/mutinynet-faucet) 12 | and [backend](https://github.com/MutinyWallet/mutinynet-faucet-rs)) 13 | * [mempool.space instance](https://github.com/mempool/mempool/) 14 | * [electrs](https://github.com/romanz/electrs) 15 | * [cashu mint](https://github.com/cashubtc/nutshell) 16 | 17 | Most of these just pull the released docker images from dockerhub, but there are also some custom services: 18 | 19 | * `bitcoind` this is a [custom build of bitcoind](https://github.com/benthecarman/bitcoin/releases) with soft forks and 20 | 30s block time. It also contains the scripts to mine signet blocks. 21 | * `electrs` this is a small fork of electrs to add a dockerfile and some fixes for signet, however these fixes ended up 22 | not being needed IIRC. 23 | * `rapid-gossip-sync-server` this is a fork of rapid-gossip-sync-server to allow for a 10m snapshot interval. At the 24 | time there was no way to change the interval in the project, now there is but is has worked so far so I have not 25 | updated it. 26 | 27 | ## Running 28 | 29 | To run the deployment, you need to have docker and docker-compose installed. Then you can run: 30 | 31 | ```bash 32 | docker-compose up -d 33 | ``` 34 | 35 | This will start all the services. You can check the logs with: 36 | 37 | ```bash 38 | docker-compose logs -f 39 | ``` 40 | 41 | You can also run the services individually: 42 | 43 | ```bash 44 | docker-compose up -d bitcoind lnd rgs_server 45 | ``` 46 | 47 | You can create some aliases to make it easier to interact with bitcoind and lnd: 48 | 49 | ```bash 50 | alias lncli="docker exec -it lnd /bin/lncli -n signet" 51 | alias bitcoin-cli="docker exec -it bitcoind /usr/local/bin/bitcoin-cli" 52 | ``` 53 | 54 | ## Updating 55 | 56 | To update the deployment, you can run: 57 | 58 | ```bash 59 | git pull 60 | docker-compose pull 61 | ``` 62 | 63 | And then restart the services: 64 | 65 | ```bash 66 | docker-compose up -d 67 | ``` 68 | -------------------------------------------------------------------------------- /bitcoind-signet.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Signet Docker Image 2 | ## ENV Variables 3 | 4 | * `BLOCKPRODUCTIONDELAY` - default sleep period between mining blocks (**mining mode only**) 5 | * if ~/.bitcoin/BLOCKPRODUCTIONDELAY.txt is present will use this value, allowing the delay to be dynamically changed. 6 | * `MINERENABLED` - flag for enabling mining chain 7 | * `NBITS` - sets min difficulty in mining (**mining mode only**) 8 | * `PRIVKEY` - private key of signet signer (**mining mode only**) 9 | * if `MINERENABLED=1` and not provided will generate this 10 | * `MINETO` - mine to a static address, if not provided will make new address for each block (**mining mode only**) 11 | * `SIGNETCHALLENGE` - sets the valid block producer for this signet 12 | * if `MINERENABLED=1` and not provided will generate this, if provded PRIVKEY also must be populated 13 | * Requied for client-mode 14 | * 15 | * `RPCUSER` - bitcoind RPC User 16 | * `RPCPASSWORD` - bitcoind RPC password 17 | * 18 | * `ONIONPROXY` - tor SOCK5 endpoint 19 | * `TORPASSWORD` - tor control port password 20 | * `TORCONTROL` - tor control port endpoint 21 | * `I2PSAM` - I2P control endpoint 22 | * `UACOMMENT` - UA Comment which would show on bitcoin-cli -netinfo printout 23 | * 24 | * `ZMQPUBRAWBLOCK` - bitcoind setting 25 | * `ZMQPUBRAWTX` - bitcoind setting 26 | * `ZMQPUBHASHBLOCK` - bitcoind setting 27 | * 28 | * `RPCBIND` - bitcoind setting 29 | * `RPCALLOWIP` - bitcoind setting 30 | * `WHITELIST` - bitcoind setting 31 | * `ADDNODE` - add seeding node location, comma-separate for multiple nodes (needed for client-mode) 32 | * `EXTERNAL_IP` - add public IP/onion endpoint information, comma-seperated for multiple IPs. 33 | 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | x-logging: 4 | &default-logging 5 | driver: "json-file" 6 | options: 7 | max-size: "50m" 8 | max-file: "3" 9 | 10 | services: 11 | bitcoind: 12 | container_name: "bitcoind" 13 | build: . 14 | user: "0:1000" 15 | logging: *default-logging 16 | restart: always 17 | stop_grace_period: 1m 18 | volumes: 19 | - ~/volumes/.bitcoin:/root/.bitcoin 20 | environment: 21 | UACOMMENT: $UACOMMENT 22 | BLOCKPRODUCTIONDELAY: $BLOCKPRODUCTIONDELAY 23 | NBITS: $NBITS 24 | RPCPASSWORD: $RPCPASSWORD 25 | PRIVKEY: $PRIVKEY 26 | SIGNETCHALLENGE: $SIGNETCHALLENGE 27 | EXTERNAL_IP: $EXTERNAL_IP 28 | ports: 29 | - "28332:28332" 30 | - "28333:28333" 31 | - "28334:28334" 32 | - "38332:38332" 33 | - "38333:38333" 34 | - "38334:38334" 35 | lnd: 36 | container_name: "lnd" 37 | image: lightninglabs/lnd:v0.18.5-beta 38 | user: "0:1000" 39 | logging: *default-logging 40 | restart: always 41 | stop_grace_period: 10m 42 | environment: 43 | RPCPASSWORD: $RPCPASSWORD 44 | command: [ 45 | "--bitcoin.active", 46 | "--bitcoin.signet", 47 | "--bitcoin.node=bitcoind", 48 | "--maxpendingchannels=10", 49 | "--rpclisten=0.0.0.0:10009", 50 | "--restlisten=0.0.0.0:8081", 51 | "--bitcoind.rpchost=bitcoind:38332", 52 | "--bitcoind.rpcuser=bitcoin", 53 | "--bitcoind.rpcpass=$RPCPASSWORD", 54 | "--bitcoind.zmqpubrawblock=tcp://bitcoind:28332", 55 | "--bitcoind.zmqpubrawtx=tcp://bitcoind:28333", 56 | "--db.bolt.auto-compact", 57 | "--db.prune-revocation", 58 | "--alias=Faucet LND", 59 | "--externalip=mutinynet.com", 60 | "--externalip=gfg7fwat27mnsmlog7wbgi6a53f2b5rj56bwokcfk45bacnb4z5kt5ad.onion", 61 | "--tlsextradomain=mutinynet.com", 62 | "--tlsextradomain=lnd", 63 | "--protocol.option-scid-alias", 64 | "--protocol.wumbo-channels", 65 | "--accept-keysend", 66 | "--minchansize=25000", 67 | "--noseedbackup", 68 | "--gc-canceled-invoices-on-startup", 69 | "--coin-selection-strategy=random", 70 | "--protocol.custom-message=513", 71 | "--protocol.custom-nodeann=39", 72 | "--protocol.custom-init=39", 73 | ] 74 | volumes: 75 | - ~/volumes/.lnd:/root/.lnd 76 | ports: 77 | - "9735:9735" 78 | - "10009:10009" 79 | - "8081:8081" 80 | lndk: 81 | build: ./lndk 82 | restart: unless-stopped 83 | user: "0:1000" 84 | logging: *default-logging 85 | depends_on: 86 | - lnd 87 | command: --address=https://lnd:10009 --cert-path=/root/.lnd/tls.cert --macaroon-path=/root/.lnd/data/chain/bitcoin/signet/admin.macaroon --log-level=trace --grpc-host=0.0.0.0 88 | environment: 89 | - RUST_BACKTRACE=1 90 | volumes: 91 | - ~/volumes/lndk:/root/.lndk 92 | - ~/volumes/.lnd:/root/.lnd:ro 93 | rgs_server: 94 | container_name: "rgs-server" 95 | logging: *default-logging 96 | restart: always 97 | stop_grace_period: 1m 98 | build: 99 | context: ./rapid-gossip-sync-server 100 | dockerfile: ./docker/Dockerfile.rgs 101 | volumes: 102 | - ~/volumes/rgs:/usr/src/app:cached 103 | links: 104 | - postgres 105 | - bitcoind 106 | depends_on: 107 | - postgres 108 | environment: 109 | RAPID_GOSSIP_SYNC_SERVER_DB_HOST: postgres 110 | RAPID_GOSSIP_SYNC_SERVER_DB_USER: lightning-rgs 111 | RAPID_GOSSIP_SYNC_SERVER_DB_PASSWORD: docker 112 | RAPID_GOSSIP_SYNC_SERVER_DB_NAME: ln_graph_sync 113 | RAPID_GOSSIP_SYNC_SERVER_NETWORK: signet 114 | RAPID_GOSSIP_SYNC_SERVER_SNAPSHOT_INTERVAL: 3600 # 1 hour 115 | BITCOIN_REST_DOMAIN: bitcoind 116 | BITCOIN_REST_PORT: 38332 117 | LN_PEERS: 02465ed5be53d04fde66c9418ff14a5f2267723810176c9212b722e542dc1afb1b@lnd:9735 # lnd's node id 118 | entrypoint: [ "rapid-gossip-sync-server" ] 119 | 120 | postgres: 121 | container_name: "postgres" 122 | image: 'postgres:12-alpine' 123 | logging: *default-logging 124 | restart: always 125 | stop_grace_period: 1m 126 | user: "0:1000" 127 | ports: 128 | - "5432:5432" 129 | volumes: 130 | - ~/volumes/postgres:/var/lib/postgresql/data 131 | environment: 132 | POSTGRES_USER: lightning-rgs 133 | POSTGRES_PASSWORD: docker 134 | POSTGRES_DB: ln_graph_sync 135 | electrs: 136 | container_name: "electrs" 137 | build: 138 | context: ./electrs 139 | user: "0:1000" 140 | logging: *default-logging 141 | restart: always 142 | stop_grace_period: 1m 143 | environment: 144 | FLAGS: '-vvvv --signet-magic cb2ddfa5 --jsonrpc-import --daemon-dir /root/.bitcoin/signet --daemon-rpc-addr bitcoind:38332 --timestamp --blocks-dir /root/.bitcoin/signet/blocks --cookie=bitcoin:$RPCPASSWORD --db-dir /root/.electrs --network signet --electrum-rpc-addr 0.0.0.0:50001 --http-addr 0.0.0.0:3003' 145 | volumes: 146 | - ~/volumes/electrs:/root/.electrs 147 | ports: 148 | - "3003:3003" 149 | - "50001:50001" 150 | nutshell: 151 | container_name: "nutshell" 152 | image: 'cashubtc/nutshell:0.16.5' 153 | logging: *default-logging 154 | restart: always 155 | stop_grace_period: 1m 156 | user: "0:1000" 157 | ports: 158 | - "3338:3338" 159 | environment: 160 | MINT_LIGHTNING_BACKEND: "LndRestWallet" 161 | MINT_LISTEN_HOST: "0.0.0.0" 162 | MINT_LISTEN_PORT: "3338" 163 | MINT_PRIVATE_KEY: $MINT_PRIVATE_KEY 164 | MINT_LND_REST_ENDPOINT: "https://lnd:8081" 165 | MINT_LND_REST_CERT: "/root/.lnd/tls.cert" 166 | MINT_LND_REST_MACAROON: "/root/.lnd/data/chain/bitcoin/signet/admin.macaroon" 167 | command: [ "poetry", "run", "mint" ] 168 | volumes: 169 | - ~/volumes/.lnd:/root/.lnd:ro 170 | - ~/volumes/nutshell:/root/.cashu 171 | faucet: 172 | container_name: "faucet" 173 | image: ghcr.io/mutinywallet/mutinynet-faucet:master 174 | user: "0:1000" 175 | logging: *default-logging 176 | restart: always 177 | stop_grace_period: 1m 178 | volumes: 179 | - ~/volumes/faucet:/root/.faucet 180 | ports: 181 | - "3000:3000" 182 | faucet_backend: 183 | container_name: "faucet_backend" 184 | image: ghcr.io/mutinywallet/mutinynet-faucet-rs:master 185 | environment: 186 | RUST_LOG: "info" 187 | BITCOIN_RPC_HOST_AND_PORT: "bitcoind:38332" 188 | BITCOIN_RPC_USER: "bitcoin" 189 | BITCOIN_RPC_PASSWORD: $RPCPASSWORD 190 | NSEC: $NSEC 191 | JWT_SECRET: $JWT_SECRET 192 | GITHUB_CLIENT_ID: $GITHUB_CLIENT_ID 193 | GITHUB_CLIENT_SECRET: $GITHUB_CLIENT_SECRET 194 | NETWORK: "signet" 195 | HOST: "https://faucet.mutinynet.com" 196 | GRPC_PORT: "10009" 197 | GRPC_HOST: "lnd" 198 | TLS_CERT_PATH: "/root/.lnd/tls.cert" 199 | ADMIN_MACAROON_PATH: "/root/.lnd/data/chain/bitcoin/signet/admin.macaroon" 200 | user: "0:1000" 201 | logging: *default-logging 202 | restart: always 203 | stop_grace_period: 1m 204 | volumes: 205 | - ~/volumes/.lnd:/root/.lnd:ro 206 | - ./faucet_config:/app/faucet_config:ro 207 | ports: 208 | - "3001:3001" 209 | lnurl_server: 210 | container_name: "lnurl_server" 211 | image: ghcr.io/benthecarman/lnurl-server:master 212 | environment: 213 | RUST_LOG: "info" 214 | LNURL_DATA_DIR: "/root/.lnurl" 215 | LNURL_PORT: "3002" 216 | LNURL_LND_HOST: "lnd" 217 | LNURL_LND_PORT: "10009" 218 | LNURL_LND_CERT_PATH: "/root/.lnd/tls.cert" 219 | LNURL_LND_MACAROON_PATH: "/root/.lnd/data/chain/bitcoin/signet/admin.macaroon" 220 | LNURL_NETWORK: "signet" 221 | LNURL_DOMAIN: "lnurl.mutinynet.com" 222 | user: "0:1000" 223 | logging: *default-logging 224 | restart: always 225 | stop_grace_period: 1m 226 | volumes: 227 | - ~/volumes/.lnd:/root/.lnd:ro 228 | - ~/volumes/lnurl:/root/.lnurl 229 | ports: 230 | - "3002:3002" 231 | web: 232 | container_name: "mempool_frontend" 233 | environment: 234 | MAINNET_ENABLED: "false" 235 | ROOT_NETWORK: "signet" 236 | FRONTEND_HTTP_PORT: "8080" 237 | BACKEND_MAINNET_HTTP_HOST: "api" 238 | LIGHTNING: "true" 239 | image: mempool/frontend:latest 240 | user: "0:1000" 241 | logging: *default-logging 242 | restart: always 243 | stop_grace_period: 1m 244 | command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'" 245 | ports: 246 | - "8080:8080" 247 | api: 248 | container_name: "mempool_backend" 249 | environment: 250 | MEMPOOL_NETWORK: "signet" 251 | MEMPOOL_BACKEND: "none" 252 | ELECTRUM_HOST: "electrs" 253 | ELECTRUM_PORT: "50001" 254 | ELECTRUM_TLS_ENABLED: "false" 255 | ESPLORA_REST_API_URL: "http://electrs:3003" 256 | CORE_RPC_HOST: "bitcoind" 257 | CORE_RPC_PORT: "38332" 258 | CORE_RPC_USERNAME: "bitcoin" 259 | CORE_RPC_PASSWORD: $RPCPASSWORD 260 | CORE_RPC_TIMEOUT: "60000" 261 | DATABASE_ENABLED: "true" 262 | DATABASE_HOST: "db" 263 | DATABASE_DATABASE: "mempool" 264 | DATABASE_USERNAME: "mempool" 265 | DATABASE_PASSWORD: "mempool" 266 | STATISTICS_ENABLED: "false" 267 | LIGHTNING_ENABLED: "true" 268 | LIGHTNING_BACKEND: "lnd" 269 | LND_TLS_CERT_PATH: "/root/.lnd/tls.cert" 270 | LND_MACAROON_PATH: "/root/.lnd/data/chain/bitcoin/signet/admin.macaroon" 271 | LND_REST_API_URL: "https://lnd:8081" 272 | image: mempool/backend:latest 273 | logging: *default-logging 274 | user: "0:1000" 275 | restart: always 276 | stop_grace_period: 1m 277 | command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh" 278 | ports: 279 | - "8889:8889" 280 | - "8999:8999" 281 | volumes: 282 | - ~/volumes/mempool:/backend/cache 283 | - ~/volumes/.lnd:/root/.lnd:ro 284 | db: 285 | container_name: "mempool_db" 286 | environment: 287 | MYSQL_DATABASE: "mempool" 288 | MYSQL_USER: "mempool" 289 | MYSQL_PASSWORD: "mempool" 290 | MYSQL_ROOT_PASSWORD: "admin" 291 | image: mariadb:10.5.8 292 | logging: *default-logging 293 | user: "0:1000" 294 | restart: always 295 | stop_grace_period: 1m 296 | volumes: 297 | - ~/volumes/mysql/data:/var/lib/mysql 298 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | shutdown_gracefully(){ 5 | 6 | echo "Container is shutting down, lets make sure bitcoind flushes the db." 7 | bitcoin-cli stop 8 | sleep 5 9 | } 10 | trap shutdown_gracefully SIGTERM SIGHUP SIGQUIT SIGINT 11 | 12 | mkdir -p "${BITCOIN_DIR}" 13 | # check if this is first run if so run init if config 14 | if [[ ! -f "${BITCOIN_DIR}/install_done" ]]; then 15 | echo "install_done file not found, running install.sh." 16 | install.sh #this is config based on args passed into mining node or peer. 17 | else 18 | echo "install_done file exists, skipping setup process." 19 | echo "rewrite bitcoin.conf" 20 | gen-bitcoind-conf.sh >~/.bitcoin/bitcoin.conf 21 | fi 22 | 23 | $@ & 24 | echo "Infinate loop" 25 | while true 26 | do 27 | tail -f /dev/null & wait ${!} 28 | done -------------------------------------------------------------------------------- /gen-bitcoind-conf.sh: -------------------------------------------------------------------------------- 1 | SIGNETCHALLENGE=${SIGNETCHALLENGE:-$(cat ~/.bitcoin/SIGNETCHALLENGE.txt)} 2 | 3 | RPCAUTH=$(/usr/local/bin/rpcauth.py $RPCUSER $RPCPASSWORD | tr -d '\n') 4 | echo "signet=1" 5 | 6 | if [[ "$COOKIEFILE" == "true" ]]; then 7 | echo "rpccookiefile=/root/.bitcoin/.cookie 8 | rpcauth=$RPCAUTH" 9 | else 10 | echo "rpcauth=$RPCAUTH 11 | rpcuser=$RPCUSER 12 | rpcpassword=$RPCPASSWORD" 13 | fi 14 | 15 | echo "txindex=1 16 | blockfilterindex=1 17 | peerblockfilters=1 18 | coinstatsindex=1 19 | dnsseed=0 20 | persistmempool=1 21 | uacomment=$UACOMMENT" 22 | 23 | if [[ "$EXTERNAL_IP" != "" ]]; then 24 | echo $EXTERNAL_IP | tr ',' '\n' | while read ip; do 25 | echo "externalip=$ip" 26 | done 27 | fi 28 | 29 | echo "[signet] 30 | rest=1 31 | daemon=1 32 | listen=1 33 | server=1 34 | acceptnonstdtxn=1 35 | v2transport=1 36 | discover=1 37 | signetblocktime=$BLOCKPRODUCTIONDELAY 38 | signetchallenge=$SIGNETCHALLENGE 39 | zmqpubrawblock=$ZMQPUBRAWBLOCK 40 | zmqpubrawtx=$ZMQPUBRAWTX 41 | zmqpubhashblock=$ZMQPUBHASHBLOCK 42 | rpcbind=$RPCBIND 43 | rpcallowip=$RPCALLOWIP 44 | whitelist=$WHITELIST 45 | fallbackfee=0.0002" 46 | 47 | if [[ "$ADDNODE" != "" ]]; then 48 | echo $ADDNODE | tr ',' '\n' | while read node; do 49 | echo "addnode=$node" 50 | done 51 | fi 52 | 53 | 54 | if [[ "$I2PSAM" != "" ]]; then 55 | echo "i2psam=$I2PSAM" 56 | fi 57 | if [[ "$ONIONPROXY" != "" ]]; then 58 | echo "onion=$ONIONPROXY" # unless have static IP won't resolve the control port as domain 59 | fi 60 | 61 | if [[ "$TORPASSWORD" != "" ]]; then 62 | echo "torpassword=$TORPASSWORD" 63 | fi 64 | 65 | if [[ "$TORCONTROL" != "" ]]; then 66 | echo "torcontrol=$TORCONTROL" 67 | fi 68 | -------------------------------------------------------------------------------- /gen-signet-keys.sh: -------------------------------------------------------------------------------- 1 | DATADIR=${DATADIR:-"regtest-temp"} 2 | BITCOINCLI=${BITCOINCLI:-"bitcoin-cli -regtest -datadir=$DATADIR "} 3 | BITCOIND=${BITCOIND:-"bitcoind -datadir=$DATADIR -regtest -daemon "} 4 | 5 | write_files() { 6 | # echo "ADDR=" $ADDR 7 | echo "PRIVKEY=" $PRIVKEY 8 | # echo "PUBKEY=" $PUBKEY 9 | echo "SIGNETCHALLENGE=" $SIGNETCHALLENGE 10 | # echo $ADDR > ~/.bitcoin/ADDR.txt 11 | echo $PRIVKEY >~/.bitcoin/PRIVKEY.txt 12 | # echo $PUBKEY > ~/.bitcoin/PUBKEY.txt 13 | echo $SIGNETCHALLENGE >~/.bitcoin/SIGNETCHALLENGE.txt 14 | } 15 | 16 | if [[ "$MINERENABLED" == "1" && ("$SIGNETCHALLENGE" == "" || "$PRIVKEY" == "") ]]; then 17 | echo "Generating new signetchallange and privkey." 18 | #clean if exists 19 | rm -rf $DATADIR 20 | #make it fresh 21 | mkdir $DATADIR 22 | #kill any daemon running stuff 23 | pkill bitcoind 24 | #minimal config file (hardcode bitcoin:bitcoin for rpc) 25 | echo " 26 | regtest=1 27 | server=1 28 | rpcauth=bitcoin:c8c8b9740a470454255b7a38d4f38a52\$e8530d1c739a3bb0ec6e9513290def11651afbfd2b979f38c16ec2cf76cf348a 29 | rpcuser=bitcoin 30 | rpcpassword=bitcoin 31 | " >$DATADIR/bitcoin.conf 32 | #start daemon 33 | $BITCOIND -wallet="temp" 34 | #wait a bit for startup 35 | sleep 5s 36 | #create wallet 37 | $BITCOINCLI -named createwallet wallet_name="tmp" descriptors=false 38 | #export future signet seeding key data 39 | ADDR=$($BITCOINCLI getnewaddress) 40 | PRIVKEY=$($BITCOINCLI dumpprivkey $ADDR) 41 | PUBKEY=$($BITCOINCLI getaddressinfo $ADDR | jq .pubkey | tr -d '""') 42 | #don't need regtest anymore 43 | $BITCOINCLI stop 44 | SIGNETCHALLENGE=$(echo '5121'$PUBKEY'51ae') 45 | 46 | #cleanup 47 | rm -rf $DATADIR 48 | else 49 | echo "Imported signetchallange and privkey being used." 50 | fi 51 | 52 | write_files 53 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | echo "Generate or import keyset" 2 | gen-signet-keys.sh 3 | echo "Generate bitcoind configuration" 4 | gen-bitcoind-conf.sh >~/.bitcoin/bitcoin.conf 5 | echo "Setup Signet" 6 | setup-signet.sh 7 | 8 | if [[ "$MINE_GENESIS" == "1" ]]; then 9 | echo "Mine Genesis Block" 10 | mine-genesis.sh 11 | fi 12 | 13 | touch ~/.bitcoin/install_done 14 | -------------------------------------------------------------------------------- /lndk/Dockerfile: -------------------------------------------------------------------------------- 1 | ################# 2 | # Builder image # 3 | ################# 4 | FROM rust:1.78-bookworm AS builder 5 | 6 | # References for lndk 7 | ARG LNDK_SOURCE=https://github.com/lndk-org/lndk.git 8 | ARG LNDK_REF=60e17a0c94177f7d2d160aa08f5cc32a1274b51e 9 | 10 | # Add utils 11 | RUN apt-get update \ 12 | && apt-get install -y --no-install-recommends \ 13 | protobuf-compiler \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | # Set the working directory in the Docker image 18 | WORKDIR /usr/src 19 | 20 | # Grab and install the latest version of lndk 21 | RUN git clone ${LNDK_SOURCE} . \ 22 | && git reset --hard ${LNDK_REF} \ 23 | && cargo build --release 24 | 25 | # Copy the compiled binaries to /bin/ 26 | RUN cp ./target/release/lndk /bin/ 27 | RUN cp ./target/release/lndk-cli /bin/ 28 | 29 | ############### 30 | # final image # 31 | ############### 32 | FROM debian:bookworm-slim AS final 33 | 34 | # Add utils 35 | RUN apt-get update \ 36 | && apt-get install -y --no-install-recommends \ 37 | bash \ 38 | curl \ 39 | && apt-get clean \ 40 | && rm -rf /var/lib/apt/lists/* 41 | 42 | # Copy the compiled binaries from the builder image 43 | COPY --from=builder /bin/lndk /usr/local/bin/ 44 | COPY --from=builder /bin/lndk-cli /usr/local/bin/ 45 | 46 | # Expose grpc port 47 | EXPOSE 7000 48 | 49 | ENTRYPOINT ["lndk"] -------------------------------------------------------------------------------- /mine-genesis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ADDR=${ADDR:-$(bitcoin-cli getnewaddress)} 3 | NBITS=${NBITS:-"1e0377ae"} #minimum difficulty in signet 4 | miner --cli="bitcoin-cli" generate --address=$ADDR --grind-cmd="bitcoin-util grind" --nbits=$NBITS --set-block-time=$(date +%s) 5 | 6 | 7 | -------------------------------------------------------------------------------- /mine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NBITS=${NBITS:-"1e0377ae"} #minimum difficulty in signet 3 | 4 | while true; do 5 | if [[ -f "${BITCOIN_DIR}/MINE_ADDRESS.txt" ]]; then 6 | ADDR=$(cat ~/.bitcoin/MINE_ADDRESS.txt) 7 | else 8 | ADDR=${MINETO:-$(bitcoin-cli getnewaddress)} 9 | fi 10 | if [[ -f "${BITCOIN_DIR}/BLOCKPRODUCTIONDELAY.txt" ]]; then 11 | BLOCKPRODUCTIONDELAY_OVERRIDE=$(cat ~/.bitcoin/BLOCKPRODUCTIONDELAY.txt) 12 | echo "Delay OVERRIDE before next block" $BLOCKPRODUCTIONDELAY_OVERRIDE "seconds." 13 | sleep $BLOCKPRODUCTIONDELAY_OVERRIDE 14 | else 15 | BLOCKPRODUCTIONDELAY=${BLOCKPRODUCTIONDELAY:="0"} 16 | if [[ BLOCKPRODUCTIONDELAY -gt 0 ]]; then 17 | echo "Delay before next block" $BLOCKPRODUCTIONDELAY "seconds." 18 | sleep $BLOCKPRODUCTIONDELAY 19 | fi 20 | fi 21 | echo "Mine To:" $ADDR 22 | miner --cli="bitcoin-cli" generate --grind-cmd="bitcoin-util grind" --address=$ADDR --nbits=$NBITS --set-block-time=$(date +%s) 23 | done 24 | -------------------------------------------------------------------------------- /miner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2020 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | # Modified for quick blocks and run in leaner docker container 6 | import argparse 7 | import base64 8 | import json 9 | import logging 10 | import math 11 | import os 12 | import re 13 | import struct 14 | import sys 15 | import time 16 | import subprocess 17 | 18 | from io import BytesIO 19 | 20 | PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 21 | PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "miner_imports")) 22 | sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL) 23 | 24 | from test_framework.blocktools import WITNESS_COMMITMENT_HEADER, script_BIP34_coinbase_height # noqa: E402 25 | from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_hex, deser_string, hash256, ser_compact_size, ser_string, ser_uint256, tx_from_hex, uint256_from_str # noqa: E402 26 | from test_framework.script import CScriptOp # noqa: E402 27 | 28 | logging.basicConfig( 29 | format='%(asctime)s %(levelname)s %(message)s', 30 | level=logging.INFO, 31 | datefmt='%Y-%m-%d %H:%M:%S') 32 | 33 | SIGNET_HEADER = b"\xec\xc7\xda\xa2" 34 | PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed 35 | RE_MULTIMINER = re.compile("^(\d+)(-(\d+))?/(\d+)$") 36 | 37 | # #### some helpers that could go into test_framework 38 | 39 | # like from_hex, but without the hex part 40 | def FromBinary(cls, stream): 41 | """deserialize a binary stream (or bytes object) into an object""" 42 | # handle bytes object by turning it into a stream 43 | was_bytes = isinstance(stream, bytes) 44 | if was_bytes: 45 | stream = BytesIO(stream) 46 | obj = cls() 47 | obj.deserialize(stream) 48 | if was_bytes: 49 | assert len(stream.read()) == 0 50 | return obj 51 | 52 | class PSBTMap: 53 | """Class for serializing and deserializing PSBT maps""" 54 | 55 | def __init__(self, map=None): 56 | self.map = map if map is not None else {} 57 | 58 | def deserialize(self, f): 59 | m = {} 60 | while True: 61 | k = deser_string(f) 62 | if len(k) == 0: 63 | break 64 | v = deser_string(f) 65 | if len(k) == 1: 66 | k = k[0] 67 | assert k not in m 68 | m[k] = v 69 | self.map = m 70 | 71 | def serialize(self): 72 | m = b"" 73 | for k,v in self.map.items(): 74 | if isinstance(k, int) and 0 <= k and k <= 255: 75 | k = bytes([k]) 76 | m += ser_compact_size(len(k)) + k 77 | m += ser_compact_size(len(v)) + v 78 | m += b"\x00" 79 | return m 80 | 81 | class PSBT: 82 | """Class for serializing and deserializing PSBTs""" 83 | 84 | def __init__(self): 85 | self.g = PSBTMap() 86 | self.i = [] 87 | self.o = [] 88 | self.tx = None 89 | 90 | def deserialize(self, f): 91 | assert f.read(5) == b"psbt\xff" 92 | self.g = FromBinary(PSBTMap, f) 93 | assert 0 in self.g.map 94 | self.tx = FromBinary(CTransaction, self.g.map[0]) 95 | self.i = [FromBinary(PSBTMap, f) for _ in self.tx.vin] 96 | self.o = [FromBinary(PSBTMap, f) for _ in self.tx.vout] 97 | return self 98 | 99 | def serialize(self): 100 | assert isinstance(self.g, PSBTMap) 101 | assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i) 102 | assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o) 103 | assert 0 in self.g.map 104 | tx = FromBinary(CTransaction, self.g.map[0]) 105 | assert len(tx.vin) == len(self.i) 106 | assert len(tx.vout) == len(self.o) 107 | 108 | psbt = [x.serialize() for x in [self.g] + self.i + self.o] 109 | return b"psbt\xff" + b"".join(psbt) 110 | 111 | def to_base64(self): 112 | return base64.b64encode(self.serialize()).decode("utf8") 113 | 114 | @classmethod 115 | def from_base64(cls, b64psbt): 116 | return FromBinary(cls, base64.b64decode(b64psbt)) 117 | 118 | # ##### 119 | 120 | def create_coinbase(height, value, spk): 121 | cb = CTransaction() 122 | cb.vin = [CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)] 123 | cb.vout = [CTxOut(value, spk)] 124 | return cb 125 | 126 | def get_witness_script(witness_root, witness_nonce): 127 | commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))) 128 | return b"\x6a" + CScriptOp.encode_op_pushdata(WITNESS_COMMITMENT_HEADER + ser_uint256(commitment)) 129 | 130 | def signet_txs(block, challenge): 131 | # assumes signet solution has not been added yet so does not need 132 | # to be removed 133 | 134 | txs = block.vtx[:] 135 | txs[0] = CTransaction(txs[0]) 136 | txs[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER) 137 | hashes = [] 138 | for tx in txs: 139 | tx.rehash() 140 | hashes.append(ser_uint256(tx.sha256)) 141 | mroot = block.get_merkle_root(hashes) 142 | 143 | sd = b"" 144 | sd += struct.pack("> 24) & 0xff 280 | return (nbits & 0x00ffffff) * 2**(8*(shift - 3)) 281 | 282 | def target_to_nbits(target): 283 | tstr = "{0:x}".format(target) 284 | if len(tstr) < 6: 285 | tstr = ("000000"+tstr)[-6:] 286 | if len(tstr) % 2 != 0: 287 | tstr = "0" + tstr 288 | if int(tstr[0],16) >= 0x8: 289 | # avoid "negative" 290 | tstr = "00" + tstr 291 | fix = int(tstr[:6], 16) 292 | sz = len(tstr)//2 293 | if tstr[6:] != "0"*(sz*2-6): 294 | fix += 1 295 | 296 | return int("%02x%06x" % (sz,fix), 16) 297 | 298 | def seconds_to_hms(s): 299 | if s == 0: 300 | return "0s" 301 | neg = (s < 0) 302 | if neg: 303 | s = -s 304 | out = "" 305 | if s % 60 > 0: 306 | out = "%ds" % (s % 60) 307 | s //= 60 308 | if s % 60 > 0: 309 | out = "%dm%s" % (s % 60, out) 310 | s //= 60 311 | if s > 0: 312 | out = "%dh%s" % (s, out) 313 | if neg: 314 | out = "-" + out 315 | return out 316 | 317 | def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson): 318 | this_interval = 0.000001 319 | return this_interval 320 | 321 | def next_block_is_mine(last_hash, my_blocks): 322 | det_rand = int(last_hash[-16:-8], 16) 323 | return my_blocks[0] <= (det_rand % my_blocks[2]) < my_blocks[1] 324 | 325 | def do_generate(args): 326 | if args.max_blocks is not None: 327 | if args.ongoing: 328 | logging.error("Cannot specify both --ongoing and --max-blocks") 329 | return 1 330 | if args.max_blocks < 1: 331 | logging.error("N must be a positive integer") 332 | return 1 333 | max_blocks = args.max_blocks 334 | elif args.ongoing: 335 | max_blocks = None 336 | else: 337 | max_blocks = 1 338 | 339 | if args.set_block_time is not None and max_blocks != 1: 340 | logging.error("Cannot specify --ongoing or --max-blocks > 1 when using --set-block-time") 341 | return 1 342 | if args.set_block_time is not None and args.set_block_time < 0: 343 | args.set_block_time = time.time() 344 | logging.info("Treating negative block time as current time (%d)" % (args.set_block_time)) 345 | 346 | if args.min_nbits: 347 | if args.nbits is not None: 348 | logging.error("Cannot specify --nbits and --min-nbits") 349 | return 1 350 | args.nbits = "1e0377ae" 351 | logging.info("Using nbits=%s" % (args.nbits)) 352 | 353 | if args.set_block_time is None: 354 | if args.nbits is None or len(args.nbits) != 8: 355 | logging.error("Must specify --nbits (use calibrate command to determine value)") 356 | return 1 357 | 358 | if args.multiminer is None: 359 | my_blocks = (0,1,1) 360 | else: 361 | if not args.ongoing: 362 | logging.error("Cannot specify --multiminer without --ongoing") 363 | return 1 364 | m = RE_MULTIMINER.match(args.multiminer) 365 | if m is None: 366 | logging.error("--multiminer argument must be k/m or j-k/m") 367 | return 1 368 | start,_,stop,total = m.groups() 369 | if stop is None: 370 | stop = start 371 | start, stop, total = map(int, (start, stop, total)) 372 | if stop < start or start <= 0 or total < stop or total == 0: 373 | logging.error("Inconsistent values for --multiminer") 374 | return 1 375 | my_blocks = (start-1, stop, total) 376 | 377 | ultimate_target = nbits_to_target(int(args.nbits,16)) 378 | 379 | mined_blocks = 0 380 | bestheader = {"hash": None} 381 | lastheader = None 382 | while max_blocks is None or mined_blocks < max_blocks: 383 | 384 | # current status? 385 | bci = json.loads(args.bcli("getblockchaininfo")) 386 | 387 | if bestheader["hash"] != bci["bestblockhash"]: 388 | bestheader = json.loads(args.bcli("getblockheader", bci["bestblockhash"])) 389 | 390 | if lastheader is None: 391 | lastheader = bestheader["hash"] 392 | elif bestheader["hash"] != lastheader: 393 | next_delta = next_block_delta(int(bestheader["bits"], 16), bestheader["hash"], ultimate_target, args.poisson) 394 | next_delta += bestheader["time"] - time.time() 395 | next_is_mine = next_block_is_mine(bestheader["hash"], my_blocks) 396 | logging.info("Received new block at height %d; next in %s (%s)", bestheader["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup")) 397 | lastheader = bestheader["hash"] 398 | 399 | # when is the next block due to be mined? 400 | now = time.time() 401 | if args.set_block_time is not None: 402 | logging.debug("Setting start time to %d", args.set_block_time) 403 | mine_time = args.set_block_time 404 | action_time = now 405 | is_mine = True 406 | elif bestheader["height"] == 0: 407 | time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) 408 | time_delta *= 100 # 100 blocks 409 | logging.info("Backdating time for first block to %d minutes ago" % (time_delta/60)) 410 | mine_time = now - time_delta 411 | action_time = now 412 | is_mine = True 413 | else: 414 | time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) 415 | mine_time = bestheader["time"] + time_delta 416 | 417 | is_mine = next_block_is_mine(bci["bestblockhash"], my_blocks) 418 | 419 | action_time = mine_time 420 | if not is_mine: 421 | action_time += args.backup_delay 422 | 423 | if args.standby_delay > 0: 424 | action_time += args.standby_delay 425 | elif mined_blocks == 0: 426 | # for non-standby, always mine immediately on startup, 427 | # even if the next block shouldn't be ours 428 | action_time = now 429 | 430 | # don't want fractional times so round down 431 | mine_time = int(mine_time) 432 | action_time = int(action_time) 433 | 434 | # can't mine a block 2h in the future; 1h55m for some safety 435 | action_time = max(action_time, mine_time - 6900) 436 | 437 | # ready to go? otherwise sleep and check for new block 438 | if now < action_time: 439 | sleep_for = min(action_time - now, 60) 440 | if mine_time < now: 441 | # someone else might have mined the block, 442 | # so check frequently, so we don't end up late 443 | # mining the next block if it's ours 444 | sleep_for = min(20, sleep_for) 445 | minestr = "mine" if is_mine else "backup" 446 | logging.debug("Sleeping for %s, next block due in %s (%s)" % (seconds_to_hms(sleep_for), seconds_to_hms(mine_time - now), minestr)) 447 | time.sleep(sleep_for) 448 | continue 449 | 450 | # gbt 451 | tmpl = json.loads(args.bcli("getblocktemplate", '{"rules":["signet","segwit"]}')) 452 | if tmpl["previousblockhash"] != bci["bestblockhash"]: 453 | logging.warning("GBT based off unexpected block (%s not %s), retrying", tmpl["previousblockhash"], bci["bestblockhash"]) 454 | time.sleep(1) 455 | continue 456 | 457 | logging.debug("GBT template: %s", tmpl) 458 | 459 | if tmpl["mintime"] > mine_time: 460 | logging.info("Updating block time from %d to %d", mine_time, tmpl["mintime"]) 461 | mine_time = tmpl["mintime"] 462 | if mine_time > now: 463 | logging.error("GBT mintime is in the future: %d is %d seconds later than %d", mine_time, (mine_time-now), now) 464 | return 1 465 | 466 | # address for reward 467 | reward_addr, reward_spk = get_reward_addr_spk(args, tmpl["height"]) 468 | 469 | # mine block 470 | logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine) 471 | mined_blocks += 1 472 | psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) 473 | input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') 474 | psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) 475 | if not psbt_signed.get("complete",False): 476 | logging.debug("Generated PSBT: %s" % (psbt,)) 477 | sys.stderr.write("PSBT signing failed\n") 478 | return 1 479 | block, signet_solution = do_decode_psbt(psbt_signed["psbt"]) 480 | block = finish_block(block, signet_solution, args.grind_cmd) 481 | 482 | # submit block 483 | r = args.bcli("-stdin", "submitblock", input=block.serialize().hex().encode('utf8')) 484 | 485 | # report 486 | bstr = "block" if is_mine else "backup block" 487 | 488 | next_delta = next_block_delta(block.nBits, block.hash, ultimate_target, args.poisson) 489 | next_delta += block.nTime - time.time() 490 | next_is_mine = next_block_is_mine(block.hash, my_blocks) 491 | 492 | logging.debug("Block hash %s payout to %s", block.hash, reward_addr) 493 | logging.info("Mined %s at height %d; next in %s (%s)", bstr, tmpl["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup")) 494 | if r != "": 495 | logging.warning("submitblock returned %s for height %d hash %s", r, tmpl["height"], block.hash) 496 | lastheader = block.hash 497 | 498 | def do_calibrate(args): 499 | if args.nbits is not None and args.seconds is not None: 500 | sys.stderr.write("Can only specify one of --nbits or --seconds\n") 501 | return 1 502 | if args.nbits is not None and len(args.nbits) != 8: 503 | sys.stderr.write("Must specify 8 hex digits for --nbits\n") 504 | return 1 505 | 506 | TRIALS = 600 # gets variance down pretty low 507 | TRIAL_BITS = 0x1e3ea75f # takes about 5m to do 600 trials 508 | 509 | header = CBlockHeader() 510 | header.nBits = TRIAL_BITS 511 | targ = nbits_to_target(header.nBits) 512 | 513 | start = time.time() 514 | count = 0 515 | for i in range(TRIALS): 516 | header.nTime = i 517 | header.nNonce = 0 518 | headhex = header.serialize().hex() 519 | cmd = args.grind_cmd.split(" ") + [headhex] 520 | newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip() 521 | 522 | avg = (time.time() - start) * 1.0 / TRIALS 523 | 524 | if args.nbits is not None: 525 | want_targ = nbits_to_target(int(args.nbits,16)) 526 | want_time = avg*targ/want_targ 527 | else: 528 | want_time = args.seconds if args.seconds is not None else 25 529 | want_targ = int(targ*(avg/want_time)) 530 | 531 | print("nbits=%08x for %ds average mining time" % (target_to_nbits(want_targ), want_time)) 532 | return 0 533 | 534 | def bitcoin_cli(basecmd, args, **kwargs): 535 | cmd = basecmd + ["-signet"] + args 536 | logging.debug("Calling bitcoin-cli: %r", cmd) 537 | out = subprocess.run(cmd, stdout=subprocess.PIPE, **kwargs, check=True).stdout 538 | if isinstance(out, bytes): 539 | out = out.decode('utf8') 540 | return out.strip() 541 | 542 | def main(): 543 | parser = argparse.ArgumentParser() 544 | parser.add_argument("--cli", default="bitcoin-cli", type=str, help="bitcoin-cli command") 545 | parser.add_argument("--debug", action="store_true", help="Print debugging info") 546 | parser.add_argument("--quiet", action="store_true", help="Only print warnings/errors") 547 | 548 | cmds = parser.add_subparsers(help="sub-commands") 549 | genpsbt = cmds.add_parser("genpsbt", help="Generate a block PSBT for signing") 550 | genpsbt.set_defaults(fn=do_genpsbt) 551 | 552 | solvepsbt = cmds.add_parser("solvepsbt", help="Solve a signed block PSBT") 553 | solvepsbt.set_defaults(fn=do_solvepsbt) 554 | 555 | generate = cmds.add_parser("generate", help="Mine blocks") 556 | generate.set_defaults(fn=do_generate) 557 | generate.add_argument("--ongoing", action="store_true", help="Keep mining blocks") 558 | generate.add_argument("--max-blocks", default=None, type=int, help="Max blocks to mine (default=1)") 559 | generate.add_argument("--set-block-time", default=None, type=int, help="Set block time (unix timestamp)") 560 | generate.add_argument("--nbits", default=None, type=str, help="Target nBits (specify difficulty)") 561 | generate.add_argument("--min-nbits", action="store_true", help="Target minimum nBits (use min difficulty)") 562 | generate.add_argument("--poisson", action="store_true", help="Simulate randomised block times") 563 | generate.add_argument("--multiminer", default=None, type=str, help="Specify which set of blocks to mine (eg: 1-40/100 for the first 40%%, 2/3 for the second 3rd)") 564 | generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)") 565 | generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)") 566 | 567 | calibrate = cmds.add_parser("calibrate", help="Calibrate difficulty") 568 | calibrate.set_defaults(fn=do_calibrate) 569 | calibrate.add_argument("--nbits", type=str, default=None) 570 | calibrate.add_argument("--seconds", type=int, default=None) 571 | 572 | for sp in [genpsbt, generate]: 573 | sp.add_argument("--address", default=None, type=str, help="Address for block reward payment") 574 | sp.add_argument("--descriptor", default=None, type=str, help="Descriptor for block reward payment") 575 | 576 | for sp in [solvepsbt, generate, calibrate]: 577 | sp.add_argument("--grind-cmd", default=None, type=str, required=(sp==calibrate), help="Command to grind a block header for proof-of-work") 578 | 579 | args = parser.parse_args(sys.argv[1:]) 580 | 581 | args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(args.cli.split(" "), list(a), input=input, **kwargs) 582 | 583 | if hasattr(args, "address") and hasattr(args, "descriptor"): 584 | if args.address is None and args.descriptor is None: 585 | sys.stderr.write("Must specify --address or --descriptor\n") 586 | return 1 587 | elif args.address is not None and args.descriptor is not None: 588 | sys.stderr.write("Only specify one of --address or --descriptor\n") 589 | return 1 590 | args.derived_addresses = {} 591 | 592 | if args.debug: 593 | logging.getLogger().setLevel(logging.DEBUG) 594 | elif args.quiet: 595 | logging.getLogger().setLevel(logging.WARNING) 596 | else: 597 | logging.getLogger().setLevel(logging.INFO) 598 | 599 | if hasattr(args, "fn"): 600 | return args.fn(args) 601 | else: 602 | logging.error("Must specify command") 603 | return 1 604 | 605 | if __name__ == "__main__": 606 | main() 607 | 608 | 609 | -------------------------------------------------------------------------------- /miner_imports/test_framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MutinyWallet/mutiny-net/a44c7963bfaeb153e0b22a50194b9a6587cbc8e1/miner_imports/test_framework/__init__.py -------------------------------------------------------------------------------- /miner_imports/test_framework/address.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2016-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Encode and decode Bitcoin addresses. 6 | 7 | - base58 P2PKH and P2SH addresses. 8 | - bech32 segwit v0 P2WPKH and P2WSH addresses. 9 | - bech32m segwit v1 P2TR addresses.""" 10 | 11 | import enum 12 | import unittest 13 | 14 | from .script import ( 15 | CScript, 16 | OP_0, 17 | OP_TRUE, 18 | hash160, 19 | hash256, 20 | sha256, 21 | taproot_construct, 22 | ) 23 | from .segwit_addr import encode_segwit_address 24 | from .util import assert_equal 25 | 26 | ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' 27 | ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97' 28 | # Coins sent to this address can be spent with a witness stack of just OP_TRUE 29 | ADDRESS_BCRT1_P2WSH_OP_TRUE = 'bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85' 30 | 31 | 32 | class AddressType(enum.Enum): 33 | bech32 = 'bech32' 34 | p2sh_segwit = 'p2sh-segwit' 35 | legacy = 'legacy' # P2PKH 36 | 37 | 38 | chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 39 | 40 | 41 | def create_deterministic_address_bcrt1_p2tr_op_true(): 42 | """ 43 | Generates a deterministic bech32m address (segwit v1 output) that 44 | can be spent with a witness stack of OP_TRUE and the control block 45 | with internal public key (script-path spending). 46 | 47 | Returns a tuple with the generated address and the internal key. 48 | """ 49 | internal_key = (1).to_bytes(32, 'big') 50 | scriptPubKey = taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).scriptPubKey 51 | address = encode_segwit_address("bcrt", 1, scriptPubKey[2:]) 52 | assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') 53 | return (address, internal_key) 54 | 55 | 56 | def byte_to_base58(b, version): 57 | result = '' 58 | b = bytes([version]) + b # prepend version 59 | b += hash256(b)[:4] # append checksum 60 | value = int.from_bytes(b, 'big') 61 | while value > 0: 62 | result = chars[value % 58] + result 63 | value //= 58 64 | while b[0] == 0: 65 | result = chars[0] + result 66 | b = b[1:] 67 | return result 68 | 69 | 70 | def base58_to_byte(s): 71 | """Converts a base58-encoded string to its data and version. 72 | 73 | Throws if the base58 checksum is invalid.""" 74 | if not s: 75 | return b'' 76 | n = 0 77 | for c in s: 78 | n *= 58 79 | assert c in chars 80 | digit = chars.index(c) 81 | n += digit 82 | h = '%x' % n 83 | if len(h) % 2: 84 | h = '0' + h 85 | res = n.to_bytes((n.bit_length() + 7) // 8, 'big') 86 | pad = 0 87 | for c in s: 88 | if c == chars[0]: 89 | pad += 1 90 | else: 91 | break 92 | res = b'\x00' * pad + res 93 | 94 | # Assert if the checksum is invalid 95 | assert_equal(hash256(res[:-4])[:4], res[-4:]) 96 | 97 | return res[1:-4], int(res[0]) 98 | 99 | 100 | def keyhash_to_p2pkh(hash, main=False): 101 | assert len(hash) == 20 102 | version = 0 if main else 111 103 | return byte_to_base58(hash, version) 104 | 105 | def scripthash_to_p2sh(hash, main=False): 106 | assert len(hash) == 20 107 | version = 5 if main else 196 108 | return byte_to_base58(hash, version) 109 | 110 | def key_to_p2pkh(key, main=False): 111 | key = check_key(key) 112 | return keyhash_to_p2pkh(hash160(key), main) 113 | 114 | def script_to_p2sh(script, main=False): 115 | script = check_script(script) 116 | return scripthash_to_p2sh(hash160(script), main) 117 | 118 | def key_to_p2sh_p2wpkh(key, main=False): 119 | key = check_key(key) 120 | p2shscript = CScript([OP_0, hash160(key)]) 121 | return script_to_p2sh(p2shscript, main) 122 | 123 | def program_to_witness(version, program, main=False): 124 | if (type(program) is str): 125 | program = bytes.fromhex(program) 126 | assert 0 <= version <= 16 127 | assert 2 <= len(program) <= 40 128 | assert version > 0 or len(program) in [20, 32] 129 | return encode_segwit_address("bc" if main else "bcrt", version, program) 130 | 131 | def script_to_p2wsh(script, main=False): 132 | script = check_script(script) 133 | return program_to_witness(0, sha256(script), main) 134 | 135 | def key_to_p2wpkh(key, main=False): 136 | key = check_key(key) 137 | return program_to_witness(0, hash160(key), main) 138 | 139 | def script_to_p2sh_p2wsh(script, main=False): 140 | script = check_script(script) 141 | p2shscript = CScript([OP_0, sha256(script)]) 142 | return script_to_p2sh(p2shscript, main) 143 | 144 | def check_key(key): 145 | if (type(key) is str): 146 | key = bytes.fromhex(key) # Assuming this is hex string 147 | if (type(key) is bytes and (len(key) == 33 or len(key) == 65)): 148 | return key 149 | assert False 150 | 151 | def check_script(script): 152 | if (type(script) is str): 153 | script = bytes.fromhex(script) # Assuming this is hex string 154 | if (type(script) is bytes or type(script) is CScript): 155 | return script 156 | assert False 157 | 158 | 159 | class TestFrameworkScript(unittest.TestCase): 160 | def test_base58encodedecode(self): 161 | def check_base58(data, version): 162 | self.assertEqual(base58_to_byte(byte_to_base58(data, version)), (data, version)) 163 | 164 | check_base58(bytes.fromhex('1f8ea1702a7bd4941bca0941b852c4bbfedb2e05'), 111) 165 | check_base58(bytes.fromhex('3a0b05f4d7f66c3ba7009f453530296c845cc9cf'), 111) 166 | check_base58(bytes.fromhex('41c1eaf111802559bad61b60d62b1f897c63928a'), 111) 167 | check_base58(bytes.fromhex('0041c1eaf111802559bad61b60d62b1f897c63928a'), 111) 168 | check_base58(bytes.fromhex('000041c1eaf111802559bad61b60d62b1f897c63928a'), 111) 169 | check_base58(bytes.fromhex('00000041c1eaf111802559bad61b60d62b1f897c63928a'), 111) 170 | check_base58(bytes.fromhex('1f8ea1702a7bd4941bca0941b852c4bbfedb2e05'), 0) 171 | check_base58(bytes.fromhex('3a0b05f4d7f66c3ba7009f453530296c845cc9cf'), 0) 172 | check_base58(bytes.fromhex('41c1eaf111802559bad61b60d62b1f897c63928a'), 0) 173 | check_base58(bytes.fromhex('0041c1eaf111802559bad61b60d62b1f897c63928a'), 0) 174 | check_base58(bytes.fromhex('000041c1eaf111802559bad61b60d62b1f897c63928a'), 0) 175 | check_base58(bytes.fromhex('00000041c1eaf111802559bad61b60d62b1f897c63928a'), 0) 176 | -------------------------------------------------------------------------------- /miner_imports/test_framework/authproxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Jeff Garzik 2 | # 3 | # Previous copyright, from python-jsonrpc/jsonrpc/proxy.py: 4 | # 5 | # Copyright (c) 2007 Jan-Klaas Kollhof 6 | # 7 | # This file is part of jsonrpc. 8 | # 9 | # jsonrpc is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU Lesser General Public License as published by 11 | # the Free Software Foundation; either version 2.1 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This software is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU Lesser General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Lesser General Public License 20 | # along with this software; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 22 | """HTTP proxy for opening RPC connection to bitcoind. 23 | 24 | AuthServiceProxy has the following improvements over python-jsonrpc's 25 | ServiceProxy class: 26 | 27 | - HTTP connections persist for the life of the AuthServiceProxy object 28 | (if server supports HTTP/1.1) 29 | - sends protocol 'version', per JSON-RPC 1.1 30 | - sends proper, incrementing 'id' 31 | - sends Basic HTTP authentication headers 32 | - parses all JSON numbers that look like floats as Decimal 33 | - uses standard Python json lib 34 | """ 35 | 36 | import base64 37 | import decimal 38 | from http import HTTPStatus 39 | import http.client 40 | import json 41 | import logging 42 | import os 43 | import socket 44 | import time 45 | import urllib.parse 46 | 47 | HTTP_TIMEOUT = 30 48 | USER_AGENT = "AuthServiceProxy/0.1" 49 | 50 | log = logging.getLogger("BitcoinRPC") 51 | 52 | class JSONRPCException(Exception): 53 | def __init__(self, rpc_error, http_status=None): 54 | try: 55 | errmsg = '%(message)s (%(code)i)' % rpc_error 56 | except (KeyError, TypeError): 57 | errmsg = '' 58 | super().__init__(errmsg) 59 | self.error = rpc_error 60 | self.http_status = http_status 61 | 62 | 63 | def EncodeDecimal(o): 64 | if isinstance(o, decimal.Decimal): 65 | return str(o) 66 | raise TypeError(repr(o) + " is not JSON serializable") 67 | 68 | class AuthServiceProxy(): 69 | __id_count = 0 70 | 71 | # ensure_ascii: escape unicode as \uXXXX, passed to json.dumps 72 | def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True): 73 | self.__service_url = service_url 74 | self._service_name = service_name 75 | self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests 76 | self.__url = urllib.parse.urlparse(service_url) 77 | user = None if self.__url.username is None else self.__url.username.encode('utf8') 78 | passwd = None if self.__url.password is None else self.__url.password.encode('utf8') 79 | authpair = user + b':' + passwd 80 | self.__auth_header = b'Basic ' + base64.b64encode(authpair) 81 | self.timeout = timeout 82 | self._set_conn(connection) 83 | 84 | def __getattr__(self, name): 85 | if name.startswith('__') and name.endswith('__'): 86 | # Python internal stuff 87 | raise AttributeError 88 | if self._service_name is not None: 89 | name = "%s.%s" % (self._service_name, name) 90 | return AuthServiceProxy(self.__service_url, name, connection=self.__conn) 91 | 92 | def _request(self, method, path, postdata): 93 | ''' 94 | Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout). 95 | This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5. 96 | ''' 97 | headers = {'Host': self.__url.hostname, 98 | 'User-Agent': USER_AGENT, 99 | 'Authorization': self.__auth_header, 100 | 'Content-type': 'application/json'} 101 | if os.name == 'nt': 102 | # Windows somehow does not like to re-use connections 103 | # TODO: Find out why the connection would disconnect occasionally and make it reusable on Windows 104 | # Avoid "ConnectionAbortedError: [WinError 10053] An established connection was aborted by the software in your host machine" 105 | self._set_conn() 106 | try: 107 | self.__conn.request(method, path, postdata, headers) 108 | return self._get_response() 109 | except (BrokenPipeError, ConnectionResetError): 110 | # Python 3.5+ raises BrokenPipeError when the connection was reset 111 | # ConnectionResetError happens on FreeBSD 112 | self.__conn.close() 113 | self.__conn.request(method, path, postdata, headers) 114 | return self._get_response() 115 | except OSError as e: 116 | # Workaround for a bug on macOS. See https://bugs.python.org/issue33450 117 | retry = '[Errno 41] Protocol wrong type for socket' in str(e) 118 | if retry: 119 | self.__conn.close() 120 | self.__conn.request(method, path, postdata, headers) 121 | return self._get_response() 122 | else: 123 | raise 124 | 125 | def get_request(self, *args, **argsn): 126 | AuthServiceProxy.__id_count += 1 127 | 128 | log.debug("-{}-> {} {}".format( 129 | AuthServiceProxy.__id_count, 130 | self._service_name, 131 | json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii), 132 | )) 133 | if args and argsn: 134 | raise ValueError('Cannot handle both named and positional arguments') 135 | return {'version': '1.1', 136 | 'method': self._service_name, 137 | 'params': args or argsn, 138 | 'id': AuthServiceProxy.__id_count} 139 | 140 | def __call__(self, *args, **argsn): 141 | postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) 142 | response, status = self._request('POST', self.__url.path, postdata.encode('utf-8')) 143 | if response['error'] is not None: 144 | raise JSONRPCException(response['error'], status) 145 | elif 'result' not in response: 146 | raise JSONRPCException({ 147 | 'code': -343, 'message': 'missing JSON-RPC result'}, status) 148 | elif status != HTTPStatus.OK: 149 | raise JSONRPCException({ 150 | 'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status) 151 | else: 152 | return response['result'] 153 | 154 | def batch(self, rpc_call_list): 155 | postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) 156 | log.debug("--> " + postdata) 157 | response, status = self._request('POST', self.__url.path, postdata.encode('utf-8')) 158 | if status != HTTPStatus.OK: 159 | raise JSONRPCException({ 160 | 'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status) 161 | return response 162 | 163 | def _get_response(self): 164 | req_start_time = time.time() 165 | try: 166 | http_response = self.__conn.getresponse() 167 | except socket.timeout: 168 | raise JSONRPCException({ 169 | 'code': -344, 170 | 'message': '%r RPC took longer than %f seconds. Consider ' 171 | 'using larger timeout for calls that take ' 172 | 'longer to return.' % (self._service_name, 173 | self.__conn.timeout)}) 174 | if http_response is None: 175 | raise JSONRPCException({ 176 | 'code': -342, 'message': 'missing HTTP response from server'}) 177 | 178 | content_type = http_response.getheader('Content-Type') 179 | if content_type != 'application/json': 180 | raise JSONRPCException( 181 | {'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)}, 182 | http_response.status) 183 | 184 | responsedata = http_response.read().decode('utf8') 185 | response = json.loads(responsedata, parse_float=decimal.Decimal) 186 | elapsed = time.time() - req_start_time 187 | if "error" in response and response["error"] is None: 188 | log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) 189 | else: 190 | log.debug("<-- [%.6f] %s" % (elapsed, responsedata)) 191 | return response, http_response.status 192 | 193 | def __truediv__(self, relative_uri): 194 | return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn) 195 | 196 | def _set_conn(self, connection=None): 197 | port = 80 if self.__url.port is None else self.__url.port 198 | if connection: 199 | self.__conn = connection 200 | self.timeout = connection.timeout 201 | elif self.__url.scheme == 'https': 202 | self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=self.timeout) 203 | else: 204 | self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=self.timeout) 205 | -------------------------------------------------------------------------------- /miner_imports/test_framework/bdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2020-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """ 6 | Utilities for working directly with the wallet's BDB database file 7 | 8 | This is specific to the configuration of BDB used in this project: 9 | - pagesize: 4096 bytes 10 | - Outer database contains single subdatabase named 'main' 11 | - btree 12 | - btree leaf pages 13 | 14 | Each key-value pair is two entries in a btree leaf. The first is the key, the one that follows 15 | is the value. And so on. Note that the entry data is itself not in the correct order. Instead 16 | entry offsets are stored in the correct order and those offsets are needed to then retrieve 17 | the data itself. 18 | 19 | Page format can be found in BDB source code dbinc/db_page.h 20 | This only implements the deserialization of btree metadata pages and normal btree pages. Overflow 21 | pages are not implemented but may be needed in the future if dealing with wallets with large 22 | transactions. 23 | 24 | `db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file 25 | """ 26 | 27 | import struct 28 | 29 | # Important constants 30 | PAGESIZE = 4096 31 | OUTER_META_PAGE = 0 32 | INNER_META_PAGE = 2 33 | 34 | # Page type values 35 | BTREE_INTERNAL = 3 36 | BTREE_LEAF = 5 37 | BTREE_META = 9 38 | 39 | # Some magic numbers for sanity checking 40 | BTREE_MAGIC = 0x053162 41 | DB_VERSION = 9 42 | 43 | # Deserializes a leaf page into a dict. 44 | # Btree internal pages have the same header, for those, return None. 45 | # For the btree leaf pages, deserialize them and put all the data into a dict 46 | def dump_leaf_page(data): 47 | page_info = {} 48 | page_header = data[0:26] 49 | _, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header) 50 | page_info['pgno'] = pgno 51 | page_info['prev_pgno'] = prev_pgno 52 | page_info['next_pgno'] = next_pgno 53 | page_info['hf_offset'] = hf_offset 54 | page_info['level'] = level 55 | page_info['pg_type'] = pg_type 56 | page_info['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2]) 57 | page_info['entries'] = [] 58 | 59 | if pg_type == BTREE_INTERNAL: 60 | # Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us 61 | return None 62 | 63 | assert pg_type == BTREE_LEAF, 'A non-btree leaf page has been encountered while dumping leaves' 64 | 65 | for i in range(0, entries): 66 | offset = page_info['entry_offsets'][i] 67 | entry = {'offset': offset} 68 | page_data_header = data[offset:offset + 3] 69 | e_len, pg_type = struct.unpack('HB', page_data_header) 70 | entry['len'] = e_len 71 | entry['pg_type'] = pg_type 72 | entry['data'] = data[offset + 3:offset + 3 + e_len] 73 | page_info['entries'].append(entry) 74 | 75 | return page_info 76 | 77 | # Deserializes a btree metadata page into a dict. 78 | # Does a simple sanity check on the magic value, type, and version 79 | def dump_meta_page(page): 80 | # metadata page 81 | # general metadata 82 | metadata = {} 83 | meta_page = page[0:72] 84 | _, pgno, magic, version, pagesize, encrypt_alg, pg_type, metaflags, _, free, last_pgno, nparts, key_count, record_count, flags, uid = struct.unpack('QIIIIBBBBIIIIII20s', meta_page) 85 | metadata['pgno'] = pgno 86 | metadata['magic'] = magic 87 | metadata['version'] = version 88 | metadata['pagesize'] = pagesize 89 | metadata['encrypt_alg'] = encrypt_alg 90 | metadata['pg_type'] = pg_type 91 | metadata['metaflags'] = metaflags 92 | metadata['free'] = free 93 | metadata['last_pgno'] = last_pgno 94 | metadata['nparts'] = nparts 95 | metadata['key_count'] = key_count 96 | metadata['record_count'] = record_count 97 | metadata['flags'] = flags 98 | metadata['uid'] = uid.hex().encode() 99 | 100 | assert magic == BTREE_MAGIC, 'bdb magic does not match bdb btree magic' 101 | assert pg_type == BTREE_META, 'Metadata page is not a btree metadata page' 102 | assert version == DB_VERSION, 'Database too new' 103 | 104 | # btree metadata 105 | btree_meta_page = page[72:512] 106 | _, minkey, re_len, re_pad, root, _, crypto_magic, _, iv, chksum = struct.unpack('IIIII368sI12s16s20s', btree_meta_page) 107 | metadata['minkey'] = minkey 108 | metadata['re_len'] = re_len 109 | metadata['re_pad'] = re_pad 110 | metadata['root'] = root 111 | metadata['crypto_magic'] = crypto_magic 112 | metadata['iv'] = iv.hex().encode() 113 | metadata['chksum'] = chksum.hex().encode() 114 | 115 | return metadata 116 | 117 | # Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict 118 | def extract_kv_pairs(page_data): 119 | out = {} 120 | last_key = None 121 | for i, entry in enumerate(page_data['entries']): 122 | # By virtue of these all being pairs, even number entries are keys, and odd are values 123 | if i % 2 == 0: 124 | out[entry['data']] = b'' 125 | last_key = entry['data'] 126 | else: 127 | out[last_key] = entry['data'] 128 | return out 129 | 130 | # Extract the key-value pairs of the BDB file given in filename 131 | def dump_bdb_kv(filename): 132 | # Read in the BDB file and start deserializing it 133 | pages = [] 134 | with open(filename, 'rb') as f: 135 | data = f.read(PAGESIZE) 136 | while len(data) > 0: 137 | pages.append(data) 138 | data = f.read(PAGESIZE) 139 | 140 | # Sanity check the meta pages 141 | dump_meta_page(pages[OUTER_META_PAGE]) 142 | dump_meta_page(pages[INNER_META_PAGE]) 143 | 144 | # Fetch the kv pairs from the leaf pages 145 | kv = {} 146 | for i in range(3, len(pages)): 147 | info = dump_leaf_page(pages[i]) 148 | if info is not None: 149 | info_kv = extract_kv_pairs(info) 150 | kv = {**kv, **info_kv} 151 | return kv 152 | -------------------------------------------------------------------------------- /miner_imports/test_framework/bip340_test_vectors.csv: -------------------------------------------------------------------------------- 1 | index,secret key,public key,aux_rand,message,signature,verification result,comment 2 | 0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE, 3 | 1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE, 4 | 2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE, 5 | 3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n 6 | 4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE, 7 | 5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve 8 | 6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false 9 | 7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message 10 | 8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value 11 | 9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0 12 | 10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1 13 | 11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve 14 | 12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size 15 | 13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order 16 | 14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size 17 | -------------------------------------------------------------------------------- /miner_imports/test_framework/blocktools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2015-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Utilities for manipulating blocks and transactions.""" 6 | 7 | import struct 8 | import time 9 | import unittest 10 | 11 | from .address import ( 12 | key_to_p2sh_p2wpkh, 13 | key_to_p2wpkh, 14 | script_to_p2sh_p2wsh, 15 | script_to_p2wsh, 16 | ) 17 | from .messages import ( 18 | CBlock, 19 | COIN, 20 | COutPoint, 21 | CTransaction, 22 | CTxIn, 23 | CTxInWitness, 24 | CTxOut, 25 | SEQUENCE_FINAL, 26 | hash256, 27 | ser_uint256, 28 | tx_from_hex, 29 | uint256_from_str, 30 | ) 31 | from .script import ( 32 | CScript, 33 | CScriptNum, 34 | CScriptOp, 35 | OP_1, 36 | OP_RETURN, 37 | OP_TRUE, 38 | ) 39 | from .script_util import ( 40 | key_to_p2pk_script, 41 | key_to_p2wpkh_script, 42 | keys_to_multisig_script, 43 | script_to_p2wsh_script, 44 | ) 45 | from .util import assert_equal 46 | 47 | WITNESS_SCALE_FACTOR = 4 48 | MAX_BLOCK_SIGOPS = 20000 49 | MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR 50 | 51 | # Genesis block time (regtest) 52 | TIME_GENESIS_BLOCK = 1296688602 53 | 54 | MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60 55 | 56 | # Coinbase transaction outputs can only be spent after this number of new blocks (network rule) 57 | COINBASE_MATURITY = 100 58 | 59 | # From BIP141 60 | WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed" 61 | 62 | NORMAL_GBT_REQUEST_PARAMS = {"rules": ["segwit"]} 63 | VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4 64 | 65 | 66 | def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl=None, txlist=None): 67 | """Create a block (with regtest difficulty).""" 68 | block = CBlock() 69 | if tmpl is None: 70 | tmpl = {} 71 | block.nVersion = version or tmpl.get('version') or VERSIONBITS_LAST_OLD_BLOCK_VERSION 72 | block.nTime = ntime or tmpl.get('curtime') or int(time.time() + 600) 73 | block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10) 74 | if tmpl and not tmpl.get('bits') is None: 75 | block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0] 76 | else: 77 | block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams 78 | if coinbase is None: 79 | coinbase = create_coinbase(height=tmpl['height']) 80 | block.vtx.append(coinbase) 81 | if txlist: 82 | for tx in txlist: 83 | if not hasattr(tx, 'calc_sha256'): 84 | tx = tx_from_hex(tx) 85 | block.vtx.append(tx) 86 | block.hashMerkleRoot = block.calc_merkle_root() 87 | block.calc_sha256() 88 | return block 89 | 90 | def get_witness_script(witness_root, witness_nonce): 91 | witness_commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))) 92 | output_data = WITNESS_COMMITMENT_HEADER + ser_uint256(witness_commitment) 93 | return CScript([OP_RETURN, output_data]) 94 | 95 | def add_witness_commitment(block, nonce=0): 96 | """Add a witness commitment to the block's coinbase transaction. 97 | 98 | According to BIP141, blocks with witness rules active must commit to the 99 | hash of all in-block transactions including witness.""" 100 | # First calculate the merkle root of the block's 101 | # transactions, with witnesses. 102 | witness_nonce = nonce 103 | witness_root = block.calc_witness_merkle_root() 104 | # witness_nonce should go to coinbase witness. 105 | block.vtx[0].wit.vtxinwit = [CTxInWitness()] 106 | block.vtx[0].wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(witness_nonce)] 107 | 108 | # witness commitment is the last OP_RETURN output in coinbase 109 | block.vtx[0].vout.append(CTxOut(0, get_witness_script(witness_root, witness_nonce))) 110 | block.vtx[0].rehash() 111 | block.hashMerkleRoot = block.calc_merkle_root() 112 | block.rehash() 113 | 114 | 115 | def script_BIP34_coinbase_height(height): 116 | if height <= 16: 117 | res = CScriptOp.encode_op_n(height) 118 | # Append dummy to increase scriptSig size above 2 (see bad-cb-length consensus rule) 119 | return CScript([res, OP_1]) 120 | return CScript([CScriptNum(height)]) 121 | 122 | 123 | def create_coinbase(height, pubkey=None, extra_output_script=None, fees=0, nValue=50): 124 | """Create a coinbase transaction. 125 | 126 | If pubkey is passed in, the coinbase output will be a P2PK output; 127 | otherwise an anyone-can-spend output. 128 | 129 | If extra_output_script is given, make a 0-value output to that 130 | script. This is useful to pad block weight/sigops as needed. """ 131 | coinbase = CTransaction() 132 | coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), SEQUENCE_FINAL)) 133 | coinbaseoutput = CTxOut() 134 | coinbaseoutput.nValue = nValue * COIN 135 | if nValue == 50: 136 | halvings = int(height / 150) # regtest 137 | coinbaseoutput.nValue >>= halvings 138 | coinbaseoutput.nValue += fees 139 | if pubkey is not None: 140 | coinbaseoutput.scriptPubKey = key_to_p2pk_script(pubkey) 141 | else: 142 | coinbaseoutput.scriptPubKey = CScript([OP_TRUE]) 143 | coinbase.vout = [coinbaseoutput] 144 | if extra_output_script is not None: 145 | coinbaseoutput2 = CTxOut() 146 | coinbaseoutput2.nValue = 0 147 | coinbaseoutput2.scriptPubKey = extra_output_script 148 | coinbase.vout.append(coinbaseoutput2) 149 | coinbase.calc_sha256() 150 | return coinbase 151 | 152 | def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=CScript()): 153 | """Return one-input, one-output transaction object 154 | spending the prevtx's n-th output with the given amount. 155 | 156 | Can optionally pass scriptPubKey and scriptSig, default is anyone-can-spend output. 157 | """ 158 | tx = CTransaction() 159 | assert n < len(prevtx.vout) 160 | tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, SEQUENCE_FINAL)) 161 | tx.vout.append(CTxOut(amount, script_pub_key)) 162 | tx.calc_sha256() 163 | return tx 164 | 165 | def create_transaction(node, txid, to_address, *, amount): 166 | """ Return signed transaction spending the first output of the 167 | input txid. Note that the node must have a wallet that can 168 | sign for the output that is being spent. 169 | """ 170 | raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) 171 | tx = tx_from_hex(raw_tx) 172 | return tx 173 | 174 | def create_raw_transaction(node, txid, to_address, *, amount): 175 | """ Return raw signed transaction spending the first output of the 176 | input txid. Note that the node must have a wallet that can sign 177 | for the output that is being spent. 178 | """ 179 | psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) 180 | for _ in range(2): 181 | for w in node.listwallets(): 182 | wrpc = node.get_wallet_rpc(w) 183 | signed_psbt = wrpc.walletprocesspsbt(psbt) 184 | psbt = signed_psbt['psbt'] 185 | final_psbt = node.finalizepsbt(psbt) 186 | assert_equal(final_psbt["complete"], True) 187 | return final_psbt['hex'] 188 | 189 | def get_legacy_sigopcount_block(block, accurate=True): 190 | count = 0 191 | for tx in block.vtx: 192 | count += get_legacy_sigopcount_tx(tx, accurate) 193 | return count 194 | 195 | def get_legacy_sigopcount_tx(tx, accurate=True): 196 | count = 0 197 | for i in tx.vout: 198 | count += i.scriptPubKey.GetSigOpCount(accurate) 199 | for j in tx.vin: 200 | # scriptSig might be of type bytes, so convert to CScript for the moment 201 | count += CScript(j.scriptSig).GetSigOpCount(accurate) 202 | return count 203 | 204 | def witness_script(use_p2wsh, pubkey): 205 | """Create a scriptPubKey for a pay-to-witness TxOut. 206 | 207 | This is either a P2WPKH output for the given pubkey, or a P2WSH output of a 208 | 1-of-1 multisig for the given pubkey. Returns the hex encoding of the 209 | scriptPubKey.""" 210 | if not use_p2wsh: 211 | # P2WPKH instead 212 | pkscript = key_to_p2wpkh_script(pubkey) 213 | else: 214 | # 1-of-1 multisig 215 | witness_script = keys_to_multisig_script([pubkey]) 216 | pkscript = script_to_p2wsh_script(witness_script) 217 | return pkscript.hex() 218 | 219 | def create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount): 220 | """Return a transaction (in hex) that spends the given utxo to a segwit output. 221 | 222 | Optionally wrap the segwit output using P2SH.""" 223 | if use_p2wsh: 224 | program = keys_to_multisig_script([pubkey]) 225 | addr = script_to_p2sh_p2wsh(program) if encode_p2sh else script_to_p2wsh(program) 226 | else: 227 | addr = key_to_p2sh_p2wpkh(pubkey) if encode_p2sh else key_to_p2wpkh(pubkey) 228 | if not encode_p2sh: 229 | assert_equal(node.getaddressinfo(addr)['scriptPubKey'], witness_script(use_p2wsh, pubkey)) 230 | return node.createrawtransaction([utxo], {addr: amount}) 231 | 232 | def send_to_witness(use_p2wsh, node, utxo, pubkey, encode_p2sh, amount, sign=True, insert_redeem_script=""): 233 | """Create a transaction spending a given utxo to a segwit output. 234 | 235 | The output corresponds to the given pubkey: use_p2wsh determines whether to 236 | use P2WPKH or P2WSH; encode_p2sh determines whether to wrap in P2SH. 237 | sign=True will have the given node sign the transaction. 238 | insert_redeem_script will be added to the scriptSig, if given.""" 239 | tx_to_witness = create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount) 240 | if (sign): 241 | signed = node.signrawtransactionwithwallet(tx_to_witness) 242 | assert "errors" not in signed or len(["errors"]) == 0 243 | return node.sendrawtransaction(signed["hex"]) 244 | else: 245 | if (insert_redeem_script): 246 | tx = tx_from_hex(tx_to_witness) 247 | tx.vin[0].scriptSig += CScript([bytes.fromhex(insert_redeem_script)]) 248 | tx_to_witness = tx.serialize().hex() 249 | 250 | return node.sendrawtransaction(tx_to_witness) 251 | 252 | class TestFrameworkBlockTools(unittest.TestCase): 253 | def test_create_coinbase(self): 254 | height = 20 255 | coinbase_tx = create_coinbase(height=height) 256 | assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), height) 257 | -------------------------------------------------------------------------------- /miner_imports/test_framework/coverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2015-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Utilities for doing coverage analysis on the RPC interface. 6 | 7 | Provides a way to track which RPC commands are exercised during 8 | testing. 9 | """ 10 | 11 | import os 12 | 13 | from .authproxy import AuthServiceProxy 14 | 15 | REFERENCE_FILENAME = 'rpc_interface.txt' 16 | 17 | 18 | class AuthServiceProxyWrapper(): 19 | """ 20 | An object that wraps AuthServiceProxy to record specific RPC calls. 21 | 22 | """ 23 | def __init__(self, auth_service_proxy_instance: AuthServiceProxy, rpc_url: str, coverage_logfile: str=None): 24 | """ 25 | Kwargs: 26 | auth_service_proxy_instance: the instance being wrapped. 27 | rpc_url: url of the RPC instance being wrapped 28 | coverage_logfile: if specified, write each service_name 29 | out to a file when called. 30 | 31 | """ 32 | self.auth_service_proxy_instance = auth_service_proxy_instance 33 | self.rpc_url = rpc_url 34 | self.coverage_logfile = coverage_logfile 35 | 36 | def __getattr__(self, name): 37 | return_val = getattr(self.auth_service_proxy_instance, name) 38 | if not isinstance(return_val, type(self.auth_service_proxy_instance)): 39 | # If proxy getattr returned an unwrapped value, do the same here. 40 | return return_val 41 | return AuthServiceProxyWrapper(return_val, self.rpc_url, self.coverage_logfile) 42 | 43 | def __call__(self, *args, **kwargs): 44 | """ 45 | Delegates to AuthServiceProxy, then writes the particular RPC method 46 | called to a file. 47 | 48 | """ 49 | return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs) 50 | self._log_call() 51 | return return_val 52 | 53 | def _log_call(self): 54 | rpc_method = self.auth_service_proxy_instance._service_name 55 | 56 | if self.coverage_logfile: 57 | with open(self.coverage_logfile, 'a+', encoding='utf8') as f: 58 | f.write("%s\n" % rpc_method) 59 | 60 | def __truediv__(self, relative_uri): 61 | return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri, 62 | self.rpc_url, 63 | self.coverage_logfile) 64 | 65 | def get_request(self, *args, **kwargs): 66 | self._log_call() 67 | return self.auth_service_proxy_instance.get_request(*args, **kwargs) 68 | 69 | def get_filename(dirname, n_node): 70 | """ 71 | Get a filename unique to the test process ID and node. 72 | 73 | This file will contain a list of RPC commands covered. 74 | """ 75 | pid = str(os.getpid()) 76 | return os.path.join( 77 | dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node))) 78 | 79 | 80 | def write_all_rpc_commands(dirname: str, node: AuthServiceProxy) -> bool: 81 | """ 82 | Write out a list of all RPC functions available in `bitcoin-cli` for 83 | coverage comparison. This will only happen once per coverage 84 | directory. 85 | 86 | Args: 87 | dirname: temporary test dir 88 | node: client 89 | 90 | Returns: 91 | if the RPC interface file was written. 92 | 93 | """ 94 | filename = os.path.join(dirname, REFERENCE_FILENAME) 95 | 96 | if os.path.isfile(filename): 97 | return False 98 | 99 | help_output = node.help().split('\n') 100 | commands = set() 101 | 102 | for line in help_output: 103 | line = line.strip() 104 | 105 | # Ignore blanks and headers 106 | if line and not line.startswith('='): 107 | commands.add("%s\n" % line.split()[0]) 108 | 109 | with open(filename, 'w', encoding='utf8') as f: 110 | f.writelines(list(commands)) 111 | 112 | return True 113 | -------------------------------------------------------------------------------- /miner_imports/test_framework/descriptors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2019 Pieter Wuille 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Utility functions related to output descriptors""" 6 | 7 | import re 8 | 9 | INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " 10 | CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 11 | GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd] 12 | 13 | def descsum_polymod(symbols): 14 | """Internal function that computes the descriptor checksum.""" 15 | chk = 1 16 | for value in symbols: 17 | top = chk >> 35 18 | chk = (chk & 0x7ffffffff) << 5 ^ value 19 | for i in range(5): 20 | chk ^= GENERATOR[i] if ((top >> i) & 1) else 0 21 | return chk 22 | 23 | def descsum_expand(s): 24 | """Internal function that does the character to symbol expansion""" 25 | groups = [] 26 | symbols = [] 27 | for c in s: 28 | if not c in INPUT_CHARSET: 29 | return None 30 | v = INPUT_CHARSET.find(c) 31 | symbols.append(v & 31) 32 | groups.append(v >> 5) 33 | if len(groups) == 3: 34 | symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2]) 35 | groups = [] 36 | if len(groups) == 1: 37 | symbols.append(groups[0]) 38 | elif len(groups) == 2: 39 | symbols.append(groups[0] * 3 + groups[1]) 40 | return symbols 41 | 42 | def descsum_create(s): 43 | """Add a checksum to a descriptor without""" 44 | symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0] 45 | checksum = descsum_polymod(symbols) ^ 1 46 | return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8)) 47 | 48 | def descsum_check(s, require=True): 49 | """Verify that the checksum is correct in a descriptor""" 50 | if not '#' in s: 51 | return not require 52 | if s[-9] != '#': 53 | return False 54 | if not all(x in CHECKSUM_CHARSET for x in s[-8:]): 55 | return False 56 | symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]] 57 | return descsum_polymod(symbols) == 1 58 | 59 | def drop_origins(s): 60 | '''Drop the key origins from a descriptor''' 61 | desc = re.sub(r'\[.+?\]', '', s) 62 | if '#' in s: 63 | desc = desc[:desc.index('#')] 64 | return descsum_create(desc) 65 | -------------------------------------------------------------------------------- /miner_imports/test_framework/key.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Pieter Wuille 2 | # Distributed under the MIT software license, see the accompanying 3 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | """Test-only secp256k1 elliptic curve implementation 5 | 6 | WARNING: This code is slow, uses bad randomness, does not properly protect 7 | keys, and is trivially vulnerable to side channel attacks. Do not use for 8 | anything but tests.""" 9 | import csv 10 | import hashlib 11 | import hmac 12 | import os 13 | import random 14 | import unittest 15 | 16 | from .util import modinv 17 | 18 | def TaggedHash(tag, data): 19 | ss = hashlib.sha256(tag.encode('utf-8')).digest() 20 | ss += ss 21 | ss += data 22 | return hashlib.sha256(ss).digest() 23 | 24 | def jacobi_symbol(n, k): 25 | """Compute the Jacobi symbol of n modulo k 26 | 27 | See https://en.wikipedia.org/wiki/Jacobi_symbol 28 | 29 | For our application k is always prime, so this is the same as the Legendre symbol.""" 30 | assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k" 31 | n %= k 32 | t = 0 33 | while n != 0: 34 | while n & 1 == 0: 35 | n >>= 1 36 | r = k & 7 37 | t ^= (r == 3 or r == 5) 38 | n, k = k, n 39 | t ^= (n & k & 3 == 3) 40 | n = n % k 41 | if k == 1: 42 | return -1 if t else 1 43 | return 0 44 | 45 | def modsqrt(a, p): 46 | """Compute the square root of a modulo p when p % 4 = 3. 47 | 48 | The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm 49 | 50 | Limiting this function to only work for p % 4 = 3 means we don't need to 51 | iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd 52 | is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4) 53 | 54 | secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4. 55 | """ 56 | if p % 4 != 3: 57 | raise NotImplementedError("modsqrt only implemented for p % 4 = 3") 58 | sqrt = pow(a, (p + 1)//4, p) 59 | if pow(sqrt, 2, p) == a % p: 60 | return sqrt 61 | return None 62 | 63 | class EllipticCurve: 64 | def __init__(self, p, a, b): 65 | """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" 66 | self.p = p 67 | self.a = a % p 68 | self.b = b % p 69 | 70 | def affine(self, p1): 71 | """Convert a Jacobian point tuple p1 to affine form, or None if at infinity. 72 | 73 | An affine point is represented as the Jacobian (x, y, 1)""" 74 | x1, y1, z1 = p1 75 | if z1 == 0: 76 | return None 77 | inv = modinv(z1, self.p) 78 | inv_2 = (inv**2) % self.p 79 | inv_3 = (inv_2 * inv) % self.p 80 | return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) 81 | 82 | def has_even_y(self, p1): 83 | """Whether the point p1 has an even Y coordinate when expressed in affine coordinates.""" 84 | return not (p1[2] == 0 or self.affine(p1)[1] & 1) 85 | 86 | def negate(self, p1): 87 | """Negate a Jacobian point tuple p1.""" 88 | x1, y1, z1 = p1 89 | return (x1, (self.p - y1) % self.p, z1) 90 | 91 | def on_curve(self, p1): 92 | """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" 93 | x1, y1, z1 = p1 94 | z2 = pow(z1, 2, self.p) 95 | z4 = pow(z2, 2, self.p) 96 | return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0 97 | 98 | def is_x_coord(self, x): 99 | """Test whether x is a valid X coordinate on the curve.""" 100 | x_3 = pow(x, 3, self.p) 101 | return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 102 | 103 | def lift_x(self, x): 104 | """Given an X coordinate on the curve, return a corresponding affine point for which the Y coordinate is even.""" 105 | x_3 = pow(x, 3, self.p) 106 | v = x_3 + self.a * x + self.b 107 | y = modsqrt(v, self.p) 108 | if y is None: 109 | return None 110 | return (x, self.p - y if y & 1 else y, 1) 111 | 112 | def double(self, p1): 113 | """Double a Jacobian tuple p1 114 | 115 | See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling""" 116 | x1, y1, z1 = p1 117 | if z1 == 0: 118 | return (0, 1, 0) 119 | y1_2 = (y1**2) % self.p 120 | y1_4 = (y1_2**2) % self.p 121 | x1_2 = (x1**2) % self.p 122 | s = (4*x1*y1_2) % self.p 123 | m = 3*x1_2 124 | if self.a: 125 | m += self.a * pow(z1, 4, self.p) 126 | m = m % self.p 127 | x2 = (m**2 - 2*s) % self.p 128 | y2 = (m*(s - x2) - 8*y1_4) % self.p 129 | z2 = (2*y1*z1) % self.p 130 | return (x2, y2, z2) 131 | 132 | def add_mixed(self, p1, p2): 133 | """Add a Jacobian tuple p1 and an affine tuple p2 134 | 135 | See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)""" 136 | x1, y1, z1 = p1 137 | x2, y2, z2 = p2 138 | assert(z2 == 1) 139 | # Adding to the point at infinity is a no-op 140 | if z1 == 0: 141 | return p2 142 | z1_2 = (z1**2) % self.p 143 | z1_3 = (z1_2 * z1) % self.p 144 | u2 = (x2 * z1_2) % self.p 145 | s2 = (y2 * z1_3) % self.p 146 | if x1 == u2: 147 | if (y1 != s2): 148 | # p1 and p2 are inverses. Return the point at infinity. 149 | return (0, 1, 0) 150 | # p1 == p2. The formulas below fail when the two points are equal. 151 | return self.double(p1) 152 | h = u2 - x1 153 | r = s2 - y1 154 | h_2 = (h**2) % self.p 155 | h_3 = (h_2 * h) % self.p 156 | u1_h_2 = (x1 * h_2) % self.p 157 | x3 = (r**2 - h_3 - 2*u1_h_2) % self.p 158 | y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p 159 | z3 = (h*z1) % self.p 160 | return (x3, y3, z3) 161 | 162 | def add(self, p1, p2): 163 | """Add two Jacobian tuples p1 and p2 164 | 165 | See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition""" 166 | x1, y1, z1 = p1 167 | x2, y2, z2 = p2 168 | # Adding the point at infinity is a no-op 169 | if z1 == 0: 170 | return p2 171 | if z2 == 0: 172 | return p1 173 | # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1 174 | if z1 == 1: 175 | return self.add_mixed(p2, p1) 176 | if z2 == 1: 177 | return self.add_mixed(p1, p2) 178 | z1_2 = (z1**2) % self.p 179 | z1_3 = (z1_2 * z1) % self.p 180 | z2_2 = (z2**2) % self.p 181 | z2_3 = (z2_2 * z2) % self.p 182 | u1 = (x1 * z2_2) % self.p 183 | u2 = (x2 * z1_2) % self.p 184 | s1 = (y1 * z2_3) % self.p 185 | s2 = (y2 * z1_3) % self.p 186 | if u1 == u2: 187 | if (s1 != s2): 188 | # p1 and p2 are inverses. Return the point at infinity. 189 | return (0, 1, 0) 190 | # p1 == p2. The formulas below fail when the two points are equal. 191 | return self.double(p1) 192 | h = u2 - u1 193 | r = s2 - s1 194 | h_2 = (h**2) % self.p 195 | h_3 = (h_2 * h) % self.p 196 | u1_h_2 = (u1 * h_2) % self.p 197 | x3 = (r**2 - h_3 - 2*u1_h_2) % self.p 198 | y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p 199 | z3 = (h*z1*z2) % self.p 200 | return (x3, y3, z3) 201 | 202 | def mul(self, ps): 203 | """Compute a (multi) point multiplication 204 | 205 | ps is a list of (Jacobian tuple, scalar) pairs. 206 | """ 207 | r = (0, 1, 0) 208 | for i in range(255, -1, -1): 209 | r = self.double(r) 210 | for (p, n) in ps: 211 | if ((n >> i) & 1): 212 | r = self.add(r, p) 213 | return r 214 | 215 | SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977 216 | SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7) 217 | SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) 218 | SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 219 | SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 220 | 221 | class ECPubKey(): 222 | """A secp256k1 public key""" 223 | 224 | def __init__(self): 225 | """Construct an uninitialized public key""" 226 | self.valid = False 227 | 228 | def set(self, data): 229 | """Construct a public key from a serialization in compressed or uncompressed format""" 230 | if (len(data) == 65 and data[0] == 0x04): 231 | p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1) 232 | self.valid = SECP256K1.on_curve(p) 233 | if self.valid: 234 | self.p = p 235 | self.compressed = False 236 | elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)): 237 | x = int.from_bytes(data[1:33], 'big') 238 | if SECP256K1.is_x_coord(x): 239 | p = SECP256K1.lift_x(x) 240 | # Make the Y coordinate odd if required (lift_x always produces 241 | # a point with an even Y coordinate). 242 | if data[0] & 1: 243 | p = SECP256K1.negate(p) 244 | self.p = p 245 | self.valid = True 246 | self.compressed = True 247 | else: 248 | self.valid = False 249 | else: 250 | self.valid = False 251 | 252 | @property 253 | def is_compressed(self): 254 | return self.compressed 255 | 256 | @property 257 | def is_valid(self): 258 | return self.valid 259 | 260 | def get_bytes(self): 261 | assert(self.valid) 262 | p = SECP256K1.affine(self.p) 263 | if p is None: 264 | return None 265 | if self.compressed: 266 | return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') 267 | else: 268 | return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') 269 | 270 | def verify_ecdsa(self, sig, msg, low_s=True): 271 | """Verify a strictly DER-encoded ECDSA signature against this pubkey. 272 | 273 | See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the 274 | ECDSA verifier algorithm""" 275 | assert(self.valid) 276 | 277 | # Extract r and s from the DER formatted signature. Return false for 278 | # any DER encoding errors. 279 | if (sig[1] + 2 != len(sig)): 280 | return False 281 | if (len(sig) < 4): 282 | return False 283 | if (sig[0] != 0x30): 284 | return False 285 | if (sig[2] != 0x02): 286 | return False 287 | rlen = sig[3] 288 | if (len(sig) < 6 + rlen): 289 | return False 290 | if rlen < 1 or rlen > 33: 291 | return False 292 | if sig[4] >= 0x80: 293 | return False 294 | if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)): 295 | return False 296 | r = int.from_bytes(sig[4:4+rlen], 'big') 297 | if (sig[4+rlen] != 0x02): 298 | return False 299 | slen = sig[5+rlen] 300 | if slen < 1 or slen > 33: 301 | return False 302 | if (len(sig) != 6 + rlen + slen): 303 | return False 304 | if sig[6+rlen] >= 0x80: 305 | return False 306 | if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)): 307 | return False 308 | s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big') 309 | 310 | # Verify that r and s are within the group order 311 | if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: 312 | return False 313 | if low_s and s >= SECP256K1_ORDER_HALF: 314 | return False 315 | z = int.from_bytes(msg, 'big') 316 | 317 | # Run verifier algorithm on r, s 318 | w = modinv(s, SECP256K1_ORDER) 319 | u1 = z*w % SECP256K1_ORDER 320 | u2 = r*w % SECP256K1_ORDER 321 | R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) 322 | if R is None or (R[0] % SECP256K1_ORDER) != r: 323 | return False 324 | return True 325 | 326 | def generate_privkey(): 327 | """Generate a valid random 32-byte private key.""" 328 | return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big') 329 | 330 | def rfc6979_nonce(key): 331 | """Compute signing nonce using RFC6979.""" 332 | v = bytes([1] * 32) 333 | k = bytes([0] * 32) 334 | k = hmac.new(k, v + b"\x00" + key, 'sha256').digest() 335 | v = hmac.new(k, v, 'sha256').digest() 336 | k = hmac.new(k, v + b"\x01" + key, 'sha256').digest() 337 | v = hmac.new(k, v, 'sha256').digest() 338 | return hmac.new(k, v, 'sha256').digest() 339 | 340 | class ECKey(): 341 | """A secp256k1 private key""" 342 | 343 | def __init__(self): 344 | self.valid = False 345 | 346 | def set(self, secret, compressed): 347 | """Construct a private key object with given 32-byte secret and compressed flag.""" 348 | assert(len(secret) == 32) 349 | secret = int.from_bytes(secret, 'big') 350 | self.valid = (secret > 0 and secret < SECP256K1_ORDER) 351 | if self.valid: 352 | self.secret = secret 353 | self.compressed = compressed 354 | 355 | def generate(self, compressed=True): 356 | """Generate a random private key (compressed or uncompressed).""" 357 | self.set(generate_privkey(), compressed) 358 | 359 | def get_bytes(self): 360 | """Retrieve the 32-byte representation of this key.""" 361 | assert(self.valid) 362 | return self.secret.to_bytes(32, 'big') 363 | 364 | @property 365 | def is_valid(self): 366 | return self.valid 367 | 368 | @property 369 | def is_compressed(self): 370 | return self.compressed 371 | 372 | def get_pubkey(self): 373 | """Compute an ECPubKey object for this secret key.""" 374 | assert(self.valid) 375 | ret = ECPubKey() 376 | p = SECP256K1.mul([(SECP256K1_G, self.secret)]) 377 | ret.p = p 378 | ret.valid = True 379 | ret.compressed = self.compressed 380 | return ret 381 | 382 | def sign_ecdsa(self, msg, low_s=True, rfc6979=False): 383 | """Construct a DER-encoded ECDSA signature with this key. 384 | 385 | See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the 386 | ECDSA signer algorithm.""" 387 | assert(self.valid) 388 | z = int.from_bytes(msg, 'big') 389 | # Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation) 390 | if rfc6979: 391 | k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big') 392 | else: 393 | k = random.randrange(1, SECP256K1_ORDER) 394 | R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) 395 | r = R[0] % SECP256K1_ORDER 396 | s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER 397 | if low_s and s > SECP256K1_ORDER_HALF: 398 | s = SECP256K1_ORDER - s 399 | # Represent in DER format. The byte representations of r and s have 400 | # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33 401 | # bytes). 402 | rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') 403 | sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') 404 | return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb 405 | 406 | def compute_xonly_pubkey(key): 407 | """Compute an x-only (32 byte) public key from a (32 byte) private key. 408 | 409 | This also returns whether the resulting public key was negated. 410 | """ 411 | 412 | assert len(key) == 32 413 | x = int.from_bytes(key, 'big') 414 | if x == 0 or x >= SECP256K1_ORDER: 415 | return (None, None) 416 | P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)])) 417 | return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P)) 418 | 419 | def tweak_add_privkey(key, tweak): 420 | """Tweak a private key (after negating it if needed).""" 421 | 422 | assert len(key) == 32 423 | assert len(tweak) == 32 424 | 425 | x = int.from_bytes(key, 'big') 426 | if x == 0 or x >= SECP256K1_ORDER: 427 | return None 428 | if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])): 429 | x = SECP256K1_ORDER - x 430 | t = int.from_bytes(tweak, 'big') 431 | if t >= SECP256K1_ORDER: 432 | return None 433 | x = (x + t) % SECP256K1_ORDER 434 | if x == 0: 435 | return None 436 | return x.to_bytes(32, 'big') 437 | 438 | def tweak_add_pubkey(key, tweak): 439 | """Tweak a public key and return whether the result had to be negated.""" 440 | 441 | assert len(key) == 32 442 | assert len(tweak) == 32 443 | 444 | x_coord = int.from_bytes(key, 'big') 445 | if x_coord >= SECP256K1_FIELD_SIZE: 446 | return None 447 | P = SECP256K1.lift_x(x_coord) 448 | if P is None: 449 | return None 450 | t = int.from_bytes(tweak, 'big') 451 | if t >= SECP256K1_ORDER: 452 | return None 453 | Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)])) 454 | if Q is None: 455 | return None 456 | return (Q[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(Q)) 457 | 458 | def verify_schnorr(key, sig, msg): 459 | """Verify a Schnorr signature (see BIP 340). 460 | 461 | - key is a 32-byte xonly pubkey (computed using compute_xonly_pubkey). 462 | - sig is a 64-byte Schnorr signature 463 | - msg is a 32-byte message 464 | """ 465 | assert len(key) == 32 466 | assert len(msg) == 32 467 | assert len(sig) == 64 468 | 469 | x_coord = int.from_bytes(key, 'big') 470 | if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE: 471 | return False 472 | P = SECP256K1.lift_x(x_coord) 473 | if P is None: 474 | return False 475 | r = int.from_bytes(sig[0:32], 'big') 476 | if r >= SECP256K1_FIELD_SIZE: 477 | return False 478 | s = int.from_bytes(sig[32:64], 'big') 479 | if s >= SECP256K1_ORDER: 480 | return False 481 | e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % SECP256K1_ORDER 482 | R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)]) 483 | if not SECP256K1.has_even_y(R): 484 | return False 485 | if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]: 486 | return False 487 | return True 488 | 489 | def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): 490 | """Create a Schnorr signature (see BIP 340).""" 491 | 492 | if aux is None: 493 | aux = bytes(32) 494 | 495 | assert len(key) == 32 496 | assert len(msg) == 32 497 | assert len(aux) == 32 498 | 499 | sec = int.from_bytes(key, 'big') 500 | if sec == 0 or sec >= SECP256K1_ORDER: 501 | return None 502 | P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)])) 503 | if SECP256K1.has_even_y(P) == flip_p: 504 | sec = SECP256K1_ORDER - sec 505 | t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big') 506 | kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER 507 | assert kp != 0 508 | R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) 509 | k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp 510 | e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER 511 | return R[0].to_bytes(32, 'big') + ((k + e * sec) % SECP256K1_ORDER).to_bytes(32, 'big') 512 | 513 | class TestFrameworkKey(unittest.TestCase): 514 | def test_schnorr(self): 515 | """Test the Python Schnorr implementation.""" 516 | byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, SECP256K1_ORDER - 1, SECP256K1_ORDER, 2**256 - 1]] 517 | keys = {} 518 | for privkey in byte_arrays: # build array of key/pubkey pairs 519 | pubkey, _ = compute_xonly_pubkey(privkey) 520 | if pubkey is not None: 521 | keys[privkey] = pubkey 522 | for msg in byte_arrays: # test every combination of message, signing key, verification key 523 | for sign_privkey, _ in keys.items(): 524 | sig = sign_schnorr(sign_privkey, msg) 525 | for verify_privkey, verify_pubkey in keys.items(): 526 | if verify_privkey == sign_privkey: 527 | self.assertTrue(verify_schnorr(verify_pubkey, sig, msg)) 528 | sig = list(sig) 529 | sig[random.randrange(64)] ^= (1 << (random.randrange(8))) # damaging signature should break things 530 | sig = bytes(sig) 531 | self.assertFalse(verify_schnorr(verify_pubkey, sig, msg)) 532 | 533 | def test_schnorr_testvectors(self): 534 | """Implement the BIP340 test vectors (read from bip340_test_vectors.csv).""" 535 | num_tests = 0 536 | vectors_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bip340_test_vectors.csv') 537 | with open(vectors_file, newline='', encoding='utf8') as csvfile: 538 | reader = csv.reader(csvfile) 539 | next(reader) 540 | for row in reader: 541 | (i_str, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str, comment) = row 542 | i = int(i_str) 543 | pubkey = bytes.fromhex(pubkey_hex) 544 | msg = bytes.fromhex(msg_hex) 545 | sig = bytes.fromhex(sig_hex) 546 | result = result_str == 'TRUE' 547 | if seckey_hex != '': 548 | seckey = bytes.fromhex(seckey_hex) 549 | pubkey_actual = compute_xonly_pubkey(seckey)[0] 550 | self.assertEqual(pubkey.hex(), pubkey_actual.hex(), "BIP340 test vector %i (%s): pubkey mismatch" % (i, comment)) 551 | aux_rand = bytes.fromhex(aux_rand_hex) 552 | try: 553 | sig_actual = sign_schnorr(seckey, msg, aux_rand) 554 | self.assertEqual(sig.hex(), sig_actual.hex(), "BIP340 test vector %i (%s): sig mismatch" % (i, comment)) 555 | except RuntimeError as e: 556 | self.fail("BIP340 test vector %i (%s): signing raised exception %s" % (i, comment, e)) 557 | result_actual = verify_schnorr(pubkey, sig, msg) 558 | if result: 559 | self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification failed" % (i, comment)) 560 | else: 561 | self.assertEqual(result, result_actual, "BIP340 test vector %i (%s): verification succeeded unexpectedly" % (i, comment)) 562 | num_tests += 1 563 | self.assertTrue(num_tests >= 15) # expect at least 15 test vectors 564 | -------------------------------------------------------------------------------- /miner_imports/test_framework/muhash.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Pieter Wuille 2 | # Distributed under the MIT software license, see the accompanying 3 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | """Native Python MuHash3072 implementation.""" 5 | 6 | import hashlib 7 | import unittest 8 | 9 | from .util import modinv 10 | 11 | def rot32(v, bits): 12 | """Rotate the 32-bit value v left by bits bits.""" 13 | bits %= 32 # Make sure the term below does not throw an exception 14 | return ((v << bits) & 0xffffffff) | (v >> (32 - bits)) 15 | 16 | def chacha20_doubleround(s): 17 | """Apply a ChaCha20 double round to 16-element state array s. 18 | 19 | See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439 20 | """ 21 | QUARTER_ROUNDS = [(0, 4, 8, 12), 22 | (1, 5, 9, 13), 23 | (2, 6, 10, 14), 24 | (3, 7, 11, 15), 25 | (0, 5, 10, 15), 26 | (1, 6, 11, 12), 27 | (2, 7, 8, 13), 28 | (3, 4, 9, 14)] 29 | 30 | for a, b, c, d in QUARTER_ROUNDS: 31 | s[a] = (s[a] + s[b]) & 0xffffffff 32 | s[d] = rot32(s[d] ^ s[a], 16) 33 | s[c] = (s[c] + s[d]) & 0xffffffff 34 | s[b] = rot32(s[b] ^ s[c], 12) 35 | s[a] = (s[a] + s[b]) & 0xffffffff 36 | s[d] = rot32(s[d] ^ s[a], 8) 37 | s[c] = (s[c] + s[d]) & 0xffffffff 38 | s[b] = rot32(s[b] ^ s[c], 7) 39 | 40 | def chacha20_32_to_384(key32): 41 | """Specialized ChaCha20 implementation with 32-byte key, 0 IV, 384-byte output.""" 42 | # See RFC 8439 section 2.3 for chacha20 parameters 43 | CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] 44 | 45 | key_bytes = [0]*8 46 | for i in range(8): 47 | key_bytes[i] = int.from_bytes(key32[(4 * i):(4 * (i+1))], 'little') 48 | 49 | INITIALIZATION_VECTOR = [0] * 4 50 | init = CONSTANTS + key_bytes + INITIALIZATION_VECTOR 51 | out = bytearray() 52 | for counter in range(6): 53 | init[12] = counter 54 | s = init.copy() 55 | for _ in range(10): 56 | chacha20_doubleround(s) 57 | for i in range(16): 58 | out.extend(((s[i] + init[i]) & 0xffffffff).to_bytes(4, 'little')) 59 | return bytes(out) 60 | 61 | def data_to_num3072(data): 62 | """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations.""" 63 | bytes384 = chacha20_32_to_384(data) 64 | return int.from_bytes(bytes384, 'little') 65 | 66 | class MuHash3072: 67 | """Class representing the MuHash3072 computation of a set. 68 | 69 | See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html 70 | """ 71 | 72 | MODULUS = 2**3072 - 1103717 73 | 74 | def __init__(self): 75 | """Initialize for an empty set.""" 76 | self.numerator = 1 77 | self.denominator = 1 78 | 79 | def insert(self, data): 80 | """Insert a byte array data in the set.""" 81 | data_hash = hashlib.sha256(data).digest() 82 | self.numerator = (self.numerator * data_to_num3072(data_hash)) % self.MODULUS 83 | 84 | def remove(self, data): 85 | """Remove a byte array from the set.""" 86 | data_hash = hashlib.sha256(data).digest() 87 | self.denominator = (self.denominator * data_to_num3072(data_hash)) % self.MODULUS 88 | 89 | def digest(self): 90 | """Extract the final hash. Does not modify this object.""" 91 | val = (self.numerator * modinv(self.denominator, self.MODULUS)) % self.MODULUS 92 | bytes384 = val.to_bytes(384, 'little') 93 | return hashlib.sha256(bytes384).digest() 94 | 95 | class TestFrameworkMuhash(unittest.TestCase): 96 | def test_muhash(self): 97 | muhash = MuHash3072() 98 | muhash.insert(b'\x00' * 32) 99 | muhash.insert((b'\x01' + b'\x00' * 31)) 100 | muhash.remove((b'\x02' + b'\x00' * 31)) 101 | finalized = muhash.digest() 102 | # This mirrors the result in the C++ MuHash3072 unit test 103 | self.assertEqual(finalized[::-1].hex(), "10d312b100cbd32ada024a6646e40d3482fcff103668d2625f10002a607d5863") 104 | 105 | def test_chacha20(self): 106 | def chacha_check(key, result): 107 | self.assertEqual(chacha20_32_to_384(key)[:64].hex(), result) 108 | 109 | # Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 110 | # Since the nonce is hardcoded to 0 in our function we only use those vectors. 111 | chacha_check([0]*32, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586") 112 | chacha_check([0]*31 + [1], "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963") 113 | -------------------------------------------------------------------------------- /miner_imports/test_framework/netutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2014-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Linux network utilities. 6 | 7 | Roughly based on http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code/ by Ricardo Pascal 8 | """ 9 | 10 | import sys 11 | import socket 12 | import struct 13 | import array 14 | import os 15 | 16 | # STATE_ESTABLISHED = '01' 17 | # STATE_SYN_SENT = '02' 18 | # STATE_SYN_RECV = '03' 19 | # STATE_FIN_WAIT1 = '04' 20 | # STATE_FIN_WAIT2 = '05' 21 | # STATE_TIME_WAIT = '06' 22 | # STATE_CLOSE = '07' 23 | # STATE_CLOSE_WAIT = '08' 24 | # STATE_LAST_ACK = '09' 25 | STATE_LISTEN = '0A' 26 | # STATE_CLOSING = '0B' 27 | 28 | def get_socket_inodes(pid): 29 | ''' 30 | Get list of socket inodes for process pid. 31 | ''' 32 | base = '/proc/%i/fd' % pid 33 | inodes = [] 34 | for item in os.listdir(base): 35 | target = os.readlink(os.path.join(base, item)) 36 | if target.startswith('socket:'): 37 | inodes.append(int(target[8:-1])) 38 | return inodes 39 | 40 | def _remove_empty(array): 41 | return [x for x in array if x !=''] 42 | 43 | def _convert_ip_port(array): 44 | host,port = array.split(':') 45 | # convert host from mangled-per-four-bytes form as used by kernel 46 | host = bytes.fromhex(host) 47 | host_out = '' 48 | for x in range(0, len(host) // 4): 49 | (val,) = struct.unpack('=I', host[x*4:(x+1)*4]) 50 | host_out += '%08x' % val 51 | 52 | return host_out,int(port,16) 53 | 54 | def netstat(typ='tcp'): 55 | ''' 56 | Function to return a list with status of tcp connections at linux systems 57 | To get pid of all network process running on system, you must run this script 58 | as superuser 59 | ''' 60 | with open('/proc/net/'+typ,'r',encoding='utf8') as f: 61 | content = f.readlines() 62 | content.pop(0) 63 | result = [] 64 | for line in content: 65 | line_array = _remove_empty(line.split(' ')) # Split lines and remove empty spaces. 66 | tcp_id = line_array[0] 67 | l_addr = _convert_ip_port(line_array[1]) 68 | r_addr = _convert_ip_port(line_array[2]) 69 | state = line_array[3] 70 | inode = int(line_array[9]) # Need the inode to match with process pid. 71 | nline = [tcp_id, l_addr, r_addr, state, inode] 72 | result.append(nline) 73 | return result 74 | 75 | def get_bind_addrs(pid): 76 | ''' 77 | Get bind addresses as (host,port) tuples for process pid. 78 | ''' 79 | inodes = get_socket_inodes(pid) 80 | bind_addrs = [] 81 | for conn in netstat('tcp') + netstat('tcp6'): 82 | if conn[3] == STATE_LISTEN and conn[4] in inodes: 83 | bind_addrs.append(conn[1]) 84 | return bind_addrs 85 | 86 | # from: https://code.activestate.com/recipes/439093/ 87 | def all_interfaces(): 88 | ''' 89 | Return all interfaces that are up 90 | ''' 91 | import fcntl # Linux only, so only import when required 92 | 93 | is_64bits = sys.maxsize > 2**32 94 | struct_size = 40 if is_64bits else 32 95 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 96 | max_possible = 8 # initial value 97 | while True: 98 | bytes = max_possible * struct_size 99 | names = array.array('B', b'\0' * bytes) 100 | outbytes = struct.unpack('iL', fcntl.ioctl( 101 | s.fileno(), 102 | 0x8912, # SIOCGIFCONF 103 | struct.pack('iL', bytes, names.buffer_info()[0]) 104 | ))[0] 105 | if outbytes == bytes: 106 | max_possible *= 2 107 | else: 108 | break 109 | namestr = names.tobytes() 110 | return [(namestr[i:i+16].split(b'\0', 1)[0], 111 | socket.inet_ntoa(namestr[i+20:i+24])) 112 | for i in range(0, outbytes, struct_size)] 113 | 114 | def addr_to_hex(addr): 115 | ''' 116 | Convert string IPv4 or IPv6 address to binary address as returned by 117 | get_bind_addrs. 118 | Very naive implementation that certainly doesn't work for all IPv6 variants. 119 | ''' 120 | if '.' in addr: # IPv4 121 | addr = [int(x) for x in addr.split('.')] 122 | elif ':' in addr: # IPv6 123 | sub = [[], []] # prefix, suffix 124 | x = 0 125 | addr = addr.split(':') 126 | for i,comp in enumerate(addr): 127 | if comp == '': 128 | if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end 129 | continue 130 | x += 1 # :: skips to suffix 131 | assert x < 2 132 | else: # two bytes per component 133 | val = int(comp, 16) 134 | sub[x].append(val >> 8) 135 | sub[x].append(val & 0xff) 136 | nullbytes = 16 - len(sub[0]) - len(sub[1]) 137 | assert (x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0) 138 | addr = sub[0] + ([0] * nullbytes) + sub[1] 139 | else: 140 | raise ValueError('Could not parse address %s' % addr) 141 | return bytearray(addr).hex() 142 | 143 | def test_ipv6_local(): 144 | ''' 145 | Check for (local) IPv6 support. 146 | ''' 147 | # By using SOCK_DGRAM this will not actually make a connection, but it will 148 | # fail if there is no route to IPv6 localhost. 149 | have_ipv6 = True 150 | try: 151 | s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 152 | s.connect(('::1', 1)) 153 | except socket.error: 154 | have_ipv6 = False 155 | return have_ipv6 156 | -------------------------------------------------------------------------------- /miner_imports/test_framework/ripemd160.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Pieter Wuille 2 | # Distributed under the MIT software license, see the accompanying 3 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | """Test-only pure Python RIPEMD160 implementation.""" 5 | 6 | import unittest 7 | 8 | # Message schedule indexes for the left path. 9 | ML = [ 10 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 11 | 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 12 | 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 13 | 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 14 | 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 15 | ] 16 | 17 | # Message schedule indexes for the right path. 18 | MR = [ 19 | 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 20 | 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 21 | 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 22 | 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 23 | 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 24 | ] 25 | 26 | # Rotation counts for the left path. 27 | RL = [ 28 | 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 29 | 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 30 | 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 31 | 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 32 | 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 33 | ] 34 | 35 | # Rotation counts for the right path. 36 | RR = [ 37 | 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 38 | 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 39 | 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 40 | 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 41 | 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 42 | ] 43 | 44 | # K constants for the left path. 45 | KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] 46 | 47 | # K constants for the right path. 48 | KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] 49 | 50 | 51 | def fi(x, y, z, i): 52 | """The f1, f2, f3, f4, and f5 functions from the specification.""" 53 | if i == 0: 54 | return x ^ y ^ z 55 | elif i == 1: 56 | return (x & y) | (~x & z) 57 | elif i == 2: 58 | return (x | ~y) ^ z 59 | elif i == 3: 60 | return (x & z) | (y & ~z) 61 | elif i == 4: 62 | return x ^ (y | ~z) 63 | else: 64 | assert False 65 | 66 | 67 | def rol(x, i): 68 | """Rotate the bottom 32 bits of x left by i bits.""" 69 | return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff 70 | 71 | 72 | def compress(h0, h1, h2, h3, h4, block): 73 | """Compress state (h0, h1, h2, h3, h4) with block.""" 74 | # Left path variables. 75 | al, bl, cl, dl, el = h0, h1, h2, h3, h4 76 | # Right path variables. 77 | ar, br, cr, dr, er = h0, h1, h2, h3, h4 78 | # Message variables. 79 | x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] 80 | 81 | # Iterate over the 80 rounds of the compression. 82 | for j in range(80): 83 | rnd = j >> 4 84 | # Perform left side of the transformation. 85 | al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el 86 | al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl 87 | # Perform right side of the transformation. 88 | ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er 89 | ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr 90 | 91 | # Compose old state, left transform, and right transform into new state. 92 | return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr 93 | 94 | 95 | def ripemd160(data): 96 | """Compute the RIPEMD-160 hash of data.""" 97 | # Initialize state. 98 | state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) 99 | # Process full 64-byte blocks in the input. 100 | for b in range(len(data) >> 6): 101 | state = compress(*state, data[64*b:64*(b+1)]) 102 | # Construct final blocks (with padding and size). 103 | pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) 104 | fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') 105 | # Process final blocks. 106 | for b in range(len(fin) >> 6): 107 | state = compress(*state, fin[64*b:64*(b+1)]) 108 | # Produce output. 109 | return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) 110 | 111 | 112 | class TestFrameworkKey(unittest.TestCase): 113 | def test_ripemd160(self): 114 | """RIPEMD-160 test vectors.""" 115 | # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html 116 | for msg, hexout in [ 117 | (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), 118 | (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), 119 | (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), 120 | (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), 121 | (b"abcdefghijklmnopqrstuvwxyz", 122 | "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), 123 | (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 124 | "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), 125 | (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 126 | "b0e20b6e3116640286ed3a87a5713079b21f5189"), 127 | (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), 128 | (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") 129 | ]: 130 | self.assertEqual(ripemd160(msg).hex(), hexout) 131 | -------------------------------------------------------------------------------- /miner_imports/test_framework/script_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2019-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Useful Script constants and utils.""" 6 | from test_framework.script import ( 7 | CScript, 8 | CScriptOp, 9 | OP_0, 10 | OP_CHECKMULTISIG, 11 | OP_CHECKSIG, 12 | OP_DUP, 13 | OP_EQUAL, 14 | OP_EQUALVERIFY, 15 | OP_HASH160, 16 | hash160, 17 | sha256, 18 | ) 19 | 20 | # To prevent a "tx-size-small" policy rule error, a transaction has to have a 21 | # non-witness size of at least 82 bytes (MIN_STANDARD_TX_NONWITNESS_SIZE in 22 | # src/policy/policy.h). Considering a Tx with the smallest possible single 23 | # input (blank, empty scriptSig), and with an output omitting the scriptPubKey, 24 | # we get to a minimum size of 60 bytes: 25 | # 26 | # Tx Skeleton: 4 [Version] + 1 [InCount] + 1 [OutCount] + 4 [LockTime] = 10 bytes 27 | # Blank Input: 32 [PrevTxHash] + 4 [Index] + 1 [scriptSigLen] + 4 [SeqNo] = 41 bytes 28 | # Output: 8 [Amount] + 1 [scriptPubKeyLen] = 9 bytes 29 | # 30 | # Hence, the scriptPubKey of the single output has to have a size of at 31 | # least 22 bytes, which corresponds to the size of a P2WPKH scriptPubKey. 32 | # The following script constant consists of a single push of 21 bytes of 'a': 33 | # <21-bytes of 'a'> 34 | # resulting in a 22-byte size. It should be used whenever (small) fake 35 | # scriptPubKeys are needed, to guarantee that the minimum transaction size is 36 | # met. 37 | DUMMY_P2WPKH_SCRIPT = CScript([b'a' * 21]) 38 | DUMMY_2_P2WPKH_SCRIPT = CScript([b'b' * 21]) 39 | 40 | 41 | def key_to_p2pk_script(key): 42 | key = check_key(key) 43 | return CScript([key, OP_CHECKSIG]) 44 | 45 | 46 | def keys_to_multisig_script(keys, *, k=None): 47 | n = len(keys) 48 | if k is None: # n-of-n multisig by default 49 | k = n 50 | assert k <= n 51 | op_k = CScriptOp.encode_op_n(k) 52 | op_n = CScriptOp.encode_op_n(n) 53 | checked_keys = [check_key(key) for key in keys] 54 | return CScript([op_k] + checked_keys + [op_n, OP_CHECKMULTISIG]) 55 | 56 | 57 | def keyhash_to_p2pkh_script(hash): 58 | assert len(hash) == 20 59 | return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG]) 60 | 61 | 62 | def scripthash_to_p2sh_script(hash): 63 | assert len(hash) == 20 64 | return CScript([OP_HASH160, hash, OP_EQUAL]) 65 | 66 | 67 | def key_to_p2pkh_script(key): 68 | key = check_key(key) 69 | return keyhash_to_p2pkh_script(hash160(key)) 70 | 71 | 72 | def script_to_p2sh_script(script): 73 | script = check_script(script) 74 | return scripthash_to_p2sh_script(hash160(script)) 75 | 76 | 77 | def key_to_p2sh_p2wpkh_script(key): 78 | key = check_key(key) 79 | p2shscript = CScript([OP_0, hash160(key)]) 80 | return script_to_p2sh_script(p2shscript) 81 | 82 | 83 | def program_to_witness_script(version, program): 84 | if isinstance(program, str): 85 | program = bytes.fromhex(program) 86 | assert 0 <= version <= 16 87 | assert 2 <= len(program) <= 40 88 | assert version > 0 or len(program) in [20, 32] 89 | return CScript([version, program]) 90 | 91 | 92 | def script_to_p2wsh_script(script): 93 | script = check_script(script) 94 | return program_to_witness_script(0, sha256(script)) 95 | 96 | 97 | def key_to_p2wpkh_script(key): 98 | key = check_key(key) 99 | return program_to_witness_script(0, hash160(key)) 100 | 101 | 102 | def script_to_p2sh_p2wsh_script(script): 103 | script = check_script(script) 104 | p2shscript = CScript([OP_0, sha256(script)]) 105 | return script_to_p2sh_script(p2shscript) 106 | 107 | 108 | def check_key(key): 109 | if isinstance(key, str): 110 | key = bytes.fromhex(key) # Assuming this is hex string 111 | if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65): 112 | return key 113 | assert False 114 | 115 | 116 | def check_script(script): 117 | if isinstance(script, str): 118 | script = bytes.fromhex(script) # Assuming this is hex string 119 | if isinstance(script, bytes) or isinstance(script, CScript): 120 | return script 121 | assert False 122 | -------------------------------------------------------------------------------- /miner_imports/test_framework/segwit_addr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2017 Pieter Wuille 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Reference implementation for Bech32/Bech32m and segwit addresses.""" 6 | import unittest 7 | from enum import Enum 8 | 9 | CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 10 | BECH32_CONST = 1 11 | BECH32M_CONST = 0x2bc830a3 12 | 13 | class Encoding(Enum): 14 | """Enumeration type to list the various supported encodings.""" 15 | BECH32 = 1 16 | BECH32M = 2 17 | 18 | 19 | def bech32_polymod(values): 20 | """Internal function that computes the Bech32 checksum.""" 21 | generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 22 | chk = 1 23 | for value in values: 24 | top = chk >> 25 25 | chk = (chk & 0x1ffffff) << 5 ^ value 26 | for i in range(5): 27 | chk ^= generator[i] if ((top >> i) & 1) else 0 28 | return chk 29 | 30 | 31 | def bech32_hrp_expand(hrp): 32 | """Expand the HRP into values for checksum computation.""" 33 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] 34 | 35 | 36 | def bech32_verify_checksum(hrp, data): 37 | """Verify a checksum given HRP and converted data characters.""" 38 | check = bech32_polymod(bech32_hrp_expand(hrp) + data) 39 | if check == BECH32_CONST: 40 | return Encoding.BECH32 41 | elif check == BECH32M_CONST: 42 | return Encoding.BECH32M 43 | else: 44 | return None 45 | 46 | def bech32_create_checksum(encoding, hrp, data): 47 | """Compute the checksum values given HRP and data.""" 48 | values = bech32_hrp_expand(hrp) + data 49 | const = BECH32M_CONST if encoding == Encoding.BECH32M else BECH32_CONST 50 | polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const 51 | return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] 52 | 53 | 54 | def bech32_encode(encoding, hrp, data): 55 | """Compute a Bech32 or Bech32m string given HRP and data values.""" 56 | combined = data + bech32_create_checksum(encoding, hrp, data) 57 | return hrp + '1' + ''.join([CHARSET[d] for d in combined]) 58 | 59 | 60 | def bech32_decode(bech): 61 | """Validate a Bech32/Bech32m string, and determine HRP and data.""" 62 | if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or 63 | (bech.lower() != bech and bech.upper() != bech)): 64 | return (None, None, None) 65 | bech = bech.lower() 66 | pos = bech.rfind('1') 67 | if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: 68 | return (None, None, None) 69 | if not all(x in CHARSET for x in bech[pos+1:]): 70 | return (None, None, None) 71 | hrp = bech[:pos] 72 | data = [CHARSET.find(x) for x in bech[pos+1:]] 73 | encoding = bech32_verify_checksum(hrp, data) 74 | if encoding is None: 75 | return (None, None, None) 76 | return (encoding, hrp, data[:-6]) 77 | 78 | 79 | def convertbits(data, frombits, tobits, pad=True): 80 | """General power-of-2 base conversion.""" 81 | acc = 0 82 | bits = 0 83 | ret = [] 84 | maxv = (1 << tobits) - 1 85 | max_acc = (1 << (frombits + tobits - 1)) - 1 86 | for value in data: 87 | if value < 0 or (value >> frombits): 88 | return None 89 | acc = ((acc << frombits) | value) & max_acc 90 | bits += frombits 91 | while bits >= tobits: 92 | bits -= tobits 93 | ret.append((acc >> bits) & maxv) 94 | if pad: 95 | if bits: 96 | ret.append((acc << (tobits - bits)) & maxv) 97 | elif bits >= frombits or ((acc << (tobits - bits)) & maxv): 98 | return None 99 | return ret 100 | 101 | 102 | def decode_segwit_address(hrp, addr): 103 | """Decode a segwit address.""" 104 | encoding, hrpgot, data = bech32_decode(addr) 105 | if hrpgot != hrp: 106 | return (None, None) 107 | decoded = convertbits(data[1:], 5, 8, False) 108 | if decoded is None or len(decoded) < 2 or len(decoded) > 40: 109 | return (None, None) 110 | if data[0] > 16: 111 | return (None, None) 112 | if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: 113 | return (None, None) 114 | if (data[0] == 0 and encoding != Encoding.BECH32) or (data[0] != 0 and encoding != Encoding.BECH32M): 115 | return (None, None) 116 | return (data[0], decoded) 117 | 118 | 119 | def encode_segwit_address(hrp, witver, witprog): 120 | """Encode a segwit address.""" 121 | encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M 122 | ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) 123 | if decode_segwit_address(hrp, ret) == (None, None): 124 | return None 125 | return ret 126 | 127 | class TestFrameworkScript(unittest.TestCase): 128 | def test_segwit_encode_decode(self): 129 | def test_python_bech32(addr): 130 | hrp = addr[:4] 131 | self.assertEqual(hrp, "bcrt") 132 | (witver, witprog) = decode_segwit_address(hrp, addr) 133 | self.assertEqual(encode_segwit_address(hrp, witver, witprog), addr) 134 | 135 | # P2WPKH 136 | test_python_bech32('bcrt1qthmht0k2qnh3wy7336z05lu2km7emzfpm3wg46') 137 | # P2WSH 138 | test_python_bech32('bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj') 139 | test_python_bech32('bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85') 140 | # P2TR 141 | test_python_bech32('bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6') 142 | -------------------------------------------------------------------------------- /miner_imports/test_framework/siphash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2016-2018 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Specialized SipHash-2-4 implementations. 6 | 7 | This implements SipHash-2-4 for 256-bit integers. 8 | """ 9 | 10 | def rotl64(n, b): 11 | return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b 12 | 13 | def siphash_round(v0, v1, v2, v3): 14 | v0 = (v0 + v1) & ((1 << 64) - 1) 15 | v1 = rotl64(v1, 13) 16 | v1 ^= v0 17 | v0 = rotl64(v0, 32) 18 | v2 = (v2 + v3) & ((1 << 64) - 1) 19 | v3 = rotl64(v3, 16) 20 | v3 ^= v2 21 | v0 = (v0 + v3) & ((1 << 64) - 1) 22 | v3 = rotl64(v3, 21) 23 | v3 ^= v0 24 | v2 = (v2 + v1) & ((1 << 64) - 1) 25 | v1 = rotl64(v1, 17) 26 | v1 ^= v2 27 | v2 = rotl64(v2, 32) 28 | return (v0, v1, v2, v3) 29 | 30 | def siphash256(k0, k1, h): 31 | n0 = h & ((1 << 64) - 1) 32 | n1 = (h >> 64) & ((1 << 64) - 1) 33 | n2 = (h >> 128) & ((1 << 64) - 1) 34 | n3 = (h >> 192) & ((1 << 64) - 1) 35 | v0 = 0x736f6d6570736575 ^ k0 36 | v1 = 0x646f72616e646f6d ^ k1 37 | v2 = 0x6c7967656e657261 ^ k0 38 | v3 = 0x7465646279746573 ^ k1 ^ n0 39 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 40 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 41 | v0 ^= n0 42 | v3 ^= n1 43 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 44 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 45 | v0 ^= n1 46 | v3 ^= n2 47 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 48 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 49 | v0 ^= n2 50 | v3 ^= n3 51 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 52 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 53 | v0 ^= n3 54 | v3 ^= 0x2000000000000000 55 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 56 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 57 | v0 ^= 0x2000000000000000 58 | v2 ^= 0xFF 59 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 60 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 61 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 62 | v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3) 63 | return v0 ^ v1 ^ v2 ^ v3 64 | -------------------------------------------------------------------------------- /miner_imports/test_framework/socks5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2015-2019 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Dummy Socks5 server for testing.""" 6 | 7 | import socket 8 | import threading 9 | import queue 10 | import logging 11 | 12 | logger = logging.getLogger("TestFramework.socks5") 13 | 14 | # Protocol constants 15 | class Command: 16 | CONNECT = 0x01 17 | 18 | class AddressType: 19 | IPV4 = 0x01 20 | DOMAINNAME = 0x03 21 | IPV6 = 0x04 22 | 23 | # Utility functions 24 | def recvall(s, n): 25 | """Receive n bytes from a socket, or fail.""" 26 | rv = bytearray() 27 | while n > 0: 28 | d = s.recv(n) 29 | if not d: 30 | raise IOError('Unexpected end of stream') 31 | rv.extend(d) 32 | n -= len(d) 33 | return rv 34 | 35 | # Implementation classes 36 | class Socks5Configuration(): 37 | """Proxy configuration.""" 38 | def __init__(self): 39 | self.addr = None # Bind address (must be set) 40 | self.af = socket.AF_INET # Bind address family 41 | self.unauth = False # Support unauthenticated 42 | self.auth = False # Support authentication 43 | 44 | class Socks5Command(): 45 | """Information about an incoming socks5 command.""" 46 | def __init__(self, cmd, atyp, addr, port, username, password): 47 | self.cmd = cmd # Command (one of Command.*) 48 | self.atyp = atyp # Address type (one of AddressType.*) 49 | self.addr = addr # Address 50 | self.port = port # Port to connect to 51 | self.username = username 52 | self.password = password 53 | def __repr__(self): 54 | return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password) 55 | 56 | class Socks5Connection(): 57 | def __init__(self, serv, conn): 58 | self.serv = serv 59 | self.conn = conn 60 | 61 | def handle(self): 62 | """Handle socks5 request according to RFC192.""" 63 | try: 64 | # Verify socks version 65 | ver = recvall(self.conn, 1)[0] 66 | if ver != 0x05: 67 | raise IOError('Invalid socks version %i' % ver) 68 | # Choose authentication method 69 | nmethods = recvall(self.conn, 1)[0] 70 | methods = bytearray(recvall(self.conn, nmethods)) 71 | method = None 72 | if 0x02 in methods and self.serv.conf.auth: 73 | method = 0x02 # username/password 74 | elif 0x00 in methods and self.serv.conf.unauth: 75 | method = 0x00 # unauthenticated 76 | if method is None: 77 | raise IOError('No supported authentication method was offered') 78 | # Send response 79 | self.conn.sendall(bytearray([0x05, method])) 80 | # Read authentication (optional) 81 | username = None 82 | password = None 83 | if method == 0x02: 84 | ver = recvall(self.conn, 1)[0] 85 | if ver != 0x01: 86 | raise IOError('Invalid auth packet version %i' % ver) 87 | ulen = recvall(self.conn, 1)[0] 88 | username = str(recvall(self.conn, ulen)) 89 | plen = recvall(self.conn, 1)[0] 90 | password = str(recvall(self.conn, plen)) 91 | # Send authentication response 92 | self.conn.sendall(bytearray([0x01, 0x00])) 93 | 94 | # Read connect request 95 | ver, cmd, _, atyp = recvall(self.conn, 4) 96 | if ver != 0x05: 97 | raise IOError('Invalid socks version %i in connect request' % ver) 98 | if cmd != Command.CONNECT: 99 | raise IOError('Unhandled command %i in connect request' % cmd) 100 | 101 | if atyp == AddressType.IPV4: 102 | addr = recvall(self.conn, 4) 103 | elif atyp == AddressType.DOMAINNAME: 104 | n = recvall(self.conn, 1)[0] 105 | addr = recvall(self.conn, n) 106 | elif atyp == AddressType.IPV6: 107 | addr = recvall(self.conn, 16) 108 | else: 109 | raise IOError('Unknown address type %i' % atyp) 110 | port_hi,port_lo = recvall(self.conn, 2) 111 | port = (port_hi << 8) | port_lo 112 | 113 | # Send dummy response 114 | self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])) 115 | 116 | cmdin = Socks5Command(cmd, atyp, addr, port, username, password) 117 | self.serv.queue.put(cmdin) 118 | logger.info('Proxy: %s', cmdin) 119 | # Fall through to disconnect 120 | except Exception as e: 121 | logger.exception("socks5 request handling failed.") 122 | self.serv.queue.put(e) 123 | finally: 124 | self.conn.close() 125 | 126 | class Socks5Server(): 127 | def __init__(self, conf): 128 | self.conf = conf 129 | self.s = socket.socket(conf.af) 130 | self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 131 | self.s.bind(conf.addr) 132 | self.s.listen(5) 133 | self.running = False 134 | self.thread = None 135 | self.queue = queue.Queue() # report connections and exceptions to client 136 | 137 | def run(self): 138 | while self.running: 139 | (sockconn, _) = self.s.accept() 140 | if self.running: 141 | conn = Socks5Connection(self, sockconn) 142 | thread = threading.Thread(None, conn.handle) 143 | thread.daemon = True 144 | thread.start() 145 | 146 | def start(self): 147 | assert not self.running 148 | self.running = True 149 | self.thread = threading.Thread(None, self.run) 150 | self.thread.daemon = True 151 | self.thread.start() 152 | 153 | def stop(self): 154 | self.running = False 155 | # connect to self to end run loop 156 | s = socket.socket(self.conf.af) 157 | s.connect(self.conf.addr) 158 | s.close() 159 | self.thread.join() 160 | 161 | -------------------------------------------------------------------------------- /miner_imports/test_framework/test_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2019 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | 6 | from test_framework.test_framework import BitcoinTestFramework 7 | 8 | class TestShell: 9 | """Wrapper Class for BitcoinTestFramework. 10 | 11 | The TestShell class extends the BitcoinTestFramework 12 | rpc & daemon process management functionality to external 13 | python environments. 14 | 15 | It is a singleton class, which ensures that users only 16 | start a single TestShell at a time.""" 17 | 18 | class __TestShell(BitcoinTestFramework): 19 | def set_test_params(self): 20 | pass 21 | 22 | def run_test(self): 23 | pass 24 | 25 | def setup(self, **kwargs): 26 | if self.running: 27 | print("TestShell is already running!") 28 | return 29 | 30 | # Num_nodes parameter must be set 31 | # by BitcoinTestFramework child class. 32 | self.num_nodes = 1 33 | 34 | # User parameters override default values. 35 | for key, value in kwargs.items(): 36 | if hasattr(self, key): 37 | setattr(self, key, value) 38 | elif hasattr(self.options, key): 39 | setattr(self.options, key, value) 40 | else: 41 | raise KeyError(key + " not a valid parameter key!") 42 | 43 | super().setup() 44 | self.running = True 45 | return self 46 | 47 | def shutdown(self): 48 | if not self.running: 49 | print("TestShell is not running!") 50 | else: 51 | super().shutdown() 52 | self.running = False 53 | 54 | def reset(self): 55 | if self.running: 56 | print("Shutdown TestShell before resetting!") 57 | else: 58 | self.num_nodes = None 59 | super().__init__() 60 | 61 | instance = None 62 | 63 | def __new__(cls): 64 | # This implementation enforces singleton pattern, and will return the 65 | # previously initialized instance if available 66 | if not TestShell.instance: 67 | TestShell.instance = TestShell.__TestShell() 68 | TestShell.instance.running = False 69 | return TestShell.instance 70 | 71 | def __getattr__(self, name): 72 | return getattr(self.instance, name) 73 | 74 | def __setattr__(self, name, value): 75 | return setattr(self.instance, name, value) 76 | -------------------------------------------------------------------------------- /miner_imports/test_framework/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2014-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Helpful routines for regression testing.""" 6 | 7 | from base64 import b64encode 8 | from decimal import Decimal, ROUND_DOWN 9 | from subprocess import CalledProcessError 10 | import hashlib 11 | import inspect 12 | import json 13 | import logging 14 | import os 15 | import re 16 | import time 17 | import unittest 18 | 19 | from . import coverage 20 | from .authproxy import AuthServiceProxy, JSONRPCException 21 | from typing import Callable, Optional 22 | 23 | logger = logging.getLogger("TestFramework.utils") 24 | 25 | # Assert functions 26 | ################## 27 | 28 | 29 | def assert_approx(v, vexp, vspan=0.00001): 30 | """Assert that `v` is within `vspan` of `vexp`""" 31 | if v < vexp - vspan: 32 | raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan))) 33 | if v > vexp + vspan: 34 | raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan))) 35 | 36 | 37 | def assert_fee_amount(fee, tx_size, feerate_BTC_kvB): 38 | """Assert the fee is in range.""" 39 | assert isinstance(tx_size, int) 40 | target_fee = get_fee(tx_size, feerate_BTC_kvB) 41 | if fee < target_fee: 42 | raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee))) 43 | # allow the wallet's estimation to be at most 2 bytes off 44 | high_fee = get_fee(tx_size + 2, feerate_BTC_kvB) 45 | if fee > high_fee: 46 | raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee))) 47 | 48 | 49 | def assert_equal(thing1, thing2, *args): 50 | if thing1 != thing2 or any(thing1 != arg for arg in args): 51 | raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args)) 52 | 53 | 54 | def assert_greater_than(thing1, thing2): 55 | if thing1 <= thing2: 56 | raise AssertionError("%s <= %s" % (str(thing1), str(thing2))) 57 | 58 | 59 | def assert_greater_than_or_equal(thing1, thing2): 60 | if thing1 < thing2: 61 | raise AssertionError("%s < %s" % (str(thing1), str(thing2))) 62 | 63 | 64 | def assert_raises(exc, fun, *args, **kwds): 65 | assert_raises_message(exc, None, fun, *args, **kwds) 66 | 67 | 68 | def assert_raises_message(exc, message, fun, *args, **kwds): 69 | try: 70 | fun(*args, **kwds) 71 | except JSONRPCException: 72 | raise AssertionError("Use assert_raises_rpc_error() to test RPC failures") 73 | except exc as e: 74 | if message is not None and message not in e.error['message']: 75 | raise AssertionError( 76 | "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( 77 | message, e.error['message'])) 78 | except Exception as e: 79 | raise AssertionError("Unexpected exception raised: " + type(e).__name__) 80 | else: 81 | raise AssertionError("No exception raised") 82 | 83 | 84 | def assert_raises_process_error(returncode: int, output: str, fun: Callable, *args, **kwds): 85 | """Execute a process and asserts the process return code and output. 86 | 87 | Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError 88 | and verifies that the return code and output are as expected. Throws AssertionError if 89 | no CalledProcessError was raised or if the return code and output are not as expected. 90 | 91 | Args: 92 | returncode: the process return code. 93 | output: [a substring of] the process output. 94 | fun: the function to call. This should execute a process. 95 | args*: positional arguments for the function. 96 | kwds**: named arguments for the function. 97 | """ 98 | try: 99 | fun(*args, **kwds) 100 | except CalledProcessError as e: 101 | if returncode != e.returncode: 102 | raise AssertionError("Unexpected returncode %i" % e.returncode) 103 | if output not in e.output: 104 | raise AssertionError("Expected substring not found:" + e.output) 105 | else: 106 | raise AssertionError("No exception raised") 107 | 108 | 109 | def assert_raises_rpc_error(code: Optional[int], message: Optional[str], fun: Callable, *args, **kwds): 110 | """Run an RPC and verify that a specific JSONRPC exception code and message is raised. 111 | 112 | Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException 113 | and verifies that the error code and message are as expected. Throws AssertionError if 114 | no JSONRPCException was raised or if the error code/message are not as expected. 115 | 116 | Args: 117 | code: the error code returned by the RPC call (defined in src/rpc/protocol.h). 118 | Set to None if checking the error code is not required. 119 | message: [a substring of] the error string returned by the RPC call. 120 | Set to None if checking the error string is not required. 121 | fun: the function to call. This should be the name of an RPC. 122 | args*: positional arguments for the function. 123 | kwds**: named arguments for the function. 124 | """ 125 | assert try_rpc(code, message, fun, *args, **kwds), "No exception raised" 126 | 127 | 128 | def try_rpc(code, message, fun, *args, **kwds): 129 | """Tries to run an rpc command. 130 | 131 | Test against error code and message if the rpc fails. 132 | Returns whether a JSONRPCException was raised.""" 133 | try: 134 | fun(*args, **kwds) 135 | except JSONRPCException as e: 136 | # JSONRPCException was thrown as expected. Check the code and message values are correct. 137 | if (code is not None) and (code != e.error["code"]): 138 | raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"]) 139 | if (message is not None) and (message not in e.error['message']): 140 | raise AssertionError( 141 | "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( 142 | message, e.error['message'])) 143 | return True 144 | except Exception as e: 145 | raise AssertionError("Unexpected exception raised: " + type(e).__name__) 146 | else: 147 | return False 148 | 149 | 150 | def assert_is_hex_string(string): 151 | try: 152 | int(string, 16) 153 | except Exception as e: 154 | raise AssertionError("Couldn't interpret %r as hexadecimal; raised: %s" % (string, e)) 155 | 156 | 157 | def assert_is_hash_string(string, length=64): 158 | if not isinstance(string, str): 159 | raise AssertionError("Expected a string, got type %r" % type(string)) 160 | elif length and len(string) != length: 161 | raise AssertionError("String of length %d expected; got %d" % (length, len(string))) 162 | elif not re.match('[abcdef0-9]+$', string): 163 | raise AssertionError("String %r contains invalid characters for a hash." % string) 164 | 165 | 166 | def assert_array_result(object_array, to_match, expected, should_not_find=False): 167 | """ 168 | Pass in array of JSON objects, a dictionary with key/value pairs 169 | to match against, and another dictionary with expected key/value 170 | pairs. 171 | If the should_not_find flag is true, to_match should not be found 172 | in object_array 173 | """ 174 | if should_not_find: 175 | assert_equal(expected, {}) 176 | num_matched = 0 177 | for item in object_array: 178 | all_match = True 179 | for key, value in to_match.items(): 180 | if item[key] != value: 181 | all_match = False 182 | if not all_match: 183 | continue 184 | elif should_not_find: 185 | num_matched = num_matched + 1 186 | for key, value in expected.items(): 187 | if item[key] != value: 188 | raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value))) 189 | num_matched = num_matched + 1 190 | if num_matched == 0 and not should_not_find: 191 | raise AssertionError("No objects matched %s" % (str(to_match))) 192 | if num_matched > 0 and should_not_find: 193 | raise AssertionError("Objects were found %s" % (str(to_match))) 194 | 195 | 196 | # Utility functions 197 | ################### 198 | 199 | 200 | def check_json_precision(): 201 | """Make sure json library being used does not lose precision converting BTC values""" 202 | n = Decimal("20000000.00000003") 203 | satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8) 204 | if satoshis != 2000000000000003: 205 | raise RuntimeError("JSON encode/decode loses precision") 206 | 207 | 208 | def EncodeDecimal(o): 209 | if isinstance(o, Decimal): 210 | return str(o) 211 | raise TypeError(repr(o) + " is not JSON serializable") 212 | 213 | 214 | def count_bytes(hex_string): 215 | return len(bytearray.fromhex(hex_string)) 216 | 217 | 218 | def str_to_b64str(string): 219 | return b64encode(string.encode('utf-8')).decode('ascii') 220 | 221 | 222 | def ceildiv(a, b): 223 | """ 224 | Divide 2 ints and round up to next int rather than round down 225 | Implementation requires python integers, which have a // operator that does floor division. 226 | Other types like decimal.Decimal whose // operator truncates towards 0 will not work. 227 | """ 228 | assert isinstance(a, int) 229 | assert isinstance(b, int) 230 | return -(-a // b) 231 | 232 | 233 | def get_fee(tx_size, feerate_btc_kvb): 234 | """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee""" 235 | feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors 236 | target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat 237 | return target_fee_sat / Decimal(1e8) # Return result in BTC 238 | 239 | 240 | def satoshi_round(amount): 241 | return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) 242 | 243 | 244 | def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): 245 | """Sleep until the predicate resolves to be True. 246 | 247 | Warning: Note that this method is not recommended to be used in tests as it is 248 | not aware of the context of the test framework. Using the `wait_until()` members 249 | from `BitcoinTestFramework` or `P2PInterface` class ensures the timeout is 250 | properly scaled. Furthermore, `wait_until()` from `P2PInterface` class in 251 | `p2p.py` has a preset lock. 252 | """ 253 | if attempts == float('inf') and timeout == float('inf'): 254 | timeout = 60 255 | timeout = timeout * timeout_factor 256 | attempt = 0 257 | time_end = time.time() + timeout 258 | 259 | while attempt < attempts and time.time() < time_end: 260 | if lock: 261 | with lock: 262 | if predicate(): 263 | return 264 | else: 265 | if predicate(): 266 | return 267 | attempt += 1 268 | time.sleep(0.05) 269 | 270 | # Print the cause of the timeout 271 | predicate_source = "''''\n" + inspect.getsource(predicate) + "'''" 272 | logger.error("wait_until() failed. Predicate: {}".format(predicate_source)) 273 | if attempt >= attempts: 274 | raise AssertionError("Predicate {} not true after {} attempts".format(predicate_source, attempts)) 275 | elif time.time() >= time_end: 276 | raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout)) 277 | raise RuntimeError('Unreachable') 278 | 279 | 280 | def sha256sum_file(filename): 281 | h = hashlib.sha256() 282 | with open(filename, 'rb') as f: 283 | d = f.read(4096) 284 | while len(d) > 0: 285 | h.update(d) 286 | d = f.read(4096) 287 | return h.digest() 288 | 289 | # RPC/P2P connection constants and functions 290 | ############################################ 291 | 292 | # The maximum number of nodes a single test can spawn 293 | MAX_NODES = 12 294 | # Don't assign rpc or p2p ports lower than this 295 | PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000)) 296 | # The number of ports to "reserve" for p2p and rpc, each 297 | PORT_RANGE = 5000 298 | 299 | 300 | class PortSeed: 301 | # Must be initialized with a unique integer for each process 302 | n = None 303 | 304 | 305 | def get_rpc_proxy(url: str, node_number: int, *, timeout: int=None, coveragedir: str=None) -> coverage.AuthServiceProxyWrapper: 306 | """ 307 | Args: 308 | url: URL of the RPC server to call 309 | node_number: the node number (or id) that this calls to 310 | 311 | Kwargs: 312 | timeout: HTTP timeout in seconds 313 | coveragedir: Directory 314 | 315 | Returns: 316 | AuthServiceProxy. convenience object for making RPC calls. 317 | 318 | """ 319 | proxy_kwargs = {} 320 | if timeout is not None: 321 | proxy_kwargs['timeout'] = int(timeout) 322 | 323 | proxy = AuthServiceProxy(url, **proxy_kwargs) 324 | 325 | coverage_logfile = coverage.get_filename(coveragedir, node_number) if coveragedir else None 326 | 327 | return coverage.AuthServiceProxyWrapper(proxy, url, coverage_logfile) 328 | 329 | 330 | def p2p_port(n): 331 | assert n <= MAX_NODES 332 | return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) 333 | 334 | 335 | def rpc_port(n): 336 | return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) 337 | 338 | 339 | def rpc_url(datadir, i, chain, rpchost): 340 | rpc_u, rpc_p = get_auth_cookie(datadir, chain) 341 | host = '127.0.0.1' 342 | port = rpc_port(i) 343 | if rpchost: 344 | parts = rpchost.split(':') 345 | if len(parts) == 2: 346 | host, port = parts 347 | else: 348 | host = rpchost 349 | return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port)) 350 | 351 | 352 | # Node functions 353 | ################ 354 | 355 | 356 | def initialize_datadir(dirname, n, chain, disable_autoconnect=True): 357 | datadir = get_datadir_path(dirname, n) 358 | if not os.path.isdir(datadir): 359 | os.makedirs(datadir) 360 | write_config(os.path.join(datadir, "bitcoin.conf"), n=n, chain=chain, disable_autoconnect=disable_autoconnect) 361 | os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) 362 | os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) 363 | return datadir 364 | 365 | 366 | def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect=True): 367 | # Translate chain subdirectory name to config name 368 | if chain == 'testnet3': 369 | chain_name_conf_arg = 'testnet' 370 | chain_name_conf_section = 'test' 371 | else: 372 | chain_name_conf_arg = chain 373 | chain_name_conf_section = chain 374 | with open(config_path, 'w', encoding='utf8') as f: 375 | if chain_name_conf_arg: 376 | f.write("{}=1\n".format(chain_name_conf_arg)) 377 | if chain_name_conf_section: 378 | f.write("[{}]\n".format(chain_name_conf_section)) 379 | f.write("port=" + str(p2p_port(n)) + "\n") 380 | f.write("rpcport=" + str(rpc_port(n)) + "\n") 381 | f.write("fallbackfee=0.0002\n") 382 | f.write("server=1\n") 383 | f.write("keypool=1\n") 384 | f.write("discover=0\n") 385 | f.write("dnsseed=0\n") 386 | f.write("fixedseeds=0\n") 387 | f.write("listenonion=0\n") 388 | # Increase peertimeout to avoid disconnects while using mocktime. 389 | # peertimeout is measured in mock time, so setting it large enough to 390 | # cover any duration in mock time is sufficient. It can be overridden 391 | # in tests. 392 | f.write("peertimeout=999999999\n") 393 | f.write("printtoconsole=0\n") 394 | f.write("upnp=0\n") 395 | f.write("natpmp=0\n") 396 | f.write("shrinkdebugfile=0\n") 397 | # To improve SQLite wallet performance so that the tests don't timeout, use -unsafesqlitesync 398 | f.write("unsafesqlitesync=1\n") 399 | if disable_autoconnect: 400 | f.write("connect=0\n") 401 | f.write(extra_config) 402 | 403 | 404 | def get_datadir_path(dirname, n): 405 | return os.path.join(dirname, "node" + str(n)) 406 | 407 | 408 | def append_config(datadir, options): 409 | with open(os.path.join(datadir, "bitcoin.conf"), 'a', encoding='utf8') as f: 410 | for option in options: 411 | f.write(option + "\n") 412 | 413 | 414 | def get_auth_cookie(datadir, chain): 415 | user = None 416 | password = None 417 | if os.path.isfile(os.path.join(datadir, "bitcoin.conf")): 418 | with open(os.path.join(datadir, "bitcoin.conf"), 'r', encoding='utf8') as f: 419 | for line in f: 420 | if line.startswith("rpcuser="): 421 | assert user is None # Ensure that there is only one rpcuser line 422 | user = line.split("=")[1].strip("\n") 423 | if line.startswith("rpcpassword="): 424 | assert password is None # Ensure that there is only one rpcpassword line 425 | password = line.split("=")[1].strip("\n") 426 | try: 427 | with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f: 428 | userpass = f.read() 429 | split_userpass = userpass.split(':') 430 | user = split_userpass[0] 431 | password = split_userpass[1] 432 | except OSError: 433 | pass 434 | if user is None or password is None: 435 | raise ValueError("No RPC credentials") 436 | return user, password 437 | 438 | 439 | # If a cookie file exists in the given datadir, delete it. 440 | def delete_cookie_file(datadir, chain): 441 | if os.path.isfile(os.path.join(datadir, chain, ".cookie")): 442 | logger.debug("Deleting leftover cookie file") 443 | os.remove(os.path.join(datadir, chain, ".cookie")) 444 | 445 | 446 | def softfork_active(node, key): 447 | """Return whether a softfork is active.""" 448 | return node.getdeploymentinfo()['deployments'][key]['active'] 449 | 450 | 451 | def set_node_times(nodes, t): 452 | for node in nodes: 453 | node.setmocktime(t) 454 | 455 | 456 | def check_node_connections(*, node, num_in, num_out): 457 | info = node.getnetworkinfo() 458 | assert_equal(info["connections_in"], num_in) 459 | assert_equal(info["connections_out"], num_out) 460 | 461 | 462 | # Transaction/Block functions 463 | ############################# 464 | 465 | 466 | def find_output(node, txid, amount, *, blockhash=None): 467 | """ 468 | Return index to output of txid with value amount 469 | Raises exception if there is none. 470 | """ 471 | txdata = node.getrawtransaction(txid, 1, blockhash) 472 | for i in range(len(txdata["vout"])): 473 | if txdata["vout"][i]["value"] == amount: 474 | return i 475 | raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount))) 476 | 477 | 478 | # Helper to create at least "count" utxos 479 | # Pass in a fee that is sufficient for relay and mining new transactions. 480 | def create_confirmed_utxos(test_framework, fee, node, count, **kwargs): 481 | to_generate = int(0.5 * count) + 101 482 | while to_generate > 0: 483 | test_framework.generate(node, min(25, to_generate), **kwargs) 484 | to_generate -= 25 485 | utxos = node.listunspent() 486 | iterations = count - len(utxos) 487 | addr1 = node.getnewaddress() 488 | addr2 = node.getnewaddress() 489 | if iterations <= 0: 490 | return utxos 491 | for _ in range(iterations): 492 | t = utxos.pop() 493 | inputs = [] 494 | inputs.append({"txid": t["txid"], "vout": t["vout"]}) 495 | outputs = {} 496 | send_value = t['amount'] - fee 497 | outputs[addr1] = satoshi_round(send_value / 2) 498 | outputs[addr2] = satoshi_round(send_value / 2) 499 | raw_tx = node.createrawtransaction(inputs, outputs) 500 | signed_tx = node.signrawtransactionwithwallet(raw_tx)["hex"] 501 | node.sendrawtransaction(signed_tx) 502 | 503 | while (node.getmempoolinfo()['size'] > 0): 504 | test_framework.generate(node, 1, **kwargs) 505 | 506 | utxos = node.listunspent() 507 | assert len(utxos) >= count 508 | return utxos 509 | 510 | 511 | def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs): 512 | """Build and send a transaction that spends the given inputs (specified 513 | by lists of parent_txid:vout each), with the desired total value and fee, 514 | equally divided up to the desired number of outputs. 515 | 516 | Returns a tuple with the txid and the amount sent per output. 517 | """ 518 | send_value = satoshi_round((value - fee)/num_outputs) 519 | inputs = [] 520 | for (txid, vout) in zip(parent_txids, vouts): 521 | inputs.append({'txid' : txid, 'vout' : vout}) 522 | outputs = {} 523 | for _ in range(num_outputs): 524 | outputs[node.getnewaddress()] = send_value 525 | rawtx = node.createrawtransaction(inputs, outputs, 0, True) 526 | signedtx = node.signrawtransactionwithwallet(rawtx) 527 | txid = node.sendrawtransaction(signedtx['hex']) 528 | fulltx = node.getrawtransaction(txid, 1) 529 | assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output 530 | return (txid, send_value) 531 | 532 | 533 | # Create large OP_RETURN txouts that can be appended to a transaction 534 | # to make it large (helper for constructing large transactions). 535 | def gen_return_txouts(): 536 | # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create 537 | # So we have big transactions (and therefore can't fit very many into each block) 538 | # create one script_pubkey 539 | script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes 540 | for _ in range(512): 541 | script_pubkey = script_pubkey + "01" 542 | # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change 543 | txouts = [] 544 | from .messages import CTxOut 545 | txout = CTxOut() 546 | txout.nValue = 0 547 | txout.scriptPubKey = bytes.fromhex(script_pubkey) 548 | for _ in range(128): 549 | txouts.append(txout) 550 | return txouts 551 | 552 | 553 | # Create a spend of each passed-in utxo, splicing in "txouts" to each raw 554 | # transaction to make it large. See gen_return_txouts() above. 555 | def create_lots_of_big_transactions(node, txouts, utxos, num, fee): 556 | addr = node.getnewaddress() 557 | txids = [] 558 | from .messages import tx_from_hex 559 | for _ in range(num): 560 | t = utxos.pop() 561 | inputs = [{"txid": t["txid"], "vout": t["vout"]}] 562 | outputs = {} 563 | change = t['amount'] - fee 564 | outputs[addr] = satoshi_round(change) 565 | rawtx = node.createrawtransaction(inputs, outputs) 566 | tx = tx_from_hex(rawtx) 567 | for txout in txouts: 568 | tx.vout.append(txout) 569 | newtx = tx.serialize().hex() 570 | signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") 571 | txid = node.sendrawtransaction(signresult["hex"], 0) 572 | txids.append(txid) 573 | return txids 574 | 575 | 576 | def mine_large_block(test_framework, node, utxos=None): 577 | # generate a 66k transaction, 578 | # and 14 of them is close to the 1MB block limit 579 | num = 14 580 | txouts = gen_return_txouts() 581 | utxos = utxos if utxos is not None else [] 582 | if len(utxos) < num: 583 | utxos.clear() 584 | utxos.extend(node.listunspent()) 585 | fee = 100 * node.getnetworkinfo()["relayfee"] 586 | create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee) 587 | test_framework.generate(node, 1) 588 | 589 | 590 | def find_vout_for_address(node, txid, addr): 591 | """ 592 | Locate the vout index of the given transaction sending to the 593 | given address. Raises runtime error exception if not found. 594 | """ 595 | tx = node.getrawtransaction(txid, True) 596 | for i in range(len(tx["vout"])): 597 | if addr == tx["vout"][i]["scriptPubKey"]["address"]: 598 | return i 599 | raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr)) 600 | 601 | def modinv(a, n): 602 | """Compute the modular inverse of a modulo n using the extended Euclidean 603 | Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. 604 | """ 605 | # TODO: Change to pow(a, -1, n) available in Python 3.8 606 | t1, t2 = 0, 1 607 | r1, r2 = n, a 608 | while r2 != 0: 609 | q = r1 // r2 610 | t1, t2 = t2, t1 - q * t2 611 | r1, r2 = r2, r1 - q * r2 612 | if r1 > 1: 613 | return None 614 | if t1 < 0: 615 | t1 += n 616 | return t1 617 | 618 | class TestFrameworkUtil(unittest.TestCase): 619 | def test_modinv(self): 620 | test_vectors = [ 621 | [7, 11], 622 | [11, 29], 623 | [90, 13], 624 | [1891, 3797], 625 | [6003722857, 77695236973], 626 | ] 627 | 628 | for a, n in test_vectors: 629 | self.assertEqual(modinv(a, n), pow(a, n-2, n)) 630 | -------------------------------------------------------------------------------- /miner_imports/test_framework/wallet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2020-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """A limited-functionality wallet, which may replace a real wallet in tests""" 6 | 7 | from copy import deepcopy 8 | from decimal import Decimal 9 | from enum import Enum 10 | from random import choice 11 | from typing import Optional 12 | from test_framework.address import ( 13 | base58_to_byte, 14 | create_deterministic_address_bcrt1_p2tr_op_true, 15 | key_to_p2pkh, 16 | key_to_p2sh_p2wpkh, 17 | key_to_p2wpkh, 18 | ) 19 | from test_framework.descriptors import descsum_create 20 | from test_framework.key import ECKey 21 | from test_framework.messages import ( 22 | COIN, 23 | COutPoint, 24 | CTransaction, 25 | CTxIn, 26 | CTxInWitness, 27 | CTxOut, 28 | tx_from_hex, 29 | ) 30 | from test_framework.script import ( 31 | CScript, 32 | LegacySignatureHash, 33 | LEAF_VERSION_TAPSCRIPT, 34 | OP_NOP, 35 | OP_TRUE, 36 | SIGHASH_ALL, 37 | ) 38 | from test_framework.script_util import ( 39 | key_to_p2pk_script, 40 | key_to_p2pkh_script, 41 | key_to_p2sh_p2wpkh_script, 42 | key_to_p2wpkh_script, 43 | keyhash_to_p2pkh_script, 44 | scripthash_to_p2sh_script, 45 | ) 46 | from test_framework.util import ( 47 | assert_equal, 48 | assert_greater_than_or_equal, 49 | ) 50 | 51 | DEFAULT_FEE = Decimal("0.0001") 52 | 53 | class MiniWalletMode(Enum): 54 | """Determines the transaction type the MiniWallet is creating and spending. 55 | 56 | For most purposes, the default mode ADDRESS_OP_TRUE should be sufficient; 57 | it simply uses a fixed bech32m P2TR address whose coins are spent with a 58 | witness stack of OP_TRUE, i.e. following an anyone-can-spend policy. 59 | However, if the transactions need to be modified by the user (e.g. prepending 60 | scriptSig for testing opcodes that are activated by a soft-fork), or the txs 61 | should contain an actual signature, the raw modes RAW_OP_TRUE and RAW_P2PK 62 | can be useful. Summary of modes: 63 | 64 | | output | | tx is | can modify | needs 65 | mode | description | address | standard | scriptSig | signing 66 | ----------------+-------------------+-----------+----------+------------+---------- 67 | ADDRESS_OP_TRUE | anyone-can-spend | bech32m | yes | no | no 68 | RAW_OP_TRUE | anyone-can-spend | - (raw) | no | yes | no 69 | RAW_P2PK | pay-to-public-key | - (raw) | yes | yes | yes 70 | """ 71 | ADDRESS_OP_TRUE = 1 72 | RAW_OP_TRUE = 2 73 | RAW_P2PK = 3 74 | 75 | 76 | class MiniWallet: 77 | def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE): 78 | self._test_node = test_node 79 | self._utxos = [] 80 | self._priv_key = None 81 | self._address = None 82 | 83 | assert isinstance(mode, MiniWalletMode) 84 | if mode == MiniWalletMode.RAW_OP_TRUE: 85 | self._scriptPubKey = bytes(CScript([OP_TRUE])) 86 | elif mode == MiniWalletMode.RAW_P2PK: 87 | # use simple deterministic private key (k=1) 88 | self._priv_key = ECKey() 89 | self._priv_key.set((1).to_bytes(32, 'big'), True) 90 | pub_key = self._priv_key.get_pubkey() 91 | self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes()) 92 | elif mode == MiniWalletMode.ADDRESS_OP_TRUE: 93 | self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true() 94 | self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey']) 95 | 96 | def rescan_utxos(self): 97 | """Drop all utxos and rescan the utxo set""" 98 | self._utxos = [] 99 | res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()]) 100 | assert_equal(True, res['success']) 101 | for utxo in res['unspents']: 102 | self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']}) 103 | 104 | def scan_tx(self, tx): 105 | """Scan the tx for self._scriptPubKey outputs and add them to self._utxos""" 106 | for out in tx['vout']: 107 | if out['scriptPubKey']['hex'] == self._scriptPubKey.hex(): 108 | self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0}) 109 | 110 | def sign_tx(self, tx, fixed_length=True): 111 | """Sign tx that has been created by MiniWallet in P2PK mode""" 112 | assert self._priv_key is not None 113 | (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) 114 | assert err is None 115 | # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): 116 | # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes) 117 | # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes 118 | der_sig = b'' 119 | while not len(der_sig) == 71: 120 | der_sig = self._priv_key.sign_ecdsa(sighash) 121 | if not fixed_length: 122 | break 123 | tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) 124 | 125 | def generate(self, num_blocks, **kwargs): 126 | """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" 127 | blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs) 128 | for b in blocks: 129 | block_info = self._test_node.getblock(blockhash=b, verbosity=2) 130 | cb_tx = block_info['tx'][0] 131 | self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']}) 132 | return blocks 133 | 134 | def get_descriptor(self): 135 | return descsum_create(f'raw({self._scriptPubKey.hex()})') 136 | 137 | def get_address(self): 138 | return self._address 139 | 140 | def get_utxo(self, *, txid: Optional[str]='', mark_as_spent=True): 141 | """ 142 | Returns a utxo and marks it as spent (pops it from the internal list) 143 | 144 | Args: 145 | txid: get the first utxo we find from a specific transaction 146 | """ 147 | index = -1 # by default the last utxo 148 | self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last 149 | if txid: 150 | utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos)) 151 | index = self._utxos.index(utxo) 152 | if mark_as_spent: 153 | return self._utxos.pop(index) 154 | else: 155 | return self._utxos[index] 156 | 157 | def send_self_transfer(self, **kwargs): 158 | """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" 159 | tx = self.create_self_transfer(**kwargs) 160 | self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex']) 161 | return tx 162 | 163 | def send_to(self, *, from_node, scriptPubKey, amount, fee=1000): 164 | """ 165 | Create and send a tx with an output to a given scriptPubKey/amount, 166 | plus a change output to our internal address. To keep things simple, a 167 | fixed fee given in Satoshi is used. 168 | 169 | Note that this method fails if there is no single internal utxo 170 | available that can cover the cost for the amount and the fixed fee 171 | (the utxo with the largest value is taken). 172 | 173 | Returns a tuple (txid, n) referring to the created external utxo outpoint. 174 | """ 175 | tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx'] 176 | assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee) 177 | tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet 178 | tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned 179 | txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) 180 | return txid, 1 181 | 182 | def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): 183 | """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" 184 | from_node = from_node or self._test_node 185 | utxo_to_spend = utxo_to_spend or self.get_utxo() 186 | if self._priv_key is None: 187 | vsize = Decimal(104) # anyone-can-spend 188 | else: 189 | vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) 190 | send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000))) 191 | assert send_value > 0 192 | 193 | tx = CTransaction() 194 | tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)] 195 | tx.vout = [CTxOut(send_value, self._scriptPubKey)] 196 | tx.nLockTime = locktime 197 | if not self._address: 198 | # raw script 199 | if self._priv_key is not None: 200 | # P2PK, need to sign 201 | self.sign_tx(tx) 202 | else: 203 | # anyone-can-spend 204 | tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size 205 | else: 206 | tx.wit.vtxinwit = [CTxInWitness()] 207 | tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] 208 | tx_hex = tx.serialize().hex() 209 | 210 | tx_info = from_node.testmempoolaccept([tx_hex])[0] 211 | assert_equal(mempool_valid, tx_info['allowed']) 212 | if mempool_valid: 213 | assert_equal(tx_info['vsize'], vsize) 214 | assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) 215 | return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx} 216 | 217 | def sendrawtransaction(self, *, from_node, tx_hex, **kwargs): 218 | txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs) 219 | self.scan_tx(from_node.decoderawtransaction(tx_hex)) 220 | return txid 221 | 222 | 223 | def getnewdestination(address_type='bech32'): 224 | """Generate a random destination of the specified type and return the 225 | corresponding public key, scriptPubKey and address. Supported types are 226 | 'legacy', 'p2sh-segwit' and 'bech32'. Can be used when a random 227 | destination is needed, but no compiled wallet is available (e.g. as 228 | replacement to the getnewaddress/getaddressinfo RPCs).""" 229 | key = ECKey() 230 | key.generate() 231 | pubkey = key.get_pubkey().get_bytes() 232 | if address_type == 'legacy': 233 | scriptpubkey = key_to_p2pkh_script(pubkey) 234 | address = key_to_p2pkh(pubkey) 235 | elif address_type == 'p2sh-segwit': 236 | scriptpubkey = key_to_p2sh_p2wpkh_script(pubkey) 237 | address = key_to_p2sh_p2wpkh(pubkey) 238 | elif address_type == 'bech32': 239 | scriptpubkey = key_to_p2wpkh_script(pubkey) 240 | address = key_to_p2wpkh(pubkey) 241 | # TODO: also support bech32m (need to generate x-only-pubkey) 242 | else: 243 | assert False 244 | return pubkey, scriptpubkey, address 245 | 246 | 247 | def address_to_scriptpubkey(address): 248 | """Converts a given address to the corresponding output script (scriptPubKey).""" 249 | payload, version = base58_to_byte(address) 250 | if version == 111: # testnet pubkey hash 251 | return keyhash_to_p2pkh_script(payload) 252 | elif version == 196: # testnet script hash 253 | return scripthash_to_p2sh_script(payload) 254 | # TODO: also support other address formats 255 | else: 256 | assert False 257 | 258 | 259 | def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): 260 | """Build a transaction that spends parent_txid.vout[n] and produces one output with 261 | amount = parent_value with a fee deducted. 262 | Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created). 263 | """ 264 | inputs = [{"txid": parent_txid, "vout": n}] 265 | my_value = parent_value - fee 266 | outputs = {address : my_value} 267 | rawtx = node.createrawtransaction(inputs, outputs) 268 | prevtxs = [{ 269 | "txid": parent_txid, 270 | "vout": n, 271 | "scriptPubKey": parent_locking_script, 272 | "amount": parent_value, 273 | }] if parent_locking_script else None 274 | signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=privkeys, prevtxs=prevtxs) 275 | assert signedtx["complete"] 276 | tx = tx_from_hex(signedtx["hex"]) 277 | return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex()) 278 | 279 | def create_child_with_parents(node, address, privkeys, parents_tx, values, locking_scripts, fee=DEFAULT_FEE): 280 | """Creates a transaction that spends the first output of each parent in parents_tx.""" 281 | num_parents = len(parents_tx) 282 | total_value = sum(values) 283 | inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx] 284 | outputs = {address : total_value - fee} 285 | rawtx_child = node.createrawtransaction(inputs, outputs) 286 | prevtxs = [] 287 | for i in range(num_parents): 288 | prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]}) 289 | signedtx_child = node.signrawtransactionwithkey(hexstring=rawtx_child, privkeys=privkeys, prevtxs=prevtxs) 290 | assert signedtx_child["complete"] 291 | return signedtx_child["hex"] 292 | 293 | def create_raw_chain(node, first_coin, address, privkeys, chain_length=25): 294 | """Helper function: create a "chain" of chain_length transactions. The nth transaction in the 295 | chain is a child of the n-1th transaction and parent of the n+1th transaction. 296 | """ 297 | parent_locking_script = None 298 | txid = first_coin["txid"] 299 | chain_hex = [] 300 | chain_txns = [] 301 | value = first_coin["amount"] 302 | 303 | for _ in range(chain_length): 304 | (tx, txhex, value, parent_locking_script) = make_chain(node, address, privkeys, txid, value, 0, parent_locking_script) 305 | txid = tx.rehash() 306 | chain_hex.append(txhex) 307 | chain_txns.append(tx) 308 | 309 | return (chain_hex, chain_txns) 310 | 311 | def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None): 312 | """Pad a transaction with extra outputs until it reaches a target weight (or higher). 313 | returns CTransaction object 314 | """ 315 | tx_heavy = deepcopy(tx) 316 | assert_greater_than_or_equal(target_weight, tx_heavy.get_weight()) 317 | while tx_heavy.get_weight() < target_weight: 318 | random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes 319 | for _ in range(512*2): 320 | random_spk += choice("0123456789ABCDEF") 321 | tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk))) 322 | # Re-sign the transaction 323 | if privkeys: 324 | signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs) 325 | return tx_from_hex(signed["hex"]) 326 | # OP_TRUE 327 | tx_heavy.wit.vtxinwit = [CTxInWitness()] 328 | tx_heavy.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] 329 | return tx_heavy 330 | -------------------------------------------------------------------------------- /miner_imports/test_framework/wallet_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2018-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | """Useful util functions for testing the wallet""" 6 | from collections import namedtuple 7 | 8 | from test_framework.address import ( 9 | byte_to_base58, 10 | key_to_p2pkh, 11 | key_to_p2sh_p2wpkh, 12 | key_to_p2wpkh, 13 | script_to_p2sh, 14 | script_to_p2sh_p2wsh, 15 | script_to_p2wsh, 16 | ) 17 | from test_framework.key import ECKey 18 | from test_framework.script_util import ( 19 | key_to_p2pkh_script, 20 | key_to_p2wpkh_script, 21 | keys_to_multisig_script, 22 | script_to_p2sh_script, 23 | script_to_p2wsh_script, 24 | ) 25 | 26 | Key = namedtuple('Key', ['privkey', 27 | 'pubkey', 28 | 'p2pkh_script', 29 | 'p2pkh_addr', 30 | 'p2wpkh_script', 31 | 'p2wpkh_addr', 32 | 'p2sh_p2wpkh_script', 33 | 'p2sh_p2wpkh_redeem_script', 34 | 'p2sh_p2wpkh_addr']) 35 | 36 | Multisig = namedtuple('Multisig', ['privkeys', 37 | 'pubkeys', 38 | 'p2sh_script', 39 | 'p2sh_addr', 40 | 'redeem_script', 41 | 'p2wsh_script', 42 | 'p2wsh_addr', 43 | 'p2sh_p2wsh_script', 44 | 'p2sh_p2wsh_addr']) 45 | 46 | def get_key(node): 47 | """Generate a fresh key on node 48 | 49 | Returns a named tuple of privkey, pubkey and all address and scripts.""" 50 | addr = node.getnewaddress() 51 | pubkey = node.getaddressinfo(addr)['pubkey'] 52 | return Key(privkey=node.dumpprivkey(addr), 53 | pubkey=pubkey, 54 | p2pkh_script=key_to_p2pkh_script(pubkey).hex(), 55 | p2pkh_addr=key_to_p2pkh(pubkey), 56 | p2wpkh_script=key_to_p2wpkh_script(pubkey).hex(), 57 | p2wpkh_addr=key_to_p2wpkh(pubkey), 58 | p2sh_p2wpkh_script=script_to_p2sh_script(key_to_p2wpkh_script(pubkey)).hex(), 59 | p2sh_p2wpkh_redeem_script=key_to_p2wpkh_script(pubkey).hex(), 60 | p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey)) 61 | 62 | def get_generate_key(): 63 | """Generate a fresh key 64 | 65 | Returns a named tuple of privkey, pubkey and all address and scripts.""" 66 | eckey = ECKey() 67 | eckey.generate() 68 | privkey = bytes_to_wif(eckey.get_bytes()) 69 | pubkey = eckey.get_pubkey().get_bytes().hex() 70 | return Key(privkey=privkey, 71 | pubkey=pubkey, 72 | p2pkh_script=key_to_p2pkh_script(pubkey).hex(), 73 | p2pkh_addr=key_to_p2pkh(pubkey), 74 | p2wpkh_script=key_to_p2wpkh_script(pubkey).hex(), 75 | p2wpkh_addr=key_to_p2wpkh(pubkey), 76 | p2sh_p2wpkh_script=script_to_p2sh_script(key_to_p2wpkh_script(pubkey)).hex(), 77 | p2sh_p2wpkh_redeem_script=key_to_p2wpkh_script(pubkey).hex(), 78 | p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey)) 79 | 80 | def get_multisig(node): 81 | """Generate a fresh 2-of-3 multisig on node 82 | 83 | Returns a named tuple of privkeys, pubkeys and all address and scripts.""" 84 | addrs = [] 85 | pubkeys = [] 86 | for _ in range(3): 87 | addr = node.getaddressinfo(node.getnewaddress()) 88 | addrs.append(addr['address']) 89 | pubkeys.append(addr['pubkey']) 90 | script_code = keys_to_multisig_script(pubkeys, k=2) 91 | witness_script = script_to_p2wsh_script(script_code) 92 | return Multisig(privkeys=[node.dumpprivkey(addr) for addr in addrs], 93 | pubkeys=pubkeys, 94 | p2sh_script=script_to_p2sh_script(script_code).hex(), 95 | p2sh_addr=script_to_p2sh(script_code), 96 | redeem_script=script_code.hex(), 97 | p2wsh_script=witness_script.hex(), 98 | p2wsh_addr=script_to_p2wsh(script_code), 99 | p2sh_p2wsh_script=script_to_p2sh_script(witness_script).hex(), 100 | p2sh_p2wsh_addr=script_to_p2sh_p2wsh(script_code)) 101 | 102 | def test_address(node, address, **kwargs): 103 | """Get address info for `address` and test whether the returned values are as expected.""" 104 | addr_info = node.getaddressinfo(address) 105 | for key, value in kwargs.items(): 106 | if value is None: 107 | if key in addr_info.keys(): 108 | raise AssertionError("key {} unexpectedly returned in getaddressinfo.".format(key)) 109 | elif addr_info[key] != value: 110 | raise AssertionError("key {} value {} did not match expected value {}".format(key, addr_info[key], value)) 111 | 112 | def bytes_to_wif(b, compressed=True): 113 | if compressed: 114 | b += b'\x01' 115 | return byte_to_base58(b, 239) 116 | 117 | def generate_wif_key(): 118 | # Makes a WIF privkey for imports 119 | k = ECKey() 120 | k.generate() 121 | return bytes_to_wif(k.get_bytes(), k.is_compressed) 122 | -------------------------------------------------------------------------------- /nginx/cashu.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name cashu.mutinynet.com; 3 | 4 | location / { 5 | add_header 'Access-Control-Allow-Origin' '*' always; 6 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 7 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Cotent-Length' always; 8 | 9 | proxy_pass http://127.0.0.1:3338; 10 | } 11 | 12 | listen 443 ssl; # managed by Certbot 13 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 14 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 15 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 16 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 17 | 18 | 19 | 20 | } 21 | server { 22 | if ($host = cashu.mutinynet.com) { 23 | return 301 https://$host$request_uri; 24 | } # managed by Certbot 25 | 26 | 27 | server_name cashu.mutinynet.com; 28 | listen 80; 29 | return 404; # managed by Certbot 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /nginx/faucet.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name faucet.mutinynet.com; 3 | 4 | location /api/bolt11 { 5 | add_header 'Access-Control-Allow-Origin' '*' always; 6 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 7 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Cotent-Length' always; 8 | 9 | proxy_pass http://127.0.0.1:3001; 10 | } 11 | 12 | location /auth/ { 13 | proxy_pass http://127.0.0.1:3001; 14 | } 15 | 16 | location /api/ { 17 | 18 | # rate limits for faucet POST 19 | limit_req zone=faucet_limit_zone burst=1; 20 | 21 | # Forward the original client IP 22 | proxy_set_header X-Real-IP $remote_addr; 23 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 24 | proxy_set_header Host $host; 25 | proxy_set_header X-Forwarded-Proto $scheme; 26 | 27 | # Other proxy settings 28 | proxy_set_header X-Forwarded-Host $host; 29 | proxy_set_header X-Forwarded-Port $server_port; 30 | 31 | proxy_pass http://127.0.0.1:3001; 32 | } 33 | 34 | 35 | location / { 36 | if ($request_method = 'OPTIONS') { 37 | add_header 'Access-Control-Allow-Origin' '*'; 38 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 39 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 40 | add_header 'Access-Control-Max-Age' 1728000; 41 | add_header 'Content-Type' 'text/plain; charset=utf-8'; 42 | add_header 'Content-Length' 0; 43 | return 204; 44 | } 45 | if ($request_method = 'GET') { 46 | add_header 'Access-Control-Allow-Origin' '*'; 47 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 48 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 49 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; 50 | } 51 | proxy_pass http://127.0.0.1:3000; 52 | } 53 | 54 | 55 | listen 443 ssl; # managed by Certbot 56 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 57 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 58 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 59 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 60 | 61 | 62 | } 63 | server { 64 | if ($host = faucet.mutinynet.com) { 65 | return 301 https://$host$request_uri; 66 | } # managed by Certbot 67 | 68 | 69 | server_name faucet.mutinynet.com; 70 | listen 80; 71 | return 404; # managed by Certbot 72 | 73 | } 74 | -------------------------------------------------------------------------------- /nginx/lnurl.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name lnurl.mutinynet.com; 3 | 4 | location / { 5 | add_header 'Access-Control-Allow-Origin' '*' always; 6 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 7 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Cotent-Length' always; 8 | 9 | proxy_pass http://127.0.0.1:3002; 10 | } 11 | 12 | listen 443 ssl; # managed by Certbot 13 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 14 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 15 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 16 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 17 | 18 | 19 | 20 | } 21 | server { 22 | if ($host = lnurl.mutinynet.com) { 23 | return 301 https://$host$request_uri; 24 | } # managed by Certbot 25 | 26 | 27 | server_name lnurl.mutinynet.com; 28 | listen 80; 29 | return 404; # managed by Certbot 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /nginx/mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name mutinynet.com; 3 | location /api/v1/ws { 4 | proxy_pass http://127.0.0.1:8999/; 5 | proxy_http_version 1.1; 6 | proxy_set_header Upgrade $http_upgrade; 7 | proxy_set_header Connection "Upgrade"; 8 | } 9 | 10 | location /api/v1/ { 11 | if ($request_method = 'OPTIONS') { 12 | add_header 'Access-Control-Allow-Origin' '*' always; 13 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 14 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 15 | add_header 'Access-Control-Max-Age' 1728000 always; 16 | add_header 'Content-Type' 'text/plain; charset=utf-8' always; 17 | add_header 'Content-Length' 0 always; 18 | return 204; 19 | } 20 | 21 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 22 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 23 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 24 | 25 | proxy_pass http://127.0.0.1:8999; 26 | } 27 | 28 | 29 | location /api/ { 30 | if ($request_method = 'OPTIONS') { 31 | add_header 'Access-Control-Allow-Origin' '*' always; 32 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 33 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 34 | add_header 'Access-Control-Max-Age' 1728000 always; 35 | add_header 'Content-Type' 'text/plain; charset=utf-8' always; 36 | add_header 'Content-Length' 0 always; 37 | return 204; 38 | } 39 | 40 | if ($request_method = 'GET') { 41 | add_header 'Access-Control-Allow-Origin' '*' always; 42 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 43 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 44 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 45 | } 46 | 47 | if ($request_method = 'POST') { 48 | add_header 'Access-Control-Allow-Origin' '*' always; 49 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 50 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 51 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 52 | } 53 | 54 | proxy_pass http://127.0.0.1:3003/; 55 | } 56 | 57 | # mainnet API 58 | location /ws { 59 | proxy_pass http://127.0.0.1:8999/; 60 | proxy_http_version 1.1; 61 | proxy_set_header Upgrade $http_upgrade; 62 | proxy_set_header Connection "Upgrade"; 63 | } 64 | location / { 65 | proxy_pass http://127.0.0.1:8080; 66 | } 67 | 68 | 69 | listen 443 ssl; # managed by Certbot 70 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 71 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 72 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 73 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 74 | 75 | } 76 | server { 77 | if ($host = mutinynet.com) { 78 | return 301 https://$host$request_uri; 79 | } # managed by Certbot 80 | 81 | 82 | server_name mutinynet.com; 83 | listen 80; 84 | return 404; # managed by Certbot 85 | } 86 | -------------------------------------------------------------------------------- /nginx/rgs.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name rgs.mutinynet.com; 3 | root /var/www/rgs; 4 | 5 | location / { 6 | if ($request_method = 'OPTIONS') { 7 | add_header 'Access-Control-Allow-Origin' '*' always; 8 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 9 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 10 | add_header 'Access-Control-Max-Age' 1728000 always; 11 | add_header 'Content-Type' 'text/plain; charset=utf-8' always; 12 | add_header 'Content-Length' 0 always; 13 | return 204; 14 | } 15 | if ($request_method = 'GET') { 16 | add_header 'Access-Control-Allow-Origin' '*' always; 17 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 18 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 19 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 20 | } 21 | if ($request_method = 'POST') { 22 | add_header 'Access-Control-Allow-Origin' '*' always; 23 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 24 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 25 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 26 | } 27 | 28 | index index.html; # Default files to serve 29 | } 30 | 31 | location /snapshot { 32 | try_files $uri $uri/ /res/symlinks/$1.bin; 33 | autoindex on; 34 | rewrite ^/snapshot/(\d+)$ /snapshot/$1.bin break; 35 | rewrite ^/snapshot/(\d+)\.bin$ /snapshot/$1 break; 36 | } 37 | 38 | 39 | listen 443 ssl; # managed by Certbot 40 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 41 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 42 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 43 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 44 | 45 | 46 | } 47 | server { 48 | if ($host = rgs.mutinynet.com) { 49 | return 301 https://$host$request_uri; 50 | } # managed by Certbot 51 | 52 | 53 | server_name rgs.mutinynet.com; 54 | listen 80; 55 | return 404; # managed by Certbot 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /nginx/rpc.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name rpc.mutinynet.com; 3 | 4 | location / { 5 | 6 | if ($request_method = 'OPTIONS') { 7 | add_header 'Access-Control-Allow-Origin' '*'; 8 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 9 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 10 | add_header 'Access-Control-Max-Age' 1728000; 11 | add_header 'Content-Type' 'text/plain; charset=utf-8'; 12 | add_header 'Content-Length' 0; 13 | return 204; 14 | } 15 | if ($request_method = 'GET') { 16 | add_header 'Access-Control-Allow-Origin' '*' always; 17 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; 18 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 19 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; 20 | } 21 | 22 | proxy_set_header X-Forwarded-Proto $scheme; 23 | proxy_pass https://127.0.0.1:8081; 24 | } 25 | 26 | 27 | listen 443 ssl; # managed by Certbot 28 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 29 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 30 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 31 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 32 | 33 | } 34 | server { 35 | if ($host = rpc.mutinynet.com) { 36 | return 301 https://$host$request_uri; 37 | } # managed by Certbot 38 | 39 | 40 | server_name rpc.mutinynet.com; 41 | listen 80; 42 | return 404; # managed by Certbot 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /nginx/www.mutinynet.com: -------------------------------------------------------------------------------- 1 | server { 2 | server_name www.mutinynet.com; 3 | location /api/v1/ws { 4 | proxy_pass http://127.0.0.1:8999/; 5 | proxy_http_version 1.1; 6 | proxy_set_header Upgrade $http_upgrade; 7 | proxy_set_header Connection "Upgrade"; 8 | } 9 | location /api/v1 { 10 | rewrite ^/api/v1(.*)$ /api$1 last; 11 | } 12 | location /api/ { 13 | if ($request_method = 'OPTIONS') { 14 | add_header 'Access-Control-Allow-Origin' '*'; 15 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 16 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 17 | add_header 'Access-Control-Max-Age' 1728000; 18 | add_header 'Content-Type' 'text/plain; charset=utf-8'; 19 | add_header 'Content-Length' 0; 20 | return 204; 21 | } 22 | if ($request_method = 'GET') { 23 | add_header 'Access-Control-Allow-Origin' '*'; 24 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 25 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 26 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; 27 | } 28 | if ($request_method = 'POST') { 29 | add_header 'Access-Control-Allow-Origin' '*'; 30 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 31 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 32 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; 33 | } 34 | proxy_pass http://127.0.0.1:3003/; 35 | } 36 | 37 | # mainnet API 38 | location /ws { 39 | proxy_pass http://127.0.0.1:8999/; 40 | proxy_http_version 1.1; 41 | proxy_set_header Upgrade $http_upgrade; 42 | proxy_set_header Connection "Upgrade"; 43 | } 44 | location / { 45 | proxy_pass http://127.0.0.1:8080; 46 | } 47 | 48 | 49 | listen 443 ssl; # managed by Certbot 50 | ssl_certificate /etc/letsencrypt/live/mutinynet.com/fullchain.pem; # managed by Certbot 51 | ssl_certificate_key /etc/letsencrypt/live/mutinynet.com/privkey.pem; # managed by Certbot 52 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 53 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 54 | 55 | } 56 | server { 57 | if ($host = www.mutinynet.com) { 58 | return 301 https://$host$request_uri; 59 | } # managed by Certbot 60 | 61 | 62 | server_name www.mutinynet.com; 63 | listen 80; 64 | return 404; # managed by Certbot 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /rpcauth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2015-2021 The Bitcoin Core developers 3 | # Distributed under the MIT software license, see the accompanying 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | 6 | from argparse import ArgumentParser 7 | from base64 import urlsafe_b64encode 8 | from getpass import getpass 9 | from os import urandom 10 | 11 | import hmac 12 | 13 | def generate_salt(size): 14 | """Create size byte hex salt""" 15 | return urandom(size).hex() 16 | 17 | def generate_password(): 18 | """Create 32 byte b64 password""" 19 | return urlsafe_b64encode(urandom(32)).decode('utf-8') 20 | 21 | def password_to_hmac(salt, password): 22 | m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') 23 | return m.hexdigest() 24 | 25 | def main(): 26 | parser = ArgumentParser(description='Create login credentials for a JSON-RPC user') 27 | parser.add_argument('username', help='the username for authentication') 28 | parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?') 29 | args = parser.parse_args() 30 | 31 | if not args.password: 32 | args.password = generate_password() 33 | elif args.password == '-': 34 | args.password = getpass() 35 | 36 | # Create 16 byte hex salt 37 | salt = generate_salt(16) 38 | password_hmac = password_to_hmac(salt, args.password) 39 | 40 | print('{0}:{1}${2}'.format(args.username, salt, password_hmac)) 41 | 42 | if __name__ == '__main__': 43 | main() -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # run bitcoind 4 | bitcoind --daemonwait 5 | sleep 5 6 | echo "get magic" 7 | magic=$(cat /root/.bitcoin/signet/debug.log | grep -m1 magic) 8 | magic=${magic:(-8)} 9 | echo $magic > /root/.bitcoin/MAGIC.txt 10 | 11 | # if in mining mode 12 | if [[ "$MINERENABLED" == "1" ]]; then 13 | mine.sh 14 | fi -------------------------------------------------------------------------------- /setup-signet.sh: -------------------------------------------------------------------------------- 1 | PRIVKEY=${PRIVKEY:-$(cat ~/.bitcoin/PRIVKEY.txt)} 2 | DATADIR=${DATADIR:-~/.bitcoin/} 3 | bitcoind -datadir=$DATADIR --daemonwait -persistmempool 4 | bitcoin-cli -datadir=$DATADIR -named createwallet wallet_name="custom_signet" load_on_startup=true descriptors=false 5 | 6 | #only used in case of mining node 7 | if [[ "$MINERENABLED" == "1" ]]; then 8 | bitcoin-cli -datadir=$DATADIR importprivkey $PRIVKEY 9 | ## for future with descriptor wallets, cannot seem to get it working yet 10 | # descinfo=$(bitcoin-cli getdescriptorinfo "wpkh(${PRIVKEY})") 11 | # checksum=$(echo "$descinfo" | jq .checksum | tr -d '"' | tr -d "\n") 12 | # desc='[{"desc":"wpkh('$PRIVKEY')#'$checksum'","timestamp":0,"internal":false}]' 13 | # bitcoin-cli -datadir=$DATADIR importdescriptors $desc 14 | fi --------------------------------------------------------------------------------