diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..23cc6e7 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,58 @@ +name: Tests + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + test-python: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package + run: pip install -e . + + - name: Run tests + run: | + cd tests + python run_tests.py + + test-micropython: + name: MicroPython (Unix port) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install MicroPython Unix port + run: | + sudo apt-get update + sudo apt-get install -y build-essential libffi-dev pkg-config + git clone --depth 1 https://github.com/micropython/micropython.git /tmp/micropython + cd /tmp/micropython/ports/unix + make submodules + make + + - name: Run tests on MicroPython + run: | + cd tests + /tmp/micropython/ports/unix/build-standard/micropython run_tests.py + continue-on-error: true # MicroPython may lack some deps diff --git a/src/embit/networks.py b/src/embit/networks.py index 6f1a541..dc853a4 100644 --- a/src/embit/networks.py +++ b/src/embit/networks.py @@ -1,5 +1,20 @@ from .misc import const + +def get_network(name): + """ + Get network by name with testnet4 fallback. + Bitcoin Core 28.0+ uses 'testnet4' as the network name, + which has the same address parameters as testnet3. + """ + if name in NETWORKS: + return NETWORKS[name] + # testnet4 uses same parameters as testnet3 + if name == "testnet4": + return NETWORKS["test"] + return None + + NETWORKS = { "main": { "name": "Mainnet", @@ -73,4 +88,24 @@ "Zpub": b"\x02\x57\x54\x83", "bip32": const(1), }, + # testnet4: Bitcoin Core 28.0+ replacement for testnet3 + # Uses identical address parameters (bech32 "tb", same xpub/xprv versions) + "testnet4": { + "name": "Testnet4", + "wif": b"\xEF", + "p2pkh": b"\x6F", + "p2sh": b"\xC4", + "bech32": "tb", + "xprv": b"\x04\x35\x83\x94", + "xpub": b"\x04\x35\x87\xcf", + "yprv": b"\x04\x4a\x4e\x28", + "zprv": b"\x04\x5f\x18\xbc", + "Yprv": b"\x02\x42\x85\xb5", + "Zprv": b"\x02\x57\x50\x48", + "ypub": b"\x04\x4a\x52\x62", + "zpub": b"\x04\x5f\x1c\xf6", + "Ypub": b"\x02\x42\x89\xef", + "Zpub": b"\x02\x57\x54\x83", + "bip32": const(1), + }, } diff --git a/tests/tests/test_networks.py b/tests/tests/test_networks.py new file mode 100644 index 0000000..acc522a --- /dev/null +++ b/tests/tests/test_networks.py @@ -0,0 +1,65 @@ +"""Tests for network definitions and get_network function.""" + +from embit import networks +from unittest import TestCase + + +class TestNetworks(TestCase): + """Test Bitcoin network definitions.""" + + def test_all_networks_present(self): + """All expected Bitcoin networks should be defined.""" + expected = ["main", "test", "regtest", "signet", "testnet4"] + for name in expected: + self.assertIn(name, networks.NETWORKS) + + def test_testnet4_parameters(self): + """testnet4 should have same parameters as testnet3.""" + test = networks.NETWORKS["test"] + testnet4 = networks.NETWORKS["testnet4"] + + # Same address parameters + self.assertEqual(testnet4["bech32"], test["bech32"]) + self.assertEqual(testnet4["bech32"], "tb") + self.assertEqual(testnet4["p2pkh"], test["p2pkh"]) + self.assertEqual(testnet4["p2sh"], test["p2sh"]) + self.assertEqual(testnet4["wif"], test["wif"]) + + # Same xpub/xprv versions + self.assertEqual(testnet4["xpub"], test["xpub"]) + self.assertEqual(testnet4["xprv"], test["xprv"]) + self.assertEqual(testnet4["zpub"], test["zpub"]) + self.assertEqual(testnet4["zprv"], test["zprv"]) + + # Same coin type + self.assertEqual(testnet4["bip32"], test["bip32"]) + + def test_get_network_direct(self): + """get_network should return network by name.""" + main = networks.get_network("main") + self.assertEqual(main["name"], "Mainnet") + self.assertEqual(main["bech32"], "bc") + + test = networks.get_network("test") + self.assertEqual(test["name"], "Testnet") + self.assertEqual(test["bech32"], "tb") + + def test_get_network_testnet4(self): + """get_network should handle testnet4.""" + testnet4 = networks.get_network("testnet4") + self.assertIsNotNone(testnet4) + self.assertEqual(testnet4["name"], "Testnet4") + self.assertEqual(testnet4["bech32"], "tb") + + def test_get_network_unknown(self): + """get_network should return None for unknown networks.""" + result = networks.get_network("unknown_network") + self.assertIsNone(result) + + def test_mainnet_bech32(self): + """Mainnet should use 'bc' prefix.""" + self.assertEqual(networks.NETWORKS["main"]["bech32"], "bc") + + def test_regtest_bech32(self): + """Regtest should use 'bcrt' prefix.""" + self.assertEqual(networks.NETWORKS["regtest"]["bech32"], "bcrt")