├── .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", 157 | "\n", 158 | "%3\n", 159 | "\n", 160 | "\n", 161 | "\n", 162 | "2\n", 163 | "\n", 164 | "2\n", 165 | "tx = 100\n", 166 | "\n", 167 | "\n", 168 | "\n", 169 | "1\n", 170 | "\n", 171 | "1\n", 172 | "User balance = 100\n", 173 | "\n", 174 | "\n", 175 | "\n", 176 | "1->2\n", 177 | "\n", 178 | "\n", 179 | "0000\n", 180 | "\n", 181 | "\n", 182 | "\n", 183 | "5\n", 184 | "\n", 185 | "5\n", 186 | "tx = 50\n", 187 | "\n", 188 | "\n", 189 | "\n", 190 | "4\n", 191 | "\n", 192 | "4\n", 193 | "User balance = 50\n", 194 | "\n", 195 | "\n", 196 | "\n", 197 | "4->5\n", 198 | "\n", 199 | "\n", 200 | "0001\n", 201 | "\n", 202 | "\n", 203 | "\n", 204 | "3\n", 205 | "\n", 206 | "3\n", 207 | "\n", 208 | "\n", 209 | "\n", 210 | "3->1\n", 211 | "\n", 212 | "\n", 213 | "00010\n", 214 | "\n", 215 | "\n", 216 | "\n", 217 | "3->4\n", 218 | "\n", 219 | "\n", 220 | "10100\n", 221 | "\n", 222 | "\n", 223 | "\n", 224 | "0\n", 225 | "\n", 226 | "0\n", 227 | "\n", 228 | "\n", 229 | "\n", 230 | "0->3\n", 231 | "\n", 232 | "\n", 233 | "0\n", 234 | "\n", 235 | "\n", 236 | "\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", 290 | "\n", 291 | "%3\n", 292 | "\n", 293 | "\n", 294 | "\n", 295 | "2\n", 296 | "\n", 297 | "2\n", 298 | "tx = 100\n", 299 | "\n", 300 | "\n", 301 | "\n", 302 | "5\n", 303 | "\n", 304 | "5\n", 305 | "tx = 50\n", 306 | "\n", 307 | "\n", 308 | "\n", 309 | "6\n", 310 | "\n", 311 | "6\n", 312 | "tx = -10\n", 313 | "\n", 314 | "\n", 315 | "\n", 316 | "1\n", 317 | "\n", 318 | "1\n", 319 | "User balance = 90\n", 320 | "\n", 321 | "\n", 322 | "\n", 323 | "1->2\n", 324 | "\n", 325 | "\n", 326 | "0000\n", 327 | "\n", 328 | "\n", 329 | "\n", 330 | "1->6\n", 331 | "\n", 332 | "\n", 333 | "1000\n", 334 | "\n", 335 | "\n", 336 | "\n", 337 | "7\n", 338 | "\n", 339 | "7\n", 340 | "tx = 10\n", 341 | "\n", 342 | "\n", 343 | "\n", 344 | "4\n", 345 | "\n", 346 | "4\n", 347 | "User balance = 60\n", 348 | "\n", 349 | "\n", 350 | "\n", 351 | "4->5\n", 352 | "\n", 353 | "\n", 354 | "0001\n", 355 | "\n", 356 | "\n", 357 | "\n", 358 | "4->7\n", 359 | "\n", 360 | "\n", 361 | "1000\n", 362 | "\n", 363 | "\n", 364 | "\n", 365 | "3\n", 366 | "\n", 367 | "3\n", 368 | "\n", 369 | "\n", 370 | "\n", 371 | "3->1\n", 372 | "\n", 373 | "\n", 374 | "00010\n", 375 | "\n", 376 | "\n", 377 | "\n", 378 | "3->4\n", 379 | "\n", 380 | "\n", 381 | "10100\n", 382 | "\n", 383 | "\n", 384 | "\n", 385 | "0\n", 386 | "\n", 387 | "0\n", 388 | "\n", 389 | "\n", 390 | "\n", 391 | "0->3\n", 392 | "\n", 393 | "\n", 394 | "0\n", 395 | "\n", 396 | "\n", 397 | "\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", 450 | "\n", 451 | "%3\n", 452 | "\n", 453 | "\n", 454 | "\n", 455 | "2\n", 456 | "\n", 457 | "2\n", 458 | "tx = 100\n", 459 | "\n", 460 | "\n", 461 | "\n", 462 | "5\n", 463 | "\n", 464 | "5\n", 465 | "tx = 50\n", 466 | "\n", 467 | "\n", 468 | "\n", 469 | "7\n", 470 | "\n", 471 | "7\n", 472 | "tx = 10\n", 473 | "\n", 474 | "\n", 475 | "\n", 476 | "4\n", 477 | "\n", 478 | "4\n", 479 | "User balance = 60\n", 480 | "\n", 481 | "\n", 482 | "\n", 483 | "4->5\n", 484 | "\n", 485 | "\n", 486 | "0001\n", 487 | "\n", 488 | "\n", 489 | "\n", 490 | "4->7\n", 491 | "\n", 492 | "\n", 493 | "1000\n", 494 | "\n", 495 | "\n", 496 | "\n", 497 | "6\n", 498 | "\n", 499 | "6\n", 500 | "tx = -10\n", 501 | "\n", 502 | "\n", 503 | "\n", 504 | "11\n", 505 | "\n", 506 | "11\n", 507 | "tx = -40\n", 508 | "\n", 509 | "\n", 510 | "\n", 511 | "10\n", 512 | "\n", 513 | "10\n", 514 | "balance = -50\n", 515 | "\n", 516 | "\n", 517 | "\n", 518 | "10->6\n", 519 | "\n", 520 | "\n", 521 | "0\n", 522 | "\n", 523 | "\n", 524 | "\n", 525 | "10->11\n", 526 | "\n", 527 | "\n", 528 | "1\n", 529 | "\n", 530 | "\n", 531 | "\n", 532 | "1\n", 533 | "\n", 534 | "1\n", 535 | "User balance = 50\n", 536 | "\n", 537 | "\n", 538 | "\n", 539 | "1->2\n", 540 | "\n", 541 | "\n", 542 | "0000\n", 543 | "\n", 544 | "\n", 545 | "\n", 546 | "1->10\n", 547 | "\n", 548 | "\n", 549 | "100\n", 550 | "\n", 551 | "\n", 552 | "\n", 553 | "3\n", 554 | "\n", 555 | "3\n", 556 | "\n", 557 | "\n", 558 | "\n", 559 | "3->4\n", 560 | "\n", 561 | "\n", 562 | "10100\n", 563 | "\n", 564 | "\n", 565 | "\n", 566 | "3->1\n", 567 | "\n", 568 | "\n", 569 | "00010\n", 570 | "\n", 571 | "\n", 572 | "\n", 573 | "9\n", 574 | "\n", 575 | "9\n", 576 | "tx = 0\n", 577 | "\n", 578 | "\n", 579 | "\n", 580 | "13\n", 581 | "\n", 582 | "13\n", 583 | "tx = 40\n", 584 | "\n", 585 | "\n", 586 | "\n", 587 | "12\n", 588 | "\n", 589 | "12\n", 590 | "balance = 40\n", 591 | "\n", 592 | "\n", 593 | "\n", 594 | "12->9\n", 595 | "\n", 596 | "\n", 597 | "111\n", 598 | "\n", 599 | "\n", 600 | "\n", 601 | "12->13\n", 602 | "\n", 603 | "\n", 604 | "001\n", 605 | "\n", 606 | "\n", 607 | "\n", 608 | "8\n", 609 | "\n", 610 | "8\n", 611 | "User balance = 40\n", 612 | "\n", 613 | "\n", 614 | "\n", 615 | "8->12\n", 616 | "\n", 617 | "\n", 618 | "1\n", 619 | "\n", 620 | "\n", 621 | "\n", 622 | "0\n", 623 | "\n", 624 | "0\n", 625 | "\n", 626 | "\n", 627 | "\n", 628 | "0->3\n", 629 | "\n", 630 | "\n", 631 | "0\n", 632 | "\n", 633 | "\n", 634 | "\n", 635 | "0->8\n", 636 | "\n", 637 | "\n", 638 | "111111\n", 639 | "\n", 640 | "\n", 641 | "\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", 676 | "\n", 677 | "%3\n", 678 | "\n", 679 | "\n", 680 | "\n", 681 | "4\n", 682 | "\n", 683 | "4\n", 684 | "User balance = 10\n", 685 | "\n", 686 | "\n", 687 | "\n", 688 | "5\n", 689 | "\n", 690 | "5\n", 691 | "tx = 50\n", 692 | "\n", 693 | "\n", 694 | "\n", 695 | "4->5\n", 696 | "\n", 697 | "\n", 698 | "0001\n", 699 | "\n", 700 | "\n", 701 | "\n", 702 | "14\n", 703 | "\n", 704 | "14\n", 705 | "balance = -40\n", 706 | "\n", 707 | "\n", 708 | "\n", 709 | "4->14\n", 710 | "\n", 711 | "\n", 712 | "100\n", 713 | "\n", 714 | "\n", 715 | "\n", 716 | "3\n", 717 | "\n", 718 | "3\n", 719 | "\n", 720 | "\n", 721 | "\n", 722 | "3->4\n", 723 | "\n", 724 | "\n", 725 | "10100\n", 726 | "\n", 727 | "\n", 728 | "\n", 729 | "1\n", 730 | "\n", 731 | "1\n", 732 | "User balance = 50\n", 733 | "\n", 734 | "\n", 735 | "\n", 736 | "3->1\n", 737 | "\n", 738 | "\n", 739 | "00010\n", 740 | "\n", 741 | "\n", 742 | "\n", 743 | "2\n", 744 | "\n", 745 | "2\n", 746 | "tx = 100\n", 747 | "\n", 748 | "\n", 749 | "\n", 750 | "13\n", 751 | "\n", 752 | "13\n", 753 | "tx = 40\n", 754 | "\n", 755 | "\n", 756 | "\n", 757 | "17\n", 758 | "\n", 759 | "17\n", 760 | "tx = 50\n", 761 | "\n", 762 | "\n", 763 | "\n", 764 | "16\n", 765 | "\n", 766 | "16\n", 767 | "balance = 90\n", 768 | "\n", 769 | "\n", 770 | "\n", 771 | "16->13\n", 772 | "\n", 773 | "\n", 774 | "1\n", 775 | "\n", 776 | "\n", 777 | "\n", 778 | "16->17\n", 779 | "\n", 780 | "\n", 781 | "2\n", 782 | "\n", 783 | "\n", 784 | "\n", 785 | "12\n", 786 | "\n", 787 | "12\n", 788 | "balance = 90\n", 789 | "\n", 790 | "\n", 791 | "\n", 792 | "12->16\n", 793 | "\n", 794 | "\n", 795 | "00\n", 796 | "\n", 797 | "\n", 798 | "\n", 799 | "9\n", 800 | "\n", 801 | "9\n", 802 | "tx = 0\n", 803 | "\n", 804 | "\n", 805 | "\n", 806 | "12->9\n", 807 | "\n", 808 | "\n", 809 | "111\n", 810 | "\n", 811 | "\n", 812 | "\n", 813 | "8\n", 814 | "\n", 815 | "8\n", 816 | "User balance = 90\n", 817 | "\n", 818 | "\n", 819 | "\n", 820 | "8->12\n", 821 | "\n", 822 | "\n", 823 | "1\n", 824 | "\n", 825 | "\n", 826 | "\n", 827 | "0\n", 828 | "\n", 829 | "0\n", 830 | "\n", 831 | "\n", 832 | "\n", 833 | "0->3\n", 834 | "\n", 835 | "\n", 836 | "0\n", 837 | "\n", 838 | "\n", 839 | "\n", 840 | "0->8\n", 841 | "\n", 842 | "\n", 843 | "111111\n", 844 | "\n", 845 | "\n", 846 | "\n", 847 | "6\n", 848 | "\n", 849 | "6\n", 850 | "tx = -10\n", 851 | "\n", 852 | "\n", 853 | "\n", 854 | "11\n", 855 | "\n", 856 | "11\n", 857 | "tx = -40\n", 858 | "\n", 859 | "\n", 860 | "\n", 861 | "10\n", 862 | "\n", 863 | "10\n", 864 | "balance = -50\n", 865 | "\n", 866 | "\n", 867 | "\n", 868 | "10->6\n", 869 | "\n", 870 | "\n", 871 | "0\n", 872 | "\n", 873 | "\n", 874 | "\n", 875 | "10->11\n", 876 | "\n", 877 | "\n", 878 | "1\n", 879 | "\n", 880 | "\n", 881 | "\n", 882 | "1->2\n", 883 | "\n", 884 | "\n", 885 | "0000\n", 886 | "\n", 887 | "\n", 888 | "\n", 889 | "1->10\n", 890 | "\n", 891 | "\n", 892 | "100\n", 893 | "\n", 894 | "\n", 895 | "\n", 896 | "7\n", 897 | "\n", 898 | "7\n", 899 | "tx = 10\n", 900 | "\n", 901 | "\n", 902 | "\n", 903 | "15\n", 904 | "\n", 905 | "15\n", 906 | "tx = -50\n", 907 | "\n", 908 | "\n", 909 | "\n", 910 | "14->7\n", 911 | "\n", 912 | "\n", 913 | "0\n", 914 | "\n", 915 | "\n", 916 | "\n", 917 | "14->15\n", 918 | "\n", 919 | "\n", 920 | "2\n", 921 | "\n", 922 | "\n", 923 | "\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", 246 | "\n", 247 | "%3\n", 248 | "\n", 249 | "\n", 250 | "\n", 251 | "13\n", 252 | "\n", 253 | "13\n", 254 | "balance = -85\n", 255 | "\n", 256 | "\n", 257 | "\n", 258 | "11\n", 259 | "\n", 260 | "11\n", 261 | "tx = -39\n", 262 | "\n", 263 | "\n", 264 | "\n", 265 | "13->11\n", 266 | "\n", 267 | "\n", 268 | "3d157fa1ac48831be80605848659f582c3c6685878af83590bd0fb0\n", 269 | "\n", 270 | "\n", 271 | "\n", 272 | "14\n", 273 | "\n", 274 | "14\n", 275 | "tx = -46\n", 276 | "\n", 277 | "\n", 278 | "\n", 279 | "13->14\n", 280 | "\n", 281 | "\n", 282 | "7f8b1e6d15ac7daff3f004d99b1d6b4157c797e2c4dd05868276ced\n", 283 | "\n", 284 | "\n", 285 | "\n", 286 | "1\n", 287 | "\n", 288 | "1\n", 289 | "User balance = 999739\n", 290 | "\n", 291 | "\n", 292 | "\n", 293 | "1->13\n", 294 | "\n", 295 | "\n", 296 | "e\n", 297 | "\n", 298 | "\n", 299 | "\n", 300 | "2\n", 301 | "\n", 302 | "2\n", 303 | "tx = 1000000\n", 304 | "\n", 305 | "\n", 306 | "\n", 307 | "1->2\n", 308 | "\n", 309 | "\n", 310 | "64fd0e7160668c7db724a0fdc07ede6a46d50187635833c6af1a9adf\n", 311 | "\n", 312 | "\n", 313 | "\n", 314 | "5\n", 315 | "\n", 316 | "5\n", 317 | "tx = -96\n", 318 | "\n", 319 | "\n", 320 | "\n", 321 | "1->5\n", 322 | "\n", 323 | "\n", 324 | "5f12e6031f35d2d599b227b52142184b60786005029292ee289f54ca\n", 325 | "\n", 326 | "\n", 327 | "\n", 328 | "7\n", 329 | "\n", 330 | "7\n", 331 | "tx = -91\n", 332 | "\n", 333 | "\n", 334 | "\n", 335 | "1->7\n", 336 | "\n", 337 | "\n", 338 | "10956e66f17a2e4aa9daeba7413a39d940c095a97dac04a024cd3332\n", 339 | "\n", 340 | "\n", 341 | "\n", 342 | "10\n", 343 | "\n", 344 | "10\n", 345 | "tx = 11\n", 346 | "\n", 347 | "\n", 348 | "\n", 349 | "1->10\n", 350 | "\n", 351 | "\n", 352 | "0fc587411f5cc5686200b72bfc3d317a488d4987e7f2ee120812b231\n", 353 | "\n", 354 | "\n", 355 | "\n", 356 | "12\n", 357 | "\n", 358 | "12\n", 359 | "tx = 39\n", 360 | "\n", 361 | "\n", 362 | "\n", 363 | "4\n", 364 | "\n", 365 | "4\n", 366 | "tx = 1000000\n", 367 | "\n", 368 | "\n", 369 | "\n", 370 | "16\n", 371 | "\n", 372 | "16\n", 373 | "tx = 46\n", 374 | "\n", 375 | "\n", 376 | "\n", 377 | "15\n", 378 | "\n", 379 | "15\n", 380 | "balance = 85\n", 381 | "\n", 382 | "\n", 383 | "\n", 384 | "15->12\n", 385 | "\n", 386 | "\n", 387 | "3d157fa1ac48831be80605848659f582c3c6685878af83590bd0fb0\n", 388 | "\n", 389 | "\n", 390 | "\n", 391 | "15->16\n", 392 | "\n", 393 | "\n", 394 | "7f8b1e6d15ac7daff3f004d99b1d6b4157c797e2c4dd05868276ced\n", 395 | "\n", 396 | "\n", 397 | "\n", 398 | "3\n", 399 | "\n", 400 | "3\n", 401 | "User balance = 1000261\n", 402 | "\n", 403 | "\n", 404 | "\n", 405 | "3->4\n", 406 | "\n", 407 | "\n", 408 | "4afaca94959771860447a4f657d8fb22d8729057cbac7e03b3343bd6\n", 409 | "\n", 410 | "\n", 411 | "\n", 412 | "3->15\n", 413 | "\n", 414 | "\n", 415 | "e\n", 416 | "\n", 417 | "\n", 418 | "\n", 419 | "6\n", 420 | "\n", 421 | "6\n", 422 | "tx = 96\n", 423 | "\n", 424 | "\n", 425 | "\n", 426 | "3->6\n", 427 | "\n", 428 | "\n", 429 | "5f12e6031f35d2d599b227b52142184b60786005029292ee289f54ca\n", 430 | "\n", 431 | "\n", 432 | "\n", 433 | "8\n", 434 | "\n", 435 | "8\n", 436 | "tx = 91\n", 437 | "\n", 438 | "\n", 439 | "\n", 440 | "3->8\n", 441 | "\n", 442 | "\n", 443 | "10956e66f17a2e4aa9daeba7413a39d940c095a97dac04a024cd3332\n", 444 | "\n", 445 | "\n", 446 | "\n", 447 | "9\n", 448 | "\n", 449 | "9\n", 450 | "tx = -11\n", 451 | "\n", 452 | "\n", 453 | "\n", 454 | "3->9\n", 455 | "\n", 456 | "\n", 457 | "0fc587411f5cc5686200b72bfc3d317a488d4987e7f2ee120812b231\n", 458 | "\n", 459 | "\n", 460 | "\n", 461 | "0\n", 462 | "\n", 463 | "0\n", 464 | "\n", 465 | "\n", 466 | "\n", 467 | "0->1\n", 468 | "\n", 469 | "\n", 470 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f\n", 471 | "\n", 472 | "\n", 473 | "\n", 474 | "0->3\n", 475 | "\n", 476 | "\n", 477 | "fff9292b4201617bdc4d3053fce02734166a683d7d858a7f5f59b073\n", 478 | "\n", 479 | "\n", 480 | "\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", 38 | "\n", 39 | "%3\n", 40 | "\n", 41 | "\n", 42 | "\n", 43 | "1,1\n", 44 | "\n", 45 | "a\n", 46 | "\n", 47 | "\n", 48 | "\n", 49 | "1,2\n", 50 | "\n", 51 | "b\n", 52 | "\n", 53 | "\n", 54 | "\n", 55 | "1,3\n", 56 | "\n", 57 | "c\n", 58 | "\n", 59 | "\n", 60 | "\n", 61 | "1,4\n", 62 | "\n", 63 | "d\n", 64 | "\n", 65 | "\n", 66 | "\n", 67 | "1,5\n", 68 | "\n", 69 | "e\n", 70 | "\n", 71 | "\n", 72 | "\n", 73 | "1,6\n", 74 | "\n", 75 | "f\n", 76 | "\n", 77 | "\n", 78 | "\n", 79 | "1,7\n", 80 | "\n", 81 | "g\n", 82 | "\n", 83 | "\n", 84 | "\n", 85 | "1,8\n", 86 | "\n", 87 | "g\n", 88 | "\n", 89 | "\n", 90 | "\n", 91 | "2,1\n", 92 | "\n", 93 | "ab\n", 94 | "\n", 95 | "\n", 96 | "\n", 97 | "2,1--1,1\n", 98 | "\n", 99 | "\n", 100 | "\n", 101 | "\n", 102 | "2,1--1,2\n", 103 | "\n", 104 | "\n", 105 | "\n", 106 | "\n", 107 | "2,2\n", 108 | "\n", 109 | "cd\n", 110 | "\n", 111 | "\n", 112 | "\n", 113 | "2,2--1,3\n", 114 | "\n", 115 | "\n", 116 | "\n", 117 | "\n", 118 | "2,2--1,4\n", 119 | "\n", 120 | "\n", 121 | "\n", 122 | "\n", 123 | "2,3\n", 124 | "\n", 125 | "ef\n", 126 | "\n", 127 | "\n", 128 | "\n", 129 | "2,3--1,5\n", 130 | "\n", 131 | "\n", 132 | "\n", 133 | "\n", 134 | "2,3--1,6\n", 135 | "\n", 136 | "\n", 137 | "\n", 138 | "\n", 139 | "2,4\n", 140 | "\n", 141 | "gg\n", 142 | "\n", 143 | "\n", 144 | "\n", 145 | "2,4--1,7\n", 146 | "\n", 147 | "\n", 148 | "\n", 149 | "\n", 150 | "2,4--1,8\n", 151 | "\n", 152 | "\n", 153 | "\n", 154 | "\n", 155 | "3,1\n", 156 | "\n", 157 | "abcd\n", 158 | "\n", 159 | "\n", 160 | "\n", 161 | "3,1--2,1\n", 162 | "\n", 163 | "\n", 164 | "\n", 165 | "\n", 166 | "3,1--2,2\n", 167 | "\n", 168 | "\n", 169 | "\n", 170 | "\n", 171 | "3,2\n", 172 | "\n", 173 | "efgg\n", 174 | "\n", 175 | "\n", 176 | "\n", 177 | "3,2--2,3\n", 178 | "\n", 179 | "\n", 180 | "\n", 181 | "\n", 182 | "3,2--2,4\n", 183 | "\n", 184 | "\n", 185 | "\n", 186 | "\n", 187 | "4,1\n", 188 | "\n", 189 | "abcdefgg\n", 190 | "\n", 191 | "\n", 192 | "\n", 193 | "4,1--3,1\n", 194 | "\n", 195 | "\n", 196 | "\n", 197 | "\n", 198 | "4,1--3,2\n", 199 | "\n", 200 | "\n", 201 | "\n", 202 | "\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", 233 | "\n", 234 | "%3\n", 235 | "\n", 236 | "\n", 237 | "\n", 238 | "1,1\n", 239 | "\n", 240 | "a\n", 241 | "\n", 242 | "\n", 243 | "\n", 244 | "1,2\n", 245 | "\n", 246 | "b\n", 247 | "\n", 248 | "\n", 249 | "\n", 250 | "1,3\n", 251 | "\n", 252 | "c\n", 253 | "\n", 254 | "\n", 255 | "\n", 256 | "1,4\n", 257 | "\n", 258 | "d\n", 259 | "\n", 260 | "\n", 261 | "\n", 262 | "1,5\n", 263 | "\n", 264 | "e\n", 265 | "\n", 266 | "\n", 267 | "\n", 268 | "1,6\n", 269 | "\n", 270 | "f\n", 271 | "\n", 272 | "\n", 273 | "\n", 274 | "1,7\n", 275 | "\n", 276 | "g\n", 277 | "\n", 278 | "\n", 279 | "\n", 280 | "1,8\n", 281 | "\n", 282 | "g\n", 283 | "\n", 284 | "\n", 285 | "\n", 286 | "2,1\n", 287 | "\n", 288 | "ab\n", 289 | "\n", 290 | "\n", 291 | "\n", 292 | "2,1--1,1\n", 293 | "\n", 294 | "\n", 295 | "\n", 296 | "\n", 297 | "2,1--1,2\n", 298 | "\n", 299 | "\n", 300 | "\n", 301 | "\n", 302 | "2,2\n", 303 | "\n", 304 | "cd\n", 305 | "\n", 306 | "\n", 307 | "\n", 308 | "2,2--1,3\n", 309 | "\n", 310 | "\n", 311 | "\n", 312 | "\n", 313 | "2,2--1,4\n", 314 | "\n", 315 | "\n", 316 | "\n", 317 | "\n", 318 | "2,3\n", 319 | "\n", 320 | "ef\n", 321 | "\n", 322 | "\n", 323 | "\n", 324 | "2,3--1,5\n", 325 | "\n", 326 | "\n", 327 | "\n", 328 | "\n", 329 | "2,3--1,6\n", 330 | "\n", 331 | "\n", 332 | "\n", 333 | "\n", 334 | "2,4\n", 335 | "\n", 336 | "gg\n", 337 | "\n", 338 | "\n", 339 | "\n", 340 | "2,4--1,7\n", 341 | "\n", 342 | "\n", 343 | "\n", 344 | "\n", 345 | "2,4--1,8\n", 346 | "\n", 347 | "\n", 348 | "\n", 349 | "\n", 350 | "3,1\n", 351 | "\n", 352 | "abcd\n", 353 | "\n", 354 | "\n", 355 | "\n", 356 | "3,1--2,1\n", 357 | "\n", 358 | "\n", 359 | "\n", 360 | "\n", 361 | "3,1--2,2\n", 362 | "\n", 363 | "\n", 364 | "\n", 365 | "\n", 366 | "3,2\n", 367 | "\n", 368 | "efgg\n", 369 | "\n", 370 | "\n", 371 | "\n", 372 | "3,2--2,3\n", 373 | "\n", 374 | "\n", 375 | "\n", 376 | "\n", 377 | "3,2--2,4\n", 378 | "\n", 379 | "\n", 380 | "\n", 381 | "\n", 382 | "4,1\n", 383 | "\n", 384 | "abcdefgg\n", 385 | "\n", 386 | "\n", 387 | "\n", 388 | "4,1--3,1\n", 389 | "\n", 390 | "\n", 391 | "\n", 392 | "\n", 393 | "4,1--3,2\n", 394 | "\n", 395 | "\n", 396 | "\n", 397 | "\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 | --------------------------------------------------------------------------------