Browse Source

Merge pull request #192 from erasmospunk/legacy-rpc

Legacy rpc and coin additions
master
Neil 8 years ago
committed by GitHub
parent
commit
721685ba4b
  1. 83
      lib/coins.py
  2. 53
      lib/tx.py
  3. 15
      lib/util.py
  4. 62
      server/daemon.py
  5. 19
      tests/blocks/digibyte_mainnet_4394891.json
  6. 15
      tests/blocks/reddcoin_mainnet_1200000.json
  7. 20
      tests/blocks/reddcoin_mainnet_8000.json
  8. 2
      tests/test_blocks.py

83
lib/coins.py

@ -39,9 +39,10 @@ from hashlib import sha256
import lib.util as util import lib.util as util
from lib.hash import Base58, hash160, double_sha256, hash_to_str from lib.hash import Base58, hash160, double_sha256, hash_to_str
from lib.script import ScriptPubKey from lib.script import ScriptPubKey
from lib.tx import Deserializer, DeserializerSegWit, DeserializerAuxPow, DeserializerZcash from lib.tx import Deserializer, DeserializerSegWit, DeserializerAuxPow, \
DeserializerZcash, DeserializerTxTime, DeserializerReddcoin
from server.block_processor import BlockProcessor from server.block_processor import BlockProcessor
from server.daemon import Daemon from server.daemon import Daemon, LegacyRPCDaemon
from server.session import ElectrumX from server.session import ElectrumX
Block = namedtuple("Block", "header transactions") Block = namedtuple("Block", "header transactions")
@ -667,6 +668,7 @@ class DigiByte(Coin):
WIF_BYTE = bytes.fromhex("80") WIF_BYTE = bytes.fromhex("80")
GENESIS_HASH = ('7497ea1b465eb39f1c8f507bc877078f' GENESIS_HASH = ('7497ea1b465eb39f1c8f507bc877078f'
'e016d6fcb6dfad3a64c98dcc6e1e8496') 'e016d6fcb6dfad3a64c98dcc6e1e8496')
DESERIALIZER = DeserializerSegWit
TX_COUNT = 1046018 TX_COUNT = 1046018
TX_COUNT_HEIGHT = 1435000 TX_COUNT_HEIGHT = 1435000
TX_PER_BLOCK = 1000 TX_PER_BLOCK = 1000
@ -801,3 +803,80 @@ class Einsteinium(Coin):
IRC_PREFIX = "E_" IRC_PREFIX = "E_"
IRC_CHANNEL = "#electrum-emc2" IRC_CHANNEL = "#electrum-emc2"
RPC_PORT = 41879 RPC_PORT = 41879
REORG_LIMIT = 2000
class Blackcoin(Coin):
NAME = "Blackcoin"
SHORTNAME = "BLK"
NET = "mainnet"
XPUB_VERBYTES = bytes.fromhex("0488B21E")
XPRV_VERBYTES = bytes.fromhex("0488ADE4")
P2PKH_VERBYTE = bytes.fromhex("19")
P2SH_VERBYTES = [bytes.fromhex("55")]
WIF_BYTE = bytes.fromhex("99")
GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d'
'f2c183bf232f263d0ba5b101911e4563')
DESERIALIZER = DeserializerTxTime
DAEMON = LegacyRPCDaemon
TX_COUNT = 4594999
TX_COUNT_HEIGHT = 1667070
TX_PER_BLOCK = 3
IRC_PREFIX = "E_"
IRC_CHANNEL = "#electrum-blk"
RPC_PORT = 15715
REORG_LIMIT = 5000
HEADER_HASH = None
@classmethod
def header_hash(cls, header):
'''Given a header return the hash.'''
if cls.HEADER_HASH is None:
import scrypt
cls.HEADER_HASH = lambda x: scrypt.hash(x, x, 1024, 1, 1, 32)
version, = struct.unpack('<I', header[:4])
if version > 6:
return super().header_hash(header)
else:
return cls.HEADER_HASH(header);
class Peercoin(Coin):
NAME = "Peercoin"
SHORTNAME = "PPC"
NET = "mainnet"
P2PKH_VERBYTE = bytes.fromhex("37")
P2SH_VERBYTES = [bytes.fromhex("75")]
WIF_BYTE = bytes.fromhex("b7")
GENESIS_HASH = ('0000000032fe677166d54963b62a4677'
'd8957e87c508eaa4fd7eb1c880cd27e3')
DESERIALIZER = DeserializerTxTime
DAEMON = LegacyRPCDaemon
TX_COUNT = 1207356
TX_COUNT_HEIGHT = 306425
TX_PER_BLOCK = 4
IRC_PREFIX = "E_"
IRC_CHANNEL = "#electrum-ppc"
RPC_PORT = 9902
REORG_LIMIT = 5000
class Reddcoin(Coin):
NAME = "Reddcoin"
SHORTNAME = "RDD"
NET = "mainnet"
XPUB_VERBYTES = bytes.fromhex("0488B21E")
XPRV_VERBYTES = bytes.fromhex("0488ADE4")
P2PKH_VERBYTE = bytes.fromhex("3d")
P2SH_VERBYTES = [bytes.fromhex("05")]
WIF_BYTE = bytes.fromhex("bd")
GENESIS_HASH = ('b868e0d95a3c3c0e0dadc67ee587aaf9'
'dc8acbf99e3b4b3110fad4eb74c1decc')
DESERIALIZER = DeserializerReddcoin
TX_COUNT = 5413508
TX_COUNT_HEIGHT = 1717382
TX_PER_BLOCK = 3
IRC_PREFIX = "E_"
IRC_CHANNEL = "#electrum-rdd"
RPC_PORT = 45443

53
lib/tx.py

@ -123,6 +123,11 @@ class Deserializer(object):
self._read_varbytes(), # pk_script self._read_varbytes(), # pk_script
) )
def _read_byte(self):
cursor = self.cursor
self.cursor += 1
return self.binary[cursor]
def _read_nbytes(self, n): def _read_nbytes(self, n):
cursor = self.cursor cursor = self.cursor
self.cursor = end = cursor + n self.cursor = end = cursor + n
@ -182,11 +187,6 @@ class DeserializerSegWit(Deserializer):
# https://bitcoincore.org/en/segwit_wallet_dev/#transaction-serialization # 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): def _read_witness(self, fields):
read_witness_field = self._read_witness_field read_witness_field = self._read_witness_field
return [read_witness_field() for i in range(fields)] return [read_witness_field() for i in range(fields)]
@ -290,3 +290,46 @@ class DeserializerZcash(Deserializer):
self.cursor += 32 # joinSplitPubKey self.cursor += 32 # joinSplitPubKey
self.cursor += 64 # joinSplitSig self.cursor += 64 # joinSplitSig
return base_tx, double_sha256(self.binary[start:self.cursor]) 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])
class DeserializerReddcoin(Deserializer):
def read_tx(self):
start = self.cursor
version = self._read_le_int32()
inputs = self._read_inputs()
outputs = self._read_outputs()
locktime = self._read_le_uint32()
if version > 1:
time = self._read_le_uint32()
else:
time = 0
return TxTime(
version,
time,
inputs,
outputs,
locktime,
), double_sha256(self.binary[start:self.cursor])

15
lib/util.py

@ -34,6 +34,7 @@ import logging
import re import re
import sys import sys
from collections import Container, Mapping from collections import Container, Mapping
from struct import pack
class LoggedClass(object): class LoggedClass(object):
@ -156,6 +157,20 @@ def int_to_bytes(value):
return value.to_bytes((value.bit_length() + 7) // 8, 'big') 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): def increment_byte_string(bs):
'''Return the lexicographically next byte string of the same length. '''Return the lexicographically next byte string of the same length.

62
server/daemon.py

@ -12,10 +12,14 @@ import asyncio
import json import json
import time import time
import traceback import traceback
from calendar import timegm
from struct import pack
from time import strptime
import aiohttp import aiohttp
import lib.util as util import lib.util as util
from lib.hash import hex_str_to_hash
class DaemonError(Exception): class DaemonError(Exception):
@ -256,3 +260,61 @@ class DashDaemon(Daemon):
async def masternode_list(self, params ): async def masternode_list(self, params ):
'''Return the masternode status.''' '''Return the masternode status.'''
return await self._send_single('masternodelist', params) 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"))

19
tests/blocks/digibyte_mainnet_4394891.json

File diff suppressed because one or more lines are too long

15
tests/blocks/reddcoin_mainnet_1200000.json

@ -0,0 +1,15 @@
{
"hash": "bea68724bfcdc5d35bf9bbf9cb6680e196e5661afe95b2a205e74a2fe175ac79",
"size": 443,
"height": 1200000,
"merkleroot": "504b073c16d872d24f2c3de8a4c2c76d08df5056f3a4a8d0e32ff4220215a250",
"tx": [
"6aaad9725ae7beb40d80dac9c5300a8e1cf8783adb0ea41da4988b3476bda9b8",
"4a949402995c11b3306c0c91fd85edf0d3eb8dee4bf6bd07a241fa170156cd3c"
],
"time": 1463612841,
"nonce": 0,
"bits": "1c0a4691",
"previousblockhash": "438b564171da6fbbe6fd9d52c16ea2b1aa8c169951822225cf097d5da7cdba76",
"block": "0300000076bacda75d7d09cf2522825199168caab1a26ec1529dfde6bb6fda7141568b4350a2150222f42fe3d0a8a4f35650df086dc7c2a4e83d2c4fd272d8163c074b50a9f53c5791460a1c000000000202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff020000ffffffff0100000000000000000000000000a9f53c570200000001a40cad8a9afe2888f746d762cb36649b5afd4e8ce4468fd8d08fc296d26dc4840100000048473044022036392ee6eb58c5a9a2a681692cabdc2b00166c374cfb711055bc2c4d6c61a1d40220475728eed260bf972ef44909f0d6fa282f17e92b5e57ee383c7171e8a3baee1f01ffffffff030000000000000000000056b12a38720000232102bee8ce24a99260fbb6c10f0b904498fa71ec08e51b531878d3f6568ef09acb91ac0ad6b22a38720000232102bee8ce24a99260fbb6c10f0b904498fa71ec08e51b531878d3f6568ef09acb91ac00000000a9f53c57473045022100fe801bae06c9db3076fad2f72930f76dbe1cae29a162447b13d0df749e5913df02203621013f87da4dbca08702d8c7975f702bad9df40902038b93e622a0dd9c0896"
}

20
tests/blocks/reddcoin_mainnet_8000.json

@ -0,0 +1,20 @@
{
"hash": "4889bb7d1ba24cc66c2d903f6643b0ade243aca5101a8aff87ab4c2ab2a15ec5",
"size": 1560,
"height": 80000,
"merkleroot": "193313cfa4d8a4bc15fb5526b69a87c922e0f6520295f66165358f0af6b5d637",
"tx": [
"ad01e368a301b855d5f4499bc787b161428d6994c4847c0b2813950630a73950",
"1799481d7fed61c029159d314f75f3d6f69a7f8c237443470394085307802782",
"8db4b2c62fca987462c482d24ce0b78d2a3dd3928d5d99112ccad75deb6ff7de",
"ab0a1e66e54c737be6ea2be2c61cd55879d33c0fc5d35aa6389487e06c809cfc",
"1bb3854ed7fe9905b5637d405cd0715e5cb6f5fe233304a1588c53bdcf60f593",
"08d3ccf77f30e62d8773669adea730698806516239933ac7c4285bcacdb37989",
"19cbdc4acfb07dc29c73f039d8f5db967ce30c0667fda60babc700a7c53c0b5f"
],
"time": 1396181239,
"nonce": 1368399360,
"bits": "1c0cc111",
"previousblockhash": "e34cfbf84095c64276ee098de50c3a1f863df9336968e9fb8a973bdd52e3ed04",
"block": "0200000004ede352dd3b978afbe9686933f93d861f3a0ce58d09ee7642c69540f8fb4ce337d6b5f60a8f356561f6950252f6e022c9879ab62655fb15bca4d8a4cf133319f708385311c10c1c001e90510701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2703803801062f503253482f04f608385308f800f159010000000d2f7374726174756d506f6f6c2f000000000100e63572180900001976a9148d41bc27ab2cc999338750edd4b0012bdb36f70288ac0000000001000000014b1a8085e29ca8ef2bf1de5f0637c6ef597b67223a087458e51e21168a0e44a3000000006b48304502200b781c255481e90f0e1d2fedbc1ffb42562434c324566444da8718a8a2c5182d022100f50faa7a9f7b90b4b805050c9731a79fb9c599ddfb3d84449d0cff7ee216bf59012103d7ab8ea88d09589410bdb93cd466d92f56985a3cff6d74dce3f033500135f0c5ffffffff02d72ea96581330e001976a91422758e8f790ea0e4ab87ad3990e8af86c77375c088ac1c1cab70190000001976a91434e880ed4cb32ebb1e0842b4f05efe562724f08788ac000000000100000001616c5b1a7ee823fa2d5347011b34e1ea027f9494823d37fb175eece8f852f987000000006a473044022000be9cf6677d879d170c597b8a465137577119ebc7d01773dc13df7af7e0bf1102202acfce90f478c0d179ab98d708f1e24f6dab4fe60c75893f8bad12991b30f41301210355dad820f63f1c315dc16c5afd9782e4d0b225ea29320a85576bc2c82fde6e7effffffff02ceb618fa97ac10001976a914e14548bfd2e14e0cabaf535c7c80a227238b35e188ac1c1cab70190000001976a914d2046a1ad1dbc32e69dae4da0a8730379105936e88ac000000000100000001a6b3081431b43c3247df88b3b6d123d2f2d7ba2095c6ef4f6532feb2c45f9210010000006b4830450221008fb902cc4130bae26439c47c13467a7d8a8c52ac2d88a200548f1e8f8b100b910220125b45cee0765389a59d4cca65482bdf79d3bc8fdaa5a0142e7829e4a2568124012103cdece1576249c8e05fb0aa2cbe61aa959330ff2f9e3c5cd2e5152e90650d9386ffffffff02bbba56d0d88606001976a91407499b20688a0b61b4a526681647de739dab818e88ac1c1cab70190000001976a9147085556af12556138277188e3958a869eeced02088ac000000000100000001fc9c806ce0879438a65ad3c50f3cd37958d51cc6e22beae67b734ce5661e0aab000000006c493046022100dca959b02a4dde588b3e5c3e71877797b97d7094a82cdd6b6b52c3d04a8c17c3022100938b2f70eed007d20ef9d7d055fc9b8785e71e3f0981558503fb3635b08aa6d40121039d216b71bad34246ceff262afe6df520761fc696fd9862c3f2f7e337ad93d881ffffffff0202386cc4f57e06001976a914ee343e816e6782262c3f6b1b9ec8f8c17d47a88c88acb9a1f405e30700001976a914ba81e33df7ba3d18728c6c206f8ad0b30b83b71988ac00000000010000000193f560cfbd538c58a1043323fef5b65c5e71d05c407d63b50599fed74e85b31b000000006a4730440220153f0a0a16e13943c4869e8f768c64e9f1844d14823f80878a6e44752a041c49022036ec13a307bafee74387048c3772cfb5ebdc138d70d6b4c256788a86db93ab5801210281232e155b37ebd64759ee4983962e9f8ccfd95e302d828de1406549e7c327a4ffffffff029014fad0166506001976a914b05959ea5dd831fd082488298466c9307a46f55b88ac72427cedde1900001976a914c2e3e90990f452c19ccef5df1cc3711c2e5d448288ac0000000001000000018979b3cdca5b28c4c73a9339625106886930a7de9a6673872de6307ff7ccd308000000006b483045022100ec50258bfec642e6c986192f338b7a1eec84c872d9b51ccc6f1c7329da20af77022047a6836d7c5f416c2eef6ef59fae9cc627ff80882897fe3eabd775e2a4a08533012102240bb70ae679cb25d60e2e0f90f98017eac7b6abbf1e00797ef930f02f0b98eeffffffff029e75ab66cd6306001976a9144c9ef3b178febefc62a0067e67e8434afe864a6788acf2bd5864490100001976a9143cde6d950e730b199c5857564afe7f222e139ead88ac00000000"
}

2
tests/test_blocks.py

@ -47,7 +47,7 @@ for name in os.listdir(BLOCKS_DIR):
with open(os.path.join(BLOCKS_DIR, name)) as f: with open(os.path.join(BLOCKS_DIR, name)) as f:
blocks.append((coin, json.load(f))) blocks.append((coin, json.load(f)))
except Exception as e: except Exception as e:
blocks.append(pytest.mark.skip(name)) blocks.append(pytest.fail(name))
@pytest.fixture(params=blocks) @pytest.fixture(params=blocks)

Loading…
Cancel
Save