Source code for hodl.block.Blockchain

"""
Classes:
Blockchain:
Class for storing blockchain. Connects to the sqlite3 database
Main methods:
    new_block - creates new block
    new_transaction - creates new transaction
    new_sc - creates new smart contract
Block:
Class for storing block
Transaction:
Class for storing transaction.
Smart_contract:
Class for storing smart contract.
Some abbreviations:
bch - blockchain
tnx - transaction
sc - smart contract(DApp)
"""
from .sc import *
from .Transaction import *
from .Block import Block
from .UnfilledBlock import UnfilledBlock
import sqlite3
from threading import RLock
import re


lock = RLock()
# todo: blockchain freeze before new block
# todo: transaction and smart contract limit or hash mining, remove smart contract's comission
# todo: smart contracts and SC messages connected to transaction
# todo: remove transaction froms


[docs]class Blockchain: """Class for blockchain""" def __init__(self, filename='bch.db', m='w'): """ Init :param filename: filename for database :type filename: str :param m: mode :type m: str """ self.f = filename if m != 'ro': self.conn = sqlite3.connect('db/' + filename, check_same_thread=False) else: self.conn = sqlite3.connect('db/' + filename + '?mode=ro', uri=True, check_same_thread=False) self.cursor = self.conn.cursor() self.conn.execute('''CREATE TABLE IF NOT EXISTS blocks (ind integer, block text)''') self.conn.commit() def __getitem__(self, item): if type(item) == slice: l = [] if item.step is None: step = 1 else: step = item.step for i in range([item.start, len(self)][item.stop is not None], [item.stop, len(self)][item.stop is None], [step, len(self)][item.stop is not None]): l.append(self[i]) return l if type(item) == tuple: tnx = self[item[0]].txs[item[1]] if tnx.sc: return tnx.sc else: return tnx if item < 0: item += len(self) lock.acquire(True) self.cursor.execute("SELECT * FROM blocks WHERE ind=?", (item,)) s = self.cursor.fetchone()[1] lock.release() return Block.from_json(s)
[docs] def append(self, block): """ Appends blockchain with a block :param block: block to add in blockchain :type block: Block """ lock.acquire(True) self.cursor.execute("INSERT INTO blocks VALUES (?, ?)", (len(self), str(block))) self.conn.commit() lock.release()
[docs] def index(self, block): """ Finds block in chain (by hash) :param block: block to index :type block: Block :return: index :rtype: int """ for i in range(len(self)): if self[i].h == block.h: return i
[docs] def tnxiter(self, maxn=None, fr=(0, 0)): """ Iterate in all transactions in blockchain :param maxn: maximum tnx index (latest tnx of all transactions to iterate in) :param fr: minimum tnx index (earliest tnx of all transactions to iterate in) """ maxn = list(maxn) if maxn is None: maxn = ['l', 'l'] if maxn[0] == 'l': maxn[0] = len(self) if maxn[1] == 'l': maxn[1] = len(self[-1].txs) if maxn[0] < 0: maxn[0] = len(self) + maxn[0] if maxn[1] < 0: maxn[1] = len(self[-1].txs) + maxn[1] if fr[0] > maxn[0]: return elif fr[0] == maxn[0]: for tnx in self[fr[0]].txs[fr[1]:maxn[1]]: yield tnx else: for tnx in self[fr[0]].txs[fr[1]:]: yield tnx for i in range(fr[0] + 1, maxn[0] - 1): for tnx in self[i].txs: yield tnx for tnx in self[maxn[0] - 1].txs[:maxn[1]]: yield tnx
[docs] def get_sc(self, smartcontract): """ Get smart contract by index :param smartcontract: index :type smartcontract: str :return: Smart contract :type: SmartContrct """ smartcontract = re.sub(r'[\[\] ]', '', smartcontract).split(',') smartcontract = list(map(int, smartcontract)) return self[smartcontract[0]].contracts[smartcontract[1]]
[docs] def verify_sc_sign(self, smartcontract, sign): """ Verify smart contract's sign :param smartcontract: smart contract's index :type smartcontract: str :param sign: sign :type sign: str :return: validness :type: bool """ return self.get_sc(smartcontract).validate_sign(sign)
[docs] def money(self, wallet, at=None): """ Count balance of wallet :param wallet: wallet to count balance at :type wallet: str :param at: latest tnx to count :type at: list :return: balance :rtype: float """ if at is None: at = ['l', 'l'] money = 0 for tnx in self.tnxiter(maxn=at): # every tnx in every block outs, outns = rm_dubl_from_outs(tnx.outs, tnx.outns) l = zip([self.pubkey_by_nick(o) for o in outs], outns, range(len(outns))) for w, n, j in l: if (w == wallet or w == self.pubkey_by_nick(wallet)) and not tnx.spent(self)[j] \ and 'mining' not in tnx.outs: money += n return round(money, 10)
[docs] def is_valid(self): """ Checks validness of the whole chain :return: validness of the whole chain """ for i in range(1, len(self)): if not self[i].is_unfilled: if not self[i].is_valid(self): print('block not valid:', i + 1) return False return True
[docs] def new_transaction(self, author, froms, outs, outns, sign='signing', privkey='', sc=tuple()): """ Creates new transaction and adds it to the chain :param str author: transaction author :param list froms: transaction froms :param list outs: transaction outs :param list outns: transaction outns :param str sign: transaction sign if it's already signed or 'signing' :param str privkey: private key if tnx is not already signed :param sc: index of smart contract connected with transaction or ()/[]/None :type sc: tuple or list :return: index of created transaction :rtype: list """ tnx = Transaction() tnx.gen(author, froms, outs, outns, (len(self) - 1, len(self[-1].txs)), sign, privkey, sc=sc) b = self[-1] b.append(tnx) self[-1] = b ind = [len(self) - 1, len(self[-1].txs) - 1] log.info(f'created transaction with index {ind}') return ind
[docs] def new_sc(self, text, author, author_priv, memsize=10000000, lang="js"): """ Creates new smart contract and adds it to the chain :param str text: smart contract code :param str author: SC's author :param str author_priv: author's private key :param int memsize: SC's memory size :param str lang: SC language :return: [index of created sc, index of transaction this SC is connected with] :rtype: [list[int], list[int]] """ log.debug('Blockchain.new_sc') b = self[-1] sc = SmartContract(text, author, [len(self) - 1, len(b.contracts)], memsize=memsize, langr=lang) sc.sign_sc(author_priv) b.contracts.append(sc) self[-1] = b sc.update(self) ind = len(self) - 1, len(self[-1].contracts) - 1 tnxind = self.new_transaction(author, [], [], [], privkey=author_priv, sc=ind) log.info(f'created sc with index {ind} connected to tnx {tnxind}') return ind, tnxind
def __len__(self): lock.acquire(True) self.cursor.execute("SELECT ind FROM blocks") l = len(self.cursor.fetchall()) lock.release() return l def __iter__(self): self.current = -1 return self def __next__(self): self.current += 1 if self.current != len(self): return self[self.current] else: raise StopIteration def __setitem__(self, key, value): lock.acquire(True) # todo: tuple indexes, for example bch[1, 2] = tnx if key < 0: key += len(self) self.cursor.execute("""UPDATE blocks SET block = ? WHERE ind = ?""", (str(value), key)) self.conn.commit() lock.release()
[docs] def get_block(self, i, sync_get): """ Return full block (In local blockchain might be only unfilled copy of block i (UnfilledBlock), then get full block from other peer) :param i: block's index :type i: int :param sync_get: function than gets object (block, transaction, smart contract) from network :type sync_get: function :return: Block """ if not self[i].is_unfilled: return self[i] else: return sync_get(json.dumps([{'type': 'block', 'index': i}]))
[docs] def add_miner(self, miner): """ add proof-of-work miner miner = [hash, n, address, t] :param list miner: miner """ b = self[-1] b.powminers.append(miner) self[-1] = b
[docs] def clean(self): """ Delete all blocks from blockchain """ lock.acquire(True) self.cursor.execute('''DELETE FROM blocks''') self.conn.commit() lock.release()
[docs] def add_sc(self, sc): """ Add smart contract :param sc: smart contract to add :return: index of added sc :rtype: list or tuple """ b = self[-1] b.contracts.append(sc) self[-1] = b ind = len(self) - 1, len(self[-1].sc) - 1 return ind
def commit(self): self.close() self.conn = sqlite3.connect(self.f) self.cursor = self.conn.cursor()
[docs] def pubkey_by_nick(self, nick, maxn=('l', 'l')): """ Nicks can be used in transactions. Nicks can be defined in transaction with author=pubkey;nick; Nick can be transfered in transaction with autor=pubkey;nick;new pubkey; :param nick: str: pubkey, nick or nick definition :param maxn: tuple: maximum index or ('l', 'l') for entire blockchain :return: str: pubkey """ if ';' not in nick and '[' not in nick and len(nick) > 20: return nick elif '[' in nick and ';' not in nick: return nick if nick.count(';') >= 2: return nick.split(';')[0] o = None for tnx in self.tnxiter(maxn=maxn): if tnx.author.endswith(nick + ';'): o = tnx.author.split(';')[0] elif ';' in tnx.author: if tnx.author.count(';') == 3 and tnx.author.split(';')[1] == nick: o = tnx.author.split(';')[1] return o
[docs] def has_traffic_rest(self, user, time_to, price, mined_hash=None): """ HODL has no commission, so, to avoid spam, user has limit of actions in proportion to his balance. Next actions must be mined by this user. (todo) :param user: author of action to confirm :type user: str :param time_to: time of action (index of last tnx at than moment) :type time_to: list[int] :param price: price of the action :type price: float :param mined_hash: mined hash for mined actions :type mined_hash: int :return: validness of the action :rtype: bool """ m = self.money(user, time_to) # todo: count all action prices return True
[docs] def close(self): """Close connection to database""" self.conn.commit() self.conn.close()
def __repr__(self): return str([[len(b.txs), len(b.contracts)] for b in self])