From 189b5f47809d2e7a930400b9ff2c91fcb091b836 Mon Sep 17 00:00:00 2001 From: BlackcoinDev Date: Wed, 22 Jul 2026 15:00:21 +0200 Subject: [PATCH 1/2] altcoin (blackcoin): upgrade to segwit add tests - SegWit has been activated on mainnet since block 5805000. - Changed DeserializerBlackcoin to DeserializerBlackcoinSegWit. - Add correct minfee value for mainnet. - add block and transaction tests. - Already running in production. --- src/electrumx/lib/coins.py | 8 +- src/electrumx/lib/tx.py | 80 +++++++++++++++---- tests/blocks/blackcoin_mainnet_5962634.json | 26 ++++++ ...lackcoin_mainnet_001_v2_segwit_p2wpkh.json | 37 +++++++++ ...blackcoin_mainnet_002_v2_legacy_p2pkh.json | 33 ++++++++ 5 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 tests/blocks/blackcoin_mainnet_5962634.json create mode 100644 tests/transactions/blackcoin_mainnet_001_v2_segwit_p2wpkh.json create mode 100644 tests/transactions/blackcoin_mainnet_002_v2_legacy_p2pkh.json diff --git a/src/electrumx/lib/coins.py b/src/electrumx/lib/coins.py index 46702b23b..0d199aab9 100644 --- a/src/electrumx/lib/coins.py +++ b/src/electrumx/lib/coins.py @@ -1593,15 +1593,15 @@ class Blackcoin(ScryptMixin, Coin): P2SH_VERBYTES = (bytes.fromhex("55"),) GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d' 'f2c183bf232f263d0ba5b101911e4563') - DESERIALIZER = lib_tx.DeserializerBlackcoin + DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit DAEMON = daemon.FakeEstimateFeeDaemon TX_COUNT = 10802426 TX_COUNT_HEIGHT = 3431329 TX_PER_BLOCK = 3 RPC_PORT = 15715 REORG_LIMIT = 500 - ESTIMATE_FEE = 0.0001 - RELAY_FEE = 0.0001 + ESTIMATE_FEE = 0.001 + RELAY_FEE = 0.001 PEERS = [ 'electrum1.blackcoin.nl t10001 s10002', 'electrum2.blackcoin.nl t20001 s20002', @@ -1617,7 +1617,7 @@ class BlackcoinTestnet(Blackcoin): P2SH_VERBYTES = (bytes.fromhex("C4"),) GENESIS_HASH = ('0000724595fb3b9609d441cbfb957761' '5c292abf07d996d3edabc48de843642d') - DESERIALIZER = lib_tx.DeserializerBlackcoin + DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit DAEMON = daemon.FakeEstimateFeeDaemon TX_COUNT = 1401229 TX_COUNT_HEIGHT = 698611 diff --git a/src/electrumx/lib/tx.py b/src/electrumx/lib/tx.py index 51c7536e3..d6ff90f99 100644 --- a/src/electrumx/lib/tx.py +++ b/src/electrumx/lib/tx.py @@ -851,18 +851,73 @@ def blake2s(data): return blake2s_hash.digest() -class DeserializerBlackcoin(Deserializer): +class DeserializerBlackcoinSegWit(DeserializerSegWit): + '''Deserializer for Blackcoin transactions. + * V1 Includes a 4-byte `nTime` field: + Returns: TxTime | TxTimeSegWit + * V2 Standard Bitcoin format (no `nTime` field): + Returns: Tx | TxSegWit + ''' BLACKCOIN_TX_VERSION = 2 def _get_version(self): + '''Peek at the transaction version without advancing the cursor.''' result, = unpack_le_int32_from(self.binary, self.cursor) return result - def read_tx(self): + def _read_tx_parts(self): orig_start = self.cursor - start = self.cursor version = self._get_version() + if version < self.BLACKCOIN_TX_VERSION: + # V1: 4-byte nTime shifts SegWit marker to offset 8. + if (self.cursor + 9 < self._binary_length and + self.binary[self.cursor + 8] == 0 and + self.binary[self.cursor + 9] != 0): + # V1 SegWit: version + time + marker + flag + ... + version = self._read_le_int32() + time = self._read_le_uint32() + start = self.cursor + orig_ser = self.binary[orig_start:start] + + marker = self._read_byte() + flag = self._read_byte() + + start = self.cursor + inputs = self._read_inputs() + outputs = self._read_outputs() + orig_ser += self.binary[start:self.cursor] + + witness_start = self.cursor + witness = self._read_witness(len(inputs)) + witness_size = self.cursor - witness_start + 2 # +2 for marker/flag + + start = self.cursor + locktime = self._read_le_uint32() + orig_ser += self.binary[start:self.cursor] + + base_size = self.cursor - orig_start - witness_size + weight = 4 * base_size + witness_size + vsize = weight // 4 + (weight % 4 > 0) + + txid = self.TX_HASH_FN(orig_ser) + wtxid = self.TX_HASH_FN(self.binary[orig_start:self.cursor]) + + tx = TxTimeSegWit( + version=version, + time=time, + marker=marker, + flag=flag, + inputs=inputs, + outputs=outputs, + witness=witness, + locktime=locktime, + txid_rev=txid, + wtxid_rev=wtxid, + ) + return tx, vsize + + # V1 legacy: no SegWit. tx = TxTime( version=self._read_le_int32(), time=self._read_le_uint32(), @@ -872,17 +927,14 @@ def read_tx(self): txid_rev=None, wtxid_rev=None, ) - else: - tx = Tx( - version=self._read_le_int32(), - inputs=self._read_inputs(), - outputs=self._read_outputs(), - locktime=self._read_le_uint32(), - txid_rev=None, - wtxid_rev=None, - ) - tx.txid_rev = tx.wtxid_rev = self.TX_HASH_FN(self.binary[orig_start:self.cursor]) - return tx + tx.txid_rev = tx.wtxid_rev = self.TX_HASH_FN(self.binary[orig_start:self.cursor]) + return tx, self.cursor - orig_start + + # V2: Standard Bitcoin format (no nTime). Delegate to base class. + return super()._read_tx_parts() + + def read_tx(self): + return self._read_tx_parts()[0] class DeserializerReddcoin(Deserializer): diff --git a/tests/blocks/blackcoin_mainnet_5962634.json b/tests/blocks/blackcoin_mainnet_5962634.json new file mode 100644 index 000000000..70b18f3de --- /dev/null +++ b/tests/blocks/blackcoin_mainnet_5962634.json @@ -0,0 +1,26 @@ +{ + "hash": "76a039c7574e10b08ce189163a1cc5e886db59fb4a40d22acc487fe4369b0977", + "confirmations": 1, + "size": 1185, + "height": 5962634, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "bc25140a8c75f2112c2560c96420a8e805760e5b31dc08ea5d4c2bb4f1e1290c", + "tx": [ + "a7a15e6b2ba911fb7d2c53f6edb52d5f93d780804a69bd0b6a3de8cfd6026926", + "295ea4a96850daf5d3722d30638cc2073021c93cdb80d4ff885a8983505c8c37", + "964fa09dd6b1359654847c8990fb1bd7a71f22383d7462e34d8acb320f166906", + "b4fec47cb3ecd63699244b279f5bda4ed3188ed59be4a28ddd13c442499d7158" + ], + "time": 1784719536, + "mediantime": 1784719536, + "nonce": 0, + "bits": "1a13a7fd", + "difficulty": 853519.8255535484, + "chainwork": "00000000000000000000000000000000000000000000051c364381948fd1c6cd", + "previousblockhash": "f44376a0d2fc9926eef5864685280a16850cdd2fa1f3e2838d7d14c66f53f9e6", + "flags": "proof-of-stake", + "modifier": "bc3c073b02ae1d195ae5e1aaae075cffb3bb4495a7a0f65ad44a73273c690145", + "signature": "304402202d4ee0c814a643770eab28dd78821cc08eea300fa004268a3ccb3309c1398e6402200f1d440da54a025caa16c9a2ae74dcceb192f4df914feb57c859671677702218", + "block": "00000020e6f9536fc6147d8d83e2f3a12fdd0c85160a28854686f5ee2699fcd2a07643f40c29e1f1b42b4c5dea08dc315b0e7605e8a82064c960252c11f2758c0a1425bcb0a8606afda7131a0000000004020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff06038afb5a0101ffffffff020000000000000000000000000000000000266a24aa21a9edf4789774a41c7fd39f27d3ff460e1ecad8ed86818d0d8bc2f0750f6934a7f0bb01200000000000000000000000000000000000000000000000000000000000000000000000000200000001c43fc05fe3f86d4c0bac7d0ad7de9e8d5ca3146a4c3499b0af6ddf6e46a85474010000004847304402206ee85b7c24a7a3ca4ebbb14f42ce32c01c1d78327950abbe53573101ce33d2cb0220279290999b927304c184fb2fe656485df0c65a8fc5d4574dc7fd56b1ced294ec01ffffffff03000000000000000000d4352eb111000000232103f612408b6c4bb37d491b05b565302f3ebe3f8c6cf4fd1977106daad54d77a20aacc0e1e400000000001976a914a20cf4062ecbf7358b871e6062b550aa2fb92ec788ac0000000002000000012f3fd4f94f9d0ec4cec2c3218b29257426872da284a7507e33af932a68bf8c51000000006a473044022014c90a2d5d4084f42a6f6acff358e4ce6d03ad8b411cfb4d55e01f9c06f55c2e022075356554f58ee48ba9d1c9a1d826051f882fde1effbff59b65c5b4eb655b731e0121036eabb22623e17ceaa9791500eca643e8e78e683c9540e9e05cf3ad1eb012793efdffffff024427ef47170000001976a914085f283018f571e673c38efe7a648493ce3b499088ac0000000000000000576a4c5451515350524f4f4651515032001901000000000000190076a914085f283018f571e673c38efe7a648493ce3b499088ac220060207dffe6c2f4bf3b25b966a333aa0249767a3661665efd839d7a66305d9c19eeeb00000000020000000001013717284ba41def76cccf5ac0959367c22ee0adef186f073fe9f0d70edb28d9090000000000feffffff0108c7052a01000000160014945d7a8e8655c6a3eb0433608f6f6a114e6c88040247304402203f37ba980c61eb9190f9c33ff6b0c04afa0a2e5337881418f21b2f94584ebb52022033ccac2bdff1e3934a4eba125a8cd81928370b155e62b1af03a7b6ca334cb6d3012103af816cdb5bed82c74894f7bef9e5cdb3b9476d478d5c4277b1542e3ab6272fd589fb5a0046304402202d4ee0c814a643770eab28dd78821cc08eea300fa004268a3ccb3309c1398e6402200f1d440da54a025caa16c9a2ae74dcceb192f4df914feb57c859671677702218" +} diff --git a/tests/transactions/blackcoin_mainnet_001_v2_segwit_p2wpkh.json b/tests/transactions/blackcoin_mainnet_001_v2_segwit_p2wpkh.json new file mode 100644 index 000000000..9a42fac5a --- /dev/null +++ b/tests/transactions/blackcoin_mainnet_001_v2_segwit_p2wpkh.json @@ -0,0 +1,37 @@ +{ + "txid": "6c3b7453da544de05e718532abf36d752d03e64ec005eae4dfb3822697f3b321", + "hash": "36667cf3dae0e0b3d18b1f3229270b562d80c0ca7c42f59de94d0590144062be", + "version": 2, + "size": 194, + "vsize": 113, + "weight": 449, + "locktime": 5962633, + "vin": [ + { + "txid": "09d928db0ed7f0e93f076f18efade02ec2679395c05acfcc76ef1da44b281737", + "vout": 0, + "scriptSig": { + "asm": "", + "hex": "" + }, + "txinwitness": [ + "304402203f37ba980c61eb9190f9c33ff6b0c04afa0a2e5337881418f21b2f94584ebb52022033ccac2bdff1e3934a4eba125a8cd81928370b155e62b1af03a7b6ca334cb6d301", + "03af816cdb5bed82c74894f7bef9e5cdb3b9476d478d5c4277b1542e3ab6272fd5" + ], + "sequence": 4294967294 + } + ], + "vout": [ + { + "value": 4999989000, + "n": 0, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 945d7a8e8655c6a3eb0433608f6f6a114e6c8804 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914945d7a8e8655c6a3eb0433608f6f6a114e6c880488ac", + "address": "BHyZc85obh8oLAsj4hXkAdsaWAGkYYx9GT", + "type": "pubkeyhash" + } + } + ], + "hex": "020000000001013717284ba41def76cccf5ac0959367c22ee0adef186f073fe9f0d70edb28d9090000000000feffffff0108c7052a010000001976a914945d7a8e8655c6a3eb0433608f6f6a114e6c880488ac0247304402203f37ba980c61eb9190f9c33ff6b0c04afa0a2e5337881418f21b2f94584ebb52022033ccac2bdff1e3934a4eba125a8cd81928370b155e62b1af03a7b6ca334cb6d3012103af816cdb5bed82c74894f7bef9e5cdb3b9476d478d5c4277b1542e3ab6272fd589fb5a00" +} diff --git a/tests/transactions/blackcoin_mainnet_002_v2_legacy_p2pkh.json b/tests/transactions/blackcoin_mainnet_002_v2_legacy_p2pkh.json new file mode 100644 index 000000000..7b2d20566 --- /dev/null +++ b/tests/transactions/blackcoin_mainnet_002_v2_legacy_p2pkh.json @@ -0,0 +1,33 @@ +{ + "txid": "4730aea4301091d4073a07b7eb28091c9f0dc445a3601e6b714d68a8af0da1d4", + "hash": "4730aea4301091d4073a07b7eb28091c9f0dc445a3601e6b714d68a8af0da1d4", + "version": 2, + "size": 191, + "vsize": 191, + "weight": 764, + "locktime": 0, + "vin": [ + { + "txid": "9b951c2b08142a8122f916bfec2fbe634e578faef1e89ae991b83303df8bc86e", + "vout": 0, + "scriptSig": { + "asm": "304402207b7a74cd22e35925e00330eaa0f9e3aa5b3f449daba32236bb1bb742199a5c0502201e16cced25f0fce4557fe808ef23291eb85d2de81e030805b4371e5c772f69d1[ALL] 03d9c69d204e5bfa548bbf6236f12be6ddfa7b4151937210b0c78cc8e2bb34134d", + "hex": "47304402207b7a74cd22e35925e00330eaa0f9e3aa5b3f449daba32236bb1bb742199a5c0502201e16cced25f0fce4557fe808ef23291eb85d2de81e030805b4371e5c772f69d1012103d9c69d204e5bfa548bbf6236f12be6ddfa7b4151937210b0c78cc8e2bb34134d" + }, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 99989697000, + "n": 0, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 9a237782659d31103818426f577f1ef16bb1f9ae OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9149a237782659d31103818426f577f1ef16bb1f9ae88ac", + "address": "BJW6AY3xAveXHgeMt5NG68Bq4UNDm9UNUs", + "type": "pubkeyhash" + } + } + ], + "hex": "02000000016ec88bdf0333b891e99ae8f1ae8f574e63be2fecbf16f922812a14082b1c959b000000006a47304402207b7a74cd22e35925e00330eaa0f9e3aa5b3f449daba32236bb1bb742199a5c0502201e16cced25f0fce4557fe808ef23291eb85d2de81e030805b4371e5c772f69d1012103d9c69d204e5bfa548bbf6236f12be6ddfa7b4151937210b0c78cc8e2bb34134dffffffff01e8b1d947170000001976a9149a237782659d31103818426f577f1ef16bb1f9ae88ac00000000" +} From 7f441cf66ac1a71f4807bbf5ff3bed1d85068481 Mon Sep 17 00:00:00 2001 From: BlackcoinDev Date: Wed, 22 Jul 2026 19:38:28 +0200 Subject: [PATCH 2/2] altcoin (blackcoin): protocol 1.6+ expects 'incrementalrelayfee' field Electrum-BLK v4.8.0 uses Electrum Protocol 1.6 and needs incrementalrelayfee value. Blackcoin has no RBF features, and 'getmempoolinfo' is missing the field. Without modifying the main code, it is better to create a specific BlackcoinDaemon class. and add Blackcoin specifics there. Also for future changes --- src/electrumx/lib/coins.py | 6 ++---- src/electrumx/server/daemon.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/electrumx/lib/coins.py b/src/electrumx/lib/coins.py index 0d199aab9..41d865553 100644 --- a/src/electrumx/lib/coins.py +++ b/src/electrumx/lib/coins.py @@ -1594,14 +1594,13 @@ class Blackcoin(ScryptMixin, Coin): GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d' 'f2c183bf232f263d0ba5b101911e4563') DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit - DAEMON = daemon.FakeEstimateFeeDaemon + DAEMON = daemon.BlackcoinDaemon TX_COUNT = 10802426 TX_COUNT_HEIGHT = 3431329 TX_PER_BLOCK = 3 RPC_PORT = 15715 REORG_LIMIT = 500 ESTIMATE_FEE = 0.001 - RELAY_FEE = 0.001 PEERS = [ 'electrum1.blackcoin.nl t10001 s10002', 'electrum2.blackcoin.nl t20001 s20002', @@ -1618,14 +1617,13 @@ class BlackcoinTestnet(Blackcoin): GENESIS_HASH = ('0000724595fb3b9609d441cbfb957761' '5c292abf07d996d3edabc48de843642d') DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit - DAEMON = daemon.FakeEstimateFeeDaemon + DAEMON = daemon.BlackcoinDaemon TX_COUNT = 1401229 TX_COUNT_HEIGHT = 698611 TX_PER_BLOCK = 2 RPC_PORT = 25715 REORG_LIMIT = 500 ESTIMATE_FEE = 0.001 - RELAY_FEE = 0.001 PEERS = [ 'electrum1.blackcoin.nl t10011 s10012', 'electrum2.blackcoin.nl t20011 s20012', diff --git a/src/electrumx/server/daemon.py b/src/electrumx/server/daemon.py index e02be5c75..5a300684e 100644 --- a/src/electrumx/server/daemon.py +++ b/src/electrumx/server/daemon.py @@ -435,6 +435,23 @@ async def relayfee(self): return self.coin.RELAY_FEE +class BlackcoinDaemon(Daemon): + '''Daemon for Blackcoin lacking estimatefee RPCs + and 'incrementalrelayfee' in getmempoolinfo.''' + + async def estimatefee(self, block_count, estimate_mode=None): + '''Return the fee estimate for the given parameters.''' + return self.coin.ESTIMATE_FEE + + async def mempool_info(self) -> dict[str, float]: + mempool_info = await self.getmempoolinfo() + return { + 'mempoolminfee': mempool_info['mempoolminfee'], + 'minrelaytxfee': mempool_info['minrelaytxfee'], + 'incrementalrelayfee': mempool_info.get('incrementalrelayfee', 0.0), + } + + class LegacyRPCDaemon(Daemon): '''Handles connections to a daemon at the given URL.