Source code for bbc_ledger

# -*- coding: utf-8 -*-
import os
import sqlite3

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

import binascii

transaction_db_definition = [
    ["transaction_id", "BLOB"], ["asset_group_id", "BLOB"], ["transaction_data", "BLOB"],
]

auxiliary_db_definition = [
    ["id", "INTEGER"], ["resource_id", "BLOB"], ["asset_group_id", "BLOB"],
    ["resource_type", "INTEGER"], ["data", "BLOB"],
]


[docs]class ResourceType: Transaction_data = 0 Asset_ID = 1 Asset_file = 2 Edge_incoming = 3 Edge_outgoing = 4 Owner_asset = 5
[docs]class BBcLedger: """ Database manager SQL style only (for PoC alpha version) """ def __init__(self, config, dbtype="sqlite", loglevel="all", logname=None): """ only support sqlite3 :param dbtype: type of database system """ self.config = config conf = self.config.get_config() self.logger = logger.get_logger(key="bbc_ledger", level=loglevel, logname=logname) if 'ledger' not in conf: self.logger.error("No 'ledger' entry in config!!") os._exit(1) if 'type' not in conf['ledger']: self.logger.error("No 'ledger'.'type' entry in config!!") os._exit(1) if conf['ledger']['type'] != "sqlite3": self.logger.error("Currently, only sqlite3 is supported.") os._exit(1) self.dbtype = dbtype self.db_name = dict() self.db = dict() self.db_cur = dict()
[docs] def add_domain(self, domain_id): """ Add domain in the ledger :param domain_id: :return: """ conf = self.config.get_config() self.db_name[domain_id] = dict() domain_id_str = binascii.b2a_hex(domain_id).decode() domain_dir = conf['workingdir'] + "/" + domain_id_str + "/" if not os.path.exists(domain_dir): os.mkdir(domain_dir, 0o777) self.db_name[domain_id]['transaction_db'] = domain_dir + \ conf['ledger'].get('transaction_db', "bbc_transaction.sqlite3") self.db_name[domain_id]['auxiliary_db'] = domain_dir + \ conf['ledger'].get('auxiliary_db', "bbc_aux.sqlite3") self.db[domain_id] = dict() self.db_cur[domain_id] = dict() self.create_table_in_db(domain_id, 'transaction_db', 'transaction_table', transaction_db_definition, primary_keys=[0], indices=[0]) self.create_table_in_db(domain_id, 'auxiliary_db', 'auxiliary_table', auxiliary_db_definition, primary_keys=[0], indices=[1, 3])
[docs] def open_db(self, domain_id, dbname): """ (internal use) open DB :param domain_id: :param dbname: :return: """ if domain_id not in self.db or domain_id not in self.db_cur: return self.db[domain_id][dbname] = sqlite3.connect(self.db_name[domain_id][dbname], isolation_level=None) self.db_cur[domain_id][dbname] = self.db[domain_id][dbname].cursor()
[docs] def close_db(self, domain_id, dbname): """ (internal use) close DB :param domain_id: :param dbname: :return: """ if domain_id not in self.db or domain_id not in self.db_cur: return self.db_cur[domain_id][dbname].close() self.db[domain_id][dbname].close()
[docs] def create_table_in_db(self, domain_id, dbname, tbl, tbl_definition, primary_keys=[], indices=[]): """ (internal use) Create a new table in a DB :param domain_id: :param dbname: :param tbl: :param tbl_definition: :param primary_keys: :param indices: :return: """ if domain_id not in self.db or domain_id not in self.db_cur or domain_id not in self.db_name: return if self.check_table_existence(domain_id, 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(domain_id, dbname, sql) for idx in indices: self.exec_sql(domain_id, dbname, "CREATE INDEX transaction_table_idx_%d ON %s (%s);" % (idx, tbl, tbl_definition[idx][0]))
[docs] def exec_sql_fetchone(self, domain_id, dbname, sql, *dat): """ (internal use) Exec SQL and get one record :param domain_id: :param dbname: :param sql: :param dat: :return: """ if domain_id not in self.db or domain_id not in self.db_cur or domain_id not in self.db_name: return None if dbname not in self.db[domain_id]: self.open_db(domain_id, dbname) if len(dat) > 0: ret = self.db_cur[domain_id][dbname].execute(sql, (*dat,)).fetchone() else: ret = self.db_cur[domain_id][dbname].execute(sql).fetchone() if ret is not None: ret = list(ret) return ret
[docs] def exec_sql(self, domain_id, dbname, sql, *dat): """ (internal use) Exec SQL and get all records :param domain_id: :param dbname: :param sql: :param dat: :return: """ if domain_id not in self.db or domain_id not in self.db_cur or domain_id not in self.db_name: return None if dbname not in self.db[domain_id]: self.open_db(domain_id, dbname) if len(dat) > 0: ret = self.db_cur[domain_id][dbname].execute(sql, (*dat,)) else: ret = self.db_cur[domain_id][dbname].execute(sql) if ret is not None: ret = list(ret) return ret
[docs] def check_table_existence(self, domain_id, dbname, name): """ (internal use) checking table existence :param domain_id: :param dbname: :param name: :return: the corresponding record array or None """ ret = self.exec_sql_fetchone(domain_id, dbname, "select * from sqlite_master where type='table' and name=?", name) return ret
[docs] def find_locally(self, domain_id, asset_group_id, resource_id, resource_type): """ Find data by ID :param domain_id: :param asset_group_id :param resource_id: Transaction_ID or Asset_ID :param resource_type: ResourceType value :return: data, data_type """ if resource_type == ResourceType.Transaction_data: row = self.exec_sql_fetchone(domain_id, "transaction_db", "select * from transaction_table where transaction_id = ? AND asset_group_id = ?", resource_id, asset_group_id) if row is not None: return row[2] else: row = self.exec_sql_fetchone(domain_id, "auxiliary_db", "select * from auxiliary_table where resource_id = ? AND " "asset_group_id = ? AND resource_type = ?", resource_id, asset_group_id, resource_type) if row is not None: return row[4] return None
[docs] def insert_locally(self, domain_id, asset_group_id, resource_id, resource_type, data, require_uniqueness=True): """ Insert data in the local ledger :param domain_id: :param asset_group_id: :param resource_id: Transaction_ID, Asset_ID, or Owner_ID :param resource_type: ResourceType value :param data: Transaction Data (serialized), Transacrtion_ID, or Node_ID :param require_uniqueness: Ignore uniqueness if True :return: True/False """ if resource_type == ResourceType.Transaction_data: if self.exec_sql_fetchone(domain_id, "transaction_db", "select * from transaction_table where transaction_id = ? AND asset_group_id = ?", resource_id, asset_group_id) is not None: return False sql = "insert into transaction_table values (?, ?, ?)" self.exec_sql(domain_id, "transaction_db", sql, resource_id, asset_group_id, data) else: if require_uniqueness and self.exec_sql_fetchone(domain_id, "auxiliary_db", "select * from auxiliary_table where resource_id = ? AND " "asset_group_id = ? AND resource_type = ?", resource_id, asset_group_id, resource_type) is not None: self.logger.debug("Found duplicate transaction ID") return False sql = "insert into auxiliary_table(resource_id, asset_group_id, resource_type, data) values (?, ?, ?, ?)" self.exec_sql(domain_id, "auxiliary_db", sql, resource_id, asset_group_id, resource_type, data) return True
[docs] def remove(self, domain_id, asset_group_id, resource_id): """ Remove data :param domain_id: :param asset_group_id: :param resource_id: Transaction_ID, Asset_ID, or Owner_ID :return: True/False """ if self.exec_sql_fetchone(domain_id, "transaction_db", "select * from transaction_table where transaction_id = ? AND asset_group_id = ?", resource_id, asset_group_id) is not None: self.exec_sql(domain_id, "transaction_db", "delete from transaction_table where asset_group_id = ? and transaction_id = ?", asset_group_id, resource_id) if self.exec_sql_fetchone(domain_id, "auxiliary_db", "select * from auxiliary_table where resource_id = ? AND asset_group_id = ?", resource_id, asset_group_id) is not None: self.exec_sql(domain_id, "auxiliary_db", "delete from auxiliary_table where asset_group_id = ? and resource_id = ?", asset_group_id, resource_id) return True