diff --git a/src/electrumx/lib/coins.py b/src/electrumx/lib/coins.py index 46702b23b..41d865553 100644 --- a/src/electrumx/lib/coins.py +++ b/src/electrumx/lib/coins.py @@ -1593,15 +1593,14 @@ class Blackcoin(ScryptMixin, Coin): P2SH_VERBYTES = (bytes.fromhex("55"),) GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d' 'f2c183bf232f263d0ba5b101911e4563') - DESERIALIZER = lib_tx.DeserializerBlackcoin - DAEMON = daemon.FakeEstimateFeeDaemon + DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit + DAEMON = daemon.BlackcoinDaemon 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 PEERS = [ 'electrum1.blackcoin.nl t10001 s10002', 'electrum2.blackcoin.nl t20001 s20002', @@ -1617,15 +1616,14 @@ class BlackcoinTestnet(Blackcoin): P2SH_VERBYTES = (bytes.fromhex("C4"),) GENESIS_HASH = ('0000724595fb3b9609d441cbfb957761' '5c292abf07d996d3edabc48de843642d') - DESERIALIZER = lib_tx.DeserializerBlackcoin - DAEMON = daemon.FakeEstimateFeeDaemon + DESERIALIZER = lib_tx.DeserializerBlackcoinSegWit + 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/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/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. 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" +}