Browse Source

Add Decred support (#550)

* Refactor reorg_hashes function

* Add Decred support
patch-2
John L. Jegutanis 7 years ago
committed by Neil
parent
commit
0815ff8e24
  1. 63
      electrumx/lib/coins.py
  2. 61
      electrumx/lib/tx.py
  3. 27
      electrumx/server/block_processor.py
  4. 90
      electrumx/server/daemon.py
  5. 15
      tests/blocks/decred_mainnet_100.json
  6. 21
      tests/blocks/decred_mainnet_120000.json
  7. 25
      tests/lib/test_addresses.py
  8. 60
      tests/test_transactions.py
  9. 79
      tests/transactions/decred_mainnet_9cde4a.json

63
electrumx/lib/coins.py

@ -43,7 +43,7 @@ from electrumx.lib.hash import Base58, hash160, double_sha256, hash_to_hex_str
from electrumx.lib.hash import HASHX_LEN from electrumx.lib.hash import HASHX_LEN
from electrumx.lib.script import ScriptPubKey, OpCodes from electrumx.lib.script import ScriptPubKey, OpCodes
import electrumx.lib.tx as lib_tx import electrumx.lib.tx as lib_tx
from electrumx.server.block_processor import BlockProcessor import electrumx.server.block_processor as block_proc
import electrumx.server.daemon as daemon import electrumx.server.daemon as daemon
from electrumx.server.session import ElectrumX, DashElectrumX from electrumx.server.session import ElectrumX, DashElectrumX
@ -69,7 +69,7 @@ class Coin(object):
SESSIONCLS = ElectrumX SESSIONCLS = ElectrumX
DESERIALIZER = lib_tx.Deserializer DESERIALIZER = lib_tx.Deserializer
DAEMON = daemon.Daemon DAEMON = daemon.Daemon
BLOCK_PROCESSOR = BlockProcessor BLOCK_PROCESSOR = block_proc.BlockProcessor
MEMPOOL_HISTOGRAM_REFRESH_SECS = 500 MEMPOOL_HISTOGRAM_REFRESH_SECS = 500
XPUB_VERBYTES = bytes('????', 'utf-8') XPUB_VERBYTES = bytes('????', 'utf-8')
XPRV_VERBYTES = bytes('????', 'utf-8') XPRV_VERBYTES = bytes('????', 'utf-8')
@ -1676,6 +1676,65 @@ class BitcoinAtom(Coin):
return deserializer.read_header(height, cls.BASIC_HEADER_SIZE) return deserializer.read_header(height, cls.BASIC_HEADER_SIZE)
class Decred(Coin):
NAME = "Decred"
SHORTNAME = "DCR"
NET = "mainnet"
XPUB_VERBYTES = bytes.fromhex("02fda926")
XPRV_VERBYTES = bytes.fromhex("02fda4e8")
P2PKH_VERBYTE = bytes.fromhex("073f")
P2SH_VERBYTES = [bytes.fromhex("071a")]
WIF_BYTE = bytes.fromhex("230e")
GENESIS_HASH = ('298e5cc3d985bfe7f81dc135f360abe0'
'89edd4396b86d2de66b0cef42b21d980')
BASIC_HEADER_SIZE = 180
HEADER_HASH = lib_tx.DeserializerDecred.blake256
DESERIALIZER = lib_tx.DeserializerDecred
DAEMON = daemon.DecredDaemon
BLOCK_PROCESSOR = block_proc.DecredBlockProcessor
ENCODE_CHECK = partial(Base58.encode_check,
hash_fn=lib_tx.DeserializerDecred.blake256d)
DECODE_CHECK = partial(Base58.decode_check,
hash_fn=lib_tx.DeserializerDecred.blake256d)
HEADER_UNPACK = struct.Struct('<i32s32s32sH6sHBBIIQIIII32sI').unpack_from
TX_COUNT = 4629388
TX_COUNT_HEIGHT = 260628
TX_PER_BLOCK = 17
REORG_LIMIT = 1000
RPC_PORT = 9109
@classmethod
def header_hash(cls, header):
'''Given a header return the hash.'''
return cls.HEADER_HASH(header)
@classmethod
def block(cls, raw_block, height):
'''Return a Block namedtuple given a raw block and its height.'''
if height > 0:
return super().block(raw_block, height)
else:
return Block(raw_block, cls.block_header(raw_block, height), [])
@classmethod
def electrum_header(cls, header, height):
labels = ('version', 'prev_block_hash', 'merkle_root', 'stake_root',
'vote_bits', 'final_state', 'voters', 'fresh_stake',
'revocations', 'pool_size', 'bits', 'sbits', 'block_height',
'size', 'timestamp', 'nonce', 'extra_data', 'stake_version')
values = cls.HEADER_UNPACK(header)
h = dict(zip(labels, values))
# Convert some values
assert h['block_height'] == height
h['prev_block_hash'] = hash_to_hex_str(h['prev_block_hash'])
h['merkle_root'] = hash_to_hex_str(h['merkle_root'])
h['stake_root'] = hash_to_hex_str(h['stake_root'])
h['final_state'] = h['final_state'].hex()
h['extra_data'] = h['extra_data'].hex()
return h
class Axe(Dash): class Axe(Dash):
NAME = "Axe" NAME = "Axe"
SHORTNAME = "AXE" SHORTNAME = "AXE"

61
electrumx/lib/tx.py

@ -29,6 +29,7 @@
from collections import namedtuple from collections import namedtuple
from struct import pack
from electrumx.lib.hash import sha256, double_sha256, hash_to_hex_str from electrumx.lib.hash import sha256, double_sha256, hash_to_hex_str
from electrumx.lib.util import ( from electrumx.lib.util import (
@ -428,8 +429,6 @@ class TxInputDcr(namedtuple("TxInput", "prev_hash prev_idx tree sequence")):
@cachedproperty @cachedproperty
def is_coinbase(self): def is_coinbase(self):
# The previous output of a coin base must have a max value index and a
# zero hash.
return (self.prev_hash == TxInputDcr.ZERO and return (self.prev_hash == TxInputDcr.ZERO and
self.prev_idx == TxInputDcr.MINUS_1) self.prev_idx == TxInputDcr.MINUS_1)
@ -440,13 +439,13 @@ class TxInputDcr(namedtuple("TxInput", "prev_hash prev_idx tree sequence")):
class TxOutputDcr(namedtuple("TxOutput", "value version pk_script")): class TxOutputDcr(namedtuple("TxOutput", "value version pk_script")):
'''Class representing a transaction output.''' '''Class representing a Decred transaction output.'''
pass pass
class TxDcr(namedtuple("Tx", "version inputs outputs locktime expiry " class TxDcr(namedtuple("Tx", "version inputs outputs locktime expiry "
"witness")): "witness")):
'''Class representing transaction that has a time field.''' '''Class representing a Decred transaction.'''
@cachedproperty @cachedproperty
def is_coinbase(self): def is_coinbase(self):
@ -454,22 +453,38 @@ class TxDcr(namedtuple("Tx", "version inputs outputs locktime expiry "
class DeserializerDecred(Deserializer): class DeserializerDecred(Deserializer):
@staticmethod @staticmethod
def blake256(data): def blake256(data):
from blake256.blake256 import blake_hash from blake256.blake256 import blake_hash
return blake_hash(data) return blake_hash(data)
@staticmethod
def blake256d(data):
from blake256.blake256 import blake_hash
return blake_hash(blake_hash(data))
def read_tx(self):
return self._read_tx_parts(produce_hash=False)[0]
def read_tx_and_hash(self):
tx, tx_hash, vsize = self._read_tx_parts()
return tx, tx_hash
def read_tx_and_vsize(self):
tx, tx_hash, vsize = self._read_tx_parts(produce_hash=False)
return tx, vsize
def read_tx_block(self): def read_tx_block(self):
'''Returns a list of (deserialized_tx, tx_hash) pairs.''' '''Returns a list of (deserialized_tx, tx_hash) pairs.'''
read_tx = self.read_tx read = self.read_tx_and_hash
txs = [read_tx() for _ in range(self._read_varint())] txs = [read() for _ in range(self._read_varint())]
stxs = [read_tx() for _ in range(self._read_varint())] stxs = [read() for _ in range(self._read_varint())]
return txs + stxs return txs + stxs
def _read_inputs(self): def read_tx_tree(self):
read_input = self._read_input '''Returns a list of deserialized_tx without tx hashes.'''
return [read_input() for i in range(self._read_varint())] read_tx = self.read_tx
return [read_tx() for _ in range(self._read_varint())]
def _read_input(self): def _read_input(self):
return TxInputDcr( return TxInputDcr(
@ -479,10 +494,6 @@ class DeserializerDecred(Deserializer):
self._read_le_uint32(), # sequence self._read_le_uint32(), # sequence
) )
def _read_outputs(self):
read_output = self._read_output
return [read_output() for _ in range(self._read_varint())]
def _read_output(self): def _read_output(self):
return TxOutputDcr( return TxOutputDcr(
self._read_le_int64(), # value self._read_le_int64(), # value
@ -502,15 +513,29 @@ class DeserializerDecred(Deserializer):
script = self._read_varbytes() script = self._read_varbytes()
return value_in, block_height, block_index, script return value_in, block_height, block_index, script
def read_tx(self): def _read_tx_parts(self, produce_hash=True):
start = self.cursor start = self.cursor
version = self._read_le_int32() version = self._read_le_int32()
inputs = self._read_inputs() inputs = self._read_inputs()
outputs = self._read_outputs() outputs = self._read_outputs()
locktime = self._read_le_uint32() locktime = self._read_le_uint32()
expiry = self._read_le_uint32() expiry = self._read_le_uint32()
no_witness_tx = b'\x01\x00\x01\x00' + self.binary[start+4:self.cursor] end_prefix = self.cursor
witness = self._read_witness(len(inputs)) witness = self._read_witness(len(inputs))
# Drop the coinbase-like input from a vote tx as it creates problems
# with UTXOs lookups and mempool management
if inputs[0].is_coinbase and len(inputs) > 1:
inputs = inputs[1:]
if produce_hash:
# TxSerializeNoWitness << 16 == 0x10000
no_witness_header = pack('<I', 0x10000 | (version & 0xffff))
prefix_tx = no_witness_header + self.binary[start+4:end_prefix]
tx_hash = self.blake256(prefix_tx)
else:
tx_hash = None
return TxDcr( return TxDcr(
version, version,
inputs, inputs,
@ -518,4 +543,4 @@ class DeserializerDecred(Deserializer):
locktime, locktime,
expiry, expiry,
witness witness
), DeserializerDecred.blake256(no_witness_tx) ), tx_hash, self.cursor - start

27
electrumx/server/block_processor.py

@ -263,6 +263,16 @@ class BlockProcessor(electrumx.server.db.DB):
The hashes are returned in order of increasing height. Start The hashes are returned in order of increasing height. Start
is the height of the first hash, last of the last. is the height of the first hash, last of the last.
''' '''
start, count = self.calc_reorg_range(count)
last = start + count - 1
s = '' if count == 1 else 's'
self.logger.info(f'chain was reorganised replacing {count:,d} '
f'block{s} at heights {start:,d}-{last:,d}')
return start, last, self.fs_block_hashes(start, count)
async def calc_reorg_range(self, count):
'''Calculate the reorg range'''
def diff_pos(hashes1, hashes2): def diff_pos(hashes1, hashes2):
'''Returns the index of the first difference in the hash lists. '''Returns the index of the first difference in the hash lists.
@ -291,12 +301,7 @@ class BlockProcessor(electrumx.server.db.DB):
else: else:
start = (self.height - count) + 1 start = (self.height - count) + 1
last = start + count - 1 return start, count
s = '' if count == 1 else 's'
self.logger.info(f'chain was reorganised replacing {count:,d} '
f'block{s} at heights {start:,d}-{last:,d}')
return start, last, self.fs_block_hashes(start, count)
def flush_state(self, batch): def flush_state(self, batch):
'''Flush chain state to the batch.''' '''Flush chain state to the batch.'''
@ -826,3 +831,13 @@ class BlockProcessor(electrumx.server.db.DB):
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 = super().calc_reorg_range(count)
if start > 0:
# A reorg in Decred can invalidate the previous block
start -= 1
count += 1
return start, count

90
electrumx/server/daemon.py

@ -17,8 +17,10 @@ from time import strptime
import aiohttp import aiohttp
from electrumx.lib.util import int_to_varint, hex_to_bytes, class_logger from electrumx.lib.util import int_to_varint, hex_to_bytes, class_logger, \
from electrumx.lib.hash import hex_str_to_hash unpack_uint16_from
from electrumx.lib.hash import hex_str_to_hash, hash_to_hex_str
from electrumx.lib.tx import DeserializerDecred
from aiorpcx import JSONRPC from aiorpcx import JSONRPC
@ -365,3 +367,87 @@ class LegacyRPCDaemon(Daemon):
if isinstance(t, int): if isinstance(t, int):
return t return t
return timegm(strptime(t, "%Y-%m-%d %H:%M:%S %Z")) return timegm(strptime(t, "%Y-%m-%d %H:%M:%S %Z"))
class DecredDaemon(Daemon):
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)
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_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(hex_hashes, 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_hashes(self):
mempool = await super().mempool_hashes()
# 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 client_session(self):
# FIXME allow self signed certificates
connector = aiohttp.TCPConnector(verify_ssl=False)
return aiohttp.ClientSession(connector=connector)

15
tests/blocks/decred_mainnet_100.json

@ -0,0 +1,15 @@
{
"hash": "0000000000017dd91008ec7c0ea63749b81d9a5188d9efc8d2d8cc0bdcff4d2a",
"size": 382,
"height": 100,
"merkleroot": "5c49629cefa3d5eb640a3236f6e970386e0b0826a5d33d566de36aec534fa93d",
"stakeroot": "0000000000000000000000000000000000000000000000000000000000000000",
"tx": [
"c813acfcad624ccf19e6240358b95cbbb1b728ee94557556dc373cceae1a7e4b"
],
"time": 1454961067,
"nonce": 3396292691,
"bits": "1b01ffff",
"previousblockhash": "000000000000dcecdf2c1ae9bb3e2e3135e7765b1902938ff67e2be489ab8131",
"block": "010000003181ab89e42b7ef68f9302195b76e735312e3ebbe91a2cdfecdc0000000000003da94f53ec6ae36d563dd3a526080b6e3870e9f636320a64ebd5a3ef9c62495c000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000ffff011b00c2eb0b00000000640000007e010000abf1b85653506fca9885f1c26941ecf1010000000000000000000000000000000000000000000000000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff03fa1a981200000000000017a914f5916158e3e2c4551c1796708db8367207ed13bb8700000000000000000000266a2464000000000000000000000000000000000000000000000000000000733ea5b290c04d1fdea1906f0000000000001976a9145b98376242c78de2003e7940d7e44270c39b83eb88ac000000000000000001d8bc28820000000000000000ffffffff0800002f646372642f00"
}

21
tests/blocks/decred_mainnet_120000.json

File diff suppressed because one or more lines are too long

25
tests/lib/test_addresses.py

@ -26,28 +26,31 @@
import pytest import pytest
from electrumx.lib.coins import Litecoin, BitcoinCash, Zcash, Emercoin, BitcoinGold import electrumx.lib.coins as coins
from electrumx.lib.hash import Base58
addresses = [ addresses = [
(BitcoinCash, "13xDKJbjh4acmLpNVr6Lc9hFcXRr9fyt4x", (coins.BitcoinCash, "13xDKJbjh4acmLpNVr6Lc9hFcXRr9fyt4x",
"206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"), "206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"),
(BitcoinCash, "3GxRZWkJufR5XA8hnNJgQ2gkASSheoBcmW", (coins.BitcoinCash, "3GxRZWkJufR5XA8hnNJgQ2gkASSheoBcmW",
"a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"), "a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"),
(BitcoinGold, "GZjH8pETu5xXd5DTt5VAqS9giooLNoHjnJ", (coins.BitcoinGold, "GZjH8pETu5xXd5DTt5VAqS9giooLNoHjnJ",
"ae40655d7006806fd668248d10e7822c0b774dab", "3a1af301b378ad92493b17"), "ae40655d7006806fd668248d10e7822c0b774dab", "3a1af301b378ad92493b17"),
(BitcoinGold, "AXfENBm9FP1PMa8AWnVPZZ4tHEwBiqNZav", (coins.BitcoinGold, "AXfENBm9FP1PMa8AWnVPZZ4tHEwBiqNZav",
"ae40655d7006806fd668248d10e7822c0b774dab", "cb3db4271432c0ac9f88d5"), "ae40655d7006806fd668248d10e7822c0b774dab", "cb3db4271432c0ac9f88d5"),
(Emercoin, "ELAeVHQg2mmdTTrTrZSzMgAQyXfC9TSRys", (coins.Emercoin, "ELAeVHQg2mmdTTrTrZSzMgAQyXfC9TSRys",
"210c4482ad8eacb0d349992973608300677adb15", "d71f2df4ef1b397088d731"), "210c4482ad8eacb0d349992973608300677adb15", "d71f2df4ef1b397088d731"),
(Litecoin, "LNBAaWuZmipg29WXfz5dtAm1pjo8FEH8yg", (coins.Litecoin, "LNBAaWuZmipg29WXfz5dtAm1pjo8FEH8yg",
"206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"), "206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"),
(Litecoin, "MPAZsQAGrnGWKfQbtFJ2Dfw9V939e7D3E2", (coins.Litecoin, "MPAZsQAGrnGWKfQbtFJ2Dfw9V939e7D3E2",
"a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"), "a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"),
(Zcash, "t1LppKe1sfPNDMysGSGuTjxoAsBcvvSYv5j", (coins.Zcash, "t1LppKe1sfPNDMysGSGuTjxoAsBcvvSYv5j",
"206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"), "206168f5322583ff37f8e55665a4789ae8963532", "b8cb80b26e8932f5b12a7e"),
(Zcash, "t3Zq2ZrASszCg7oBbio7oXqnfR6dnSWqo76", (coins.Zcash, "t3Zq2ZrASszCg7oBbio7oXqnfR6dnSWqo76",
"a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"), "a773db925b09add367dcc253c1f9bbc1d11ec6fd", "062d8515e50cb92b8a3a73"),
(coins.Decred, "DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
"2789d58cfa0957d206f025c2af056fc8a77cebb0", "8cc9b11122272bd7b79a50"),
(coins.Decred, "DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
"f0b4e85100aee1a996f22915eb3c3f764d53779a", "a03c1a27de9ac3b3122e8d"),
] ]

60
tests/test_transactions.py

@ -0,0 +1,60 @@
# Copyright (c) 2018, John L. Jegutanis
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
import json
import os
from binascii import unhexlify
import pytest
from electrumx.lib.coins import Coin
from electrumx.lib.hash import hash_to_hex_str
TRANSACTION_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'transactions')
# Find out which db engines to test
# Those that are not installed will be skipped
transactions = []
for name in os.listdir(TRANSACTION_DIR):
try:
name_parts = name.split("_")
coinFound = Coin.lookup_coin_class(name_parts[0], name_parts[1])
with open(os.path.join(TRANSACTION_DIR, name)) as f:
transactions.append((coinFound, json.load(f)))
except Exception as e:
transactions.append(pytest.fail(name))
@pytest.fixture(params=transactions)
def transaction_details(request):
return request.param
def test_transaction(transaction_details):
coin, tx_info = transaction_details
raw_tx = unhexlify(tx_info['hex'])
tx, tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash()
assert tx_info['txid'] == hash_to_hex_str(tx_hash)
vin = tx_info['vin']
for i in range(len(vin)):
assert vin[i]['txid'] == hash_to_hex_str(tx.inputs[i].prev_hash)
assert vin[i]['vout'] == tx.inputs[i].prev_idx
vout = tx_info['vout']
for i in range(len(vout)):
# value pk_script
assert vout[i]['value'] == tx.outputs[i].value
spk = vout[i]['scriptPubKey']
tx_pks = tx.outputs[i].pk_script
assert spk['hex'] == tx_pks.hex()
assert spk['address'] == coin.address_from_script(tx_pks)
assert coin.address_to_hashX(spk['address']) == \
coin.hashX_from_script(tx_pks)

79
tests/transactions/decred_mainnet_9cde4a.json

@ -0,0 +1,79 @@
{
"hex": "01000000022b7a5d0b09429a430ee2d465ff4ec1cec8080f66c59989ef03680dcb26a817460400000000ffffffff4d1bb7488ea38c16542cb80f505cc3d0d1757e3887ba25c8256619ec9ab1c5c00000000000ffffffff09eb3334030000000000001976a914ea9ab497c58159b8b0bf23becd179c076f8fbb9188ac78d0f4020000000000001976a9144820daabe97c3efcd3b34dfa51caf432d68d6a4d88ac541965340000000000001976a91495880e8c485ee28349d9885aa0fe448aa96e237488acb42f94000000000000001976a914a0d1579a51dbb6f26469944711d7e3f2f7a0b26088ac2a01fa020000000000001976a91444386bacdf3d7a353dabb2b334db9506791e83f288ac5c3be50b0000000000001976a9149f66566103e93e3cc29bebfed48450c3869e9a7688acc6fc91000000000000001976a91492ebcb796c918f5db19540726f91ca06a3b7640a88acd2b1c23c0000000000001976a9148806f0ae0b862006e39835d8cf91ac7f9d1c97d588ac6790f5020000000000001976a91418f82896bef63e67e125951bc464362eefdb00bb88ac0000000000000000021ee5573600000000bfd40100010000006a473044022051876acd9f0b716eaf383873db604cd7f39d659db2e909701dcbd655b469121e022036c165ab21c16865bab81696b41b3dcb85eb802867490b034f4cce2ac643b7640121026ea7725f46b9ab3931dd789907e80380eb91771cc681a1711c4468b71589d390ea20fe5300000000bad40100010000006a4730440220401007a7c4ba4b982babb08120b62f260484f61491e557f0a0d7d9c4484acf0e022041288a676750d421c63e09e7c11787a49bfec49e5490202220ddeb3406e2d3da01210371f6b9914081c10adfbac8fde9c9731c29fdb1bb235ba70f28cb0cf5f7268b2e",
"txid": "9cde4a9685fa9e38ccf4da4bda1c1a123b3f2ed2c418f6bc128d7f5fbbee413d",
"vin": [
{
"txid": "4617a826cb0d6803ef8999c5660f08c8cec14eff65d4e20e439a42090b5d7a2b",
"vout": 4
},
{
"txid": "c0c5b19aec196625c825ba87387e75d1d0c35c500fb82c54168ca38e48b71b4d",
"vout": 0
}
],
"vout": [
{
"value": 53752811,
"scriptPubKey": {
"hex": "76a914ea9ab497c58159b8b0bf23becd179c076f8fbb9188ac",
"address": "DsnMNwSYjwgks3c1kWVbaB6npxv8LXnGc8U"
}
},
{
"value": 49598584,
"scriptPubKey": {
"hex": "76a9144820daabe97c3efcd3b34dfa51caf432d68d6a4d88ac",
"address": "DsXYHVzSPfo4jrYAkLJN7yFuAfY3mxA4JGy"
}
},
{
"value": 879040852,
"scriptPubKey": {
"hex": "76a91495880e8c485ee28349d9885aa0fe448aa96e237488ac",
"address": "DsebZAQadwBurUCowGbsbgYTpMETHBibZLf"
}
},
{
"value": 9711540,
"scriptPubKey": {
"hex": "76a914a0d1579a51dbb6f26469944711d7e3f2f7a0b26088ac",
"address": "DsfdEPUTpsCBJjjS8ovgp65uYwws18FQpas"
}
},
{
"value": 49938730,
"scriptPubKey": {
"hex": "76a91444386bacdf3d7a353dabb2b334db9506791e83f288ac",
"address": "DsXBd2eWDHujo7wjYTsUwRw7wHoiYN5Ynhg"
}
},
{
"value": 199572316,
"scriptPubKey": {
"hex": "76a9149f66566103e93e3cc29bebfed48450c3869e9a7688ac",
"address": "DsfVjXTdcisQ6j5c5ZrhLFfJC56ZMCCwkr2"
}
},
{
"value": 9567430,
"scriptPubKey": {
"hex": "76a91492ebcb796c918f5db19540726f91ca06a3b7640a88ac",
"address": "DseMkckQQVxjEu6t2bTD65gW4EY2GiGuhNk"
}
},
{
"value": 1019392466,
"scriptPubKey": {
"hex": "76a9148806f0ae0b862006e39835d8cf91ac7f9d1c97d588ac",
"address": "DsdN9hjHeY8FiDYdEpesfTy215sdN5Ly74v"
}
},
{
"value": 49647719,
"scriptPubKey": {
"hex": "76a91418f82896bef63e67e125951bc464362eefdb00bb88ac",
"address": "DsTEvzPjXppZGYkLXZdZN16FwGaeMEjudpe"
}
}
]
}
Loading…
Cancel
Save