Add Block class and handling

This commit is contained in:
Michał Sałaban 2020-01-22 11:37:03 +01:00
parent ccf5066739
commit 1b503fd0ab
6 changed files with 250 additions and 0 deletions

View File

@ -9,6 +9,7 @@ import requests
from .. import exceptions
from ..account import Account
from ..address import address, Address, SubAddress
from ..block import Block
from ..const import NET_MAIN, NET_TEST, NET_STAGE
from ..numbers import from_atomic, to_atomic, PaymentID
from ..seed import Seed
@ -94,6 +95,33 @@ class JSONRPCDaemon(object):
return res['headers']
raise exceptions.BackendException(res['status'])
def block(self, bhash=None, height=None):
data = {}
if bhash:
data['hash'] = bhash
if height:
data['height'] = height
res = self.raw_jsonrpc_request('get_block', data)
if res['status'] == 'OK':
bhdr = res['block_header']
sub_json = json.loads(res['json'])
data = {
'blob': res['blob'],
'hash': bhdr['hash'],
'height': bhdr['height'],
'timestamp': datetime.fromtimestamp(bhdr['timestamp']),
'version': (bhdr['major_version'], bhdr['minor_version']),
'difficulty': bhdr['difficulty'],
'nonce': bhdr['nonce'],
'orphan': bhdr['orphan_status'],
'prev_hash': bhdr['prev_hash'],
'reward': bhdr['reward'],
'transactions': self.transactions(
[bhdr['miner_tx_hash']] + sub_json['tx_hashes']),
}
return Block(**data)
raise exceptions.BackendException(res['status'])
def transactions(self, hashes):
res = self.raw_request('/get_transactions', {
'txs_hashes': hashes,

42
monero/block.py Normal file
View File

@ -0,0 +1,42 @@
import operator
import six
from .transaction import Transaction
class Block(object):
"""
A Monero block. Identified by `hash` (optionaly by `height`).
This class is not intended to be turned into objects by the user,
it is used by backends.
"""
hash = None
height = None
timestamp = None
version = None
difficulty = None
nonce = None
orphan = False
prev_hash = None
reward = None
blob = None
transactions = None
def __init__(self, **kwargs):
for k in ('hash', 'height', 'timestamp', 'version', 'difficulty', 'nonce', 'prev_hash', 'reward'):
setattr(self, k, kwargs[k])
self.orphan = kwargs['orphan']
self.transactions = kwargs['transactions']
self.blob = kwargs.get('blob', None)
def __contains__(self, tx):
if isinstance(tx, six.string_types):
txid = tx
elif isinstance(tx, Transaction):
txid = tx.hash
else:
raise ValueError(
"Only Transaction or hash strings may be used to test existence in Blocks, "
"got '{:s}'".format(tx))
return txid in map(operator.attrgetter('hash'), self.transactions)

View File

@ -58,6 +58,19 @@ class Daemon(object):
"""
return self._backend.headers(start_height, end_height)
def block(self, bhash=None, height=None):
"""
Returns a block of specified height or hash.
:param str bhash: block hash, or
:param int height: block height
:rtype: :class:`Block <monero.block.Block>`
"""
if not height and not bhash:
raise ValueError("Height or hash must be specified")
return self._backend.block(bhash=bhash, height=height)
def transactions(self, hashes):
"""
Returns transactions matching given hashes. Accepts single hash or a sequence.

View File

@ -0,0 +1,43 @@
{
"id": 0,
"jsonrpc": "2.0",
"result": {
"blob": "0b0cd6e0afee0551f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce413af2580802d4cb1b01ff98cb1b01d6a9ddfc9bbe0302e351068aea8f05e9e30f876c3f203a89d09e7e07786979d79df6c7f8cbb75ff12101aaa20f9b81d64636002de35a6a4914ac4fb36145da0ba0f9d670e6c2b6a11c8000047e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de124fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0",
"block_header": {
"block_size": 9632,
"block_weight": 9632,
"cumulative_difficulty": 11915811790,
"cumulative_difficulty_top64": 0,
"depth": 49003,
"difficulty": 3590,
"difficulty_top64": 0,
"hash": "423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89",
"height": 451992,
"long_term_weight": 9632,
"major_version": 11,
"miner_tx_hash": "f2bd4cb3dafd5c096be7e1ec908f98bf34903f5a013faa65a0d0c8998154c583",
"minor_version": 12,
"nonce": 140046906,
"num_txes": 4,
"orphan_status": false,
"pow_hash": "",
"prev_hash": "51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41",
"reward": 15331952645334,
"timestamp": 1573646422,
"wide_cumulative_difficulty": "0x2c63cdbce",
"wide_difficulty": "0xe06"
},
"credits": 0,
"json": "{\n \"major_version\": 11, \n \"minor_version\": 12, \n \"timestamp\": 1573646422, \n \"prev_id\": \"51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41\", \n \"nonce\": 140046906, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 452052, \n \"vin\": [ {\n \"gen\": {\n \"height\": 451992\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 15331952645334, \n \"target\": {\n \"key\": \"e351068aea8f05e9e30f876c3f203a89d09e7e07786979d79df6c7f8cbb75ff1\"\n }\n }\n ], \n \"extra\": [ 1, 170, 162, 15, 155, 129, 214, 70, 54, 0, 45, 227, 90, 106, 73, 20, 172, 79, 179, 97, 69, 218, 11, 160, 249, 214, 112, 230, 194, 182, 161, 28, 128\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ \"7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b\", \"3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106\", \"bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1\", \"24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0\"\n ]\n}",
"miner_tx_hash": "",
"status": "OK",
"top_hash": "",
"tx_hashes": [
"7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b",
"3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106",
"bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1",
"24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0"
],
"untrusted": false
}
}

View File

@ -56,6 +56,29 @@ class JSONRPCDaemonTestCase(JSONTestCase):
self.assertGreater(txs[0].fee, 0)
self.assertGreater(txs[1].fee, 0)
@responses.activate
def test_block(self):
responses.add(responses.POST, self.jsonrpc_url,
json=self._read("test_block-423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89.json"),
status=200)
responses.add(responses.POST, self.transactions_url,
json=self._read("test_block-423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89-txns.json"),
status=200)
blk = self.daemon.block("423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89")
self.assertEqual(
blk.hash,
"423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89")
self.assertEqual(blk.height, 451992)
self.assertIn("24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0", blk)
for tx in blk.transactions:
self.assertIn(tx, blk)
# tx not in block
self.assertNotIn("e3a3b8361777c8f4f1fd423b86655b5c775de0230b44aa5b82f506135a96c53a", blk)
# wrong arg type
self.assertRaises(ValueError, lambda txid: txid in blk, 1245)
@responses.activate
def test_transactions(self):
responses.add(responses.POST, self.transactions_url,