diff --git a/shared/block_height.py b/shared/block_height.py new file mode 100644 index 00000000..cc57e64b --- /dev/null +++ b/shared/block_height.py @@ -0,0 +1,9 @@ +# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# AUTO-generated. +# +# Updated: 2026-06-19 14:13:23 UTC + +BLOCK_HEIGHT = 932301 + +# EOF diff --git a/shared/chains.py b/shared/chains.py index 67b4060a..1531b740 100644 --- a/shared/chains.py +++ b/shared/chains.py @@ -8,6 +8,7 @@ from ubinascii import hexlify as b2a_hex from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2TR from public_constants import AF_P2SH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH from public_constants import AFC_PUBKEY, AFC_SEGWIT, AFC_BECH32, AFC_SCRIPT +from block_height import BLOCK_HEIGHT from serializations import hash160, ser_compact_size, disassemble from ucollections import namedtuple from opcodes import OP_RETURN, OP_1, OP_16 @@ -297,7 +298,7 @@ class BitcoinMain(ChainsBase): # see ctype = 'BTC' name = 'Bitcoin Mainnet' - ccc_min_block = 939464 # Mar 5/2026 + ccc_min_block = BLOCK_HEIGHT slip132 = { AF_CLASSIC: Slip132Version(0x0488B21E, 0x0488ADE4, 'x'), diff --git a/shared/manifest.py b/shared/manifest.py index a2cc842b..d8fa2841 100644 --- a/shared/manifest.py +++ b/shared/manifest.py @@ -6,6 +6,7 @@ freeze_as_mpy('', [ 'address_explorer.py', 'auth.py', 'backups.py', + 'block_height.py', 'callgate.py', 'ccc.py', 'chains.py', diff --git a/stm32/make_block_height.py b/stm32/make_block_height.py new file mode 100644 index 00000000..6dd75ca5 --- /dev/null +++ b/stm32/make_block_height.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# Capture current (mainnet) block height for SSSP/CCC features +# +import sys, time, datetime +import urllib.request + + +FILE_NAME = "../shared/block_height.py" + + +def _get_block_height(url): + with urllib.request.urlopen(url) as response: + height_data = response.read().decode().strip() + return int(height_data) + + +def get_block_height(url): + try: + return _get_block_height(url) + except: + time.sleep(2) + return _get_block_height(url) + + +def parse_block_height_file(): + with open(FILE_NAME, "r") as f: + for l in f.readlines(): + if l.startswith("BLOCK_HEIGHT ="): + return int(l.split("=")[-1].strip()) + + return None + + +def write_block_height_file(block_height): + now = datetime.datetime.now(datetime.timezone.utc) + with open(FILE_NAME, "wt") as f: + f.write('''\ +# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC. +# +# AUTO-generated. +# + +# As of %s UTC +BLOCK_HEIGHT = %d + +# EOF +''' % (now.strftime("%Y-%m-%d %H:%M:%S"), block_height)) + + +def main(): + current_height = None + for _ in range(2): + bh_a = get_block_height("https://mempool.space/api/blocks/tip/height") + bh_b = get_block_height("https://blockstream.info/api/blocks/tip/height") + if bh_a == bh_b: + current_height = bh_a + break + + time.sleep(5) + + if current_height is None: + raise RuntimeError("Could not get current block height") + + file_block_height = parse_block_height_file() + if file_block_height is None: + raise RuntimeError("Could not parse block height from file") + + if current_height > file_block_height: + write_block_height_file(current_height) + sys.exit(1) + else: + sys.exit(0) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/stm32/make_filetime.py b/stm32/make_filetime.py index f795dc8b..6ab9dfe0 100755 --- a/stm32/make_filetime.py +++ b/stm32/make_filetime.py @@ -5,14 +5,14 @@ # Capture build time and version number into a number used as the timestamp on # all created files for that Coldcard version. # -import os, sys, time, datetime +import os, sys, datetime out_fname, version = sys.argv[1:] assert out_fname.endswith('.c'), out_fname if os.path.exists(out_fname): - # to help deterministic builds, don't replace the file from git if verison # is right + # to help deterministic builds, don't replace the file from git if version # is right with open(out_fname, 'rt') as fd: if ('// version: %s\n' % version) in fd.read(): print("==> %s already version %s; not changing it" % (out_fname, version)) @@ -22,7 +22,7 @@ if os.path.exists(out_fname): today = datetime.date.today() value = ((today.year - 1980) << 25) | (today.month << 21) | (today.day << 16) -# only 2second resolution for times, so can only support minor verion up to x.x.5 and hard to see +# only 2second resolution for times, so can only support minor version up to x.x.5 and hard to see # anyway, let's omit ... worst case, use the date instead ver = ''.join(v for v in version if v in '0123456789.') # strip letter codes from end h, m, _ = [int(x) for x in ver.split('b')[0].split('.')] diff --git a/stm32/shared.mk b/stm32/shared.mk index 6bf29a3c..e0e03d2f 100644 --- a/stm32/shared.mk +++ b/stm32/shared.mk @@ -82,6 +82,18 @@ $(BOARD)/file_time.c: make_filetime.py *-Makefile shared.mk ./make_filetime.py $(BOARD)/file_time.c $(VERSION_STRING) cp $(BOARD)/file_time.c . + +.PHONY: block_height + +block_height: + @python3 make_block_height.py; \ + if [ $$? -eq 0 ]; then \ + echo "Block Height file already up-to-date."; \ + else \ + echo "Block Height file updated."; \ + git commit -m "update block height" ../shared/block_height.py; \ + fi + # Make a factory release: using key #1 # - when executed in a repro w/o the required key, it defaults to key zero # - and that's what happens inside the Docker build @@ -91,7 +103,7 @@ production.bin: firmware-signed.bin Makefile SUBMAKE = $(MAKE) -f $(PARENT_MKFILE) .PHONY: release -release: submods-match code-committed +release: submods-match code-committed block_height $(SUBMAKE) clean $(SUBMAKE) repro test -f built/production.bin @@ -118,7 +130,7 @@ rc1: rc2: RC2_TIMESTAMP = $(shell date "+%F_%H%M") rc2: RC2_FNAME = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-coldcard.dfu rc2: RC2_FNAME_FACT = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-factory.dfu -rc2: submods-match code-committed +rc2: submods-match code-committed block_height $(SUBMAKE) clean $(SUBMAKE) repro test -f built/production.bin