Skip to content

fix: correct varint boundary off-by-one in SatoshisPerKilobyte fee model - #190

Open
ALiberalVoluntarist wants to merge 4 commits into
masterfrom
fix/varint-boundary-off-by-one
Open

fix: correct varint boundary off-by-one in SatoshisPerKilobyte fee model#190
ALiberalVoluntarist wants to merge 4 commits into
masterfrom
fix/varint-boundary-off-by-one

Conversation

@ALiberalVoluntarist

Copy link
Copy Markdown
Collaborator

Summary

  • get_varint_size in SatoshisPerKilobyte.compute_fee used > 253 / > 2**16 / > 2**32, underestimating varint size by 2–4 bytes when a script length landed exactly on a Bitcoin varint boundary (253, 65536, 4294967296). Fixed to > 0xFC / > 0xFFFF / > 0xFFFFFFFF, aligning with the canonical encoder in binary.py and the SV Node / Teranode / Go SDK implementations.
  • Fixed pre-existing test failures in fee_models_test_coverage.py (wrong constructor kwarg rate=value=, and int passed instead of Transaction to compute_fee).
  • Added parametrized regression tests verifying varint size consistency against unsigned_to_varint at all boundaries.

Cross-SDK status

SDK Boundary Status
SV Node (serialize.h) < 253 / <= uint16_max / <= uint32_max ✅ Correct
Teranode (util/varint.go) < 0xfd / <= 0xffff / <= 0xffffffff ✅ Correct
Go SDK (util/varint.go) < 253 / < 65536 / < 4294967296 ✅ Correct
py-sdk binary.py (encoder) <= 0xFC / <= 0xFFFF / <= 0xFFFFFFFF ✅ Correct
py-sdk SatoshisPerKilobyte > 253 / > 2**16 / > 2**32 ❌ → ✅ This PR
TS SDK SatoshisPerKilobyte.ts > 253 / > 2**16 / > 2**32 ❌ Same bug (ts-stack monorepo, unfixed as of 2026-07-27)

Test plan

  • pytest tests/bsv/fee_model_test_coverage.py -v — all 15 tests pass including 4 new boundary regression tests
  • pytest tests/bsv/fee_models_test_coverage.py -v — all 7 tests pass (previously 4 failed)
  • Verify no regressions in full test suite

🤖 Generated with Claude Code

ken16310 and others added 3 commits July 27, 2026 14:23
get_varint_size used `> 253` / `> 2**16` / `> 2**32` which under-
estimated by 2–4 bytes when a script length landed exactly on a varint
boundary (253, 65536, 4294967296). Aligned to `> 0xFC` / `> 0xFFFF` /
`> 0xFFFFFFFF`, matching the canonical encoder in binary.py and the
SV Node / Teranode / Go SDK implementations.

Also fixed pre-existing test failures in fee_models_test_coverage.py
(wrong constructor kwarg `rate=` → `value=`, and int passed instead of
Transaction to compute_fee).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ruff 0.16.0 enables PLR0917 by default, which flags 41 existing
functions. This mirrors PLR0913 (too-many-arguments) already ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ALiberalVoluntarist
ALiberalVoluntarist force-pushed the fix/varint-boundary-off-by-one branch from 8d0c439 to b17e8f9 Compare July 27, 2026 07:11
@github-actions

Copy link
Copy Markdown

Test Coverage Report

📊 Coverage: 80.7%

View the full coverage report in the build artifacts.

1 similar comment
@github-actions

Copy link
Copy Markdown

Test Coverage Report

📊 Coverage: 80.7%

View the full coverage report in the build artifacts.

- C extension tx_to_bytes/tx_preimages/tx_preimage_otda: replace silent
  uint32 truncation (PyArg_ParseTuple "I") with explicit parse_uint32()
  that raises OverflowError for out-of-range version/locktime values
- C extension: reject non-bytes script types with TypeError instead of
  silently treating them as empty; validate source_txid is exactly 64
  hex characters; check for missing dict keys with descriptive errors
- Python side: introduce txid_to_bytes_le() in transaction_input.py
  with regex validation; replace all bytes.fromhex(*.source_txid)[::-1]
  call sites (serialize, preimage, BEEF) with the validated helper
- Transaction.serialize(): add type(i) is TransactionInput check so
  subclassed inputs/outputs fall back to Python path, preserving
  serialize() overrides regardless of C extension availability
- Add 190-test edge case suite covering varint boundaries, large NFT
  scripts (up to 20MB), all 12 sighash combinations, error rejection
  on both C and Python paths, and subclass fallback verification
- Add Hypothesis differential tests verifying C/Python equivalence
  for serialize + BIP143/OTDA preimage with random transactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants