Skip to content

Commit 5d96c2e

Browse files
committed
Merge bitcoin/bitcoin#31907: qa: clarify and document one assumeutxo test case with malleated snapshot
e5ff4e4 qa: use a clearer and documented amount error in malleated snapshot (Antoine Poinsot) b34fdb5 test: introduce output amount (de)compression routines (Sebastian Falbesoner) a7911ed test: introduce VARINT (de)serialization routines (Sebastian Falbesoner) Pull request description: The `feature_assumeutxo.py` functional test checks various errors with malleated snapshots. Some of these cases are brittle or use confusing and undocumented values. Fix one of those by using a clear, documented and forward-compatible value. I ran across those when working on an unrelated changeset which affected the snapshot. It took me a while to understand where the seemingly magic byte string was coming from, so i figured it was worth proposing this patch on its own for the sake of making the test more maintainable. See commit messages for details. ACKs for top commit: janb84: re ACK [e5ff4e4](bitcoin/bitcoin@e5ff4e4) theStack: ACK e5ff4e4 fjahr: Code review ACK e5ff4e4 i-am-yuvi: tACK e5ff4e4 Tree-SHA512: 60f022b7176836ce05e8f287b436329d7ca6460f3fcd95f78cd24e07a95a7d4d9cbbb68a117916a113fe451732b09a012d300fe860ff33d61823eca797ceddaf
2 parents 57d611e + e5ff4e4 commit 5d96c2e

File tree

4 files changed

+110
-2
lines changed

4 files changed

+110
-2
lines changed

test/functional/feature_assumeutxo.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
create_block,
1717
create_coinbase
1818
)
19+
from test_framework.compressor import (
20+
compress_amount,
21+
)
1922
from test_framework.messages import (
2023
CBlockHeader,
2124
from_hex,
2225
msg_headers,
23-
tx_from_hex
26+
tx_from_hex,
27+
ser_varint,
28+
MAX_MONEY,
2429
)
2530
from test_framework.p2p import (
2631
P2PInterface,
@@ -139,7 +144,14 @@ def expected_error(msg):
139144
[b"\x81", 34, "3da966ba9826fb6d2604260e01607b55ba44e1a5de298606b08704bc62570ea8", None], # wrong coin code VARINT
140145
[b"\x80", 34, "091e893b3ccb4334378709578025356c8bcb0a623f37c7c4e493133c988648e5", None], # another wrong coin code
141146
[b"\x84\x58", 34, None, "Bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0
142-
[b"\xCA\xD2\x8F\x5A", 39, None, "Bad snapshot data after deserializing 0 coins - bad tx out value"], # Amount exceeds MAX_MONEY
147+
[
148+
# compressed txout value + scriptpubkey
149+
ser_varint(compress_amount(MAX_MONEY + 1)) + ser_varint(0),
150+
# txid + coins per txid + vout + coin height
151+
32 + 1 + 1 + 2,
152+
None,
153+
"Bad snapshot data after deserializing 0 coins - bad tx out value"
154+
], # Amount exceeds MAX_MONEY
143155
]
144156

145157
for content, offset, wrong_hash, custom_message in cases:

test/functional/feature_framework_unit_tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"address",
1919
"crypto.bip324_cipher",
2020
"blocktools",
21+
"compressor",
2122
"crypto.chacha20",
2223
"crypto.ellswift",
2324
"key",
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2025-present 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+
"""Routines for compressing transaction output amounts and scripts."""
6+
import unittest
7+
8+
from .messages import COIN
9+
10+
11+
def compress_amount(n):
12+
if n == 0:
13+
return 0
14+
e = 0
15+
while ((n % 10) == 0) and (e < 9):
16+
n //= 10
17+
e += 1
18+
if e < 9:
19+
d = n % 10
20+
assert (d >= 1 and d <= 9)
21+
n //= 10
22+
return 1 + (n*9 + d - 1)*10 + e
23+
else:
24+
return 1 + (n - 1)*10 + 9
25+
26+
27+
def decompress_amount(x):
28+
if x == 0:
29+
return 0
30+
x -= 1
31+
e = x % 10
32+
x //= 10
33+
n = 0
34+
if e < 9:
35+
d = (x % 9) + 1
36+
x //= 9
37+
n = x * 10 + d
38+
else:
39+
n = x + 1
40+
while e > 0:
41+
n *= 10
42+
e -= 1
43+
return n
44+
45+
46+
class TestFrameworkCompressor(unittest.TestCase):
47+
def test_amount_compress_decompress(self):
48+
def check_amount(amount, expected_compressed):
49+
self.assertEqual(compress_amount(amount), expected_compressed)
50+
self.assertEqual(decompress_amount(expected_compressed), amount)
51+
52+
# test cases from compress_tests.cpp:compress_amounts
53+
check_amount(0, 0x0)
54+
check_amount(1, 0x1)
55+
check_amount(1000000, 0x7)
56+
check_amount(COIN, 0x9)
57+
check_amount(50*COIN, 0x32)
58+
check_amount(21000000*COIN, 0x1406f40)

test/functional/test_framework/messages.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,26 @@ def deser_compact_size(f):
120120
return nit
121121

122122

123+
def ser_varint(l):
124+
r = b""
125+
while True:
126+
r = bytes([(l & 0x7f) | (0x80 if len(r) > 0 else 0x00)]) + r
127+
if l <= 0x7f:
128+
return r
129+
l = (l >> 7) - 1
130+
131+
132+
def deser_varint(f):
133+
n = 0
134+
while True:
135+
dat = f.read(1)[0]
136+
n = (n << 7) | (dat & 0x7f)
137+
if (dat & 0x80) > 0:
138+
n += 1
139+
else:
140+
return n
141+
142+
123143
def deser_string(f):
124144
nit = deser_compact_size(f)
125145
return f.read(nit)
@@ -1913,3 +1933,20 @@ def check_addrv2(ip, net):
19131933
check_addrv2("2bqghnldu6mcug4pikzprwhtjjnsyederctvci6klcwzepnjd46ikjyd.onion", CAddress.NET_TORV3)
19141934
check_addrv2("255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p", CAddress.NET_I2P)
19151935
check_addrv2("fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa", CAddress.NET_CJDNS)
1936+
1937+
def test_varint_encode_decode(self):
1938+
def check_varint(num, expected_encoding_hex):
1939+
expected_encoding = bytes.fromhex(expected_encoding_hex)
1940+
self.assertEqual(ser_varint(num), expected_encoding)
1941+
self.assertEqual(deser_varint(BytesIO(expected_encoding)), num)
1942+
1943+
# test cases from serialize_tests.cpp:varint_bitpatterns
1944+
check_varint(0, "00")
1945+
check_varint(0x7f, "7f")
1946+
check_varint(0x80, "8000")
1947+
check_varint(0x1234, "a334")
1948+
check_varint(0xffff, "82fe7f")
1949+
check_varint(0x123456, "c7e756")
1950+
check_varint(0x80123456, "86ffc7e756")
1951+
check_varint(0xffffffff, "8efefefe7f")
1952+
check_varint(0xffffffffffffffff, "80fefefefefefefefe7f")

0 commit comments

Comments
 (0)