├── .gitignore
├── README.md
├── With database
├── .ipynb_checkpoints
│ ├── Expariments-checkpoint.ipynb
│ ├── MerkleTreeExample-checkpoint.ipynb
│ └── PatricaiTrieExample-checkpoint.ipynb
├── Experiments.ipynb
├── MerkleDatabase.py
├── MerkleDatabaseTest.py
├── MerkleTree.py
├── MerkleTreeDraw.py
├── MerkleTreeExample.ipynb
├── MerkleValidate.py
├── PatricaiTrieExample.ipynb
├── PatriciaDatabase.py
├── PatriciaDatabaseTest.py
├── PatriciaTrie.py
├── experiment.py
├── hashfunction.py
└── patricia_main.py
└── Without database
├── GetBalanceExample.py
├── MerkleTree.py
├── MerkleTreeExample.ipynb
├── PatricaiTrieExample.ipynb
└── PatriciaTrie.py
/.gitignore:
--------------------------------------------------------------------------------
1 | With\ database/__pycache__
2 | With\ database/.ipynb_checkpoints
3 | With\ database/.DS_Store
4 | Without\ database/__pycache__
5 | Without\ database/.ipynb_checkpoints
6 | Without\ database/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blockchain-Extension-for-PostgreSQL-Data-Storage
2 | Big data storage is a part of almost each life area. Database usage is a standard way to organize information and gather and querying data in such systems. The lack of audit tools is an issue in many applications, especially for users without full access or without enough resources to look up the data storage. The blockchain is an emerging technology with the potential to resolve such issues.
3 |
4 | Numerous classical database systems operate nowadays. Enabling new blockchain-related features for them implies moving to a platform with another database inside or duplicating its parts in a blockchain system. Both ways are difficult to migrate and maintain. The paper describes how to organize blockchain extension for the data storage using the database for a particular example of an account-based cryptocurrency prototype so that the transaction and balance requests can support cryptographic proofs for the response correctness. The numerical experiments to check an overhead of the proposed extension are provided.
5 |
--------------------------------------------------------------------------------
/With database/.ipynb_checkpoints/PatricaiTrieExample-checkpoint.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from PatriciaDatabase import PatriciaDatabase\n",
10 | "from PatriciaTrie import PatriciaTrie\n",
11 | "from PatriciaDatabaseTest import test"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "import hashlib"
21 | ]
22 | },
23 | {
24 | "cell_type": "markdown",
25 | "metadata": {},
26 | "source": [
27 | "Arguments to connectiong database"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": 2,
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "args = {\n",
37 | " 'DB_NAME': \"blockchain_postgresql\", \n",
38 | " #'DB_USER': \"darkhannurlybay\",\n",
39 | " #'DB_PASSWORD': \"\",\n",
40 | " #'DB_HOST': \"localhost\",\n",
41 | " 'DB_PORT': \"5432\",\n",
42 | " 'verbose': False\n",
43 | "}"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "Run unit tests"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 3,
56 | "metadata": {},
57 | "outputs": [
58 | {
59 | "name": "stdout",
60 | "output_type": "stream",
61 | "text": [
62 | "\n",
63 | "Seems like good!\n"
64 | ]
65 | }
66 | ],
67 | "source": [
68 | "test(args)"
69 | ]
70 | },
71 | {
72 | "cell_type": "markdown",
73 | "metadata": {},
74 | "source": [
75 | "Creating some users in our system"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": 13,
81 | "metadata": {},
82 | "outputs": [
83 | {
84 | "data": {
85 | "text/plain": [
86 | "\"\\nuser = {\\n 'Alice': '000010',\\n 'Bob': '010100',\\n 'Sally': '111111'\\n}\""
87 | ]
88 | },
89 | "execution_count": 13,
90 | "metadata": {},
91 | "output_type": "execute_result"
92 | }
93 | ],
94 | "source": [
95 | "users = {}\n",
96 | "#user = { 'Alice': '000010', 'Bob': '010100', 'Sally': '111111'}"
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "execution_count": 15,
102 | "metadata": {},
103 | "outputs": [],
104 | "source": [
105 | "number_of_users = 2"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": 16,
111 | "metadata": {},
112 | "outputs": [
113 | {
114 | "name": "stdout",
115 | "output_type": "stream",
116 | "text": [
117 | "0\n",
118 | "1\n"
119 | ]
120 | }
121 | ],
122 | "source": [
123 | "for i in range(number_of_users):\n",
124 | " users['user_'+str(i)] = \n",
125 | " "
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": 5,
131 | "metadata": {},
132 | "outputs": [
133 | {
134 | "name": "stdout",
135 | "output_type": "stream",
136 | "text": [
137 | "['node_id', 'parent_id', 'balance', 'hash', 'type']\n",
138 | "(2, 1, 100, 'f53bde1660418aeeb9a6065070c6e6333dfd9a92dfd87678fbdccb2cbdc711e7', 'leaf')\n",
139 | "(1, 3, 100, '2f10bb8d7401f8a189d20f96825c2317da3f680150aa0ce2d1c99924e848eac4', 'user')\n",
140 | "(5, 4, 50, 'a79d4b5c455492bcce16a018d44191a17111cf02b1a1569215215c6759ba5c2a', 'leaf')\n",
141 | "(4, 3, 50, '9d5fb89c58ae421060477568ba48b97d7ac71fc1ed40b9bced800b7051a187aa', 'user')\n",
142 | "(3, 0, 0, '04a03f1ccc23d843f8ea893fc2c3efb818645c8b38f7043fa78daac1968db33e', 'before')\n",
143 | "(0, None, 0, '8c64d4a2cb24a7fe061e412fa564a7a50296f4cd17daff482c3f1e88b729f0e6', 'root')\n"
144 | ]
145 | },
146 | {
147 | "data": {
148 | "image/svg+xml": [
149 | "\n",
150 | "\n",
152 | "\n",
154 | "\n",
155 | "\n"
237 | ],
238 | "text/plain": [
239 | ""
240 | ]
241 | },
242 | "execution_count": 5,
243 | "metadata": {},
244 | "output_type": "execute_result"
245 | }
246 | ],
247 | "source": [
248 | "db = PatriciaDatabase(**args)\n",
249 | "db.delete_tables()\n",
250 | "db.create_tables()\n",
251 | "\n",
252 | "t = PatriciaTrie(db, simple_hash=False)\n",
253 | "\n",
254 | "t.create(user['Alice'], 100, '0000')\n",
255 | "t.create(user['Bob'], 50, '0001')\n",
256 | "\n",
257 | "db.print_column_name('PatriciaNode');\n",
258 | "for row in t.db.show_table('PatriciaNode'):\n",
259 | " print(row)\n",
260 | " \n",
261 | "t.draw()"
262 | ]
263 | },
264 | {
265 | "cell_type": "code",
266 | "execution_count": 6,
267 | "metadata": {},
268 | "outputs": [],
269 | "source": [
270 | "assert t.get_balance(user['Alice']) == 100\n",
271 | "assert t.get_balance(user['Bob']) == 50"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": 7,
277 | "metadata": {},
278 | "outputs": [
279 | {
280 | "data": {
281 | "image/svg+xml": [
282 | "\n",
283 | "\n",
285 | "\n",
287 | "\n",
288 | "\n"
398 | ],
399 | "text/plain": [
400 | ""
401 | ]
402 | },
403 | "execution_count": 7,
404 | "metadata": {},
405 | "output_type": "execute_result"
406 | }
407 | ],
408 | "source": [
409 | "t.spend(user['Alice'], user['Bob'], 10, '1000')\n",
410 | "assert t.get_balance(user['Alice']) == 90\n",
411 | "assert t.get_balance(user['Bob']) == 60\n",
412 | "t.draw()"
413 | ]
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": 8,
418 | "metadata": {},
419 | "outputs": [],
420 | "source": [
421 | "db.close_session()"
422 | ]
423 | },
424 | {
425 | "cell_type": "code",
426 | "execution_count": 9,
427 | "metadata": {},
428 | "outputs": [],
429 | "source": [
430 | "db = PatriciaDatabase(**args)\n",
431 | "t = PatriciaTrie(db, simple_hash=False)"
432 | ]
433 | },
434 | {
435 | "cell_type": "code",
436 | "execution_count": 10,
437 | "metadata": {},
438 | "outputs": [
439 | {
440 | "data": {
441 | "image/svg+xml": [
442 | "\n",
443 | "\n",
445 | "\n",
447 | "\n",
448 | "\n"
642 | ],
643 | "text/plain": [
644 | ""
645 | ]
646 | },
647 | "execution_count": 10,
648 | "metadata": {},
649 | "output_type": "execute_result"
650 | }
651 | ],
652 | "source": [
653 | "t.create(user['Sally'], 0, '1111')\n",
654 | "t.spend(user['Alice'], user['Sally'], 40, '1001')\n",
655 | "assert t.get_balance(user['Alice']) == 50\n",
656 | "assert t.get_balance(user['Sally']) == 40\n",
657 | "t.draw()"
658 | ]
659 | },
660 | {
661 | "cell_type": "code",
662 | "execution_count": 11,
663 | "metadata": {},
664 | "outputs": [
665 | {
666 | "data": {
667 | "image/svg+xml": [
668 | "\n",
669 | "\n",
671 | "\n",
673 | "\n",
674 | "\n"
924 | ],
925 | "text/plain": [
926 | ""
927 | ]
928 | },
929 | "execution_count": 11,
930 | "metadata": {},
931 | "output_type": "execute_result"
932 | }
933 | ],
934 | "source": [
935 | "t.spend(user['Bob'], user['Sally'], 50, '1002')\n",
936 | "t.get_balance(user['Bob'])\n",
937 | "assert t.get_balance(user['Bob']) == 10\n",
938 | "assert t.get_balance(user['Sally']) == 90\n",
939 | "t.draw()"
940 | ]
941 | },
942 | {
943 | "cell_type": "code",
944 | "execution_count": 12,
945 | "metadata": {},
946 | "outputs": [],
947 | "source": [
948 | "db.close_session()"
949 | ]
950 | },
951 | {
952 | "cell_type": "code",
953 | "execution_count": null,
954 | "metadata": {},
955 | "outputs": [],
956 | "source": []
957 | }
958 | ],
959 | "metadata": {
960 | "kernelspec": {
961 | "display_name": "Python 3",
962 | "language": "python",
963 | "name": "python3"
964 | },
965 | "language_info": {
966 | "codemirror_mode": {
967 | "name": "ipython",
968 | "version": 3
969 | },
970 | "file_extension": ".py",
971 | "mimetype": "text/x-python",
972 | "name": "python",
973 | "nbconvert_exporter": "python",
974 | "pygments_lexer": "ipython3",
975 | "version": "3.7.4"
976 | }
977 | },
978 | "nbformat": 4,
979 | "nbformat_minor": 4
980 | }
981 |
--------------------------------------------------------------------------------
/With database/MerkleDatabase.py:
--------------------------------------------------------------------------------
1 | import psycopg2
2 |
3 | class MerkleDatabase():
4 | def __init__(self, DB_NAME="blockchain_postgresql",
5 | #DB_USER="yashmadhwal",
6 | #DB_PASSWORD="",
7 | #DB_HOST="localhost",
8 | DB_PORT="5432",
9 | verbose=False):
10 | try:
11 | self.conn = psycopg2.connect(
12 | dbname=DB_NAME,
13 | #user=DB_USER,
14 | #password=DB_PASSWORD,
15 | #host=DB_HOST,
16 | port=DB_PORT
17 | )
18 | self.cur = self.conn.cursor()
19 | self.verbose = verbose
20 | if self.verbose: print("successfully connected to server!")
21 | except:
22 | print("failed while connection to server")
23 |
24 | def delete_tables(self):
25 | self.cur.execute("DROP TABLE IF EXISTS MerkleNode")
26 | self.cur.execute("DROP TABLE IF EXISTS Block")
27 | self.cur.execute("DROP TABLE IF EXISTS Balance")
28 | self.cur.execute("DROP TABLE IF EXISTS Transaction")
29 | self.conn.commit()
30 | if self.verbose: print("...table was deleted")
31 |
32 | def create_tables(self):
33 | self.cur.execute(
34 | """CREATE TABLE MerkleNode(
35 | node_id SERIAL PRIMARY KEY,
36 | block_id INT,
37 | level INT,
38 | position INT,
39 | hash VARCHAR(255),
40 | UNIQUE (block_id, level, position)
41 | )"""
42 | )
43 | self.cur.execute(
44 | '''CREATE TABLE Block (
45 | block_id SERIAL PRIMARY KEY,
46 | merkle_hash VARCHAR(255),
47 | merkle_height INT,
48 | patricia_hash VARCHAR(255),
49 | block_hash VARCHAR(255)
50 | )'''
51 | )
52 | self.cur.execute(
53 | '''CREATE TABLE balance (
54 | id SERIAL PRIMARY KEY,
55 | user_id VARCHAR(255),
56 | amount INT,
57 | UNIQUE (user_id)
58 | )'''
59 | )
60 | self.cur.execute(
61 | '''CREATE TABLE transaction (
62 | transaction_id SERIAL PRIMARY KEY,
63 | tx_hash VARCHAR(255),
64 | type VARCHAR(255),
65 | user1 VARCHAR(255),
66 | user2 VARCHAR(255),
67 | amount INT,
68 | block_id INT,
69 | position INT,
70 | UNIQUE (tx_hash)
71 | )'''
72 | )
73 |
74 | if self.verbose: print("table were created...")
75 | self.conn.commit()
76 |
77 | # BALANCE
78 | def get_balance_of_user(self, user):
79 | self.cur.execute(
80 | "SELECT amount FROM Balance WHERE user_id = '{}'".format(user)
81 | )
82 | result = self.cur.fetchone()
83 | if result is None:
84 | return None
85 | return result[0]
86 |
87 | def create_user(self, user, amount):
88 | self.cur.execute(
89 | "INSERT INTO Balance (user_id, amount) VALUES ('{}', {})".format(user, amount)
90 | )
91 | self.conn.commit()
92 |
93 | def update_user_balance(self, user, newbalance):
94 | query = "UPDATE Balance SET amount = {} WHERE user_id = '{}'"
95 | self.cur.execute(query.format(newbalance, user))
96 |
97 | # TRANSACTION
98 | def get_trancsaction(self, transaction_hash):
99 | self.cur.execute(
100 | "SELECT * FROM Transaction WHERE tx_hash = '{}'".format(transaction_hash)
101 | )
102 | result = self.cur.fetchone()
103 | if result is None:
104 | return None
105 | return result
106 |
107 | def insert_trancsaction(self, txhash, _type, user1, amount, blockid, position, user2='NULL'):
108 | query = '''
109 | INSERT INTO Transaction (tx_hash, type, user1, user2, amount, block_id, position)
110 | VALUES ('{}', '{}', '{}', '{}', {}, {}, {})'''
111 | self.cur.execute(query.format(txhash, _type, user1, user2, amount, blockid, position))
112 | self.conn.commit()
113 |
114 | def get_table(self, table_name='MerkleNode'):
115 | self.cur.execute("SELECT * FROM " + table_name)
116 | table = []
117 | for row in self.cur.fetchall():
118 | table += [row]
119 | return table
120 |
121 | def insert_node(self, block, level, position, _hash):
122 | self.cur.execute(
123 | '''INSERT INTO MerkleNode (block_id, level, position, hash)
124 | VALUES ({}, {}, {}, '{}')'''.format(block, level, position, _hash)
125 | )
126 | self.conn.commit()
127 |
128 | def get_nodes_by_block(self, block):
129 | self.cur.execute(
130 | "SELECT * FROM MerkleNode WHERE block_id = {}".format(block)
131 | )
132 | table = []
133 | for row in self.cur.fetchall():
134 | table += [row]
135 | return table
136 |
137 | def get_calculated_hash(self, block, level, position):
138 | self.cur.execute(
139 | '''SELECT hash FROM MerkleNode WHERE
140 | block_id = {} AND level = {} AND position = {}'''.format(block, level, position)
141 | )
142 | result = self.cur.fetchone()
143 | if result is None:
144 | return None
145 | return result[0]
146 |
147 | def insert_merkle_root(self, merklehash, merkleheight):
148 | query = "INSERT INTO Block (merkle_hash, merkle_height) VALUES('{}', {})"
149 | self.cur.execute(query.format(merklehash, merkleheight))
150 | self.conn.commit()
151 |
152 | def update_info(self, field, new_value, idx):
153 | query = "UPDATE Block SET {} = '{}' WHERE block_id = {}"
154 | self.cur.execute(query.format(field, new_value, idx))
155 |
156 | def get_root_info(self, block, field='merkle_hash'):
157 | self.cur.execute('''SELECT {} FROM Block WHERE block_id = {}'''.format(field, block))
158 | result = self.cur.fetchone()
159 | if result is None:
160 | return None
161 | return result[0]
162 |
163 | def get_block_number(self):
164 | self.cur.execute('''SELECT COUNT(*) FROM Block''')
165 | result = self.cur.fetchone()
166 | return result[0]
167 |
168 | def close_session(self):
169 | self.conn.commit()
170 | self.conn.close()
171 | if self.verbose: print("connection to server was closed...")
172 |
173 | def print_column_name(self, table_name='Block'):
174 | self.cur.execute("SELECT * FROM {} LIMIT 0".format(table_name))
175 | print([desc[0] for desc in self.cur.description])
176 |
177 | def get_table_size(self, table_name):
178 | self.cur.execute("select pg_relation_size('{}')".format(table_name))
179 | result = self.cur.fetchone()
180 | return result[0]
--------------------------------------------------------------------------------
/With database/MerkleDatabaseTest.py:
--------------------------------------------------------------------------------
1 | from MerkleDatabase import MerkleDatabase
2 |
3 | def test(args):
4 | db = MerkleDatabase(**args)
5 | db.delete_tables()
6 | db.create_tables()
7 |
8 | # Unittest 0
9 | assert db.get_block_number() == 0
10 |
11 | # Unittest 1
12 | db.insert_node(1, 1, 2, "a")
13 | assert db.get_calculated_hash(1, 1, 2) == "a"
14 |
15 | # Unittest 2
16 | db.insert_node(1, 1, 3, "b")
17 | assert db.get_calculated_hash(1, 1, 3) == "b"
18 |
19 | # Unittest 3
20 | db.insert_merkle_root("ab" , 2)
21 | assert db.get_root_info(1, 'merkle_hash') == "ab"
22 |
23 | # Unittest 4
24 | db.insert_merkle_root("abcd", 3)
25 | assert db.get_root_info(2, 'merkle_hash') == "abcd"
26 | assert db.get_root_info(2, 'merkle_height') == 3
27 |
28 | # Unittest 5
29 | assert db.get_block_number() == 2
30 |
31 | # Unittest 6
32 | db.create_user('Alice', 50)
33 | db.create_user('Bob', 100)
34 | db.create_user('Darkhan', 29)
35 |
36 | assert db.get_balance_of_user('Alice') == 50
37 | assert db.get_balance_of_user('Bob') == 100
38 | assert db.get_balance_of_user('Darkhan') == 29
39 |
40 | # Unittest 7
41 | db.update_user_balance('Darkhan', 20)
42 | assert db.get_balance_of_user('Darkhan') == 20
43 |
44 | # Unittest 8
45 | db.insert_trancsaction(
46 | txhash='1', _type='create', user1='Alice',
47 | amount=12, blockid=1, position=1
48 | )
49 | assert db.get_trancsaction('1')[1] == '1'
50 | assert db.get_trancsaction('1')[3] == 'Alice'
51 |
52 | db.delete_tables()
53 | db.close_session()
54 |
55 | print("\nSeems like good!")
56 |
--------------------------------------------------------------------------------
/With database/MerkleTree.py:
--------------------------------------------------------------------------------
1 | from hashfunction import get_hash
2 | import copy
3 |
4 | class MerkleTree:
5 | def __init__(self, db, transactions, simple_hash=True):
6 | """
7 | 1. db - database where store all calculated hashes
8 | 2. transactions - list of transactions
9 | 3. block - id of block of transactions
10 | return simple hash, otherwise return sha256
11 | """
12 | self.block = db.get_block_number() + 1
13 | self.simple_hash = simple_hash
14 | self.node_hash = dict() # key: (block_id, level, position)
15 |
16 | # Complete transactions for full binary tree
17 | self.transactions = copy.deepcopy(transactions)
18 | idx = len(self.transactions)
19 | for i in range(self.complete_to_full(idx) - idx):
20 | self.transactions += [self.transactions[-1]]
21 | self.height = self.get_height(len(self.transactions))
22 |
23 | # Calculating hash of each node in merkle tree
24 | for i in range(len(self.transactions)):
25 | self.node_hash[(self.block, 1, i + 1)] = self.transactions[i]
26 | _ = self.calculate_hash(self.transactions)
27 |
28 | # Save all calculated data in database
29 | for item in self.node_hash:
30 | db.insert_node(item[0], item[1], item[2], self.node_hash[item])
31 | db.insert_merkle_root(
32 | self.node_hash[(self.block, self.height, 1)], self.height)
33 |
34 | def calculate_hash(self, hashes, level=1):
35 | """
36 | recursive function for calculating hash
37 | of each node in merkle tree.
38 | """
39 | if len(hashes) == 1:
40 | return hashes[0]
41 |
42 | temp = []
43 | for i in range(0, len(hashes), 2):
44 | _hash = get_hash(hashes[i] + hashes[i + 1], self.simple_hash)
45 | self.node_hash[(self.block, level + 1, i // 2 + 1)] = _hash
46 | temp.append(_hash)
47 |
48 | return self.calculate_hash(temp, level + 1)
49 |
50 | @staticmethod
51 | def get_height(idx):
52 | """
53 | input: idx - right index for complete tree. Example idx = 1, 2, 4, 8,...
54 | output: level of complete this tree
55 | """
56 | level = 0
57 | while idx > 0:
58 | level += 1
59 | idx //= 2
60 | return level
61 |
62 | @staticmethod
63 | def complete_to_full(idx):
64 | """
65 | input: idx - right index of tree.
66 | output: right index for complete tree
67 | """
68 | # if idx == 1:
69 | # return 1
70 | i = 1
71 | while i * 2 < idx:
72 | i *= 2
73 | return 2 * i
74 |
--------------------------------------------------------------------------------
/With database/MerkleTreeDraw.py:
--------------------------------------------------------------------------------
1 | from graphviz import Graph
2 | from MerkleValidate import get_nodes_for_validation
3 |
4 | def MerkleTreeDraw(db, block, idx_for_validation=None):
5 | """
6 | This function draw Merkle tree
7 | """
8 | def parent(idx):
9 | if idx % 2 == 0: return idx // 2
10 | return idx // 2 + 1
11 |
12 | def left_child(idx):
13 | return 2 * idx - 1
14 |
15 | def right_child(idx):
16 | return 2 * idx
17 |
18 | def dfs(level, idx):
19 | if level == 1:
20 | return
21 |
22 | node = str(level) + ',' + str(idx)
23 |
24 | # left child
25 | node_left = str(level - 1) + ',' + str(left_child(idx))
26 | G.edge(node, node_left)
27 | dfs(level - 1, left_child(idx))
28 |
29 | # right child
30 | node_right = str(level - 1) + ',' + str(right_child(idx))
31 | G.edge(node, node_right)
32 | dfs(level - 1, right_child(idx))
33 |
34 | G = Graph()
35 | height = db.get_root_info(block, 'merkle_height')
36 | table = db.get_nodes_by_block(block)
37 | validation_nodes = None
38 | if not idx_for_validation is None:
39 | validation_nodes = get_nodes_for_validation(db, block, idx_for_validation)
40 | validation_nodes = [(x[0], x[1]) for x in validation_nodes]
41 |
42 | # Defining all nodes
43 | for row in table: # id, blockid, level, position, hash
44 | name = str(row[2]) + ',' + str(row[3])
45 | label = row[4]
46 | if validation_nodes is None:
47 | G.node(name=name, label=label)
48 | elif (row[2], row[3]) in validation_nodes:
49 | G.node(name=name, label=label, color='lightblue2', style='filled')
50 | elif (row[2], row[3]) == (1, idx_for_validation):
51 | G.node(name=name, label=label, color='violet', style='filled')
52 | else:
53 | G.node(name=name, label=label)
54 |
55 | # Adding edges
56 | dfs(height, 1)
57 | return G
58 |
--------------------------------------------------------------------------------
/With database/MerkleValidate.py:
--------------------------------------------------------------------------------
1 | def get_nodes_for_validation(db, block, position):
2 | """
3 | :param db: database which store merkle trees data
4 | :param block: Id of block
5 | :param position: index leaf for varification
6 | :return: nodes (level, position) for verification node with id idx in given block
7 | """
8 | def parent(idx):
9 | if idx % 2 == 0: return idx // 2
10 | return idx // 2 + 1
11 |
12 | def left_child(idx):
13 | return 2 * idx - 1
14 |
15 | def right_child(idx):
16 | return 2 * idx
17 |
18 | height = db.get_root_info(block, 'merkle_height')
19 | nodes = []
20 | idx = position
21 |
22 | for i in range(height - 1):
23 | prev = idx
24 | idx = parent(idx)
25 | left = left_child(idx)
26 | right = right_child(idx)
27 | if left == prev:
28 | nodes += [(i + 1, right, db.get_calculated_hash(block, i + 1, right))]
29 | else:
30 | nodes += [(i + 1, left, db.get_calculated_hash(block, i + 1, left))]
31 |
32 | return nodes
33 |
34 |
--------------------------------------------------------------------------------
/With database/PatricaiTrieExample.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 19,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from PatriciaDatabase import PatriciaDatabase\n",
10 | "from PatriciaTrie import PatriciaTrie\n",
11 | "from PatriciaDatabaseTest import test"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": 20,
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "import hashlib\n",
21 | "import random"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Arguments to connectiong database"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 21,
34 | "metadata": {},
35 | "outputs": [],
36 | "source": [
37 | "args = {\n",
38 | " 'DB_NAME': \"blockchain_postgresql\", \n",
39 | " #'DB_USER': \"darkhannurlybay\",\n",
40 | " #'DB_PASSWORD': \"\",\n",
41 | " #'DB_HOST': \"localhost\",\n",
42 | " 'DB_PORT': \"5432\",\n",
43 | " 'verbose': False\n",
44 | "}"
45 | ]
46 | },
47 | {
48 | "cell_type": "markdown",
49 | "metadata": {},
50 | "source": [
51 | "Run unit tests"
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 22,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "name": "stdout",
61 | "output_type": "stream",
62 | "text": [
63 | "\n",
64 | "Seems like good!\n"
65 | ]
66 | }
67 | ],
68 | "source": [
69 | "test(args)"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "metadata": {},
75 | "source": [
76 | "Creating some users in our system"
77 | ]
78 | },
79 | {
80 | "cell_type": "code",
81 | "execution_count": 23,
82 | "metadata": {},
83 | "outputs": [],
84 | "source": [
85 | "users = {}\n",
86 | "#user = { 'Alice': '000010', 'Bob': '010100', 'Sally': '111111'}"
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": 24,
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "number_of_users = 2"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": 25,
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "for i in range(number_of_users):\n",
105 | " users['user_'+str(i)] = hashlib.sha224(bytes(i)).hexdigest()"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": 26,
111 | "metadata": {},
112 | "outputs": [
113 | {
114 | "name": "stdout",
115 | "output_type": "stream",
116 | "text": [
117 | "('user_0', 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')\n",
118 | "('user_1', 'fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073')\n"
119 | ]
120 | }
121 | ],
122 | "source": [
123 | "#Displaying users' list\n",
124 | "for i in users.items():\n",
125 | " print(i)"
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": 33,
131 | "metadata": {},
132 | "outputs": [],
133 | "source": [
134 | "db = PatriciaDatabase(**args)\n",
135 | "db.delete_tables()\n",
136 | "db.create_tables()"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": null,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "t = PatriciaTrie(db, simple_hash=False)\n",
146 | "\n",
147 | "#creating users with initial balances i.e. 10 ** 6\n",
148 | "amount = 10 ** 6\n",
149 | "for i in users.items():\n",
150 | " \n",
151 | " tx_hash = hashlib.sha224(str(i[1] + str(amount)).encode('utf-8')).hexdigest()\n",
152 | " t.create(i[1],amount,tx_hash)\n",
153 | "\n",
154 | "#t.create(user['Alice'], 100, '0000')\n",
155 | "#t.create(user['Bob'], 50, '0001')\n",
156 | "\n",
157 | "db.print_column_name('PatriciaNode');\n",
158 | "for row in t.db.show_table('PatriciaNode'):\n",
159 | " print(row)\n",
160 | " \n",
161 | "t.draw()"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": 28,
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "#assert t.get_balance(user['Alice']) == 100\n",
171 | "#assert t.get_balance(user['Bob']) == 50"
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": 29,
177 | "metadata": {},
178 | "outputs": [],
179 | "source": [
180 | "#t.spend(user['Alice'], user['Bob'], 10, '1000')\n",
181 | "#assert t.get_balance(user['Alice']) == 90\n",
182 | "#assert t.get_balance(user['Bob']) == 60\n",
183 | "#t.draw()"
184 | ]
185 | },
186 | {
187 | "cell_type": "code",
188 | "execution_count": 30,
189 | "metadata": {},
190 | "outputs": [
191 | {
192 | "name": "stdout",
193 | "output_type": "stream",
194 | "text": [
195 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f -> fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073\n",
196 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f -> fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073\n",
197 | "fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073 -> d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f\n",
198 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f -> fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073\n",
199 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f -> fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073\n"
200 | ]
201 | },
202 | {
203 | "data": {
204 | "text/plain": [
205 | "'d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42ffff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b07346'"
206 | ]
207 | },
208 | "execution_count": 30,
209 | "metadata": {},
210 | "output_type": "execute_result"
211 | }
212 | ],
213 | "source": [
214 | "#creating random transactions\n",
215 | "transactions = 5\n",
216 | "for i in range(transactions):\n",
217 | " sender = random.choice(list(users.values()))\n",
218 | " receiver = random.choice(list(i for i in users.values() if i not in sender))\n",
219 | " print(sender,' -> ',receiver)\n",
220 | " amount = random.randint(0, 100)\n",
221 | " transaction_string = str(sender + receiver + str(amount))\n",
222 | " tx_hash_balance = hashlib.sha224(transaction_string.encode('utf-8')).hexdigest()\n",
223 | " #t.spend(users[sender], users[receiver],amount , tx_hash_balance)\n",
224 | " t.spend(sender, receiver,amount , tx_hash_balance)\n",
225 | " \n",
226 | "#t.draw()\n",
227 | "transaction_string\n"
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": 31,
233 | "metadata": {},
234 | "outputs": [
235 | {
236 | "data": {
237 | "image/svg+xml": [
238 | "\n",
239 | "\n",
241 | "\n",
243 | "\n",
244 | "\n"
481 | ],
482 | "text/plain": [
483 | ""
484 | ]
485 | },
486 | "execution_count": 31,
487 | "metadata": {},
488 | "output_type": "execute_result"
489 | }
490 | ],
491 | "source": [
492 | "t.draw()"
493 | ]
494 | },
495 | {
496 | "cell_type": "code",
497 | "execution_count": 32,
498 | "metadata": {},
499 | "outputs": [],
500 | "source": [
501 | "db.close_session()"
502 | ]
503 | },
504 | {
505 | "cell_type": "code",
506 | "execution_count": null,
507 | "metadata": {},
508 | "outputs": [],
509 | "source": []
510 | }
511 | ],
512 | "metadata": {
513 | "kernelspec": {
514 | "display_name": "Python 3",
515 | "language": "python",
516 | "name": "python3"
517 | },
518 | "language_info": {
519 | "codemirror_mode": {
520 | "name": "ipython",
521 | "version": 3
522 | },
523 | "file_extension": ".py",
524 | "mimetype": "text/x-python",
525 | "name": "python",
526 | "nbconvert_exporter": "python",
527 | "pygments_lexer": "ipython3",
528 | "version": "3.7.4"
529 | }
530 | },
531 | "nbformat": 4,
532 | "nbformat_minor": 4
533 | }
534 |
--------------------------------------------------------------------------------
/With database/PatriciaDatabase.py:
--------------------------------------------------------------------------------
1 | import psycopg2
2 |
3 | class PatriciaDatabase():
4 | def __init__(self, DB_NAME="blockchain_postgresql",
5 | #DB_USER="stxxaqoh",
6 | #DB_PASSWORD="T94ybwpTs9z3mbqF0nQ02mFFeFPDlQhj",
7 | #DB_HOST="drona.db.elephantsql.com",
8 | DB_PORT="5432",
9 | verbose=False):
10 | '''Interface to connecting, manipulating with database for
11 | Patricia tree, which contain next tables:
12 | 1. PatriciaEdge: (node_id, child_id, prefix)
13 | 2. PatriciaNode: (node_id, parent_id, balance, hash, type).
14 | type - type of node:
15 | a. root - root of patricia tree
16 | b. before (it means that node before user node)
17 | c. user - node with start user node
18 | d. leaf - leaf of patricia tree
19 | '''
20 | try:
21 | self.conn = psycopg2.connect(dbname=DB_NAME,
22 | #user=DB_USER,
23 | #password=DB_PASSWORD, host=DB_HOST,
24 | port=DB_PORT)
25 | self.cur = self.conn.cursor()
26 | self.verbose = verbose
27 | if self.verbose: print("successfully connected to server!")
28 | except:
29 | print("failed while connection to server")
30 |
31 | def delete_tables(self):
32 | self.cur.execute("DROP TABLE IF EXISTS PatriciaEdge")
33 | self.cur.execute("DROP TABLE IF EXISTS PatriciaNode")
34 | self.conn.commit()
35 | if self.verbose: print("...table was deleted")
36 |
37 | def create_tables(self):
38 | self.cur.execute(
39 | '''CREATE TABLE PatriciaNode (
40 | node_id SERIAL PRIMARY KEY,
41 | parent_id INT,
42 | balance INT DEFAULT 0,
43 | hash VARCHAR(255),
44 | type VARCHAR(255)
45 | )'''
46 | )
47 | self.cur.execute(
48 | """CREATE TABLE PatriciaEdge(
49 | edge_id SERIAL PRIMARY KEY,
50 | node_id INT,
51 | child_id INT,
52 | prefix VARCHAR(255)
53 | )"""
54 | )
55 | # If table PatriciaNode is empty let's initialize it
56 | self.cur.execute("SELECT * FROM PatriciaNode")
57 | if self.cur.fetchone() is None:
58 | query = "INSERT INTO PatriciaNode (node_id, type) VALUES (0, 'root')"
59 | self.cur.execute(query)
60 |
61 | self.conn.commit()
62 | if self.verbose: print("table were created")
63 |
64 | def create_new_row(self, table_name, idx):
65 | self.cur.execute(
66 | '''INSERT INTO {} (node_id)
67 | VALUES ({})'''.format(table_name, idx)
68 | )
69 | self.conn.commit()
70 | if self.verbose: print("...", idx, "was added to", table_name)
71 |
72 | self.cur.execute('SELECT COUNT(*) FROM {};'.format(table_name))
73 | result = self.cur.fetchone()
74 | return result[0]
75 |
76 | def update_cell(self, table_name, field, new_value, column, idx):
77 | '''
78 | for PatriciaNode: column is node_id
79 | for PatriciaEdge: column is edge_id
80 | '''
81 | query = "UPDATE {} SET {} = '{}' WHERE {} = {}"
82 | self.cur.execute(query.format(table_name, field, new_value, column, idx))
83 | if self.verbose: print('updated...')
84 | self.conn.commit()
85 |
86 | def get_cell(self, table_name, field, column, idx):
87 | '''
88 | for PatriciaNode: column is node_id
89 | for PatriciaEdge: column is edge_id
90 | '''
91 | self.cur.execute(
92 | '''SELECT {} FROM {} WHERE {} = {}'''.format(
93 | field, table_name, column, idx)
94 | )
95 | result = self.cur.fetchone()
96 | if result is None:
97 | return None
98 | return result[0]
99 |
100 | def show_table(self, table_name):
101 | self.cur.execute("SELECT * FROM " + table_name)
102 | result = []
103 | for row in self.cur.fetchall():
104 | result += [row]
105 | return result
106 |
107 | def get_cnt(self):
108 | self.cur.execute('SELECT COUNT(*) FROM PatriciaNode;')
109 | result = self.cur.fetchone()
110 | return result[0] - 1
111 |
112 | def get_branch(self, idx, prefix):
113 | self.cur.execute(
114 | '''SELECT * FROM PatriciaEdge WHERE
115 | node_id = {} AND '{}' LIKE CONCAT(prefix, '%')'''.format(idx, prefix)
116 | )
117 | result = self.cur.fetchone()
118 | return result
119 |
120 | def get_all_branch(self, idx):
121 | self.cur.execute(
122 | "SELECT * FROM PatriciaEdge WHERE node_id = {}".format(idx)
123 | )
124 | result = []
125 | for row in self.cur.fetchall():
126 | result += [row]
127 | return result
128 |
129 | def print_column_name(self, table_name='PatriciaEdge'):
130 | self.cur.execute("SELECT * FROM {} LIMIT 0".format(table_name))
131 | print([desc[0] for desc in self.cur.description])
132 |
133 | def close_session(self):
134 | self.conn.commit()
135 | self.conn.close()
136 | if self.verbose: print("connection to server was closed...")
137 |
138 | def get_table_size(self, table_name):
139 | self.cur.execute("select pg_relation_size('{}')".format(table_name))
140 | result = self.cur.fetchone()
141 | return result[0]
--------------------------------------------------------------------------------
/With database/PatriciaDatabaseTest.py:
--------------------------------------------------------------------------------
1 | from PatriciaDatabase import PatriciaDatabase
2 |
3 | def test(args):
4 | db = PatriciaDatabase(**args)
5 | db.delete_tables()
6 | db.create_tables()
7 |
8 | # Unit test 1
9 | assert db.get_cell('PatriciaNode', 'type', 'node_id', 0) == 'root'
10 |
11 | # Unit test 2
12 | db.update_cell('PatriciaNode', 'balance', 100, 'node_id', 0)
13 | assert db.get_cell('PatriciaNode', 'balance', 'node_id', 0) == 100
14 |
15 | db.update_cell('PatriciaNode', 'hash', '!@#$%', 'node_id', 0)
16 | assert db.get_cell('PatriciaNode', 'hash', 'node_id', 0) == '!@#$%'
17 |
18 | # Unit test 3
19 | assert db.create_new_row('PatriciaEdge', 0) == 1
20 | assert db.get_cell('PatriciaEdge', 'prefix', 'edge_id', 1) == None
21 | assert db.get_cell('PatriciaEdge', 'child_id', 'edge_id', 1) == None
22 |
23 | # Unit test 4
24 | db.update_cell('PatriciaEdge', 'child_id', 2, 'edge_id', 1)
25 | assert db.get_cell('PatriciaEdge', 'child_id', 'edge_id', 1) == 2
26 |
27 | # Unit test 5
28 | db.update_cell('PatriciaEdge', 'prefix', 'Dark', 'edge_id', 1)
29 | assert db.get_cell('PatriciaEdge', 'prefix', 'edge_id', 1) == 'Dark'
30 |
31 | # Unit test 6
32 | assert db.create_new_row('PatriciaNode', 2) == 2
33 | db.update_cell('PatriciaNode', 'hash', '####', 'node_id', 2)
34 | assert db.get_cell('PatriciaNode', 'hash', 'node_id', 2) == '####'
35 |
36 | # Unit test 7
37 | assert db.create_new_row('PatriciaEdge', 0) == 2
38 | db.update_cell('PatriciaEdge', 'prefix', 'Sally', 'edge_id', 2)
39 | db.show_table('PatriciaEdge')
40 |
41 | assert db.get_branch(0, 'Darkhan') == (1, 0, 2, 'Dark')
42 | assert db.get_branch(2, 'Dark') == None
43 |
44 | db.delete_tables()
45 | db.close_session()
46 |
47 | print("\nSeems like good!")
48 |
--------------------------------------------------------------------------------
/With database/PatriciaTrie.py:
--------------------------------------------------------------------------------
1 | from hashfunction import get_hash
2 | from graphviz import Digraph
3 |
4 | class PatriciaTrie:
5 | def __init__(self, db, simple_hash=True):
6 | self.db = db
7 | self.simple_hash = simple_hash
8 | self.cnt = self.db.get_cnt() #count of nodes
9 |
10 | def get_balance(self, user:str):
11 | """
12 | get user balance
13 | """
14 | # Find user in MPT
15 | idx = self.dfs_simple(0, user)
16 |
17 | balance = self.db.get_cell(
18 | table_name='PatriciaNode',
19 | field='balance',
20 | column='node_id',
21 | idx=idx,
22 | )
23 | return balance
24 |
25 | def dfs_simple(self, idx, prefix):
26 | """
27 | Helper recursive get_balance
28 | """
29 | '''
30 | Commit: index of given prefix(user), find index of a given prefix.
31 | here prefix = user_key (eg hash of Alice)
32 | '''
33 | if prefix == '': return idx
34 | branch = self.db.get_branch(idx, prefix)
35 | k = self.common_prefix_len(branch[3], prefix)
36 | return self.dfs_simple(branch[2], prefix[k:])
37 |
38 | def create(self, user, amount, txhash):
39 | """
40 | :param user, amount:int
41 | :param txhash: A hash of these transactions which calculated by RPL
42 | :return: None. Just update MPT
43 | """
44 | # Find user in MPT
45 | idx = self.dfs(0, user, amount, update_balance=False, minus=False)
46 | self.db.update_cell('PatriciaNode', 'type', 'user', 'node_id', idx)
47 |
48 | # Find transacntion hash for given user
49 | idx = self.dfs(idx, txhash, amount, update_balance=True, minus=False)
50 |
51 | self.db.update_cell('PatriciaNode', 'balance', amount, 'node_id', idx)
52 | self.db.update_cell('PatriciaNode', 'type', 'leaf', 'node_id', idx)
53 |
54 | # case 1: hash_leaf = hash(tx_hash|balance_charge)
55 | node_hash = get_hash(
56 | '(hash:' + txhash + '|' + 'blc:' + str(amount) + ')',
57 | self.simple_hash
58 | )
59 | self.db.update_cell('PatriciaNode', 'hash', node_hash, 'node_id', idx)
60 | self.update_node_hash(idx)
61 |
62 | def spend(self, user1: str, user2: str, amount: int, txhash: str):
63 | """
64 | :param tx: list of transactions: [user1, user2, amount, transaction hash]
65 | :param txhash: A hash of these transactions which calculated by RPL
66 | :return: None. Just updated MPT
67 | """
68 | minus = True
69 | for user in [user1, user2]:
70 | # Find user1 in MPT
71 | idx = self.dfs_simple(0, user)
72 | # idx = self.dfs(0, user, amount, update_balance=False, minus=False)
73 | # self.db.update_cell('InfoPatricia', 'type', 'user', 'node_id', idx)
74 |
75 | # Find transacntion hash for given user
76 | idx = self.dfs(idx, txhash, amount, update_balance=True, minus=minus)
77 | self.db.update_cell('PatriciaNode', 'type', 'leaf', 'node_id', idx)
78 |
79 | if minus:
80 | self.db.update_cell('PatriciaNode', 'balance', -amount, 'node_id', idx)
81 | else:
82 | self.db.update_cell('PatriciaNode', 'balance', amount, 'node_id', idx)
83 |
84 | # case 1: hash_leaf = hash(tx_hash|balance_charge)
85 | node_hash = get_hash(
86 | '(hash:' + txhash + '|' + 'blc:' + str(amount) + ')',
87 | self.simple_hash
88 | )
89 | self.db.update_cell('PatriciaNode', 'hash', node_hash, 'node_id', idx)
90 | self.update_node_hash(idx)
91 |
92 | minus = False
93 |
94 | def common_prefix_len(self, prefix1: str, prefix2: str):
95 | """
96 | Helper function for self._dfs. Return command prefix for prefix1 and prefix2
97 | len(prefix1) <= len(prefix2)
98 | """
99 | same = 0
100 | for i in range(len(prefix1)):
101 | if prefix1[i] == prefix2[i]:
102 | same += 1
103 | else:
104 | break
105 | return same
106 |
107 | def dfs(self, idx: int, prefix: str, amount: int, update_balance=False, minus=False):
108 | """
109 | Helper recursive function for self.create
110 | :param idx: id for starting node
111 | :param prefix: prefix for searching. prefix user or hash
112 | :param hash: hash of transaction
113 | :return: None. Just updated MPT
114 | """
115 | if prefix == '':
116 | return idx
117 |
118 | # Initially we don't find any branch of node idx, which have
119 | # at least one same character with given prefix
120 | found = False
121 | if update_balance:
122 | balance = self.db.get_cell('PatriciaNode', 'balance', 'node_id', idx)
123 | if minus:
124 | balance -= amount
125 | else:
126 | balance += amount
127 | self.db.update_cell('PatriciaNode', 'balance', balance, 'node_id', idx)
128 |
129 | for branch in self.db.get_all_branch(idx): # branch: (ID, node_id, child idx, prefix)
130 | k = self.common_prefix_len(branch[3], prefix)
131 | if k > 0:
132 | found = True
133 | if k == len(branch[3]):
134 | return self.dfs(branch[2], prefix[k:], amount, update_balance, minus)
135 | else:
136 | child_id = branch[2]
137 | diff = branch[3][k:]
138 | self.cnt += 1
139 |
140 | self.db.update_cell('PatriciaEdge', 'child_id', self.cnt, 'edge_id', branch[0])
141 | self.db.update_cell('PatriciaEdge', 'prefix', prefix[: k], 'edge_id', branch[0])
142 |
143 | # parent
144 | self.db.create_new_row('PatriciaNode', self.cnt)
145 | self.db.update_cell('PatriciaNode', 'parent_id', idx, 'node_id', self.cnt)
146 | self.db.update_cell('PatriciaNode', 'type', 'before', 'node_id', self.cnt)
147 |
148 | self.db.update_cell('PatriciaNode', 'parent_id', self.cnt, 'node_id', child_id)
149 |
150 | ID = self.db.create_new_row('PatriciaEdge', self.cnt)
151 | self.db.update_cell('PatriciaEdge', 'child_id', child_id, 'edge_id', ID)
152 | self.db.update_cell('PatriciaEdge', 'prefix', diff, 'edge_id', ID)
153 |
154 | ID = self.db.create_new_row('PatriciaEdge', self.cnt)
155 | self.db.update_cell('PatriciaEdge', 'child_id', self.cnt + 1, 'edge_id', ID)
156 | self.db.update_cell('PatriciaEdge', 'prefix', prefix[k:], 'edge_id', ID)
157 |
158 | if update_balance:
159 | self.db.update_cell('PatriciaNode', 'type', 'descendant', 'node_id', self.cnt)
160 |
161 | # parent
162 | self.db.create_new_row('PatriciaNode', self.cnt + 1)
163 | self.db.update_cell('PatriciaNode', 'parent_id', self.cnt, 'node_id', self.cnt + 1)
164 | self.db.update_cell('PatriciaNode', 'type', 'before', 'node_id', self.cnt + 1)
165 |
166 | # update transaction balance:
167 | if update_balance:
168 | balance = self.db.get_cell('PatriciaNode', 'balance', 'node_id', child_id)
169 | if minus:
170 | balance -= amount
171 | else:
172 | balance += amount
173 | self.db.update_cell('PatriciaNode', 'balance', balance, 'node_id', self.cnt)
174 | self.cnt += 1
175 | return self.cnt
176 | if not found:
177 | ID = self.db.create_new_row('PatriciaEdge', idx)
178 |
179 | self.db.update_cell('PatriciaEdge', 'child_id', self.cnt + 1, 'edge_id', ID)
180 | self.db.update_cell('PatriciaEdge', 'prefix', prefix, 'edge_id', ID)
181 |
182 | self.db.create_new_row('PatriciaNode', self.cnt + 1)
183 | self.db.update_cell('PatriciaNode', 'parent_id', idx, 'node_id', self.cnt + 1)
184 |
185 | self.cnt += 1
186 | return self.cnt
187 |
188 | def update_node_hash(self, idx: int):
189 | """
190 | case 1:
191 | If a leaf vertex (corresponds to a transaction):
192 | hash_leaf = hash(balance_charge|tx_hash)
193 | case 2:
194 | If the vertex is descendant of the user:
195 | hash(balance|prefix_to_child_node + hash of child| ...)
196 | case 3:
197 | If the vertex is a user:
198 | hash_user = hash(id|balance|prefix_to_child_node + hash of child| ...)
199 | case 4:
200 | If the vertex is the ancestor (before) of users (for example, root),
201 | hash(prefix_to_child_node + hash of child| ...)
202 | """
203 | # Go up
204 | while idx != 0:
205 | idx = self.db.get_cell('PatriciaNode', 'parent_id', 'node_id', idx)
206 | node_type = self.db.get_cell('PatriciaNode', 'type', 'node_id', idx)
207 | balance = self.db.get_cell('PatriciaNode', 'balance', 'node_id', idx)
208 |
209 | if node_type == 'descendant':
210 | temp = '(blc:' + str(balance) + ')|'
211 | elif node_type == 'user':
212 | temp = '(id:' + str(str(idx) + '|' + 'blc:' + str(balance)) + ')|'
213 | else:
214 | temp = ''
215 |
216 | # cancatinating of child hashes
217 | for branch in self.db.get_all_branch(idx): # branch: (edge_id, node_id, child idx, prefix)
218 | node_hash = self.db.get_cell('PatriciaNode', 'hash', 'node_id', branch[2])
219 | temp += '(' + 'prf:' + branch[3] + '|' + 'hash:' + node_hash + ')'
220 |
221 | self.db.update_cell(
222 | 'PatriciaNode', 'hash', get_hash(temp, self.simple_hash),
223 | 'node_id', idx
224 | )
225 |
226 | def beautify(self, G, idx: int):
227 | """
228 | Helper function for self.draw
229 | """
230 |
231 | node_type = self.db.get_cell('PatriciaNode', 'type', 'node_id', idx)
232 | balance = self.db.get_cell('PatriciaNode', 'balance', 'node_id', idx)
233 |
234 | if 'user' == node_type :
235 | G.node(
236 | str(idx),
237 | str(idx) + "\nUser balance = " + str(balance),
238 | shape='square', color='gold1', style='filled'
239 | )
240 | elif 'leaf' == node_type:
241 | G.node(
242 | str(idx),
243 | str(idx) + "\ntx = " + str(balance),
244 | color='lightblue2', style='filled'
245 | )
246 | elif 'descendant' == node_type:
247 | G.node(
248 | str(idx),
249 | str(idx) + "\nbalance = " + str(balance)
250 | )
251 | else:
252 | G.node(str(idx))
253 |
254 | def draw(self):
255 | """
256 | This function draw Merkle patricia tree
257 | """
258 | G = Digraph()
259 |
260 | # Defining all nodes
261 | for row in self.db.show_table('PatriciaNode'):
262 | self.beautify(G, row[0])
263 |
264 | # Adding edges
265 | for row in self.db.show_table('PatriciaEdge'):
266 | G.edge(str(row[1]), str(row[2]), label=row[3])
267 | return G
268 |
269 |
--------------------------------------------------------------------------------
/With database/experiment.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Fri Jul 3 11:23:17 2020
5 |
6 | @author: yashmadhwal
7 | """
8 |
9 | from PatriciaDatabase import PatriciaDatabase
10 | from PatriciaTrie import PatriciaTrie
11 | from MerkleDatabase import MerkleDatabase
12 | from MerkleTree import MerkleTree
13 | from MerkleTreeDraw import MerkleTreeDraw
14 | from hashfunction import get_hash
15 | from MerkleValidate import get_nodes_for_validation
16 | import hashlib
17 |
18 | import numpy as np
19 | import time
20 | import matplotlib.pyplot as plt
21 | from tqdm import tqdm
22 | import random
23 |
24 | def generate_users(user_number, init_budget=10**6):
25 | """
26 | This function create users with initial budget
27 | and with their transaction hash
28 | """
29 | users = []
30 | transaction = []
31 |
32 | for i in range(user_number):
33 | #user = get_hash(str(i), simple_hash=False)
34 | user = hashlib.sha224(bytes(i)).hexdigest()
35 | users += [user]
36 | tx_hash = hashlib.sha224((user + str(init_budget)).encode('utf-8')).hexdigest()
37 | transaction += [('create', user, init_budget, tx_hash)]
38 |
39 | return users, transaction
40 |
41 |
42 | def generate_transaction(users, max_amount):
43 | """
44 | This function create spend one transaction
45 | for given users with selected randomly.
46 | """
47 | transaction = None
48 |
49 | # Random spends among users
50 | idx1 = random.choice(list(users))
51 | idx2 = random.choice(list(i for i in users if i not in idx1))
52 | amount = np.random.randint(low=1, high=max_amount)
53 | tx_hash = hashlib.sha224((idx1+idx2+str(amount)).encode('utf-8')).hexdigest()
54 | transaction = ('spend', idx1, idx2, amount, tx_hash)
55 |
56 | return transaction
57 |
58 | def update(transactions):
59 | """
60 | transactions: ('spend', user1, user2, amount, tx_hash)
61 | ('create', user, budget, tx_hash)
62 | |transactions| <= block_size
63 | """
64 | block = []
65 | block_id = db_mekrle.get_block_number() + 1
66 |
67 | for position, tx in enumerate(transactions):
68 | if tx[0] == 'create':
69 | # Update Patricia tree
70 | patricai_trie.create(tx[1], tx[2], tx[3])
71 |
72 | # Save user and transaction as raw information
73 | db_mekrle.create_user(tx[1], tx[2])
74 | db_mekrle.insert_trancsaction(
75 | txhash=tx[3], _type='create', user1=tx[1],
76 | amount=tx[2], blockid=block_id, position=position + 1
77 | )
78 |
79 | if tx[0] == 'spend':
80 | # Update Patricia tree
81 | patricai_trie.spend(tx[1], tx[2], tx[3], tx[4])
82 |
83 | # Save user and transaction as raw information
84 | db_mekrle.update_user_balance(
85 | tx[1], db_mekrle.get_balance_of_user(tx[1]) - tx[3]
86 | )
87 | db_mekrle.update_user_balance(
88 | tx[2], db_mekrle.get_balance_of_user(tx[2]) + tx[3]
89 | )
90 | db_mekrle.insert_trancsaction(
91 | txhash=tx[4], _type='spend', user1=tx[1], user2=tx[2],
92 | amount=tx[3], blockid=block_id, position=position + 1
93 | )
94 | block.append(tx[-1])
95 |
96 | # Create merkle tree from block of transactions
97 | merkle_tree = MerkleTree(db_mekrle, block, simple_hash=False)
98 | merkle_root_hash = db_mekrle.get_root_info(
99 | block=block_id, field='merkle_hash'
100 | )
101 |
102 | # Add patricia root hash to database
103 | patricia_root_hash = db_patricia.get_cell('PatriciaNode', 'hash', 'node_id', 1)
104 | db_mekrle.update_info(
105 | field='patricia_hash', new_value=patricia_root_hash, idx=block_id
106 | )
107 |
108 | # Calculate block hash
109 | prev_block_hash = ''
110 | if block_id != 1:
111 | prev_block_hash = db_mekrle.get_root_info(
112 | block=block_id - 1, field='block_hash'
113 | )
114 |
115 | block_hash = get_hash(
116 | merkle_root_hash + patricia_root_hash + prev_block_hash,
117 | simple_hash=False
118 | )
119 |
120 | # Save block hash in database
121 | db_mekrle.update_info(
122 | field='block_hash', new_value=block_hash, idx=block_id
123 | )
124 |
125 | args = {
126 | 'DB_NAME': "blockchain_postgresql",
127 | 'DB_PORT': "5432",
128 | 'verbose': False
129 | }
130 |
131 |
132 | #______EXPERIMENT_____
133 | def measure_balance_request(block_size, cnt):
134 | usual = np.zeros(cnt)
135 | specific = np.zeros(cnt)
136 |
137 | for j, user in enumerate(np.random.choice(users, size=cnt)):
138 | # Usual balance request
139 | start = time.time()
140 | _ = db_mekrle.get_balance_of_user(user)
141 | end = time.time()
142 | usual[j] = 1000 * (end - start)
143 |
144 | # Patricia balance request
145 | start = time.time()
146 | _ = patricai_trie.get_balance(user)
147 | end = time.time()
148 | specific[j] = 1000 * (end - start)
149 |
150 | time_[block_size]['times_mean']['balance_request']['usual'].append(usual.mean())
151 | time_[block_size]['times_mean']['balance_request']['specific'].append(specific.mean())
152 |
153 | time_[block_size]['times_std']['balance_request']['usual'].append(usual.std())
154 | time_[block_size]['times_std']['balance_request']['specific'].append(specific.std())
155 |
156 |
157 | def measure_transaction_time(block_size, cnt):
158 | usual = np.zeros(cnt)
159 | specific = np.zeros(cnt)
160 |
161 | for j, tx in enumerate(np.random.choice(transactions, size=cnt)):
162 | # Usual transaction request
163 | start = time.time()
164 | _ = db_mekrle.get_trancsaction(tx)
165 | end = time.time()
166 | usual[j] = 1000 * (end - start)
167 |
168 | # Merkle transaction request
169 | start = time.time()
170 | transaction = db_mekrle.get_trancsaction(tx)
171 | nodes_for_validation = get_nodes_for_validation(
172 | db_mekrle, block=transaction[6], position=transaction[7]
173 | )
174 | end = time.time()
175 | specific[j] = 1000 * (end - start)
176 |
177 | time_[block_size]['times_mean']['transaction_request']['usual'].append(usual.mean())
178 | time_[block_size]['times_mean']['transaction_request']['specific'].append(specific.mean())
179 |
180 | time_[block_size]['times_std']['transaction_request']['usual'].append(usual.std())
181 | time_[block_size]['times_std']['transaction_request']['specific'].append(specific.std())
182 |
183 |
184 |
185 | time_ = dict()
186 | user_number = 100
187 | amount = 10000
188 | n_iter = 100
189 |
190 | BLOCK_SIZES = [2 ** (i + 1) for i in range(4)]
191 | experiment_frequency = 2
192 |
193 | for block_size in BLOCK_SIZES:
194 | print('block_size', block_size)
195 |
196 | # Connection to database and clean all tables
197 | db_patricia = PatriciaDatabase(**args)
198 | db_patricia.delete_tables()
199 | db_patricia.create_tables()
200 | patricai_trie = PatriciaTrie(db_patricia, simple_hash=False)
201 |
202 | db_mekrle = MerkleDatabase(**args)
203 | db_mekrle.delete_tables()
204 | db_mekrle.create_tables()
205 |
206 | # create users
207 | users, transactions = generate_users(user_number=user_number)
208 |
209 | for i in range(0, len(transactions), block_size):
210 | update(transactions[i: i + block_size])
211 |
212 | tx_block = []
213 | transactions = [] # This for transaction_request
214 |
215 | time_[block_size] = {
216 | 'adding_time': [],
217 | 'x_axis_adding_time': [],
218 |
219 | 'memory_usage': {
220 | 'merkle': [],
221 | 'patricia': [],
222 | 'usual': []
223 | },
224 | 'x_axis': [],
225 | 'times_mean': {
226 | 'balance_request': {'usual': [], 'specific': []},
227 | 'transaction_request': {'usual': [], 'specific': []}
228 | },
229 | 'times_std': {
230 | 'balance_request': {'usual': [], 'specific': []},
231 | 'transaction_request':{'usual': [], 'specific': []}
232 | }
233 | }
234 |
235 | for i in tqdm(range(n_iter)):
236 |
237 | tx = generate_transaction(users, amount)
238 | tx_block.append(tx)
239 |
240 | if (i + 1) % block_size == 0:
241 | start = time.time()
242 | update(tx_block)
243 | end = time.time()
244 | time_[block_size]['adding_time'].append(1000 * (end - start) / block_size)
245 | time_[block_size]['x_axis_adding_time'].append(i)
246 |
247 | for trx in tx_block:
248 | transactions.append(trx[-1])
249 | tx_block = []
250 |
251 | if (i + 1) % experiment_frequency == 0 and i >= block_size:
252 | cnt = 10
253 | measure_balance_request(block_size, cnt)
254 | measure_transaction_time(block_size, cnt)
255 | time_[block_size]['memory_usage']['merkle'].append(
256 | db_mekrle.get_table_size('Block') +
257 | db_mekrle.get_table_size('MerkleNode')
258 | )
259 | time_[block_size]['memory_usage']['patricia'].append(
260 | db_patricia.get_table_size('PatriciaEdge') +
261 | db_patricia.get_table_size('PatriciaNode')
262 | )
263 | time_[block_size]['memory_usage']['usual'].append(
264 | db_patricia.get_table_size('balance') +
265 | db_patricia.get_table_size('transaction')
266 | )
267 | time_[block_size]['x_axis'].append(i)
268 | db_mekrle.close_session()
269 | db_patricia.close_session()
270 |
271 |
272 | #____Ploting____
273 | def convert_to_numpy(dictionary):
274 | for key in dictionary:
275 | for _type in dictionary[key]:
276 | dictionary[key][_type] = np.array(dictionary[key][_type])
277 |
278 |
279 |
280 | for block_size in time_:
281 | convert_to_numpy(time_[block_size]['times_mean'])
282 | convert_to_numpy(time_[block_size]['times_std'])
283 |
284 |
285 | fig, axes = plt.subplots(nrows=1, ncols=3, sharex=False, figsize=(22, 5))
286 | legend = []
287 | for i in range(len(BLOCK_SIZES)):
288 | bs = BLOCK_SIZES[i]
289 | legend.append('block size:' + str(bs))
290 | axes[0].plot(time_[bs]['x_axis'], time_[bs]['memory_usage']['merkle'], '-')
291 | axes[1].plot(time_[bs]['x_axis'], time_[bs]['memory_usage']['patricia'], '-')
292 | axes[2].plot(time_[bs]['x_axis'], time_[bs]['memory_usage']['usual'], '-')
293 |
294 | title = [
295 | 'The size of the table for \nstoring data in a Merkle tree',
296 | 'Table size for \ndata storage in the Patricia tree',
297 | 'Table size for \nmain data storage'
298 | ]
299 | for i in range(3):
300 | axes[i].legend(legend);
301 | axes[i].set_title(title[i])
302 | axes[i].set_xlabel('No Tx')
303 | axes[i].set_ylabel('table size (bytes)')
304 | plt.show()
305 |
306 | '''
307 | # plt.title('Размер таблицы для \nхранения данных. Размер блока 32',)
308 | plt.plot(TIME[16]['x_axis'], TIME[16]['memory_usage']['merkle'], '-')
309 | plt.plot(TIME[16]['x_axis'], TIME[16]['memory_usage']['patricia'], '-')
310 | plt.plot(TIME[16]['x_axis'], TIME[16]['memory_usage']['usual'], '-')
311 | plt.legend(['merkle', 'patricia', 'usual'])
312 | plt.xlabel('No oF Tx')
313 | plt.ylabel('Size of Table');
314 | '''
315 |
316 |
317 | legend = []
318 | plt.figure(figsize=(20,10))
319 |
320 | for i in range(len(BLOCK_SIZES)):
321 | bs = BLOCK_SIZES[i]
322 | legend.append('block size:' + str(bs))
323 | plt.plot(time_[bs]['x_axis_adding_time'], time_[bs]['adding_time'], '--')
324 |
325 | plt.legend(legend)
326 | # plt.title('Среднее время обработки одной транзакции')
327 | plt.xlabel('No of Tx')
328 | plt.ylabel('Time(s)');
329 |
330 |
331 |
332 |
333 | #balance
334 | fig, axes = plt.subplots(nrows=2, ncols=3, sharex=False, figsize=(20, 10))
335 |
336 | for k in range(len(BLOCK_SIZES)):
337 | i, j = k // 3, k % 3
338 | bs = BLOCK_SIZES[k]
339 | x = time_[bs]['x_axis']
340 |
341 | y1 = time_[bs]['times_mean']['balance_request']['usual']
342 | std1 = time_[bs]['times_std']['balance_request']['usual']
343 |
344 | y2 = time_[bs]['times_mean']['balance_request']['specific']
345 | std2 = time_[bs]['times_std']['balance_request']['specific']
346 |
347 | axes[i, j].plot(x, y1, '-o')
348 | axes[i, j].plot(x, y2, '-o')
349 |
350 |
351 | axes[i, j].fill_between(x, y1 - 2 * std1, y1 + 2 * std1, alpha=0.1)
352 | axes[i, j].fill_between(x, y2 - 2 * std2, y2 + 2 * std2, alpha=0.1)
353 |
354 | axes[i, j].set_title('block size: ' + str(bs))
355 | axes[i, j].legend(['usual', 'patricia'])
356 | axes[i, j].set_xlabel('кол-во транзакции')
357 | axes[i, j].set_ylabel('время, сек')
358 | axes[i, j].set_ylim(bottom = 0, top=10)
359 |
360 |
361 |
362 |
363 | #Transction
364 | fig, axes = plt.subplots(nrows=2, ncols=3, sharex=False, figsize=(20, 10))
365 |
366 | for k in range(len(BLOCK_SIZES)):
367 | i, j = k // 3, k % 3
368 | bs = BLOCK_SIZES[k]
369 | x = time_[bs]['x_axis']
370 |
371 | y1 = time_[bs]['times_mean']['transaction_request']['usual']
372 | std1 = time_[bs]['times_std']['transaction_request']['usual']
373 |
374 | y2 = time_[bs]['times_mean']['transaction_request']['specific']
375 | std2 = time_[bs]['times_std']['transaction_request']['specific']
376 |
377 | axes[i, j].plot(x, y1, '-o')
378 | axes[i, j].plot(x, y2, '-o')
379 |
380 |
381 | axes[i, j].fill_between(x, y1 - 2 * std1, y1 + 2 * std1, alpha=0.1)
382 | axes[i, j].fill_between(x, y2 - 2 * std2, y2 + 2 * std2, alpha=0.1)
383 |
384 | axes[i, j].set_title('block size: ' + str(bs))
385 | axes[i, j].legend(['usual', 'merkle'])
386 | axes[i, j].set_xlabel('кол-во транзакции')
387 | axes[i, j].set_ylabel('время, сек')
388 | axes[i, j].set_ylim(bottom = 0, top=4)
389 |
390 |
391 | y_usual = []
392 | y_special = []
393 | x = []
394 | for k in range(len(BLOCK_SIZES)):
395 | bs = BLOCK_SIZES[k]
396 | x.append(bs)
397 | y_usual.append(time_[bs]['times_mean']['balance_request']['usual'].mean())
398 | y_special.append(time_[bs]['times_mean']['balance_request']['specific'].mean())
399 |
400 | plt.xlabel('Size of Block')
401 | #plt.xscale('log')
402 | plt.ylabel('Time')
403 | #plt.yscale('log')
404 | plt.plot(x, np.exp(np.array(y_usual)), '-o')
405 | plt.plot(x, np.exp(np.array(y_special)), '-o')
406 | plt.legend(['usual', 'patricia']);
407 |
--------------------------------------------------------------------------------
/With database/hashfunction.py:
--------------------------------------------------------------------------------
1 | from hashlib import sha256
2 |
3 |
4 | def get_hash(data, simple_hash=False):
5 | """
6 | input: data - string
7 | output: string, hash of data by sha256 algorithm
8 | """
9 | if simple_hash:
10 | return data
11 | return sha256(data.encode()).hexdigest()
12 |
--------------------------------------------------------------------------------
/With database/patricia_main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | """
4 | Created on Tue Jun 9 23:08:45 2020
5 |
6 | @author: yashmadhwal
7 | """
8 |
9 | from PatriciaDatabase import PatriciaDatabase
10 | from PatriciaTrie import PatriciaTrie
11 | from PatriciaDatabaseTest import test
12 |
13 | args = {
14 | 'DB_NAME': "blockchain_postgresql",
15 | #'DB_USER': "darkhannurlybay",
16 | #'DB_PASSWORD': "",
17 | #'DB_HOST': "localhost",
18 | 'DB_PORT': "5432",
19 | 'verbose': False
20 | }
21 |
22 | test(args)
23 |
24 | #Creating some Users
25 | user = {
26 | 'Alice': '000010',
27 | 'Bob': '010100',
28 | 'Sally': '111111',
29 | 'Yash': '1111100'
30 | }
31 |
32 | db = PatriciaDatabase(**args)
33 | db.delete_tables()
34 | db.create_tables()
35 |
36 | t = PatriciaTrie(db, simple_hash=False)
37 |
38 | t.create(user['Alice'], 100, '0000')
39 | t.create(user['Bob'], 50, '0001')
40 |
41 | db.print_column_name('PatriciaNode');
42 | for row in t.db.show_table('PatriciaNode'):
43 | print(row)
44 |
45 | t.draw()
46 | '''
47 | assert t.get_balance(user['Alice']) == 100
48 | assert t.get_balance(user['Bob']) == 50
49 |
50 |
51 | t.spend(user['Alice'], user['Bob'], 10, '1000')
52 | assert t.get_balance(user['Alice']) == 90
53 | assert t.get_balance(user['Bob']) == 60
54 | t.draw()
55 |
56 | db = PatriciaDatabase(**args)
57 | t = PatriciaTrie(db, simple_hash=False)
58 |
59 | t.create(user['Sally'], 0, '1111')
60 | t.spend(user['Alice'], user['Sally'], 40, '1001')
61 | assert t.get_balance(user['Alice']) == 50
62 | assert t.get_balance(user['Sally']) == 40
63 | t.draw()
64 |
65 | t.create(user['Yash'], 0, '0111')
66 | assert t.get_balance(user['Yash']) == 0
67 | t.draw()
68 |
69 | t.spend(user['Alice'], user['Yash'], 10, '1010')
70 | t.draw()
71 | db.close_session()
72 | '''
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Without database/GetBalanceExample.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 |
5 | # -*- coding: utf-8 -*-
6 | """
7 | Created on Mon Jun 29 13:00:53 2020
8 |
9 | @author: yashmadhwal
10 | """
11 | import hashlib
12 | import time
13 | import numpy as np
14 | import matplotlib.pyplot as plt
15 | import random
16 | from PatriciaTrie import PatriciaTrie
17 |
18 | samples = 4
19 | time_array = np.zeros((samples,),dtype=float)
20 | amount = 0
21 | transactions = 10000
22 | number_of_request = 10000 #number of request from individual users, if users are 2, imagine they are making individual request.
23 | initial_balance = 10**6
24 |
25 | for user_number_index in range(1,samples):
26 | number_of_users = 2 ** user_number_index
27 |
28 |
29 | #creating users as per user_index key[user_no]: value[hash(user_no)]
30 | #declaring empty dictionary
31 | user = {}
32 |
33 | #appending user:
34 | for i in range(number_of_users):
35 | user['user_'+str(i)] = hashlib.sha224(bytes(int(i))).hexdigest()
36 |
37 | #object of PatriciaTrie
38 | t = PatriciaTrie(simple_hash=False)
39 |
40 | #creating users with initial transaction
41 | for i in user:
42 | tx_hash = hashlib.sha224(i.encode('utf-8')).hexdigest()
43 | t.create([(user[i], initial_balance)], tx_hash)
44 |
45 | #Making Random Transactions
46 | for i in range(transactions):
47 | sender = random.choice(list(user))
48 | receiver = random.choice(list(i for i in user if i not in sender))
49 | amount = random.randint(0, 100)
50 | transaction_string = str(sender + receiver + str(amount))
51 | tx_hash_balance = hashlib.sha224(transaction_string.encode('utf-8')).hexdigest()
52 | t.spend(user[sender], user[receiver],amount , tx_hash_balance)
53 |
54 | #-----X and Y plot------
55 | random_indexes = np.random.randint(number_of_users, size=number_of_request)#This array or random index is the request id.
56 | users_for_requests = [hashlib.sha224(bytes(x)).hexdigest() for x in random_indexes]
57 | start_time = time.time()
58 | for request_index in range(number_of_request):
59 | t.info[t.dfs(0, users_for_requests[request_index], amount, update_balance=False, minus=False)]['balance']
60 | end_time = time.time()
61 | time_array[user_number_index] = end_time - start_time
62 |
63 | plt.title('Random Transfers Number', loc='center')
64 | plt.xlabel('Number of users')
65 | plt.ylabel('Time to retrieve request')
66 | #plt.xscale('log')
67 | plt.plot([2 ** x for x in range(1, samples)], time_array[1:],'k.-', linewidth=1, markersize=12)
68 | plt.show()
69 |
70 |
71 | visualize_graph = False
72 | if visualize_graph:
73 | t.draw()
--------------------------------------------------------------------------------
/Without database/MerkleTree.py:
--------------------------------------------------------------------------------
1 | from hashlib import sha256
2 | import copy
3 | from graphviz import Graph
4 |
5 |
6 | class MerkleTree:
7 | def __init__(self, transactions, block_id=1, simple_hash=True):
8 | """
9 | 0. transactions - list of transactions
10 | 1. self.simple_hash - boolean. If is True then function self.get_hash()
11 | return simple hash, otherwise return sha256
12 | 2. db - database where store all calculated hashes
13 | """
14 | self.block_id = block_id
15 | self.simple_hash = simple_hash
16 | self.node_hash = dict() # key: (block_id, level, position)
17 |
18 | # Complete transactions for full binary tree
19 | self.transactions = copy.deepcopy(transactions)
20 | idx = len(self.transactions)
21 | for i in range(self.complete_to_full(idx) - idx):
22 | self.transactions += [self.transactions[-1]]
23 | self.height = self.get_height(len(self.transactions))
24 |
25 | # Calculating hash of each node in merkle tree
26 | for i in range(len(self.transactions)):
27 | self.node_hash[(self.block_id, 1, i + 1)] = self.transactions[i]
28 | _ = self.calculate_hash(self.transactions)
29 |
30 | def get_hash(self, data):
31 | """
32 | input: data - string
33 | output: string, hash of data by sha256 algorithm
34 | """
35 | if self.simple_hash:
36 | return data
37 | return sha256(data.encode()).hexdigest()
38 |
39 | def calculate_hash(self, hashes, level=1):
40 | """
41 | recursive function for calculating hash
42 | of each node in merkle tree.
43 | """
44 | if len(hashes) == 1:
45 | return hashes[0]
46 |
47 | temp = []
48 | for i in range(0, len(hashes), 2):
49 | _hash = self.get_hash(hashes[i] + hashes[i + 1])
50 | self.node_hash[(self.block_id, level + 1, i // 2 + 1)] = _hash
51 | temp.append(_hash)
52 |
53 | return self.calculate_hash(temp, level + 1)
54 |
55 | def dfs(self, G, level, idx):
56 | """
57 | recursive function for draw merkle tree
58 | """
59 | if level == 1:
60 | return
61 |
62 | node = str(level) + ',' + str(idx)
63 |
64 | # left child
65 | node_left = str(level - 1) + ',' + str(self.left_child(idx))
66 | G.edge(node, node_left)
67 | self.dfs(G, level - 1, self.left_child(idx))
68 |
69 | # right child
70 | node_right = str(level - 1) + ',' + str(self.right_child(idx))
71 | G.edge(node, node_right)
72 | self.dfs(G, level - 1, self.right_child(idx))
73 |
74 | def draw(self, validation_nodes=None, idx_for_validation=None):
75 | """This function draw Merkle tree"""
76 | G = Graph()
77 |
78 | # Defining all nodes
79 | for row in self.node_hash: # blockid, level, position
80 | name = str(row[1]) + ',' + str(row[2])
81 | label = self.node_hash[row]
82 |
83 | if validation_nodes is None:
84 | G.node(name=name, label=label)
85 | elif (row[1], row[2]) in validation_nodes:
86 | G.node(name=name, label=label, color='lightblue2', style='filled')
87 | elif (row[1], row[2]) == (1, idx_for_validation):
88 | G.node(name=name, label=label, color='violet', style='filled')
89 | else:
90 | G.node(name=name, label=label)
91 |
92 | # Adding edges
93 | self.dfs(G, self.height, 1)
94 | return G
95 |
96 | @staticmethod
97 | def get_height(idx):
98 | """
99 | input: idx - right index for complete tree. Example idx = 1, 2, 4, 8,...
100 | output: level of complete this tree
101 | """
102 | level = 0
103 | while idx > 0:
104 | level += 1
105 | idx //= 2
106 | return level
107 |
108 | @staticmethod
109 | def complete_to_full(idx):
110 | """
111 | input: idx - right index of tree.
112 | output: right index for complete tree
113 | """
114 | # if idx == 1:
115 | # return 1
116 | i = 1
117 | while i * 2 < idx:
118 | i *= 2
119 | return 2 * i
120 |
121 | @staticmethod
122 | def parent(idx):
123 | if idx % 2 == 0:
124 | return idx // 2
125 | return idx // 2 + 1
126 |
127 | @staticmethod
128 | def left_child(idx):
129 | return 2 * idx - 1
130 |
131 | @staticmethod
132 | def right_child(idx):
133 | return 2 * idx
134 |
135 | def get_nodes_for_validation(self, idx, get_graph=False):
136 | """
137 | idx: index in [1, 2, ...]
138 | :return list of nodes for validation
139 | """
140 | idx_for_validation = idx
141 | result = []
142 | for i in range(self.height - 1):
143 | prev = idx
144 | idx = self.parent(idx)
145 | left = self.left_child(idx)
146 | right = self.right_child(idx)
147 | if left == prev:
148 | result += [(i + 1, right)]
149 | else:
150 | result += [(i + 1, left)]
151 | if get_graph:
152 | return result, self.draw(result, idx_for_validation)
153 | return result
--------------------------------------------------------------------------------
/Without database/MerkleTreeExample.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from MerkleTree import MerkleTree"
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": 2,
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "transactions = [chr(97 + i) for i in range(7)]\n",
19 | "tree = MerkleTree(transactions)"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": 3,
25 | "metadata": {},
26 | "outputs": [
27 | {
28 | "data": {
29 | "image/svg+xml": [
30 | "\n",
31 | "\n",
33 | "\n",
35 | "\n",
36 | "\n"
203 | ],
204 | "text/plain": [
205 | ""
206 | ]
207 | },
208 | "execution_count": 3,
209 | "metadata": {},
210 | "output_type": "execute_result"
211 | }
212 | ],
213 | "source": [
214 | "tree.draw()"
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": 4,
220 | "metadata": {},
221 | "outputs": [
222 | {
223 | "data": {
224 | "image/svg+xml": [
225 | "\n",
226 | "\n",
228 | "\n",
230 | "\n",
231 | "\n"
398 | ],
399 | "text/plain": [
400 | ""
401 | ]
402 | },
403 | "execution_count": 4,
404 | "metadata": {},
405 | "output_type": "execute_result"
406 | }
407 | ],
408 | "source": [
409 | "result, G = tree.get_nodes_for_validation(idx=1, get_graph=True)\n",
410 | "G"
411 | ]
412 | },
413 | {
414 | "cell_type": "code",
415 | "execution_count": null,
416 | "metadata": {},
417 | "outputs": [],
418 | "source": []
419 | }
420 | ],
421 | "metadata": {
422 | "kernelspec": {
423 | "display_name": "Python 3",
424 | "language": "python",
425 | "name": "python3"
426 | },
427 | "language_info": {
428 | "codemirror_mode": {
429 | "name": "ipython",
430 | "version": 3
431 | },
432 | "file_extension": ".py",
433 | "mimetype": "text/x-python",
434 | "name": "python",
435 | "nbconvert_exporter": "python",
436 | "pygments_lexer": "ipython3",
437 | "version": "3.7.6"
438 | }
439 | },
440 | "nbformat": 4,
441 | "nbformat_minor": 4
442 | }
443 |
--------------------------------------------------------------------------------
/Without database/PatriciaTrie.py:
--------------------------------------------------------------------------------
1 | from hashlib import sha256
2 | from graphviz import Digraph
3 |
4 | class PatriciaTrie:
5 | def __init__(self, simple_hash=True):
6 | self.simple_hash = simple_hash
7 | self.table = dict() # key: node_id, value: [child idx, prefix]
8 | self.cnt = 0 # count of nodes
9 | self.info = dict() # Store info about balance, parent_id, hash, type of node.
10 | self.info[0] = dict({'type': 'root'})
11 |
12 | def get_hash(self, data: str) -> str:
13 | """input: data - string
14 | output: string, hash of data by sha256 algorithm
15 | """
16 | if self.simple_hash:
17 | return data
18 | sha_value = sha256(data.encode()).hexdigest()
19 | return sha_value
20 |
21 | def create(self, tx: list, txhash: str):
22 | """
23 | :param tx: list of transactions: [(user1, amount1), (user2, amount2), ...]
24 | txhash: A hash of these transactions which calculated by RPL
25 | :return: None. Just update MPT
26 | """
27 | for (user, amount) in tx:
28 | # Find user in MPT
29 | idx = self.dfs(0, user, amount, update_balance=False, minus=False)
30 | self.info[idx]['type'] = 'user'
31 |
32 | # Find transaction hash for given user
33 | idx = self.dfs(idx, txhash, amount, update_balance=True, minus=False)
34 | self.info[idx]['balance'] = amount
35 | self.info[idx]['type'] = 'leaf'
36 |
37 | # case 1: hash_leaf = hash(tx_hash|balance_charge)
38 | self.info[idx]['hash'] = self.get_hash('(hash:' + txhash + '|' + 'blc:' + str(amount) + ')')
39 | self.update_node_hash(idx)
40 |
41 | def spend(self, user1: str, user2: str, amount: int, txhash: str):
42 | """
43 | :param tx: list of transactions: [user1, user2, amount, transaction hash]
44 | :param txhash: A hash of these transactions which calculated by RPL
45 | :return: None. Just updated MPT
46 | """
47 | minus = True
48 | for user in [user1, user2]:
49 | # Find user1 in MPT
50 | idx = self.dfs(0, user, amount, update_balance=False, minus=False)
51 | self.info[idx]['type'] = 'user'
52 |
53 | # Find transacntion hash for given user
54 | idx = self.dfs(idx, txhash, amount, update_balance=True, minus=minus)
55 | self.info[idx]['type'] = 'leaf'
56 |
57 | if minus:
58 | self.info[idx]['balance'] = -amount
59 | else:
60 | self.info[idx]['balance'] = amount
61 |
62 | # case 1: hash_leaf = hash(tx_hash|balance_charge)
63 | self.info[idx]['hash'] = self.get_hash('(hash:' + txhash + '|' + 'blc:' + str(amount) + ')')
64 | self.update_node_hash(idx)
65 |
66 | minus = False
67 |
68 | def common_prefix_len(self, prefix1: str, prefix2: str):
69 | """
70 | Helper function for self._dfs. Return command prefix for prefix1 and prefix2
71 | len(prefix1) <= len(prefix2)
72 | """
73 | same = 0
74 | for i in range(len(prefix1)):
75 | if prefix1[i] == prefix2[i]:
76 | same += 1
77 | else:
78 | break
79 | return same
80 |
81 | def dfs(self, idx: int, prefix: str, amount: int, update_balance=False, minus=False):
82 | """
83 | Helper recursive function for self.create
84 | :param idx: id for starting node
85 | :param prefix: prefix for searching. prefix user or hash
86 | :param hash: hash of transaction
87 | :return: None. Just updated MPT
88 | """
89 | if prefix == '':
90 | return idx
91 |
92 | # Initially we don't find any branch of node idx, which have
93 | # at least one same character with given prefix
94 | found = False
95 | if update_balance:
96 | if 'balance' not in self.info[idx]:
97 | self.info[idx]['balance'] = 0
98 | if minus:
99 | self.info[idx]['balance'] -= amount
100 | else:
101 | self.info[idx]['balance'] += amount
102 |
103 | if idx in self.table:
104 | for branch in self.table[idx]: # branch: [child idx, prefix]
105 | k = self.common_prefix_len(branch[1], prefix)
106 | if k > 0:
107 | found = True
108 | if k == len(branch[1]):
109 | return self.dfs(branch[0], prefix[k:], amount, update_balance, minus)
110 | else:
111 | child_id = branch[0]
112 | diff = branch[1][k:]
113 | self.cnt += 1
114 | branch[0] = self.cnt
115 | branch[1] = prefix[: k]
116 |
117 | # parent
118 | self.info[self.cnt] = dict({'parent': idx, 'type': 'before'})
119 | self.info[child_id]['parent'] = self.cnt
120 |
121 | self.table[self.cnt] = []
122 | self.table[self.cnt].append([child_id, diff])
123 | self.table[self.cnt].append([self.cnt + 1, prefix[k:]])
124 | if update_balance:
125 | self.info[self.cnt]['type'] = 'descendant'
126 |
127 | # parent
128 | self.info[self.cnt + 1] = dict({'parent': self.cnt, 'type': 'before'})
129 |
130 | # update transaction balance:
131 | if update_balance:
132 | if minus:
133 | self.info[self.cnt]['balance'] = self.info[child_id]['balance'] - amount
134 | else:
135 | self.info[self.cnt]['balance'] = self.info[child_id]['balance'] + amount
136 | self.cnt += 1
137 | return self.cnt
138 | if not found:
139 | if idx not in self.table:
140 | self.table[idx] = []
141 | self.table[idx].append([self.cnt + 1, prefix])
142 | self.info[self.cnt + 1] = dict({'parent': idx})
143 | self.cnt += 1
144 | return self.cnt
145 |
146 |
147 | def update_node_hash(self, idx: int):
148 | """
149 | case 1:
150 | If a leaf vertex (corresponds to a transaction):
151 | hash_leaf = hash(balance_charge|tx_hash)
152 | case 2:
153 | If the vertex is descendant of the user:
154 | hash(balance|prefix_to_child_node + hash of child| ...)
155 | case 3:
156 | If the vertex is a user:
157 | hash_user = hash(id|balance|prefix_to_child_node + hash of child| ...)
158 | case 4:
159 | If the vertex is the ancestor (before) of users (for example, root),
160 | hash(prefix_to_child_node + hash of child| ...)
161 | """
162 | # Go up
163 | while idx != 0:
164 | idx = self.info[idx]['parent']
165 | if self.info[idx]['type'] == 'descendant':
166 | temp = '(blc:' + str(self.info[idx]['balance']) + ')|'
167 | elif self.info[idx]['type'] == 'user':
168 | temp = '(id:' + str(str(idx) + '|' + 'blc:' + str(self.info[idx]['balance'])) + ')|'
169 | else:
170 | temp = ''
171 |
172 | # cancatinating of child hashes
173 | for branch in self.table[idx]: # [child idx, prefix]
174 | temp += '(' + 'prf:' + branch[1] + '|' + 'hash:' + self.info[branch[0]]['hash'] + ')'
175 | self.info[idx]['hash'] = self.get_hash(temp)
176 |
177 | def beautify(self, G, idx: int):
178 | """
179 | Helper function for self.draw
180 | """
181 | node_type = self.info[idx]['type']
182 | if 'user' == node_type :
183 | G.node(
184 | str(idx),
185 | str(idx) + "\nUser balance = " + str(self.info[idx]['balance']),
186 | shape='square', color='gold1', style='filled'
187 | )
188 | elif 'leaf' == node_type:
189 | G.node(
190 | str(idx),
191 | str(idx) + "\ntx = " + str(self.info[idx]['balance']),
192 | color='lightblue2', style='filled'
193 | )
194 | elif 'descendant' == node_type:
195 | G.node(
196 | str(idx),
197 | str(idx) + "\nbalance = " + str(self.info[idx]['balance'])
198 | )
199 | else:
200 | G.node(str(idx))
201 |
202 | def draw(self):
203 | """
204 | This function draw Merkle patricia tree
205 | """
206 | G = Digraph()
207 |
208 | # Defining all nodes
209 | for parent_id in self.table:
210 | self.beautify(G, parent_id)
211 |
212 | for branch in self.table[parent_id]:
213 | self.beautify(G, branch[0])
214 |
215 | # Adding edges
216 | for parent_id in self.table:
217 | for branch in self.table[parent_id]:
218 | G.edge(str(parent_id), str(branch[0]), label=branch[1])
219 | return G
220 |
--------------------------------------------------------------------------------