Source code for ledger_subsystem

# -*- coding: utf-8 -*-
"""
Copyright (c) 2017 beyond-blockchain.org.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import binascii
import copy
import hashlib
import json
import os
import sqlite3
import threading

import sys
sys.path.extend(["../../"])
from bbc1.common import logger
from bbc1.core.ethereum import bbc_ethereum


merkle_branch_db_definition = [
    ["digest", "BLOB"],
    ["left", "BLOB"],
    ["right", "BLOB"],
]

merkle_leaf_db_definition = [
    ["digest", "BLOB"],
    ["left", "BLOB"],
    ["right", "BLOB"],
    ["prev", "BLOB"], 
]

merkle_root_db_definition = [
    ["root", "BLOB"],
    ["spec", "BLOB"],
]

temp_json = {
    "digest": None,
    "left": None,
    "right": None,
    "prev": None,
    "count": 0,
}


[docs]class Queue: def __init__(self): self.queue = [] self.event = threading.Event()
[docs] def wait_msg(self, flash_others=False): ret = None while ret is None: try: if len(self.queue) == 0: self.event.wait() self.event.clear() ret = self.queue.pop(0) except: ret = None if flash_others: self.queue.clear() return ret return ret
[docs] def append_msg(self, msg): self.queue.append(msg) self.event.set()
[docs]class LedgerSubsystem: def __init__(self, config, enabled=False, dbtype="sqlite", loglevel="all", logname=None): self.logger = logger.get_logger(key="ledger_subsystem", level=loglevel, logname=logname) self.queue = Queue() self.config = config self.enabled = enabled conf = self.config.get_config() self.eth = None self.temp_file = conf['workingdir'] + '/ledger_subsystem.json' self.capacity = conf['ledger_subsystem']['max_transactions'] self.interval = conf['ledger_subsystem']['max_seconds'] self.dbtype = dbtype if self.enabled: self.enable() thread_loop = threading.Thread(target=self.subsystem_loop) thread_loop.setDaemon(True) thread_loop.start()
[docs] def append_msg(self, msg): self.queue.append_msg(msg=msg)
[docs] def check_table_existence(self, dbname, name): ret = self.exec_sql_fetchone(dbname, "select * from sqlite_master where type='table' and name=?", name) return ret
[docs] def close_db(self, dbname): self.db_cur[dbname].close() self.db[dbname].close()
[docs] def close_merkle_tree(self, jTemp): self.logger.debug("closing a merkle tree") self.timer.cancel() self.timer = threading.Timer(self.interval, self.subsystem_timer) self.timer.start() digest = None if jTemp['left'] != None: jTemp['right'] == jTemp['left'] msg = binascii.a2b_hex(jTemp['left']) digest = hashlib.sha256(msg + msg).digest() jTemp['digest'] = str(binascii.b2a_hex(digest), 'utf-8') self.write_leaf(jTemp, digest=digest, left=msg, right=msg) elif jTemp['prev'] != None: digest = binascii.a2b_hex(jTemp['prev']) f = open(self.temp_file, 'w') json.dump(temp_json, f, indent=2) f.close() if digest == None: self.logger.debug("nothing to close") return lBase = self.get_merkle_base(digest) while True: count = 0 dLeft = None lTop = list() for digest in lBase: if dLeft == None: dLeft = digest else: dRight = digest digest = hashlib.sha256(dLeft + dRight).digest() self.write_branch(digest=digest, left=dLeft, right=dRight) lTop.append(digest) dLeft = None count += 1 if dLeft != None: dRight = dLeft digest = hashlib.sha256(dLeft + dRight).digest() self.write_branch(digest=digest, left=dLeft, right=dRight) lTop.append(digest) lBase = lTop if count <= 2: break conf = self.config.get_config() if conf['ledger_subsystem']['subsystem'] == 'ethereum': self.write_merkle_root(lBase[0])
[docs] def create_table_in_db(self, dbname, tbl, tbl_definition, primary_keys=[], indices=[]): if self.check_table_existence(dbname, tbl) is not None: return sql = "CREATE TABLE %s " % tbl sql += "(" sql += ", ".join(["%s %s" % (d[0],d[1]) for d in tbl_definition]) if len(primary_keys) > 0: sql += ", PRIMARY KEY (" sql += ", ".join(tbl_definition[p][0] for p in primary_keys) sql += ")" sql += ");" self.exec_sql(dbname, sql) for idx in indices: self.exec_sql(dbname, "CREATE INDEX %s_idx_%d ON %s (%s);" % (tbl, idx, tbl, tbl_definition[idx][0]))
[docs] def enable(self): conf = self.config.get_config() if conf['ledger_subsystem']['subsystem'] == 'ethereum': prevdir = os.getcwd() os.chdir('ethereum') self.eth = bbc_ethereum.BBcEthereum( conf['ethereum']['account'], conf['ethereum']['passphrase'], conf['ethereum']['contract_address'] ) os.chdir(prevdir) else: self.logger.error("Currently, Ethereum only is supported.") os.exit(1) self.timer = threading.Timer(self.interval, self.subsystem_timer) self.timer.start() self.enabled = True
[docs] def disable(self): """ Disable the ledger subsystem :return: """ self.timer.cancel() self.enabled = False
[docs] def exec_sql(self, dbname, sql, *dat): if dbname not in self.db: self.open_db(dbname) if len(dat) > 0: ret = self.db_cur[dbname].execute(sql, (*dat,)) else: ret = self.db_cur[dbname].execute(sql) if ret is not None: ret = list(ret) return ret
[docs] def exec_sql_fetchone(self, dbname, sql, *dat): if dbname not in self.db: self.open_db(dbname) if len(dat) > 0: ret = self.db_cur[dbname].execute(sql, (*dat,)).fetchone() else: ret = self.db_cur[dbname].execute(sql).fetchone() if ret is not None: ret = list(ret) return ret
[docs] def get_merkle_base(self, digest): lBase = list() while True: row = self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_leaf_table where digest=?', digest ) if row == None: break lBase.insert(0, row[0]) digest = row[3] return lBase
[docs] def open_db(self, dbname): self.db[dbname] = sqlite3.connect(self.dbname[dbname], isolation_level=None) self.db_cur[dbname] = self.db[dbname].cursor()
[docs] def register_transaction(self, asset_group_id, transaction_id): if self.enabled: self.append_msg(transaction_id) else: self.logger.warning("ledger subsystem not enabled")
[docs] def subsystem_loop(self): self.logger.debug("Start subsystem_loop") conf = self.config.get_config() self.dbname = dict() self.dbname['auxiliary_db'] = conf['workingdir'] + '/' + \ conf['ledger'].get('auxiliary_db', 'bbc_aux.sqlite3') self.db_cur = dict() self.db = dict() self.create_table_in_db('auxiliary_db', 'merkle_leaf_table', merkle_leaf_db_definition, primary_keys=[0], indices=[1, 2]) self.create_table_in_db('auxiliary_db', 'merkle_branch_table', merkle_branch_db_definition, primary_keys=[0], indices=[1, 2]) self.create_table_in_db('auxiliary_db', 'merkle_root_table', merkle_root_db_definition, primary_keys=[0], indices=[0]) while True: msg = self.queue.wait_msg() if os.path.exists(self.temp_file): f = open(self.temp_file, 'r') jTemp = json.load(f) f.close() else: jTemp = copy.deepcopy(temp_json) if type(msg) == tuple: if msg[0] == 'timer': self.logger.debug("got message: %s" % msg[0]) self.close_merkle_tree(jTemp) elif msg[0] == 'verify': self.logger.debug("got message: %s %s" % (msg[0], msg[1])) self.verify_digest(msg[1], msg[3]) msg[2].set() else: self.logger.debug("got message: %s" % msg) digest = None if jTemp['left'] == None: jTemp['left'] = str(binascii.b2a_hex(msg), 'utf-8') elif jTemp['right'] == None: jTemp['right'] = str(binascii.b2a_hex(msg), 'utf-8') target = binascii.a2b_hex(jTemp['left']) + msg digest = hashlib.sha256(target).digest() jTemp['digest'] = str(binascii.b2a_hex(digest), 'utf-8') f = open(self.temp_file, 'w') json.dump(jTemp, f, indent=2) f.close() if jTemp['digest'] != None: self.write_leaf(jTemp, digest=digest, right=msg) if jTemp['count'] >= self.capacity: self.close_merkle_tree(jTemp)
[docs] def subsystem_timer(self): self.append_msg(('timer',))
[docs] def verify_transaction(self, asset_group_id, transaction_id): dic = dict() if self.enabled: e = threading.Event() self.append_msg(('verify', transaction_id, e, dic)) e.wait() else: self.logger.warning("ledger subsystem not enabled") return dic
[docs] def verify_digest(self, digest, dic): row = self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_leaf_table where left=? or right=?', digest, digest ) if row == None: self.logger.debug("transaction not found") dic['result'] = False return subtree = list() while True: subtree.append({ 'position': 'left' if row[2] == digest else 'right', 'digest': str(binascii.b2a_hex( row[1] if row[2] == digest else row[2] ), 'utf-8') }) digest = row[0] row = self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_branch_table where left=? or right=?', digest, digest ) if row == None: break row = self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_root_table where root=?', digest ) if row == None: self.logger.warning("merkle root not found") dic['result'] = False return specList = row[1].split(':') block = self.eth.test(digest) if block <= 0: self.logger.warning("merkle root not anchored") dic['result'] = False return spec = { 'subsystem': specList[0], 'chain_id': specList[1], 'contract': specList[2], 'contract_address': specList[3], 'block': block, } dic['result'] = True dic['spec'] = spec dic['subtree'] = subtree
[docs] def write_branch(self, digest=None, left=None, right=None): if self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_branch_table where digest=?', digest ) != None: self.logger.warning("collision of digests detected") else: self.exec_sql( 'auxiliary_db', 'insert into merkle_branch_table values (?, ?, ?)', digest, left, right )
[docs] def write_leaf(self, jTemp, digest=None, left=None, right=None): if digest == None: digest = binascii.a2b_hex(jTemp['digest']) if jTemp['prev'] == None: prev = bytes() else: prev = binascii.a2b_hex(jTemp['prev']) if self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_leaf_table where digest=?', digest ) != None: self.logger.warning("collision of digests detected") else: self.exec_sql( 'auxiliary_db', 'insert into merkle_leaf_table values (?, ?, ?, ?)', digest, left if left != None else binascii.a2b_hex(jTemp['left']), right if right != None else binascii.a2b_hex(jTemp['right']), prev ) jTemp['prev'] = jTemp['digest'] jTemp['digest'] = None jTemp['left'] = None jTemp['right'] = None jTemp['count'] += 2 f = open(self.temp_file, 'w') json.dump(jTemp, f, indent=2) f.close()
[docs] def write_merkle_root(self, root): conf = self.config.get_config() self.write_root( root=root, spec='ethereum:%d:BBcAnchor:%s' % (conf['ethereum']['chain_id'], conf['ethereum']['contract_address']) ) self.eth.blockingSet(root)
[docs] def write_root(self, root=None, spec=None): if self.exec_sql_fetchone( 'auxiliary_db', 'select * from merkle_root_table where root=?', root ) != None: self.logger.warning("collision of digests detected") else: self.exec_sql( 'auxiliary_db', 'insert into merkle_root_table values (?, ?)', root, spec )
# end of core/ledger_subsystem.py