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:
John L. Jegutanis 2017-06-10 19:41:44 +03:00
parent 0cf4210a66
commit 1e9a65dccb
3 changed files with 103 additions and 5 deletions

View File

@ -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])

View File

@ -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.

View File

@ -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"))