Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test suite with continuous integration #32

Merged
merged 5 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/scripts/install-bitcoind.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

set -xeu

DIRNAME="bitcoin-${BITCOIN_CORE_VERSION}"
FILENAME="${DIRNAME}-x86_64-linux-gnu.tar.gz"

cd "${HOME}"
wget -q "https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_CORE_VERSION}/${FILENAME}"
tar -xf "${FILENAME}"
sudo mv "${DIRNAME}"/bin/* "/usr/local/bin"
rm -rf "${FILENAME}" "${DIRNAME}"
13 changes: 13 additions & 0 deletions .github/scripts/install-cln.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh

set -xeu

DIRNAME="usr"
FILENAME="clightning-v${CORE_LIGHTNING_VERSION}-Ubuntu-24.04.tar.xz"

cd "${HOME}"
wget -q "https://github.com/ElementsProject/lightning/releases/download/v${CORE_LIGHTNING_VERSION}/${FILENAME}"
tar -xf "${FILENAME}"
sudo cp -r "${DIRNAME}"/bin /usr/local
sudo cp -r "${DIRNAME}"/libexec /usr/local/libexec
rm -rf "${FILENAME}" "${DIRNAME}"
43 changes: 43 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: continuous integration

on: push

env:
BITCOIN_CORE_VERSION: '27.2'
CORE_LIGHTNING_VERSION: '24.08.2'
GO_VERSION: '1.22'
PYTHON_VERSION: '3.13'

jobs:
isolation-tests:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- run: make trustedcoin
- run: go test -v

integration-tests:
runs-on: ubuntu-24.04
env:
VENV_PATH: venv
VALGRIND: 0
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: make trustedcoin
- run: .github/scripts/install-bitcoind.sh
- run: .github/scripts/install-cln.sh
- run: |
python -m venv ${{ env.VENV_PATH }}
echo "${{ env.VENV_PATH }}/bin" >> $GITHUB_PATH
source ${{ env.VENV_PATH }}/bin/activate
pip install -r requirements.txt
- run: pytest
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist/*
__pycache__/
dist/
trustedcoin
venv/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## The `trustedcoin` plugin

[![continuous integration](https://github.com/nbd-wtf/trustedcoin/actions/workflows/test.yml/badge.svg)](https://github.com/nbd-wtf/trustedcoin/actions/workflows/test.yml)

A plugin that uses block explorers (`blockstream.info`, `mempool.space`, `mempool.emzy.de`, `blockchair.com`, `blockchain.info` -- [suggest others](https://github.com/fiatjaf/trustedcoin/issues)) as backends instead of your own Bitcoin node.

This isn't what you should be doing, but sometimes you may need it.
Expand Down
14 changes: 13 additions & 1 deletion estimatefees.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ type FeeRate struct {
FeeRate int `json:"feerate"`
}

func getFeeRates() (*EstimatedFees, error) {
func getFeeRates(network string) (*EstimatedFees, error) {
if network == "regtest" {
return &EstimatedFees{
FeeRateFloor: 1000,
FeeRates: []FeeRate{
{Blocks: 2, FeeRate: 1000},
{Blocks: 6, FeeRate: 1000},
{Blocks: 12, FeeRate: 1000},
{Blocks: 100, FeeRate: 1000},
},
}, nil
}

// try bitcoind first
if bitcoind != nil {
in2, err2 := bitcoind.EstimateSmartFee(2, &btcjson.EstimateModeConservative)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/fiatjaf/lightningd-gjson-rpc v1.6.2
github.com/fiatjaf/lightningd-gjson-rpc v1.6.3
)

require (
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,9 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c=
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
github.com/fiatjaf/lightningd-gjson-rpc v1.6.2 h1:QlnPE3piGCAd8qElWIPuVbDYq4E1WTspgwP01q3olrI=
github.com/fiatjaf/lightningd-gjson-rpc v1.6.2/go.mod h1:DqVHlrgk0q0J08nbPBCwDVuB7vzPohRnrzuGZ0ct0fg=
github.com/fiatjaf/lightningd-gjson-rpc v1.6.3 h1:HcIF8YRIz06HmYak/NCx93SLveZ3gybUpwg5hHSgG10=
github.com/fiatjaf/lightningd-gjson-rpc v1.6.3/go.mod h1:DqVHlrgk0q0J08nbPBCwDVuB7vzPohRnrzuGZ0ct0fg=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
31 changes: 31 additions & 0 deletions integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pyln.client import RpcError
from pyln.testing.fixtures import *

def test_bcli(node_factory, bitcoind, chainparams):
"""
Based on the test_bcli from Core Lightning
"""
node = node_factory.get_node(opts={
"disable-plugin": "bcli",
"plugin": os.path.join(os.getcwd(), 'trustedcoin'),
})

# We cant stop it dynamically
with pytest.raises(RpcError):
node.rpc.plugin_stop("bcli")

# Failure case of feerate is tested in test_misc.py
estimates = node.rpc.call("estimatefees")
assert 'feerate_floor' in estimates
assert [f['blocks'] for f in estimates['feerates']] == [2, 6, 12, 100]

resp = node.rpc.call("getchaininfo", {"last_height": 0})
assert resp["chain"] == chainparams['name']
for field in ["headercount", "blockcount", "ibd"]:
assert field in resp

# We shouldn't get upset if we ask for an unknown-yet block
resp = node.rpc.call("getrawblockbyheight", {"height": 500})
assert resp["blockhash"] is resp["block"] is None
resp = node.rpc.call("getrawblockbyheight", {"height": 50})
assert resp["blockhash"] is not None and resp["blockhash"] is not None
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func main() {
{Name: "bitcoin-rpcport", Type: "string", Description: "Port to bitcoind RPC (optional).", Default: ""},
{Name: "bitcoin-rpcuser", Type: "string", Description: "Username to bitcoind RPC (optional).", Default: ""},
{Name: "bitcoin-rpcpassword", Type: "string", Description: "Password to bitcoind RPC (optional).", Default: ""},
{Name: "bitcoin-datadir", Type: "string", Description: "-datadir arg for bitcoin-cli. For compatibility with bcli, not actually used.", Default: ""},
},
RPCMethods: []plugin.RPCMethod{
{
Expand Down Expand Up @@ -131,7 +132,7 @@ func main() {
Description: "Get the Bitcoin feerate in sat/kilo-vbyte.",
LongDescription: "",
Handler: func(p *plugin.Plugin, params plugin.Params) (resp any, errCode int, err error) {
estfees, err := getFeeRates()
estfees, err := getFeeRates(p.Network)
if err != nil {
p.Logf("estimatefees error: %s", err.Error())
estfees = &EstimatedFees{}
Expand Down
69 changes: 69 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main_test

import (
"bufio"
"io"
"os/exec"
"strings"
"testing"
)

const executable = "./trustedcoin"

const getManifestRequest = `{"jsonrpc":"2.0","id":"getmanifest","method":"getmanifest","params":{}}`
const getManifestExpectedResponse = `{"jsonrpc":"2.0","id":"getmanifest","result":{"options":[{"name":"bitcoin-rpcconnect","type":"string","default":"","description":"Hostname (IP) to bitcoind RPC (optional)."},{"name":"bitcoin-rpcport","type":"string","default":"","description":"Port to bitcoind RPC (optional)."},{"name":"bitcoin-rpcuser","type":"string","default":"","description":"Username to bitcoind RPC (optional)."},{"name":"bitcoin-rpcpassword","type":"string","default":"","description":"Password to bitcoind RPC (optional)."},{"name":"bitcoin-datadir","type":"string","default":"","description":"-datadir arg for bitcoin-cli. For compatibility with bcli, not actually used."}],"rpcmethods":[{"name":"getrawblockbyheight","usage":"height","description":"Get the bitcoin block at a given height","long_description":""},{"name":"getchaininfo","usage":"","description":"Get the chain id, the header count, the block count and whether this is IBD.","long_description":""},{"name":"estimatefees","usage":"","description":"Get the Bitcoin feerate in sat/kilo-vbyte.","long_description":""},{"name":"sendrawtransaction","usage":"tx","description":"Send a raw transaction to the Bitcoin network.","long_description":""},{"name":"getutxout","usage":"txid vout","description":"Get informations about an output, identified by a {txid} an a {vout}","long_description":""}],"subscriptions":[],"hooks":[],"featurebits":{"features":"","channel":"","init":"","invoice":""},"dynamic":false,"notifications":[]}}`

const initRequest = `{"jsonrpc":"2.0","id":"init","method":"init","params":{"options":{},"configuration":{"network":"bitcoin","lightning-dir":"/tmp","rpc-file":"foo"}}}`
const initExpectedResponse = `{"jsonrpc":"2.0","id":"init"}`

const shutdownNotification = `{"jsonrpc":"2.0","method":"shutdown","params":{}}`

func TestInitAndShutdown(t *testing.T) {
cmd, stdin, stdout, stderr := start(t)
stop(t, cmd, stdin, stdout, stderr)
}

func start(t *testing.T) (*exec.Cmd, io.WriteCloser, io.ReadCloser, io.ReadCloser) {
cmd := exec.Command(executable)
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()

err := cmd.Start()
if err != nil {
t.Fatalf("expected trustedcoin to start, got %v", err)
}

_, _ = io.WriteString(stdin, getManifestRequest)
if response := readline(stdout); response != getManifestExpectedResponse {
t.Fatalf("unexpected manifest response: %s", response)
}

_, _ = io.WriteString(stdin, initRequest)
if response := readline(stdout); response != initExpectedResponse {
t.Fatalf("unexpected init response: %s", response)
}

if response := readline(stderr); !strings.Contains(response, "initialized plugin") {
t.Fatalf("unexpected output in stderr: %s", response)
}

return cmd, stdin, stdout, stderr
}

func stop(t *testing.T, cmd *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) {
_, _ = io.WriteString(stdin, shutdownNotification)
_ = stdin.Close()
_ = stdout.Close()
_ = stderr.Close()

if err := cmd.Wait(); err != nil {
t.Fatalf("expected process to exit cleanly, got %v", err)
}
}

func readline(r io.Reader) string {
line, _ := bufio.NewReader(r).ReadString('\n')

return strings.TrimSuffix(line, "\n")
}
12 changes: 12 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# build updated requirements.txt with:
# $ python -m venv venv
# $ source venv/bin/activate
# $ pip install pip-tools
# $ pip-compile --strip-extras requirements.in
# load current requirements.txt in a virtualenv with:
# $ python -m venv venv
# $ source venv/bin/activate
# $ pip install -r requirements.txt

pyln.client
pyln.testing
102 changes: 102 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --strip-extras requirements.in
#
asn1crypto==1.5.1
# via coincurve
attrs==24.2.0
# via
# jsonschema
# referencing
base58==2.1.1
# via pyln-proto
bitarray==2.9.3
# via bitstring
bitstring==4.2.3
# via pyln-proto
blinker==1.9.0
# via flask
certifi==2024.8.30
# via requests
cffi==1.17.1
# via
# coincurve
# cryptography
charset-normalizer==3.4.0
# via requests
cheroot==10.0.0
# via pyln-testing
click==8.1.7
# via flask
coincurve==20.0.0
# via pyln-proto
cryptography==42.0.8
# via pyln-proto
ephemeral-port-reserve==1.1.4
# via pyln-testing
flask==2.3.3
# via pyln-testing
idna==3.10
# via requests
iniconfig==2.0.0
# via pytest
itsdangerous==2.2.0
# via flask
jaraco-functools==4.1.0
# via cheroot
jinja2==3.1.4
# via flask
jsonschema==4.23.0
# via pyln-testing
jsonschema-specifications==2024.10.1
# via jsonschema
markupsafe==3.0.2
# via
# jinja2
# werkzeug
more-itertools==10.5.0
# via
# cheroot
# jaraco-functools
packaging==24.2
# via pytest
pluggy==1.5.0
# via pytest
psutil==5.9.8
# via pyln-testing
psycopg2-binary==2.9.10
# via pyln-testing
pycparser==2.22
# via cffi
pyln-bolt7==1.0.246
# via pyln-client
pyln-client==24.8.2
# via
# -r requirements.in
# pyln-testing
pyln-proto==24.8.2
# via pyln-client
pyln-testing==24.8.2
# via -r requirements.in
pysocks==1.7.1
# via pyln-proto
pytest==7.4.4
# via pyln-testing
python-bitcoinlib==0.11.2
# via pyln-testing
referencing==0.35.1
# via
# jsonschema
# jsonschema-specifications
requests==2.32.3
# via pyln-testing
rpds-py==0.21.0
# via
# jsonschema
# referencing
urllib3==2.2.3
# via requests
werkzeug==3.1.3
# via flask