├── LICENSE ├── README.MD ├── nanodb.ksy ├── nanodb.png └── sample ├── .gitignore ├── nanodb.py ├── read.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Nano 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This repository contains a Kaitai Struct specification for the format of keys and values in the **Nano ledger database**, as well as a Python sample that uses the generated parser. 4 | 5 | The wallet database is out of scope for this specification. 6 | 7 | Please consider filing a ticket if you discover an issue with the specification or the generated code. 8 | 9 | # Diagram 10 | 11 | ![Alt text](nanodb.png?raw=true "Nano") 12 | 13 | # Python example 14 | 15 | `read.py` is a sample Python script to demonstrate the generated Kaitai parser and help validate its coverage. 16 | 17 | Install dependencies through `pip3 install -r requirements.txt` 18 | 19 | The generated parser is included in the repository for convenience, but you can generate it yourself with `kaitai-struct-compiler -t python --outdir sample nanodb.ksy` 20 | 21 | The example assumes *lmdb*, but should be easy to adapt to RocksDB as they share the key/value content format. 22 | 23 | ## Usage 24 | 25 | **Only run this script on a database that is not currently in use by other processes** 26 | 27 | Get help: 28 | 29 | `python3 read.py -h` 30 | 31 | Read 5 entries from every table, using data.ldb in the current directory 32 | 33 | `python3 read.py --count 5` 34 | 35 | Read from a specific data.ldb file, and only the vote table: 36 | 37 | `python3 read.py --filename /my/nano/data.ldb --count 5 --table vote` 38 | 39 | Lookup a state block with a specific hash using --key 40 | 41 | `python3 read.py --count 1 --table state_blocks --key 57265474B5C38ED312259DC2E18DADE5C8C45763ECE51C274E2717E2FD688F8D` 42 | 43 | ## Sample output 44 | 45 | A few examples: 46 | 47 | `python3 read.py --count 1 --table accounts` 48 | 49 | ``` 50 | head block : AB957D79C4777B574647BDA3460D7D7A855F4F607F07EAFF6318FAFFC27A21EC 51 | open block : 44F6918529FB5DC4C638512C4D81482D2121CAF142BED39FB6D44F6AB4AB244F 52 | balance : 60523806000000000000000000000000 53 | block count : 5 54 | last modified : 2018-12-17 08:11:09 55 | representative : xrb_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou 56 | ``` 57 | 58 | Note that the sample script skips accounts with zero balance. 59 | 60 | `python3 read.py --count 1 --table vote` 61 | ``` 62 | vote account : xrb_13hhh1mukd8kywseuxhiiq5hwronoqbjqu5o4pneouxadtfig8tuw6hbt1f5 63 | signature : E1B4FCF6234C9F171B18EEEA5E7A99EF40C1F5543BA33BA9AC6799458F9F889AC9244B1A5D29B05B29586AD57C88951DF3FD01CAAD0411DDAE4B7007FB8B310F 64 | sequence : 38330158 65 | block type : state 66 | account : xrb_3owqgr1i6iqtot63yg1rhd3jgrmpz5bwz3348azbcxottjaeqacxpf9wtmqj 67 | representative : xrb_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh 68 | previous : A02AAFD01A6A7FE4D2212186066BBA20CCD4A57772ABD81E9C0E89584E052F48 69 | balance : 2343000000000000000000000000000000 70 | link : 234B680321669312851CBC45490BAA73251668F1897F8B873214D23BB97AA7CF 71 | link as account : xrb_1atdf13k4snm4c4jsh47b67tnws74snh54dzjg5m678k9gwqobyhbf43mbm6 72 | signature : 8FD94851311E33CCE59341D2D62F39290337421C56AD2F1952D3E934DDDE4F35514D29764C3C822A1576E35DDDC40C5E7652F1F6E5043D1FD41F720ADF783E0A 73 | work : 0x961ca14f47fcf532 74 | ``` 75 | 76 | Which is a vote for block https://nanocrawler.cc/explorer/block/9EBCD3A15832AA701E9832B2CC1DD96446B499F9C82548A6F5324FE556143D3E 77 | 78 | `python3 read.py --count 1 --table pending` 79 | 80 | ``` 81 | key account : 0000000000000000000000000000000000000000000000000000000000000000 82 | key hash : 00002E43956C771A6120EA21D7917F7349FD8BE9F5B01D2BDB89ACA3D5B220F0 83 | source : nano_1hmqzugsmsn4jxtzo5yrm4rsysftkh9343363hctgrjch1984d8ey9zoyqex 84 | amount : 2 85 | epoch : epoch_1 86 | ``` 87 | 88 | `python3 read.py --count 1 --table state_blocks` 89 | 90 | ``` 91 | hash : 0000026BF6EF2FAC78C9105CABA1E44FAF866548248E5D0926C6B6E0E5BFCDBA 92 | account : nano_1gjku9ph9dtcfhs3x4xi9exxktdof14e8rwbk4qkztemjqbpfb6xheqknbp8 93 | representative : nano_3dmtrrws3pocycmbqwawk6xs7446qxa36fcncush4s1pejk16ksbmakis78m 94 | previous : 33DD2660E8AC2E0E283AB9162FC5AD88853EB26DF64439B1E7E92D1A0952C1C4 95 | balance : 1671360722739128456642786434 96 | link : 7180E432F07ABB020136631E029F88675346408A2C0803D16B78146701E62300 97 | link as account : nano_1we1wish1you1a1merry1christmas1and1a1happy1new1year14awbdw9b 98 | signature : 37EED28CAF1169894D015340524B85299154C9A6B41A02D352B2B9662F0F1E2E7C340732E7B2F43C59AA000ED3690C99D3F63C0E628DDD20D759B02DEFF2EE0C 99 | work : 0xe16c3225e275f088 100 | sideband: 101 | successor : EC1D4FF78B272F97BF1B3FA0E3D6DE700FE30502475E0F7F24CA57D53909E217 102 | height : 584542 103 | timestamp : 2019-12-23 18:01:03 104 | epoch : epoch_1 105 | ``` 106 | 107 | `python3 read.py --count 4 --table peers` 108 | 109 | ``` 110 | address : ::ffff:5b79:7713 111 | ipv4 mapped : 91.121.119.19 112 | port : 7075 113 | 114 | address : ::ffff:5e10:72eb 115 | ipv4 mapped : 94.16.114.235 116 | port : 7075 117 | 118 | address : ::ffff:5e10:7a33 119 | ipv4 mapped : 94.16.122.51 120 | port : 7075 121 | 122 | address : ::ffff:5eed:3920 123 | ipv4 mapped : 94.237.57.32 124 | port : 7075 125 | ``` 126 | 127 | `python3 read.py --count 4 --table online_weight` 128 | 129 | ``` 130 | timestamp : 2020-01-02 14:39:36.078484 131 | online weight : 96426874920614605364417680260758071281 132 | 133 | timestamp : 2020-01-02 14:44:37.230748 134 | online weight : 96812388601760176820534532978385899934 135 | 136 | timestamp : 2020-01-02 14:49:48.346698 137 | online weight : 96427874270863686929012362506644761248 138 | 139 | timestamp : 2020-01-05 04:21:51.079236 140 | online weight : 97683094289936149573532425682751217817 141 | ``` 142 | 143 | `python3 read.py --count 4 --table meta` 144 | 145 | ``` 146 | Database version: 16 147 | ``` 148 | 149 | ## Generating a diagram 150 | 151 | This needs the package `graphviz` 152 | 153 | `kaitai-struct-compiler -t graphviz nanodb.ksy && dot -Tpng nanodb.dot > nanodb.png` 154 | -------------------------------------------------------------------------------- /nanodb.ksy: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------- 2 | # Nano database definition 3 | # ---------------------------------------------------------------------- 4 | meta: 5 | id: nanodb 6 | title: Nano Ledger Database Binary Format 7 | license: BSD 2-Clause 8 | endian: le 9 | 10 | enums: 11 | 12 | # The database version covered by this specification 13 | database_version: 14 | 19: value 15 | 16 | enum_blocktype: 17 | 0x00: invalid 18 | 0x01: not_a_block 19 | 0x02: send 20 | 0x03: receive 21 | 0x04: open 22 | 0x05: change 23 | 0x06: state 24 | 25 | enum_epoch: 26 | 0x00: invalid 27 | 0x01: unspecified 28 | 0x02: epoch_0 29 | 0x03: epoch_1 30 | 0x04: epoch_2 31 | 32 | enum_signature_verification: 33 | 0x0: unknown 34 | 0x1: invalid 35 | 0x2: valid 36 | 0x3: valid_epoch 37 | 38 | # Keys in the meta table. Note that keys are converted to 32-byte integers by the node. 39 | meta_key: 40 | 1: version 41 | 42 | types: 43 | 44 | # ------------------------------------------------------------------- 45 | # Table: confirmation_height 46 | # ------------------------------------------------------------------- 47 | confirmation_height: 48 | doc: Confirmed height per account 49 | seq: 50 | - id: key 51 | type: confirmation_height_key 52 | - id: value 53 | type: confirmation_height_value 54 | confirmation_height_key: 55 | seq: 56 | - id: account 57 | size: 32 58 | confirmation_height_value: 59 | seq: 60 | - id: height 61 | type: u8le 62 | - id: frontier 63 | size: 32 64 | doc: Hash of confirmed frontier block at this height 65 | 66 | # ------------------------------------------------------------------- 67 | # Table: frontiers 68 | # ------------------------------------------------------------------- 69 | frontiers: 70 | doc: Mapping from block hash to account. This is not used after epoch 1. 71 | seq: 72 | - id: key 73 | type: frontiers_key 74 | - id: value 75 | type: frontiers_value 76 | frontiers_key: 77 | seq: 78 | - id: hash 79 | size: 32 80 | frontiers_value: 81 | seq: 82 | - id: account 83 | size: 32 84 | 85 | # ------------------------------------------------------------------- 86 | # Table: meta 87 | # Structure of value may depends on key in the future, hence a 88 | # separate definition for each key (currently only version is used) 89 | # ------------------------------------------------------------------- 90 | meta_key: 91 | seq: 92 | - id: key 93 | size: 32 94 | 95 | meta_version: 96 | doc: Value of key meta_key#version 97 | seq: 98 | - id: database_version 99 | size: 32 100 | doc: 32-byte big endian integer representing the database version 101 | 102 | # ------------------------------------------------------------------- 103 | # Table: online_weight 104 | # ------------------------------------------------------------------- 105 | online_weight: 106 | doc: Stores online weight trended over time 107 | seq: 108 | - id: key 109 | type: online_weight_key 110 | - id: value 111 | type: online_weight_value 112 | online_weight_key: 113 | seq: 114 | - id: timestamp 115 | type: u8be 116 | doc: Unix epoch 117 | online_weight_value: 118 | seq: 119 | - id: amount 120 | size: 16 121 | doc: 128 bit amount representing online weight at timestamp 122 | 123 | # ------------------------------------------------------------------- 124 | # Table: peers 125 | # ------------------------------------------------------------------- 126 | peers: 127 | doc: Peer cache table. All data is stored in the key. 128 | seq: 129 | - id: address 130 | doc: IPv6 address bytes, big endian 131 | size: 16 132 | - id: port 133 | doc: Port number, big endian 134 | type: u2be 135 | 136 | # ------------------------------------------------------------------- 137 | # Table: pending 138 | # ------------------------------------------------------------------- 139 | pending: 140 | doc: Pending table 141 | seq: 142 | - id: key 143 | type: pending_key 144 | - id: value 145 | type: pending_value 146 | 147 | pending_key: 148 | seq: 149 | - id: account 150 | size: 32 151 | - id: hash 152 | doc: Block hash 153 | size: 32 154 | 155 | pending_value: 156 | seq: 157 | - id: source 158 | doc: Source account 159 | size: 32 160 | - id: amount 161 | size: 16 162 | - id: epoch 163 | type: u1 164 | enum: enum_epoch 165 | 166 | # ------------------------------------------------------------------- 167 | # Table: blocks 168 | # ------------------------------------------------------------------- 169 | blocks: 170 | seq: 171 | - id: key 172 | type: blocks_key 173 | - id: value 174 | type: blocks_value 175 | blocks_key: 176 | seq: 177 | - id: hash 178 | size: 32 179 | blocks_value: 180 | seq: 181 | - id: block_type 182 | type: u1 183 | enum: enum_blocktype 184 | - id: block_value 185 | type: 186 | switch-on: block_type 187 | cases: 188 | 'enum_blocktype::send': send_value 189 | 'enum_blocktype::receive': receive_value 190 | 'enum_blocktype::open': open_value 191 | 'enum_blocktype::change': change_value 192 | 'enum_blocktype::state': state_value 193 | _: ignore_until_eof 194 | 195 | change_value: 196 | seq: 197 | - id: block 198 | type: block_change 199 | - id: sideband 200 | type: change_sideband 201 | change_sideband: 202 | seq: 203 | - id: successor 204 | size: 32 205 | doc: Hash of successor block, if any 206 | - id: account 207 | size: 32 208 | doc: Public key to identify which account chain the block belongs to 209 | - id: height 210 | type: u8be 211 | doc: Block height, big endian 212 | - id: balance 213 | size: 16 214 | doc: 128 bit balance in raw 215 | - id: timestamp 216 | type: u8be 217 | doc: Unix epoch 218 | open_value: 219 | seq: 220 | - id: block 221 | type: block_open 222 | - id: sideband 223 | type: open_sideband 224 | open_sideband: 225 | seq: 226 | - id: successor 227 | size: 32 228 | doc: Hash of successor block, if any 229 | - id: balance 230 | size: 16 231 | doc: 128 bit balance in raw 232 | - id: timestamp 233 | type: u8be 234 | doc: Unix epoch, big endian 235 | receive_value: 236 | seq: 237 | - id: block 238 | type: block_receive 239 | - id: sideband 240 | type: receive_sideband 241 | receive_sideband: 242 | seq: 243 | - id: successor 244 | size: 32 245 | doc: Hash of successor block, if any 246 | - id: account 247 | size: 32 248 | doc: Public key to identify which account chain the block belongs to 249 | - id: height 250 | type: u8be 251 | doc: Block height, big endian 252 | - id: balance 253 | size: 16 254 | doc: 128 bit balance in raw 255 | - id: timestamp 256 | type: u8be 257 | doc: Unix epoch, big endian 258 | send_value: 259 | seq: 260 | - id: block 261 | type: block_send 262 | - id: sideband 263 | type: send_sideband 264 | send_sideband: 265 | seq: 266 | - id: successor 267 | size: 32 268 | doc: Hash of successor block, if any 269 | - id: account 270 | size: 32 271 | doc: Public key to identify which account chain the block belongs to 272 | - id: height 273 | type: u8be 274 | doc: Block height, big endian 275 | - id: timestamp 276 | type: u8be 277 | doc: Unix epoch, big endian 278 | state_value: 279 | seq: 280 | - id: block 281 | type: block_state 282 | - id: sideband 283 | type: state_sideband 284 | state_sideband: 285 | seq: 286 | - id: successor 287 | size: 32 288 | doc: Hash of successor block, if any 289 | - id: height 290 | type: u8be 291 | doc: Block height, big endian 292 | - id: timestamp 293 | type: u8be 294 | doc: Unix epoch, big endian 295 | - id: is_send 296 | type: b1 297 | - id: is_receive 298 | type: b1 299 | - id: is_epoch 300 | type: b1 301 | - id: epoch 302 | type: b5 303 | enum: enum_epoch 304 | doc: Which ledger epoch this block belongs to 305 | 306 | # ------------------------------------------------------------------- 307 | # Table: unchecked 308 | # ------------------------------------------------------------------- 309 | unchecked: 310 | seq: 311 | - id: key 312 | type: unchecked_key 313 | - id: value 314 | type: unchecked_value 315 | unchecked_key: 316 | doc: Key of the unchecked table 317 | seq: 318 | - id: previous 319 | size: 32 320 | - id: hash 321 | size: 32 322 | unchecked_value: 323 | doc: Information about an unchecked block 324 | seq: 325 | - id: block_type 326 | type: u1 327 | enum: enum_blocktype 328 | - id: block 329 | type: block_selector(block_type.to_i) 330 | - id: account 331 | size: 32 332 | - id: modified 333 | type: u8le 334 | - id: verified 335 | doc: Signature verification status 336 | type: u1 337 | enum: enum_signature_verification 338 | 339 | # ------------------------------------------------------------------- 340 | # Table: vote 341 | # ------------------------------------------------------------------- 342 | vote: 343 | seq: 344 | - id: key 345 | type: vote_key 346 | - id: value 347 | type: vote_value 348 | 349 | vote_key: 350 | doc: Key of the vote table 351 | seq: 352 | - id: account 353 | size: 32 354 | doc: Public key 355 | 356 | vote_value: 357 | doc: Vote and block(s) 358 | seq: 359 | - id: account 360 | size: 32 361 | - id: signature 362 | size: 64 363 | - id: sequence 364 | type: u8le 365 | - id: block_type 366 | type: u1 367 | enum: enum_blocktype 368 | - id: votebyhash 369 | if: block_type == enum_blocktype::not_a_block 370 | type: vote_by_hash 371 | - id: block 372 | if: block_type != enum_blocktype::not_a_block 373 | type: block_selector(block_type.to_i) 374 | 375 | vote_by_hash: 376 | doc: A sequence of up to 12 hashes, terminated by EOF. 377 | seq: 378 | - id: hashes 379 | type: vote_by_hash_entry(_index) 380 | repeat: until 381 | repeat-until: _index == 12 or _io.eof 382 | if: not _io.eof 383 | 384 | vote_by_hash_entry: 385 | doc: The serialized hash in VBH is prepended by not_a_block 386 | params: 387 | - id: idx 388 | type: u4 389 | seq: 390 | - id: block_type 391 | doc: Always enum_blocktype::not_a_block 392 | type: u1 393 | enum: enum_blocktype 394 | if: idx > 0 395 | - id: block_hash 396 | size: 32 397 | 398 | # ------------------------------------------------------------------- 399 | # Table: accounts 400 | # ------------------------------------------------------------------- 401 | accounts: 402 | seq: 403 | - id: key 404 | type: accounts_key 405 | - id: value 406 | type: accounts_value 407 | 408 | accounts_key: 409 | seq: 410 | - id: account 411 | size: 32 412 | doc: Public key 413 | 414 | accounts_value: 415 | seq: 416 | - id: head 417 | size: 32 418 | doc: Hash of head block 419 | - id: representative 420 | size: 32 421 | doc: Public key of account representative 422 | - id: open_block 423 | size: 32 424 | doc: Hash of the open block for this account 425 | - id: balance 426 | size: 16 427 | doc: 128 bit balance in raw 428 | - id: modified 429 | type: u8le 430 | doc: Last account modification, in seconds since unix epoch 431 | - id: block_count 432 | type: u8le 433 | doc: Number of blocks on the account chain 434 | 435 | # ------------------------------------------------------------------- 436 | # Block definitions 437 | # ------------------------------------------------------------------- 438 | 439 | block_send: 440 | seq: 441 | - id: previous 442 | size: 32 443 | doc: Hash of the previous block 444 | - id: destination 445 | size: 32 446 | doc: Public key of destination account 447 | - id: balance 448 | size: 16 449 | doc: 128-bit big endian balance 450 | - id: signature 451 | size: 64 452 | doc: ed25519 signature 453 | - id: work 454 | type: u8le 455 | doc: Proof of work 456 | 457 | block_receive: 458 | seq: 459 | - id: previous 460 | size: 32 461 | doc: Hash of the previous block 462 | - id: source 463 | size: 32 464 | doc: Hash of the source send block 465 | - id: signature 466 | size: 64 467 | doc: ed25519 signature 468 | - id: work 469 | type: u8le 470 | doc: Proof of work 471 | 472 | block_open: 473 | seq: 474 | - id: source 475 | size: 32 476 | doc: Hash of the source send block 477 | - id: representative 478 | size: 32 479 | doc: Public key of initial representative account 480 | - id: account 481 | size: 32 482 | doc: Public key of account being opened 483 | - id: signature 484 | size: 64 485 | doc: ed25519 signature 486 | - id: work 487 | type: u8le 488 | doc: Proof of work 489 | 490 | block_change: 491 | seq: 492 | - id: previous 493 | size: 32 494 | doc: Hash of the previous block 495 | - id: representative 496 | size: 32 497 | doc: Public key of new representative account 498 | - id: signature 499 | size: 64 500 | doc: ed25519 signature 501 | - id: work 502 | type: u8le 503 | doc: Proof of work 504 | 505 | block_state: 506 | seq: 507 | - id: account 508 | size: 32 509 | doc: Public key of account represented by this state block 510 | - id: previous 511 | size: 32 512 | doc: Hash of previous block 513 | - id: representative 514 | size: 32 515 | doc: Public key of the representative account 516 | - id: balance 517 | size: 16 518 | doc: 128-bit big endian balance 519 | - id: link 520 | size: 32 521 | doc: Pairing send's block hash (open/receive), 0 (change) or destination public key (send) 522 | - id: signature 523 | size: 64 524 | doc: ed25519 signature 525 | - id: work 526 | type: u8be 527 | doc: Proof of work (big endian) 528 | doc: State block 529 | 530 | # The block selector takes an integer argument representing the block type. 531 | # Note that enum arguments are not yet supported in kaitai, hence the to_i casts. 532 | block_selector: 533 | doc: Selects a block based on the argument 534 | params: 535 | - id: arg_block_type 536 | type: u1 537 | seq: 538 | - id: block 539 | type: 540 | switch-on: arg_block_type 541 | cases: 542 | 'enum_blocktype::send.to_i': block_send 543 | 'enum_blocktype::receive.to_i': block_receive 544 | 'enum_blocktype::open.to_i': block_open 545 | 'enum_blocktype::change.to_i': block_change 546 | 'enum_blocktype::state.to_i': block_state 547 | _: ignore_until_eof 548 | 549 | # Catch-all that ignores until eof 550 | ignore_until_eof: 551 | seq: 552 | - id: empty 553 | type: u1 554 | repeat: until 555 | repeat-until: _io.eof 556 | if: not _io.eof 557 | -------------------------------------------------------------------------------- /nanodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanocurrency/nanodb-specification/0b716f4e1c52c97918d6116f5d864848e786e813/nanodb.png -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /sample/nanodb.py: -------------------------------------------------------------------------------- 1 | # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 2 | 3 | from pkg_resources import parse_version 4 | from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO 5 | from enum import Enum 6 | 7 | 8 | if parse_version(ks_version) < parse_version('0.7'): 9 | raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version)) 10 | 11 | class Nanodb(KaitaiStruct): 12 | 13 | class EnumBlocktype(Enum): 14 | invalid = 0 15 | not_a_block = 1 16 | send = 2 17 | receive = 3 18 | open = 4 19 | change = 5 20 | state = 6 21 | 22 | class MetaKey(Enum): 23 | version = 1 24 | 25 | class DatabaseVersion(Enum): 26 | value = 17 27 | 28 | class EnumEpoch(Enum): 29 | invalid = 0 30 | unspecified = 1 31 | epoch_0 = 2 32 | epoch_1 = 3 33 | epoch_2 = 4 34 | 35 | class EnumSignatureVerification(Enum): 36 | unknown = 0 37 | invalid = 1 38 | valid = 2 39 | valid_epoch = 3 40 | def __init__(self, _io, _parent=None, _root=None): 41 | self._io = _io 42 | self._parent = _parent 43 | self._root = _root if _root else self 44 | self._read() 45 | 46 | def _read(self): 47 | pass 48 | 49 | class StateBlockSideband(KaitaiStruct): 50 | def __init__(self, _io, _parent=None, _root=None): 51 | self._io = _io 52 | self._parent = _parent 53 | self._root = _root if _root else self 54 | self._read() 55 | 56 | def _read(self): 57 | self.successor = self._io.read_bytes(32) 58 | self.height = self._io.read_u8be() 59 | self.timestamp = self._io.read_u8be() 60 | self.is_send = self._io.read_bits_int(1) != 0 61 | self.is_receive = self._io.read_bits_int(1) != 0 62 | self.is_epoch = self._io.read_bits_int(1) != 0 63 | self.epoch = self._root.EnumEpoch(self._io.read_bits_int(5)) 64 | 65 | 66 | class ReceiveKey(KaitaiStruct): 67 | def __init__(self, _io, _parent=None, _root=None): 68 | self._io = _io 69 | self._parent = _parent 70 | self._root = _root if _root else self 71 | self._read() 72 | 73 | def _read(self): 74 | self.hash = self._io.read_bytes(32) 75 | 76 | 77 | class BlockSend(KaitaiStruct): 78 | def __init__(self, _io, _parent=None, _root=None): 79 | self._io = _io 80 | self._parent = _parent 81 | self._root = _root if _root else self 82 | self._read() 83 | 84 | def _read(self): 85 | self.previous = self._io.read_bytes(32) 86 | self.destination = self._io.read_bytes(32) 87 | self.balance = self._io.read_bytes(16) 88 | self.signature = self._io.read_bytes(64) 89 | self.work = self._io.read_u8le() 90 | 91 | 92 | class Unchecked(KaitaiStruct): 93 | def __init__(self, _io, _parent=None, _root=None): 94 | self._io = _io 95 | self._parent = _parent 96 | self._root = _root if _root else self 97 | self._read() 98 | 99 | def _read(self): 100 | self.key = self._root.UncheckedKey(self._io, self, self._root) 101 | self.value = self._root.UncheckedValue(self._io, self, self._root) 102 | 103 | 104 | class StateBlocksKey(KaitaiStruct): 105 | def __init__(self, _io, _parent=None, _root=None): 106 | self._io = _io 107 | self._parent = _parent 108 | self._root = _root if _root else self 109 | self._read() 110 | 111 | def _read(self): 112 | self.hash = self._io.read_bytes(32) 113 | 114 | 115 | class MetaVersion(KaitaiStruct): 116 | """Value of key meta_key#version.""" 117 | def __init__(self, _io, _parent=None, _root=None): 118 | self._io = _io 119 | self._parent = _parent 120 | self._root = _root if _root else self 121 | self._read() 122 | 123 | def _read(self): 124 | self.database_version = self._io.read_bytes(32) 125 | 126 | 127 | class OpenKey(KaitaiStruct): 128 | def __init__(self, _io, _parent=None, _root=None): 129 | self._io = _io 130 | self._parent = _parent 131 | self._root = _root if _root else self 132 | self._read() 133 | 134 | def _read(self): 135 | self.hash = self._io.read_bytes(32) 136 | 137 | 138 | class VoteValue(KaitaiStruct): 139 | """Vote and block(s).""" 140 | def __init__(self, _io, _parent=None, _root=None): 141 | self._io = _io 142 | self._parent = _parent 143 | self._root = _root if _root else self 144 | self._read() 145 | 146 | def _read(self): 147 | self.account = self._io.read_bytes(32) 148 | self.signature = self._io.read_bytes(64) 149 | self.sequence = self._io.read_u8le() 150 | self.block_type = self._root.EnumBlocktype(self._io.read_u1()) 151 | if self.block_type == self._root.EnumBlocktype.not_a_block: 152 | self.votebyhash = self._root.VoteByHash(self._io, self, self._root) 153 | 154 | if self.block_type != self._root.EnumBlocktype.not_a_block: 155 | self.block = self._root.BlockSelector(self.block_type.value, self._io, self, self._root) 156 | 157 | 158 | 159 | class FrontiersKey(KaitaiStruct): 160 | def __init__(self, _io, _parent=None, _root=None): 161 | self._io = _io 162 | self._parent = _parent 163 | self._root = _root if _root else self 164 | self._read() 165 | 166 | def _read(self): 167 | self.hash = self._io.read_bytes(32) 168 | 169 | 170 | class BlockSelector(KaitaiStruct): 171 | """Selects a block based on the argument.""" 172 | def __init__(self, arg_block_type, _io, _parent=None, _root=None): 173 | self._io = _io 174 | self._parent = _parent 175 | self._root = _root if _root else self 176 | self.arg_block_type = arg_block_type 177 | self._read() 178 | 179 | def _read(self): 180 | _on = self.arg_block_type 181 | if _on == self._root.EnumBlocktype.open.value: 182 | self.block = self._root.BlockOpen(self._io, self, self._root) 183 | elif _on == self._root.EnumBlocktype.state.value: 184 | self.block = self._root.BlockState(self._io, self, self._root) 185 | elif _on == self._root.EnumBlocktype.receive.value: 186 | self.block = self._root.BlockReceive(self._io, self, self._root) 187 | elif _on == self._root.EnumBlocktype.send.value: 188 | self.block = self._root.BlockSend(self._io, self, self._root) 189 | elif _on == self._root.EnumBlocktype.change.value: 190 | self.block = self._root.BlockChange(self._io, self, self._root) 191 | else: 192 | self.block = self._root.IgnoreUntilEof(self._io, self, self._root) 193 | 194 | 195 | class BlockReceive(KaitaiStruct): 196 | def __init__(self, _io, _parent=None, _root=None): 197 | self._io = _io 198 | self._parent = _parent 199 | self._root = _root if _root else self 200 | self._read() 201 | 202 | def _read(self): 203 | self.previous = self._io.read_bytes(32) 204 | self.source = self._io.read_bytes(32) 205 | self.signature = self._io.read_bytes(64) 206 | self.work = self._io.read_u8le() 207 | 208 | 209 | class MetaKey(KaitaiStruct): 210 | def __init__(self, _io, _parent=None, _root=None): 211 | self._io = _io 212 | self._parent = _parent 213 | self._root = _root if _root else self 214 | self._read() 215 | 216 | def _read(self): 217 | self.key = self._io.read_bytes(32) 218 | 219 | 220 | class BlockChange(KaitaiStruct): 221 | def __init__(self, _io, _parent=None, _root=None): 222 | self._io = _io 223 | self._parent = _parent 224 | self._root = _root if _root else self 225 | self._read() 226 | 227 | def _read(self): 228 | self.previous = self._io.read_bytes(32) 229 | self.representative = self._io.read_bytes(32) 230 | self.signature = self._io.read_bytes(64) 231 | self.work = self._io.read_u8le() 232 | 233 | 234 | class PendingKey(KaitaiStruct): 235 | def __init__(self, _io, _parent=None, _root=None): 236 | self._io = _io 237 | self._parent = _parent 238 | self._root = _root if _root else self 239 | self._read() 240 | 241 | def _read(self): 242 | self.account = self._io.read_bytes(32) 243 | self.hash = self._io.read_bytes(32) 244 | 245 | 246 | class ChangeKey(KaitaiStruct): 247 | def __init__(self, _io, _parent=None, _root=None): 248 | self._io = _io 249 | self._parent = _parent 250 | self._root = _root if _root else self 251 | self._read() 252 | 253 | def _read(self): 254 | self.hash = self._io.read_bytes(32) 255 | 256 | 257 | class PendingValue(KaitaiStruct): 258 | def __init__(self, _io, _parent=None, _root=None): 259 | self._io = _io 260 | self._parent = _parent 261 | self._root = _root if _root else self 262 | self._read() 263 | 264 | def _read(self): 265 | self.source = self._io.read_bytes(32) 266 | self.amount = self._io.read_bytes(16) 267 | self.epoch = self._root.EnumEpoch(self._io.read_u1()) 268 | 269 | 270 | class VoteKey(KaitaiStruct): 271 | """Key of the vote table.""" 272 | def __init__(self, _io, _parent=None, _root=None): 273 | self._io = _io 274 | self._parent = _parent 275 | self._root = _root if _root else self 276 | self._read() 277 | 278 | def _read(self): 279 | self.account = self._io.read_bytes(32) 280 | 281 | 282 | class Receive(KaitaiStruct): 283 | def __init__(self, _io, _parent=None, _root=None): 284 | self._io = _io 285 | self._parent = _parent 286 | self._root = _root if _root else self 287 | self._read() 288 | 289 | def _read(self): 290 | self.key = self._root.ReceiveKey(self._io, self, self._root) 291 | self.value = self._root.ReceiveValue(self._io, self, self._root) 292 | 293 | 294 | class AccountsKey(KaitaiStruct): 295 | def __init__(self, _io, _parent=None, _root=None): 296 | self._io = _io 297 | self._parent = _parent 298 | self._root = _root if _root else self 299 | self._read() 300 | 301 | def _read(self): 302 | self.account = self._io.read_bytes(32) 303 | 304 | 305 | class Frontiers(KaitaiStruct): 306 | """Mapping from block hash to account. This is not used after epoch 1.""" 307 | def __init__(self, _io, _parent=None, _root=None): 308 | self._io = _io 309 | self._parent = _parent 310 | self._root = _root if _root else self 311 | self._read() 312 | 313 | def _read(self): 314 | self.key = self._root.FrontiersKey(self._io, self, self._root) 315 | self.value = self._root.FrontiersValue(self._io, self, self._root) 316 | 317 | 318 | class AccountsValue(KaitaiStruct): 319 | def __init__(self, _io, _parent=None, _root=None): 320 | self._io = _io 321 | self._parent = _parent 322 | self._root = _root if _root else self 323 | self._read() 324 | 325 | def _read(self): 326 | self.head = self._io.read_bytes(32) 327 | self.representative = self._io.read_bytes(32) 328 | self.open_block = self._io.read_bytes(32) 329 | self.balance = self._io.read_bytes(16) 330 | self.modified = self._io.read_u8le() 331 | self.block_count = self._io.read_u8le() 332 | 333 | 334 | class OnlineWeightValue(KaitaiStruct): 335 | def __init__(self, _io, _parent=None, _root=None): 336 | self._io = _io 337 | self._parent = _parent 338 | self._root = _root if _root else self 339 | self._read() 340 | 341 | def _read(self): 342 | self.amount = self._io.read_bytes(16) 343 | 344 | 345 | class FrontiersValue(KaitaiStruct): 346 | def __init__(self, _io, _parent=None, _root=None): 347 | self._io = _io 348 | self._parent = _parent 349 | self._root = _root if _root else self 350 | self._read() 351 | 352 | def _read(self): 353 | self.account = self._io.read_bytes(32) 354 | 355 | 356 | class ReceiveValue(KaitaiStruct): 357 | def __init__(self, _io, _parent=None, _root=None): 358 | self._io = _io 359 | self._parent = _parent 360 | self._root = _root if _root else self 361 | self._read() 362 | 363 | def _read(self): 364 | self.block = self._root.BlockReceive(self._io, self, self._root) 365 | self.sideband = self._root.ReceiveSideband(self._io, self, self._root) 366 | 367 | 368 | class Open(KaitaiStruct): 369 | def __init__(self, _io, _parent=None, _root=None): 370 | self._io = _io 371 | self._parent = _parent 372 | self._root = _root if _root else self 373 | self._read() 374 | 375 | def _read(self): 376 | self.key = self._root.OpenKey(self._io, self, self._root) 377 | self.value = self._root.OpenValue(self._io, self, self._root) 378 | 379 | 380 | class StateBlocksValue(KaitaiStruct): 381 | def __init__(self, _io, _parent=None, _root=None): 382 | self._io = _io 383 | self._parent = _parent 384 | self._root = _root if _root else self 385 | self._read() 386 | 387 | def _read(self): 388 | self.block = self._root.BlockState(self._io, self, self._root) 389 | self.sideband = self._root.StateBlockSideband(self._io, self, self._root) 390 | 391 | 392 | class VoteByHashEntry(KaitaiStruct): 393 | """The serialized hash in VBH is prepended by not_a_block.""" 394 | def __init__(self, idx, _io, _parent=None, _root=None): 395 | self._io = _io 396 | self._parent = _parent 397 | self._root = _root if _root else self 398 | self.idx = idx 399 | self._read() 400 | 401 | def _read(self): 402 | if self.idx > 0: 403 | self.block_type = self._root.EnumBlocktype(self._io.read_u1()) 404 | 405 | self.block_hash = self._io.read_bytes(32) 406 | 407 | 408 | class BlockOpen(KaitaiStruct): 409 | def __init__(self, _io, _parent=None, _root=None): 410 | self._io = _io 411 | self._parent = _parent 412 | self._root = _root if _root else self 413 | self._read() 414 | 415 | def _read(self): 416 | self.source = self._io.read_bytes(32) 417 | self.representative = self._io.read_bytes(32) 418 | self.account = self._io.read_bytes(32) 419 | self.signature = self._io.read_bytes(64) 420 | self.work = self._io.read_u8le() 421 | 422 | 423 | class IgnoreUntilEof(KaitaiStruct): 424 | def __init__(self, _io, _parent=None, _root=None): 425 | self._io = _io 426 | self._parent = _parent 427 | self._root = _root if _root else self 428 | self._read() 429 | 430 | def _read(self): 431 | if not (self._io.is_eof()): 432 | self.empty = [] 433 | i = 0 434 | while True: 435 | _ = self._io.read_u1() 436 | self.empty.append(_) 437 | if self._io.is_eof(): 438 | break 439 | i += 1 440 | 441 | 442 | 443 | class Change(KaitaiStruct): 444 | def __init__(self, _io, _parent=None, _root=None): 445 | self._io = _io 446 | self._parent = _parent 447 | self._root = _root if _root else self 448 | self._read() 449 | 450 | def _read(self): 451 | self.key = self._root.ChangeKey(self._io, self, self._root) 452 | self.value = self._root.ChangeValue(self._io, self, self._root) 453 | 454 | 455 | class StateBlocks(KaitaiStruct): 456 | def __init__(self, _io, _parent=None, _root=None): 457 | self._io = _io 458 | self._parent = _parent 459 | self._root = _root if _root else self 460 | self._read() 461 | 462 | def _read(self): 463 | self.key = self._root.StateBlocksKey(self._io, self, self._root) 464 | self.value = self._root.StateBlocksValue(self._io, self, self._root) 465 | 466 | 467 | class SendValue(KaitaiStruct): 468 | def __init__(self, _io, _parent=None, _root=None): 469 | self._io = _io 470 | self._parent = _parent 471 | self._root = _root if _root else self 472 | self._read() 473 | 474 | def _read(self): 475 | self.block = self._root.BlockSend(self._io, self, self._root) 476 | self.sideband = self._root.SendSideband(self._io, self, self._root) 477 | 478 | 479 | class UncheckedKey(KaitaiStruct): 480 | """Key of the unchecked table.""" 481 | def __init__(self, _io, _parent=None, _root=None): 482 | self._io = _io 483 | self._parent = _parent 484 | self._root = _root if _root else self 485 | self._read() 486 | 487 | def _read(self): 488 | self.previous = self._io.read_bytes(32) 489 | self.hash = self._io.read_bytes(32) 490 | 491 | 492 | class OnlineWeightKey(KaitaiStruct): 493 | def __init__(self, _io, _parent=None, _root=None): 494 | self._io = _io 495 | self._parent = _parent 496 | self._root = _root if _root else self 497 | self._read() 498 | 499 | def _read(self): 500 | self.timestamp = self._io.read_u8be() 501 | 502 | 503 | class ConfirmationHeight(KaitaiStruct): 504 | """Confirmed height per account.""" 505 | def __init__(self, _io, _parent=None, _root=None): 506 | self._io = _io 507 | self._parent = _parent 508 | self._root = _root if _root else self 509 | self._read() 510 | 511 | def _read(self): 512 | self.key = self._root.ConfirmationHeightKey(self._io, self, self._root) 513 | self.value = self._root.ConfirmationHeightValue(self._io, self, self._root) 514 | 515 | 516 | class ChangeSideband(KaitaiStruct): 517 | def __init__(self, _io, _parent=None, _root=None): 518 | self._io = _io 519 | self._parent = _parent 520 | self._root = _root if _root else self 521 | self._read() 522 | 523 | def _read(self): 524 | self.successor = self._io.read_bytes(32) 525 | self.account = self._io.read_bytes(32) 526 | self.height = self._io.read_u8be() 527 | self.balance = self._io.read_bytes(16) 528 | self.timestamp = self._io.read_u8be() 529 | 530 | 531 | class OpenSideband(KaitaiStruct): 532 | def __init__(self, _io, _parent=None, _root=None): 533 | self._io = _io 534 | self._parent = _parent 535 | self._root = _root if _root else self 536 | self._read() 537 | 538 | def _read(self): 539 | self.successor = self._io.read_bytes(32) 540 | self.balance = self._io.read_bytes(16) 541 | self.timestamp = self._io.read_u8be() 542 | 543 | 544 | class ConfirmationHeightKey(KaitaiStruct): 545 | def __init__(self, _io, _parent=None, _root=None): 546 | self._io = _io 547 | self._parent = _parent 548 | self._root = _root if _root else self 549 | self._read() 550 | 551 | def _read(self): 552 | self.account = self._io.read_bytes(32) 553 | 554 | 555 | class ConfirmationHeightValue(KaitaiStruct): 556 | def __init__(self, _io, _parent=None, _root=None): 557 | self._io = _io 558 | self._parent = _parent 559 | self._root = _root if _root else self 560 | self._read() 561 | 562 | def _read(self): 563 | self.height = self._io.read_u8le() 564 | self.frontier = self._io.read_bytes(32) 565 | 566 | 567 | class SendSideband(KaitaiStruct): 568 | def __init__(self, _io, _parent=None, _root=None): 569 | self._io = _io 570 | self._parent = _parent 571 | self._root = _root if _root else self 572 | self._read() 573 | 574 | def _read(self): 575 | self.successor = self._io.read_bytes(32) 576 | self.account = self._io.read_bytes(32) 577 | self.height = self._io.read_u8be() 578 | self.timestamp = self._io.read_u8be() 579 | 580 | 581 | class BlockState(KaitaiStruct): 582 | """State block.""" 583 | def __init__(self, _io, _parent=None, _root=None): 584 | self._io = _io 585 | self._parent = _parent 586 | self._root = _root if _root else self 587 | self._read() 588 | 589 | def _read(self): 590 | self.account = self._io.read_bytes(32) 591 | self.previous = self._io.read_bytes(32) 592 | self.representative = self._io.read_bytes(32) 593 | self.balance = self._io.read_bytes(16) 594 | self.link = self._io.read_bytes(32) 595 | self.signature = self._io.read_bytes(64) 596 | self.work = self._io.read_u8be() 597 | 598 | 599 | class OpenValue(KaitaiStruct): 600 | def __init__(self, _io, _parent=None, _root=None): 601 | self._io = _io 602 | self._parent = _parent 603 | self._root = _root if _root else self 604 | self._read() 605 | 606 | def _read(self): 607 | self.block = self._root.BlockOpen(self._io, self, self._root) 608 | self.sideband = self._root.OpenSideband(self._io, self, self._root) 609 | 610 | 611 | class ReceiveSideband(KaitaiStruct): 612 | def __init__(self, _io, _parent=None, _root=None): 613 | self._io = _io 614 | self._parent = _parent 615 | self._root = _root if _root else self 616 | self._read() 617 | 618 | def _read(self): 619 | self.successor = self._io.read_bytes(32) 620 | self.account = self._io.read_bytes(32) 621 | self.height = self._io.read_u8be() 622 | self.balance = self._io.read_bytes(16) 623 | self.timestamp = self._io.read_u8be() 624 | 625 | 626 | class ChangeValue(KaitaiStruct): 627 | def __init__(self, _io, _parent=None, _root=None): 628 | self._io = _io 629 | self._parent = _parent 630 | self._root = _root if _root else self 631 | self._read() 632 | 633 | def _read(self): 634 | self.block = self._root.BlockChange(self._io, self, self._root) 635 | self.sideband = self._root.ChangeSideband(self._io, self, self._root) 636 | 637 | 638 | class VoteByHash(KaitaiStruct): 639 | """A sequence of up to 12 hashes, terminated by EOF.""" 640 | def __init__(self, _io, _parent=None, _root=None): 641 | self._io = _io 642 | self._parent = _parent 643 | self._root = _root if _root else self 644 | self._read() 645 | 646 | def _read(self): 647 | if not (self._io.is_eof()): 648 | self.hashes = [] 649 | i = 0 650 | while True: 651 | _ = self._root.VoteByHashEntry(i, self._io, self, self._root) 652 | self.hashes.append(_) 653 | if ((i == 12) or (self._io.is_eof())) : 654 | break 655 | i += 1 656 | 657 | 658 | 659 | class Peers(KaitaiStruct): 660 | """Peer cache table. All data is stored in the key.""" 661 | def __init__(self, _io, _parent=None, _root=None): 662 | self._io = _io 663 | self._parent = _parent 664 | self._root = _root if _root else self 665 | self._read() 666 | 667 | def _read(self): 668 | self.address = self._io.read_bytes(16) 669 | self.port = self._io.read_u2be() 670 | 671 | 672 | class Vote(KaitaiStruct): 673 | def __init__(self, _io, _parent=None, _root=None): 674 | self._io = _io 675 | self._parent = _parent 676 | self._root = _root if _root else self 677 | self._read() 678 | 679 | def _read(self): 680 | self.key = self._root.VoteKey(self._io, self, self._root) 681 | self.value = self._root.VoteValue(self._io, self, self._root) 682 | 683 | 684 | class Pending(KaitaiStruct): 685 | """Pending table.""" 686 | def __init__(self, _io, _parent=None, _root=None): 687 | self._io = _io 688 | self._parent = _parent 689 | self._root = _root if _root else self 690 | self._read() 691 | 692 | def _read(self): 693 | self.key = self._root.PendingKey(self._io, self, self._root) 694 | self.value = self._root.PendingValue(self._io, self, self._root) 695 | 696 | 697 | class SendKey(KaitaiStruct): 698 | def __init__(self, _io, _parent=None, _root=None): 699 | self._io = _io 700 | self._parent = _parent 701 | self._root = _root if _root else self 702 | self._read() 703 | 704 | def _read(self): 705 | self.hash = self._io.read_bytes(32) 706 | 707 | 708 | class Accounts(KaitaiStruct): 709 | def __init__(self, _io, _parent=None, _root=None): 710 | self._io = _io 711 | self._parent = _parent 712 | self._root = _root if _root else self 713 | self._read() 714 | 715 | def _read(self): 716 | self.key = self._root.AccountsKey(self._io, self, self._root) 717 | self.value = self._root.AccountsValue(self._io, self, self._root) 718 | 719 | 720 | class UncheckedValue(KaitaiStruct): 721 | """Information about an unchecked block.""" 722 | def __init__(self, _io, _parent=None, _root=None): 723 | self._io = _io 724 | self._parent = _parent 725 | self._root = _root if _root else self 726 | self._read() 727 | 728 | def _read(self): 729 | self.block_type = self._root.EnumBlocktype(self._io.read_u1()) 730 | self.block = self._root.BlockSelector(self.block_type.value, self._io, self, self._root) 731 | self.account = self._io.read_bytes(32) 732 | self.modified = self._io.read_u8le() 733 | self.verified = self._root.EnumSignatureVerification(self._io.read_u1()) 734 | 735 | 736 | class OnlineWeight(KaitaiStruct): 737 | """Stores online weight trended over time.""" 738 | def __init__(self, _io, _parent=None, _root=None): 739 | self._io = _io 740 | self._parent = _parent 741 | self._root = _root if _root else self 742 | self._read() 743 | 744 | def _read(self): 745 | self.key = self._root.OnlineWeightKey(self._io, self, self._root) 746 | self.value = self._root.OnlineWeightValue(self._io, self, self._root) 747 | 748 | 749 | class Send(KaitaiStruct): 750 | def __init__(self, _io, _parent=None, _root=None): 751 | self._io = _io 752 | self._parent = _parent 753 | self._root = _root if _root else self 754 | self._read() 755 | 756 | def _read(self): 757 | self.key = self._root.SendKey(self._io, self, self._root) 758 | self.value = self._root.SendValue(self._io, self, self._root) 759 | -------------------------------------------------------------------------------- /sample/read.py: -------------------------------------------------------------------------------- 1 | import io 2 | import sys 3 | import os.path 4 | import datetime 5 | import argparse 6 | import ipaddress 7 | import lmdb 8 | import nanolib 9 | from nanodb import Nanodb 10 | from kaitaistruct import KaitaiStream 11 | 12 | def print_state_block(block, level): 13 | print('{}account : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.account.hex()))) 14 | print('{}representative : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.representative.hex()))) 15 | print('{}previous : {}'.format(" "*level, block.previous.hex().upper())) 16 | print('{}balance : {}'.format(" "*level, nanolib.blocks.parse_hex_balance(block.balance.hex().upper()))) 17 | print('{}link : {}'.format(" "*level, block.link.hex().upper())) 18 | print('{}link as account : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.link.hex()))) 19 | print('{}signature : {}'.format(" "*level, block.signature.hex().upper())) 20 | print('{}work : {}'.format(" "*level, hex(block.work))) 21 | 22 | def print_send_block(block, level): 23 | print('{}previous : {}'.format(" "*level, block.previous.hex().upper())) 24 | print('{}destination : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.destination.hex()))) 25 | print('{}balance : {}'.format(" "*level, nanolib.blocks.parse_hex_balance(block.balance.hex().upper()))) 26 | print('{}signature : {}'.format(" "*level, block.signature.hex().upper())) 27 | print('{}work : {}'.format(" "*level, hex(block.work))) 28 | 29 | def print_receive_block(block, level): 30 | print('{}previous : {}'.format(" "*level, block.previous.hex().upper())) 31 | print('{}source hash : {}'.format(" "*level, block.source.hex().upper())) 32 | print('{}signature : {}'.format(" "*level, block.signature.hex().upper())) 33 | print('{}work : {}'.format(" "*level, hex(block.work))) 34 | 35 | def print_open_block(block, level): 36 | print('{}account : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.account.hex()))) 37 | print('{}source hash : {}'.format(" "*level, block.source.hex().upper())) 38 | print('{}representative : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.representative.hex()))) 39 | print('{}signature : {}'.format(" "*level, block.signature.hex().upper())) 40 | print('{}work : {}'.format(" "*level, hex(block.work))) 41 | 42 | def print_change_block(block, level): 43 | print('{}previous : {}'.format(" "*level, block.previous.hex().upper())) 44 | print('{}representative : {}'.format(" "*level, nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=block.representative.hex()))) 45 | print('{}signature : {}'.format(" "*level, block.signature.hex().upper())) 46 | print('{}work : {}'.format(" "*level, hex(block.work))) 47 | 48 | def print_header(header): 49 | print(header) 50 | print("-"*len(header)) 51 | 52 | # Parse arguments 53 | parser = argparse.ArgumentParser() 54 | parser.add_argument("--filename", type=str, help="Path to the data.ldb file (not directory). If omitted, data.ldb is assumed to be in the current directory") 55 | parser.add_argument("--table", type=str, default='all', help="Name of table to dump, or all to dump all tables.") 56 | parser.add_argument("--count", type=int, default=10, help="Number of entries to display from the table(s)") 57 | parser.add_argument("--key", type=str, help="Start iterating at this exact key. This must be a byte array in hex representation.") 58 | args = parser.parse_args() 59 | 60 | try: 61 | # Override database filename 62 | filename = 'data.ldb' 63 | if args.filename: 64 | filename = args.filename 65 | if not os.path.isfile(filename): 66 | raise Exception("Database doesn't exist") 67 | 68 | env = lmdb.open(filename, subdir=False, max_dbs=100) 69 | 70 | # Accounts table 71 | # Print details about the first N accounts with balance 72 | if args.table == 'all' or args.table == 'accounts': 73 | print_header('accounts') 74 | accounts_db = env.open_db('accounts'.encode()) 75 | 76 | count = 0 77 | with env.begin() as txn: 78 | cursor = txn.cursor(accounts_db) 79 | for key, value in cursor: 80 | 81 | keystream = KaitaiStream(io.BytesIO(key)) 82 | valstream = KaitaiStream(io.BytesIO(value)) 83 | 84 | account_key = Nanodb.AccountsKey(keystream) 85 | account_info = Nanodb.AccountsValue(valstream) 86 | 87 | balance = nanolib.blocks.parse_hex_balance(account_info.balance.hex().upper()) 88 | 89 | if balance > 0: 90 | print('account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=account_key.account.hex()))) 91 | print(' head block : {}'.format(account_info.head.hex().upper())) 92 | print(' open block : {}'.format(account_info.open_block.hex().upper())) 93 | print(' balance : {}'.format(balance)) 94 | print(' block count : {}'.format(account_info.block_count)) 95 | print(' last modified : {}'.format(datetime.datetime.utcfromtimestamp(account_info.modified))) 96 | print(' representative : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=account_info.representative.hex()))) 97 | print('') 98 | 99 | count+=1 100 | if count >= args.count: 101 | break 102 | cursor.close() 103 | if count == 0: 104 | print('(empty)\n') 105 | 106 | # Send table 107 | if args.table == 'all' or args.table == 'send': 108 | print_header('send') 109 | state_db = env.open_db('send'.encode()) 110 | 111 | count = 0 112 | with env.begin() as txn: 113 | cursor = txn.cursor(state_db) 114 | if args.key: 115 | cursor.set_key(bytearray.fromhex(args.key)) 116 | count = 0 117 | for key, value in cursor: 118 | keystream = KaitaiStream(io.BytesIO(key)) 119 | valstream = KaitaiStream(io.BytesIO(value)) 120 | send_key = Nanodb.SendKey(keystream) 121 | send_block = Nanodb.SendValue(valstream, None, Nanodb(None)) 122 | 123 | print('hash : {}'.format(send_key.hash.hex().upper())) 124 | print_send_block(send_block.block, 2) 125 | print(' sideband:') 126 | print(' successor : {}'.format(send_block.sideband.successor.hex().upper())) 127 | print(' account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=send_block.sideband.account.hex()))) 128 | print(' height : {}'.format(send_block.sideband.height)) 129 | print(' timestamp : {}'.format(datetime.datetime.utcfromtimestamp(send_block.sideband.timestamp))) 130 | print('') 131 | 132 | count+=1 133 | if count >= args.count: 134 | break 135 | cursor.close() 136 | if count == 0: 137 | print('(empty)\n') 138 | 139 | # Receive table 140 | if args.table == 'all' or args.table == 'receive': 141 | print_header('receive') 142 | state_db = env.open_db('receive'.encode()) 143 | 144 | count = 0 145 | with env.begin() as txn: 146 | cursor = txn.cursor(state_db) 147 | if args.key: 148 | cursor.set_key(bytearray.fromhex(args.key)) 149 | count = 0 150 | for key, value in cursor: 151 | keystream = KaitaiStream(io.BytesIO(key)) 152 | valstream = KaitaiStream(io.BytesIO(value)) 153 | receive_key = Nanodb.ReceiveKey(keystream) 154 | receive_block = Nanodb.ReceiveValue(valstream, None, Nanodb(None)) 155 | 156 | print('hash : {}'.format(receive_key.hash.hex().upper())) 157 | print_receive_block(receive_block.block, 2) 158 | print(' sideband:') 159 | print(' successor : {}'.format(receive_block.sideband.successor.hex().upper())) 160 | print(' account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=receive_block.sideband.account.hex()))) 161 | print(' height : {}'.format(receive_block.sideband.height)) 162 | print(' balance : {}'.format(nanolib.blocks.parse_hex_balance(receive_block.sideband.balance.hex().upper()))) 163 | print(' timestamp : {}'.format(datetime.datetime.utcfromtimestamp(receive_block.sideband.timestamp))) 164 | print('') 165 | 166 | count+=1 167 | if count >= args.count: 168 | break 169 | cursor.close() 170 | if count == 0: 171 | print('(empty)\n') 172 | 173 | # Open table 174 | if args.table == 'all' or args.table == 'open': 175 | print_header('open') 176 | state_db = env.open_db('open'.encode()) 177 | 178 | count = 0 179 | with env.begin() as txn: 180 | cursor = txn.cursor(state_db) 181 | if args.key: 182 | cursor.set_key(bytearray.fromhex(args.key)) 183 | count = 0 184 | for key, value in cursor: 185 | keystream = KaitaiStream(io.BytesIO(key)) 186 | valstream = KaitaiStream(io.BytesIO(value)) 187 | open_key = Nanodb.OpenKey(keystream) 188 | open_block = Nanodb.OpenValue(valstream, None, Nanodb(None)) 189 | 190 | print('hash : {}'.format(open_key.hash.hex().upper())) 191 | print_open_block(open_block.block, 2) 192 | print(' sideband:') 193 | print(' successor : {}'.format(open_block.sideband.successor.hex().upper())) 194 | print(' balance : {}'.format(nanolib.blocks.parse_hex_balance(open_block.sideband.balance.hex().upper()))) 195 | print(' timestamp : {}'.format(datetime.datetime.utcfromtimestamp(open_block.sideband.timestamp))) 196 | print('') 197 | 198 | count+=1 199 | if count >= args.count: 200 | break 201 | cursor.close() 202 | if count == 0: 203 | print('(empty)\n') 204 | 205 | # Change table 206 | if args.table == 'all' or args.table == 'change': 207 | print_header('change') 208 | state_db = env.open_db('change'.encode()) 209 | 210 | count = 0 211 | with env.begin() as txn: 212 | cursor = txn.cursor(state_db) 213 | if args.key: 214 | cursor.set_key(bytearray.fromhex(args.key)) 215 | count = 0 216 | for key, value in cursor: 217 | keystream = KaitaiStream(io.BytesIO(key)) 218 | valstream = KaitaiStream(io.BytesIO(value)) 219 | change_key = Nanodb.ChangeKey(keystream) 220 | change_block = Nanodb.ChangeValue(valstream, None, Nanodb(None)) 221 | 222 | print('hash : {}'.format(change_key.hash.hex().upper())) 223 | print_change_block(change_block.block, 2) 224 | print(' sideband:') 225 | print(' successor : {}'.format(change_block.sideband.successor.hex().upper())) 226 | print(' account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=change_block.sideband.account.hex()))) 227 | print(' height : {}'.format(change_block.sideband.height)) 228 | print(' balance : {}'.format(nanolib.blocks.parse_hex_balance(change_block.sideband.balance.hex().upper()))) 229 | print(' timestamp : {}'.format(datetime.datetime.utcfromtimestamp(change_block.sideband.timestamp))) 230 | print('') 231 | 232 | count+=1 233 | if count >= args.count: 234 | break 235 | cursor.close() 236 | if count == 0: 237 | print('(empty)\n') 238 | 239 | # State blocks table 240 | if args.table == 'all' or args.table == 'state_blocks': 241 | print_header('state_blocks') 242 | state_db = env.open_db('state_blocks'.encode()) 243 | 244 | count = 0 245 | with env.begin() as txn: 246 | cursor = txn.cursor(state_db) 247 | if args.key: 248 | cursor.set_key(bytearray.fromhex(args.key)) 249 | count = 0 250 | for key, value in cursor: 251 | keystream = KaitaiStream(io.BytesIO(key)) 252 | valstream = KaitaiStream(io.BytesIO(value)) 253 | state_key = Nanodb.StateBlocksKey(keystream) 254 | state_block = Nanodb.StateBlocksValue(valstream, None, Nanodb(None)) 255 | 256 | print('hash : {}'.format(state_key.hash.hex().upper())) 257 | print_state_block(state_block.block, 2) 258 | print(' sideband:') 259 | print(' successor : {}'.format(state_block.sideband.successor.hex().upper())) 260 | print(' height : {}'.format(state_block.sideband.height)) 261 | print(' timestamp : {}'.format(datetime.datetime.utcfromtimestamp(state_block.sideband.timestamp))) 262 | print(' is_send : {}'.format(state_block.sideband.is_send)) 263 | print(' is_receive : {}'.format(state_block.sideband.is_receive)) 264 | print(' is_epoch : {}'.format(state_block.sideband.is_epoch)) 265 | print(' epoch : {}'.format(state_block.sideband.epoch.name)) 266 | print('') 267 | 268 | count+=1 269 | if count >= args.count: 270 | break 271 | cursor.close() 272 | if count == 0: 273 | print('(empty)\n') 274 | 275 | # Vote table 276 | if args.table == 'all' or args.table == 'vote': 277 | print_header('vote') 278 | vote_db = env.open_db('vote'.encode()) 279 | 280 | count = 0 281 | with env.begin() as txn: 282 | cursor = txn.cursor(vote_db) 283 | count = 0 284 | for key, value in cursor: 285 | 286 | keystream = KaitaiStream(io.BytesIO(key)) 287 | valstream = KaitaiStream(io.BytesIO(value)) 288 | vote_key = Nanodb.VoteKey(keystream) 289 | vote_info = Nanodb.VoteValue(valstream, None, Nanodb(None)) 290 | 291 | print('vote account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=vote_key.account.hex()))) 292 | print(' signature : {}'.format(vote_info.signature.hex().upper())) 293 | print(' sequence : {}'.format(vote_info.sequence)) 294 | print(' block type : {}'.format(vote_info.block_type.name)) 295 | if vote_info.block_type == Nanodb.EnumBlocktype.not_a_block: 296 | print(' vbh hashes:') 297 | for entry in vote_info.votebyhash.hashes: 298 | print(' {}'.format(entry.block_hash.hex().upper())) 299 | elif vote_info.block_type == Nanodb.EnumBlocktype.state: 300 | print_state_block(vote_info.block.block, 2) 301 | elif vote_info.block_type == Nanodb.EnumBlocktype.receive: 302 | print_receive_block(vote_info.block.block, 2) 303 | elif vote_info.block_type == Nanodb.EnumBlocktype.send: 304 | print_send_block(vote_info.block.block, 2) 305 | elif vote_info.block_type == Nanodb.EnumBlocktype.open: 306 | print_open_block(vote_info.block.block, 2) 307 | elif vote_info.block_type == Nanodb.EnumBlocktype.change: 308 | print_change_block(vote_info.block.block, 2) 309 | print('') 310 | 311 | count+=1 312 | if count >= args.count: 313 | break 314 | cursor.close() 315 | if count == 0: 316 | print('(empty)\n') 317 | 318 | # Unchecked table 319 | if args.table == 'all' or args.table == 'unchecked': 320 | print_header('unchecked') 321 | state_db = env.open_db('unchecked'.encode()) 322 | 323 | count = 0 324 | with env.begin() as txn: 325 | cursor = txn.cursor(state_db) 326 | if args.key: 327 | cursor.set_key(bytearray.fromhex(args.key)) 328 | count = 0 329 | for key, value in cursor: 330 | keystream = KaitaiStream(io.BytesIO(key)) 331 | valstream = KaitaiStream(io.BytesIO(value)) 332 | unchecked_key = Nanodb.UncheckedKey(keystream) 333 | unchecked_info = Nanodb.UncheckedValue(valstream, None, Nanodb(None)) 334 | 335 | print('key previous : {}'.format(unchecked_key.previous.hex().upper())) 336 | print('key hash : {}'.format(unchecked_key.hash.hex().upper())) 337 | print(' block type : {}'.format(unchecked_info.block_type.name)) 338 | if unchecked_info.block_type == Nanodb.EnumBlocktype.state: 339 | print_state_block(unchecked_info.block.block, 2) 340 | elif unchecked_info.block_type == Nanodb.EnumBlocktype.receive: 341 | print_receive_block(unchecked_info.block.block, 2) 342 | elif unchecked_info.block_type == Nanodb.EnumBlocktype.send: 343 | print_send_block(unchecked_info.block.block, 2) 344 | elif unchecked_info.block_type == Nanodb.EnumBlocktype.open: 345 | print_open_block(unchecked_info.block.block, 2) 346 | elif unchecked_info.block_type == Nanodb.EnumBlocktype.change: 347 | print_change_block(unchecked_info.block.block, 2) 348 | print('') 349 | 350 | count+=1 351 | if count >= args.count: 352 | break 353 | cursor.close() 354 | if count == 0: 355 | print('(empty)\n') 356 | 357 | # Pending table 358 | if args.table == 'all' or args.table == 'pending': 359 | print_header('pending') 360 | state_db = env.open_db('pending'.encode()) 361 | 362 | count = 0 363 | with env.begin() as txn: 364 | cursor = txn.cursor(state_db) 365 | if args.key: 366 | cursor.set_key(bytearray.fromhex(args.key)) 367 | count = 0 368 | for key, value in cursor: 369 | keystream = KaitaiStream(io.BytesIO(key)) 370 | valstream = KaitaiStream(io.BytesIO(value)) 371 | pending_key = Nanodb.PendingKey(keystream) 372 | pending_info = Nanodb.PendingValue(valstream, None, Nanodb(None)) 373 | 374 | print('key account : {}'.format(pending_key.account.hex().upper())) 375 | print('key hash : {}'.format(pending_key.hash.hex().upper())) 376 | print(' source : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=pending_info.source.hex()))) 377 | print(' amount : {}'.format(nanolib.blocks.parse_hex_balance(pending_info.amount.hex()))) 378 | print(' epoch : {}'.format(pending_info.epoch.name)) 379 | print('') 380 | 381 | count+=1 382 | if count >= args.count: 383 | break 384 | cursor.close() 385 | if count == 0: 386 | print('(empty)\n') 387 | 388 | # Peers table 389 | if args.table == 'all' or args.table == 'peers': 390 | print_header('peers') 391 | state_db = env.open_db('peers'.encode()) 392 | 393 | count = 0 394 | with env.begin() as txn: 395 | cursor = txn.cursor(state_db) 396 | if args.key: 397 | cursor.set_key(bytearray.fromhex(args.key)) 398 | count = 0 399 | for key, value in cursor: 400 | keystream = KaitaiStream(io.BytesIO(key)) 401 | peer_key = Nanodb.Peers(keystream) 402 | 403 | address = ipaddress.IPv6Address(peer_key.address) 404 | print('address : {}'.format(address)) 405 | print('ipv4 mapped : {}'.format(address.ipv4_mapped)) 406 | print('port : {}'.format(peer_key.port)) 407 | print('') 408 | 409 | count+=1 410 | if count >= args.count: 411 | break 412 | cursor.close() 413 | if count == 0: 414 | print('(empty)\n') 415 | 416 | # Online weight table 417 | if args.table == 'all' or args.table == 'online_weight': 418 | print_header('online_weight') 419 | state_db = env.open_db('online_weight'.encode()) 420 | 421 | count = 0 422 | with env.begin() as txn: 423 | cursor = txn.cursor(state_db) 424 | if args.key: 425 | cursor.set_key(bytearray.fromhex(args.key)) 426 | count = 0 427 | for key, value in cursor: 428 | keystream = KaitaiStream(io.BytesIO(key)) 429 | valstream = KaitaiStream(io.BytesIO(value)) 430 | weight_key = Nanodb.OnlineWeightKey(keystream) 431 | weight_info = Nanodb.OnlineWeightValue(valstream, None, Nanodb(None)) 432 | 433 | # Weight timestamp is stored in microseconds since epoch 434 | print('timestamp : {}'.format(datetime.datetime.utcfromtimestamp(weight_key.timestamp/1000000))) 435 | print('online weight : {}'.format(nanolib.blocks.parse_hex_balance(weight_info.amount.hex().upper()))) 436 | print('') 437 | 438 | count+=1 439 | if count >= args.count: 440 | break 441 | cursor.close() 442 | if count == 0: 443 | print('(empty)\n') 444 | 445 | # Confirmation height table 446 | if args.table == 'all' or args.table == 'confirmation_height': 447 | print_header('confirmation_height') 448 | state_db = env.open_db('confirmation_height'.encode()) 449 | 450 | count = 0 451 | with env.begin() as txn: 452 | cursor = txn.cursor(state_db) 453 | if args.key: 454 | cursor.set_key(bytearray.fromhex(args.key)) 455 | count = 0 456 | for key, value in cursor: 457 | keystream = KaitaiStream(io.BytesIO(key)) 458 | valstream = KaitaiStream(io.BytesIO(value)) 459 | height_key = Nanodb.ConfirmationHeightKey(keystream) 460 | height_info = Nanodb.ConfirmationHeightValue(valstream, None, Nanodb(None)) 461 | 462 | # height timestamp is stored in microseconds since epoch 463 | print('account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=height_key.account.hex()))) 464 | print('confirmed height : {}'.format(height_info.height)) 465 | print('frontier hash : {}'.format(height_info.frontier.hex().upper())) 466 | print('') 467 | 468 | count+=1 469 | if count >= args.count: 470 | break 471 | cursor.close() 472 | if count == 0: 473 | print('(empty)\n') 474 | 475 | # Frontiers table. This is usually empty on synced ledgers, because frontiers 476 | # are removed when inserting epoch blocks. 477 | if args.table == 'all' or args.table == 'frontiers': 478 | print_header('frontiers') 479 | state_db = env.open_db('frontiers'.encode()) 480 | 481 | count = 0 482 | with env.begin() as txn: 483 | cursor = txn.cursor(state_db) 484 | if args.key: 485 | cursor.set_key(bytearray.fromhex(args.key)) 486 | count = 0 487 | for key, value in cursor: 488 | keystream = KaitaiStream(io.BytesIO(key)) 489 | valstream = KaitaiStream(io.BytesIO(value)) 490 | frontiers_key = Nanodb.FrontiersKey(keystream) 491 | frontiers_info = Nanodb.FrontiersValue(valstream, None, Nanodb(None)) 492 | 493 | # Weight timestamp is stored in microseconds since epoch 494 | print('hash : {}'.format(frontiers_key.hash.hex().upper())) 495 | print('account : {}'.format(nanolib.accounts.get_account_id(prefix=nanolib.AccountIDPrefix.NANO, public_key=frontiers_info.account.hex()))) 496 | print('') 497 | 498 | count+=1 499 | if count >= args.count: 500 | break 501 | cursor.close() 502 | if count == 0: 503 | print('(empty)\n') 504 | 505 | # Meta table 506 | if args.table == 'all' or args.table == 'meta': 507 | print_header('meta') 508 | state_db = env.open_db('meta'.encode()) 509 | 510 | with env.begin() as txn: 511 | cursor = txn.cursor(state_db) 512 | if args.key: 513 | cursor.set_key(bytearray.fromhex(args.key)) 514 | count = 0 515 | for key, value in cursor: 516 | keystream = KaitaiStream(io.BytesIO(key)) 517 | valstream = KaitaiStream(io.BytesIO(value)) 518 | meta_key = Nanodb.MetaKey(keystream) 519 | key = int.from_bytes(meta_key.key, byteorder='big', signed=False) 520 | if key == 1: 521 | meta_version = Nanodb.MetaVersion(valstream, None, Nanodb(None)) 522 | print('Database version: ', int.from_bytes(meta_version.database_version, byteorder='big', signed=False)) 523 | else: 524 | print('Key not recognized: ', key) 525 | cursor.close() 526 | if count == 0: 527 | print('(empty)\n') 528 | 529 | env.close() 530 | except Exception as ex: 531 | print (ex) 532 | -------------------------------------------------------------------------------- /sample/requirements.txt: -------------------------------------------------------------------------------- 1 | kaitaistruct==0.8 2 | lmdb==1.0 3 | nanolib==0.3 4 | --------------------------------------------------------------------------------