1#!/usr/bin/env python3
2# Copyright (c) 2015-2016 The Bitcoin Core developers
3# Distributed under the MIT software license, see the accompanying
4# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5# BlockStore: a helper class that keeps a map of blocks and implements
6#             helper functions for responding to getheaders and getdata,
7#             and for constructing a getheaders message
8#
9
10from .mininode import *
11from io import BytesIO
12import dbm.dumb as dbmd
13
14class BlockStore(object):
15    def __init__(self, datadir):
16        self.blockDB = dbmd.open(datadir + "/blocks", 'c')
17        self.currentBlock = 0
18        self.headers_map = dict()
19
20    def close(self):
21        self.blockDB.close()
22
23    def erase(self, blockhash):
24        del self.blockDB[repr(blockhash)]
25
26    # lookup an entry and return the item as raw bytes
27    def get(self, blockhash):
28        value = None
29        try:
30            value = self.blockDB[repr(blockhash)]
31        except KeyError:
32            return None
33        return value
34
35    # lookup an entry and return it as a CBlock
36    def get_block(self, blockhash):
37        ret = None
38        serialized_block = self.get(blockhash)
39        if serialized_block is not None:
40            f = BytesIO(serialized_block)
41            ret = CBlock()
42            ret.deserialize(f)
43            ret.calc_sha256()
44        return ret
45
46    def get_header(self, blockhash):
47        try:
48            return self.headers_map[blockhash]
49        except KeyError:
50            return None
51
52    # Note: this pulls full blocks out of the database just to retrieve
53    # the headers -- perhaps we could keep a separate data structure
54    # to avoid this overhead.
55    def headers_for(self, locator, hash_stop, current_tip=None):
56        if current_tip is None:
57            current_tip = self.currentBlock
58        current_block_header = self.get_header(current_tip)
59        if current_block_header is None:
60            return None
61
62        response = msg_headers()
63        headersList = [ current_block_header ]
64        maxheaders = 2000
65        while (headersList[0].sha256 not in locator.vHave):
66            prevBlockHash = headersList[0].hashPrevBlock
67            prevBlockHeader = self.get_header(prevBlockHash)
68            if prevBlockHeader is not None:
69                headersList.insert(0, prevBlockHeader)
70            else:
71                break
72        headersList = headersList[:maxheaders] # truncate if we have too many
73        hashList = [x.sha256 for x in headersList]
74        index = len(headersList)
75        if (hash_stop in hashList):
76            index = hashList.index(hash_stop)+1
77        response.headers = headersList[:index]
78        return response
79
80    def add_block(self, block):
81        block.calc_sha256()
82        try:
83            self.blockDB[repr(block.sha256)] = bytes(block.serialize())
84        except TypeError as e:
85            print("Unexpected error: ", sys.exc_info()[0], e.args)
86        self.currentBlock = block.sha256
87        self.headers_map[block.sha256] = CBlockHeader(block)
88
89    def add_header(self, header):
90        self.headers_map[header.sha256] = header
91
92    # lookup the hashes in "inv", and return p2p messages for delivering
93    # blocks found.
94    def get_blocks(self, inv):
95        responses = []
96        for i in inv:
97            if (i.type == 2): # MSG_BLOCK
98                data = self.get(i.hash)
99                if data is not None:
100                    # Use msg_generic to avoid re-serialization
101                    responses.append(msg_generic(b"block", data))
102        return responses
103
104    def get_locator(self, current_tip=None):
105        if current_tip is None:
106            current_tip = self.currentBlock
107        r = []
108        counter = 0
109        step = 1
110        lastBlock = self.get_block(current_tip)
111        while lastBlock is not None:
112            r.append(lastBlock.hashPrevBlock)
113            for i in range(step):
114                lastBlock = self.get_block(lastBlock.hashPrevBlock)
115                if lastBlock is None:
116                    break
117            counter += 1
118            if counter > 10:
119                step *= 2
120        locator = CBlockLocator()
121        locator.vHave = r
122        return locator
123
124class TxStore(object):
125    def __init__(self, datadir):
126        self.txDB = dbmd.open(datadir + "/transactions", 'c')
127
128    def close(self):
129        self.txDB.close()
130
131    # lookup an entry and return the item as raw bytes
132    def get(self, txhash):
133        value = None
134        try:
135            value = self.txDB[repr(txhash)]
136        except KeyError:
137            return None
138        return value
139
140    def get_transaction(self, txhash):
141        ret = None
142        serialized_tx = self.get(txhash)
143        if serialized_tx is not None:
144            f = BytesIO(serialized_tx)
145            ret = CTransaction()
146            ret.deserialize(f)
147            ret.calc_sha256()
148        return ret
149
150    def add_transaction(self, tx):
151        tx.calc_sha256()
152        try:
153            self.txDB[repr(tx.sha256)] = bytes(tx.serialize())
154        except TypeError as e:
155            print("Unexpected error: ", sys.exc_info()[0], e.args)
156
157    def get_transactions(self, inv):
158        responses = []
159        for i in inv:
160            if (i.type == 1): # MSG_TX
161                tx = self.get(i.hash)
162                if tx is not None:
163                    responses.append(msg_generic(b"tx", tx))
164        return responses
165