#!/usr/bin/env python3 # BlockStore: a helper class that keeps a map of blocks and implements # helper functions for responding to getheaders and getdata, # and for constructing a getheaders message # from io import BytesIO import dbm.ndbm import sys from .environment import network, Network if network() == Network.BCH: from .bch.nodemessages import ( MsgTx, CBlock, MsgHeaders, CBlockHeader, MsgBlock, CTransaction, ) elif network() == Network.NEX: from .nex.nodemessages import ( MsgTx, CBlock, MsgHeaders, CBlockHeader, MsgBlock, CTransaction, ) else: raise NotImplementedError() class BlockStore: def __init__(self, datadir): self.block_db = dbm.ndbm.open(datadir + "/blocks", "c") self.current_block = 0 self.headers_map = {} def close(self): self.block_db.close() def get(self, blockhash): serialized_block = None try: serialized_block = self.block_db[repr(blockhash)] except KeyError: return None f = BytesIO(serialized_block) ret = CBlock() ret.deserialize(f) ret.calc_hash() return ret def get_header(self, blockhash): try: return self.headers_map[blockhash] except KeyError: return None # Note: this pulls full blocks out of the database just to retrieve # the headers -- perhaps we could keep a separate data structure # to avoid this overhead. def headers_for(self, locator, hash_stop, current_tip=None): if current_tip is None: current_tip = self.current_block current_block_header = self.get_header(current_tip) if current_block_header is None: return None response = MsgHeaders() headers_list = [current_block_header] maxheaders = 2000 while headers_list[0].gethash() not in locator.vHave: prev_block_hash = headers_list[0].hashPrevBlock prev_block_header = self.get_header(prev_block_hash) if prev_block_header is not None: headers_list.insert(0, prev_block_header) else: break headers_list = headers_list[:maxheaders] # truncate if we have too many hash_list = [x.gethash() for x in headers_list] index = len(headers_list) if hash_stop in hash_list: index = hash_list.index(hash_stop) + 1 response.headers = headers_list[:index] return response def add_block(self, block): block.calc_hash() try: self.block_db[repr(block.gethash())] = bytes(block.serialize()) except TypeError as e: print("Unexpected error: ", sys.exc_info()[0], e.args) self.current_block = block.gethash() self.headers_map[block.gethash()] = CBlockHeader(block) def add_header(self, header): self.headers_map[header.gethash()] = header def get_blocks(self, inv): responses = [] for i in inv: if i.type == 2: # MSG_BLOCK block = self.get(i.hash) if block is not None: responses.append(MsgBlock(block)) return responses class TxStore: def __init__(self, datadir): self.tx_db = dbm.ndbm.open(datadir + "/transactions", "c") def close(self): self.tx_db.close() def get(self, txhash): serialized_tx = None try: serialized_tx = self.tx_db[repr(txhash)] except KeyError: return None f = BytesIO(serialized_tx) ret = CTransaction() ret.deserialize(f) ret.calc_idem() return ret def add_transaction(self, tx): idem = tx.calcIdem() try: self.tx_db[repr(idem)] = bytes(tx.serialize()) except TypeError as e: print("Unexpected error: ", sys.exc_info()[0], e.args) def get_transactions(self, inv): responses = [] for i in inv: if i.type == 1: # MSG_TX tx = self.get(i.hash) if tx is not None: responses.append(MsgTx(tx)) return responses