├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── flit.ini ├── iotapy ├── __init__.py ├── iota.py ├── iri.py ├── network │ ├── __init__.py │ └── transaction_requester.py ├── resources │ ├── Snapshot.sig │ └── Snapshot.txt ├── snapshot.py ├── storage │ ├── __init__.py │ ├── converter.py │ ├── providers │ │ ├── __init__.py │ │ ├── rocksdb.py │ │ ├── types │ │ │ ├── __init__.py │ │ │ ├── address.py │ │ │ ├── approvee.py │ │ │ ├── bundle.py │ │ │ ├── milestone.py │ │ │ ├── state_diff.py │ │ │ ├── tag.py │ │ │ ├── transaction.py │ │ │ └── transaction_metadata.py │ │ └── zmq.py │ └── tangle.py └── validators │ ├── __init__.py │ ├── ledger.py │ └── transaction.py ├── requirements.txt └── test ├── __init__.py ├── test_providers.py ├── test_snapshot.py ├── test_transaction_validator.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Louie Lu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | python-rocksdb-iota = ">=0.7.2" 11 | PyOTA = ">=2.0.1" 12 | 13 | 14 | [dev-packages] 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "629300e27e3b75f3bf7fce52d7390a512ac9a43fc50d94cbc75b0bea988a2f05" 5 | }, 6 | "host-environment-markers": { 7 | "implementation_name": "cpython", 8 | "implementation_version": "3.6.3", 9 | "os_name": "posix", 10 | "platform_machine": "x86_64", 11 | "platform_python_implementation": "CPython", 12 | "platform_release": "4.13.10-1-ARCH", 13 | "platform_system": "Linux", 14 | "platform_version": "#1 SMP PREEMPT Fri Oct 27 16:16:03 CEST 2017", 15 | "python_full_version": "3.6.3", 16 | "python_version": "3.6", 17 | "sys_platform": "linux" 18 | }, 19 | "pipfile-spec": 6, 20 | "requires": {}, 21 | "sources": [ 22 | { 23 | "name": "pypi", 24 | "url": "https://pypi.python.org/simple", 25 | "verify_ssl": true 26 | } 27 | ] 28 | }, 29 | "default": { 30 | "asn1crypto": { 31 | "hashes": [ 32 | "sha256:654b7db3b120e23474e9a1e5e38d268c77e58a9e17d2cb595456c37309846494", 33 | "sha256:0874981329cfebb366d6584c3d16e913f2a0eb026c9463efcc4aaf42a9d94d70" 34 | ], 35 | "version": "==0.23.0" 36 | }, 37 | "certifi": { 38 | "hashes": [ 39 | "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", 40 | "sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5" 41 | ], 42 | "version": "==2017.7.27.1" 43 | }, 44 | "cffi": { 45 | "hashes": [ 46 | "sha256:2c707e97ad7b0417713543be7cb87315c015bb5dd97903480168d60ebe3e313e", 47 | "sha256:6d8c7e20eb90be9e1ccce8e8dd4ee5163b37289fc5708f9eeafc00adc07ba891", 48 | "sha256:627298d788edcb317b6a01347428501e773f5e8f2988407231c07e50e3f6c1cf", 49 | "sha256:bdd28cf8302eeca1b4c70ec727de384d4f6ea640b0e698934fd9b4c3bc88eeb1", 50 | "sha256:248198cb714fe09f5c60b6acba3675d52199c6142641536796cdf89dd45e5590", 51 | "sha256:c962cb68987cbfb70b034f153bfa467c615c0b55305d39b3237c4bdbdbc8b0f4", 52 | "sha256:401ba2f6c1f1672b6c38670e1c00fa5f84f841edd30c32742dab5c7151cd89bf", 53 | "sha256:1c103c0ee8235c47c4892288b2287014f33e7cb24b9d4a665be3aa744377dcb9", 54 | "sha256:d7461ef8671ae40f991384bbc4a6b1b79f4e7175d8052584be44041996f46517", 55 | "sha256:3ac9be5763238da1d6fa467c43e3f86472626837a478588c94165df09e62e120", 56 | "sha256:d54a7c37f954fdbb971873c935a77ddc33690cec9b7ac254d9f948c43c32fa83", 57 | "sha256:4d9bf1b23896bcd4d042e823f50ad36fb6d8e1e645a3dfb2fe2f070851489b92", 58 | "sha256:61cf049b1c649d8eec360a1a1d09a61c37b9b2d542364506e8feb4afd232363d", 59 | "sha256:ce3da410ae2ab8709565cc3b18fbe9a0eb96ea7b2189416098c48d839ecced84", 60 | "sha256:e72d8b5056f967ecb57e166537408bc913f2f97dc568027fb6342fcfa9f81d64", 61 | "sha256:11a8ba88ef6ae89110ef029dae7f1a293365e50bdd0c6ca973beed80cec95ae4", 62 | "sha256:974f69112721ba2e8a6acd0f6b68a5e11432710a3eca4e4e6f4d7aaf99214ed1", 63 | "sha256:062c66dabc3faf8e0db1ca09a6b8e308846e5d35f43bed1a68c492b0d96ac171", 64 | "sha256:03a9b9efc280dbe6be149a7fa689f59a822df009eee633fdaf55a6f38795861f", 65 | "sha256:8b3d6dc9981cedfb1ddcd4600ec0c7f5ac2c6ad2dc482011c7eecb4ae9c819e0", 66 | "sha256:09b7d195d163b515ef7c2b2e26a689c9816c83d5319cceac6c36ffdab97ab048", 67 | "sha256:943b94667749d1cfcd964e215a20b9c891deae913202ee8eacaf2b94164b155f", 68 | "sha256:89829f5cfbcb5ad568a3d61bd23a8e33ad69b488d8f6a385e0097a4c20742a9b", 69 | "sha256:ba78da7c940b041cdbb5aaff5afe11e8a8f25fe19564c12eefea5c5bd86930ca", 70 | "sha256:a79b15b9bb4726672865cf5b0f63dee4835974a2b11b49652d70d49003f5d1f4", 71 | "sha256:f6799913eb510b682de971ddef062bbb4a200f190e55cae81c413bc1fd4733c1", 72 | "sha256:e7f5ad6b12f21b77d3a37d5c67260e464f4e9068eb0c0622f61d0e30390b31b6", 73 | "sha256:5f96c92d5f5713ccb71e76dfa14cf819c59ecb9778e94bcb541e13e6d96d1ce5", 74 | "sha256:5357b465e3d6b98972b7810f9969c913d365e75b09b7ba813f5f0577fe1ac9f4", 75 | "sha256:75e1de9ba7c155d89bcf67d149b1c741df553c8158536e8d27e63167403159af", 76 | "sha256:ab87dd91c0c4073758d07334c1e5f712ce8fe48f007b86f8238773963ee700a6" 77 | ], 78 | "markers": "platform_python_implementation != 'PyPy'", 79 | "version": "==1.11.2" 80 | }, 81 | "chardet": { 82 | "hashes": [ 83 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", 84 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" 85 | ], 86 | "version": "==3.0.4" 87 | }, 88 | "class-registry": { 89 | "hashes": [ 90 | "sha256:12d28642d89bc4f823369b36ad337988e9a423230e8bb3b6e43e50c5f5c0a031", 91 | "sha256:97a116a6e448e183d14d6fe82b5797b824dff9d10fdf3adf16936286f737597a" 92 | ], 93 | "version": "==2.1.0" 94 | }, 95 | "cryptography": { 96 | "hashes": [ 97 | "sha256:7b538467f155e9a758bf6b1e8baea109dd886ee89e6b479509cef8a56bd27b09", 98 | "sha256:67cb5979526fec6cf74083b0218a697e1229436a873d50cfe6a20626820841d6", 99 | "sha256:706b64b39315ecb07f94dbd8cd3532e1d4512158581e075271d462ed10941242", 100 | "sha256:78f4178f169728761e7c1a116e13fc0fb557437294705822aa5efe000fbd37e5", 101 | "sha256:72901278962b60ba2007bddb069ceab6265a43a88d5ce3986d9f60c35ed6bcc1", 102 | "sha256:e8030c922627affa6be03cd6b892bcda6ac425c18abe14f854c54bacf7b38076", 103 | "sha256:98b2a8e98e8b62601c142441833d7b2af7390007ab0274d475d353246ed29d0d", 104 | "sha256:bd872a01bd96b44369fac7f141c87851a17312cc20055568dabac89008de8ad6", 105 | "sha256:b55dff1981f88aa10138a6a416862b82d10d70e171a8c3c05ee97d29d039ee35", 106 | "sha256:ef63b62121cbf1564328954b80e8ff1cc5034493b946da23c68d7ef26f60a679", 107 | "sha256:6dd3b90cc46fbc720905449d152adca1f64e828c5df362403c04a387036d025e", 108 | "sha256:fa3827a7541d86956910d8352a9e1ac615d30f0b1693c894195e87c95b79f9c6", 109 | "sha256:525b857e9e3e56fc37168d06c3710fc48b41f2487c6007109ba839a55c49768f", 110 | "sha256:542f1b7dae143e17cd5618b08ddaf7d62f9885895602f02674d9269f3c0737d1", 111 | "sha256:04adc77dc49b3716a5336dad307bfb6773e541883ef1a20cd6902e2f1c1cb46d", 112 | "sha256:f5a2ceb2b60b291f1638780c1ee6c765d3dd89845f940cec39e86c18bfd5ad1c", 113 | "sha256:81eb1292db18d764bc0168720900388622ab4823f9b8daf6fb784a0675c1e195", 114 | "sha256:9261de9e2297d5faae82360795f1b4ee69f5544a49b0dbda522368f01882538b", 115 | "sha256:02ccbb6564d89595a846a0710cae4b3598585fe9cbf35215c12b6ccbf8bd19ae", 116 | "sha256:d42e62423364b35b4532c5683f2c28e6082fa5bd9ca38c2145697e83c3b4a8b1", 117 | "sha256:2e2003cc0051244ebebc21d59e517adcc435122d7b89600d69072761ba02e6c4", 118 | "sha256:b2eeb56a9773f2c59b5531551f071a4f155bd3109133de1d1b5fb13a6b574421", 119 | "sha256:d7f348e4f5df146a0e75998544bab6d42313cf19a81a6e49990ab7b27cc9c73b" 120 | ], 121 | "version": "==2.1.2" 122 | }, 123 | "filters": { 124 | "hashes": [ 125 | "sha256:248150dfd768f9211f4c897696f3f4c370697e0f19b9c1f85577a27495b18496", 126 | "sha256:b74fad6a7885f1380dd12fde0f849e8c5e459919ce314f76c2352064c7a30796" 127 | ], 128 | "version": "==1.3.2" 129 | }, 130 | "idna": { 131 | "hashes": [ 132 | "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", 133 | "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" 134 | ], 135 | "version": "==2.6" 136 | }, 137 | "pycparser": { 138 | "hashes": [ 139 | "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" 140 | ], 141 | "version": "==2.18" 142 | }, 143 | "pyopenssl": { 144 | "hashes": [ 145 | "sha256:aade9985b93eaec51b0c0a2a60d14bb8dcff1ff8e36fe542e3c22812ec07315e", 146 | "sha256:29630b9064a82e04d8242ea01d7c93d70ec320f5e3ed48e95fcabc6b1d0f6c76" 147 | ], 148 | "version": "==17.3.0" 149 | }, 150 | "pyota": { 151 | "hashes": [ 152 | "sha256:b50c18e235cc8260ca22d4d36c9cd2f2f575e9fecab40e38af8bd75a807e3d3e", 153 | "sha256:d9fb56ed9cc2c63543fd391ebd4eaaf6e2b3234050befbbb200571f949b28b7c" 154 | ], 155 | "version": "==2.0.1" 156 | }, 157 | "pysha3": { 158 | "hashes": [ 159 | "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9", 160 | "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf", 161 | "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5", 162 | "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f", 163 | "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b", 164 | "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d", 165 | "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603", 166 | "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24", 167 | "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48", 168 | "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f", 169 | "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608", 170 | "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07", 171 | "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d", 172 | "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9", 173 | "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8", 174 | "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77", 175 | "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4", 176 | "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030", 177 | "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef", 178 | "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0", 179 | "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e" 180 | ], 181 | "version": "==1.0.2" 182 | }, 183 | "python-dateutil": { 184 | "hashes": [ 185 | "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c", 186 | "sha256:891c38b2a02f5bb1be3e4793866c8df49c7d19baabf9c1bad62547e0b4866aca" 187 | ], 188 | "version": "==2.6.1" 189 | }, 190 | "python-rocksdb-iota": { 191 | "hashes": [ 192 | "sha256:d3834a4dd0cf0bd6e06beb8be2cd2a1c83e8764266da6f1d3232ff3b75e077de" 193 | ], 194 | "version": "==0.7.2" 195 | }, 196 | "pytz": { 197 | "hashes": [ 198 | "sha256:80af0f3008046b9975242012a985f04c5df1f01eed4ec1633d56cc47a75a6a48", 199 | "sha256:feb2365914948b8620347784b6b6da356f31c9d03560259070b2f30cff3d469d", 200 | "sha256:59707844a9825589878236ff2f4e0dc9958511b7ffaae94dc615da07d4a68d33", 201 | "sha256:d0ef5ef55ed3d37854320d4926b04a4cb42a2e88f71da9ddfdacfde8e364f027", 202 | "sha256:c41c62827ce9cafacd6f2f7018e4f83a6f1986e87bfd000b8cfbd4ab5da95f1a", 203 | "sha256:8cc90340159b5d7ced6f2ba77694d946fc975b09f1a51d93f3ce3bb399396f94", 204 | "sha256:dd2e4ca6ce3785c8dd342d1853dd9052b19290d5bf66060846e5dc6b8d6667f7", 205 | "sha256:699d18a2a56f19ee5698ab1123bbcc1d269d061996aeb1eda6d89248d3542b82", 206 | "sha256:fae4cffc040921b8a2d60c6cf0b5d662c1190fe54d718271db4eb17d44a185b7" 207 | ], 208 | "version": "==2017.3" 209 | }, 210 | "regex": { 211 | "hashes": [ 212 | "sha256:2f66aae1f85316ea11121e114005737138262213e39bee55cbba270344b7d6b6", 213 | "sha256:633a74730f774b995dad327cc6ff4c88c6f3a723e41c74cd85e2e062c67c3c10", 214 | "sha256:c8959faa9f4faef9b143ef65c12f767ece412c25336727d65ffde579ffc0e75f", 215 | "sha256:644a9e82a39cb3a8694fe907a589d3438d90abb937378e54db032c26061a76cb", 216 | "sha256:4495d2c707c31033dc7add6374b838d33ef189e7106de764b9d0c05e80cccd29", 217 | "sha256:38f255d20be5f1be6f436ac551600ae64e457fc8c4b0702d9e0361c2e81ba6cb", 218 | "sha256:43a6e5b91d3d2f9eb58710514b67209173983c44b506b83bb517f3a81365c798", 219 | "sha256:2063d31c28e0615bdd9b826b774fde9bf9ee653a61c868229eab6719cca1d063", 220 | "sha256:b95ae9b1c893e4187a57c5055bf3dc441a16097183a8bea5fcd052ebe6382293", 221 | "sha256:0570f49c0adbc8b3c7884af6ead0d702cb1fde15e2c1f5d577c8fbb7d9971e30", 222 | "sha256:7e2fb5b65a4b30571acf8e9f42fb83eecaa53a5ccbfab4d361bbdcb4de6ef30c", 223 | "sha256:9b7a3be54e5bef338eb6e1d8f90618a8cbc9955399aaf0354d2c5e1cfb4de8e9", 224 | "sha256:10eee551447686d77f8c8e0346ff9d893c9af594bb7ec3a2dcd904ef35f4e998", 225 | "sha256:e70d7334d22581a9d19979bbd998314affacb678da0617b1ca936203005d00cd", 226 | "sha256:5632e3806e5962cb257d59c61290b31e5efe14d8eb356e85d70cc601f255e95a", 227 | "sha256:0b8a7a609f8db94f047fed41d6cc4423620e4f2481f54973309d7d01c64766ef", 228 | "sha256:d273e439348ce266ff26d9552660323ac0a8609d6c8b29f68ab85143e81391a8", 229 | "sha256:98bf6f091b4f6b09cd302003c1ec06d9662122b456586e51bb8812eeceb5abd9", 230 | "sha256:80166c9e21c0171c7b502035f3ba25f43b5122def387ca6ba9706b6892fed7aa" 231 | ], 232 | "version": "==2017.9.23" 233 | }, 234 | "requests": { 235 | "hashes": [ 236 | "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", 237 | "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" 238 | ], 239 | "version": "==2.18.4" 240 | }, 241 | "six": { 242 | "hashes": [ 243 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", 244 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" 245 | ], 246 | "version": "==1.11.0" 247 | }, 248 | "urllib3": { 249 | "hashes": [ 250 | "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", 251 | "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" 252 | ], 253 | "version": "==1.22" 254 | } 255 | }, 256 | "develop": {} 257 | } 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IOTA-Python - A Pure-Python implementation of IOTA node 2 | ======================================================= 3 | 4 | The target of this project is to create a pure Python implementation of IOTA node. 5 | 6 | 7 | ## Current Develop Status 8 | 9 | * rocksdb: read, write, store (batch) 10 | * tangle: get, first, latest, next, save, store 11 | * ISS, snapshot: validate, diff, patch 12 | * tx_requester: function, but not thread 13 | * tx_validator: validate, check_solidity 14 | 15 | Roadmap: 16 | 17 | * Documentation of IRI behavior and description 18 | * Send TX (tips selection, attach to tangle, broadcast and store) 19 | * Network node (TCP and UDP) 20 | 21 | 22 | ## Getting Started 23 | 24 | ### Dependencies 25 | 26 | * rocksdb 27 | 28 | You can see the rocksdb install guide from 29 | [facebook/rocksdb - INSTALL.md](https://github.com/facebook/rocksdb/blob/master/INSTALL.md). 30 | 31 | For Arch Linux user, simply typing: 32 | 33 | ``` 34 | $ yaourt -S rocksdb 35 | ``` 36 | 37 | ### Requirements 38 | 39 | * [python-rocksdb-iota](https://github.com/mlouielu/python-rocksdb) 40 | * [pyota](https://github.com/iotaledger/iota.lib.py) 41 | 42 | ### Installation of IOTA-Python 43 | 44 | #### $ pipenv install iotapy 45 | 46 | To install IOTA-Python, simply run this simple command in your terminal of choice: 47 | 48 | ``` 49 | $ pipenv install iotapy 50 | ``` 51 | 52 | #### Get the Source Code 53 | 54 | IOTA-Python is actively developed on GitHub, where the code is 55 | [always available](https://github.com/mlouielu/iota-python). 56 | 57 | You can clone the source code from repository: 58 | 59 | ``` 60 | $ git clone https://github.com/mlouielu/iota-python 61 | ``` 62 | 63 | Once you have a copy of source code, you can install the package easily: 64 | 65 | ``` 66 | $ cd iota-python 67 | $ pip install -r requirements.txt 68 | $ python setup.py install 69 | ``` 70 | 71 | ## Tutorials - About RocksDBProvider 72 | 73 | This is a tutorial of IOTA-Python to read the database from java IRI, and this 74 | is what IOTA-Python can do now. 75 | 76 | ### Open IRI rocksdb 77 | 78 | Remember, make sure `db_path` and `db_log_path` is point to *your* database path. 79 | At this point, IOTA-Python didn't support writing data back to the database (also, 80 | it have a lock on it, if you want to write it), so `read_only` should also be `True`. 81 | 82 | ```python 83 | >>> import iota 84 | >>> import iotapy 85 | >>> r = iotapy.storage.providers.rocksdb.RocksDBProvider( 86 | db_path='/var/db/iota/mainnetdb', 87 | db_log_path='/var/db/iota/mainnetdb.log', 88 | read_only=True 89 | ) 90 | >>> r.init() # Absolute remember to init database 91 | >>> 92 | ``` 93 | 94 | ### Access IRI rocksdb 95 | 96 | Now you open the database, you can get the data inside it! IRI using rocksdb 97 | column family to separate the data stored. For column family list, please visit 98 | this [blog post](https://blog.louie.lu/2017/10/31/iota-iri-rocksdb-data-storage-structure/) 99 | 100 | Now, we can try to open a transaction, here we got an example transaction hash: 101 | `GTXDTJVUTVSNHYFPJUOWFKTGQTCMNKZPJDJXSWVQWTXYRDZAVZTX9KFBRIMRQEQLMCMVAUKMZWMHA9999`. 102 | 103 | You can check this tx information at this 104 | [page](https://thetangle.org/transaction/GTXDTJVUTVSNHYFPJUOWFKTGQTCMNKZPJDJXSWVQWTXYRDZAVZTX9KFBRIMRQEQLMCMVAUKMZWMHA9999) 105 | 106 | ```python 107 | >>> txh = iota.TransactionHash('GTXDTJVUTVSNHYFPJUOWFKTGQTCMNKZPJDJXSWVQWTXYRDZAVZTX9KFBRIMRQEQLMCMVAUKMZWMHA9999') 108 | >>> column_family = 'transaction' 109 | >>> tx = r.get(txh, column_family) 110 | >>> tx.tag 111 | Tag(b'EXAMPLEPYTHONLIB99999999999') 112 | >>> tx.bundle_hash 113 | BundleHash(b'CRNTWYOGTYKPAHYHNESJOKLRFYQQGCXXUZIZQFTCCLSTZODTRBPZWTX9TVHNDNNIWTULV9GFLAPPSTCC9') 114 | >>> tx.signature_message_fragment 115 | Fragment(b'RBTC9D9DCDFAEACCWCXCGDEAXCGDEAPCEAHDTCGDHDEAUCFDCDADEAZBMDHDWCCDBD999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999') 116 | >>> tx.signature_message_fragment.as_string() 117 | 'Hello! This is a test from Python' 118 | >>> tx.as_json_compatible() 119 | { 120 | 'hash_': ..., 121 | 'signature_message_fragment': ..., 122 | 'address': Address(b'9TPHVCFLAZTZSDUWFBLCJOZICJKKPVDMAASWJZNFFBKRDDTEOUJHR9JVGTJNI9IYNVISZVXARWJFKUZWC'), 123 | 'value': 0, 124 | 'legacy_tag': ..., 125 | 'timestamp': 1508993435, 126 | 'current_index': 0, 127 | 'last_index': 0, 128 | 'bundle_hash': ..., 129 | 'trunk_transaction_hash': ..., 130 | 'branch_transaction_hash': ..., 131 | 'tag': Tag(b'EXAMPLEPYTHONLIB99999999999'), 132 | 'attachment_timestamp': 1508993445508, 133 | 'attachment_timestamp_lower_bound': 0, 134 | 'attachment_timestamp_upper_bound': 12, 135 | 'nonce': Nonce(b'HYNAKUFLKW9UZXXIDJFGUMUDDVX') 136 | } 137 | >>> 138 | ``` 139 | 140 | If you are using method such as `RocksDBProvider.get`, `RocksDBProvider.latest`, 141 | please use the following column family name: 142 | 143 | ``` 144 | address 145 | approvee 146 | bundle 147 | milestone 148 | state_diff 149 | tag 150 | transaction 151 | transaction_metadata 152 | ``` 153 | 154 | 155 | The full list of column family name can be found at `RocksDBProvider.column_family_names`, 156 | but this list is used for low level database access, it only used at `RocksDBProvider.db`. 157 | 158 | ``` 159 | b'default' 160 | b'transaction' 161 | b'transaction-metadata' 162 | b'milestone' 163 | b'stateDiff' 164 | b'address' 165 | b'approvee' 166 | b'bundle' 167 | b'tag' 168 | ``` 169 | 170 | Example of `RocksDBProvider` methods: 171 | 172 | ```python 173 | # Get address' transaction record 174 | >>> adr = iota.Address('9TPHVCFLAZTZSDUWFBLCJOZICJKKPVDMAASWJZNFFBKRDDTEOUJHR9JVGTJNI9IYNVISZVXARWJFKUZWC') 175 | >>> r.get(adr,'address') 176 | 177 | 178 | # Get next address (in database) 179 | >>> r.next(adr, 'address') 180 | (Address(b'9BPQJPGAMDVKAQ9FDPQSVINPMHSIUUXKYMIZQGPBDGGEUZGLEVFIWUYO9MEIPOYUBYVQGJCFYRWTQENCZ'), ) 181 | 182 | # Get first milestone 183 | >>> r.first('milestone') 184 | (243001, (243001, TransactionHash(b'9PPVIKDMKUDXTYJFF9YNWUPPMOYZTYKRBFGLGDCNNNIMWAMGVJGEHOCOUDYRVYPPSDKDKDQXUBMYA9999'))) 185 | 186 | # Get latest milestone 187 | >>> r.latest('milestone') 188 | (265486, (265486, TransactionHash(b'TFWZVEQZGQGBUBKMFA9YKBVDGBWWMXWCGGYYAGPZKGXWKJQRUNSMXJBSVVGYRJKCS9GNWULQSMAGZ9999'))) 189 | 190 | # Get next milestone 191 | >>> key, value = r.first('milestone') 192 | >>> r.next(key, 'milestone') 193 | (243002, (243002, TransactionHash(b'XHIOO9EJ9H9ULPJM9MIJSTPHNPIUAAJ9NLYHZLHDBCSECCJVRGDWHTRUEUIQXWVLBYOCBNHFWWWPA9999'))) 194 | ``` 195 | 196 | Low level db access (something you don't need to touch at this moment): 197 | 198 | ```python 199 | >>> column_handler = r.db.column_family_handles[b'transaction-metadata'] 200 | >>> txh = iota.TransactionHash('GTXDTJVUTVSNHYFPJUOWFKTGQTCMNKZPJDJXSWVQWTXYRDZAVZTX9KFBRIMRQEQLMCMVAUKMZWMHA9999') 201 | >>> key = iotapy.storage.converter.from_trits_to_binary(txh.as_trits()) 202 | >>> value = r.db.get(key, column_handler) 203 | b"6\x8d\xd6\xb2v\xf7\xde9\xcb\xab\x07\x1f\xb9\xfc\x1e@s\xcfpU\xb8\x17\xad23Wf\xd52\xb5\xe0\xc4\xb7\xd5\xb6\xcb\xb7\xc1\xdb$\xad%\xd4\xa2\xf3\xefI'\xd5\xa7Y\xe2\xa5]G\x9e\xb82\xd7\x1f&\x9cR4\xa7\x13\xff\x00\x00\x00!\x0c;\xa2\x1b\xc6-\xa0\xd5\xbe\xaa*\xb1\x95\xbcUI\xb5l\x89\xa1\xe3\xacn\x90@\x10\xdf\xf9Un\xb7\xb1\x93\x9b\xc7\x017:o\xba\xd8\xfdq\xff\xe2\x00\x00\x00\xb4T\xa1\xa01\xc0\xb7\xd8U\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\xf1i\x9b\xb4T\xa1\xa01\xc0\xb7\xd8U\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01_W\x04\xae\x84\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x01\xff\xff\xff\xff\x00\x00\x00\x00Y\xf1i\xa5\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x03\xd6\xe0local" 204 | >>> 205 | ``` 206 | 207 | ### EXPLOSION! An example of exploring the IRI database! 208 | 209 | ![](https://pm1.narvii.com/6258/7f113d43b699f954c3d65df5aa5f397936363099_hq.jpg) 210 | 211 | *EXPLOSION!* 212 | 213 | Glade you are here, now you gain the full access power of the IRI database. 214 | 215 | We all know that IOTA is a DAG, that is, theoretically speaking, we can start 216 | from a transaction, and go to its parent, and its parent, and its parent...and 217 | we will finally get to the genesis address / node / block whatever. 218 | 219 | Here is a small script that I trying to find this answer: 220 | 221 | ```python 222 | import time 223 | import iota 224 | import iotapy 225 | 226 | 227 | # Initialize the database 228 | r = iotapy.storage.providers.rocksdb.RocksDBProvider( 229 | db_path='/var/db/iota/mainnetdb', 230 | db_log_path='/var/db/iota/mainnetdb.log', 231 | read_only=True) 232 | r.init() 233 | 234 | 235 | # We will start from this transaction 236 | txh = iota.TransactionHash('UNUK99RCIWLUQ9WMUT9MPQSZCHTUMGN9IWOCOXWMNPICCCQKLLNIIE9UIFGKZLHRI9QAOEQXQJLL99999') 237 | tx = r.get(txh, 'transaction') 238 | 239 | # Stop at here 240 | EMPTY = iota.TransactionHash('9' * 81) 241 | 242 | # Go to find the genesis! 243 | i = 0 244 | while True: 245 | i += 1 246 | if i % 100 == 0: 247 | print(txh, time.ctime(tx.timestamp)) 248 | 249 | # Branch is outside the bundle and trunk is in the bundle 250 | # So we choose branch at here 251 | if tx.branch_transaction_hash != EMPTY: 252 | txh = tx.branch_transaction_hash 253 | tx = r.get(tx.branch_transaction_hash, 'transaction') 254 | else: 255 | # This is the end of the journey 256 | print("\nProbably the genesis?") 257 | print(txh) 258 | break 259 | 260 | ``` 261 | 262 | It will run like: 263 | 264 | ``` 265 | NQRZKAE9CDFGJUPOMTHCVPOQE9LENTSHNJYDXCWZJ9IRXKLWTGRIHYZCZWSEIOKQFVBBEBNTXHNBA9999 Thu Oct 26 11:23:31 2017 266 | KMRPH9BVZVFRCEGDEROQBMZTNOQECYRKIJJ9MWGNOUMWVPLFIJ9PRANLDXSTZCVJGHTXTYCWYGJFZ9999 Thu Oct 26 08:40:52 2017 267 | JQLXELCKS9QSNWZNHJJICAQMGZRTGFPGNRRNJWTBEPZWKEHLJINXVLPOMMQCMQSOEUZGRHSKBNWEA9999 Thu Oct 26 06:40:31 2017 268 | IOKLUAGDGKSU9RGHZXCQJIBRTFPCNVCIVG9TQNIGE9DVIYIUDCEVTMPYBZQXHNYLNSNFTXDWNH9BA9999 Thu Oct 26 05:41:28 2017 269 | AUEDLXAJZPW9URCQZABSRNJGYOOSDG9OFHUMXINMVULHOIVWOBDLSSS9DPNTSXHTDKG9MGKMUESD99999 Thu Oct 26 04:38:37 2017 270 | DZRYEISPEUXXVQMGBSPVMOFFTWPDYQ9EWVYZUQTNX9NPSEWRGNMSZMY9BOTABSLPCTKMIGWAAIPJA9999 Thu Oct 26 03:45:03 2017 271 | UPAIBMKZEOTUEHHLEYZEEKZFLDGWHWJ9UZLGLRV9NGZPWSGWY9DPITWSDZPQQZDBOYFJDLJYTDKXZ9999 Thu Oct 26 02:51:53 2017 272 | MZPRET9XVFBK9LQZLFNN9UGQLLBYAVGAKJGODQIKVZWHPDLZQLOTFMMGEAGSGWGPEYBLJLLJJXOEZ9999 Thu Oct 26 02:08:16 2017 273 | XPMVSHVFNKYSTKPSHLPHDQCKEGWTERKEHTDRUKIQRE9KDDQVAQHUOQHYOPTRZGIIQHLYGYCPTLAEA9999 Thu Oct 26 01:17:58 2017 274 | MHDQIXDMBOFHCOMLGNHPSJGNIXIDAQQNW9CJKSXPYIOIITQES99KNYTPNPOQBTVZQXYOKBVEIZB999999 Thu Oct 26 00:37:14 2017 275 | ... 276 | GXWKBVNFPFX99PGDCH9EQZMLHITMBFAPDQRNNGNGOOXYXEEYYDDDFWJRDQINOEJFSITMEUY9VMVO99999 Tue Oct 24 06:37:13 2017 277 | UAPTGGCKCGCPWGVQEKBCPKNQUWOPVRBAFAQYHSDU9AJMVINJFVZSM9URBYUAIHCPKOJRBWSOOSHLZ9999 Tue Oct 24 06:16:17 2017 278 | FPPJWECLHTF9HGGEMNH9FUIAVYJWDOIOXYFRRJ9S9HENTSESTGJYLYOSNNJGKDOUOGXOWFURNDNEZ9999 Tue Oct 24 05:56:10 2017 279 | HSDV9SKUYCBWNXFIHKLNAXEVPDTGLSPMTMTXCYISS9M9SPL9DERO9GCQLNWY9KBIHOLPYBCTCYECA9999 Tue Oct 24 05:35:44 2017 280 | LQPVJDHGQPILXVUPCCZGJMCAQHROJLHQKBILEUDBKQAEHMHEOEK9GMRDWEKVRXRB99TOYQQLTZDGZ9999 Tue Oct 24 05:14:13 2017 281 | FOCMONRMEOBQEBDOJOQPOOPLRIYCI9PMSJXXNEWAZPBRCDGZTZB9KI9SSMKKFBGBDONMILXWOBEFA9999 Tue Oct 24 04:53:03 2017 282 | BEFSRMKVEWXXHERTONXCQLOAVQYVRPFFZCQIDGUOLLZCFLTRPRHEVYNAPQFKGJOCDAJYNVUJDQIRZ9999 Tue Oct 24 04:32:48 2017 283 | QVHPAOWGDFTTLYQERDRNBBNCJJCWTJSDBIKBLDYZOLJ9PWFWFWUXP9DGPKTLRZDFZNLFRYXSZF9O99999 Tue Oct 24 04:12:07 2017 284 | 285 | Probably the genesis? 286 | 9PPVIKDMKUDXTYJFF9YNWUPPMOYZTYKRBFGLGDCNNNIMWAMGVJGEHOCOUDYRVYPPSDKDKDQXUBMYA9999 287 | ``` 288 | 289 | Check this transaction on [thetangle](https://thetangle.org/transaction/9PPVIKDMKUDXTYJFF9YNWUPPMOYZTYKRBFGLGDCNNNIMWAMGVJGEHOCOUDYRVYPPSDKDKDQXUBMYA9999) 290 | 291 | ## Documentation 292 | 293 | ~~Source code is your best friend~~ 294 | 295 | Documentation is still under construct. 296 | -------------------------------------------------------------------------------- /flit.ini: -------------------------------------------------------------------------------- 1 | [metadata] 2 | module = iotapy 3 | author = Louie Lu 4 | author-email = git@louie.lu 5 | maintainer = Louie Lu 6 | maintainer-email = git@louie.lu 7 | home-page = https://github.com/mlouielu/iota-python 8 | requires-python = >= 3 9 | requires = pyota (>= 2.0.1) 10 | python-rocksdb-iota (>= 0.7.2) 11 | description-file = README.md 12 | classifiers = License :: OSI Approved :: MIT License 13 | Programming Language :: Python :: 3 14 | Topic :: Software Development :: Libraries :: Python Modules 15 | Intended Audience :: Developers 16 | -------------------------------------------------------------------------------- /iotapy/__init__.py: -------------------------------------------------------------------------------- 1 | """A Pure-Python implementation of IOTA node""" 2 | 3 | import iota 4 | import iotapy.network 5 | import iotapy.snapshot 6 | import iotapy.storage 7 | import iotapy.validators 8 | 9 | __version__ = '0.1.2' 10 | 11 | 12 | # Monkey patch iota.Transaction to handle metadata 13 | iota.Transaction = iotapy.storage.providers.types.transaction.Transaction 14 | -------------------------------------------------------------------------------- /iotapy/iota.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Iota: 5 | def __init__(self, **kwargs): 6 | self.configuration = kwargs 7 | self.testnet = kwargs.get('testnet') 8 | self.max_peers = kwargs.get('max_peers') 9 | self.udp_port = kwargs.get('udp_port') 10 | self.tcp_port = kwargs.get('tcp_port') 11 | -------------------------------------------------------------------------------- /iotapy/iri.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class IRI: 5 | MAINNET_COORDINATOR = '' 6 | -------------------------------------------------------------------------------- /iotapy/network/__init__.py: -------------------------------------------------------------------------------- 1 | from iotapy.network.transaction_requester import TransactionRequester 2 | -------------------------------------------------------------------------------- /iotapy/network/transaction_requester.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | import iotapy 5 | from typing import List 6 | 7 | 8 | EMPTY_HASH = iota.Hash('9' * 81) 9 | 10 | 11 | class TransactionRequester: 12 | def __init__(self, tangle): 13 | self.tangle = tangle 14 | self.transactions_to_request = set() 15 | self.milestone_transactions_to_request = set() 16 | 17 | def get_requested_transactions(self): 18 | return self.transactions_to_request + self.milestone_transactions_to_request 19 | 20 | def number_of_transactions_to_request(self): 21 | return len(self.transactions_to_request) + len(self.milestone_transactions_to_request) 22 | 23 | def clear_transaction_request(self, txh: iota.TransactionHash): 24 | rt = txh in self.transaction_to_request or txh in self.milestone_transactions_to_request 25 | self.transaction_to_request.discard(txh) 26 | self.milestone_transactions_to_request.discard(txh) 27 | 28 | return rt 29 | 30 | def request_transactions(self, txhs: List[iota.TransactionHash], milestone: bool): 31 | for txh in txhs: 32 | self.request_transaction(txh, milestone) 33 | 34 | def request_transaction(self, txh: iota.TransactionHash, milestone: bool): 35 | if txh != EMPTY_HASH and not self.tangle.get(txh, 'transaction'): 36 | if milestone: 37 | self.transactions_to_request.discard(txh) 38 | self.milestone_transactions_to_request.add(txh) 39 | else: 40 | 41 | if txh not in self.milestone_transactions_to_request: 42 | self.transactions_to_request.add(txh) 43 | 44 | def transaction_to_request(self, milestone: bool): 45 | txh = None 46 | if milestone: 47 | reqs = self.milestone_transactions_to_request 48 | if not reqs: 49 | reqs = self.transactions_to_request 50 | else: 51 | reqs = self.transactions_to_request 52 | if not reqs: 53 | reqs = self.milestone_transactions_to_request 54 | 55 | # XXX: Synchonized in Java, but Python? 56 | # We will hang at this point 57 | while reqs: 58 | txh = reqs.pop() 59 | if not self.tangle.get(txh, 'transaction'): 60 | reqs.add(txh) 61 | else: 62 | break 63 | 64 | return txh 65 | -------------------------------------------------------------------------------- /iotapy/resources/Snapshot.sig: -------------------------------------------------------------------------------- 1 | PKMXSGLWRNKAY9NRXDAJDOHTPGFE9QVJPY9CBEAUH9J9ZCVHUXAHBJFZJGVKFUECLAKFYX9N9VHPZBHSV9PASABPD9EAMHVUI9HNYHAJPATCAVIEFDJRTPOFSUVQ9TFPYORRSCWQKFALY99YLDREPPYGGN9ZJCWQDTKBWEDIPFZFSCOTCOJOGHVKARWRG9HRLMKJFILTF9TVNWWETFUPWCPHZYSSENFNZGAQBZHLIJEAEFMTLIDTMVJDRONJCNFMNUZBNQTXVTOK9MSHMIPCLDHJBSFRAZKPNXSNKRWTFLQPWYILBI9ZG9KLGBGMXCJZUXKXQ9BBMEKLWOBRITA9PZQN9UR9ETVXFAVGTQIUMBMSPYVGYKZCSQVBKU9UPZEPQ9EE9D9HURDOACYUOANQUIKUAT9VC9UHJJWOKROMMDSABMAABRGNVACCELDVFDGEHXMWAYZVZVPLOOUDBNPHJAMWGMZN9ODFGHIZTMRCKDAANDOUGPJUKTMMDFNWWHJWHBFDPRFNHDP9GJZPAAEJXEQMZ9VKYPYITWRPIYHSQRPQYZNDACKFEONNFUFZOJMS9PGVYCVAMBPCVKLSBNS9D9MZQWCCQOYKCJZOHHTPKESIXPTCBUAALFZGEOZGDIOOEMCGHDXPT9BXLJWR9CI9CY9MPHTVYMYJDEXBELWLIAKXZXXWCMJMKHZCJNTMDBJQBQZYQHSETEGBUFVZIUTMYIFLDKCNVBKYTDQ9XLOUSBBSHEJMTYMBCYUZMYIXBFHZMADICJBVLPSEJAARLL9YQJFGHWMYVN9AAYRHABFAROSLMHPLRGBYXGJOTAX9VCNNE9XLXSZSAKXBUXVGCIO9MANNWPMLAUJDRGVI9XSXYCWWVGSNRZKIONNQVDCIZBYWPH9BCXRZSMCIRQGGLXZ9KDZXSCYIQPNXCYTQSZXXRXZORJQDJEVPRVADQSIPYGEWMYBMLSV9CXNQKSZWWLHVPXFUJCUNFETASHLJMVFAYSTVMASASGRPYVNALASCCWZNQWKNAKWSIHNRSSMBYEGEOERVZYJYRTMSACHYYODOHTXTKACHPZPXZXAYFV9EBHIIAAOFXYRPZK9WMQ9KFJJTPHWYVTLTRKMMPBQVUNMVOBJKJ9RMTSOHQPOPMMZFEEGTWGGIYPAPUSNNO9LMNKBZUXC9UEFR9A9GQTRWDJCMLMVYSK99BUIYNGAZCASNNLB9SRGJMEREJQEUVYQBAEBBVJHWXBGOPTQJOWII9AJQ9KFPCGMAOKAWDTXQGATEZGSACRYGL9HZZIAQKIUKXXXYKZMVXTAXKTXQDN9DNHYX9VDD9QIWBLZFSQRITBMJUGFZZDWIIEGEHKWTDGNRAXYNKKFATVTWFSTSPLIXWZWUUHPPKAFZRETRHYDHQTPJMZAVDZSJKRWVOWIIMMLUVMWMHBKRLHXOCDZRYVBDWZO9X9BWYLKAISWNSYFAKKXLUSVWU9PYGNBRLFQWVPLFCYNOQMOZWLSYFUNOTZWHCSTFSW9OHZJ9PNOMFRQTGRHTNGCWFNCQSGMMOLSPJOHIWNONYORDDKYZUGPIOQGLHVRYTVEQXNPKGKKTAXPJSKJJGFRXOYWKTQ99BRN9ANUJKSYYWNGWIOTDFRJVYUXJRPVT9XLNGNSBPJLNWMQZLDCDOQUUEAUOCL9KBYJAYGNZOJK9EJBEM9OWNKKEA9SODTNRUQWVUBPCMTBWEXGLSVYNBUVV9CDAUPLVCYREADORP9UNETIZHGKCWBDNWGM9LPNQXGDCEMILPRSYZ9ZKKGTXXSJCSAVMVFQKTVARGKK9AGUJSVAQUWEGXJLCSLKFWUYNZHURFHEVRXRZHCIDLFFWQAPBUJHYPZKOFGBFXRPQMLUQTCGSJDWZPTNYSFYFUCX9OJIVLMJXXCHJGBJLIYVAJXUYPGGESTTZCXRXQTSLX9UTRQLCEESNHGTVEZTPQJIWTTNCXXTLLXWZPQRNOOJJKNBIXYAGBEIDPSUMOCV9VIHTZZDNDYNJHRXUCKEKXCL9IRQ9BOKZAOLLCYVQB9RRUNVKIWWNSGPKDNDXRSJLWVHMNLLAHVGCZJYOZALRFMPLTKKSLDVUYYDCXGKXYDFGDSCJRKJPBSEW9LY9XURXZXARYBFOYKPMYUWQXUKNJUFGLHIVBNGORDSRRHORA9DZQKFAHEPH9P9CQCW 2 | NJKMGXYPNKZQZETJFGBTHPRFSZGLVXIJINSWCVSMUBZLUWQXBHNJFWDDETLKVCJHZIBSPNQIBSYQESYVESPBABGUHPHCXXTI9FUCANYBAQUBH9MUJMDLCTQZHZSEEQDMOZMHCLSILNOEDDQYMRWVQKFELARXVYPTYRQUJHOOZEXYIJA9KOMOBMGGBNMTYGOSANCVRJARTECCHWLNZEGCKIKFFHRWSPGPTBDURVQURSMXWKFCQ9NVQRLODYSAGYSKMOWOXOSXZYYNNFXXFNXNSFGMPMG99SAUVUSPUIKQLMPQBQJSW9KFABKRFMRIYRICFBRVOTIGEZ9NAWFUJVFVRQBZMHQCMBVIPDTINIHNSQLGIRUWSGIFJN9ELTWNHSNLOSZZUKDNYXNGSXYDLSBOONJPDWFJETNP9HWKPCEMMADPXRRWLUDMWIVSYXNAFQIUQHVJUEFEDDHRORSLKYAP9YNG9GWEWSXGLZYYNTKUDJBMDWLBCISGLVAPLFNIHJZWQFXHIHWFEVCWYSKEDK9PJOCVWPQDILJGMWIZBDV9CGRTJVO9EUHAMGYXQNILXOQFMFHUTICREZHIMDCXKSKCNCGICMJJHNZSMDABDHSPWDSPHPVJCRSCWDLRTJRQUYHDHCR9RIYDNTVBORNRMYVFULKJ9AWMHMZXFCEPPDWSWGPCHMIDBNOZEJPWRFHDQDYTYGSSOTGRQTOQXRBZLPLEAOVYAGYHRVVEBQZSHMXWCL9ZHRIC9ILBYCSRGKASFLXNV9MM9ZBCHIQMFFHYHCEJMQMLQFLAZSKFGKEJRAV9LHOJGI9CUZFETP9FFNBCSRAIJMFYHWRWCILKUCSTYWDQRVYAXYEC9WZJQZEFPQQZLPTGPIQJUEMNACIUJGXZDGOAZARVTNYGHLENYHVX9RXKYTYCDWRXUMJVTQYT9NAVMZCXYFRTCWMDSGGDUYBCBCJWEJWGCGVLGUHVCFDQPDJYNVGKPEIWGWBU9XXGVOLOWXPAYYNREKTBEKUBDJHDM9DXJNSINWOXHPNZH9A9YSULYADCSHVFZGZZOPZFZZDLFVBXGMESBJK99C9DAMUMKBQCT9FHPWRKFLGAFWDNVBCJVRIANTTEKFIHZMRKJZXGEQCKWGPR9MMKUXBCZJHBJVZRADSSN9Z9KKICTGUILNVDLGDOQCKKQTNEVCWPLUOHZZXHLEVK9BIPVGIPUZCLHIBEESGNIK9JJV9VUMPKDEZBXGVGTXBDOLSQJNEA9DLTQDGGEIORPLHBEMOQWN9BCFMLWRNCIAEKIDSUYFVPHYKYAMAIDFCWNQPLSQZTBRBYAYBFSQLRKWCELOMYE9WJJFTFXGNEGFXIJDPQVFTVKLYVEVSPYXPDGCFVFOEKUYMY9MFB9ZIVHVEFSBQEQWGWDACE9KTWFUEBVACOOIIDHRHLCLYGBIORWWHCLHPPBVTUAWSKESOHFKPVWNSJHWAZRHACHKXUOOXAQSZLVKHQRBEDHXKTJUDDCQQLNZGZTUHHRTNCDVSPSWZBBUS9N9XKMVAGJK9SIHMFOWDDFRWLRGCJEAIAYTHCYIY9D9YCDTAZQMBSVDKRLXZMFTSWHXJKRUSFKOTJKGKMVVAZYQWIVWUZAMAYTPVSVQ9JSBARNMAAZBHLBESSIGTLZLXDSC9ECNASKJXZINON9CEVBBYODKHEOKGKRVAZVQCYVFOXRSCRAKBBHBYZJTX9ZMDRTI9ABELYIJEYBWVDOMDTZFBJJEBGHGBRMPDAYZPNBMNSVFGOUTCMDEYVSVPKGBOFMBUAEKSFOUEX9ZAWFLLONFDQYFBNEN9RCMUIORK9MIIRJXNE9VNZDPGNOMELYDTANFVTW9BHMYOJQUOVOZMIL9FWPDKEDPXUATLJ9MEHAWCTMHQOJMEZZOUHKZGXYQJZVYQMMGNALPNURMRAWWQQSWG9NVISBUZJOCQTVKAMQDEJWPGARHIXOHCBC9OMUOQKNWFXTVNCDUYMIXFROBLRU9FSTOBSCQJPQFMMBXGCEDBHVZQUCBVNJTSOTYYRQZBIECI9MPVJMMBYRQXGZZZBIIPPXVPHSFJPSIILIU9EMSAA9PZSHWGH9CBNWHWZZNQILIPAJFJUIEVZYVQEWWYLHUSUCRZRZRHLGVPSSJDHIBRSVHLNLQJDDU9QFBPCEPWOUJHIAGFADSTBQBPETTQ 3 | WRRQJNIKIOUHELSRNHEBBOSZUMNGBZMQWLHXKKOBEXAAYEYRVRJSRUFYCUHYDTXWOGXEYMGXPWTXFPQTRIBHUMMLXTPSSIRHOQWFZZPLFCGMTWQKVTADJORMWRXALVMLEH9UPWYQPGDNNUHTUSECSGKSNKVYBXDWWVMMJ9VSRTZASZPI9TJBDCJQHYHWPRXI9YXBAHWBTFQADTPDXGTJVIKNKLVJROQEATVWFSLCNDOVBDTAFHWXO9YI9LB9WTHHDOPXSWT9SQAUAXZMOG9FTYOYTTOOUKAZFDAPFSNTJV9F9PG9KOAGJ9RJOBLNPCXJMDGFZOERFPLRTJZDPKPPKGDNUEPVCQHZMYHRIWIXJACNLLFBHOPCCEJGVEAEYNPI9OVSJLYWGAGQFBRJGOJGGJCVQUDDGCKYWWCWKIQDR9CKUIBPOPNGUPKEEO9IXFHWFOODCZFXNVGKJRWSWVFEUPFRNRCVPDTWTISZMNKTROAIOBKKIECXVZNAEYT9SZVKLHADCIQSQ9CLFHCDEANUOOFKSF9YXSGIPNWNRZJOMOHSATPGWO9QIYFBRMYFPRKFBAIISVJEJRVSEURJQJHUNPWNKECXKEMBKJTKHMDHMHSHLMANEJKUQEOMFJZZHDUZKFRKLDYPZXRBZCWLGEUC9UXGWQFKREPNJZKUAODKFKFZTNQ9WZAJYSFKCVUBTC9MWYFVTOEWCMRRHXCZZHOGG9AVAEVTEIA9UDKUYBMORIPQIMT9HXSOZWSGSDWQTAOTUZOGINDHRUVAGDYIZRTDERKZRSRRQVQ9KODP9EDOLRDDR9LYVUCZLDQZNAEDGANPXOVRR9JAQZPFERPEVHONISCUYWAAPODZYGSKLTMUUNQNMMHVBWQAQKLJ9GMDDMBFIMRWCTPOTCHMTTCWMJRNRAWVQMGBYPM9MSEPZWXRYWQNH99XKKJ9HRGTFWPCMVEGLWVEOYUVTKIQPVQ99YEXHCMMOIGMRMNLEYT99SLVLWNGCQABBHUCXOQKPLBLFCQPCGQMZDSAGIDJLEJILONNPBPROGOAAILXIZP9PDHFIOEDXDVSTRQQPKEHVRVFEITQJPUDDOKFVXPL9WZUP9PZB9CINTFZVXKUFHAEMSMXRBDZUQRCHGBZAVPSHZJOOQVTPZHDRFXTPLL9TVQMQ9OMVFCDMODIY99CCBU9BYXWHGHARA9YAKUTSASNACYNZHXNSVVPKO9DCBOMEYSHOJJSTUODSHRYABZYTQWQRIFRSUPIHGDKRZHFVDVCVXVNOMERBREMUHISBH9EUEOSXLSRYPXVTQYGTJGYQVZMLNYQSZWCOMGT9ILKDGYATPFQDPHIWMGNLTIHCBZVFVAFZTYUI9IFWRHZSRXQYMLQXYYIJKRYDZICEYYDIXMGTNSB9HKGBBGJEXYVOTCSADNSMHXUHVJTXBWEPQPYETXPLQEYAMDCVXTZCIOG9ZPLGFGCYAZXNRQLECHGEJBHOZYXHTCQFY9HFBGOZCQTGYDNZICOYBETOOHSEVQWJXMY9DZXDLKEMJ9SGWDZIWFNBAVWMUKYWZHOUNBSJDTSCXGMKXWQMYASBRDQMIACFZTKMGQUBZQJFGHLAHLTB9IFY9FNUUFAXMORHGRIZFTMPLBADAYULJO9NWZBNVYHIJTXITTSVRJCSKMKNPPKGYXGHPCC99LIWUY9DSSCBLOEUNYLSYOTJQADBVSZNIHRGGIAIEEGO9ULQNCTHYDOC9IIVIVEWEKPVQCDUAZSLLIKMVDVSAMDB9TTM9NDZMEPOLCMRRTCAEUXGOIERCMSJWIBJPTSUUMRZAUKHUTIWLO9TESS9DCBDPMFQXHXKIKAYTOEEOIXPBYAUFZOVXHOJJDXJZPVSBAFFUDYNSBWOSKD9GIKLZNTRAFZUTZVMQDNBXPSBIGN9LEOVYKEFFFLL9UNCINHMZOTWVWEJADYTI9SWEVJAQA9RKUSCINMDUOURZQELDVRGBHN9XYVSMHZW9VSKSJABDXHHKHPOO9UWVVTKQQZCQIAFVCKPXBDVRWRLJPGBMNVWVTTNQJIRQVQDXBU9AKKTGRJLWQZXJIIDPFSBYLSJWAOCTKMDZ9AUVMHPNDZIPVRWZFBJKZQU9XPMCNQNSSSF9IO9GH9YINWLPCSUHVFCYKKJYULNERVMKCXVPNJLW9AJVD9MYLENSJTHMU 4 | XN9BTYVMMJNRNBVQMDUXSAYYMGILROSXFKCFEU9ENJDXDFPQLIHGNKUYWZEEC9SP9GJHYXUEQBGHRPHOWTKRLTSEUWECUHAJJEMTCRFCORHGN9SJVK9NBTLZPC9SOZVDBZWRTSTIAKXNLLWMGAHPMAYQRVECXLGVQNEDIRCVQEFSZRTENMPVPAXMBWNWXASJYGPASVXEVCLQLSUWWZLIJAGCNACAIJRYJZ9GZTQUJHRGVEJNF9GIZMEOD9ACIRJMYYDFVTUJGGAQTUCTQZJZIJVFEDLQ9JAYQUODVM9CMCQSDKBWIRKTYSNRINAQEBHSDSHIVS9YHMNZUWGA9CD9UGUPKQUZWERLCOOOWHSHBP9ZQFXUVNBPFCZMV9HXGDCTAMJRQNNTJMJQRDSMFKEKQGMJDHTWYGTQAVEJVHXKVIEPLHEZSOUGOZNEBVNKIPUEBBXWYWYGGBMTZKTYYYFWV9LCZNHGZSQXSZSPOY 5 | -------------------------------------------------------------------------------- /iotapy/snapshot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import io 4 | import iota 5 | import iotapy 6 | from typing import List, Dict 7 | from pkg_resources import resource_string 8 | 9 | 10 | HASH_LENGTH = 243 11 | CURL_HASH_LENGTH = HASH_LENGTH * 3 12 | 13 | 14 | class ISS: 15 | # Initial Snapshot Signature 16 | NUMBER_OF_FRAGMENT_CHUNKS = 27 17 | MIN_TRYTE_VALUE = -13 18 | 19 | 20 | @classmethod 21 | def address(self, sponge_type, digests: List[int]): 22 | if not digests or len(digests) % HASH_LENGTH != 0: 23 | raise ValueError('Invalid digests length: %d' % len(digests)) 24 | 25 | trits = [] 26 | hash = sponge_type() 27 | hash.absorb(digests) 28 | hash.squeeze(trits) 29 | 30 | return trits 31 | 32 | @classmethod 33 | def digest(self, sponge_type, normalized_bundle_fragment: List[int], signature_fragment: iota.TryteString): 34 | hash = sponge_type() 35 | trits = signature_fragment.as_trits() 36 | d = [] 37 | 38 | for j in range(self.NUMBER_OF_FRAGMENT_CHUNKS): 39 | for k in range(normalized_bundle_fragment[j] - self.MIN_TRYTE_VALUE): 40 | hash.reset() 41 | hash.absorb(trits, j * HASH_LENGTH, (j + 1) * HASH_LENGTH) 42 | hash.squeeze(trits, j * HASH_LENGTH) 43 | 44 | hash.reset() 45 | hash.absorb(trits) 46 | hash.squeeze(d) 47 | 48 | return d 49 | 50 | @classmethod 51 | def get_merkle_root(self, sponge_type, hash: List[int], trits: List[int], 52 | offset: int, index: int, length: int): 53 | curl = sponge_type() 54 | for i in range(length): 55 | curl.reset() 56 | if index & 1 == 0: 57 | curl.absorb(hash) 58 | curl.absorb(trits, offset + i * HASH_LENGTH, offset + (i + 1) * HASH_LENGTH) 59 | else: 60 | curl.absorb(trits, offset + i * HASH_LENGTH, offset + (i + 1) * HASH_LENGTH) 61 | curl.absorb(hash) 62 | curl.squeeze(hash) 63 | index >>= 1 64 | 65 | if index: 66 | return [0] * HASH_LENGTH 67 | return hash 68 | 69 | 70 | class Snapshot: 71 | SNAPSHOT_PUBKEY = 'TTXJUGKTNPOOEXSTQVVACENJOQUROXYKDRCVK9LHUXILCLABLGJTIPNF9REWHOIMEUKWQLUOKD9CZUYAC' 72 | SNAPSHOT_PUBKEY_DEPTH = 6 73 | SNAPSHOT_INDEX = 1 74 | MAX_SUPPLY = (3 ** 33 - 1) // 2 75 | 76 | 77 | def __init__(self, state: Dict[iota.Hash, int] = {}, index=0, verify=False): 78 | if state: 79 | self.state = state 80 | else: 81 | self.init_from_snapshot(verify) 82 | self.index = index 83 | 84 | 85 | def init_from_snapshot(self, verify): 86 | self.snapshot_data = resource_string('iotapy.resources', 'Snapshot.txt') 87 | self.snapshot_sig = resource_string('iotapy.resources', 'Snapshot.sig').splitlines() 88 | self.state = {} 89 | 90 | # Init snapshot 91 | curl = iota.crypto.kerl.Kerl() 92 | for line in self.snapshot_data.splitlines(): 93 | trits = iota.TryteString.from_bytes(line).as_trits() 94 | 95 | if verify: 96 | curl.absorb(trits) 97 | 98 | key, value = line.split(b';') 99 | self.state[iota.Hash(key)] = int(value) 100 | 101 | if not self.is_consistent(): 102 | raise ValueError('Snapshot total supply or address value is bad') 103 | 104 | # Check snapshot signature 105 | if not verify: 106 | return 107 | 108 | trits = [] 109 | curl.squeeze(trits) 110 | 111 | mode = iota.crypto.Curl 112 | bundle_hash = iota.BundleHash.from_trits(trits) 113 | bundles = iota.crypto.signing.normalize(bundle_hash) 114 | 115 | digests = [] 116 | for i, bundle in enumerate(bundles): 117 | digests.extend(ISS.digest(mode, bundle, iota.TryteString(self.snapshot_sig[i]))) 118 | 119 | root = ISS.get_merkle_root(mode, ISS.address(mode, digests), 120 | iota.TryteString(self.snapshot_sig[-1]).as_trits(), 121 | 0, self.SNAPSHOT_INDEX, self.SNAPSHOT_PUBKEY_DEPTH) 122 | 123 | if root != iota.TryteString(self.SNAPSHOT_PUBKEY).as_trits(): 124 | raise ValueError('Snapshot signature failed') 125 | 126 | 127 | def is_consistent(self): 128 | state_value = sum(self.state.values()) 129 | if state_value != self.MAX_SUPPLY: 130 | # Transaction resolves to incorrect ledger balance 131 | return False 132 | 133 | if any(i < 0 for i in self.state.values()): 134 | # Value in address is negative 135 | return False 136 | 137 | return True 138 | 139 | def diff(self, diff_state: Dict[iota.Hash, int]): 140 | return { 141 | k: v - self.state.get(k, 0) for k, v in diff_state.items() if 142 | (v - self.state.get(k, 0)) != 0} 143 | 144 | def patch(self, diff_state: Dict[iota.Hash, int], index: int): 145 | patch_state = { 146 | k: v + diff_state.get(k, 0) for k, v in self.state.items() if 147 | (v + diff_state.get(k, 0)) != 0} 148 | 149 | for k, v in diff_state.items(): 150 | if k not in patch_state and v > 0: 151 | patch_state[k] = v 152 | 153 | return Snapshot(patch_state, index) 154 | -------------------------------------------------------------------------------- /iotapy/storage/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iotapy.storage.tangle 4 | import iotapy.storage.converter 5 | import iotapy.storage.providers 6 | -------------------------------------------------------------------------------- /iotapy/storage/converter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | RADIX = 3 4 | BYTE_RADIX = 256 5 | MAX_TRIT_VALUE = (RADIX - 1) // 2 6 | MIN_TRIT_VALUE = -MAX_TRIT_VALUE 7 | 8 | NUMBER_OF_TRITS_IN_A_BYTE = 5 9 | NUMBER_OF_TRITS_IN_A_TRYTE = 3 10 | 11 | HASH_LENGTH = 243 12 | BYTE_TO_TRITS_MAPPINGS = [[]] * HASH_LENGTH 13 | TRYTE_TO_TRITS_MAPPINGS = [[]] * 27 14 | 15 | HIGH_INTEGER_BITS = 0xFFFFFFFF 16 | HIGH_LONG_BITS = 0xFFFFFFFFFFFFFFFF 17 | 18 | TRYTE_ALPHABET = '9ABCDEFGHIJKLMNOPQRSTUVWXYZ' 19 | 20 | MIN_TRYTE_VALUE = -13 21 | MAX_TRYTE_VALUE = 13 22 | 23 | 24 | def increment(trits, length): 25 | for i in range(length): 26 | trits[i] += 1 27 | if trits[i] > MAX_TRIT_VALUE: 28 | trits[i] = MIN_TRIT_VALUE 29 | else: 30 | break 31 | 32 | 33 | def init_converter(): 34 | global BYTE_TO_TRITS_MAPPINGS, TRYTE_TO_TRITS_MAPPINGS 35 | 36 | trits = [0] * NUMBER_OF_TRITS_IN_A_BYTE 37 | for i in range(HASH_LENGTH): 38 | BYTE_TO_TRITS_MAPPINGS[i] = trits[:NUMBER_OF_TRITS_IN_A_BYTE] 39 | increment(trits, NUMBER_OF_TRITS_IN_A_BYTE) 40 | 41 | for i in range(27): 42 | TRYTE_TO_TRITS_MAPPINGS[i] = trits[:NUMBER_OF_TRITS_IN_A_TRYTE] 43 | increment(trits, NUMBER_OF_TRITS_IN_A_TRYTE) 44 | 45 | 46 | def from_trits_to_binary(trits, offset=0, size=HASH_LENGTH): 47 | b = bytearray(b' ' * int((size + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE)) 48 | for i in range(len(b)): 49 | value = 0 50 | for j in range(size - i * NUMBER_OF_TRITS_IN_A_BYTE - 1 if (size - i * NUMBER_OF_TRITS_IN_A_BYTE) < NUMBER_OF_TRITS_IN_A_BYTE else 4, -1, -1): 51 | value = value * RADIX + trits[offset + i * NUMBER_OF_TRITS_IN_A_BYTE + j] 52 | b[i] = value % 256 53 | return bytes(b) 54 | 55 | 56 | def from_binary_to_trits(bs, length): 57 | offset = 0 58 | trits = [0] * length 59 | for i in range(min(len(bs), length)): 60 | # We must convert the binary data 61 | # because java using different range with Python 62 | index = bs[i] if bs[i] < 127 else bs[i] - 256 + HASH_LENGTH 63 | copy_len = length - offset if length - offset < NUMBER_OF_TRITS_IN_A_BYTE else NUMBER_OF_TRITS_IN_A_BYTE 64 | trits[offset: offset + copy_len] = BYTE_TO_TRITS_MAPPINGS[index][:copy_len] 65 | offset += NUMBER_OF_TRITS_IN_A_BYTE 66 | 67 | return trits 68 | 69 | 70 | init_converter() 71 | -------------------------------------------------------------------------------- /iotapy/storage/providers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlouielu/iota-python/f100369c6dd4cf8bf6edf1a3244e9769ff841feb/iotapy/storage/providers/__init__.py -------------------------------------------------------------------------------- /iotapy/storage/providers/rocksdb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import struct 4 | import iota 5 | import rocksdb_iota 6 | import iotapy.storage.providers.types 7 | from rocksdb_iota.merge_operators import StringAppendOperator 8 | from iotapy.storage import converter 9 | 10 | 11 | KB = 1024 12 | MB = KB * 1024 13 | MERGED = ['tag', 'bundle', 'approvee', 'address', 'state_diff'] 14 | 15 | 16 | class RocksDBProvider: 17 | BLOOM_FILTER_BITS_PER_KEY = 10 18 | column_family_names = [ 19 | b'default', 20 | b'transaction', 21 | b'transaction-metadata', 22 | b'milestone', 23 | b'stateDiff', 24 | b'address', 25 | b'approvee', 26 | b'bundle', 27 | b'tag' 28 | ] 29 | 30 | column_family_python_mapping = { 31 | 'transaction_metadata': 'transaction-metadata', 32 | 'state_diff': 'stateDiff' 33 | } 34 | 35 | def __init__(self, db_path, db_log_path, cache_size=4096, read_only=True): 36 | self.db = None 37 | self.db_path = db_path 38 | self.db_log_path = db_log_path 39 | self.cache_size = cache_size 40 | self.read_only = read_only 41 | self.available = False 42 | 43 | def init(self): 44 | self.init_db(self.db_path, self.db_log_path) 45 | self.available = True 46 | 47 | def init_db(self, db_path, db_log_path): 48 | options = rocksdb_iota.Options( 49 | create_if_missing=True, 50 | db_log_dir=db_log_path, 51 | max_log_file_size=MB, 52 | max_manifest_file_size=MB, 53 | max_open_files=10000, 54 | max_background_compactions=1 55 | ) 56 | 57 | options.allow_concurrent_memtable_write = True 58 | 59 | # XXX: How to use this? 60 | block_based_table_config = rocksdb_iota.BlockBasedTableFactory( 61 | filter_policy=rocksdb_iota.BloomFilterPolicy(self.BLOOM_FILTER_BITS_PER_KEY), 62 | block_size_deviation=10, 63 | block_restart_interval=16, 64 | block_cache=rocksdb_iota.LRUCache(self.cache_size * KB), 65 | block_cache_compressed=rocksdb_iota.LRUCache(32 * KB, shard_bits=10)) 66 | options.table_factory = block_based_table_config 67 | 68 | # XXX: How to use this? 69 | column_family_options = rocksdb_iota.ColumnFamilyOptions( 70 | merge_operator=StringAppendOperator(), 71 | table_factory=block_based_table_config, 72 | max_write_buffer_number=2, 73 | write_buffer_size=2 * MB) 74 | 75 | try: 76 | self.db = rocksdb_iota.DB( 77 | self.db_path, options, self.column_family_names, 78 | read_only=self.read_only) 79 | except rocksdb_iota.errors.InvalidArgument as e: 80 | if 'Column family not found' in str(e): 81 | # Currently, rocksdb_iota didn't support 82 | # "create_if_column_family_missing" option, if we detect this 83 | # is a new database, we will need to create its whole 84 | # column family manually. 85 | self.db = rocksdb_iota.DB( 86 | self.db_path, options, [b'default'], read_only=self.read_only) 87 | 88 | # Skip to create b'default' 89 | for column_family in self.column_family_names[1:]: 90 | self.db.create_column_family(column_family) 91 | else: 92 | raise e 93 | 94 | 95 | def _convert_column_to_handler(self, column): 96 | if not isinstance(column, str): 97 | raise TypeError('Column type should be str') 98 | 99 | db_column = self.column_family_python_mapping.get(column, column) 100 | ch = self.db.column_family_handles.get(bytes(db_column, 'ascii')) 101 | if ch is None: 102 | raise KeyError('Invalid column family name: %s' % (column)) 103 | 104 | return ch 105 | 106 | def _convert_key_column(self, key, column): 107 | # Convert column to column family handler 108 | ch = self._convert_column_to_handler(column) 109 | 110 | # Expand iota.Tag to iota.Hash 111 | if column == 'tag': 112 | if not isinstance(key, iota.Tag): 113 | raise TypeError('Tag key type should be iota.Tag') 114 | key = iota.Hash(str(key)) 115 | 116 | # Convert key into trits-binary 117 | if column == 'milestone': 118 | if not isinstance(key, int): 119 | raise TypeError('Milestone key type should be int') 120 | key = struct.pack('>l', key) 121 | else: 122 | if not isinstance(key, iota.TryteString): 123 | raise TypeError('Key type should be iota.TryteString') 124 | if len(key) != iota.Hash.LEN: 125 | raise ValueError('Key length must be 81 trytes') 126 | key = converter.from_trits_to_binary(key.as_trits()) 127 | 128 | return key, ch 129 | 130 | def _get(self, key, bytes_, column): 131 | # Convert value (bytes_) into data object 132 | obj = getattr(iotapy.storage.providers.types, column).get(bytes_, key) 133 | 134 | # Handle metadata 135 | if obj and key and column == 'transaction': 136 | obj.set_metadata(self.get(key, 'transaction_metadata')) 137 | 138 | return obj 139 | 140 | def _get_key(self, bytes_, column): 141 | return getattr(iotapy.storage.providers.types, column).get_key(bytes_) 142 | 143 | def _save(self, value, column): 144 | # Convert value to bytes 145 | return getattr(iotapy.storage.providers.types, column).save(value) 146 | 147 | def get(self, key, column): 148 | k, ch = self._convert_key_column(key, column) 149 | 150 | # Get binary data from database 151 | bytes_ = self.db.get(k, ch) 152 | return self._get(key, bytes_, column) 153 | 154 | def next(self, key, column): 155 | key, ch = self._convert_key_column(key, column) 156 | 157 | it = self.db.iteritems(ch) 158 | it.seek(key) 159 | next(it) 160 | 161 | # XXX: We will get segfault if this is NULL in database 162 | key, bytes_ = it.get() 163 | key = self._get_key(key, column) 164 | 165 | # Convert into data object 166 | return key, self._get(key, bytes_, column) 167 | 168 | def first(self, column): 169 | ch = self._convert_column_to_handler(column) 170 | 171 | it = self.db.iteritems(ch) 172 | it.seek_to_first() 173 | 174 | # XXX: We will get segfault if this is NULL in database 175 | key, bytes_ = it.get() 176 | key = self._get_key(key, column) 177 | 178 | # Convert into data object 179 | return key, self._get(key, bytes_, column) 180 | 181 | def latest(self, column): 182 | ch = self._convert_column_to_handler(column) 183 | 184 | it = self.db.iteritems(ch) 185 | it.seek_to_last() 186 | 187 | # XXX: We will get segfault if this is NULL in database 188 | key, bytes_ = it.get() 189 | key = self._get_key(key, column) 190 | 191 | # Convert into data object 192 | return key, self._get(key, bytes_, column) 193 | 194 | def may_exist(self, key, column, fetch=False): 195 | key, ch = self._convert_key_column(key, column) 196 | 197 | # XXX: Not working...... 198 | return self.db.key_may_exist(key, ch)[0] 199 | 200 | def save(self, key, value, column): 201 | key, ch = self._convert_key_column(key, column) 202 | value = self._save(value, column) 203 | 204 | self.db.put(key, value, ch) 205 | 206 | def store(self, key, value, column): 207 | # Store is different then save, currently deailing with transaction 208 | # that transaction will save more data to other column 209 | batches = getattr(iotapy.storage.providers.types, column).store(key, value) 210 | 211 | write_batch = rocksdb_iota.WriteBatch() 212 | for k, v, column in batches: 213 | k, ch = self._convert_key_column(k, column) 214 | v = self._save(v, column) 215 | 216 | if column in MERGED: 217 | write_batch.merge(k, v, ch) 218 | else: 219 | write_batch.put(k, v, ch) 220 | 221 | self.db.write(write_batch) 222 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iotapy.storage.providers.types.address 4 | import iotapy.storage.providers.types.approvee 5 | import iotapy.storage.providers.types.bundle 6 | import iotapy.storage.providers.types.milestone 7 | import iotapy.storage.providers.types.state_diff 8 | import iotapy.storage.providers.types.tag 9 | import iotapy.storage.providers.types.transaction 10 | import iotapy.storage.providers.types.transaction_metadata 11 | 12 | __all__ = ['address', 'approvee', 'bundle', 'milestone', 'state_diff', 13 | 'tag',' transaction', 'transaction_metadata'] 14 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/address.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from typing import List 5 | from iotapy.storage import converter 6 | 7 | 8 | HASH_TRITS_LENGTH = 243 9 | HASH_BYTES_LENGTH = 49 10 | 11 | 12 | def get_key(bytes_: bytes): 13 | # Convert key bytes to iota.Address 14 | if not isinstance(bytes_, bytes): 15 | raise TypeError 16 | 17 | key = iota.Address.from_trits(converter.from_binary_to_trits(bytes_, HASH_TRITS_LENGTH)) 18 | return key 19 | 20 | 21 | def get(bytes_: bytes, key=None): 22 | if bytes_ is None: 23 | return iter(()) 24 | if not isinstance(bytes_, bytes): 25 | raise TypeError 26 | 27 | for i in range(0, len(bytes_), HASH_BYTES_LENGTH + 1): 28 | ti = converter.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH) 29 | yield iota.types.TransactionHash.from_trits(ti) 30 | 31 | 32 | def save(value: List[iota.TransactionHash]): 33 | if not value: 34 | return b'' 35 | 36 | return b','.join(map(converter.from_trits_to_binary, [i.as_trits() for i in value])) 37 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/approvee.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from typing import List 5 | from iotapy.storage import converter 6 | 7 | 8 | HASH_TRITS_LENGTH = 243 9 | HASH_BYTES_LENGTH = 49 10 | 11 | 12 | def get_key(bytes_: bytes): 13 | # Convert key bytes to iota.TransactionHash 14 | if not isinstance(bytes_, bytes): 15 | raise TypeError 16 | 17 | key = iota.TransactionHash.from_trits(converter.from_binary_to_trits(bytes_, HASH_TRITS_LENGTH)) 18 | return key 19 | 20 | 21 | def get(bytes_: bytes, key=None): 22 | if bytes_ is None: 23 | return iter(()) 24 | if not isinstance(bytes_, bytes): 25 | raise TypeError 26 | 27 | for i in range(0, len(bytes_), HASH_BYTES_LENGTH + 1): 28 | ti = converter.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH) 29 | yield iota.types.TransactionHash.from_trits(ti) 30 | 31 | 32 | def save(value: List[iota.TransactionHash]): 33 | if not value: 34 | return b'' 35 | 36 | return b','.join(map(converter.from_trits_to_binary, [i.as_trits() for i in value])) 37 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/bundle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from typing import List 5 | from iotapy.storage import converter 6 | 7 | 8 | HASH_TRITS_LENGTH = 243 9 | HASH_BYTES_LENGTH = 49 10 | 11 | 12 | def get_key(bytes_: bytes): 13 | # Convert key bytes to key object 14 | if not isinstance(bytes_, bytes): 15 | raise TypeError 16 | 17 | key = iota.BundleHash.from_trits(converter.from_binary_to_trits(bytes_, HASH_TRITS_LENGTH)) 18 | return key 19 | 20 | 21 | def get(bytes_: bytes, key=None): 22 | if bytes_ is None: 23 | return iter(()) 24 | if not isinstance(bytes_, bytes): 25 | raise TypeError 26 | 27 | for i in range(0, len(bytes_), HASH_BYTES_LENGTH + 1): 28 | ti = converter.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH) 29 | yield iota.types.TransactionHash.from_trits(ti) 30 | 31 | 32 | def save(value: List[iota.TransactionHash]): 33 | if not value: 34 | return b'' 35 | 36 | return b','.join(map(converter.from_trits_to_binary, [i.as_trits() for i in value])) 37 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/milestone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import struct 4 | import iota 5 | from typing import Tuple 6 | from iotapy.storage import converter 7 | 8 | 9 | HASH_TRITS_LENGTH = 243 10 | 11 | 12 | def get_key(bytes_: bytes): 13 | # Convert key bytes to key object 14 | if not isinstance(bytes_, bytes): 15 | raise TypeError 16 | 17 | key = struct.unpack('>l', bytes_)[0] 18 | return key 19 | 20 | 21 | def get(bytes_: bytes, key=None): 22 | if bytes_ is None: 23 | return None 24 | if not isinstance(bytes_, bytes): 25 | raise TypeError 26 | 27 | index = struct.unpack('>l', bytes_[:4])[0] 28 | milestone = iota.TransactionHash.from_trits( 29 | converter.from_binary_to_trits(bytes_[4:], HASH_TRITS_LENGTH)) 30 | 31 | return (index, milestone) 32 | 33 | 34 | def save(value: Tuple[int, iota.TransactionHash]): 35 | if not value: 36 | return b'' 37 | 38 | return struct.pack('>l', value[0]) + converter.from_trits_to_binary(value[1].as_trits()) 39 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/state_diff.py: -------------------------------------------------------------------------------- 1 | 2 | import struct 3 | import iota 4 | from typing import List, Tuple 5 | from iotapy.storage import converter 6 | 7 | 8 | STATE_DIFF_TRITS_LENGTH = 243 9 | STATE_DIFF_BYTES_LENGTH = 49 + 8 10 | 11 | 12 | def get_key(bytes_: bytes): 13 | # Convert key bytes to iota.TransactionHash 14 | if not isinstance(bytes_, bytes): 15 | raise TypeError 16 | 17 | key = iota.TransactionHash.from_trits(converter.from_binary_to_trits(bytes_, STATE_DIFF_TRITS_LENGTH)) 18 | return key 19 | 20 | 21 | def get(bytes_: bytes, key=None): 22 | if bytes_ is None: 23 | return iter(()) 24 | if not isinstance(bytes_, bytes): 25 | raise TypeError 26 | 27 | for i in range(0, len(bytes_), STATE_DIFF_BYTES_LENGTH + 1): 28 | value = struct.unpack('>q', bytes_[i + STATE_DIFF_BYTES_LENGTH - 8:i + STATE_DIFF_BYTES_LENGTH])[0] 29 | ti = converter.from_binary_to_trits(bytes_[i:i + STATE_DIFF_BYTES_LENGTH - 8], STATE_DIFF_TRITS_LENGTH) 30 | 31 | yield (iota.TransactionHash.from_trits(ti), value) 32 | 33 | 34 | def save(value: List[Tuple[iota.TransactionHash, int]]): 35 | if not value: 36 | return b'' 37 | 38 | return b','.join( 39 | [converter.from_trits_to_binary(i[0].as_trits()) + struct.pack('>q', i[1]) for i in value]) 40 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/tag.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from typing import List 5 | from iotapy.storage import converter 6 | 7 | 8 | TAG_TRITS_LENGTH = 81 9 | TRANSACTION_TRITS_LENGTH = 243 10 | TRANSACTION_BYTES_LENGTH = 49 11 | 12 | 13 | def get_key(bytes_: bytes): 14 | # Convert key bytes to iota.Tag 15 | if not isinstance(bytes_, bytes): 16 | raise TypeError 17 | 18 | key = iota.Tag.from_trits(converter.from_binary_to_trits(bytes_, TAG_TRITS_LENGTH)) 19 | return key 20 | 21 | 22 | def get(bytes_: bytes, key=None): 23 | if bytes_ is None: 24 | return iter(()) 25 | if not isinstance(bytes_, bytes): 26 | raise TypeError 27 | 28 | for i in range(0, len(bytes_), TRANSACTION_BYTES_LENGTH + 1): 29 | ti = converter.from_binary_to_trits(bytes_[i:i + TRANSACTION_BYTES_LENGTH], TRANSACTION_TRITS_LENGTH) 30 | yield iota.types.TransactionHash.from_trits(ti) 31 | 32 | 33 | def save(value: List[iota.TransactionHash]): 34 | if not value: 35 | return b'' 36 | 37 | return b','.join(map(converter.from_trits_to_binary, [i.as_trits() for i in value])) 38 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from iotapy.storage import converter 5 | 6 | HASH_TRITS_LENGTH = 243 7 | TRANSACTION_TRITS_LENGTH = 8019 8 | 9 | 10 | def get_key(bytes_: bytes): 11 | # Convert key bytes to iota.TransactionHash 12 | if not isinstance(bytes_, bytes): 13 | raise TypeError 14 | 15 | key = iota.TransactionHash.from_trits(converter.from_binary_to_trits(bytes_, 243)) 16 | return key 17 | 18 | 19 | def get(bytes_: bytes, key=None): 20 | if bytes_ is None: 21 | return None 22 | if not isinstance(bytes_, bytes): 23 | raise TypeError 24 | 25 | ti = converter.from_binary_to_trits(bytes_, TRANSACTION_TRITS_LENGTH) 26 | return iota.Transaction.from_tryte_string(iota.TryteString.from_trits(ti), key) 27 | 28 | 29 | def save(value: iota.Transaction): 30 | if not value: 31 | return b'' 32 | 33 | return converter.from_trits_to_binary(value.as_tryte_string().as_trits(), 0, 8019) 34 | 35 | 36 | def store(txh: iota.TransactionHash, tx: iota.Transaction): 37 | # (key, value, column) 38 | return [ 39 | (tx.address, [txh], 'address'), 40 | (tx.bundle_hash, [txh], 'bundle'), 41 | (tx.branch_transaction_hash, [txh], 'approvee'), 42 | (tx.trunk_transaction_hash, [txh], 'approvee'), 43 | (tx.legacy_tag, [txh], 'tag'), 44 | (txh, tx, 'transaction_metadata'), 45 | (txh, tx, 'transaction') 46 | ] 47 | 48 | 49 | class Transaction(iota.Transaction): 50 | validity = 0 51 | type = 1 52 | arrival_time = 0 53 | 54 | solid = False 55 | height = 0 56 | sender = b'' 57 | snapshot = 0 58 | 59 | def set_metadata(self, metadata: dict): 60 | self.validity = metadata.get('validity', self.validity) 61 | self.type = metadata.get('type', self.type) 62 | self.arrival_time = metadata.get('arrival_time', self.arrival_time) 63 | self.solid = metadata.get('solid', self.solid) 64 | self.height = metadata.get('height', self.height) 65 | self.sender = metadata.get('sender', self.sender) 66 | self.snapshot = metadata.get('snapshot', self.snapshot) 67 | -------------------------------------------------------------------------------- /iotapy/storage/providers/types/transaction_metadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import struct 4 | import iota 5 | from iotapy.storage import converter as conv 6 | 7 | 8 | TRANSACTION_METADATA_TRITS_LENGTH = 1604 9 | HASH_BYTES_LENGTH = 49 10 | HASH_TRITS_LENGTH = 243 11 | 12 | 13 | def get_key(bytes_: bytes): 14 | # Convert key bytes to iota.TransactionHash 15 | if not isinstance(bytes_, bytes): 16 | raise TypeError 17 | 18 | key = iota.TransactionHash.from_trits(conv.from_binary_to_trits(bytes_, HASH_TRITS_LENGTH)) 19 | return key 20 | 21 | 22 | def get(bytes_: bytes, key=None): 23 | if bytes_ is None: 24 | return None 25 | if not isinstance(bytes_, bytes): 26 | raise TypeError 27 | 28 | i = 0 29 | address = iota.Address.from_trits(conv.from_binary_to_trits(bytes_[:HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 30 | i += HASH_BYTES_LENGTH 31 | bundle = iota.BundleHash.from_trits(conv.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 32 | i += HASH_BYTES_LENGTH 33 | trunk = iota.TransactionHash.from_trits(conv.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 34 | i += HASH_BYTES_LENGTH 35 | branch = iota.TransactionHash.from_trits(conv.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 36 | i += HASH_BYTES_LENGTH 37 | legacy_tag = iota.Hash.from_trits(conv.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 38 | i += HASH_BYTES_LENGTH 39 | value = struct.unpack('>q', bytes_[i:i + 8])[0] 40 | i += 8 41 | current_index = struct.unpack('>q', bytes_[i:i + 8])[0] 42 | i += 8 43 | last_index = struct.unpack('>q', bytes_[i:i + 8])[0] 44 | i += 8 45 | timestamp = struct.unpack('>q', bytes_[i:i + 8])[0] 46 | i += 8 47 | 48 | tag = iota.Hash.from_trits(conv.from_binary_to_trits(bytes_[i:i + HASH_BYTES_LENGTH], HASH_TRITS_LENGTH)) 49 | i += HASH_BYTES_LENGTH 50 | attachment_timestamp = struct.unpack('>q', bytes_[i:i + 8])[0] 51 | i += 8 52 | attachment_timestamp_lower_bound = struct.unpack('>q', bytes_[i:i + 8])[0] 53 | i += 8 54 | attachment_timestamp_upper_bound = struct.unpack('>q', bytes_[i:i + 8])[0] 55 | i += 8 56 | 57 | validity = struct.unpack('>l', bytes_[i:i + 4])[0] 58 | i += 4 59 | type_ = struct.unpack('>l', bytes_[i:i + 4])[0] 60 | i += 4 61 | arrival_time = struct.unpack('>q', bytes_[i:i + 8])[0] 62 | i += 8 63 | height = struct.unpack('>q', bytes_[i:i + 8])[0] 64 | i += 8 65 | 66 | # Is confirmed? 67 | solid = bytes_[i] == 1 68 | i += 1 69 | snapshot = struct.unpack('>l', bytes_[i:i + 4])[0] 70 | i += 4 71 | sender = bytes_[i:] 72 | 73 | return { 74 | 'address': address, 75 | 'bundle_hash': bundle, 76 | 'trunk_transaction_hash': trunk, 77 | 'branch_transaction_hash': branch, 78 | 'legacy_tag': legacy_tag, 79 | 'value': value, 80 | 'current_index': current_index, 81 | 'last_index': last_index, 82 | 'timestamp': timestamp, 83 | 'tag': tag, 84 | 'attachment_timestamp': attachment_timestamp, 85 | 'attachment_timestamp_lower_bound': attachment_timestamp_lower_bound, 86 | 'attachment_timestamp_upper_bound': attachment_timestamp_upper_bound, 87 | 'validity': validity, 88 | 'type': type_, 89 | 'arrival_time': arrival_time, 90 | 'height': height, 91 | 'solid': solid, 92 | 'snapshot': snapshot, 93 | 'sender': sender 94 | } 95 | 96 | 97 | def save(value: iota.Transaction): 98 | buf = b'' 99 | 100 | buf += conv.from_trits_to_binary(value.address.as_trits()) 101 | buf += conv.from_trits_to_binary(value.bundle_hash.as_trits()) 102 | buf += conv.from_trits_to_binary(value.trunk_transaction_hash.as_trits()) 103 | buf += conv.from_trits_to_binary(value.branch_transaction_hash.as_trits()) 104 | buf += conv.from_trits_to_binary(iota.Hash.from_trits(value.legacy_tag.as_trits()).as_trits()) 105 | buf += struct.pack('>q', value.value) 106 | buf += struct.pack('>q', value.current_index) 107 | buf += struct.pack('>q', value.last_index) 108 | buf += struct.pack('>q', value.timestamp) 109 | 110 | buf += conv.from_trits_to_binary(iota.Hash.from_trits(value.tag.as_trits()).as_trits()) 111 | buf += struct.pack('>q', value.attachment_timestamp) 112 | buf += struct.pack('>q', value.attachment_timestamp_lower_bound) 113 | buf += struct.pack('>q', value.attachment_timestamp_upper_bound) 114 | 115 | buf += struct.pack('>l', value.validity) 116 | buf += struct.pack('>l', value.type) 117 | buf += struct.pack('>q', value.arrival_time) 118 | buf += struct.pack('>q', value.height) 119 | buf += struct.pack('>?', value.solid) 120 | buf += struct.pack('>l', value.snapshot) 121 | buf += value.sender 122 | 123 | return buf 124 | -------------------------------------------------------------------------------- /iotapy/storage/providers/zmq.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlouielu/iota-python/f100369c6dd4cf8bf6edf1a3244e9769ff841feb/iotapy/storage/providers/zmq.py -------------------------------------------------------------------------------- /iotapy/storage/tangle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | from iotapy.storage.providers import rocksdb 5 | 6 | 7 | class Tangle: 8 | def __init__(self, provider): 9 | self.provider = provider 10 | self.provider.init() 11 | 12 | def get(self, key, column): 13 | return self.provider.get(key, column) 14 | 15 | def first(self, column): 16 | return self.provider.first(column) 17 | 18 | def latest(self, column): 19 | return self.provider.latest(column) 20 | 21 | def save(self, key, value, column): 22 | return self.provider.save(key, value, column) 23 | 24 | def store(self, key, value, column): 25 | self.provider.store(key, value, column) 26 | -------------------------------------------------------------------------------- /iotapy/validators/__init__.py: -------------------------------------------------------------------------------- 1 | import iotapy.validators.transaction 2 | -------------------------------------------------------------------------------- /iotapy/validators/ledger.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlouielu/iota-python/f100369c6dd4cf8bf6edf1a3244e9769ff841feb/iotapy/validators/ledger.py -------------------------------------------------------------------------------- /iotapy/validators/transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import iota 4 | import iotapy 5 | from typing import List 6 | 7 | 8 | EMPTY_HASH = iota.Hash('') 9 | PERFILLED_SLOT = 1 10 | 11 | 12 | def get_transaction_weight_magnitude(tx: iota.TransactionHash): 13 | for index, i in enumerate(tx.hash.as_trits()[::-1]): 14 | if i != 0: 15 | return index 16 | 17 | return -1 18 | 19 | 20 | class TransactionValidator: 21 | MIN_WEIGHT_MAGNITUE = 14 22 | 23 | def __init__(self, tangle, tx_requester): 24 | self.tangle = tangle 25 | self.tx_requester = tx_requester 26 | 27 | def run_validation(self, tx: iota.Transaction, min_weight_magnitude: int): 28 | if tx.timestamp < 1508760000 and tx.hash != EMPTY_HASH: 29 | raise ValueError('Invalid transaction timestamp') 30 | 31 | trits = tx.as_tryte_string().as_trits() 32 | if sum(trits[2301:2295]): 33 | raise ValueError('Invalid transaction value') 34 | 35 | weight_magnitude = get_transaction_weight_magnitude(tx) 36 | if weight_magnitude < min_weight_magnitude: 37 | raise ValueError('Invalid transaction hash: weight magnitude too low') 38 | 39 | def validate_trits(self, trits: List[int], min_weight_magnitude: int): 40 | tx = iota.Transaction.from_tryte_string(iota.TryteString.from_trits(trits)) 41 | self.run_validation(tx, min_weight_magnitude) 42 | return tx 43 | 44 | def validate(self, tx: iota.Transaction, min_weight_magnitude: int): 45 | self.run_validation(tx, min_weight_magnitude) 46 | return tx 47 | 48 | def check_solidity(self, txh: iota.TransactionHash, milestone: bool): 49 | txm = self.tangle.get(txh, 'transaction_metadata') 50 | if txm and txm['solid']: 51 | return True 52 | 53 | solid = True 54 | analyzed_hashes = set([EMPTY_HASH]) 55 | non_analyzed_transactions = [txh] 56 | 57 | while non_analyzed_transactions: 58 | txh = non_analyzed_transactions.pop(0) 59 | 60 | if txh not in analyzed_hashes: 61 | analyzed_hashes.add(txh) 62 | tx = self.tangle.get(txh, 'transaction') 63 | if not tx: 64 | solid = False 65 | break 66 | if not tx.solid: 67 | if tx.type == PERFILLED_SLOT and txh != EMPTY_HASH: 68 | self.tx_requester.request_transaction(txh, milestone) 69 | solid = False 70 | break 71 | else: 72 | if solid: 73 | non_analyzed_transactions.append(tx.trunk_transaction_hash) 74 | non_analyzed_transactions.append(tx.branch_transaction_hash) 75 | 76 | if solid: 77 | self.update_solid_transactions(analyzed_hashes) 78 | return solid 79 | 80 | def update_solid_transactions(self, analyzed_hashes: List[iota.TransactionHash]): 81 | for txh in analyzed_hashes: 82 | tx = self.tangle.get(txh, 'transaction') 83 | if not tx: 84 | continue 85 | 86 | self.update_heights(tx) 87 | tx.solid = True 88 | 89 | self.tangle.save(txh, tx, 'transaction_metadata') 90 | 91 | def update_heights(self, tx: iota.Transaction): 92 | transactions = [] 93 | trunk = self.tangle.get(tx.trunk_transaction_hash, 'transaction') 94 | 95 | transactions.append(tx.hash) 96 | while trunk and trunk.height == 0 and trunk.type != PERFILLED_SLOT and trunk.hash != EMPTY_HASH: 97 | tx = trunk 98 | trunk = self.tangle.get(tx.trunk_transaction_hash, 'transaction') 99 | transactions.append(tx.hash) 100 | 101 | while transactions: 102 | txh = transactions.pop(0) 103 | tx = self.tangle.get(txh, 'transaction') 104 | 105 | if not trunk or not tx: 106 | break 107 | if trunk.hash == EMPTY_HASH and trunk.height == 0 and txh != EMPTY_HASH: 108 | tx.height = 1 109 | self.tangle.save(txh, tx, 'transaction_metadata') 110 | elif tx.type != PERFILLED_SLOT and tx.height == 0: 111 | tx.height = 1 + trunk.height 112 | self.tangle.save(txh, tx, 'transaction_metadata') 113 | else: 114 | break 115 | trunk = tx 116 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-rocksdb-iota>=0.7.2 2 | pyota>=2.0.1 3 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlouielu/iota-python/f100369c6dd4cf8bf6edf1a3244e9769ff841feb/test/__init__.py -------------------------------------------------------------------------------- /test/test_providers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import shutil 4 | import tempfile 5 | import types 6 | import unittest 7 | import iota 8 | import iotapy 9 | from collections import Counter 10 | from iota import TransactionHash 11 | from iotapy.storage.providers import rocksdb 12 | from test import utils 13 | 14 | 15 | class RocksDBProviderExistDBReadOnlyFunctionTest(unittest.TestCase): 16 | def setUp(self): 17 | self.provider = rocksdb.RocksDBProvider('/var/db/iota/mainnetdb', '/var/db/iota/mainnetdb.log', read_only=True) 18 | self.provider.init() 19 | 20 | def tearDown(self): 21 | del self.provider.db 22 | del self.provider 23 | 24 | def test_get_tag(self): 25 | key = iota.Tag(b'EXAMPLEPYTHONLIB') 26 | value = list(self.provider.get(key, 'tag')) 27 | expect = [ 28 | TransactionHash(b'GTXDTJVUTVSNHYFPJUOWFKTGQTCMNKZPJDJXSWVQWTXYRDZAVZTX9KFBRIMRQEQLMCMVAUKMZWMHA9999'), 29 | TransactionHash(b'PZYRMRVTSPQXNEQIQEBAGINMDAKOHPOLNH9LR9DTFWMWFQICXL9BJCWBUPMKZERKYKBDRIBYEJYH99999'), 30 | TransactionHash(b'UNUK99RCIWLUQ9WMUT9MPQSZCHTUMGN9IWOCOXWMNPICCCQKLLNIIE9UIFGKZLHRI9QAOEQXQJLL99999') 31 | ] 32 | 33 | for v in value: 34 | self.assertIn(v, expect) 35 | 36 | # Bad 37 | with self.assertRaises(TypeError): 38 | self.assertIsNone(self.provider.get(iota.TryteString('FOOBAR'), 'tag')) 39 | with self.assertRaises(TypeError): 40 | self.provider.get('', 'tag') 41 | 42 | 43 | def test_get_transaction(self): 44 | key = iota.TransactionHash('PZYRMRVTSPQXNEQIQEBAGINMDAKOHPOLNH9LR9DTFWMWFQICXL9BJCWBUPMKZERKYKBDRIBYEJYH99999') 45 | value = self.provider.get(key, 'transaction') 46 | 47 | # XXX: Skip the test now 48 | value.as_json_compatible() 49 | 50 | def test_get_transaction_metadata(self): 51 | key = iota.TransactionHash('PZYRMRVTSPQXNEQIQEBAGINMDAKOHPOLNH9LR9DTFWMWFQICXL9BJCWBUPMKZERKYKBDRIBYEJYH99999') 52 | value = self.provider.get(key, 'transaction_metadata') 53 | expect = {'address': iota.Address(b'9TPHVCFLAZTZSDUWFBLCJOZICJKKPVDMAASWJZNFFBKRDDTEOUJHR9JVGTJNI9IYNVISZVXARWJFKUZWC'), 54 | 'bundle_hash': iota.BundleHash(b'VLQUIJHXNWAINTXQNEQHNIASGSPYPOTNMQCM9RJPETERGLWIIKRLBZSCGPDYSFNZQ9FT9ZMQXZXNITRAC'), 55 | 'trunk_transaction_hash': TransactionHash(b'KTTHXMASNRQOSGZOW9ODAOBQFXKMPKNKWLIWDBVWJGMKQUBBPX9WHCYXCWEAHVTNZHPDKUWHOWN9Z9999'), 56 | 'branch_transaction_hash': TransactionHash(b'J9ZMRIJXZOZRNDHKOUBQGHPAPHQ9QOXFEEVQMJBMBOHKEVNUHJTEMRD9W9UDYMTGQ9ENQKDTJMJN99999'), 57 | 'legacy_tag': iota.Hash(b'EXAMPLEPYTHONLIB99999999999999999999999999999999999999999999999999999999999999999'), 58 | 'value': 0, 59 | 'current_index': 0, 60 | 'last_index': 0, 61 | 'timestamp': 1508993982, 62 | 'tag': iota.Hash(b'EXAMPLEPYTHONLIB99999999999999999999999999999999999999999999999999999999999999999'), 63 | 'attachment_timestamp': 1508993991533, 64 | 'attachment_timestamp_lower_bound': 0, 65 | 'attachment_timestamp_upper_bound': 12, 66 | 'validity': 1, 67 | 'type': -1, 68 | 'height': 0, 69 | 'solid': True} 70 | 71 | self.assertIsInstance(value, dict) 72 | self.assertEqual(value['solid'], True) 73 | for k in expect: 74 | self.assertEqual(value[k], expect[k], '"%s" different' % k) 75 | 76 | def test_get_milestone(self): 77 | key = 259773 78 | value = self.provider.get(key, 'milestone') 79 | 80 | self.assertEqual(value, (259773, iota.TransactionHash(b'9FRPRZZYOQSGZILXVNTSLKULYBODWKWTZHGGZ9WPOCRSJXSLAPNAHENFJNOEKCAMNQPBRNZDVJDZZ9999'))) 81 | 82 | # Bad 83 | self.assertIsNone(self.provider.get(-1, 'milestone')) 84 | self.assertIsNone(self.provider.get(0, 'milestone')) 85 | with self.assertRaises(TypeError): 86 | self.provider.get('test', 'milestone') 87 | with self.assertRaises(TypeError): 88 | self.provider.get(b'test', 'milestone') 89 | with self.assertRaises(TypeError): 90 | self.provider.get(None, 'milestone') 91 | 92 | def test_get_approvee(self): 93 | key = iota.TransactionHash(b'UNUK99RCIWLUQ9WMUT9MPQSZCHTUMGN9IWOCOXWMNPICCCQKLLNIIE9UIFGKZLHRI9QAOEQXQJLL99999') 94 | value = list(self.provider.get(key, 'approvee')) 95 | expect = [ 96 | TransactionHash(b'YUPJOZMOSVIEQZXSVTLSYMIGMLFGDBPSZUTRAML9MIQNCLCMPOGFRAYLSFJUDBJBFDGESTIAFGZR99999'), 97 | TransactionHash(b'ELIWEYAYXYEFOWBHJMELTKVERQWTJF9RXRLISNNQQVWGS9EMYYBVWRJYVJUYAPBGDQNYQEZOPBXWA9999'), 98 | ] 99 | 100 | self.assertEqual(Counter(value), Counter(expect)) 101 | 102 | # Bad 103 | self.assertFalse(list(self.provider.get(iota.TryteString('9' * (iota.Hash.LEN - 1) + 'A'), 'approvee'))) 104 | self.assertFalse(list(self.provider.get(iota.TransactionHash('FOOBAR'), 'approvee'))) 105 | with self.assertRaises(ValueError): 106 | self.assertIsNone(self.provider.get(iota.TryteString('FOOBAR'), 'approvee')) 107 | with self.assertRaises(TypeError): 108 | self.provider.get('', 'approvee') 109 | 110 | def test_get_bundle(self): 111 | key = iota.BundleHash(b'9ZMDWNXOJUEQQGBFHFPLIXLFBVFP9QIEJQGCD9B9NOAWBHHTCUDECJ9LSDUVP9YBIZGAKHANXBYQTADEZ') 112 | value = list(self.provider.get(key, 'bundle')) 113 | expect = [ 114 | TransactionHash(b'9KTTGBWZWKMGY9OTVFCRKUJTMOSRPNDDYKSWJENSH9MBQU9VKLVCWQPPULJHAYYM9JEMTYDTNTI9Z9999'), 115 | TransactionHash(b'HDLRDWIYLHDEOXEMCIVQOOLOGFEBUDHTRRDRLLDIYQUFPSNCUYRQBRUPB9DWLQWCBYVXTBFYFPYGZ9999'), 116 | TransactionHash(b'INLMRLGUURIAVIKCBUCBNEALFLVHFRGWPKUBBEFKOMFRROCSDGXSTWXRBHOXERJKDCURA9LJUHIN99999') 117 | ] 118 | 119 | self.assertEqual(Counter(value), Counter(expect)) 120 | 121 | # Bad 122 | self.assertFalse(list(self.provider.get(iota.TryteString('9' * (iota.Hash.LEN - 1) + 'A'), 'bundle'))) 123 | self.assertFalse(list(self.provider.get(iota.BundleHash('FOOBAR'), 'bundle'))) 124 | with self.assertRaises(ValueError): 125 | self.assertIsNone(self.provider.get(iota.TryteString('FOOBAR'), 'bundle')) 126 | with self.assertRaises(TypeError): 127 | self.provider.get('', 'bundle') 128 | 129 | def test_get_address(self): 130 | key = iota.Address(b'9TPHVCFLAZTZSDUWFBLCJOZICJKKPVDMAASWJZNFFBKRDDTEOUJHR9JVGTJNI9IYNVISZVXARWJFKUZWC') 131 | value = list(self.provider.get(key, 'address')) 132 | 133 | self.assertGreaterEqual(len(value), 84) 134 | self.assertIsInstance(value[0], iota.TransactionHash) 135 | 136 | # Bad 137 | self.assertFalse(list(self.provider.get(iota.TryteString('9' * (iota.Hash.LEN - 1) + 'A'), 'address'))) 138 | self.assertFalse(list(self.provider.get(iota.Address('FOOBAR'), 'address'))) 139 | with self.assertRaises(ValueError): 140 | self.assertIsNone(self.provider.get(iota.TryteString('FOOBAR'), 'address')) 141 | with self.assertRaises(TypeError): 142 | self.provider.get('', 'address') 143 | 144 | def test_get_state_diff(self): 145 | key, value = self.provider.latest('state_diff') 146 | 147 | self.assertIsInstance(key, iota.TransactionHash) 148 | self.assertIsInstance(value, types.GeneratorType) 149 | 150 | # Bad 151 | self.assertFalse(list(self.provider.get(iota.TryteString('9' * (iota.Hash.LEN - 1) + 'A'), 'state_diff'))) 152 | self.assertFalse(list(self.provider.get(iota.TransactionHash('FOOBAR'), 'state_diff'))) 153 | with self.assertRaises(ValueError): 154 | self.assertIsNone(self.provider.get(iota.TryteString('FOOBAR'), 'state_diff')) 155 | with self.assertRaises(TypeError): 156 | self.provider.get('', 'state_diff') 157 | 158 | 159 | def test_bad_column_value_should_raise_key_error(self): 160 | with self.assertRaises(KeyError): 161 | self.provider.get(259773, 'milestones') 162 | with self.assertRaises(KeyError): 163 | self.provider.get(iota.Hash('EXAMPLE'), 'tags') 164 | with self.assertRaises(KeyError): 165 | self.provider.get(None, '') 166 | 167 | def test_bad_column_type_should_raise_type_error(self): 168 | with self.assertRaises(TypeError): 169 | self.provider.get(None, b'') 170 | with self.assertRaises(TypeError): 171 | self.provider.get(None, None) 172 | with self.assertRaises(TypeError): 173 | self.provider.get(None, 100) 174 | 175 | def test_next_transaction(self): 176 | key = iota.TransactionHash('PZYRMRVTSPQXNEQIQEBAGINMDAKOHPOLNH9LR9DTFWMWFQICXL9BJCWBUPMKZERKYKBDRIBYEJYH99999') 177 | value = self.provider.next(key, 'transaction') 178 | 179 | self.assertEqual(value[0], iota.TransactionHash('PZYUVYZADTFXQSJCMNVOUKDKOMOBEPLQSKJGNZGCKSXOSXEJKUBTUQZGOFMVDTLXBDYABFIVGZ9JZ9999')) 180 | 181 | def test_next_milestone(self): 182 | key = 250000 183 | value = self.provider.next(key, 'milestone') 184 | 185 | self.assertEqual(value[0], key + 1) 186 | 187 | def test_first_milestone(self): 188 | key, value = self.provider.first('milestone') 189 | self.assertEqual(key, 243001) 190 | self.assertEqual(value, (243001, iota.TransactionHash(b'9PPVIKDMKUDXTYJFF9YNWUPPMOYZTYKRBFGLGDCNNNIMWAMGVJGEHOCOUDYRVYPPSDKDKDQXUBMYA9999'))) 191 | 192 | def test_latest_milestone(self): 193 | key, value = self.provider.latest('milestone') 194 | self.assertIsInstance(key, int) 195 | self.assertIsInstance(value, tuple) 196 | 197 | def test_first_transaction(self): 198 | key, value = self.provider.first('transaction') 199 | 200 | self.assertIsInstance(key, iota.TransactionHash) 201 | self.assertIsInstance(value, iota.Transaction) 202 | 203 | def test_latest_transaction(self): 204 | key, value = self.provider.latest('transaction') 205 | 206 | self.assertIsInstance(key, iota.TransactionHash) 207 | self.assertIsInstance(value, iota.Transaction) 208 | 209 | @unittest.expectedFailure 210 | def test_may_exist(self): 211 | # XXX: This function is not work now 212 | key = iota.TransactionHash('PZYRMRVTSPQXNEQIQEBAGINMDAKOHPOLNH9LR9DTFWMWFQICXL9BJCWBUPMKZERKYKBDRIBYEJYH99999') 213 | self.assertTrue(self.provider.may_exist(key, 'transaction')) 214 | self.assertFalse(self.provider.may_exist(iota.Hash('FOOBAR'), 'transaction')) 215 | 216 | 217 | class RocksDBProviderTest(unittest.TestCase): 218 | def setUp(self): 219 | self.db_path = tempfile.mkdtemp() 220 | self.db_log_path = tempfile.mkdtemp() 221 | 222 | self.provider = rocksdb.RocksDBProvider(self.db_path, self.db_log_path, read_only=False) 223 | self.provider.init() 224 | 225 | def cleanUp(self): 226 | del self.provider.db 227 | del self.provider 228 | shutil.rmtree(self.db_path) 229 | shutil.rmtree(self.db_log_path) 230 | 231 | def test_create_a_new_database(self): 232 | with tempfile.TemporaryDirectory() as db_path: 233 | with tempfile.TemporaryDirectory() as db_log_path: 234 | r = rocksdb.RocksDBProvider(str(db_path), str(db_log_path), read_only=False) 235 | r.init() 236 | ch = r.db.column_family_handles[b'transaction'] 237 | r.db.put(b'hello', b'world', ch) 238 | self.assertEqual(r.db.get(b'hello', ch), b'world') 239 | 240 | def test_save_tag(self): 241 | key = iota.Tag('EXAMPLE') 242 | value = [iota.TransactionHash('FOO'), iota.TransactionHash('BAR')] 243 | self.provider.save(key, value, 'tag') 244 | 245 | v = self.provider.get(key, 'tag') 246 | self.assertEqual(list(v), value) 247 | 248 | value = [iota.TransactionHash('THISWILLOVERRIDETHEVALUE')] 249 | self.provider.save(key, value, 'tag') 250 | 251 | v = self.provider.get(key, 'tag') 252 | self.assertEqual(list(v), value) 253 | 254 | def test_save_bundle(self): 255 | key = iota.TransactionHash('EXAMPLE') 256 | value = [iota.TransactionHash('FOO'), iota.TransactionHash('BAR')] 257 | self.provider.save(key, value, 'bundle') 258 | 259 | v = self.provider.get(key, 'bundle') 260 | self.assertEqual(list(v), value) 261 | 262 | value = [iota.TransactionHash('THISWILLOVERRIDETHEVALUE')] 263 | self.provider.save(key, value, 'bundle') 264 | 265 | v = self.provider.get(key, 'bundle') 266 | self.assertEqual(list(v), value) 267 | 268 | def test_save_approvee(self): 269 | key = iota.TransactionHash('EXAMPLE') 270 | value = [iota.TransactionHash('FOO'), iota.TransactionHash('BAR')] 271 | self.provider.save(key, value, 'approvee') 272 | 273 | v = self.provider.get(key, 'approvee') 274 | self.assertEqual(list(v), value) 275 | 276 | value = [iota.TransactionHash('THISWILLOVERRIDETHEVALUE')] 277 | self.provider.save(key, value, 'approvee') 278 | 279 | v = self.provider.get(key, 'approvee') 280 | self.assertEqual(list(v), value) 281 | 282 | def test_save_address(self): 283 | key = iota.Address('EXAMPLE') 284 | value = [iota.TransactionHash('FOO'), iota.TransactionHash('BAR')] 285 | self.provider.save(key, value, 'address') 286 | 287 | v = self.provider.get(key, 'address') 288 | self.assertEqual(list(v), value) 289 | 290 | value = [iota.TransactionHash('THISWILLOVERRIDETHEVALUE')] 291 | self.provider.save(key, value, 'address') 292 | 293 | v = self.provider.get(key, 'address') 294 | self.assertEqual(list(v), value) 295 | 296 | def test_save_state_diff(self): 297 | key = iota.TransactionHash('EXAMPLE') 298 | value = [(iota.TransactionHash('FOO'), 1000000), (iota.TransactionHash('BAR'), -10000000)] 299 | self.provider.save(key, value, 'state_diff') 300 | 301 | v = self.provider.get(key, 'state_diff') 302 | self.assertEqual(list(v), value) 303 | 304 | value = [(iota.TransactionHash('THISWILLOVERRIDETHEVALUE'), 10000)] 305 | self.provider.save(key, value, 'state_diff') 306 | 307 | v = self.provider.get(key, 'state_diff') 308 | self.assertEqual(list(v), value) 309 | 310 | def test_save_milestone(self): 311 | key = 26000 312 | value = (key, iota.TransactionHash('FOO')) 313 | self.provider.save(key, value, 'milestone') 314 | 315 | v = self.provider.get(key, 'milestone') 316 | self.assertEqual(v, value) 317 | 318 | value = (key, iota.TransactionHash('BAR')) 319 | self.provider.save(key, value, 'milestone') 320 | 321 | v = self.provider.get(key, 'milestone') 322 | self.assertEqual(v, value) 323 | 324 | def test_save_transaction_metadata(self): 325 | tx = utils.get_random_transaction() 326 | tx.solid = True 327 | tx.value = 0 328 | self.provider.save(tx.hash, tx, 'transaction_metadata') 329 | 330 | v = self.provider.get(tx.hash, 'transaction_metadata') 331 | self.assertEqual(v['solid'], True) 332 | 333 | def test_store_transaction_should_save_metadata(self): 334 | tx = utils.get_random_transaction() 335 | tx.solid = True 336 | tx.value = -100 337 | self.provider.store(tx.hash, tx, 'transaction') 338 | 339 | v = self.provider.get(tx.hash, 'transaction') 340 | self.assertEqual(v.solid, True) 341 | self.assertEqual(v.value, -100) 342 | 343 | 344 | if __name__ == '__main__': 345 | unittest.main() 346 | -------------------------------------------------------------------------------- /test/test_snapshot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import iota 5 | import iotapy 6 | 7 | 8 | snap = iotapy.snapshot.Snapshot(verify=False) 9 | 10 | 11 | class SnapshotTest(unittest.TestCase): 12 | def test_snapshot_init(self): 13 | self.assertTrue(snap.is_consistent()) 14 | 15 | def test_snapshot_diff(self): 16 | snapshot = iotapy.snapshot.Snapshot(snap.state, 0) 17 | diff = snapshot.diff(self.get_modified_state(snapshot)) 18 | self.assertEqual(len(diff.keys()), 2) 19 | 20 | def test_snapshot_patch(self): 21 | snapshot = iotapy.snapshot.Snapshot(snap.state, 0) 22 | diff = snapshot.diff(self.get_modified_state(snapshot)) 23 | 24 | new_state = snapshot.patch(diff, 0) 25 | diff = snapshot.diff(new_state.state) 26 | self.assertNotEqual(len(diff), 0) 27 | self.assertTrue(new_state.is_consistent()) 28 | 29 | @unittest.skip('Take too long time to veirfy a snapshot') 30 | def test_snapshot_init_veirfy(self): 31 | iotapy.snapshot.Snapshot(verify=True) 32 | 33 | def get_modified_state(self, snapshot): 34 | h = iota.Hash('PSRQPWWIECDGDDZXHGJNMEVJNSVOSMECPPVRPEVRZFVIZYNNXZNTOTJOZNGCZNQVSPXBXTYUJUOXYASLS') 35 | m = dict(snapshot.state) 36 | 37 | if m: 38 | k = next(iter(m)) 39 | m[h] = m[k] 40 | m[k] = 0 41 | 42 | return m 43 | -------------------------------------------------------------------------------- /test/test_transaction_validator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import random 4 | import tempfile 5 | import unittest 6 | import iota 7 | import iotapy 8 | from test import utils 9 | from iotapy.storage.providers import rocksdb 10 | 11 | 12 | class TransactionValidatorTest(unittest.TestCase): 13 | MAINNET_MWM = 14 14 | TESTNET_MWM = 13 15 | 16 | def setUp(self): 17 | self.db_path = tempfile.mkdtemp() 18 | self.db_log_path = tempfile.mkdtemp() 19 | 20 | self.provider = rocksdb.RocksDBProvider(self.db_path, self.db_log_path, read_only=False) 21 | self.tangle = iotapy.storage.tangle.Tangle(self.provider) 22 | self.tx_validator = iotapy.validators.transaction.TransactionValidator( 23 | self.tangle, iotapy.network.TransactionRequester(self.tangle)) 24 | 25 | def cleanUp(self): 26 | del self.provider.db 27 | del self.provider 28 | shutil.rmtree(self.db_path) 29 | shutil.rmtree(self.db_log_path) 30 | 31 | def test_verify_tx_is_solid(self): 32 | tx = self.get_tx_with_branch_trunk() 33 | self.assertTrue(self.tx_validator.check_solidity(tx.hash, False)) 34 | self.assertTrue(self.tx_validator.check_solidity(tx.hash, True)) 35 | 36 | def test_verify_tx_is_not_solid(self): 37 | tx = utils.get_random_transaction() 38 | tx.type = -1 39 | tx.value = 0 40 | self.tangle.store(tx.hash, tx, 'transaction') 41 | 42 | self.assertFalse(self.tx_validator.check_solidity(tx.hash, False)) 43 | self.assertFalse(self.tx_validator.check_solidity(tx.hash, True)) 44 | 45 | def get_tx_with_branch_trunk(self): 46 | trytes = ( 47 | "99999999999999999999999999999999999999999999999999999999999999" 48 | "99999999999999999999999999999999999999999999999999999999999999" 49 | "99999999999999999999999999999999999999999999999999999999999999" 50 | "99999999999999999999999999999999999999999999999999999999999999" 51 | "99999999999999999999999999999999999999999999999999999999999999" 52 | "99999999999999999999999999999999999999999999999999999999999999" 53 | "99999999999999999999999999999999999999999999999999999999999999" 54 | "99999999999999999999999999999999999999999999999999999999999999" 55 | "99999999999999999999999999999999999999999999999999999999999999" 56 | "99999999999999999999999999999999999999999999999999999999999999" 57 | "99999999999999999999999999999999999999999999999999999999999999" 58 | "99999999999999999999999999999999999999999999999999999999999999" 59 | "99999999999999999999999999999999999999999999999999999999999999" 60 | "99999999999999999999999999999999999999999999999999999999999999" 61 | "99999999999999999999999999999999999999999999999999999999999999" 62 | "99999999999999999999999999999999999999999999999999999999999999" 63 | "99999999999999999999999999999999999999999999999999999999999999" 64 | "99999999999999999999999999999999999999999999999999999999999999" 65 | "99999999999999999999999999999999999999999999999999999999999999" 66 | "99999999999999999999999999999999999999999999999999999999999999" 67 | "99999999999999999999999999999999999999999999999999999999999999" 68 | "99999999999999999999999999999999999999999999999999999999999999" 69 | "99999999999999999999999999999999999999999999999999999999999999" 70 | "99999999999999999999999999999999999999999999999999999999999999" 71 | "99999999999999999999999999999999999999999999999999999999999999" 72 | "99999999999999999999999999999999999999999999999999999999999999" 73 | "99999999999999999999999999999999999999999999999999999999999999" 74 | "99999999999999999999999999999999999999999999999999999999999999" 75 | "99999999999999999999999999999999999999999999999999999999999999" 76 | "99999999999999999999999999999999999999999999999999999999999999" 77 | "99999999999999999999999999999999999999999999999999999999999999" 78 | "99999999999999999999999999999999999999999999999999999999999999" 79 | "99999999999999999999999999999999999999999999999999999999999999" 80 | "99999999999999999999999999999999999999999999999999999999999999" 81 | "99999999999999999999999999999999999999999999999999999999999999" 82 | "99999999999999999999999999999999999999999999999999999999999999" 83 | "999999999999999999999999999999999999CFDEZBLZQYA999999999999999" 84 | "9999999999999999999999999999ZZWQHWD99C99999999C99999999CKWWDBW" 85 | "SCLMQULCTAAJGXDEMFJXPMGMAQIHDGHRBGEMUYNNCOK9YPHKEEFLFCZUSPMCJH" 86 | "AKLCIBQSGWAS99999999999999999999999999999999999999999999999999" 87 | "99999999999999999999999999999999999999999999999999999999999999" 88 | "99999999999999999999999999999999999999999999999999999999999999" 89 | "99999999999999999999999999999999999999999999999999999999999999" 90 | "9999999") 91 | 92 | trunk = iota.Transaction.from_tryte_string(trytes) 93 | branch = iota.Transaction.from_tryte_string(trytes) 94 | trunk.type = -1 95 | branch.type = -1 96 | 97 | child = utils.get_random_transaction_with_trunk_and_branch(trunk.hash, branch.hash) 98 | child.value = 0 99 | 100 | self.tangle.store(trunk.hash, trunk, 'transaction') 101 | self.tangle.store(branch.hash, branch, 'transaction') 102 | self.tangle.store(child.hash, child, 'transaction') 103 | 104 | return child 105 | -------------------------------------------------------------------------------- /test/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import random 4 | import iota 5 | 6 | 7 | def get_random_trits(length): 8 | return [random.randint(-1, 1) for _ in range(length)] 9 | 10 | 11 | def get_random_hash(): 12 | return iota.Hash.from_trits(get_random_trits(243)) 13 | 14 | 15 | def get_random_transaction_hash(): 16 | return iota.TransactionHash.from_trits(get_random_trits(243)) 17 | 18 | 19 | def get_random_transaction(): 20 | return iota.Transaction.from_tryte_string( 21 | iota.TryteString.from_trits(get_random_trits(8019)), 22 | get_random_transaction_hash()) 23 | 24 | 25 | def get_random_transaction_with_trunk_and_branch(trunk, branch): 26 | tx = get_random_transaction() 27 | tx.trunk_transaction_hash = trunk 28 | tx.branch_transaction_hash = branch 29 | tx.type = -1 30 | 31 | return tx 32 | --------------------------------------------------------------------------------