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:
1 parent
73c96101bd
commit
05f5fc9058
6 files changed
+3
-2745
No files matched your search
+2
-1139
File diff suppressed because it is too large.
Load diff
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -904,107 +904,3 @@ class BlockProcessor:
|
|||||||
self.blocks_event.set()
|
self.blocks_event.set()
|
||||||
return True
|
return True
|
||||||
return False
|
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)
|
|
||||||
@@ -11,18 +11,13 @@ daemon.'''
|
|||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
import time
|
import time
|
||||||
from calendar import timegm
|
|
||||||
from struct import pack
|
|
||||||
from typing import TYPE_CHECKING, Type, Sequence, Any, Iterable, Optional
|
from typing import TYPE_CHECKING, Type, Sequence, Any, Iterable, Optional
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiorpcx import JSONRPC
|
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,
|
from electrumx.lib.util import (class_logger, hex_to_bytes, json_deserialize,
|
||||||
json_serialize, pack_varint,
|
json_serialize)
|
||||||
unpack_le_uint16_from)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from electrumx.lib.coins import Coin
|
from electrumx.lib.coins import Coin
|
||||||
@@ -404,235 +399,3 @@ class Daemon:
|
|||||||
|
|
||||||
If the daemon has not been queried yet this returns None.'''
|
If the daemon has not been queried yet this returns None.'''
|
||||||
return self._height
|
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)
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import codecs
|
import codecs
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime
|
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
@@ -2439,335 +2438,3 @@ class LocalRPC(SessionBase):
|
|||||||
|
|
||||||
def protocol_version_string(self):
|
def protocol_version_string(self):
|
||||||
return 'RPC'
|
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
|
|
||||||
Reference in new issue
Block a user