Handle legacy daemon RPCs
Add support 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 ones as in the underlying blockchain but it is good enough for our indexing purposes.
This commit is contained in:
parent
0cf4210a66
commit
1e9a65dccb
31
lib/tx.py
31
lib/tx.py
@ -123,6 +123,11 @@ class Deserializer(object):
|
||||
self._read_varbytes(), # pk_script
|
||||
)
|
||||
|
||||
def _read_byte(self):
|
||||
cursor = self.cursor
|
||||
self.cursor += 1
|
||||
return self.binary[cursor]
|
||||
|
||||
def _read_nbytes(self, n):
|
||||
cursor = self.cursor
|
||||
self.cursor = end = cursor + n
|
||||
@ -182,11 +187,6 @@ class DeserializerSegWit(Deserializer):
|
||||
|
||||
# https://bitcoincore.org/en/segwit_wallet_dev/#transaction-serialization
|
||||
|
||||
def _read_byte(self):
|
||||
cursor = self.cursor
|
||||
self.cursor += 1
|
||||
return self.binary[cursor]
|
||||
|
||||
def _read_witness(self, fields):
|
||||
read_witness_field = self._read_witness_field
|
||||
return [read_witness_field() for i in range(fields)]
|
||||
@ -290,3 +290,24 @@ class DeserializerZcash(Deserializer):
|
||||
self.cursor += 32 # joinSplitPubKey
|
||||
self.cursor += 64 # joinSplitSig
|
||||
return base_tx, double_sha256(self.binary[start:self.cursor])
|
||||
|
||||
|
||||
class TxTime(namedtuple("Tx", "version time inputs outputs locktime")):
|
||||
'''Class representing transaction that has a time field.'''
|
||||
|
||||
@cachedproperty
|
||||
def is_coinbase(self):
|
||||
return self.inputs[0].is_coinbase
|
||||
|
||||
|
||||
class DeserializerTxTime(Deserializer):
|
||||
def read_tx(self):
|
||||
start = self.cursor
|
||||
|
||||
return TxTime(
|
||||
self._read_le_int32(), # version
|
||||
self._read_le_uint32(), # time
|
||||
self._read_inputs(), # inputs
|
||||
self._read_outputs(), # outputs
|
||||
self._read_le_uint32(), # locktime
|
||||
), double_sha256(self.binary[start:self.cursor])
|
||||
|
||||
15
lib/util.py
15
lib/util.py
@ -34,6 +34,7 @@ import logging
|
||||
import re
|
||||
import sys
|
||||
from collections import Container, Mapping
|
||||
from struct import pack
|
||||
|
||||
|
||||
class LoggedClass(object):
|
||||
@ -156,6 +157,20 @@ def int_to_bytes(value):
|
||||
return value.to_bytes((value.bit_length() + 7) // 8, 'big')
|
||||
|
||||
|
||||
def int_to_varint(value):
|
||||
'''Converts an integer to a Bitcoin-like varint bytes'''
|
||||
if value < 0:
|
||||
raise Exception("attempt to write size < 0")
|
||||
elif value < 253:
|
||||
return pack('<B', value)
|
||||
elif value < 2**16:
|
||||
return b'\xfd' + pack('<H', value)
|
||||
elif value < 2**32:
|
||||
return b'\xfe' + pack('<I', value)
|
||||
elif value < 2**64:
|
||||
return b'\xff' + pack('<Q', value)
|
||||
|
||||
|
||||
def increment_byte_string(bs):
|
||||
'''Return the lexicographically next byte string of the same length.
|
||||
|
||||
|
||||
@ -12,10 +12,14 @@ import asyncio
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
from calendar import timegm
|
||||
from struct import pack
|
||||
from time import strptime
|
||||
|
||||
import aiohttp
|
||||
|
||||
import lib.util as util
|
||||
from lib.hash import hex_str_to_hash
|
||||
|
||||
|
||||
class DaemonError(Exception):
|
||||
@ -256,3 +260,61 @@ class DashDaemon(Daemon):
|
||||
async def masternode_list(self, params ):
|
||||
'''Return the masternode status.'''
|
||||
return await self._send_single('masternodelist', params)
|
||||
|
||||
|
||||
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, hex_hashes):
|
||||
'''Return the raw binary blocks with the given hex hashes.'''
|
||||
params_iterable = ((h, False) for h in hex_hashes)
|
||||
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
|
||||
header = 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')))
|
||||
return header
|
||||
|
||||
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 += util.int_to_varint(num_txs)
|
||||
raw_block += b''.join(transactions)
|
||||
else:
|
||||
raw_block += b'\x00'
|
||||
|
||||
return raw_block
|
||||
|
||||
def timestamp_safe(self, t):
|
||||
return t if isinstance(t, int) else timegm(strptime(t, "%Y-%m-%d %H:%M:%S %Z"))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user