Remove altcoin-only server and tx code

Deletes the transaction deserializers of other coins (tx.py keeps only the
Bitcoin/segwit ones), tx_dash.py, tx_axe.py, and the session, daemon and block
processor subclasses that only other coins used.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-22 15:03:56 +02:00
1 parent 73c96101bd
commit 05f5fc9058
6 files changed
+3 -2745

No files matched your search

+2 -1139
View File
File diff suppressed because it is too large. Load diff
-489
View File
@@ -1,489 +0,0 @@
# Copyright (c) 2016-2018, Neil Booth
# Copyright (c) 2018, the ElectrumX authors
#
# All rights reserved.
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''Deserializer for AXE DIP2 special transaction types'''
from dataclasses import dataclass
from typing import Any
from electrumx.lib.tx import Deserializer, Tx
from electrumx.lib.util import (pack_le_uint16, pack_le_int32, pack_le_uint32,
pack_le_int64, pack_varint, pack_varbytes,
pack_be_uint16)
# https://github.com/dashpay/dips/blob/master/dip-0002.md
@dataclass(kw_only=True, slots=True)
class AxeTx(Tx):
'''Class representing a Axe transaction'''
tx_type: int
extra_payload: Any
def serialize(self):
nLocktime = pack_le_uint32(self.locktime)
txins = (pack_varint(len(self.inputs)) +
b''.join(tx_in.serialize() for tx_in in self.inputs))
txouts = (pack_varint(len(self.outputs)) +
b''.join(tx_out.serialize() for tx_out in self.outputs))
if self.tx_type:
uVersion = pack_le_uint16(self.version)
uTxType = pack_le_uint16(self.tx_type)
vExtra = self._serialize_extra_payload()
return uVersion + uTxType + txins + txouts + nLocktime + vExtra
else:
nVersion = pack_le_int32(self.version)
return nVersion + txins + txouts + nLocktime
def _serialize_extra_payload(self):
extra = self.extra_payload
spec_tx_class = DeserializerAxe.SPEC_TX_HANDLERS.get(self.tx_type)
if not spec_tx_class:
assert isinstance(extra, (bytes, bytearray))
return pack_varbytes(extra)
if not isinstance(extra, spec_tx_class):
raise ValueError('Axe tx_type does not conform with extra'
' payload class: %s, %s' % (self.tx_type, extra))
return pack_varbytes(extra.serialize())
# https://github.com/dashpay/dips/blob/master/dip-0002-special-transactions.md
@dataclass(kw_only=True, slots=True)
class AxeProRegTx:
'''Class representing DIP3 ProRegTx'''
version: int
type: int
mode: int
collateralOutpoint: 'TxOutPoint'
ipAddress: bytes
port: int
KeyIdOwner: bytes
PubKeyOperator: bytes
KeyIdVoting: bytes
operatorReward: int
scriptPayout: bytes
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.ipAddress) == 16
and len(self.KeyIdOwner) == 20
and len(self.PubKeyOperator) == 48
and len(self.KeyIdVoting) == 20
and len(self.inputsHash) == 32)
return (
pack_le_uint16(self.version) + # version
pack_le_uint16(self.type) + # type
pack_le_uint16(self.mode) + # mode
self.collateralOutpoint.serialize() + # collateralOutpoint
self.ipAddress + # ipAddress
pack_be_uint16(self.port) + # port
self.KeyIdOwner + # KeyIdOwner
self.PubKeyOperator + # PubKeyOperator
self.KeyIdVoting + # KeyIdVoting
pack_le_uint16(self.operatorReward) + # operatorReward
pack_varbytes(self.scriptPayout) + # scriptPayout
self.inputsHash + # inputsHash
pack_varbytes(self.payloadSig) # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeProRegTx(
version=deser._read_le_uint16(),
type=deser._read_le_uint16(),
mode=deser._read_le_uint16(),
collateralOutpoint=deser._read_outpoint(),
ipAddress=deser._read_nbytes(16),
port=deser._read_be_uint16(),
KeyIdOwner=deser._read_nbytes(20),
PubKeyOperator=deser._read_nbytes(48),
KeyIdVoting=deser._read_nbytes(20),
operatorReward=deser._read_le_uint16(),
scriptPayout=deser._read_varbytes(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_varbytes(),
)
@dataclass(kw_only=True, slots=True)
class AxeProUpServTx:
'''Class representing DIP3 ProUpServTx'''
version: int
proTxHash: bytes
ipAddress: bytes
port: int
scriptOperatorPayout: bytes
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.ipAddress) == 16
and len(self.inputsHash) == 32
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
self.proTxHash + # proTxHash
self.ipAddress + # ipAddress
pack_be_uint16(self.port) + # port
pack_varbytes(self.scriptOperatorPayout) + # scriptOperatorPayout
self.inputsHash + # inputsHash
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeProUpServTx(
version=deser._read_le_uint16(),
proTxHash=deser._read_nbytes(32),
ipAddress=deser._read_nbytes(16),
port=deser._read_be_uint16(),
scriptOperatorPayout=deser._read_varbytes(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_nbytes(96),
)
@dataclass(kw_only=True, slots=True)
class AxeProUpRegTx:
'''Class representing DIP3 ProUpRegTx'''
version: int
proTxHash: bytes
mode: int
PubKeyOperator: bytes
KeyIdVoting: bytes
scriptPayout: bytes
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.PubKeyOperator) == 48
and len(self.KeyIdVoting) == 20
and len(self.inputsHash) == 32)
return (
pack_le_uint16(self.version) + # version
self.proTxHash + # proTxHash
pack_le_uint16(self.mode) + # mode
self.PubKeyOperator + # PubKeyOperator
self.KeyIdVoting + # KeyIdVoting
pack_varbytes(self.scriptPayout) + # scriptPayout
self.inputsHash + # inputsHash
pack_varbytes(self.payloadSig) # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeProUpRegTx(
version=deser._read_le_uint16(),
proTxHash=deser._read_nbytes(32),
mode=deser._read_le_uint16(),
PubKeyOperator=deser._read_nbytes(48),
KeyIdVoting=deser._read_nbytes(20),
scriptPayout=deser._read_varbytes(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_varbytes(),
)
@dataclass(kw_only=True, slots=True)
class AxeProUpRevTx:
'''Class representing DIP3 ProUpRevTx'''
version: int
proTxHash: bytes
reason: int
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.inputsHash) == 32
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
self.proTxHash + # proTxHash
pack_le_uint16(self.reason) + # reason
self.inputsHash + # inputsHash
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeProUpRevTx(
version=deser._read_le_uint16(),
proTxHash=deser._read_nbytes(32),
reason=deser._read_le_uint16(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_nbytes(96),
)
@dataclass(kw_only=True, slots=True)
class AxeCbTx:
'''Class representing DIP4 coinbase special tx'''
version: int
height: int
merkleRootMNList: bytes
merkleRootQuorums: bytes
def serialize(self):
assert len(self.merkleRootMNList) == 32
res = (
pack_le_uint16(self.version) + # version
pack_le_uint32(self.height) + # height
self.merkleRootMNList # merkleRootMNList
)
if self.version > 1:
assert len(self.merkleRootQuorums) == 32
res += self.merkleRootQuorums # merkleRootQuorums
return res
@classmethod
def read_tx_extra(cls, deser):
version = deser._read_le_uint16()
height = deser._read_le_uint32()
merkleRootMNList = deser._read_nbytes(32)
merkleRootQuorums = b''
if version > 1:
merkleRootQuorums = deser._read_nbytes(32)
return AxeCbTx(
version=version,
height=height,
merkleRootMNList=merkleRootMNList,
merkleRootQuorums=merkleRootQuorums,
)
@dataclass(kw_only=True, slots=True)
class AxeSubTxRegister:
'''Class representing DIP5 SubTxRegister'''
version: int
userName: bytes
pubKey: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.pubKey) == 48
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
pack_varbytes(self.userName) + # userName
self.pubKey + # pubKey
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeSubTxRegister(
version=deser._read_le_uint16(),
userName=deser._read_varbytes(),
pubKey=deser._read_nbytes(48),
payloadSig=deser._read_nbytes(96),
)
@dataclass(kw_only=True, slots=True)
class AxeSubTxTopup:
'''Class representing DIP5 SubTxTopup'''
version: int
regTxHash: bytes
def serialize(self):
assert len(self.regTxHash) == 32
return (
pack_le_uint16(self.version) + # version
self.regTxHash # regTxHash
)
@classmethod
def read_tx_extra(cls, deser):
return AxeSubTxTopup(
version=deser._read_le_uint16(),
regTxHash=deser._read_nbytes(32),
)
@dataclass(kw_only=True, slots=True)
class AxeSubTxResetKey:
'''Class representing DIP5 SubTxResetKey'''
version: int
regTxHash: bytes
hashPrevSubTx: bytes
creditFee: int
newPubKey: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.regTxHash) == 32
and len(self.hashPrevSubTx) == 32
and len(self.newPubKey) == 48
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
self.regTxHash + # regTxHash
self.hashPrevSubTx + # hashPrevSubTx
pack_le_int64(self.creditFee) + # creditFee
self.newPubKey + # newPubKey
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeSubTxResetKey(
version=deser._read_le_uint16(),
regTxHash=deser._read_nbytes(32),
hashPrevSubTx=deser._read_nbytes(32),
creditFee=deser._read_le_int64(),
newPubKey=deser._read_nbytes(48),
payloadSig=deser._read_nbytes(96),
)
@dataclass(kw_only=True, slots=True)
class AxeSubTxCloseAccount:
'''Class representing DIP5 SubTxCloseAccount'''
version: int
regTxHash: bytes
hashPrevSubTx: bytes
creditFee: int
payloadSig: bytes
def serialize(self):
assert (len(self.regTxHash) == 32
and len(self.hashPrevSubTx) == 32
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
self.regTxHash + # regTxHash
self.hashPrevSubTx + # hashPrevSubTx
pack_le_int64(self.creditFee) + # creditFee
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return AxeSubTxCloseAccount(
version=deser._read_le_uint16(),
regTxHash=deser._read_nbytes(32),
hashPrevSubTx=deser._read_nbytes(32),
creditFee=deser._read_le_int64(),
payloadSig=deser._read_nbytes(96),
)
# https://dash-docs.github.io/en/developer-reference#outpoint
@dataclass(kw_only=True, slots=True)
class TxOutPoint:
'''Class representing tx output outpoint'''
hash: bytes
index: int
def serialize(self):
assert len(self.hash) == 32
return (
self.hash + # hash
pack_le_uint32(self.index) # index
)
@classmethod
def read_outpoint(cls, deser):
return TxOutPoint(
hash=deser._read_nbytes(32),
index=deser._read_le_uint32(),
)
class DeserializerAxe(Deserializer):
'''Deserializer for AXE DIP2 special tx types'''
# Supported Spec Tx types and corresponding classes mapping
PRO_REG_TX = 1
PRO_UP_SERV_TX = 2
PRO_UP_REG_TX = 3
PRO_UP_REV_TX = 4
CB_TX = 5
SUB_TX_REGISTER = 8
SUB_TX_TOPUP = 9
SUB_TX_RESET_KEY = 10
SUB_TX_CLOSE_ACCOUNT = 11
SPEC_TX_HANDLERS = {
PRO_REG_TX: AxeProRegTx,
PRO_UP_SERV_TX: AxeProUpServTx,
PRO_UP_REG_TX: AxeProUpRegTx,
PRO_UP_REV_TX: AxeProUpRevTx,
CB_TX: AxeCbTx,
SUB_TX_REGISTER: AxeSubTxRegister,
SUB_TX_TOPUP: AxeSubTxTopup,
SUB_TX_RESET_KEY: AxeSubTxResetKey,
SUB_TX_CLOSE_ACCOUNT: AxeSubTxCloseAccount,
}
def _read_outpoint(self):
return TxOutPoint.read_outpoint(self)
def read_tx(self):
start = self.cursor
header = self._read_le_uint32()
tx_type = header >> 16 # DIP2 tx type
if tx_type:
version = header & 0x0000ffff
else:
version = header
if tx_type and version < 3:
version = header
tx_type = 0
inputs = self._read_inputs()
outputs = self._read_outputs()
locktime = self._read_le_uint32()
if tx_type:
extra_payload_size = self._read_varint()
end = self.cursor + extra_payload_size
spec_tx_class = DeserializerAxe.SPEC_TX_HANDLERS.get(tx_type)
if spec_tx_class:
read_method = getattr(spec_tx_class, 'read_tx_extra', None)
extra_payload = read_method(self)
assert isinstance(extra_payload, spec_tx_class)
else:
extra_payload = self._read_nbytes(extra_payload_size)
assert self.cursor == end
else:
extra_payload = b''
txid = self.TX_HASH_FN(self.binary[start:self.cursor])
tx = AxeTx(
version=version,
inputs=inputs,
outputs=outputs,
locktime=locktime,
tx_type=tx_type,
extra_payload=extra_payload,
txid_rev=txid,
wtxid_rev=txid,
)
return tx
-442
View File
@@ -1,442 +0,0 @@
# Copyright (c) 2016-2018, Neil Booth
# Copyright (c) 2018, the ElectrumX authors
#
# All rights reserved.
#
# The MIT License (MIT)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''Deserializer for Dash DIP2 special transaction types'''
from dataclasses import dataclass
from typing import Any
from electrumx.lib.tx import Deserializer, Tx
from electrumx.lib.util import (pack_le_uint16, pack_le_int32, pack_le_uint32,
pack_le_int64, pack_varint, pack_varbytes,
pack_be_uint16)
# https://github.com/dashpay/dips/blob/master/dip-0002.md
@dataclass(kw_only=True, slots=True)
class DashTx(Tx):
'''Class representing a Dash transaction'''
tx_type: int
extra_payload: Any
def serialize(self):
nLocktime = pack_le_uint32(self.locktime)
txins = (pack_varint(len(self.inputs)) +
b''.join(tx_in.serialize() for tx_in in self.inputs))
txouts = (pack_varint(len(self.outputs)) +
b''.join(tx_out.serialize() for tx_out in self.outputs))
if self.tx_type:
uVersion = pack_le_uint16(self.version)
uTxType = pack_le_uint16(self.tx_type)
vExtra = self._serialize_extra_payload()
return uVersion + uTxType + txins + txouts + nLocktime + vExtra
else:
nVersion = pack_le_int32(self.version)
return nVersion + txins + txouts + nLocktime
def _serialize_extra_payload(self):
extra = self.extra_payload
spec_tx_class = DeserializerDash.SPEC_TX_HANDLERS.get(self.tx_type)
if not spec_tx_class:
assert isinstance(extra, (bytes, bytearray))
return pack_varbytes(extra)
if not isinstance(extra, spec_tx_class):
raise ValueError('Dash tx_type does not conform with extra'
' payload class: %s, %s' % (self.tx_type, extra))
return pack_varbytes(extra.serialize())
# https://github.com/dashpay/dips/blob/master/dip-0002-special-transactions.md
@dataclass(kw_only=True, slots=True)
class DashProRegTx:
'''Class representing DIP3 ProRegTx'''
version: int
type: int
mode: int
collateralOutpoint: 'TxOutPoint'
ipAddress: bytes
port: int
KeyIdOwner: bytes
PubKeyOperator: bytes
KeyIdVoting: bytes
operatorReward: int
scriptPayout: bytes
inputsHash: bytes
platformNodeID: bytes
platformP2PPort: int
platformHTTPPort: int
payloadSig: bytes
def serialize(self):
assert (len(self.ipAddress) == 16
and len(self.KeyIdOwner) == 20
and len(self.PubKeyOperator) == 48
and len(self.KeyIdVoting) == 20
and len(self.inputsHash) == 32)
res = (
pack_le_uint16(self.version) + # version
pack_le_uint16(self.type) + # type
pack_le_uint16(self.mode) + # mode
self.collateralOutpoint.serialize() + # collateralOutpoint
self.ipAddress + # ipAddress
pack_be_uint16(self.port) + # port
self.KeyIdOwner + # KeyIdOwner
self.PubKeyOperator + # PubKeyOperator
self.KeyIdVoting + # KeyIdVoting
pack_le_uint16(self.operatorReward) + # operatorReward
pack_varbytes(self.scriptPayout) + # scriptPayout
self.inputsHash) # inputsHash
if self.version >= 2:
res += (self.platformNodeID + # platformNodeID
pack_le_uint16(self.platformP2PPort) + # platformP2PPort
pack_le_uint16(self.platformHTTPPort)) # platformHTTPPort
res += pack_varbytes(self.payloadSig) # payloadSig
return res
@classmethod
def read_tx_extra(cls, deser):
version = deser._read_le_uint16() # version
ntype = deser._read_le_uint16() # type
mode = deser._read_le_uint16() # mode
collateralOutpoint = deser._read_outpoint() # collateralOutpoint
ipAddress = deser._read_nbytes(16) # ipAddress
port = deser._read_be_uint16() # port
KeyIdOwner = deser._read_nbytes(20) # KeyIdOwner
PubKeyOperator = deser._read_nbytes(48) # PubKeyOperator
KeyIdVoting = deser._read_nbytes(20) # KeyIdVoting
operatorReward = deser._read_le_uint16() # operatorReward
scriptPayout = deser._read_varbytes() # scriptPayout
inputsHash = deser._read_nbytes(32) # inputsHash
platformNodeID = b''
platformP2PPort = 0
platformHTTPPort = 0
if version >= 2 and ntype == 1:
platformNodeID = deser._read_nbytes(20) # platformNodeID
platformP2PPort = deser._read_le_uint16() # platformP2PPort
platformHTTPPort = deser._read_le_uint16() # platformHTTPPort
payloadSig = deser._read_varbytes() # payloadSig
return DashProRegTx(
version=version,
type=ntype,
mode=mode,
collateralOutpoint=collateralOutpoint,
ipAddress=ipAddress,
port=port,
KeyIdOwner=KeyIdOwner,
PubKeyOperator=PubKeyOperator,
KeyIdVoting=KeyIdVoting,
operatorReward=operatorReward,
scriptPayout=scriptPayout,
inputsHash=inputsHash,
platformNodeID=platformNodeID,
platformP2PPort=platformP2PPort,
platformHTTPPort=platformHTTPPort,
payloadSig=payloadSig,
)
@dataclass(kw_only=True, slots=True)
class DashProUpServTx:
'''Class representing DIP3 ProUpServTx'''
version: int
type: int
proTxHash: bytes
ipAddress: bytes
port: int
scriptOperatorPayout: bytes
inputsHash: bytes
platformNodeID: bytes
platformP2PPort: int
platformHTTPPort: int
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.ipAddress) == 16
and len(self.inputsHash) == 32
and len(self.payloadSig) == 96)
res = pack_le_uint16(self.version) # version
if self.version >= 2:
res += pack_le_uint16(self.type) # type
res += (
self.proTxHash + # proTxHash
self.ipAddress + # ipAddress
pack_be_uint16(self.port) + # port
pack_varbytes(self.scriptOperatorPayout) + # scriptOperatorPayout
self.inputsHash) # inputsHash
if self.version >= 2:
res += (self.platformNodeID + # platformNodeID
pack_le_uint16(self.platformP2PPort) + # platformP2PPort
pack_le_uint16(self.platformHTTPPort)) # platformHTTPPort
res += self.payloadSig # payloadSig
return res
@classmethod
def read_tx_extra(cls, deser):
version = deser._read_le_uint16() # version
ntype = 0
if version >= 2:
ntype = deser._read_le_uint16() # type
proTxHash = deser._read_nbytes(32) # proTxHash
ipAddress = deser._read_nbytes(16) # ipAddress
port = deser._read_be_uint16() # port
scriptOperatorPayout = deser._read_varbytes() # scriptOperatorPayout
inputsHash = deser._read_nbytes(32) # inputsHash
platformNodeID = b''
platformP2PPort = 0
platformHTTPPort = 0
if version >= 2 and ntype == 1:
platformNodeID = deser._read_nbytes(20) # platformNodeID
platformP2PPort = deser._read_le_uint16() # platformP2PPort
platformHTTPPort = deser._read_le_uint16() # platformHTTPPort
payloadSig = deser._read_nbytes(96) # payloadSig
return DashProUpServTx(
version=version,
type=ntype,
proTxHash=proTxHash,
ipAddress=ipAddress,
port=port,
scriptOperatorPayout=scriptOperatorPayout,
inputsHash=inputsHash,
platformNodeID=platformNodeID,
platformP2PPort=platformP2PPort,
platformHTTPPort=platformHTTPPort,
payloadSig=payloadSig,
)
@dataclass(kw_only=True, slots=True)
class DashProUpRegTx:
'''Class representing DIP3 ProUpRegTx'''
version: int
proTxHash: bytes
mode: int
PubKeyOperator: bytes
KeyIdVoting: bytes
scriptPayout: bytes
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.PubKeyOperator) == 48
and len(self.KeyIdVoting) == 20
and len(self.inputsHash) == 32)
return (
pack_le_uint16(self.version) + # version
self.proTxHash + # proTxHash
pack_le_uint16(self.mode) + # mode
self.PubKeyOperator + # PubKeyOperator
self.KeyIdVoting + # KeyIdVoting
pack_varbytes(self.scriptPayout) + # scriptPayout
self.inputsHash + # inputsHash
pack_varbytes(self.payloadSig) # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return DashProUpRegTx(
version=deser._read_le_uint16(),
proTxHash=deser._read_nbytes(32),
mode=deser._read_le_uint16(),
PubKeyOperator=deser._read_nbytes(48),
KeyIdVoting=deser._read_nbytes(20),
scriptPayout=deser._read_varbytes(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_varbytes(),
)
@dataclass(kw_only=True, slots=True)
class DashProUpRevTx:
'''Class representing DIP3 ProUpRevTx'''
version: int
proTxHash: bytes
reason: int
inputsHash: bytes
payloadSig: bytes
def serialize(self):
assert (len(self.proTxHash) == 32
and len(self.inputsHash) == 32
and len(self.payloadSig) == 96)
return (
pack_le_uint16(self.version) + # version
self.proTxHash + # proTxHash
pack_le_uint16(self.reason) + # reason
self.inputsHash + # inputsHash
self.payloadSig # payloadSig
)
@classmethod
def read_tx_extra(cls, deser):
return DashProUpRevTx(
version=deser._read_le_uint16(),
proTxHash=deser._read_nbytes(32),
reason=deser._read_le_uint16(),
inputsHash=deser._read_nbytes(32),
payloadSig=deser._read_nbytes(96),
)
@dataclass(kw_only=True, slots=True)
class DashCbTx:
'''Class representing DIP4 coinbase special tx'''
version: int
height: int
merkleRootMNList: bytes
merkleRootQuorums: bytes
bestCLHeightDiff: int
bestCLSignature: bytes
assetLockedAmount: int
def serialize(self):
assert len(self.merkleRootMNList) == 32
res = (
pack_le_uint16(self.version) + # version
pack_le_uint32(self.height) + # height
self.merkleRootMNList # merkleRootMNList
)
if self.version > 1:
assert len(self.merkleRootQuorums) == 32
res += self.merkleRootQuorums # merkleRootQuorums
if self.version > 2:
res += pack_varint(self.bestCLHeightDiff)
res += self.bestCLSignature
res += pack_le_int64(self.assetLockedAmount)
return res
@classmethod
def read_tx_extra(cls, deser):
version = deser._read_le_uint16()
height = deser._read_le_uint32()
merkleRootMNList = deser._read_nbytes(32)
merkleRootQuorums = b''
bestCLHeightDiff = 0
bestCLSignature = b''
assetLockedAmount = 0
if version > 1:
merkleRootQuorums = deser._read_nbytes(32)
if version > 2:
bestCLHeightDiff = deser._read_varint()
bestCLSignature = deser._read_nbytes(96)
assetLockedAmount = deser._read_le_uint64()
return DashCbTx(
version=version,
height=height,
merkleRootMNList=merkleRootMNList,
merkleRootQuorums=merkleRootQuorums,
bestCLHeightDiff=bestCLHeightDiff,
bestCLSignature=bestCLSignature,
assetLockedAmount=assetLockedAmount,
)
# https://dash-docs.github.io/en/developer-reference#outpoint
@dataclass(kw_only=True, slots=True)
class TxOutPoint:
'''Class representing tx output outpoint'''
hash: bytes
index: int
def serialize(self):
assert len(self.hash) == 32
return (
self.hash + # hash
pack_le_uint32(self.index) # index
)
@classmethod
def read_outpoint(cls, deser):
return TxOutPoint(
hash=deser._read_nbytes(32),
index=deser._read_le_uint32(),
)
class DeserializerDash(Deserializer):
'''Deserializer for Dash DIP2 special tx types'''
# Supported Spec Tx types and corresponding classes mapping
PRO_REG_TX = 1
PRO_UP_SERV_TX = 2
PRO_UP_REG_TX = 3
PRO_UP_REV_TX = 4
CB_TX = 5
SPEC_TX_HANDLERS = {
PRO_REG_TX: DashProRegTx,
PRO_UP_SERV_TX: DashProUpServTx,
PRO_UP_REG_TX: DashProUpRegTx,
PRO_UP_REV_TX: DashProUpRevTx,
CB_TX: DashCbTx,
}
def _read_outpoint(self):
return TxOutPoint.read_outpoint(self)
def read_tx(self):
start = self.cursor
header = self._read_le_uint32()
tx_type = header >> 16 # DIP2 tx type
if tx_type:
version = header & 0x0000ffff
else:
version = header
if tx_type and version < 3:
version = header
tx_type = 0
inputs = self._read_inputs()
outputs = self._read_outputs()
locktime = self._read_le_uint32()
if tx_type:
extra_payload_size = self._read_varint()
end = self.cursor + extra_payload_size
spec_tx_class = DeserializerDash.SPEC_TX_HANDLERS.get(tx_type)
if spec_tx_class:
read_method = getattr(spec_tx_class, 'read_tx_extra', None)
extra_payload = read_method(self)
assert isinstance(extra_payload, spec_tx_class)
else:
extra_payload = self._read_nbytes(extra_payload_size)
assert self.cursor == end
else:
extra_payload = b''
txid = self.TX_HASH_FN(self.binary[start:self.cursor])
tx = DashTx(
version=version,
inputs=inputs,
outputs=outputs,
locktime=locktime,
tx_type=tx_type,
extra_payload=extra_payload,
txid_rev=txid,
wtxid_rev=txid,
)
return tx
-104
View File
@@ -904,107 +904,3 @@ class BlockProcessor:
self.blocks_event.set()
return True
return False
class DecredBlockProcessor(BlockProcessor):
async def calc_reorg_range(self, count):
start, count = await super().calc_reorg_range(count)
if start > 0:
# A reorg in Decred can invalidate the previous block
start -= 1
count += 1
return start, count
class NameIndexBlockProcessor(BlockProcessor):
def advance_txs_process_outputs(
self,
txs: Sequence[Tx],
*,
height: int,
) -> None:
result = super().advance_txs_process_outputs(txs, height=height)
tx_num_start = self.db.tx_counts[height - 1] if height > 0 else 0
tx_num = tx_num_start
script_name_hashX = self.coin.name_hashX_from_script
update_touched_hashxs = self.touched_hashxs.update
hashXs_by_tx = []
append_hashXs = hashXs_by_tx.append
for tx in txs:
hashXs = []
append_hashX = hashXs.append
# Add the new UTXOs and associate them with the name script
for txout in tx.outputs:
# Get the hashX of the name script. Ignore non-name scripts.
hashX = script_name_hashX(txout.pk_script)
if hashX:
append_hashX(hashX)
append_hashXs(hashXs)
update_touched_hashxs(hashXs)
tx_num += 1
self.db.history.add_unflushed(hashXs_by_tx, tx_num_start)
return result
class LTORBlockProcessor(BlockProcessor):
# for Lexicographical Transaction ordering in blocks
# (as opposed to Topological order as used in Bitcoin)
# note: the base class already does not care about tx ordering inside a block
# for the forward (catch-up) direction, as that better parallelizes.
# This subclass is only kept for the reverse (backup) direction, for now.
# We could also parallelize backup_txs in the base class for better perf,
# likely removing the tx ordering assumptions, then we could rm this subclass.
def backup_txs(self, txs, is_unspendable):
undo_info = self.db.read_undo_info(self.height)
if undo_info is None:
raise ChainError(
f'no undo information found for height {self.height:,d}'
)
# Use local vars for speed in the loops
put_utxo = self.utxo_cache.__setitem__
spend_utxo = self.spend_utxo
add_touched_hashx = self.touched_hashxs.add
add_touched_outpoint = self.touched_outpoints.add
undo_entry_len = HASHX_LEN + TXNUM_LEN + 8
# Restore coins that had been spent
# (may include coins made then spent in this block)
n = 0
for tx in txs:
for txin in tx.inputs:
if txin.is_generation():
continue
undo_item = undo_info[n:n + undo_entry_len]
prevout = txin.prev_txid_rev + pack_txoutidx(txin.prev_idx)
put_utxo(prevout, undo_item)
add_touched_hashx(undo_item[:HASHX_LEN])
add_touched_outpoint((txin.prev_txid_rev, txin.prev_idx))
n += undo_entry_len
assert n == len(undo_info)
# Remove tx outputs made in this block, by spending them.
for tx in txs:
txid_rev = tx.txid_rev
for idx, txout in enumerate(tx.outputs):
# Spend the TX outputs. Be careful with unspendable
# outputs - we didn't save those in the first place.
if is_unspendable(txout.pk_script):
continue
# Get the hashX
cache_value = spend_utxo(txid_rev, idx)
hashX = cache_value[:HASHX_LEN]
add_touched_hashx(hashX)
add_touched_outpoint((txid_rev, idx))
self.tx_count -= len(txs)
+1 -238
View File
@@ -11,18 +11,13 @@ daemon.'''
import asyncio
import itertools
import time
from calendar import timegm
from struct import pack
from typing import TYPE_CHECKING, Type, Sequence, Any, Iterable, Optional
import aiohttp
from aiorpcx import JSONRPC
from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash
from electrumx.lib.tx import DeserializerDecred
from electrumx.lib.util import (class_logger, hex_to_bytes, json_deserialize,
json_serialize, pack_varint,
unpack_le_uint16_from)
json_serialize)
if TYPE_CHECKING:
from electrumx.lib.coins import Coin
@@ -404,235 +399,3 @@ class Daemon:
If the daemon has not been queried yet this returns None.'''
return self._height
class DashDaemon(Daemon):
async def masternode_broadcast(self, params):
'''Broadcast a transaction to the network.'''
return await self._send_single('masternodebroadcast', params)
async def masternode_list(self, params):
'''Return the masternode status.'''
return await self._send_single('masternodelist', params)
async def protx(self, params):
'''Set of commands to execute ProTx related actions.'''
return await self._send_single('protx', params)
class FakeEstimateFeeDaemon(Daemon):
'''Daemon that simulates estimatefee and relayfee RPC calls. Coin that
wants to use this daemon must define ESTIMATE_FEE & RELAY_FEE'''
async def estimatefee(self, block_count):
'''Return the fee estimate for the given parameters.'''
return self.coin.ESTIMATE_FEE
async def relayfee(self):
"""The minimum fee required for a transaction to be relayed on by the server to the
bitcoin network. Doesn't guarantee mempool acceptance."""
return self.coin.RELAY_FEE
class LegacyRPCDaemon(Daemon):
'''Handles connections to a daemon at the given URL.
This class is useful for daemons that don't have the new 'getblock'
RPC call that returns the block in hex, the workaround is to manually
recreate the block bytes. The recreated block bytes may not be the exact
as in the underlying blockchain but it is good enough for our indexing
purposes.'''
async def raw_blocks(self, bhashes_hum):
'''Return the raw binary blocks with the given hex hashes.'''
params_iterable = ((h, ) for h in bhashes_hum)
block_info = await self._send_vector('getblock', params_iterable)
blocks = []
for i in block_info:
raw_block = await self.make_raw_block(i)
blocks.append(raw_block)
# Convert hex string to bytes
return blocks
async def make_raw_header(self, b):
pbh = b.get('previousblockhash')
if pbh is None:
pbh = '0' * 64
return b''.join([
pack('<L', b.get('version')),
hex_str_to_hash(pbh),
hex_str_to_hash(b.get('merkleroot')),
pack('<L', self.timestamp_safe(b['time'])),
pack('<L', int(b.get('bits'), 16)),
pack('<L', int(b.get('nonce')))
])
async def make_raw_block(self, b):
'''Construct a raw block'''
header = await self.make_raw_header(b)
transactions = []
if b.get('height') > 0:
transactions = await self.getrawtransactions(b.get('tx'), False)
raw_block = header
num_txs = len(transactions)
if num_txs > 0:
raw_block += pack_varint(num_txs)
raw_block += b''.join(transactions)
else:
raw_block += b'\x00'
return raw_block
def timestamp_safe(self, t):
if isinstance(t, int):
return t
return timegm(time.strptime(t, "%Y-%m-%d %H:%M:%S %Z"))
class FakeEstimateLegacyRPCDaemon(LegacyRPCDaemon, FakeEstimateFeeDaemon):
pass
class DecredDaemon(Daemon):
async def raw_blocks(self, bhashes_hum):
'''Return the raw binary blocks with the given hex hashes.'''
params_iterable = ((h, False) for h in bhashes_hum)
blocks = await self._send_vector('getblock', params_iterable)
raw_blocks = []
valid_tx_tree = {}
for block in blocks:
# Convert to bytes from hex
raw_block = hex_to_bytes(block)
raw_blocks.append(raw_block)
# Check if previous block is valid
prev = self.prev_hex_hash(raw_block)
votebits = unpack_le_uint16_from(raw_block[100:102])[0]
valid_tx_tree[prev] = self.is_valid_tx_tree(votebits)
processed_raw_blocks = []
for hash, raw_block in zip(bhashes_hum, raw_blocks):
if hash in valid_tx_tree:
is_valid = valid_tx_tree[hash]
else:
# Do something complicated to figure out if this block is valid
header = await self._send_single('getblockheader', (hash, ))
if 'nextblockhash' not in header:
raise DaemonError(f'Could not find next block for {hash}')
next_hash = header['nextblockhash']
next_header = await self._send_single('getblockheader',
(next_hash, ))
is_valid = self.is_valid_tx_tree(next_header['votebits'])
if is_valid:
processed_raw_blocks.append(raw_block)
else:
# If this block is invalid remove the normal transactions
self.logger.info(f'block {hash} is invalidated')
processed_raw_blocks.append(self.strip_tx_tree(raw_block))
return processed_raw_blocks
@staticmethod
def prev_hex_hash(raw_block):
return hash_to_hex_str(raw_block[4:36])
@staticmethod
def is_valid_tx_tree(votebits):
# Check if previous block was invalidated.
return bool(votebits & (1 << 0) != 0)
def strip_tx_tree(self, raw_block):
c = self.coin
assert issubclass(c.DESERIALIZER, DeserializerDecred)
d = c.DESERIALIZER(raw_block, start=c.BASIC_HEADER_SIZE)
d.read_tx_tree() # Skip normal transactions
# Create a fake block without any normal transactions
return raw_block[:c.BASIC_HEADER_SIZE] + b'\x00' + raw_block[d.cursor:]
async def height(self):
height = await super().height()
if height > 0:
# Lie about the daemon height as the current tip can be invalidated
height -= 1
self._height = height
return height
async def mempool_txids_hum(self):
mempool = await super().mempool_txids_hum()
# Add current tip transactions to the 'fake' mempool.
real_height = await self._send_single('getblockcount')
tip_hash = await self._send_single('getblockhash', (real_height,))
tip = await self.deserialised_block(tip_hash)
# Add normal transactions except coinbase
mempool += tip['tx'][1:]
# Add stake transactions if applicable
mempool += tip.get('stx', [])
return mempool
def connector(self):
# FIXME allow self signed certificates
return aiohttp.TCPConnector(verify_ssl=False)
class PreLegacyRPCDaemon(LegacyRPCDaemon):
'''Handles connections to a daemon at the given URL.
This class is useful for daemons that don't have the new 'getblock'
RPC call that returns the block in hex, and need the False parameter
for the getblock'''
async def deserialised_block(self, hex_hash):
'''Return the deserialised block with the given hex hash.'''
return await self._send_single('getblock', (hex_hash, False))
class SmartCashDaemon(Daemon):
async def masternode_broadcast(self, params):
'''Broadcast a smartnode to the network.'''
return await self._send_single('smartnodebroadcast', params)
async def masternode_list(self, params):
'''Return the smartnode status.'''
return await self._send_single('smartnodelist', params)
async def smartrewards(self, params):
'''Return smartrewards data.'''
return await self._send_single('smartrewards', params)
class ZcoinMtpDaemon(Daemon):
def strip_mtp_data(self, raw_block):
if self.coin.is_mtp(raw_block):
return \
raw_block[:self.coin.MTP_HEADER_DATA_START*2] + \
raw_block[self.coin.MTP_HEADER_DATA_END*2:]
return raw_block
async def raw_blocks(self, bhashes_hum):
'''Return the raw binary blocks with the given hex hashes.'''
params_iterable = ((h, False) for h in bhashes_hum)
blocks = await self._send_vector('getblock', params_iterable)
# Convert hex string to bytes
return [hex_to_bytes(self.strip_mtp_data(block)) for block in blocks]
async def masternode_broadcast(self, params):
'''Broadcast a transaction to the network.'''
return await self._send_single('znodebroadcast', params)
async def masternode_list(self, params):
'''Return the masternode status.'''
return await self._send_single('znodelist', params)
async def protx(self, params):
'''Set of commands to execute ProTx related actions.'''
return await self._send_single('protx', params)
-333
View File
@@ -10,7 +10,6 @@
import asyncio
import codecs
from dataclasses import dataclass
import datetime
import itertools
import math
import os
@@ -2439,335 +2438,3 @@ class LocalRPC(SessionBase):
def protocol_version_string(self):
return 'RPC'
######################################################################
# Non-Bitcoin stuff goes strictly below this line.
######################################################################
class DashElectrumX(ElectrumX):
'''A TCP server that handles incoming Electrum Dash connections.'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mns = set()
self.mn_cache_height = 0
self.mn_cache = []
def set_request_handlers(self, ptuple):
super().set_request_handlers(ptuple)
self.request_handlers.update({
'masternode.announce.broadcast': self.phandle_masternode_announce_broadcast,
'masternode.subscribe': self.phandle_masternode_subscribe,
'masternode.list': self.phandle_masternode_list,
'protx.diff': self.phandle_protx_diff,
'protx.info': self.phandle_protx_info,
})
async def _notify_inner(
self,
*,
touched_hashxs,
touched_outpoints,
height_changed,
):
'''Notify the client about changes in masternode list.'''
await super()._notify_inner(
touched_hashxs=touched_hashxs,
touched_outpoints=touched_outpoints,
height_changed=height_changed,
)
for mn in self.mns.copy():
status = await self.daemon_request('masternode_list',
('status', mn))
await self.send_notification('masternode.subscribe',
(mn, status.get(mn)))
# Masternode command handlers
async def phandle_masternode_announce_broadcast(self, signmnb):
'''Pass through the masternode announce message to be broadcast
by the daemon.
signmnb: signed masternode broadcast message.'''
try:
return await self.daemon_request('masternode_broadcast',
('relay', signmnb))
except DaemonError as e:
error, = e.args
message = error['message']
self.logger.info(f'masternode_broadcast: {message}')
raise RPCError(BAD_REQUEST, 'the masternode broadcast was '
f'rejected.\n\n{message}\n[{signmnb}]')
async def phandle_masternode_subscribe(self, collateral):
'''Returns the status of masternode.
collateral: masternode collateral.
'''
result = await self.daemon_request('masternode_list',
('status', collateral))
if result is not None:
self.mns.add(collateral)
return result.get(collateral)
return None
async def phandle_masternode_list(self, payees):
'''
Returns the list of masternodes.
payees: a list of masternode payee addresses.
'''
if not isinstance(payees, list):
raise RPCError(BAD_REQUEST, 'expected a list of payees')
def get_masternode_payment_queue(mns):
'''Returns the calculated position in the payment queue for all the
valid masterernodes in the given mns list.
mns: a list of masternodes information.
'''
now = int(datetime.datetime.utcnow().strftime("%s"))
mn_queue = []
# Only ENABLED masternodes are considered for the list.
for line in mns:
mnstat = mns[line].split()
if mnstat[0] == 'ENABLED':
# if last paid time == 0
if int(mnstat[5]) == 0:
# use active seconds
mnstat.append(int(mnstat[4]))
else:
# now minus last paid
delta = now - int(mnstat[5])
# if > active seconds, use active seconds
if delta >= int(mnstat[4]):
mnstat.append(int(mnstat[4]))
# use active seconds
else:
mnstat.append(delta)
mn_queue.append(mnstat)
mn_queue = sorted(mn_queue, key=lambda x: x[8], reverse=True)
return mn_queue
def get_payment_position(payment_queue, address):
'''
Returns the position of the payment list for the given address.
payment_queue: position in the payment queue for the masternode.
address: masternode payee address.
'''
position = -1
for pos, mn in enumerate(payment_queue, start=1):
if mn[2] == address:
position = pos
break
return position
# Accordingly with the masternode payment queue, a custom list
# with the masternode information including the payment
# position is returned.
cache = self.session_mgr.mn_cache
if not cache or self.session_mgr.mn_cache_height != self.db.db_height:
full_mn_list = await self.daemon_request('masternode_list',
('full',))
mn_payment_queue = get_masternode_payment_queue(full_mn_list)
mn_payment_count = len(mn_payment_queue)
mn_list = []
for key, value in full_mn_list.items():
mn_data = value.split()
mn_info = {
'vin': key,
'status': mn_data[0],
'protocol': mn_data[1],
'payee': mn_data[2],
'lastseen': mn_data[3],
'activeseconds': mn_data[4],
'lastpaidtime': mn_data[5],
'lastpaidblock': mn_data[6],
'ip': mn_data[7]
}
mn_info['paymentposition'] = get_payment_position(
mn_payment_queue, mn_info['payee']
)
mn_info['inselection'] = (
mn_info['paymentposition'] < mn_payment_count // 10
)
hashX = self.coin.address_to_hashX(mn_info['payee'])
balance = await self.get_balance(hashX)
mn_info['balance'] = (sum(balance.values())
/ self.coin.VALUE_PER_COIN)
mn_list.append(mn_info)
cache.clear()
cache.extend(mn_list)
self.session_mgr.mn_cache_height = self.db.db_height
# If payees is an empty list the whole masternode list is returned
if payees:
return [mn for mn in cache if mn['payee'] in payees]
else:
return cache
async def phandle_protx_diff(self, base_height, height):
'''
Calculates a diff between two deterministic masternode lists.
The result also contains proof data.
base_height: The starting block height (starting from 1).
height: The ending block height.
'''
if not isinstance(base_height, int) or not isinstance(height, int):
raise RPCError(BAD_REQUEST, 'expected a int block heights')
max_height = self.db.db_height
if (not 1 <= base_height <= max_height or
not base_height <= height <= max_height):
raise RPCError(BAD_REQUEST,
f'require 1 <= base_height {base_height:,d} <= '
f'height {height:,d} <= '
f'chain height {max_height:,d}')
return await self.daemon_request('protx',
('diff', base_height, height))
async def phandle_protx_info(self, protx_hash):
'''
Returns detailed information about a deterministic masternode.
protx_hash: The hash of the initial ProRegTx
'''
if not isinstance(protx_hash, str):
raise RPCError(BAD_REQUEST, 'expected protx hash string')
res = await self.daemon_request('protx', ('info', protx_hash))
if 'wallet' in res:
del res['wallet']
return res
class SmartCashElectrumX(DashElectrumX):
'''A TCP server that handles incoming Electrum-SMART connections.'''
def set_request_handlers(self, ptuple):
super().set_request_handlers(ptuple)
self.request_handlers.update({
'smartrewards.current': self.phandle_smartrewards_current,
'smartrewards.check': self.phandle_smartrewards_check
})
async def phandle_smartrewards_current(self):
'''Returns the current smartrewards info.'''
result = await self.daemon_request('smartrewards', ('current',))
if result is not None:
return result
return None
async def phandle_smartrewards_check(self, addr):
'''
Returns the status of an address
addr: a single smartcash address
'''
result = await self.daemon_request('smartrewards', ('check', addr))
if result is not None:
return result
return None
class AuxPoWElectrumX(ElectrumX):
async def phandle_block_header(self, height, cp_height=0):
result = await super().phandle_block_header(height, cp_height)
# Older protocol versions don't truncate AuxPoW
if self.protocol_tuple < (1, 4, 1):
return result
# Not covered by a checkpoint; return full AuxPoW data
if cp_height == 0:
return result
# Covered by a checkpoint; truncate AuxPoW data
result['header'] = self.truncate_auxpow_single(result['header'])
return result
async def phandle_block_headers(self, start_height, count, cp_height=0):
# Older protocol versions don't truncate AuxPoW
if self.protocol_tuple < (1, 4, 1):
return await super().phandle_block_headers(start_height, count, cp_height)
# Not covered by a checkpoint; return full AuxPoW data
if cp_height == 0:
return await super().phandle_block_headers(start_height, count, cp_height)
result = await super().block_headers_array(start_height, count, cp_height)
# Covered by a checkpoint; truncate AuxPoW data
result['headers'] = self.truncate_auxpow_headers(result['headers'])
# Return headers in array form
if self.protocol_tuple >= (1, 6):
return result
# Return headers in concatenated form
result['hex'] = ''.join(result['headers'])
del result['headers']
return result
def truncate_auxpow_headers(self, headers):
result = []
for header in headers:
result.append(self.truncate_auxpow_single(header))
return result
def truncate_auxpow_single(self, header: str):
# 2 hex chars per byte
return header[:2*self.coin.TRUNCATED_HEADER_SIZE]
class NameIndexElectrumX(ElectrumX):
def set_request_handlers(self, ptuple):
super().set_request_handlers(ptuple)
if ptuple >= (1, 4, 3):
self.request_handlers['blockchain.name.get_value_proof'] = self.phandle_name_get_value_proof
async def phandle_name_get_value_proof(self, scripthash, cp_height=0):
history = await self.phandle_scripthash_get_history(scripthash)
trimmed_history = []
prev_height = None
for update in history[::-1]:
txid = update['tx_hash']
height = update['height']
if (self.coin.NAME_EXPIRATION is not None
and prev_height is not None
and height < prev_height - self.coin.NAME_EXPIRATION):
break
tx = await self.phandle_transaction_get(txid)
update['tx'] = tx
del update['tx_hash']
tx_merkle = await self.phandle_transaction_merkle(txid, height)
del tx_merkle['block_height']
update['tx_merkle'] = tx_merkle
if height <= cp_height:
header = await self.phandle_block_header(height, cp_height)
update['header'] = header
trimmed_history.append(update)
if height <= cp_height:
break
prev_height = height
return {scripthash: trimmed_history}
class NameIndexAuxPoWElectrumX(NameIndexElectrumX, AuxPoWElectrumX):
pass