Skip to content

test: adapt the functional test framework for ReddCoin PoSV#401

Merged
reddink merged 55 commits into
reddcoin-project:developfrom
reddink:pr/test-framework
Jul 16, 2026
Merged

test: adapt the functional test framework for ReddCoin PoSV#401
reddink merged 55 commits into
reddcoin-project:developfrom
reddink:pr/test-framework

Conversation

@reddink

@reddink reddink commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Adapts the Bitcoin Core functional test framework so it can drive a ReddCoin
Proof-of-Stake-Velocity (PoSV) node. Upstream's harness assumes a pure
Proof-of-Work chain with instant generate() mining, no block signatures, no
transaction nTime, and Bitcoin network parameters — none of which hold for
ReddCoin. This PR teaches the framework about PoS block creation and signing,
mocktime-driven staking, ReddCoin's network constants and address formats, and
the wallet/cache setup PoS requires.

This is test-infrastructure only (no src/ changes) and is the foundation
the adapted functional tests depend on
— the per-test adaptations
(pr/test-adaptations) build on the helpers introduced here.

What's included

PoS block support in the harness (blocktools.py, messages.py, script.py)

  • PoS block creation and block-signature (vchBlockSig) support, including
    CBlock.solve() / is_valid() awareness of PoS blocks and coinstake handling.
  • nTime field support on the CTransaction test class and
    serialize_for_legacy_sighash() for ReddCoin's nTime sighash handling.
  • vchBlockSig in compact-block serialization; create_block() syncs the block
    timestamp to the coinstake and detects PoS vs BIP9 version bits.
  • scrypt support for ReddCoin PoW block mining; MiniWallet vsize/nTime fixes.

Mocktime-driven staking (test_framework.py, test_node.py, util.py)

  • Track mocktime in the framework; advance_time_for_pos() utility.
  • generate() with PoS retry logic and time advancement; sync_time() to keep
    mocktime aligned across nodes; sync_mempools advances mocktime for trickle
    relay.

ReddCoin network parameters & address formats (messages.py, address.py, etc.)

  • MAGIC_BYTES, P2P protocol version 80016, MAX_MONEY, block-reward
    constants, COINBASE_MATURITY = 60, regtest genesis block time.
  • ReddCoin bech32 / P2SH version bytes, ADDRESS_BCRT1_UNSPENDABLE checksum,
    and bitcoin.confreddcoin.conf throughout.

Wallet & cache setup (test_framework.py, wallet.py)

  • Cache generation reworked to per-node funding with a PoS-enabled cache node;
    fund nodes 4–5 for 6-node tests; only import cache privkeys when using the cache.
  • Descriptor-wallet support in init_wallet(); BIP39 params on the
    createwallet wrapper; __truediv__ on RPCOverloadWrapper for wallet RPC
    paths.

Robustness fixes (authproxy.py, test_node.py)

  • Recover the RPC connection from CannotSendRequest / ResponseNotReady under
    load; clean up stale _base_rpc on node stop to prevent auth failures.
  • Narrow the best-effort PoS mocktime except clauses in init_wallet() and
    TestNode.generate() to JSONRPCException and log the skip, instead of a
    silent except Exception: pass (Codacy Try/Except/Pass).
  • Default the createwallet wrapper's passphrase to None (tested with
    is not None) instead of '', clearing a Bandit hardcoded-password false
    positive and matching the method's other optional params.

Test runner (test_runner.py)

  • Register the PoS test entries, use the ReddCoin symbol for the temp directory,
    skip feature_signet, disable feature_blockfilterindex_prune, and swap
    feature_bip68_sequence_pos for the adapted feature_bip68_sequence.

Testing

Framework-level change; exercised by the full functional suite. The harness
changes here are what allow the PoS-adapted tests to run at all — see
pr/test-adaptations, which registers and adapts the individual test cases on
top of this branch.

Compatibility / scope

  • No src/ changes — test tooling only; node behaviour is untouched.
  • Foundation branch: pr/test-adaptations depends on the helpers added here and
    should merge after this (they overlap only in test_runner.py, a one-hunk
    registration-list merge).

Notes

55 commits, 22 files, test/ only. Rebased cleanly onto develop.

reddink and others added 30 commits July 15, 2026 16:58
…work

- Update test_node.py PRIV_KEYS with ReddCoin regtest addresses
- Replace hardcoded Bitcoin testnet address in rpc_signrawtransaction.py
- All private keys remain the same, only addresses updated for ReddCoin compatibility
…t the test

Replace all references to bitcoin.conf with reddcoin.conf in utility functions
Update test_framework.py to use reddcoin binaries and conf

- Replace bitcoind/bitcoin-cli with reddcoind/reddcoin-cli
- Update TMPDIR_PREFIX to reddcoin_func_test_
- Update environment variables to REDDCOIND/REDDCOINCLI
- Update help text references from bitcoin to reddcoin
Updates test framework address encoding/decoding for Reddcoin:

- Change P2PKH versions: mainnet=61 (R prefix), regtest=122 (r prefix)
- Change bech32 HRP: mainnet="rdd", regtest="rcrt"
- Update unspendable addresses for regtest
- Update P2WSH OP_TRUE address for regtest
- Update unit tests to use Reddcoin versions
Modifies TestNode.generate() to advance mock time by 60 seconds before
each block generation. This is required for Reddcoin PoS where coins
need age to be eligible for staking (nStakeMinAge = 10s on regtest).

Generates blocks one-at-a-time with time advancement between each,
ensuring sufficient coinage for probabilistic PoS validation.
ReddCoin requires a loaded wallet for PoS block generation after nLastPowHeight (89 in regtest).
The cache initialization generates 199 blocks, requiring PoS support for blocks 90+.

Changes:
- Remove -disablewallet from cache node initialization
- Create default wallet after node starts for PoS block generation
Updates test framework to support Reddcoin's Proof-of-Stake consensus:

- Add txindex requirement for all test nodes (required by CreateCoinStake)
- Create legacy wallets to support importprivkey for staking
- Import test framework keys into cache node and test nodes
- Generate cache blocks one-at-a-time with 60s spacing for coin age
- Set mock time 300s past tip to ensure coins have sufficient age
- Set wallet RPC context for generatetoaddress support
- Remove IBD block 200 generation (replaced by PoS-aware tests)

Cache now successfully generates 110 PoS blocks (blocks 90-199).
Adds helper function to advance mock time for PoS coin age requirements.

The function advances mock time on test nodes to age UTXOs, which is
required for Reddcoin PoS staking (nStakeMinAge = 10s on regtest).
Default advancement is 60 seconds to ensure sufficient coinage.
Updated test framework and functional tests to use ReddCoin-specific
naming conventions instead of Bitcoin references.

Changes:
- Log messages: "bitcoind" → "reddcoind"
- Error messages: "bitcoind" → "reddcoind"
- Binary names: bitcoin-cli/bitcoin-tx → reddcoin-cli/reddcoin-tx
- Database error messages reference correct binary name

Files updated:
- feature_asmap.py: Updated log messages for asmap tests
- feature_filelock.py: Updated file locking error messages
- rpc_signer.py: Updated signer error message
- test_node.py: Updated node startup log message
- get_previous_releases.py: Updated binary file names

This ensures test output and error messages accurately reflect
ReddCoin instead of Bitcoin.
- Add mocktime tracking to TestNode class for sync_time() support
- Update set_node_times() to track mocktime on each node
- Fix advance_time_for_pos() to use tracked mocktime when set,
  allowing multiple calls without generating blocks
- Update generate() to use max of current mocktime and block time
  to avoid resetting advanced time
Fixes init_wallet to respect test's descriptor wallet preference instead
of forcing legacy wallets for all tests. PoS-specific features (importprivkey)
are now conditional on legacy wallet usage.

Changes:
- Use self.options.descriptors if set, otherwise default to False
- Only import private keys when using legacy wallets
- Prevents breaking tests that explicitly require descriptor wallets

This allows both PoS tests (needing legacy) and HD wallet tests
(needing descriptors) to coexist.
ReddCoin transactions include an nTime field (absent in Bitcoin) that is
critical for PoS functionality. Added full support to the Python test
framework's CTransaction class.

Changes:
- Add nTime to __slots__ and initialize to current time (required non-zero)
- Serialize/deserialize nTime after nLockTime when nVersion > 1
- Set CURRENT_VERSION = 2 to match ReddCoin standard transactions
- Support in both witness and non-witness serialization paths

Transaction format: nVersion | [witness] | vin | vout | nLockTime | nTime (if nVersion > 1)

Rules:
- nTime only present when nVersion > POW_TX_VERSION (1)
- nTime included in txid/wtxid but NOT in signature hash (BIP143/341)
- All test transactions now default to nVersion=2 with nTime field

This unblocks CSV/SegWit/Taproot functional testing which create
transactions manually, ensuring the test framework matches ReddCoin
Core's transaction format exactly.
Updates the test framework MAX_MONEY constant from Bitcoin's 21 million
to ReddCoin's maximum supply of 92,233,720,368 RDD. This ensures test
validation logic matches the actual consensus rules.
Updated test framework P2P protocol version constants to match ReddCoin's
current protocol version 80016 (from Bitcoin's 70016).

Changes:
- MIN_P2P_VERSION_SUPPORTED: 60001 → 80016
- P2P_VERSION: 70016 → 80016

This ensures test framework P2P connections are compatible with ReddCoin
nodes and prevents protocol version mismatch errors during testing.
Update P2P message magic bytes to match ReddCoin chain parameters
for mainnet, testnet3, regtest, and signet.
Update the unspendable P2WSH address constant to use the correct
bech32 checksum for ReddCoin's 'rcrt' HRP (human readable part).

The previous checksum 'ajapww' was invalid for the rcrt prefix.
Regenerated using encode_segwit_address('rcrt', 0, bytes(32)).
Add vchBlockSig field to CBlock class for Proof-of-Stake blocks.
PoS blocks (nVersion > 2) include a block signature that must be
serialized and deserialized correctly in functional tests.

Changes:
- Add POW_BLOCK_VERSION constant (version 2)
- Add vchBlockSig slot to CBlock
- Serialize/deserialize block signature for PoS blocks
- Update __repr__ to show signature when present
Update CBlock validation to handle Proof-of-Stake blocks:
- solve(): Skip nonce-based mining for PoS blocks (version > POW_BLOCK_VERSION)
- is_valid(): Skip hash difficulty check for PoS blocks validated by signature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Update create_block() and create_coinbase() to properly handle
Proof-of-Stake blocks in the test framework.

Changes to create_block():
- Detect PoS blocks by checking version > POW_BLOCK_VERSION (2)
- Validate that PoS block templates contain valid coinstake
- Create empty coinbase for PoS blocks (matching miner.cpp)
- Add transactions from template including coinstake

Changes to create_coinbase():
- Add outputScriptPubKey parameter for explicit control
- Support empty vout[0] for PoS blocks (0 value, empty script)
- Maintain PoW behavior for normal blocks

New function sign_pos_block():
- Sign PoS block hash with ECDSA
- Store DER-encoded signature in vchBlockSig

Update script_BIP34_coinbase_height():
- Use OP_0 instead of OP_1 to match ReddCoin's miner.cpp
ReddCoin's compact blocks include block signature in serialization:
- Add vchBlockSig field to P2PHeaderAndShortIDs class
- Deserialize/serialize vchBlockSig after nonce, before shortids
- Matches ReddCoin's CBlockHeaderAndShortTxIDs format in src/blockencodings.h
Add PoS block signature (vchBlockSig) to HeaderAndShortIDs class
for BIP 152 compact block serialization.

ReddCoin PoS blocks include a vchBlockSig field that must be preserved
when creating compact blocks. Without this, compact block deserialization
fails because the signature is missing from the reconstructed block.

Changes:
- Add vchBlockSig to HeaderAndShortIDs.__slots__
- Initialize vchBlockSig in __init__ and copy from P2P messages
- Include vchBlockSig in to_p2p() serialization
- Extract vchBlockSig from block in initialize_from_block()

This ensures compact blocks can be properly created and reconstructed
for PoS blocks with signatures.
Fix PoS block timestamp mismatch in test framework's create_block().

When getblocktemplate creates a PoS block, it sets:
- coinstake.nTime = T (part of PoS proof, cannot be modified)
- block.nTime = T + δ (curtime from template)

This causes "bad-cs-time, coinstake timestamp violation" errors because
PoS validation requires: block.nTime == coinstake.nTime

The coinstake timestamp is authoritative (part of the signed PoS proof),
so we must sync the block header timestamp to match it, not vice versa.

This fix ensures all blocks created from templates have matching
timestamps and pass PoS validation.
ReddCoin transactions include an nTime field, but the legacy signature
hash (pre-BIP143) must NOT include nTime. This matches the C++
CTransactionSignatureSerializer::Serialize() implementation.

Changes:
- messages.py: Add serialize_for_legacy_sighash() method to CTransaction
  that serializes only nVersion, vin, vout, nLockTime (excludes nTime)
- script.py: Update LegacySignatureHash() to use the new method

This fix is required for p2p_segwit.py pre-activation tests where
legacy signing is used before SegWit (BIP143) activation.
Adjust vsize from Bitcoin values (96/168) to Reddcoin values (100/172)
to account for the 4-byte nTime field serialized when nVersion > 1.
- Add scrypt hash computation for PoW validation
- Add scrypt256 field to CBlockHeader for PoW target comparison
- Update solve() to use scrypt256 instead of sha256 for mining
- Fallback to SHA256 if scrypt module not available
- Generate blocks while keeping all nodes' mocktime synchronized
- Sync mocktime across all nodes every N blocks (default: 10)
- Retry up to max_tries times on "no valid coinstake found" errors
- Advance time by 60 seconds between retry attempts
- Add sync_time() to set all nodes to the maximum tracked mocktime
- Prevents P2P disconnections caused by time drift between nodes
- Call sync_time() from sync_all() to automatically sync time
- Required for ReddCoin PoS block timestamp and coin age calculations
In PoS phase (after block 89 on regtest), use node.generate() and
send UTXOs to MiniWallet address since generatetodescriptor requires
PoW mining. This allows MiniWallet-based tests to work in both PoW
and PoS phases.
reddink added 23 commits July 15, 2026 16:59
ReddCoin regtest uses nCoinbaseMaturity = 60 (see src/chainparams.cpp),
not 100 like Bitcoin. Update the test framework constant to match.
Add COINBASE_REWARD (545000000) for regtest premine blocks 1-10 and
NORMAL_BLOCK_REWARD (300000) for blocks 11+. These values are used
by wallet tests to verify expected balances.
Skip importing deterministic coinbase privkeys when setup_clean_chain
is True, since there are no cache blocks to spend in that case.
PoS block generation is probabilistic and may fail with "no valid
coinstake found". Add automatic retry with time advancement to make
tests more reliable. This eliminates the need for manual retry loops
in individual tests.

- Add pos_retry_attempts parameter (default: 10)
- Advance mocktime by 60s between retry attempts
- Only retry on "no valid coinstake found" errors
Change PoS block detection from version-based to coinstake-based.
BIP9 version bits signaling can set block version to 536870912
(0x20000000) even for PoW blocks, which incorrectly triggered PoS
handling when using version > POW_BLOCK_VERSION (2).

Now detect PoS by checking if the first transaction in the template
is actually a coinstake (has inputs, first input not null, 2+ outputs,
first output empty) rather than relying on the block version number.
Use importdescriptors() RPC for descriptor wallets instead of
importprivkey() which only works with legacy wallets.

Key insight: getdescriptorinfo() returns the public key version of
the descriptor, but we need to keep the private key for import.
Solution: use the checksum from getdescriptorinfo() but keep the
original descriptor with the WIF private key intact.
Replace shared key import approach with explicit funding transactions.
Previously all 3 cache keys were imported into every node, causing
multiple nodes to see the same coinbase rewards. Now:

- Generate all cache blocks to PRIV_KEYS[0] only
- Fund nodes 1-3 via chained transactions from a single mature UTXO
- Each node gets 70 UTXOs of 2.5M RDD for staking
- Raise ancestor/descendant chain limits during cache generation
- Remove redundant cache key imports from init_wallet()

This gives each node isolated coin ownership while preserving
sufficient staking balance across all nodes.
get_wallet_rpc() uses `self.rpc / wallet_path` to construct wallet
RPC URLs. When self.rpc is an RPCOverloadWrapper (set during PoS
wallet context), the / operator fails because the wrapper lacks
__truediv__. Delegate to the underlying AuthServiceProxy.
ReddCoin uses P2SH version byte 5 for both mainnet and regtest
(Bitcoin uses 196 for regtest). Update scripthash_to_p2sh() and
correct the unspendable address descriptor checksum for rcrt1q.
Update TIME_GENESIS_BLOCK from Bitcoin's 1296688602 to ReddCoin's
1642570147. Tests that set mocktime relative to genesis (e.g.
rpc_blockchain.py) fail with time-too-new errors using the wrong value.
CTransaction defaults nTime to time.time() (real system time), but
PoS blocks use mocktime. A tx with a future nTime gets rejected.
Use the node's tip block time instead.
Store _base_rpc before overwriting n.rpc with wallet-scoped proxy in
init_wallet(). Use __dict__ access to avoid TestNode.__getattr__
intercepting attribute lookups (which creates bogus RPC proxies).

get_wallet_rpc() now uses _base_rpc as the base URL, preventing
double wallet paths like /wallet//wallet/wmulti.

Also track n.mocktime alongside n.setmocktime() so framework code
that reads mocktime directly stays in sync.
When a node stops and restarts, _base_rpc retained the old RPC
connection with stale cookie credentials. The second init_wallet()
call would skip updating _base_rpc (due to the conditional guard),
causing get_wallet_rpc() to use the stale connection and trigger
"incorrect password" authentication errors.

Fix by clearing _base_rpc in is_node_stopped() and unconditionally
capturing n.rpc in init_wallet(), since at that point n.rpc is
always the fresh base connection.
Add time_sync_callback to TestNode so generate() syncs mocktime to all
nodes after every block, preventing drift that breaks P2P relay. Also
increase PoS retry spacing from 60s to 300s to widen the coinstake
search window on retries.
Extend cache Phase 2 to fund nodes 4-5 from a second premine UTXO,
giving them staking-ready coins for tests like wallet_address_types.py.
When mocktime is set, frozen time prevents outbound trickle relay
Poisson timers from firing, blocking inv message delivery. Advance
mocktime by 5 seconds each poll iteration so transactions propagate.
When nodes are stopped mid-test (e.g., wallet_basic stops node 3),
sync_time() would crash trying to call setmocktime on nodes without
RPC connections. Filter by node.running and node.rpc_connected.
Extend RPCOverloadWrapper.createwallet() with wallet_type, mnemonic,
mnemonic_passphrase, language, and entropy_bits parameters. Use named
args in CLI mode when BIP39 params are set to avoid null string issues
with sparse positional parameters.
Prune mode is incompatible with -txindex required by PoS.
…equence

The original feature_bip68_sequence.py has been adapted for PoS.
Remove the temporary _pos variant and restore the original filename.
Signet chain type is not supported in ReddCoin. The node crashes
on startup with -signet. Comment out until signet support is added.
AuthServiceProxy shares a single HTTP connection across all of a node's
method calls. If a prior call's response read is interrupted under heavy
system load (a slow/timed-out or truncated response) and the caller
swallows the error, the shared connection is left in the _CS_REQ_SENT
state and never reset. The next RPC on that node then fails with
http.client.CannotSendRequest, even though the node itself is healthy and
still serving requests. This surfaces intermittently in long PoS
functional tests (e.g. feature_taproot.py) on loaded machines.

Handle CannotSendRequest/ResponseNotReady in _request the same way as
BrokenPipeError/ConnectionResetError: close the stale connection and
retry once. CannotSendRequest is raised before any bytes of the retried
request are sent, so the retry cannot double-execute the RPC.
@codacy-production

codacy-production Bot commented Jul 15, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

The PoS mocktime-advance blocks in init_wallet() and TestNode.generate()
caught a bare `except Exception: pass`, which silently swallowed every
error (Codacy Try/Except/Pass). Narrow both to the JSONRPCException these
RPC calls actually raise when there is no tip to read yet, and log the
skip at debug instead of passing silently, so genuine errors propagate.
@reddink
reddink force-pushed the pr/test-framework branch from 5ec7e39 to 1c4bb25 Compare July 16, 2026 02:34
The RPCOverloadWrapper.createwallet wrapper declared passphrase='' and
tested `if passphrase != ''`, which Codacy/Bandit flags as a possible
hardcoded password (B107/B105). Default it to None and test `is not None`,
matching how every other optional param in the method is already handled
(disable_private_keys, blank, avoid_reuse, ...). Behaviour is unchanged:
an unset passphrase still results in an unencrypted wallet.
@reddink
reddink merged commit 1542f16 into reddcoin-project:develop Jul 16, 2026
1 check passed
@reddink
reddink deleted the pr/test-framework branch July 16, 2026 05:55
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.

1 participant