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 .. import exceptions
from ..account import Account from ..account import Account
from ..address import address, Address, SubAddress from ..address import address, Address, SubAddress
from ..block import Block
from ..const import NET_MAIN, NET_TEST, NET_STAGE from ..const import NET_MAIN, NET_TEST, NET_STAGE
from ..numbers import from_atomic, to_atomic, PaymentID from ..numbers import from_atomic, to_atomic, PaymentID
from ..seed import Seed from ..seed import Seed
@ -94,6 +95,33 @@ class JSONRPCDaemon(object):
return res['headers'] return res['headers']
raise exceptions.BackendException(res['status']) 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): def transactions(self, hashes):
res = self.raw_request('/get_transactions', { res = self.raw_request('/get_transactions', {
'txs_hashes': hashes, '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) 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): def transactions(self, hashes):
""" """
Returns transactions matching given hashes. Accepts single hash or a sequence. 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[0].fee, 0)
self.assertGreater(txs[1].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 @responses.activate
def test_transactions(self): def test_transactions(self):
responses.add(responses.POST, self.transactions_url, responses.add(responses.POST, self.transactions_url,