From cb5f96b3c924688d03dd60fe7a430f63d579e193 Mon Sep 17 00:00:00 2001 From: Captain Date: Mon, 22 Feb 2021 17:48:14 +0000 Subject: [PATCH] DERO-HE STARGATE Testnet Release9 --- Changelog.md | 46 + Start.md | 2 +- block/block.go | 12 +- blockchain/blockchain.go | 205 +- blockchain/blockheader.go | 6 +- blockchain/difficulty.go | 2 +- blockchain/genesis.go | 2 +- blockchain/mempool/mempool.go | 12 +- blockchain/miner_block.go | 17 +- blockchain/prune_history.go | 11 +- blockchain/regpool/regpool.go | 4 +- blockchain/sc.go | 300 + blockchain/store.go | 47 +- blockchain/storefs.go | 2 +- blockchain/storetopo.go | 7 +- blockchain/transaction_execute.go | 294 +- blockchain/transaction_verify.go | 294 +- build_all.sh | 9 +- cmd/dero-miner/difficulty.go | 2 +- cmd/dero-miner/miner.go | 14 +- cmd/dero-wallet-cli/easymenu_post_open.go | 290 +- cmd/dero-wallet-cli/easymenu_pre_open.go | 29 +- cmd/dero-wallet-cli/main.go | 8 +- cmd/dero-wallet-cli/prompt.go | 413 +- cmd/derod/main.go | 145 +- cmd/derod/miner.go | 63 +- cmd/derod/rpc_dero_getblock.go | 12 +- cmd/derod/rpc_dero_getblockcount.go | 6 +- cmd/derod/rpc_dero_getblockheaderbyhash.go | 12 +- .../rpc_dero_getblockheaderbytopoheight.go | 10 +- cmd/derod/rpc_dero_getblocktemplate.go | 17 +- cmd/derod/rpc_dero_getencryptedbalance.go | 72 +- cmd/derod/rpc_dero_getheight.go | 10 +- cmd/derod/rpc_dero_getinfo.go | 36 +- cmd/derod/rpc_dero_getrandomaddress.go | 23 +- cmd/derod/rpc_dero_getsc.go | 165 + cmd/derod/rpc_dero_gettransactions.go | 89 +- cmd/derod/rpc_dero_gettxpool.go | 5 +- cmd/derod/rpc_dero_sendrawtransaction.go | 13 +- cmd/derod/rpc_dero_submitblock.go | 9 +- cmd/derod/rpc_get_getlastblockheader.go | 6 +- cmd/derod/websocket_server.go | 62 +- cmd/explorer/explorer.go | 212 +- cmd/explorer/templates.go | 43 +- cmd/rpc_examples/pong_server/pong_server.go | 182 + cmd/rpc_examples/readme.txt | 2 + config/config.go | 44 +- config/version.go | 2 +- cryptography/bn256/CODE_OF_CONDUCT.md | 47 + .../x/tools => cryptography/bn256}/LICENSE | 0 cryptography/bn256/Makefile | 34 + cryptography/bn256/README.md | 33 + cryptography/bn256/bn256.go | 490 + cryptography/bn256/bn256_test.go | 116 + cryptography/bn256/constants.go | 79 + cryptography/bn256/curve.go | 318 + cryptography/bn256/curve_test.go | 66 + cryptography/bn256/example_test.go | 51 + cryptography/bn256/g1_serialization.go | 424 + cryptography/bn256/g1_serialization_test.go | 241 + cryptography/bn256/g2_serialization.go | 266 + cryptography/bn256/g2_serialization_test.go | 174 + cryptography/bn256/gfp.go | 188 + cryptography/bn256/gfp12.go | 160 + cryptography/bn256/gfp2.go | 327 + cryptography/bn256/gfp2_test.go | 164 + cryptography/bn256/gfp6.go | 213 + cryptography/bn256/gfp_amd64.s | 129 + cryptography/bn256/gfp_arm64.s | 113 + cryptography/bn256/gfp_decl.go | 25 + cryptography/bn256/gfp_generic.go | 209 + cryptography/bn256/gfp_test.go | 116 + cryptography/bn256/lattice.go | 115 + cryptography/bn256/lattice_test.go | 29 + cryptography/bn256/main_test.go | 71 + cryptography/bn256/mul_amd64.h | 181 + cryptography/bn256/mul_arm64.h | 133 + cryptography/bn256/mul_bmi2_amd64.h | 112 + cryptography/bn256/optate.go | 271 + cryptography/bn256/twist.go | 217 + cryptography/crypto/LICENSE | 90 + cryptography/crypto/algebra_elgamal.go | 177 + cryptography/crypto/algebra_fieldvector.go | 201 + cryptography/crypto/algebra_pedersen.go | 110 + cryptography/crypto/algebra_pointvector.go | 192 + cryptography/crypto/bnred.go | 102 + cryptography/crypto/const.go | 57 + cryptography/crypto/fieldvector.go | 297 + cryptography/crypto/generatorparams.go | 72 + cryptography/crypto/group.go | 73 + cryptography/crypto/hash.go | 68 + cryptography/crypto/hashtopoint.go | 260 + cryptography/crypto/keccak.go | 41 + cryptography/crypto/keccak_test.go | 57 + cryptography/crypto/key_old.go | 70 + cryptography/crypto/polynomial.go | 91 + cryptography/crypto/proof_generate.go | 1206 + cryptography/crypto/proof_innerproduct.go | 313 + cryptography/crypto/proof_verify.go | 541 + cryptography/crypto/protocol_structures.go | 225 + cryptography/crypto/random.go | 44 + cryptography/crypto/userdata.go | 43 + cryptography/sha3/doc.go | 66 + cryptography/sha3/hashes.go | 97 + cryptography/sha3/hashes_generic.go | 27 + cryptography/sha3/keccakf.go | 412 + .../sha3/keccakf_amd64.go | 12 +- cryptography/sha3/keccakf_amd64.s | 390 + cryptography/sha3/register.go | 18 + cryptography/sha3/sha3.go | 193 + cryptography/sha3/sha3_s390x.go | 284 + cryptography/sha3/sha3_s390x.s | 33 + cryptography/sha3/sha3_test.go | 483 + cryptography/sha3/shake.go | 173 + cryptography/sha3/shake_generic.go | 19 + .../sha3/testdata/keccakKats.json.deflate | Bin 0 -> 540828 bytes cryptography/sha3/xor.go | 23 + cryptography/sha3/xor_generic.go | 28 + cryptography/sha3/xor_unaligned.go | 65 + dvm/deterministic_random_number.go | 68 + dvm/deterministic_random_number_test.go | 58 + dvm/dvm.go | 1120 + dvm/dvm_execution_test.go | 1757 + dvm/dvm_functions.go | 332 + dvm/dvm_functions_test.go | 132 + dvm/dvm_parse_test.go | 239 + dvm/dvm_store.go | 324 + dvm/dvm_store_memory.go | 77 + globals/globals.go | 33 +- go.mod | 3 + guide/README.md | 61 + guide/dim.md | 11 + guide/examples/lottery.bas | 90 + guide/examples/lottery_sc_guide.md | 76 + guide/examples/token.bas | 70 + guide/examples/token_sc_guide.md | 100 + guide/function.md | 27 + guide/goto.md | 6 + guide/if.md | 8 + guide/let.md | 14 + guide/return.md | 8 + guide/support_functions.md | 69 + p2p/chain_bootstrap.go | 345 + p2p/chain_response.go | 125 - p2p/chain_sync.go | 234 + p2p/common.go | 72 + p2p/connection_handler.go | 453 - p2p/connection_pool.go | 175 +- p2p/controller.go | 82 +- p2p/handshake.go | 196 - p2p/inventory_handler.go | 135 - p2p/object_pool.go | 136 - p2p/object_response.go | 160 - p2p/rpc.go | 87 + p2p/rpc_cbor_codec.go | 188 + ...{chain_request.go => rpc_chain_request.go} | 95 +- p2p/rpc_changeset.go | 136 + p2p/rpc_handshake.go | 190 + p2p/{notification.go => rpc_notifications.go} | 214 +- ...bject_request.go => rpc_object_request.go} | 91 +- p2p/rpc_treesection.go | 68 + p2p/timedsync.go | 72 - p2p/wire_structs.go | 180 +- proof/proof.go | 71 +- rpc/LICENSE | 90 + rpc/address.go | 202 + rpc/address_test.go1 | 250 + rpc/bech32.go | 217 + rpc/bech32_test.go | 402 + rpc/daemon_rpc.go | 304 + rpc/rpc.go | 423 + rpc/rpc_sc.go | 21 + rpc/wallet_rpc.go | 276 + transaction/transaction.go | 328 +- vendor/etcd.io/bbolt/.gitignore | 5 + vendor/etcd.io/bbolt/.travis.yml | 17 + .../COPYING => etcd.io/bbolt/LICENSE} | 21 +- vendor/etcd.io/bbolt/Makefile | 38 + vendor/etcd.io/bbolt/README.md | 958 + vendor/etcd.io/bbolt/allocate_test.go | 31 + vendor/etcd.io/bbolt/bolt_386.go | 7 + vendor/etcd.io/bbolt/bolt_amd64.go | 7 + vendor/etcd.io/bbolt/bolt_arm.go | 7 + vendor/etcd.io/bbolt/bolt_arm64.go | 9 + vendor/etcd.io/bbolt/bolt_linux.go | 10 + vendor/etcd.io/bbolt/bolt_mips64x.go | 9 + vendor/etcd.io/bbolt/bolt_mipsx.go | 9 + vendor/etcd.io/bbolt/bolt_openbsd.go | 27 + vendor/etcd.io/bbolt/bolt_ppc.go | 9 + vendor/etcd.io/bbolt/bolt_ppc64.go | 9 + vendor/etcd.io/bbolt/bolt_ppc64le.go | 9 + vendor/etcd.io/bbolt/bolt_riscv64.go | 9 + vendor/etcd.io/bbolt/bolt_s390x.go | 9 + vendor/etcd.io/bbolt/bolt_unix.go | 93 + vendor/etcd.io/bbolt/bolt_unix_aix.go | 90 + vendor/etcd.io/bbolt/bolt_unix_solaris.go | 88 + vendor/etcd.io/bbolt/bolt_windows.go | 141 + vendor/etcd.io/bbolt/boltsync_unix.go | 8 + vendor/etcd.io/bbolt/bucket.go | 777 + vendor/etcd.io/bbolt/bucket_test.go | 1959 + vendor/etcd.io/bbolt/cmd/bbolt/main.go | 2136 + vendor/etcd.io/bbolt/cmd/bbolt/main_test | 455 + vendor/etcd.io/bbolt/cursor.go | 396 + vendor/etcd.io/bbolt/cursor_test.go | 817 + vendor/etcd.io/bbolt/db.go | 1174 + vendor/etcd.io/bbolt/db_test.go | 1783 + vendor/etcd.io/bbolt/doc.go | 44 + vendor/etcd.io/bbolt/errors.go | 71 + vendor/etcd.io/bbolt/freelist.go | 404 + vendor/etcd.io/bbolt/freelist_hmap.go | 178 + vendor/etcd.io/bbolt/freelist_test.go | 434 + vendor/etcd.io/bbolt/go.mod | 5 + vendor/etcd.io/bbolt/go.sum | 2 + vendor/etcd.io/bbolt/manydbs_test.go | 67 + vendor/etcd.io/bbolt/node.go | 602 + vendor/etcd.io/bbolt/node_test.go | 156 + vendor/etcd.io/bbolt/page.go | 204 + vendor/etcd.io/bbolt/page_test.go | 72 + vendor/etcd.io/bbolt/quick_test.go | 90 + .../bbolt/simulation_no_freelist_sync_test.go | 47 + vendor/etcd.io/bbolt/simulation_test.go | 361 + vendor/etcd.io/bbolt/tx.go | 724 + vendor/etcd.io/bbolt/tx_test.go | 924 + vendor/etcd.io/bbolt/unsafe.go | 39 + vendor/github.com/alecthomas/.directory | 6 - .../alecthomas/jsonschema/.travis.yml | 6 - .../alecthomas/jsonschema/README.md | 140 - .../fixtures/allow_additional_props.json | 109 - .../jsonschema/fixtures/defaults.json | 109 - .../fixtures/defaults_expanded_toplevel.json | 106 - .../fixtures/required_from_jsontags.json | 105 - .../alecthomas/jsonschema/reflect.go | 451 - .../alecthomas/jsonschema/reflect_test.go | 111 - vendor/github.com/cheggaaa/pb/.travis.yml | 15 - vendor/github.com/cheggaaa/pb/LICENSE | 12 - vendor/github.com/cheggaaa/pb/README.md | 129 - vendor/github.com/cheggaaa/pb/README_V1.md | 175 - .../cheggaaa/pb/example_copy_test.go | 82 - .../cheggaaa/pb/example_multiple_test.go | 37 - vendor/github.com/cheggaaa/pb/example_test.go | 30 - vendor/github.com/cheggaaa/pb/format.go | 125 - vendor/github.com/cheggaaa/pb/format_test.go | 95 - vendor/github.com/cheggaaa/pb/go.mod | 10 - vendor/github.com/cheggaaa/pb/go.sum | 19 - vendor/github.com/cheggaaa/pb/pb.go | 506 - vendor/github.com/cheggaaa/pb/pb_appengine.go | 11 - vendor/github.com/cheggaaa/pb/pb_plan9.go | 70 - vendor/github.com/cheggaaa/pb/pb_test.go | 154 - vendor/github.com/cheggaaa/pb/pb_win.go | 143 - vendor/github.com/cheggaaa/pb/pb_x.go | 118 - vendor/github.com/cheggaaa/pb/pool.go | 104 - vendor/github.com/cheggaaa/pb/pool_win.go | 45 - vendor/github.com/cheggaaa/pb/pool_x.go | 29 - vendor/github.com/cheggaaa/pb/reader.go | 26 - vendor/github.com/cheggaaa/pb/runecount.go | 17 - .../github.com/cheggaaa/pb/runecount_test.go | 20 - vendor/github.com/cheggaaa/pb/termios_bsd.go | 9 - vendor/github.com/cheggaaa/pb/termios_sysv.go | 13 - vendor/github.com/cheggaaa/pb/v3/LICENSE | 12 - vendor/github.com/cheggaaa/pb/v3/element.go | 290 - .../github.com/cheggaaa/pb/v3/element_test.go | 278 - vendor/github.com/cheggaaa/pb/v3/go.mod | 11 - vendor/github.com/cheggaaa/pb/v3/go.sum | 21 - vendor/github.com/cheggaaa/pb/v3/io.go | 49 - vendor/github.com/cheggaaa/pb/v3/io_test.go | 100 - vendor/github.com/cheggaaa/pb/v3/pb.go | 566 - vendor/github.com/cheggaaa/pb/v3/pb_test.go | 240 - vendor/github.com/cheggaaa/pb/v3/preset.go | 15 - vendor/github.com/cheggaaa/pb/v3/speed.go | 83 - vendor/github.com/cheggaaa/pb/v3/template.go | 88 - .../cheggaaa/pb/v3/template_test.go | 53 - .../cheggaaa/pb/v3/termutil/term.go | 56 - .../cheggaaa/pb/v3/termutil/term_appengine.go | 11 - .../cheggaaa/pb/v3/termutil/term_bsd.go | 9 - .../cheggaaa/pb/v3/termutil/term_linux.go | 7 - .../cheggaaa/pb/v3/termutil/term_nix.go | 8 - .../cheggaaa/pb/v3/termutil/term_plan9.go | 50 - .../cheggaaa/pb/v3/termutil/term_solaris.go | 8 - .../cheggaaa/pb/v3/termutil/term_win.go | 155 - .../cheggaaa/pb/v3/termutil/term_x.go | 76 - vendor/github.com/cheggaaa/pb/v3/util.go | 115 - vendor/github.com/cheggaaa/pb/v3/util_test.go | 80 - vendor/github.com/cheggaaa/pb/writer.go | 26 - vendor/github.com/dchest/.directory | 6 - .../dchest/blake256/README.markdown | 56 - vendor/github.com/dchest/blake256/blake256.go | 194 - .../dchest/blake256/blake256_test.go | 188 - .../dchest/blake256/blake256block.go | 1681 - .../github.com/deroproject/graviton/cursor.go | 3 +- .../github.com/deroproject/graviton/extra.go | 4 + vendor/github.com/deroproject/graviton/go.mod | 3 + .../deroproject/graviton/special.go | 85 +- .../github.com/dgraph-io/badger/.travis.yml | 20 - .../github.com/dgraph-io/badger/CHANGELOG.md | 69 - vendor/github.com/dgraph-io/badger/LICENSE | 176 - vendor/github.com/dgraph-io/badger/README.md | 619 - .../github.com/dgraph-io/badger/appveyor.yml | 48 - vendor/github.com/dgraph-io/badger/backup.go | 174 - .../dgraph-io/badger/backup_test.go | 255 - .../dgraph-io/badger/badger/.gitignore | 1 - .../dgraph-io/badger/badger/cmd/backup.go | 72 - .../dgraph-io/badger/badger/cmd/info.go | 284 - .../dgraph-io/badger/badger/cmd/restore.go | 81 - .../dgraph-io/badger/badger/cmd/root.go | 65 - .../dgraph-io/badger/badger/main.go | 38 - .../github.com/dgraph-io/badger/compaction.go | 212 - .../dgraph-io/badger/contrib/cover.sh | 23 - vendor/github.com/dgraph-io/badger/db.go | 1230 - vendor/github.com/dgraph-io/badger/db_test.go | 1565 - .../github.com/dgraph-io/badger/dir_unix.go | 100 - .../dgraph-io/badger/dir_windows.go | 94 - vendor/github.com/dgraph-io/badger/doc.go | 28 - vendor/github.com/dgraph-io/badger/errors.go | 112 - .../badger/images/benchmarks-rocksdb.png | Bin 66938 -> 0 bytes .../dgraph-io/badger/images/diggy-shadow.png | Bin 33101 -> 0 bytes .../dgraph-io/badger/images/sketch.jpg | Bin 835620 -> 0 bytes .../badger/integration/testgc/.gitignore | 1 - .../badger/integration/testgc/main.go | 221 - .../github.com/dgraph-io/badger/iterator.go | 574 - .../dgraph-io/badger/level_handler.go | 294 - vendor/github.com/dgraph-io/badger/levels.go | 785 - .../github.com/dgraph-io/badger/managed_db.go | 79 - .../github.com/dgraph-io/badger/manifest.go | 433 - .../dgraph-io/badger/manifest_test.go | 244 - vendor/github.com/dgraph-io/badger/options.go | 144 - .../dgraph-io/badger/options/options.go | 30 - .../dgraph-io/badger/protos/backup.pb.go | 497 - .../dgraph-io/badger/protos/backup.proto | 28 - .../github.com/dgraph-io/badger/protos/gen.sh | 7 - .../dgraph-io/badger/protos/manifest.pb.go | 534 - .../dgraph-io/badger/protos/manifest.proto | 35 - .../github.com/dgraph-io/badger/skl/README.md | 113 - .../github.com/dgraph-io/badger/skl/arena.go | 136 - vendor/github.com/dgraph-io/badger/skl/skl.go | 516 - .../dgraph-io/badger/skl/skl_test.go | 475 - vendor/github.com/dgraph-io/badger/structs.go | 132 - .../dgraph-io/badger/table/README.md | 51 - .../dgraph-io/badger/table/builder.go | 235 - .../dgraph-io/badger/table/iterator.go | 539 - .../dgraph-io/badger/table/table.go | 359 - .../dgraph-io/badger/table/table_test.go | 729 - vendor/github.com/dgraph-io/badger/test.sh | 5 - .../dgraph-io/badger/transaction.go | 581 - .../dgraph-io/badger/transaction_test.go | 827 - vendor/github.com/dgraph-io/badger/util.go | 139 - vendor/github.com/dgraph-io/badger/value.go | 1216 - .../github.com/dgraph-io/badger/value_test.go | 680 - vendor/github.com/dgraph-io/badger/y/error.go | 83 - .../dgraph-io/badger/y/file_dsync.go | 25 - .../dgraph-io/badger/y/file_nodsync.go | 25 - .../github.com/dgraph-io/badger/y/iterator.go | 264 - .../dgraph-io/badger/y/iterator_test.go | 234 - .../github.com/dgraph-io/badger/y/metrics.go | 68 - .../dgraph-io/badger/y/mmap_unix.go | 63 - .../dgraph-io/badger/y/mmap_windows.go | 90 - .../dgraph-io/badger/y/watermark.go | 130 - vendor/github.com/dgraph-io/badger/y/y.go | 209 - vendor/github.com/dgryski/go-farm/.travis.yml | 38 - vendor/github.com/dgryski/go-farm/LICENSE | 23 - vendor/github.com/dgryski/go-farm/Makefile | 203 - vendor/github.com/dgryski/go-farm/README.md | 41 - vendor/github.com/dgryski/go-farm/VERSION | 1 - vendor/github.com/dgryski/go-farm/basics.go | 30 - .../github.com/dgryski/go-farm/bench_test.go | 37 - .../dgryski/go-farm/farmhash_test.go | 259 - .../github.com/dgryski/go-farm/farmhashcc.go | 199 - .../github.com/dgryski/go-farm/farmhashmk.go | 102 - .../github.com/dgryski/go-farm/farmhashna.go | 156 - .../github.com/dgryski/go-farm/farmhashuo.go | 117 - vendor/github.com/dgryski/go-farm/platform.go | 18 - vendor/github.com/ebfe/.directory | 6 - vendor/github.com/ebfe/keccak/.gitignore | 3 - vendor/github.com/ebfe/keccak/.travis.yml | 4 - vendor/github.com/ebfe/keccak/LICENSE | 23 - vendor/github.com/ebfe/keccak/keccak.go | 323 - vendor/github.com/ebfe/keccak/keccak_test.go | 272 - .../ebfe/keccak/keccak_vectors_test.go | 1929 - vendor/github.com/ebfe/keccak/sha3.go | 25 - vendor/github.com/ebfe/keccak/sha3_test.go | 54 - .../ebfe/keccak/sha3_vectors_test.go | 1796 - vendor/github.com/ebfe/keccak/shake.go | 15 - vendor/github.com/ebfe/keccak/shake_test.go | 28 - .../ebfe/keccak/shake_vectors_test.go | 1290 - .../fxamacker/cbor/.github/FUNDING.yml | 1 + .../.github/ISSUE_TEMPLATE/1-bug-report.md | 36 + .../ISSUE_TEMPLATE/2-feature-request.md | 20 + .../3-docs-wiki-or-website-issue.md | 16 + .../4-security-issue-disclosure.md | 16 + .../cbor/.github/pull_request_template.md | 69 + .../cbor/.github/workflows/ci-go-cover.yml | 33 + .../fxamacker/cbor/.github/workflows/ci.yml | 26 + .../cbor/.github/workflows/linters.yml | 20 + vendor/github.com/fxamacker/cbor/.gitignore | 12 + .../github.com/fxamacker/cbor/.golangci.yml | 86 + .../fxamacker/cbor/CBOR_BENCHMARKS.md | 264 + .../github.com/fxamacker/cbor/CBOR_GOLANG.md | 32 + .../cbor}/CODE_OF_CONDUCT.md | 17 +- .../github.com/fxamacker/cbor/CONTRIBUTING.md | 47 + .../LICENSE.txt => fxamacker/cbor/LICENSE} | 4 +- vendor/github.com/fxamacker/cbor/README.md | 1079 + vendor/github.com/fxamacker/cbor/SECURITY.md | 7 + .../github.com/fxamacker/cbor/bench_test.go | 674 + vendor/github.com/fxamacker/cbor/cache.go | 310 + vendor/github.com/fxamacker/cbor/decode.go | 1875 + .../github.com/fxamacker/cbor/decode_test.go | 5180 ++ vendor/github.com/fxamacker/cbor/doc.go | 130 + vendor/github.com/fxamacker/cbor/encode.go | 1481 + .../github.com/fxamacker/cbor/encode_test.go | 3592 + .../github.com/fxamacker/cbor/example_test.go | 571 + vendor/github.com/fxamacker/cbor/go.mod | 5 + vendor/github.com/fxamacker/cbor/go.sum | 2 + vendor/github.com/fxamacker/cbor/stream.go | 199 + .../github.com/fxamacker/cbor/stream_test.go | 425 + .../github.com/fxamacker/cbor/structfields.go | 251 + vendor/github.com/fxamacker/cbor/tag.go | 303 + vendor/github.com/fxamacker/cbor/tag_test.go | 1446 + vendor/github.com/fxamacker/cbor/valid.go | 300 + .../github.com/fxamacker/cbor/valid_test.go | 157 + .../github.com/gdamore/encoding/.appveyor.yml | 13 - .../github.com/gdamore/encoding/.travis.yml | 7 - vendor/github.com/gdamore/encoding/LICENSE | 202 - vendor/github.com/gdamore/encoding/README.md | 19 - vendor/github.com/gdamore/encoding/ascii.go | 36 - .../github.com/gdamore/encoding/ascii_test.go | 39 - vendor/github.com/gdamore/encoding/charmap.go | 196 - .../gdamore/encoding/common_test.go | 71 - vendor/github.com/gdamore/encoding/doc.go | 17 - vendor/github.com/gdamore/encoding/ebcdic.go | 273 - .../gdamore/encoding/ebcdic_test.go | 146 - vendor/github.com/gdamore/encoding/go.mod | 5 - vendor/github.com/gdamore/encoding/go.sum | 2 - vendor/github.com/gdamore/encoding/latin1.go | 33 - .../gdamore/encoding/latin1_test.go | 29 - vendor/github.com/gdamore/encoding/latin5.go | 35 - .../gdamore/encoding/latin5_test.go | 44 - vendor/github.com/gdamore/encoding/utf8.go | 35 - .../github.com/gdamore/encoding/utf8_test.go | 29 - .../github.com/gdamore/tcell/v2/.appveyor.yml | 13 - .../gdamore/tcell/v2/.github/FUNDING.yml | 3 - .../gdamore/tcell/v2/.github/workflows/go.yml | 28 - vendor/github.com/gdamore/tcell/v2/.gitignore | 1 - .../github.com/gdamore/tcell/v2/.travis.yml | 19 - vendor/github.com/gdamore/tcell/v2/AUTHORS | 4 - .../gdamore/tcell/v2/CHANGESv2.adoc | 82 - vendor/github.com/gdamore/tcell/v2/LICENSE | 202 - .../github.com/gdamore/tcell/v2/README.adoc | 280 - .../github.com/gdamore/tcell/v2/TUTORIAL.adoc | 325 - .../gdamore/tcell/v2/_demos/beep.go | 75 - .../gdamore/tcell/v2/_demos/boxes.go | 118 - .../gdamore/tcell/v2/_demos/colors.go | 163 - .../gdamore/tcell/v2/_demos/hello_world.go | 84 - .../gdamore/tcell/v2/_demos/mouse.go | 276 - .../gdamore/tcell/v2/_demos/unicode.go | 193 - vendor/github.com/gdamore/tcell/v2/attr.go | 33 - vendor/github.com/gdamore/tcell/v2/cell.go | 177 - .../gdamore/tcell/v2/charset_stub.go | 21 - .../gdamore/tcell/v2/charset_unix.go | 49 - .../gdamore/tcell/v2/charset_windows.go | 21 - vendor/github.com/gdamore/tcell/v2/color.go | 1069 - .../github.com/gdamore/tcell/v2/color_test.go | 112 - .../github.com/gdamore/tcell/v2/colorfit.go | 52 - .../gdamore/tcell/v2/console_stub.go | 23 - .../gdamore/tcell/v2/console_win.go | 1187 - vendor/github.com/gdamore/tcell/v2/doc.go | 48 - .../github.com/gdamore/tcell/v2/encoding.go | 139 - .../gdamore/tcell/v2/encoding/all.go | 115 - vendor/github.com/gdamore/tcell/v2/errors.go | 73 - vendor/github.com/gdamore/tcell/v2/event.go | 53 - .../github.com/gdamore/tcell/v2/event_test.go | 70 - vendor/github.com/gdamore/tcell/v2/go.mod | 11 - vendor/github.com/gdamore/tcell/v2/go.sum | 10 - .../github.com/gdamore/tcell/v2/interrupt.go | 41 - vendor/github.com/gdamore/tcell/v2/key.go | 470 - .../gdamore/tcell/v2/logos/patreon.png | Bin 6057 -> 0 bytes .../gdamore/tcell/v2/logos/staysail.png | Bin 17422 -> 0 bytes .../gdamore/tcell/v2/logos/tcell.png | Bin 5336 -> 0 bytes .../gdamore/tcell/v2/logos/tidelift.png | Bin 2333 -> 0 bytes vendor/github.com/gdamore/tcell/v2/mouse.go | 103 - vendor/github.com/gdamore/tcell/v2/paste.go | 48 - vendor/github.com/gdamore/tcell/v2/resize.go | 42 - vendor/github.com/gdamore/tcell/v2/runes.go | 111 - .../github.com/gdamore/tcell/v2/runes_test.go | 95 - vendor/github.com/gdamore/tcell/v2/screen.go | 219 - .../github.com/gdamore/tcell/v2/sim_test.go | 152 - .../github.com/gdamore/tcell/v2/simulation.go | 522 - vendor/github.com/gdamore/tcell/v2/style.go | 137 - .../github.com/gdamore/tcell/v2/style_test.go | 41 - .../gdamore/tcell/v2/termbox/compat.go | 379 - .../gdamore/tcell/v2/terminfo/.gitignore | 1 - .../gdamore/tcell/v2/terminfo/README.md | 25 - .../gdamore/tcell/v2/terminfo/TERMINALS.md | 7 - .../tcell/v2/terminfo/a/aixterm/term.go | 82 - .../tcell/v2/terminfo/a/alacritty/term.go | 69 - .../gdamore/tcell/v2/terminfo/a/ansi/term.go | 42 - .../tcell/v2/terminfo/b/beterm/term.go | 55 - .../gdamore/tcell/v2/terminfo/base/base.go | 32 - .../tcell/v2/terminfo/c/cygwin/term.go | 64 - .../tcell/v2/terminfo/d/dtterm/term.go | 68 - .../tcell/v2/terminfo/dynamic/dynamic.go | 447 - .../gdamore/tcell/v2/terminfo/e/emacs/term.go | 61 - .../tcell/v2/terminfo/extended/extended.go | 57 - .../gdamore/tcell/v2/terminfo/g/gnome/term.go | 130 - .../gdamore/tcell/v2/terminfo/gen.sh | 18 - .../tcell/v2/terminfo/h/hpterm/term.go | 50 - .../tcell/v2/terminfo/k/konsole/term.go | 130 - .../gdamore/tcell/v2/terminfo/k/kterm/term.go | 68 - .../gdamore/tcell/v2/terminfo/l/linux/term.go | 70 - .../gdamore/tcell/v2/terminfo/mkinfo.go | 717 - .../gdamore/tcell/v2/terminfo/models.txt | 32 - .../tcell/v2/terminfo/p/pcansi/term.go | 40 - .../gdamore/tcell/v2/terminfo/r/rxvt/term.go | 480 - .../tcell/v2/terminfo/s/screen/term.go | 128 - .../tcell/v2/terminfo/s/simpleterm/term.go | 136 - .../gdamore/tcell/v2/terminfo/s/sun/term.go | 91 - .../tcell/v2/terminfo/t/termite/term.go | 66 - .../gdamore/tcell/v2/terminfo/t/tmux/term.go | 134 - .../gdamore/tcell/v2/terminfo/terminfo.go | 803 - .../tcell/v2/terminfo/terminfo_test.go | 100 - .../gdamore/tcell/v2/terminfo/v/vt100/term.go | 48 - .../gdamore/tcell/v2/terminfo/v/vt102/term.go | 47 - .../gdamore/tcell/v2/terminfo/v/vt220/term.go | 58 - .../gdamore/tcell/v2/terminfo/v/vt320/term.go | 63 - .../gdamore/tcell/v2/terminfo/v/vt400/term.go | 46 - .../gdamore/tcell/v2/terminfo/v/vt420/term.go | 53 - .../gdamore/tcell/v2/terminfo/v/vt52/term.go | 29 - .../gdamore/tcell/v2/terminfo/w/wy50/term.go | 59 - .../gdamore/tcell/v2/terminfo/w/wy60/term.go | 63 - .../tcell/v2/terminfo/w/wy99_ansi/term.go | 114 - .../gdamore/tcell/v2/terminfo/x/xfce/term.go | 67 - .../gdamore/tcell/v2/terminfo/x/xterm/term.go | 192 - .../tcell/v2/terminfo/x/xterm_kitty/term.go | 69 - .../tcell/v2/terminfo/x/xterm_termite/term.go | 67 - .../gdamore/tcell/v2/terms_default.go | 23 - .../gdamore/tcell/v2/terms_dynamic.go | 37 - .../gdamore/tcell/v2/terms_static.go | 27 - vendor/github.com/gdamore/tcell/v2/tscreen.go | 1544 - .../gdamore/tcell/v2/tscreen_bsd.go | 121 - .../gdamore/tcell/v2/tscreen_darwin.go | 142 - .../gdamore/tcell/v2/tscreen_linux.go | 147 - .../gdamore/tcell/v2/tscreen_solaris.go | 122 - .../gdamore/tcell/v2/tscreen_stub.go | 36 - .../gdamore/tcell/v2/tscreen_windows.go | 44 - .../gdamore/tcell/v2/views/README.md | 7 - .../gdamore/tcell/v2/views/_demos/cellview.go | 222 - .../gdamore/tcell/v2/views/_demos/hbox.go | 88 - .../gdamore/tcell/v2/views/_demos/vbox.go | 81 - .../github.com/gdamore/tcell/v2/views/app.go | 195 - .../gdamore/tcell/v2/views/boxlayout.go | 331 - .../gdamore/tcell/v2/views/cellarea.go | 318 - .../gdamore/tcell/v2/views/constants.go | 60 - .../gdamore/tcell/v2/views/panel.go | 111 - .../gdamore/tcell/v2/views/spacer.go | 53 - .../gdamore/tcell/v2/views/sstext.go | 127 - .../gdamore/tcell/v2/views/sstextbar.go | 113 - .../github.com/gdamore/tcell/v2/views/text.go | 278 - .../gdamore/tcell/v2/views/text_test.go | 19 - .../gdamore/tcell/v2/views/textarea.go | 155 - .../gdamore/tcell/v2/views/textarea_test.go | 17 - .../gdamore/tcell/v2/views/textbar.go | 193 - .../github.com/gdamore/tcell/v2/views/view.go | 300 - .../gdamore/tcell/v2/views/widget.go | 160 - vendor/github.com/golang/snappy/.gitignore | 16 - vendor/github.com/golang/snappy/AUTHORS | 15 - vendor/github.com/golang/snappy/CONTRIBUTORS | 37 - vendor/github.com/golang/snappy/LICENSE | 27 - vendor/github.com/golang/snappy/README | 107 - .../golang/snappy/cmd/snappytool/main.cpp | 77 - vendor/github.com/golang/snappy/decode.go | 237 - .../github.com/golang/snappy/decode_amd64.go | 14 - .../github.com/golang/snappy/decode_amd64.s | 490 - .../github.com/golang/snappy/decode_other.go | 101 - vendor/github.com/golang/snappy/encode.go | 285 - .../github.com/golang/snappy/encode_amd64.go | 29 - .../github.com/golang/snappy/encode_amd64.s | 730 - .../github.com/golang/snappy/encode_other.go | 238 - .../github.com/golang/snappy/golden_test.go | 1965 - vendor/github.com/golang/snappy/snappy.go | 87 - .../github.com/golang/snappy/snappy_test.go | 1353 - .../snappy/testdata/Mark.Twain-Tom.Sawyer.txt | 396 - .../Mark.Twain-Tom.Sawyer.txt.rawsnappy | Bin 9871 -> 0 bytes .../go-farm => hashicorp/yamux}/.gitignore | 5 +- vendor/github.com/hashicorp/yamux/LICENSE | 362 + vendor/github.com/hashicorp/yamux/README.md | 86 + vendor/github.com/hashicorp/yamux/addr.go | 60 + .../github.com/hashicorp/yamux/bench_test.go | 254 + vendor/github.com/hashicorp/yamux/const.go | 157 + .../github.com/hashicorp/yamux/const_test.go | 72 + vendor/github.com/hashicorp/yamux/go.mod | 1 + vendor/github.com/hashicorp/yamux/mux.go | 98 + vendor/github.com/hashicorp/yamux/session.go | 653 + .../hashicorp/yamux/session_test.go | 1353 + vendor/github.com/hashicorp/yamux/spec.md | 140 + vendor/github.com/hashicorp/yamux/stream.go | 472 + vendor/github.com/hashicorp/yamux/util.go | 43 + .../github.com/hashicorp/yamux/util_test.go | 50 + vendor/github.com/rivo/tview/CONTRIBUTING.md | 35 - vendor/github.com/rivo/tview/README.md | 108 - vendor/github.com/rivo/tview/ansi.go | 258 - vendor/github.com/rivo/tview/application.go | 737 - vendor/github.com/rivo/tview/borders.go | 45 - vendor/github.com/rivo/tview/box.go | 410 - vendor/github.com/rivo/tview/button.go | 157 - vendor/github.com/rivo/tview/checkbox.go | 240 - .../github.com/rivo/tview/demos/box/README.md | 1 - .../github.com/rivo/tview/demos/box/main.go | 17 - .../rivo/tview/demos/box/screenshot.png | Bin 14939 -> 0 bytes .../rivo/tview/demos/button/README.md | 1 - .../rivo/tview/demos/button/main.go | 15 - .../rivo/tview/demos/button/screenshot.png | Bin 13208 -> 0 bytes .../rivo/tview/demos/checkbox/README.md | 1 - .../rivo/tview/demos/checkbox/main.go | 12 - .../rivo/tview/demos/checkbox/screenshot.png | Bin 11563 -> 0 bytes .../rivo/tview/demos/dropdown/README.md | 1 - .../rivo/tview/demos/dropdown/main.go | 14 - .../rivo/tview/demos/dropdown/screenshot.png | Bin 17991 -> 0 bytes .../rivo/tview/demos/flex/README.md | 1 - .../github.com/rivo/tview/demos/flex/main.go | 20 - .../rivo/tview/demos/flex/screenshot.png | Bin 34619 -> 0 bytes .../rivo/tview/demos/form/README.md | 1 - .../github.com/rivo/tview/demos/form/main.go | 24 - .../rivo/tview/demos/form/screenshot.png | Bin 29493 -> 0 bytes .../rivo/tview/demos/frame/README.md | 1 - .../github.com/rivo/tview/demos/frame/main.go | 22 - .../rivo/tview/demos/frame/screenshot.png | Bin 19879 -> 0 bytes .../rivo/tview/demos/grid/README.md | 1 - .../github.com/rivo/tview/demos/grid/main.go | 38 - .../rivo/tview/demos/grid/screenshot.png | Bin 19041 -> 0 bytes .../rivo/tview/demos/inputfield/README.md | 1 - .../demos/inputfield/autocomplete/main.go | 39 - .../inputfield/autocompleteasync/main.go | 81 - .../rivo/tview/demos/inputfield/main.go | 22 - .../tview/demos/inputfield/screenshot.png | Bin 10502 -> 0 bytes .../rivo/tview/demos/list/README.md | 1 - .../github.com/rivo/tview/demos/list/main.go | 21 - .../rivo/tview/demos/list/screenshot.png | Bin 30504 -> 0 bytes .../rivo/tview/demos/modal/README.md | 1 - .../rivo/tview/demos/modal/centered.png | Bin 75615 -> 0 bytes .../github.com/rivo/tview/demos/modal/main.go | 21 - .../rivo/tview/demos/modal/screenshot.png | Bin 18606 -> 0 bytes .../rivo/tview/demos/pages/README.md | 1 - .../github.com/rivo/tview/demos/pages/main.go | 35 - .../rivo/tview/demos/pages/screenshot.png | Bin 19161 -> 0 bytes .../github.com/rivo/tview/demos/postgres.png | Bin 116586 -> 0 bytes .../rivo/tview/demos/presentation/center.go | 16 - .../rivo/tview/demos/presentation/code.go | 25 - .../rivo/tview/demos/presentation/colors.go | 33 - .../rivo/tview/demos/presentation/cover.go | 63 - .../rivo/tview/demos/presentation/end.go | 18 - .../rivo/tview/demos/presentation/flex.go | 38 - .../rivo/tview/demos/presentation/form.go | 43 - .../rivo/tview/demos/presentation/grid.go | 60 - .../tview/demos/presentation/helloworld.go | 31 - .../tview/demos/presentation/inputfield.go | 40 - .../tview/demos/presentation/introduction.go | 14 - .../rivo/tview/demos/presentation/main.go | 103 - .../rivo/tview/demos/presentation/table.go | 362 - .../rivo/tview/demos/presentation/textview.go | 160 - .../rivo/tview/demos/presentation/treeview.go | 149 - .../rivo/tview/demos/primitive/README.md | 1 - .../demos/primitive/boxwithcenterline.png | Bin 11386 -> 0 bytes .../rivo/tview/demos/primitive/main.go | 92 - .../rivo/tview/demos/primitive/screenshot.png | Bin 15421 -> 0 bytes .../primitive/textviewwithcenterline.png | Bin 13636 -> 0 bytes .../rivo/tview/demos/table/README.md | 1 - .../github.com/rivo/tview/demos/table/main.go | 45 - .../rivo/tview/demos/table/screenshot.png | Bin 56418 -> 0 bytes .../rivo/tview/demos/textview/README.md | 1 - .../rivo/tview/demos/textview/main.go | 69 - .../rivo/tview/demos/textview/screenshot.png | Bin 108938 -> 0 bytes .../rivo/tview/demos/treeview/README.md | 1 - .../rivo/tview/demos/treeview/main.go | 62 - .../rivo/tview/demos/treeview/screenshot.png | Bin 51097 -> 0 bytes .../rivo/tview/demos/unicode/README.md | 1 - .../rivo/tview/demos/unicode/main.go | 49 - .../rivo/tview/demos/unicode/screenshot.png | Bin 11608 -> 0 bytes vendor/github.com/rivo/tview/doc.go | 179 - vendor/github.com/rivo/tview/dropdown.go | 553 - vendor/github.com/rivo/tview/flex.go | 238 - vendor/github.com/rivo/tview/form.go | 684 - vendor/github.com/rivo/tview/frame.go | 192 - vendor/github.com/rivo/tview/go.mod | 12 - vendor/github.com/rivo/tview/go.sum | 24 - vendor/github.com/rivo/tview/grid.go | 697 - vendor/github.com/rivo/tview/inputfield.go | 627 - vendor/github.com/rivo/tview/list.go | 628 - vendor/github.com/rivo/tview/modal.go | 201 - vendor/github.com/rivo/tview/pages.go | 315 - vendor/github.com/rivo/tview/primitive.go | 58 - vendor/github.com/rivo/tview/semigraphics.go | 296 - vendor/github.com/rivo/tview/styles.go | 35 - vendor/github.com/rivo/tview/table.go | 1290 - vendor/github.com/rivo/tview/textview.go | 1185 - vendor/github.com/rivo/tview/treeview.go | 787 - vendor/github.com/rivo/tview/tview.gif | Bin 2226085 -> 0 bytes vendor/github.com/rivo/tview/util.go | 642 - vendor/github.com/rivo/uniseg/README.md | 62 - vendor/github.com/rivo/uniseg/doc.go | 8 - vendor/github.com/rivo/uniseg/go.mod | 3 - vendor/github.com/rivo/uniseg/grapheme.go | 268 - .../github.com/rivo/uniseg/grapheme_test.go | 812 - vendor/github.com/rivo/uniseg/properties.go | 1658 - .../github.com/syndtr/goleveldb/.travis.yml | 13 - vendor/github.com/syndtr/goleveldb/LICENSE | 24 - vendor/github.com/syndtr/goleveldb/README.md | 107 - .../syndtr/goleveldb/leveldb/batch.go | 349 - .../syndtr/goleveldb/leveldb/batch_test.go | 147 - .../syndtr/goleveldb/leveldb/bench_test.go | 507 - .../goleveldb/leveldb/cache/bench_test.go | 29 - .../syndtr/goleveldb/leveldb/cache/cache.go | 705 - .../goleveldb/leveldb/cache/cache_test.go | 563 - .../syndtr/goleveldb/leveldb/cache/lru.go | 195 - .../syndtr/goleveldb/leveldb/comparer.go | 67 - .../leveldb/comparer/bytes_comparer.go | 51 - .../goleveldb/leveldb/comparer/comparer.go | 57 - .../syndtr/goleveldb/leveldb/corrupt_test.go | 496 - .../github.com/syndtr/goleveldb/leveldb/db.go | 1098 - .../syndtr/goleveldb/leveldb/db_compaction.go | 826 - .../syndtr/goleveldb/leveldb/db_iter.go | 360 - .../syndtr/goleveldb/leveldb/db_snapshot.go | 183 - .../syndtr/goleveldb/leveldb/db_state.go | 239 - .../syndtr/goleveldb/leveldb/db_test.go | 2926 - .../goleveldb/leveldb/db_transaction.go | 325 - .../syndtr/goleveldb/leveldb/db_util.go | 102 - .../syndtr/goleveldb/leveldb/db_write.go | 460 - .../syndtr/goleveldb/leveldb/doc.go | 92 - .../syndtr/goleveldb/leveldb/errors.go | 20 - .../syndtr/goleveldb/leveldb/errors/errors.go | 78 - .../syndtr/goleveldb/leveldb/external_test.go | 117 - .../syndtr/goleveldb/leveldb/filter.go | 31 - .../syndtr/goleveldb/leveldb/filter/bloom.go | 116 - .../goleveldb/leveldb/filter/bloom_test.go | 142 - .../syndtr/goleveldb/leveldb/filter/filter.go | 60 - .../goleveldb/leveldb/iterator/array_iter.go | 184 - .../leveldb/iterator/array_iter_test.go | 30 - .../leveldb/iterator/indexed_iter.go | 242 - .../leveldb/iterator/indexed_iter_test.go | 83 - .../syndtr/goleveldb/leveldb/iterator/iter.go | 132 - .../leveldb/iterator/iter_suite_test.go | 11 - .../goleveldb/leveldb/iterator/merged_iter.go | 304 - .../leveldb/iterator/merged_iter_test.go | 60 - .../goleveldb/leveldb/journal/journal.go | 524 - .../goleveldb/leveldb/journal/journal_test.go | 818 - .../syndtr/goleveldb/leveldb/key.go | 143 - .../syndtr/goleveldb/leveldb/key_test.go | 133 - .../goleveldb/leveldb/leveldb_suite_test.go | 11 - .../goleveldb/leveldb/memdb/bench_test.go | 75 - .../syndtr/goleveldb/leveldb/memdb/memdb.go | 475 - .../leveldb/memdb/memdb_suite_test.go | 11 - .../goleveldb/leveldb/memdb/memdb_test.go | 135 - .../syndtr/goleveldb/leveldb/opt/options.go | 684 - .../syndtr/goleveldb/leveldb/options.go | 107 - .../syndtr/goleveldb/leveldb/session.go | 210 - .../goleveldb/leveldb/session_compaction.go | 302 - .../goleveldb/leveldb/session_record.go | 323 - .../goleveldb/leveldb/session_record_test.go | 62 - .../syndtr/goleveldb/leveldb/session_util.go | 271 - .../goleveldb/leveldb/storage/file_storage.go | 599 - .../leveldb/storage/file_storage_nacl.go | 34 - .../leveldb/storage/file_storage_plan9.go | 65 - .../leveldb/storage/file_storage_solaris.go | 81 - .../leveldb/storage/file_storage_test.go | 176 - .../leveldb/storage/file_storage_unix.go | 86 - .../leveldb/storage/file_storage_windows.go | 78 - .../goleveldb/leveldb/storage/mem_storage.go | 218 - .../leveldb/storage/mem_storage_test.go | 65 - .../goleveldb/leveldb/storage/storage.go | 179 - .../syndtr/goleveldb/leveldb/table.go | 529 - .../goleveldb/leveldb/table/block_test.go | 139 - .../syndtr/goleveldb/leveldb/table/reader.go | 1135 - .../syndtr/goleveldb/leveldb/table/table.go | 177 - .../leveldb/table/table_suite_test.go | 11 - .../goleveldb/leveldb/table/table_test.go | 123 - .../syndtr/goleveldb/leveldb/table/writer.go | 375 - .../syndtr/goleveldb/leveldb/testutil/db.go | 222 - .../goleveldb/leveldb/testutil/ginkgo.go | 21 - .../syndtr/goleveldb/leveldb/testutil/iter.go | 327 - .../syndtr/goleveldb/leveldb/testutil/kv.go | 352 - .../goleveldb/leveldb/testutil/kvtest.go | 212 - .../goleveldb/leveldb/testutil/storage.go | 693 - .../syndtr/goleveldb/leveldb/testutil/util.go | 171 - .../syndtr/goleveldb/leveldb/testutil_test.go | 91 - .../syndtr/goleveldb/leveldb/util.go | 98 - .../syndtr/goleveldb/leveldb/util/buffer.go | 293 - .../goleveldb/leveldb/util/buffer_pool.go | 239 - .../goleveldb/leveldb/util/buffer_test.go | 369 - .../syndtr/goleveldb/leveldb/util/crc32.go | 30 - .../syndtr/goleveldb/leveldb/util/hash.go | 48 - .../goleveldb/leveldb/util/hash_test.go | 46 - .../syndtr/goleveldb/leveldb/util/range.go | 32 - .../syndtr/goleveldb/leveldb/util/util.go | 73 - .../syndtr/goleveldb/leveldb/version.go | 528 - .../syndtr/goleveldb/leveldb/version_test.go | 181 - .../goleveldb/manualtest/dbstress/key.go | 137 - .../goleveldb/manualtest/dbstress/main.go | 628 - .../goleveldb/manualtest/filelock/main.go | 85 - vendor/github.com/tinylib/msgp/.gitignore | 8 - vendor/github.com/tinylib/msgp/.travis.yml | 11 - vendor/github.com/tinylib/msgp/LICENSE | 8 - vendor/github.com/tinylib/msgp/Makefile | 52 - vendor/github.com/tinylib/msgp/README.md | 102 - .../tinylib/msgp/_generated/convert.go | 83 - .../tinylib/msgp/_generated/convert_test.go | 59 - .../github.com/tinylib/msgp/_generated/def.go | 265 - .../tinylib/msgp/_generated/def_test.go | 76 - .../tinylib/msgp/_generated/gen_test.go | 150 - .../tinylib/msgp/_generated/issue102.go | 49 - .../tinylib/msgp/_generated/issue191.go | 8 - .../tinylib/msgp/_generated/issue191_test.go | 16 - .../tinylib/msgp/_generated/issue94.go | 31 - .../tinylib/msgp/_generated/search.sh | 12 - vendor/github.com/tinylib/msgp/gen/decode.go | 222 - vendor/github.com/tinylib/msgp/gen/elem.go | 619 - vendor/github.com/tinylib/msgp/gen/encode.go | 198 - vendor/github.com/tinylib/msgp/gen/marshal.go | 212 - vendor/github.com/tinylib/msgp/gen/size.go | 286 - vendor/github.com/tinylib/msgp/gen/spec.go | 383 - vendor/github.com/tinylib/msgp/gen/testgen.go | 182 - .../github.com/tinylib/msgp/gen/unmarshal.go | 205 - .../github.com/tinylib/msgp/issue185_test.go | 308 - vendor/github.com/tinylib/msgp/main.go | 119 - .../tinylib/msgp/msgp/advise_linux.go | 24 - .../tinylib/msgp/msgp/advise_other.go | 17 - .../github.com/tinylib/msgp/msgp/appengine.go | 15 - .../github.com/tinylib/msgp/msgp/circular.go | 39 - vendor/github.com/tinylib/msgp/msgp/defs.go | 142 - .../github.com/tinylib/msgp/msgp/defs_test.go | 12 - vendor/github.com/tinylib/msgp/msgp/edit.go | 241 - .../github.com/tinylib/msgp/msgp/edit_test.go | 200 - vendor/github.com/tinylib/msgp/msgp/elsize.go | 99 - vendor/github.com/tinylib/msgp/msgp/errors.go | 142 - .../github.com/tinylib/msgp/msgp/extension.go | 548 - .../tinylib/msgp/msgp/extension_test.go | 49 - vendor/github.com/tinylib/msgp/msgp/file.go | 92 - .../github.com/tinylib/msgp/msgp/file_port.go | 47 - .../github.com/tinylib/msgp/msgp/file_test.go | 103 - .../tinylib/msgp/msgp/floatbench_test.go | 25 - .../github.com/tinylib/msgp/msgp/integers.go | 174 - vendor/github.com/tinylib/msgp/msgp/json.go | 542 - .../tinylib/msgp/msgp/json_bytes.go | 363 - .../tinylib/msgp/msgp/json_bytes_test.go | 121 - .../github.com/tinylib/msgp/msgp/json_test.go | 142 - vendor/github.com/tinylib/msgp/msgp/number.go | 267 - .../tinylib/msgp/msgp/number_test.go | 94 - .../github.com/tinylib/msgp/msgp/raw_test.go | 85 - vendor/github.com/tinylib/msgp/msgp/read.go | 1265 - .../tinylib/msgp/msgp/read_bytes.go | 1089 - .../tinylib/msgp/msgp/read_bytes_test.go | 518 - .../github.com/tinylib/msgp/msgp/read_test.go | 770 - vendor/github.com/tinylib/msgp/msgp/size.go | 38 - vendor/github.com/tinylib/msgp/msgp/unsafe.go | 41 - vendor/github.com/tinylib/msgp/msgp/write.go | 845 - .../tinylib/msgp/msgp/write_bytes.go | 411 - .../tinylib/msgp/msgp/write_bytes_test.go | 319 - .../tinylib/msgp/msgp/write_test.go | 405 - .../tinylib/msgp/parse/directives.go | 130 - .../github.com/tinylib/msgp/parse/getast.go | 587 - .../github.com/tinylib/msgp/parse/inline.go | 146 - .../github.com/tinylib/msgp/printer/print.go | 129 - .../vmihailenco/msgpack/.travis.yml | 15 - .../vmihailenco/msgpack/CHANGELOG.md | 14 - vendor/github.com/vmihailenco/msgpack/LICENSE | 25 - .../github.com/vmihailenco/msgpack/Makefile | 5 - .../github.com/vmihailenco/msgpack/README.md | 68 - .../vmihailenco/msgpack/appengine.go | 64 - .../vmihailenco/msgpack/bench_test.go | 323 - .../vmihailenco/msgpack/codes/codes.go | 78 - .../github.com/vmihailenco/msgpack/decode.go | 509 - .../vmihailenco/msgpack/decode_map.go | 269 - .../vmihailenco/msgpack/decode_number.go | 302 - .../vmihailenco/msgpack/decode_query.go | 158 - .../vmihailenco/msgpack/decode_slice.go | 192 - .../vmihailenco/msgpack/decode_string.go | 175 - .../vmihailenco/msgpack/decode_value.go | 276 - .../github.com/vmihailenco/msgpack/encode.go | 140 - .../vmihailenco/msgpack/encode_map.go | 166 - .../vmihailenco/msgpack/encode_number.go | 108 - .../vmihailenco/msgpack/encode_slice.go | 115 - .../vmihailenco/msgpack/encode_value.go | 167 - .../msgpack/example_CustomEncoder_test.go | 38 - .../msgpack/example_registerExt_test.go | 62 - .../vmihailenco/msgpack/example_test.go | 195 - vendor/github.com/vmihailenco/msgpack/ext.go | 188 - .../vmihailenco/msgpack/ext_test.go | 116 - .../github.com/vmihailenco/msgpack/msgpack.go | 17 - .../vmihailenco/msgpack/msgpack_test.go | 274 - vendor/github.com/vmihailenco/msgpack/tag.go | 42 - vendor/github.com/vmihailenco/msgpack/time.go | 135 - .../github.com/vmihailenco/msgpack/types.go | 280 - .../vmihailenco/msgpack/types_test.go | 924 - .../x448/float16/.github/workflows/ci.yml | 23 + .../x448/float16/.github/workflows/cover.yml | 32 + .../float16/.github/workflows/linters.yml | 24 + vendor/github.com/x448/float16/.golangci.yml | 73 + .../LICENSE.txt => x448/float16/LICENSE} | 2 +- vendor/github.com/x448/float16/README.md | 135 + vendor/github.com/x448/float16/float16.go | 302 + .../x448/float16/float16_bench_test.go | 88 + .../github.com/x448/float16/float16_test.go | 798 + vendor/github.com/x448/float16/go.mod | 3 + vendor/github.com/ybbus/jsonrpc/LICENSE | 2 +- vendor/github.com/ybbus/jsonrpc/README.md | 425 +- .../github.com/ybbus/jsonrpc/examples_test.go | 116 - vendor/github.com/ybbus/jsonrpc/go.mod | 5 + vendor/github.com/ybbus/jsonrpc/go.sum | 13 + vendor/github.com/ybbus/jsonrpc/jsonrpc.go | 822 +- .../github.com/ybbus/jsonrpc/jsonrpc_test.go | 1516 +- .../x/crypto/internal/subtle/aliasing.go | 32 + .../internal/subtle/aliasing_appengine.go | 35 + .../x/crypto/internal/subtle/aliasing_test.go | 50 + .../x/crypto/internal/wycheproof/README.md | 12 + .../x/crypto/internal/wycheproof/aead_test.go | 176 + .../internal/wycheproof/aes_cbc_test.go | 127 + .../x/crypto/internal/wycheproof/dsa_test.go | 123 + .../internal/wycheproof/ecdsa_compat_test.go | 33 + .../internal/wycheproof/ecdsa_go115_test.go | 15 + .../crypto/internal/wycheproof/ecdsa_test.go | 164 + .../crypto/internal/wycheproof/eddsa_test.go | 100 + .../x/crypto/internal/wycheproof/hkdf_test.go | 111 + .../x/crypto/internal/wycheproof/hmac_test.go | 105 + .../internal/wycheproof/internal/dsa/dsa.go | 33 + .../internal/wycheproof/rsa_pss_test.go | 164 + .../internal/wycheproof/rsa_signature_test.go | 123 + .../internal/wycheproof/wycheproof_test.go | 134 + vendor/golang.org/x/tools/.directory | 6 - vendor/golang.org/x/tools/.gitattributes | 10 - vendor/golang.org/x/tools/.gitignore | 2 - vendor/golang.org/x/tools/AUTHORS | 3 - vendor/golang.org/x/tools/CONTRIBUTING.md | 31 - vendor/golang.org/x/tools/CONTRIBUTORS | 3 - vendor/golang.org/x/tools/PATENTS | 22 - vendor/golang.org/x/tools/README.md | 27 - .../x/tools/benchmark/parse/parse.go | 131 - .../x/tools/benchmark/parse/parse_test.go | 154 - vendor/golang.org/x/tools/blog/atom/atom.go | 61 - vendor/golang.org/x/tools/blog/blog.go | 424 - .../x/tools/cmd/benchcmp/benchcmp.go | 184 - .../x/tools/cmd/benchcmp/benchcmp_test.go | 59 - .../x/tools/cmd/benchcmp/compare.go | 156 - .../x/tools/cmd/benchcmp/compare_test.go | 133 - vendor/golang.org/x/tools/cmd/benchcmp/doc.go | 37 - .../golang.org/x/tools/cmd/bundle/.gitignore | 1 - vendor/golang.org/x/tools/cmd/bundle/main.go | 468 - .../x/tools/cmd/bundle/main_test.go | 74 - .../x/tools/cmd/bundle/testdata/out.golden | 62 - .../testdata/src/domain.name/importdecl/p.go | 3 - .../cmd/bundle/testdata/src/initial/a.go | 27 - .../cmd/bundle/testdata/src/initial/b.go | 23 - .../cmd/bundle/testdata/src/initial/c.go | 12 - .../golang.org/x/tools/cmd/callgraph/main.go | 361 - .../x/tools/cmd/callgraph/main_test.go | 81 - .../cmd/callgraph/testdata/src/pkg/pkg.go | 25 - .../callgraph/testdata/src/pkg/pkg_test.go | 7 - .../x/tools/cmd/compilebench/main.go | 360 - vendor/golang.org/x/tools/cmd/cover/README | 2 - vendor/golang.org/x/tools/cmd/cover/cover.go | 722 - .../x/tools/cmd/cover/cover_test.go | 94 - vendor/golang.org/x/tools/cmd/cover/doc.go | 27 - vendor/golang.org/x/tools/cmd/cover/func.go | 166 - vendor/golang.org/x/tools/cmd/cover/html.go | 284 - .../x/tools/cmd/cover/testdata/main.go | 112 - .../x/tools/cmd/cover/testdata/test.go | 218 - .../golang.org/x/tools/cmd/digraph/digraph.go | 540 - .../x/tools/cmd/digraph/digraph_test.go | 121 - vendor/golang.org/x/tools/cmd/eg/eg.go | 150 - .../golang.org/x/tools/cmd/fiximports/main.go | 511 - .../x/tools/cmd/fiximports/main_test.go | 243 - .../testdata/src/fruit.io/banana/banana.go | 7 - .../testdata/src/fruit.io/orange/orange.go | 3 - .../testdata/src/fruit.io/pear/pear.go | 3 - .../testdata/src/new.com/one/one.go | 1 - .../testdata/src/old.com/bad/bad.go | 2 - .../testdata/src/old.com/one/one.go | 1 - .../testdata/src/titanic.biz/bar/bar.go | 2 - .../testdata/src/titanic.biz/foo/foo.go | 2 - .../x/tools/cmd/getgo/.dockerignore | 5 - .../golang.org/x/tools/cmd/getgo/.gitignore | 3 - .../golang.org/x/tools/cmd/getgo/Dockerfile | 20 - vendor/golang.org/x/tools/cmd/getgo/LICENSE | 27 - vendor/golang.org/x/tools/cmd/getgo/README.md | 71 - .../golang.org/x/tools/cmd/getgo/download.go | 184 - .../x/tools/cmd/getgo/download_test.go | 36 - vendor/golang.org/x/tools/cmd/getgo/main.go | 117 - .../golang.org/x/tools/cmd/getgo/main_test.go | 173 - vendor/golang.org/x/tools/cmd/getgo/make.bash | 13 - vendor/golang.org/x/tools/cmd/getgo/path.go | 155 - .../golang.org/x/tools/cmd/getgo/path_test.go | 58 - .../x/tools/cmd/getgo/server/README.md | 7 - .../x/tools/cmd/getgo/server/app.yaml | 7 - .../x/tools/cmd/getgo/server/main.go | 61 - vendor/golang.org/x/tools/cmd/getgo/steps.go | 133 - vendor/golang.org/x/tools/cmd/getgo/system.go | 38 - .../x/tools/cmd/getgo/system_unix.go | 55 - .../x/tools/cmd/getgo/system_windows.go | 86 - .../golang.org/x/tools/cmd/getgo/upload.bash | 19 - .../x/tools/cmd/go-contrib-init/contrib.go | 289 - .../tools/cmd/go-contrib-init/contrib_test.go | 35 - vendor/golang.org/x/tools/cmd/godex/doc.go | 69 - vendor/golang.org/x/tools/cmd/godex/gc.go | 13 - vendor/golang.org/x/tools/cmd/godex/gccgo.go | 34 - vendor/golang.org/x/tools/cmd/godex/godex.go | 211 - .../golang.org/x/tools/cmd/godex/isAlias18.go | 13 - .../golang.org/x/tools/cmd/godex/isAlias19.go | 13 - vendor/golang.org/x/tools/cmd/godex/print.go | 373 - vendor/golang.org/x/tools/cmd/godex/source.go | 19 - .../golang.org/x/tools/cmd/godex/writetype.go | 242 - .../x/tools/cmd/godoc/README.godoc-app | 56 - .../golang.org/x/tools/cmd/godoc/appinit.go | 82 - .../golang.org/x/tools/cmd/godoc/autocert.go | 77 - vendor/golang.org/x/tools/cmd/godoc/blog.go | 81 - .../golang.org/x/tools/cmd/godoc/codewalk.go | 523 - vendor/golang.org/x/tools/cmd/godoc/dl.go | 16 - vendor/golang.org/x/tools/cmd/godoc/doc.go | 153 - .../x/tools/cmd/godoc/godoc19_test.go | 9 - .../x/tools/cmd/godoc/godoc_test.go | 471 - .../golang.org/x/tools/cmd/godoc/handlers.go | 147 - vendor/golang.org/x/tools/cmd/godoc/index.go | 11 - vendor/golang.org/x/tools/cmd/godoc/main.go | 345 - vendor/golang.org/x/tools/cmd/godoc/play.go | 11 - .../x/tools/cmd/godoc/remotesearch.go | 72 - .../x/tools/cmd/godoc/setup-godoc-app.bash | 134 - vendor/golang.org/x/tools/cmd/godoc/x.go | 90 - .../golang.org/x/tools/cmd/goimports/doc.go | 45 - .../x/tools/cmd/goimports/goimports.go | 369 - .../x/tools/cmd/goimports/goimports_gc.go | 26 - .../x/tools/cmd/goimports/goimports_not_gc.go | 11 - vendor/golang.org/x/tools/cmd/gomvpkg/main.go | 94 - .../x/tools/cmd/gorename/cgo_test.go | 11 - .../x/tools/cmd/gorename/gorename_test.go | 380 - .../golang.org/x/tools/cmd/gorename/main.go | 55 - .../golang.org/x/tools/cmd/gotype/gotype.go | 341 - .../x/tools/cmd/gotype/sizesFor18.go | 40 - .../x/tools/cmd/gotype/sizesFor19.go | 15 - vendor/golang.org/x/tools/cmd/goyacc/doc.go | 70 - .../x/tools/cmd/goyacc/testdata/expr/README | 20 - .../x/tools/cmd/goyacc/testdata/expr/expr.y | 202 - .../x/tools/cmd/goyacc/testdata/expr/main.go | 14 - vendor/golang.org/x/tools/cmd/goyacc/yacc.go | 3591 - vendor/golang.org/x/tools/cmd/guru/callees.go | 257 - vendor/golang.org/x/tools/cmd/guru/callers.go | 195 - .../golang.org/x/tools/cmd/guru/callstack.go | 141 - .../golang.org/x/tools/cmd/guru/definition.go | 205 - .../golang.org/x/tools/cmd/guru/describe.go | 899 - .../golang.org/x/tools/cmd/guru/freevars.go | 223 - vendor/golang.org/x/tools/cmd/guru/guru.go | 401 - .../golang.org/x/tools/cmd/guru/guru_test.go | 329 - .../golang.org/x/tools/cmd/guru/implements.go | 364 - .../golang.org/x/tools/cmd/guru/isAlias18.go | 15 - .../golang.org/x/tools/cmd/guru/isAlias19.go | 15 - vendor/golang.org/x/tools/cmd/guru/main.go | 215 - vendor/golang.org/x/tools/cmd/guru/peers.go | 252 - .../golang.org/x/tools/cmd/guru/pointsto.go | 290 - vendor/golang.org/x/tools/cmd/guru/pos.go | 142 - .../golang.org/x/tools/cmd/guru/referrers.go | 522 - .../x/tools/cmd/guru/serial/serial.go | 259 - .../x/tools/cmd/guru/testdata/src/README.txt | 2 - .../cmd/guru/testdata/src/alias/alias.go | 23 - .../cmd/guru/testdata/src/alias/alias.golden | 47 - .../cmd/guru/testdata/src/calls-json/main.go | 16 - .../guru/testdata/src/calls-json/main.golden | 28 - .../tools/cmd/guru/testdata/src/calls/main.go | 129 - .../cmd/guru/testdata/src/calls/main.golden | 125 - .../guru/testdata/src/definition-json/main.go | 66 - .../testdata/src/definition-json/main.golden | 90 - .../testdata/src/definition-json/main19.go | 5 - .../src/definition-json/main19.golden | 5 - .../guru/testdata/src/definition-json/type.go | 3 - .../guru/testdata/src/describe-json/main.go | 29 - .../testdata/src/describe-json/main.golden | 96 - .../cmd/guru/testdata/src/describe/main.go | 102 - .../guru/testdata/src/describe/main.golden | 212 - .../cmd/guru/testdata/src/describe/main19.go | 13 - .../guru/testdata/src/describe/main19.golden | 6 - .../cmd/guru/testdata/src/freevars/main.go | 40 - .../guru/testdata/src/freevars/main.golden | 25 - .../guru/testdata/src/implements-json/main.go | 27 - .../testdata/src/implements-json/main.golden | 135 - .../src/implements-methods-json/main.go | 37 - .../src/implements-methods-json/main.golden | 266 - .../testdata/src/implements-methods/main.go | 37 - .../src/implements-methods/main.golden | 37 - .../cmd/guru/testdata/src/implements/main.go | 44 - .../guru/testdata/src/implements/main.golden | 50 - .../cmd/guru/testdata/src/imports/main.go | 29 - .../cmd/guru/testdata/src/imports/main.golden | 56 - .../x/tools/cmd/guru/testdata/src/lib/lib.go | 37 - .../guru/testdata/src/lib/sublib/sublib.go | 3 - .../tools/cmd/guru/testdata/src/main/multi.go | 13 - .../cmd/guru/testdata/src/peers-json/main.go | 13 - .../guru/testdata/src/peers-json/main.golden | 12 - .../tools/cmd/guru/testdata/src/peers/main.go | 52 - .../cmd/guru/testdata/src/peers/main.golden | 100 - .../guru/testdata/src/pointsto-json/main.go | 27 - .../testdata/src/pointsto-json/main.golden | 29 - .../cmd/guru/testdata/src/pointsto/main.go | 75 - .../guru/testdata/src/pointsto/main.golden | 96 - .../guru/testdata/src/referrers-json/main.go | 24 - .../testdata/src/referrers-json/main.golden | 234 - .../guru/testdata/src/referrers/ext_test.go | 12 - .../guru/testdata/src/referrers/int_test.go | 8 - .../cmd/guru/testdata/src/referrers/main.go | 34 - .../guru/testdata/src/referrers/main.golden | 60 - .../cmd/guru/testdata/src/reflection/main.go | 30 - .../guru/testdata/src/reflection/main.golden | 34 - .../cmd/guru/testdata/src/softerrs/main.go | 15 - .../guru/testdata/src/softerrs/main.golden | 8 - .../cmd/guru/testdata/src/what-json/main.go | 14 - .../guru/testdata/src/what-json/main.golden | 95 - .../tools/cmd/guru/testdata/src/what/main.go | 11 - .../cmd/guru/testdata/src/what/main.golden | 41 - .../cmd/guru/testdata/src/whicherrs/main.go | 32 - .../guru/testdata/src/whicherrs/main.golden | 11 - .../golang.org/x/tools/cmd/guru/unit_test.go | 105 - vendor/golang.org/x/tools/cmd/guru/what.go | 282 - .../golang.org/x/tools/cmd/guru/whicherrs.go | 327 - .../x/tools/cmd/heapview/client/.clang-format | 1 - .../x/tools/cmd/heapview/client/.gitignore | 1 - .../x/tools/cmd/heapview/client/README.md | 45 - .../x/tools/cmd/heapview/client/main.ts | 195 - .../x/tools/cmd/heapview/client/main_test.ts | 29 - .../x/tools/cmd/heapview/client/package.json | 35 - .../cmd/heapview/client/testing/karma.conf.js | 22 - .../cmd/heapview/client/testing/test_main.js | 32 - .../x/tools/cmd/heapview/client/tsconfig.json | 16 - .../x/tools/cmd/heapview/client/tslint.json | 40 - .../cmd/heapview/internal/core/mmapfile.go | 145 - .../heapview/internal/core/mmapfile_other.go | 14 - .../x/tools/cmd/heapview/internal/core/raw.go | 308 - .../golang.org/x/tools/cmd/heapview/main.go | 83 - .../x/tools/cmd/html2article/conv.go | 331 - .../x/tools/cmd/present/appengine.go | 22 - vendor/golang.org/x/tools/cmd/present/dir.go | 214 - vendor/golang.org/x/tools/cmd/present/doc.go | 57 - .../golang.org/x/tools/cmd/present/local.go | 133 - vendor/golang.org/x/tools/cmd/present/play.go | 43 - .../x/tools/cmd/present/play_http.go | 23 - .../x/tools/cmd/present/play_socket.go | 36 - .../x/tools/cmd/present/static/article.css | 161 - .../x/tools/cmd/present/static/dir.css | 186 - .../x/tools/cmd/present/static/dir.js | 41 - .../x/tools/cmd/present/static/favicon.ico | Bin 785 -> 0 bytes .../x/tools/cmd/present/static/jquery-ui.js | 6 - .../x/tools/cmd/present/static/notes.css | 32 - .../x/tools/cmd/present/static/notes.js | 161 - .../x/tools/cmd/present/static/slides.js | 612 - .../x/tools/cmd/present/static/styles.css | 534 - .../x/tools/cmd/present/templates/action.tmpl | 62 - .../tools/cmd/present/templates/article.tmpl | 98 - .../x/tools/cmd/present/templates/dir.tmpl | 108 - .../x/tools/cmd/present/templates/slides.tmpl | 106 - vendor/golang.org/x/tools/cmd/ssadump/main.go | 180 - .../golang.org/x/tools/cmd/stress/stress.go | 133 - .../x/tools/cmd/stringer/endtoend_test.go | 111 - .../x/tools/cmd/stringer/golden_test.go | 319 - .../x/tools/cmd/stringer/importer18.go | 16 - .../x/tools/cmd/stringer/importer19.go | 16 - .../x/tools/cmd/stringer/stringer.go | 655 - .../x/tools/cmd/stringer/testdata/cgo.go | 32 - .../x/tools/cmd/stringer/testdata/day.go | 39 - .../x/tools/cmd/stringer/testdata/gap.go | 44 - .../x/tools/cmd/stringer/testdata/num.go | 35 - .../x/tools/cmd/stringer/testdata/number.go | 34 - .../x/tools/cmd/stringer/testdata/prime.go | 56 - .../x/tools/cmd/stringer/testdata/unum.go | 38 - .../x/tools/cmd/stringer/testdata/unum2.go | 31 - .../x/tools/cmd/stringer/util_test.go | 77 - vendor/golang.org/x/tools/cmd/tip/Dockerfile | 132 - vendor/golang.org/x/tools/cmd/tip/Makefile | 19 - vendor/golang.org/x/tools/cmd/tip/README | 32 - vendor/golang.org/x/tools/cmd/tip/cert.go | 50 - vendor/golang.org/x/tools/cmd/tip/godoc.go | 73 - vendor/golang.org/x/tools/cmd/tip/godoc.yaml | 17 - vendor/golang.org/x/tools/cmd/tip/talks.go | 73 - vendor/golang.org/x/tools/cmd/tip/talks.yaml | 12 - vendor/golang.org/x/tools/cmd/tip/tip-rc.yaml | 40 - .../x/tools/cmd/tip/tip-service.yaml | 16 - vendor/golang.org/x/tools/cmd/tip/tip.go | 402 - vendor/golang.org/x/tools/cmd/tip/tip_test.go | 25 - .../golang.org/x/tools/cmd/toolstash/buildall | 68 - .../golang.org/x/tools/cmd/toolstash/cmp.go | 157 - .../golang.org/x/tools/cmd/toolstash/main.go | 637 - vendor/golang.org/x/tools/codereview.cfg | 1 - .../x/tools/container/intsets/popcnt_amd64.go | 20 - .../x/tools/container/intsets/popcnt_amd64.s | 30 - .../x/tools/container/intsets/popcnt_gccgo.go | 9 - .../tools/container/intsets/popcnt_gccgo_c.c | 19 - .../tools/container/intsets/popcnt_generic.go | 33 - .../x/tools/container/intsets/sparse.go | 1091 - .../x/tools/container/intsets/sparse_test.go | 710 - .../x/tools/container/intsets/util.go | 84 - .../x/tools/container/intsets/util_test.go | 58 - vendor/golang.org/x/tools/cover/profile.go | 213 - .../x/tools/go/ast/astutil/enclosing.go | 627 - .../x/tools/go/ast/astutil/enclosing_test.go | 195 - .../x/tools/go/ast/astutil/imports.go | 470 - .../x/tools/go/ast/astutil/imports_test.go | 1818 - .../x/tools/go/ast/astutil/rewrite.go | 477 - .../x/tools/go/ast/astutil/rewrite_test.go | 248 - .../golang.org/x/tools/go/ast/astutil/util.go | 14 - .../x/tools/go/buildutil/allpackages.go | 198 - .../x/tools/go/buildutil/allpackages_test.go | 78 - .../x/tools/go/buildutil/fakecontext.go | 108 - .../x/tools/go/buildutil/overlay.go | 103 - .../x/tools/go/buildutil/overlay_test.go | 70 - .../golang.org/x/tools/go/buildutil/tags.go | 75 - .../x/tools/go/buildutil/tags_test.go | 28 - .../golang.org/x/tools/go/buildutil/util.go | 212 - .../x/tools/go/buildutil/util_test.go | 72 - .../x/tools/go/buildutil/util_windows_test.go | 48 - .../x/tools/go/callgraph/callgraph.go | 129 - .../x/tools/go/callgraph/cha/cha.go | 125 - .../x/tools/go/callgraph/cha/cha_test.go | 110 - .../x/tools/go/callgraph/cha/testdata/func.go | 23 - .../tools/go/callgraph/cha/testdata/iface.go | 65 - .../x/tools/go/callgraph/cha/testdata/recv.go | 37 - .../x/tools/go/callgraph/rta/rta.go | 459 - .../x/tools/go/callgraph/rta/rta_test.go | 139 - .../x/tools/go/callgraph/rta/testdata/func.go | 37 - .../tools/go/callgraph/rta/testdata/iface.go | 79 - .../tools/go/callgraph/rta/testdata/rtype.go | 35 - .../x/tools/go/callgraph/static/static.go | 35 - .../tools/go/callgraph/static/static_test.go | 88 - .../golang.org/x/tools/go/callgraph/util.go | 181 - .../go/gccgoexportdata/gccgoexportdata.go | 129 - .../gccgoexportdata/gccgoexportdata_test.go | 67 - .../go/gccgoexportdata/testdata/errors.gox | Bin 24632 -> 0 bytes .../tools/go/gccgoexportdata/testdata/long.a | Bin 802 -> 0 bytes .../tools/go/gccgoexportdata/testdata/short.a | Bin 710 -> 0 bytes .../x/tools/go/gcexportdata/example_test.go | 121 - .../x/tools/go/gcexportdata/gcexportdata.go | 100 - .../go/gcexportdata/gcexportdata_test.go | 41 - .../x/tools/go/gcexportdata/importer.go | 73 - .../x/tools/go/gcexportdata/main.go | 81 - .../go/gcexportdata/testdata/errors-ae16.a | Bin 5494 -> 0 bytes .../x/tools/go/gcimporter15/bexport.go | 828 - .../x/tools/go/gcimporter15/bexport19_test.go | 93 - .../x/tools/go/gcimporter15/bexport_test.go | 326 - .../x/tools/go/gcimporter15/bimport.go | 993 - .../x/tools/go/gcimporter15/exportdata.go | 93 - .../x/tools/go/gcimporter15/gcimporter.go | 1041 - .../tools/go/gcimporter15/gcimporter_test.go | 521 - .../x/tools/go/gcimporter15/isAlias18.go | 13 - .../x/tools/go/gcimporter15/isAlias19.go | 13 - .../x/tools/go/gcimporter15/testdata/a.go | 14 - .../x/tools/go/gcimporter15/testdata/b.go | 11 - .../tools/go/gcimporter15/testdata/exports.go | 89 - .../go/gcimporter15/testdata/issue15920.go | 11 - .../go/gcimporter15/testdata/issue20046.go | 9 - .../x/tools/go/gcimporter15/testdata/p.go | 13 - .../go/gcimporter15/testdata/versions/test.go | 25 - .../testdata/versions/test_go1.7_0.a | Bin 1862 -> 0 bytes .../testdata/versions/test_go1.7_1.a | Bin 2316 -> 0 bytes .../go/internal/gccgoimporter/backdoor.go | 28 - .../gccgoimporter/gccgoinstallation.go | 99 - .../gccgoimporter/gccgoinstallation_test.go | 195 - .../go/internal/gccgoimporter/importer.go | 209 - .../internal/gccgoimporter/importer19_test.go | 15 - .../internal/gccgoimporter/importer_test.go | 178 - .../tools/go/internal/gccgoimporter/parser.go | 902 - .../go/internal/gccgoimporter/parser_test.go | 79 - .../internal/gccgoimporter/testdata/alias.gox | 4 - .../gccgoimporter/testdata/complexnums.go | 6 - .../gccgoimporter/testdata/complexnums.gox | 8 - .../gccgoimporter/testdata/conversions.go | 5 - .../gccgoimporter/testdata/conversions.gox | 6 - .../gccgoimporter/testdata/imports.go | 5 - .../gccgoimporter/testdata/imports.gox | 7 - .../gccgoimporter/testdata/pointer.go | 3 - .../gccgoimporter/testdata/pointer.gox | 4 - .../internal/gccgoimporter/testdata/time.gox | Bin 7977 -> 0 bytes .../gccgoimporter/testdata/unicode.gox | Bin 7945 -> 0 bytes .../go/internal/gccgoimporter/testenv_test.go | 40 - vendor/golang.org/x/tools/go/loader/cgo.go | 207 - .../x/tools/go/loader/cgo_pkgconfig.go | 39 - vendor/golang.org/x/tools/go/loader/doc.go | 205 - .../x/tools/go/loader/example_test.go | 179 - vendor/golang.org/x/tools/go/loader/loader.go | 1077 - .../x/tools/go/loader/loader_test.go | 816 - .../x/tools/go/loader/stdlib_test.go | 201 - .../x/tools/go/loader/testdata/a.go | 1 - .../x/tools/go/loader/testdata/b.go | 1 - .../x/tools/go/loader/testdata/badpkgdecl.go | 1 - vendor/golang.org/x/tools/go/loader/util.go | 124 - vendor/golang.org/x/tools/go/pointer/TODO | 33 - .../golang.org/x/tools/go/pointer/analysis.go | 452 - vendor/golang.org/x/tools/go/pointer/api.go | 285 - .../x/tools/go/pointer/callgraph.go | 61 - .../x/tools/go/pointer/constraint.go | 149 - vendor/golang.org/x/tools/go/pointer/doc.go | 610 - .../x/tools/go/pointer/example_test.go | 126 - vendor/golang.org/x/tools/go/pointer/gen.go | 1325 - vendor/golang.org/x/tools/go/pointer/hvn.go | 973 - .../x/tools/go/pointer/intrinsics.go | 361 - .../golang.org/x/tools/go/pointer/labels.go | 152 - vendor/golang.org/x/tools/go/pointer/opt.go | 132 - .../x/tools/go/pointer/pointer_test.go | 601 - vendor/golang.org/x/tools/go/pointer/print.go | 43 - vendor/golang.org/x/tools/go/pointer/query.go | 221 - .../x/tools/go/pointer/query_test.go | 68 - .../golang.org/x/tools/go/pointer/reflect.go | 1975 - vendor/golang.org/x/tools/go/pointer/solve.go | 370 - .../x/tools/go/pointer/stdlib_test.go | 111 - .../x/tools/go/pointer/testdata/a_test.go | 42 - .../x/tools/go/pointer/testdata/another.go | 36 - .../tools/go/pointer/testdata/arrayreflect.go | 191 - .../x/tools/go/pointer/testdata/arrays.go | 97 - .../x/tools/go/pointer/testdata/channels.go | 118 - .../tools/go/pointer/testdata/chanreflect.go | 85 - .../tools/go/pointer/testdata/chanreflect1.go | 35 - .../x/tools/go/pointer/testdata/context.go | 48 - .../x/tools/go/pointer/testdata/conv.go | 63 - .../x/tools/go/pointer/testdata/extended.go | 21 - .../x/tools/go/pointer/testdata/finalizer.go | 89 - .../x/tools/go/pointer/testdata/flow.go | 63 - .../x/tools/go/pointer/testdata/fmtexcerpt.go | 42 - .../x/tools/go/pointer/testdata/func.go | 205 - .../tools/go/pointer/testdata/funcreflect.go | 130 - .../x/tools/go/pointer/testdata/hello.go | 27 - .../x/tools/go/pointer/testdata/interfaces.go | 152 - .../x/tools/go/pointer/testdata/issue9002.go | 17 - .../x/tools/go/pointer/testdata/mapreflect.go | 117 - .../x/tools/go/pointer/testdata/maps.go | 74 - .../x/tools/go/pointer/testdata/panic.go | 36 - .../x/tools/go/pointer/testdata/recur.go | 11 - .../x/tools/go/pointer/testdata/reflect.go | 115 - .../x/tools/go/pointer/testdata/rtti.go | 29 - .../go/pointer/testdata/structreflect.go | 45 - .../x/tools/go/pointer/testdata/structs.go | 100 - .../x/tools/go/pointer/testdata/timer.go | 24 - vendor/golang.org/x/tools/go/pointer/util.go | 313 - vendor/golang.org/x/tools/go/ssa/blockopt.go | 187 - vendor/golang.org/x/tools/go/ssa/builder.go | 2379 - .../golang.org/x/tools/go/ssa/builder_test.go | 500 - vendor/golang.org/x/tools/go/ssa/const.go | 169 - vendor/golang.org/x/tools/go/ssa/create.go | 263 - vendor/golang.org/x/tools/go/ssa/doc.go | 123 - vendor/golang.org/x/tools/go/ssa/dom.go | 341 - vendor/golang.org/x/tools/go/ssa/emit.go | 468 - .../golang.org/x/tools/go/ssa/example_test.go | 138 - vendor/golang.org/x/tools/go/ssa/func.go | 689 - vendor/golang.org/x/tools/go/ssa/identical.go | 7 - .../golang.org/x/tools/go/ssa/identical_17.go | 7 - .../x/tools/go/ssa/identical_test.go | 9 - .../x/tools/go/ssa/interp/external.go | 537 - .../x/tools/go/ssa/interp/external_darwin.go | 35 - .../x/tools/go/ssa/interp/external_unix.go | 256 - .../x/tools/go/ssa/interp/interp.go | 764 - .../x/tools/go/ssa/interp/interp_test.go | 313 - .../golang.org/x/tools/go/ssa/interp/map.go | 121 - .../golang.org/x/tools/go/ssa/interp/ops.go | 1396 - .../x/tools/go/ssa/interp/reflect.go | 574 - .../x/tools/go/ssa/interp/testdata/a_test.go | 17 - .../x/tools/go/ssa/interp/testdata/b_test.go | 11 - .../tools/go/ssa/interp/testdata/boundmeth.go | 144 - .../x/tools/go/ssa/interp/testdata/c_test.go | 17 - .../tools/go/ssa/interp/testdata/callstack.go | 52 - .../x/tools/go/ssa/interp/testdata/complit.go | 184 - .../tools/go/ssa/interp/testdata/coverage.go | 534 - .../x/tools/go/ssa/interp/testdata/defer.go | 53 - .../tools/go/ssa/interp/testdata/fieldprom.go | 114 - .../tools/go/ssa/interp/testdata/ifaceconv.go | 83 - .../tools/go/ssa/interp/testdata/ifaceprom.go | 58 - .../tools/go/ssa/interp/testdata/initorder.go | 67 - .../tools/go/ssa/interp/testdata/methprom.go | 93 - .../tools/go/ssa/interp/testdata/mrvchain.go | 75 - .../x/tools/go/ssa/interp/testdata/range.go | 55 - .../x/tools/go/ssa/interp/testdata/recover.go | 34 - .../x/tools/go/ssa/interp/testdata/reflect.go | 11 - .../x/tools/go/ssa/interp/testdata/static.go | 58 - .../golang.org/x/tools/go/ssa/interp/value.go | 497 - vendor/golang.org/x/tools/go/ssa/lift.go | 653 - vendor/golang.org/x/tools/go/ssa/lvalue.go | 120 - vendor/golang.org/x/tools/go/ssa/methods.go | 239 - vendor/golang.org/x/tools/go/ssa/mode.go | 100 - vendor/golang.org/x/tools/go/ssa/print.go | 431 - vendor/golang.org/x/tools/go/ssa/sanity.go | 521 - vendor/golang.org/x/tools/go/ssa/source.go | 293 - .../golang.org/x/tools/go/ssa/source_test.go | 397 - vendor/golang.org/x/tools/go/ssa/ssa.go | 1696 - .../golang.org/x/tools/go/ssa/ssautil/load.go | 95 - .../x/tools/go/ssa/ssautil/load_test.go | 64 - .../x/tools/go/ssa/ssautil/switch.go | 234 - .../x/tools/go/ssa/ssautil/switch_test.go | 74 - .../tools/go/ssa/ssautil/testdata/switches.go | 357 - .../x/tools/go/ssa/ssautil/visit.go | 79 - .../golang.org/x/tools/go/ssa/stdlib_test.go | 151 - .../x/tools/go/ssa/testdata/objlookup.go | 160 - .../x/tools/go/ssa/testdata/structconv.go | 24 - .../x/tools/go/ssa/testdata/valueforexpr.go | 152 - vendor/golang.org/x/tools/go/ssa/testmain.go | 265 - .../x/tools/go/ssa/testmain_test.go | 124 - vendor/golang.org/x/tools/go/ssa/util.go | 119 - vendor/golang.org/x/tools/go/ssa/wrappers.go | 294 - .../x/tools/go/types/typeutil/example_test.go | 67 - .../x/tools/go/types/typeutil/imports.go | 31 - .../x/tools/go/types/typeutil/imports_test.go | 80 - .../x/tools/go/types/typeutil/map.go | 313 - .../x/tools/go/types/typeutil/map_test.go | 174 - .../tools/go/types/typeutil/methodsetcache.go | 72 - .../x/tools/go/types/typeutil/ui.go | 52 - .../x/tools/go/types/typeutil/ui_test.go | 61 - vendor/golang.org/x/tools/go/vcs/discovery.go | 76 - vendor/golang.org/x/tools/go/vcs/env.go | 39 - vendor/golang.org/x/tools/go/vcs/http.go | 80 - vendor/golang.org/x/tools/go/vcs/vcs.go | 731 - vendor/golang.org/x/tools/go/vcs/vcs_test.go | 142 - vendor/golang.org/x/tools/godoc/README.md | 31 - .../golang.org/x/tools/godoc/analysis/README | 111 - .../x/tools/godoc/analysis/analysis.go | 613 - .../x/tools/godoc/analysis/callgraph.go | 351 - .../x/tools/godoc/analysis/implements.go | 195 - .../golang.org/x/tools/godoc/analysis/json.go | 69 - .../x/tools/godoc/analysis/peers.go | 154 - .../x/tools/godoc/analysis/typeinfo.go | 234 - vendor/golang.org/x/tools/godoc/cmdline.go | 207 - .../golang.org/x/tools/godoc/cmdline_test.go | 294 - vendor/golang.org/x/tools/godoc/corpus.go | 157 - vendor/golang.org/x/tools/godoc/dirtrees.go | 342 - vendor/golang.org/x/tools/godoc/dl/dl.go | 547 - vendor/golang.org/x/tools/godoc/dl/dl_test.go | 137 - vendor/golang.org/x/tools/godoc/dl/tmpl.go | 279 - vendor/golang.org/x/tools/godoc/format.go | 371 - vendor/golang.org/x/tools/godoc/godoc.go | 911 - .../golang.org/x/tools/godoc/godoc17_test.go | 35 - vendor/golang.org/x/tools/godoc/godoc_test.go | 323 - vendor/golang.org/x/tools/godoc/index.go | 1581 - vendor/golang.org/x/tools/godoc/index_test.go | 323 - vendor/golang.org/x/tools/godoc/linkify.go | 195 - vendor/golang.org/x/tools/godoc/meta.go | 144 - vendor/golang.org/x/tools/godoc/page.go | 76 - vendor/golang.org/x/tools/godoc/parser.go | 74 - vendor/golang.org/x/tools/godoc/pres.go | 166 - .../golang.org/x/tools/godoc/proxy/proxy.go | 176 - .../golang.org/x/tools/godoc/redirect/hash.go | 138 - .../x/tools/godoc/redirect/redirect.go | 250 - .../x/tools/godoc/redirect/redirect_test.go | 104 - vendor/golang.org/x/tools/godoc/search.go | 139 - vendor/golang.org/x/tools/godoc/server.go | 802 - .../golang.org/x/tools/godoc/short/short.go | 173 - vendor/golang.org/x/tools/godoc/short/tmpl.go | 121 - vendor/golang.org/x/tools/godoc/snippet.go | 123 - vendor/golang.org/x/tools/godoc/spec.go | 179 - vendor/golang.org/x/tools/godoc/spot.go | 83 - .../x/tools/godoc/static/analysis/call-eg.png | Bin 11383 -> 0 bytes .../x/tools/godoc/static/analysis/call3.png | Bin 16960 -> 0 bytes .../tools/godoc/static/analysis/callers1.png | Bin 13068 -> 0 bytes .../tools/godoc/static/analysis/callers2.png | Bin 20822 -> 0 bytes .../x/tools/godoc/static/analysis/chan1.png | Bin 44287 -> 0 bytes .../x/tools/godoc/static/analysis/chan2a.png | Bin 19600 -> 0 bytes .../x/tools/godoc/static/analysis/chan2b.png | Bin 18204 -> 0 bytes .../x/tools/godoc/static/analysis/error1.png | Bin 13633 -> 0 bytes .../x/tools/godoc/static/analysis/help.html | 254 - .../tools/godoc/static/analysis/ident-def.png | Bin 13141 -> 0 bytes .../godoc/static/analysis/ident-field.png | Bin 12131 -> 0 bytes .../godoc/static/analysis/ident-func.png | Bin 12554 -> 0 bytes .../tools/godoc/static/analysis/ipcg-func.png | Bin 10919 -> 0 bytes .../tools/godoc/static/analysis/ipcg-pkg.png | Bin 21092 -> 0 bytes .../godoc/static/analysis/typeinfo-pkg.png | Bin 30004 -> 0 bytes .../godoc/static/analysis/typeinfo-src.png | Bin 16891 -> 0 bytes .../x/tools/godoc/static/callgraph.html | 15 - .../x/tools/godoc/static/codewalk.html | 56 - .../x/tools/godoc/static/codewalkdir.html | 16 - .../x/tools/godoc/static/dirlist.html | 31 - vendor/golang.org/x/tools/godoc/static/doc.go | 8 - .../x/tools/godoc/static/error.html | 9 - .../x/tools/godoc/static/example.html | 30 - vendor/golang.org/x/tools/godoc/static/gen.go | 7 - .../x/tools/godoc/static/godoc.html | 120 - .../golang.org/x/tools/godoc/static/godocs.js | 604 - .../x/tools/godoc/static/images/minus.gif | Bin 837 -> 0 bytes .../x/tools/godoc/static/images/plus.gif | Bin 841 -> 0 bytes .../static/images/treeview-black-line.gif | Bin 1877 -> 0 bytes .../godoc/static/images/treeview-black.gif | Bin 402 -> 0 bytes .../static/images/treeview-default-line.gif | Bin 1993 -> 0 bytes .../godoc/static/images/treeview-default.gif | Bin 400 -> 0 bytes .../static/images/treeview-gray-line.gif | Bin 1877 -> 0 bytes .../godoc/static/images/treeview-gray.gif | Bin 411 -> 0 bytes .../x/tools/godoc/static/implements.html | 9 - .../golang.org/x/tools/godoc/static/jquery.js | 2 - .../x/tools/godoc/static/jquery.treeview.css | 76 - .../godoc/static/jquery.treeview.edit.js | 39 - .../x/tools/godoc/static/jquery.treeview.js | 256 - .../x/tools/godoc/static/makestatic.go | 127 - .../x/tools/godoc/static/methodset.html | 9 - .../x/tools/godoc/static/opensearch.xml | 11 - .../x/tools/godoc/static/package.html | 337 - .../x/tools/godoc/static/package.txt | 116 - .../golang.org/x/tools/godoc/static/play.js | 114 - .../x/tools/godoc/static/playground.js | 467 - .../x/tools/godoc/static/search.html | 18 - .../x/tools/godoc/static/search.txt | 54 - .../x/tools/godoc/static/searchcode.html | 64 - .../x/tools/godoc/static/searchdoc.html | 24 - .../x/tools/godoc/static/searchtxt.html | 42 - .../golang.org/x/tools/godoc/static/static.go | 3720 - .../golang.org/x/tools/godoc/static/style.css | 841 - vendor/golang.org/x/tools/godoc/tab.go | 82 - vendor/golang.org/x/tools/godoc/template.go | 179 - .../golang.org/x/tools/godoc/util/throttle.go | 88 - vendor/golang.org/x/tools/godoc/util/util.go | 89 - .../golang.org/x/tools/godoc/vfs/emptyvfs.go | 85 - .../x/tools/godoc/vfs/emptyvfs_test.go | 58 - .../x/tools/godoc/vfs/gatefs/gatefs.go | 89 - .../x/tools/godoc/vfs/httpfs/httpfs.go | 94 - .../x/tools/godoc/vfs/mapfs/mapfs.go | 152 - .../x/tools/godoc/vfs/mapfs/mapfs_test.go | 111 - .../golang.org/x/tools/godoc/vfs/namespace.go | 389 - vendor/golang.org/x/tools/godoc/vfs/os.go | 65 - vendor/golang.org/x/tools/godoc/vfs/vfs.go | 45 - .../x/tools/godoc/vfs/zipfs/zipfs.go | 264 - .../x/tools/godoc/vfs/zipfs/zipfs_test.go | 189 - vendor/golang.org/x/tools/imports/fastwalk.go | 187 - .../x/tools/imports/fastwalk_dirent_fileno.go | 13 - .../x/tools/imports/fastwalk_dirent_ino.go | 14 - .../x/tools/imports/fastwalk_portable.go | 29 - .../x/tools/imports/fastwalk_test.go | 171 - .../x/tools/imports/fastwalk_unix.go | 123 - vendor/golang.org/x/tools/imports/fix.go | 1032 - vendor/golang.org/x/tools/imports/fix_test.go | 1959 - vendor/golang.org/x/tools/imports/imports.go | 289 - vendor/golang.org/x/tools/imports/mkindex.go | 173 - vendor/golang.org/x/tools/imports/mkstdlib.go | 105 - .../golang.org/x/tools/imports/sortimports.go | 212 - vendor/golang.org/x/tools/imports/zstdlib.go | 9448 --- .../x/tools/playground/appengine.go | 26 - .../x/tools/playground/appenginevm.go | 11 - .../golang.org/x/tools/playground/common.go | 63 - vendor/golang.org/x/tools/playground/local.go | 20 - .../x/tools/playground/socket/socket.go | 524 - .../x/tools/playground/socket/socket_test.go | 77 - vendor/golang.org/x/tools/present/args.go | 229 - vendor/golang.org/x/tools/present/caption.go | 22 - vendor/golang.org/x/tools/present/code.go | 267 - .../golang.org/x/tools/present/code_test.go | 225 - vendor/golang.org/x/tools/present/doc.go | 262 - vendor/golang.org/x/tools/present/html.go | 31 - vendor/golang.org/x/tools/present/iframe.go | 45 - vendor/golang.org/x/tools/present/image.go | 50 - vendor/golang.org/x/tools/present/link.go | 100 - .../golang.org/x/tools/present/link_test.go | 40 - vendor/golang.org/x/tools/present/parse.go | 559 - vendor/golang.org/x/tools/present/style.go | 167 - .../golang.org/x/tools/present/style_test.go | 124 - vendor/golang.org/x/tools/present/video.go | 51 - vendor/golang.org/x/tools/refactor/README | 1 - vendor/golang.org/x/tools/refactor/eg/eg.go | 343 - .../golang.org/x/tools/refactor/eg/eg_test.go | 160 - .../golang.org/x/tools/refactor/eg/match.go | 249 - .../golang.org/x/tools/refactor/eg/rewrite.go | 343 - .../x/tools/refactor/eg/testdata/A.template | 13 - .../x/tools/refactor/eg/testdata/A1.go | 51 - .../x/tools/refactor/eg/testdata/A1.golden | 52 - .../x/tools/refactor/eg/testdata/A2.go | 12 - .../x/tools/refactor/eg/testdata/A2.golden | 15 - .../x/tools/refactor/eg/testdata/B.template | 9 - .../x/tools/refactor/eg/testdata/B1.go | 17 - .../x/tools/refactor/eg/testdata/B1.golden | 17 - .../x/tools/refactor/eg/testdata/C.template | 10 - .../x/tools/refactor/eg/testdata/C1.go | 22 - .../x/tools/refactor/eg/testdata/C1.golden | 22 - .../x/tools/refactor/eg/testdata/D.template | 8 - .../x/tools/refactor/eg/testdata/D1.go | 12 - .../x/tools/refactor/eg/testdata/D1.golden | 12 - .../x/tools/refactor/eg/testdata/E.template | 12 - .../x/tools/refactor/eg/testdata/E1.go | 9 - .../x/tools/refactor/eg/testdata/E1.golden | 13 - .../x/tools/refactor/eg/testdata/F.template | 8 - .../x/tools/refactor/eg/testdata/F1.go | 48 - .../x/tools/refactor/eg/testdata/F1.golden | 48 - .../x/tools/refactor/eg/testdata/G.template | 10 - .../x/tools/refactor/eg/testdata/G1.go | 12 - .../x/tools/refactor/eg/testdata/G1.golden | 12 - .../x/tools/refactor/eg/testdata/H.template | 9 - .../x/tools/refactor/eg/testdata/H1.go | 12 - .../x/tools/refactor/eg/testdata/H1.golden | 12 - .../refactor/eg/testdata/bad_type.template | 8 - .../eg/testdata/expr_type_mismatch.template | 15 - .../eg/testdata/no_after_return.template | 6 - .../refactor/eg/testdata/no_before.template | 5 - .../eg/testdata/type_mismatch.template | 6 - .../x/tools/refactor/importgraph/graph.go | 167 - .../tools/refactor/importgraph/graph_test.go | 99 - .../x/tools/refactor/rename/check.go | 858 - .../x/tools/refactor/rename/mvpkg.go | 373 - .../x/tools/refactor/rename/mvpkg_test.go | 444 - .../x/tools/refactor/rename/rename.go | 603 - .../x/tools/refactor/rename/rename_test.go | 1334 - .../x/tools/refactor/rename/spec.go | 593 - .../x/tools/refactor/rename/util.go | 105 - .../x/tools/refactor/satisfy/find.go | 705 - .../x/tools/third_party/moduleloader/LICENSE | 22 - .../third_party/moduleloader/moduleloader.js | 7 - .../x/tools/third_party/typescript/LICENSE | 55 - .../third_party/typescript/typescript.js | 60312 ---------------- .../x/tools/third_party/webcomponents/LICENSE | 27 - .../webcomponents/customelements.js | 1029 - vendor/golang/protobuf/.gitignore | 17 - vendor/golang/protobuf/.travis.yml | 30 - vendor/golang/protobuf/AUTHORS | 3 - vendor/golang/protobuf/CONTRIBUTORS | 3 - vendor/golang/protobuf/LICENSE | 28 - vendor/golang/protobuf/Makefile | 48 - vendor/golang/protobuf/README.md | 283 - vendor/golang/protobuf/conformance/Makefile | 49 - .../protobuf/conformance/conformance.go | 154 - .../protobuf/conformance/conformance.sh | 4 - .../protobuf/conformance/failure_list_go.txt | 61 - .../conformance_proto/conformance.pb.go | 1816 - .../conformance_proto/conformance.proto | 273 - vendor/golang/protobuf/conformance/test.sh | 26 - .../golang/protobuf/descriptor/descriptor.go | 93 - .../protobuf/descriptor/descriptor_test.go | 32 - vendor/golang/protobuf/jsonpb/jsonpb.go | 1250 - vendor/golang/protobuf/jsonpb/jsonpb_test.go | 1165 - .../jsonpb_test_proto/more_test_objects.pb.go | 368 - .../jsonpb_test_proto/more_test_objects.proto | 69 - .../jsonpb_test_proto/test_objects.pb.go | 1278 - .../jsonpb_test_proto/test_objects.proto | 171 - vendor/golang/protobuf/proto/all_test.go | 2449 - vendor/golang/protobuf/proto/any_test.go | 300 - vendor/golang/protobuf/proto/clone.go | 253 - vendor/golang/protobuf/proto/clone_test.go | 390 - vendor/golang/protobuf/proto/decode.go | 428 - vendor/golang/protobuf/proto/decode_test.go | 255 - vendor/golang/protobuf/proto/discard.go | 350 - vendor/golang/protobuf/proto/discard_test.go | 170 - vendor/golang/protobuf/proto/encode.go | 218 - vendor/golang/protobuf/proto/encode_test.go | 85 - vendor/golang/protobuf/proto/equal.go | 300 - vendor/golang/protobuf/proto/equal_test.go | 244 - vendor/golang/protobuf/proto/extensions.go | 543 - .../golang/protobuf/proto/extensions_test.go | 688 - vendor/golang/protobuf/proto/lib.go | 921 - vendor/golang/protobuf/proto/map_test.go | 70 - vendor/golang/protobuf/proto/message_set.go | 314 - .../golang/protobuf/proto/message_set_test.go | 77 - .../golang/protobuf/proto/pointer_reflect.go | 357 - .../golang/protobuf/proto/pointer_unsafe.go | 308 - vendor/golang/protobuf/proto/properties.go | 544 - .../protobuf/proto/proto3_proto/proto3.pb.go | 611 - .../protobuf/proto/proto3_proto/proto3.proto | 97 - vendor/golang/protobuf/proto/proto3_test.go | 151 - vendor/golang/protobuf/proto/size2_test.go | 63 - vendor/golang/protobuf/proto/size_test.go | 191 - vendor/golang/protobuf/proto/table_marshal.go | 2736 - vendor/golang/protobuf/proto/table_merge.go | 654 - .../golang/protobuf/proto/table_unmarshal.go | 2048 - .../protobuf/proto/test_proto/test.pb.go | 5268 -- .../protobuf/proto/test_proto/test.proto | 570 - vendor/golang/protobuf/proto/text.go | 843 - vendor/golang/protobuf/proto/text_parser.go | 880 - .../golang/protobuf/proto/text_parser_test.go | 706 - vendor/golang/protobuf/proto/text_test.go | 518 - .../protoc-gen-go/descriptor/descriptor.pb.go | 2812 - .../protoc-gen-go/descriptor/descriptor.proto | 872 - vendor/golang/protobuf/protoc-gen-go/doc.go | 51 - .../protoc-gen-go/generator/generator.go | 2923 - .../generator/internal/remap/remap.go | 117 - .../generator/internal/remap/remap_test.go | 82 - .../protoc-gen-go/generator/name_test.go | 115 - .../protobuf/protoc-gen-go/golden_test.go | 422 - .../protobuf/protoc-gen-go/grpc/grpc.go | 484 - .../protobuf/protoc-gen-go/link_grpc.go | 34 - vendor/golang/protobuf/protoc-gen-go/main.go | 98 - .../protoc-gen-go/plugin/plugin.pb.go | 369 - .../protoc-gen-go/plugin/plugin.pb.golden | 83 - .../protoc-gen-go/plugin/plugin.proto | 167 - .../testdata/deprecated/deprecated.pb.go | 234 - .../testdata/deprecated/deprecated.proto | 69 - .../extension_base/extension_base.pb.go | 139 - .../extension_base/extension_base.proto | 48 - .../extension_extra/extension_extra.pb.go | 78 - .../extension_extra/extension_extra.proto | 40 - .../protoc-gen-go/testdata/extension_test.go | 206 - .../extension_user/extension_user.pb.go | 401 - .../extension_user/extension_user.proto | 102 - .../protoc-gen-go/testdata/grpc/grpc.pb.go | 444 - .../protoc-gen-go/testdata/grpc/grpc.proto | 61 - .../testdata/import_public/a.pb.go | 110 - .../testdata/import_public/a.proto | 45 - .../testdata/import_public/b.pb.go | 87 - .../testdata/import_public/b.proto | 43 - .../testdata/import_public/sub/a.pb.go | 100 - .../testdata/import_public/sub/a.proto | 47 - .../testdata/import_public/sub/b.pb.go | 67 - .../testdata/import_public/sub/b.proto | 39 - .../testdata/import_public_test.go | 66 - .../testdata/imports/fmt/m.pb.go | 66 - .../testdata/imports/fmt/m.proto | 35 - .../testdata/imports/test_a_1/m1.pb.go | 130 - .../testdata/imports/test_a_1/m1.proto | 44 - .../testdata/imports/test_a_1/m2.pb.go | 67 - .../testdata/imports/test_a_1/m2.proto | 35 - .../testdata/imports/test_a_2/m3.pb.go | 67 - .../testdata/imports/test_a_2/m3.proto | 35 - .../testdata/imports/test_a_2/m4.pb.go | 67 - .../testdata/imports/test_a_2/m4.proto | 35 - .../testdata/imports/test_b_1/m1.pb.go | 67 - .../testdata/imports/test_b_1/m1.proto | 35 - .../testdata/imports/test_b_1/m2.pb.go | 67 - .../testdata/imports/test_b_1/m2.proto | 35 - .../testdata/imports/test_import_a1m1.pb.go | 80 - .../testdata/imports/test_import_a1m1.proto | 42 - .../testdata/imports/test_import_a1m2.pb.go | 80 - .../testdata/imports/test_import_a1m2.proto | 42 - .../testdata/imports/test_import_all.pb.go | 138 - .../testdata/imports/test_import_all.proto | 58 - .../protoc-gen-go/testdata/main_test.go | 48 - .../protoc-gen-go/testdata/multi/multi1.pb.go | 96 - .../protoc-gen-go/testdata/multi/multi1.proto | 46 - .../protoc-gen-go/testdata/multi/multi2.pb.go | 128 - .../protoc-gen-go/testdata/multi/multi2.proto | 48 - .../protoc-gen-go/testdata/multi/multi3.pb.go | 115 - .../protoc-gen-go/testdata/multi/multi3.proto | 45 - .../protoc-gen-go/testdata/my_test/test.pb.go | 1174 - .../protoc-gen-go/testdata/my_test/test.proto | 158 - .../testdata/proto3/proto3.pb.go | 196 - .../testdata/proto3/proto3.proto | 55 - vendor/golang/protobuf/ptypes/any.go | 141 - vendor/golang/protobuf/ptypes/any/any.pb.go | 191 - vendor/golang/protobuf/ptypes/any/any.proto | 149 - vendor/golang/protobuf/ptypes/any_test.go | 155 - vendor/golang/protobuf/ptypes/doc.go | 35 - vendor/golang/protobuf/ptypes/duration.go | 102 - .../protobuf/ptypes/duration/duration.pb.go | 159 - .../protobuf/ptypes/duration/duration.proto | 117 - .../golang/protobuf/ptypes/duration_test.go | 121 - .../golang/protobuf/ptypes/empty/empty.pb.go | 79 - .../golang/protobuf/ptypes/empty/empty.proto | 52 - .../protobuf/ptypes/struct/struct.pb.go | 440 - .../protobuf/ptypes/struct/struct.proto | 96 - vendor/golang/protobuf/ptypes/timestamp.go | 134 - .../protobuf/ptypes/timestamp/timestamp.pb.go | 175 - .../protobuf/ptypes/timestamp/timestamp.proto | 133 - .../golang/protobuf/ptypes/timestamp_test.go | 153 - .../protobuf/ptypes/wrappers/wrappers.pb.go | 443 - .../protobuf/ptypes/wrappers/wrappers.proto | 118 - vendor/golang/protobuf/regenerate.sh | 53 - walletapi/balance_decoder.go | 4 +- walletapi/daemon_communication.go | 564 +- walletapi/daemon_connectivity.go | 4 +- walletapi/rpcserver/rpc_get_bulk_payments.go | 78 - .../rpcserver/rpc_get_transfer_by_txid.go | 51 +- walletapi/rpcserver/rpc_getaddress.go | 6 +- walletapi/rpcserver/rpc_getbalance.go | 6 +- walletapi/rpcserver/rpc_getheight.go | 6 +- walletapi/rpcserver/rpc_gettransfers.go | 39 +- .../rpcserver/rpc_make_integrated_address.go | 41 +- walletapi/rpcserver/rpc_query_key.go | 4 +- .../rpcserver/rpc_split_integrated_address.go | 19 +- walletapi/rpcserver/rpc_transfer.go | 117 +- walletapi/rpcserver/rpc_websocket_server.go | 50 +- walletapi/transaction_build.go | 332 +- walletapi/wallet.go | 207 +- walletapi/{db.go => wallet_disk.go} | 9 +- walletapi/{db_memory.go => wallet_memory.go} | 11 +- walletapi/wallet_pool.go | 241 +- walletapi/wallet_transfer.go | 240 +- 1757 files changed, 66952 insertions(+), 355657 deletions(-) create mode 100644 Changelog.md create mode 100644 blockchain/sc.go create mode 100644 cmd/derod/rpc_dero_getsc.go create mode 100644 cmd/rpc_examples/pong_server/pong_server.go create mode 100644 cmd/rpc_examples/readme.txt create mode 100644 cryptography/bn256/CODE_OF_CONDUCT.md rename {vendor/golang.org/x/tools => cryptography/bn256}/LICENSE (100%) create mode 100644 cryptography/bn256/Makefile create mode 100644 cryptography/bn256/README.md create mode 100644 cryptography/bn256/bn256.go create mode 100644 cryptography/bn256/bn256_test.go create mode 100644 cryptography/bn256/constants.go create mode 100644 cryptography/bn256/curve.go create mode 100644 cryptography/bn256/curve_test.go create mode 100644 cryptography/bn256/example_test.go create mode 100644 cryptography/bn256/g1_serialization.go create mode 100644 cryptography/bn256/g1_serialization_test.go create mode 100644 cryptography/bn256/g2_serialization.go create mode 100644 cryptography/bn256/g2_serialization_test.go create mode 100644 cryptography/bn256/gfp.go create mode 100644 cryptography/bn256/gfp12.go create mode 100644 cryptography/bn256/gfp2.go create mode 100644 cryptography/bn256/gfp2_test.go create mode 100644 cryptography/bn256/gfp6.go create mode 100644 cryptography/bn256/gfp_amd64.s create mode 100644 cryptography/bn256/gfp_arm64.s create mode 100644 cryptography/bn256/gfp_decl.go create mode 100644 cryptography/bn256/gfp_generic.go create mode 100644 cryptography/bn256/gfp_test.go create mode 100644 cryptography/bn256/lattice.go create mode 100644 cryptography/bn256/lattice_test.go create mode 100644 cryptography/bn256/main_test.go create mode 100644 cryptography/bn256/mul_amd64.h create mode 100644 cryptography/bn256/mul_arm64.h create mode 100644 cryptography/bn256/mul_bmi2_amd64.h create mode 100644 cryptography/bn256/optate.go create mode 100644 cryptography/bn256/twist.go create mode 100644 cryptography/crypto/LICENSE create mode 100644 cryptography/crypto/algebra_elgamal.go create mode 100644 cryptography/crypto/algebra_fieldvector.go create mode 100644 cryptography/crypto/algebra_pedersen.go create mode 100644 cryptography/crypto/algebra_pointvector.go create mode 100644 cryptography/crypto/bnred.go create mode 100644 cryptography/crypto/const.go create mode 100644 cryptography/crypto/fieldvector.go create mode 100644 cryptography/crypto/generatorparams.go create mode 100644 cryptography/crypto/group.go create mode 100644 cryptography/crypto/hash.go create mode 100644 cryptography/crypto/hashtopoint.go create mode 100644 cryptography/crypto/keccak.go create mode 100644 cryptography/crypto/keccak_test.go create mode 100644 cryptography/crypto/key_old.go create mode 100644 cryptography/crypto/polynomial.go create mode 100644 cryptography/crypto/proof_generate.go create mode 100644 cryptography/crypto/proof_innerproduct.go create mode 100644 cryptography/crypto/proof_verify.go create mode 100644 cryptography/crypto/protocol_structures.go create mode 100644 cryptography/crypto/random.go create mode 100644 cryptography/crypto/userdata.go create mode 100644 cryptography/sha3/doc.go create mode 100644 cryptography/sha3/hashes.go create mode 100644 cryptography/sha3/hashes_generic.go create mode 100644 cryptography/sha3/keccakf.go rename vendor/golang.org/x/tools/godoc/appengine.go => cryptography/sha3/keccakf_amd64.go (52%) create mode 100644 cryptography/sha3/keccakf_amd64.s create mode 100644 cryptography/sha3/register.go create mode 100644 cryptography/sha3/sha3.go create mode 100644 cryptography/sha3/sha3_s390x.go create mode 100644 cryptography/sha3/sha3_s390x.s create mode 100644 cryptography/sha3/sha3_test.go create mode 100644 cryptography/sha3/shake.go create mode 100644 cryptography/sha3/shake_generic.go create mode 100644 cryptography/sha3/testdata/keccakKats.json.deflate create mode 100644 cryptography/sha3/xor.go create mode 100644 cryptography/sha3/xor_generic.go create mode 100644 cryptography/sha3/xor_unaligned.go create mode 100644 dvm/deterministic_random_number.go create mode 100644 dvm/deterministic_random_number_test.go create mode 100644 dvm/dvm.go create mode 100644 dvm/dvm_execution_test.go create mode 100644 dvm/dvm_functions.go create mode 100644 dvm/dvm_functions_test.go create mode 100644 dvm/dvm_parse_test.go create mode 100644 dvm/dvm_store.go create mode 100644 dvm/dvm_store_memory.go create mode 100644 go.mod create mode 100644 guide/README.md create mode 100644 guide/dim.md create mode 100644 guide/examples/lottery.bas create mode 100644 guide/examples/lottery_sc_guide.md create mode 100644 guide/examples/token.bas create mode 100644 guide/examples/token_sc_guide.md create mode 100644 guide/function.md create mode 100644 guide/goto.md create mode 100644 guide/if.md create mode 100644 guide/let.md create mode 100644 guide/return.md create mode 100644 guide/support_functions.md create mode 100644 p2p/chain_bootstrap.go delete mode 100644 p2p/chain_response.go create mode 100644 p2p/chain_sync.go create mode 100644 p2p/common.go delete mode 100644 p2p/connection_handler.go delete mode 100644 p2p/handshake.go delete mode 100644 p2p/inventory_handler.go delete mode 100644 p2p/object_pool.go delete mode 100644 p2p/object_response.go create mode 100644 p2p/rpc.go create mode 100644 p2p/rpc_cbor_codec.go rename p2p/{chain_request.go => rpc_chain_request.go} (56%) create mode 100644 p2p/rpc_changeset.go create mode 100644 p2p/rpc_handshake.go rename p2p/{notification.go => rpc_notifications.go} (51%) rename p2p/{object_request.go => rpc_object_request.go} (50%) create mode 100644 p2p/rpc_treesection.go delete mode 100644 p2p/timedsync.go create mode 100644 rpc/LICENSE create mode 100644 rpc/address.go create mode 100644 rpc/address_test.go1 create mode 100644 rpc/bech32.go create mode 100644 rpc/bech32_test.go create mode 100644 rpc/daemon_rpc.go create mode 100644 rpc/rpc.go create mode 100644 rpc/rpc_sc.go create mode 100644 rpc/wallet_rpc.go create mode 100644 vendor/etcd.io/bbolt/.gitignore create mode 100644 vendor/etcd.io/bbolt/.travis.yml rename vendor/{github.com/alecthomas/jsonschema/COPYING => etcd.io/bbolt/LICENSE} (52%) create mode 100644 vendor/etcd.io/bbolt/Makefile create mode 100644 vendor/etcd.io/bbolt/README.md create mode 100644 vendor/etcd.io/bbolt/allocate_test.go create mode 100644 vendor/etcd.io/bbolt/bolt_386.go create mode 100644 vendor/etcd.io/bbolt/bolt_amd64.go create mode 100644 vendor/etcd.io/bbolt/bolt_arm.go create mode 100644 vendor/etcd.io/bbolt/bolt_arm64.go create mode 100644 vendor/etcd.io/bbolt/bolt_linux.go create mode 100644 vendor/etcd.io/bbolt/bolt_mips64x.go create mode 100644 vendor/etcd.io/bbolt/bolt_mipsx.go create mode 100644 vendor/etcd.io/bbolt/bolt_openbsd.go create mode 100644 vendor/etcd.io/bbolt/bolt_ppc.go create mode 100644 vendor/etcd.io/bbolt/bolt_ppc64.go create mode 100644 vendor/etcd.io/bbolt/bolt_ppc64le.go create mode 100644 vendor/etcd.io/bbolt/bolt_riscv64.go create mode 100644 vendor/etcd.io/bbolt/bolt_s390x.go create mode 100644 vendor/etcd.io/bbolt/bolt_unix.go create mode 100644 vendor/etcd.io/bbolt/bolt_unix_aix.go create mode 100644 vendor/etcd.io/bbolt/bolt_unix_solaris.go create mode 100644 vendor/etcd.io/bbolt/bolt_windows.go create mode 100644 vendor/etcd.io/bbolt/boltsync_unix.go create mode 100644 vendor/etcd.io/bbolt/bucket.go create mode 100644 vendor/etcd.io/bbolt/bucket_test.go create mode 100644 vendor/etcd.io/bbolt/cmd/bbolt/main.go create mode 100644 vendor/etcd.io/bbolt/cmd/bbolt/main_test create mode 100644 vendor/etcd.io/bbolt/cursor.go create mode 100644 vendor/etcd.io/bbolt/cursor_test.go create mode 100644 vendor/etcd.io/bbolt/db.go create mode 100644 vendor/etcd.io/bbolt/db_test.go create mode 100644 vendor/etcd.io/bbolt/doc.go create mode 100644 vendor/etcd.io/bbolt/errors.go create mode 100644 vendor/etcd.io/bbolt/freelist.go create mode 100644 vendor/etcd.io/bbolt/freelist_hmap.go create mode 100644 vendor/etcd.io/bbolt/freelist_test.go create mode 100644 vendor/etcd.io/bbolt/go.mod create mode 100644 vendor/etcd.io/bbolt/go.sum create mode 100644 vendor/etcd.io/bbolt/manydbs_test.go create mode 100644 vendor/etcd.io/bbolt/node.go create mode 100644 vendor/etcd.io/bbolt/node_test.go create mode 100644 vendor/etcd.io/bbolt/page.go create mode 100644 vendor/etcd.io/bbolt/page_test.go create mode 100644 vendor/etcd.io/bbolt/quick_test.go create mode 100644 vendor/etcd.io/bbolt/simulation_no_freelist_sync_test.go create mode 100644 vendor/etcd.io/bbolt/simulation_test.go create mode 100644 vendor/etcd.io/bbolt/tx.go create mode 100644 vendor/etcd.io/bbolt/tx_test.go create mode 100644 vendor/etcd.io/bbolt/unsafe.go delete mode 100644 vendor/github.com/alecthomas/.directory delete mode 100644 vendor/github.com/alecthomas/jsonschema/.travis.yml delete mode 100644 vendor/github.com/alecthomas/jsonschema/README.md delete mode 100644 vendor/github.com/alecthomas/jsonschema/fixtures/allow_additional_props.json delete mode 100644 vendor/github.com/alecthomas/jsonschema/fixtures/defaults.json delete mode 100644 vendor/github.com/alecthomas/jsonschema/fixtures/defaults_expanded_toplevel.json delete mode 100644 vendor/github.com/alecthomas/jsonschema/fixtures/required_from_jsontags.json delete mode 100644 vendor/github.com/alecthomas/jsonschema/reflect.go delete mode 100644 vendor/github.com/alecthomas/jsonschema/reflect_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/.travis.yml delete mode 100644 vendor/github.com/cheggaaa/pb/LICENSE delete mode 100644 vendor/github.com/cheggaaa/pb/README.md delete mode 100644 vendor/github.com/cheggaaa/pb/README_V1.md delete mode 100644 vendor/github.com/cheggaaa/pb/example_copy_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/example_multiple_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/example_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/format.go delete mode 100644 vendor/github.com/cheggaaa/pb/format_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/go.mod delete mode 100644 vendor/github.com/cheggaaa/pb/go.sum delete mode 100644 vendor/github.com/cheggaaa/pb/pb.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_appengine.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_plan9.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_win.go delete mode 100644 vendor/github.com/cheggaaa/pb/pb_x.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool_win.go delete mode 100644 vendor/github.com/cheggaaa/pb/pool_x.go delete mode 100644 vendor/github.com/cheggaaa/pb/reader.go delete mode 100644 vendor/github.com/cheggaaa/pb/runecount.go delete mode 100644 vendor/github.com/cheggaaa/pb/runecount_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/termios_bsd.go delete mode 100644 vendor/github.com/cheggaaa/pb/termios_sysv.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/LICENSE delete mode 100644 vendor/github.com/cheggaaa/pb/v3/element.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/element_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/go.mod delete mode 100644 vendor/github.com/cheggaaa/pb/v3/go.sum delete mode 100644 vendor/github.com/cheggaaa/pb/v3/io.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/io_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/pb.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/pb_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/preset.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/speed.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/template.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/template_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_appengine.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_bsd.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_linux.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_nix.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_plan9.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_solaris.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_win.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/termutil/term_x.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/util.go delete mode 100644 vendor/github.com/cheggaaa/pb/v3/util_test.go delete mode 100644 vendor/github.com/cheggaaa/pb/writer.go delete mode 100644 vendor/github.com/dchest/.directory delete mode 100644 vendor/github.com/dchest/blake256/README.markdown delete mode 100644 vendor/github.com/dchest/blake256/blake256.go delete mode 100644 vendor/github.com/dchest/blake256/blake256_test.go delete mode 100644 vendor/github.com/dchest/blake256/blake256block.go create mode 100644 vendor/github.com/deroproject/graviton/go.mod delete mode 100644 vendor/github.com/dgraph-io/badger/.travis.yml delete mode 100644 vendor/github.com/dgraph-io/badger/CHANGELOG.md delete mode 100644 vendor/github.com/dgraph-io/badger/LICENSE delete mode 100644 vendor/github.com/dgraph-io/badger/README.md delete mode 100644 vendor/github.com/dgraph-io/badger/appveyor.yml delete mode 100644 vendor/github.com/dgraph-io/badger/backup.go delete mode 100644 vendor/github.com/dgraph-io/badger/backup_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/.gitignore delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/backup.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/info.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/restore.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/cmd/root.go delete mode 100644 vendor/github.com/dgraph-io/badger/badger/main.go delete mode 100644 vendor/github.com/dgraph-io/badger/compaction.go delete mode 100755 vendor/github.com/dgraph-io/badger/contrib/cover.sh delete mode 100644 vendor/github.com/dgraph-io/badger/db.go delete mode 100644 vendor/github.com/dgraph-io/badger/db_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/dir_unix.go delete mode 100644 vendor/github.com/dgraph-io/badger/dir_windows.go delete mode 100644 vendor/github.com/dgraph-io/badger/doc.go delete mode 100644 vendor/github.com/dgraph-io/badger/errors.go delete mode 100644 vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png delete mode 100644 vendor/github.com/dgraph-io/badger/images/diggy-shadow.png delete mode 100644 vendor/github.com/dgraph-io/badger/images/sketch.jpg delete mode 100644 vendor/github.com/dgraph-io/badger/integration/testgc/.gitignore delete mode 100644 vendor/github.com/dgraph-io/badger/integration/testgc/main.go delete mode 100644 vendor/github.com/dgraph-io/badger/iterator.go delete mode 100644 vendor/github.com/dgraph-io/badger/level_handler.go delete mode 100644 vendor/github.com/dgraph-io/badger/levels.go delete mode 100644 vendor/github.com/dgraph-io/badger/managed_db.go delete mode 100644 vendor/github.com/dgraph-io/badger/manifest.go delete mode 100644 vendor/github.com/dgraph-io/badger/manifest_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/options.go delete mode 100644 vendor/github.com/dgraph-io/badger/options/options.go delete mode 100644 vendor/github.com/dgraph-io/badger/protos/backup.pb.go delete mode 100644 vendor/github.com/dgraph-io/badger/protos/backup.proto delete mode 100755 vendor/github.com/dgraph-io/badger/protos/gen.sh delete mode 100644 vendor/github.com/dgraph-io/badger/protos/manifest.pb.go delete mode 100644 vendor/github.com/dgraph-io/badger/protos/manifest.proto delete mode 100644 vendor/github.com/dgraph-io/badger/skl/README.md delete mode 100644 vendor/github.com/dgraph-io/badger/skl/arena.go delete mode 100644 vendor/github.com/dgraph-io/badger/skl/skl.go delete mode 100644 vendor/github.com/dgraph-io/badger/skl/skl_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/structs.go delete mode 100644 vendor/github.com/dgraph-io/badger/table/README.md delete mode 100644 vendor/github.com/dgraph-io/badger/table/builder.go delete mode 100644 vendor/github.com/dgraph-io/badger/table/iterator.go delete mode 100644 vendor/github.com/dgraph-io/badger/table/table.go delete mode 100644 vendor/github.com/dgraph-io/badger/table/table_test.go delete mode 100755 vendor/github.com/dgraph-io/badger/test.sh delete mode 100644 vendor/github.com/dgraph-io/badger/transaction.go delete mode 100644 vendor/github.com/dgraph-io/badger/transaction_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/util.go delete mode 100644 vendor/github.com/dgraph-io/badger/value.go delete mode 100644 vendor/github.com/dgraph-io/badger/value_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/error.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/file_dsync.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/file_nodsync.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/iterator.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/iterator_test.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/metrics.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/mmap_unix.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/mmap_windows.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/watermark.go delete mode 100644 vendor/github.com/dgraph-io/badger/y/y.go delete mode 100644 vendor/github.com/dgryski/go-farm/.travis.yml delete mode 100644 vendor/github.com/dgryski/go-farm/LICENSE delete mode 100644 vendor/github.com/dgryski/go-farm/Makefile delete mode 100644 vendor/github.com/dgryski/go-farm/README.md delete mode 100644 vendor/github.com/dgryski/go-farm/VERSION delete mode 100644 vendor/github.com/dgryski/go-farm/basics.go delete mode 100644 vendor/github.com/dgryski/go-farm/bench_test.go delete mode 100644 vendor/github.com/dgryski/go-farm/farmhash_test.go delete mode 100644 vendor/github.com/dgryski/go-farm/farmhashcc.go delete mode 100644 vendor/github.com/dgryski/go-farm/farmhashmk.go delete mode 100644 vendor/github.com/dgryski/go-farm/farmhashna.go delete mode 100644 vendor/github.com/dgryski/go-farm/farmhashuo.go delete mode 100644 vendor/github.com/dgryski/go-farm/platform.go delete mode 100644 vendor/github.com/ebfe/.directory delete mode 100644 vendor/github.com/ebfe/keccak/.gitignore delete mode 100644 vendor/github.com/ebfe/keccak/.travis.yml delete mode 100644 vendor/github.com/ebfe/keccak/LICENSE delete mode 100644 vendor/github.com/ebfe/keccak/keccak.go delete mode 100644 vendor/github.com/ebfe/keccak/keccak_test.go delete mode 100644 vendor/github.com/ebfe/keccak/keccak_vectors_test.go delete mode 100644 vendor/github.com/ebfe/keccak/sha3.go delete mode 100644 vendor/github.com/ebfe/keccak/sha3_test.go delete mode 100644 vendor/github.com/ebfe/keccak/sha3_vectors_test.go delete mode 100644 vendor/github.com/ebfe/keccak/shake.go delete mode 100644 vendor/github.com/ebfe/keccak/shake_test.go delete mode 100644 vendor/github.com/ebfe/keccak/shake_vectors_test.go create mode 100644 vendor/github.com/fxamacker/cbor/.github/FUNDING.yml create mode 100644 vendor/github.com/fxamacker/cbor/.github/ISSUE_TEMPLATE/1-bug-report.md create mode 100644 vendor/github.com/fxamacker/cbor/.github/ISSUE_TEMPLATE/2-feature-request.md create mode 100644 vendor/github.com/fxamacker/cbor/.github/ISSUE_TEMPLATE/3-docs-wiki-or-website-issue.md create mode 100644 vendor/github.com/fxamacker/cbor/.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md create mode 100644 vendor/github.com/fxamacker/cbor/.github/pull_request_template.md create mode 100644 vendor/github.com/fxamacker/cbor/.github/workflows/ci-go-cover.yml create mode 100644 vendor/github.com/fxamacker/cbor/.github/workflows/ci.yml create mode 100644 vendor/github.com/fxamacker/cbor/.github/workflows/linters.yml create mode 100644 vendor/github.com/fxamacker/cbor/.gitignore create mode 100644 vendor/github.com/fxamacker/cbor/.golangci.yml create mode 100644 vendor/github.com/fxamacker/cbor/CBOR_BENCHMARKS.md create mode 100644 vendor/github.com/fxamacker/cbor/CBOR_GOLANG.md rename vendor/github.com/{rivo/tview => fxamacker/cbor}/CODE_OF_CONDUCT.md (86%) create mode 100644 vendor/github.com/fxamacker/cbor/CONTRIBUTING.md rename vendor/github.com/{rivo/tview/LICENSE.txt => fxamacker/cbor/LICENSE} (95%) create mode 100644 vendor/github.com/fxamacker/cbor/README.md create mode 100644 vendor/github.com/fxamacker/cbor/SECURITY.md create mode 100644 vendor/github.com/fxamacker/cbor/bench_test.go create mode 100644 vendor/github.com/fxamacker/cbor/cache.go create mode 100644 vendor/github.com/fxamacker/cbor/decode.go create mode 100644 vendor/github.com/fxamacker/cbor/decode_test.go create mode 100644 vendor/github.com/fxamacker/cbor/doc.go create mode 100644 vendor/github.com/fxamacker/cbor/encode.go create mode 100644 vendor/github.com/fxamacker/cbor/encode_test.go create mode 100644 vendor/github.com/fxamacker/cbor/example_test.go create mode 100644 vendor/github.com/fxamacker/cbor/go.mod create mode 100644 vendor/github.com/fxamacker/cbor/go.sum create mode 100644 vendor/github.com/fxamacker/cbor/stream.go create mode 100644 vendor/github.com/fxamacker/cbor/stream_test.go create mode 100644 vendor/github.com/fxamacker/cbor/structfields.go create mode 100644 vendor/github.com/fxamacker/cbor/tag.go create mode 100644 vendor/github.com/fxamacker/cbor/tag_test.go create mode 100644 vendor/github.com/fxamacker/cbor/valid.go create mode 100644 vendor/github.com/fxamacker/cbor/valid_test.go delete mode 100644 vendor/github.com/gdamore/encoding/.appveyor.yml delete mode 100644 vendor/github.com/gdamore/encoding/.travis.yml delete mode 100644 vendor/github.com/gdamore/encoding/LICENSE delete mode 100644 vendor/github.com/gdamore/encoding/README.md delete mode 100644 vendor/github.com/gdamore/encoding/ascii.go delete mode 100644 vendor/github.com/gdamore/encoding/ascii_test.go delete mode 100644 vendor/github.com/gdamore/encoding/charmap.go delete mode 100644 vendor/github.com/gdamore/encoding/common_test.go delete mode 100644 vendor/github.com/gdamore/encoding/doc.go delete mode 100644 vendor/github.com/gdamore/encoding/ebcdic.go delete mode 100644 vendor/github.com/gdamore/encoding/ebcdic_test.go delete mode 100644 vendor/github.com/gdamore/encoding/go.mod delete mode 100644 vendor/github.com/gdamore/encoding/go.sum delete mode 100644 vendor/github.com/gdamore/encoding/latin1.go delete mode 100644 vendor/github.com/gdamore/encoding/latin1_test.go delete mode 100644 vendor/github.com/gdamore/encoding/latin5.go delete mode 100644 vendor/github.com/gdamore/encoding/latin5_test.go delete mode 100644 vendor/github.com/gdamore/encoding/utf8.go delete mode 100644 vendor/github.com/gdamore/encoding/utf8_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/.appveyor.yml delete mode 100644 vendor/github.com/gdamore/tcell/v2/.github/FUNDING.yml delete mode 100644 vendor/github.com/gdamore/tcell/v2/.github/workflows/go.yml delete mode 100644 vendor/github.com/gdamore/tcell/v2/.gitignore delete mode 100644 vendor/github.com/gdamore/tcell/v2/.travis.yml delete mode 100644 vendor/github.com/gdamore/tcell/v2/AUTHORS delete mode 100644 vendor/github.com/gdamore/tcell/v2/CHANGESv2.adoc delete mode 100644 vendor/github.com/gdamore/tcell/v2/LICENSE delete mode 100644 vendor/github.com/gdamore/tcell/v2/README.adoc delete mode 100644 vendor/github.com/gdamore/tcell/v2/TUTORIAL.adoc delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/beep.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/boxes.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/colors.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/hello_world.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/mouse.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/_demos/unicode.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/attr.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/cell.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/charset_stub.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/charset_unix.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/charset_windows.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/color.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/color_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/colorfit.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/console_stub.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/console_win.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/doc.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/encoding.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/encoding/all.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/errors.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/event.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/event_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/go.mod delete mode 100644 vendor/github.com/gdamore/tcell/v2/go.sum delete mode 100644 vendor/github.com/gdamore/tcell/v2/interrupt.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/key.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/logos/patreon.png delete mode 100644 vendor/github.com/gdamore/tcell/v2/logos/staysail.png delete mode 100644 vendor/github.com/gdamore/tcell/v2/logos/tcell.png delete mode 100644 vendor/github.com/gdamore/tcell/v2/logos/tidelift.png delete mode 100644 vendor/github.com/gdamore/tcell/v2/mouse.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/paste.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/resize.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/runes.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/runes_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/screen.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/sim_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/simulation.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/style.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/style_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/termbox/compat.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/README.md delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/mkinfo.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/models.txt delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/t/termite/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/terminfo_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_termite/term.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terms_default.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terms_dynamic.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/terms_static.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_bsd.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_darwin.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_linux.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_solaris.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_stub.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/tscreen_windows.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/README.md delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/_demos/cellview.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/_demos/hbox.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/_demos/vbox.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/app.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/boxlayout.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/cellarea.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/constants.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/panel.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/spacer.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/sstext.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/sstextbar.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/text.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/text_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/textarea.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/textarea_test.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/textbar.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/view.go delete mode 100644 vendor/github.com/gdamore/tcell/v2/views/widget.go delete mode 100644 vendor/github.com/golang/snappy/.gitignore delete mode 100644 vendor/github.com/golang/snappy/AUTHORS delete mode 100644 vendor/github.com/golang/snappy/CONTRIBUTORS delete mode 100644 vendor/github.com/golang/snappy/LICENSE delete mode 100644 vendor/github.com/golang/snappy/README delete mode 100644 vendor/github.com/golang/snappy/cmd/snappytool/main.cpp delete mode 100644 vendor/github.com/golang/snappy/decode.go delete mode 100644 vendor/github.com/golang/snappy/decode_amd64.go delete mode 100644 vendor/github.com/golang/snappy/decode_amd64.s delete mode 100644 vendor/github.com/golang/snappy/decode_other.go delete mode 100644 vendor/github.com/golang/snappy/encode.go delete mode 100644 vendor/github.com/golang/snappy/encode_amd64.go delete mode 100644 vendor/github.com/golang/snappy/encode_amd64.s delete mode 100644 vendor/github.com/golang/snappy/encode_other.go delete mode 100644 vendor/github.com/golang/snappy/golden_test.go delete mode 100644 vendor/github.com/golang/snappy/snappy.go delete mode 100644 vendor/github.com/golang/snappy/snappy_test.go delete mode 100644 vendor/github.com/golang/snappy/testdata/Mark.Twain-Tom.Sawyer.txt delete mode 100644 vendor/github.com/golang/snappy/testdata/Mark.Twain-Tom.Sawyer.txt.rawsnappy rename vendor/github.com/{dgryski/go-farm => hashicorp/yamux}/.gitignore (94%) create mode 100644 vendor/github.com/hashicorp/yamux/LICENSE create mode 100644 vendor/github.com/hashicorp/yamux/README.md create mode 100644 vendor/github.com/hashicorp/yamux/addr.go create mode 100644 vendor/github.com/hashicorp/yamux/bench_test.go create mode 100644 vendor/github.com/hashicorp/yamux/const.go create mode 100644 vendor/github.com/hashicorp/yamux/const_test.go create mode 100644 vendor/github.com/hashicorp/yamux/go.mod create mode 100644 vendor/github.com/hashicorp/yamux/mux.go create mode 100644 vendor/github.com/hashicorp/yamux/session.go create mode 100644 vendor/github.com/hashicorp/yamux/session_test.go create mode 100644 vendor/github.com/hashicorp/yamux/spec.md create mode 100644 vendor/github.com/hashicorp/yamux/stream.go create mode 100644 vendor/github.com/hashicorp/yamux/util.go create mode 100644 vendor/github.com/hashicorp/yamux/util_test.go delete mode 100644 vendor/github.com/rivo/tview/CONTRIBUTING.md delete mode 100644 vendor/github.com/rivo/tview/README.md delete mode 100644 vendor/github.com/rivo/tview/ansi.go delete mode 100644 vendor/github.com/rivo/tview/application.go delete mode 100644 vendor/github.com/rivo/tview/borders.go delete mode 100644 vendor/github.com/rivo/tview/box.go delete mode 100644 vendor/github.com/rivo/tview/button.go delete mode 100644 vendor/github.com/rivo/tview/checkbox.go delete mode 100644 vendor/github.com/rivo/tview/demos/box/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/box/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/box/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/button/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/button/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/button/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/checkbox/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/checkbox/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/checkbox/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/dropdown/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/dropdown/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/dropdown/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/flex/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/flex/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/flex/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/form/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/form/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/form/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/frame/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/frame/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/frame/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/grid/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/grid/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/grid/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/inputfield/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/inputfield/autocomplete/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/inputfield/autocompleteasync/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/inputfield/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/inputfield/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/list/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/list/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/list/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/modal/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/modal/centered.png delete mode 100644 vendor/github.com/rivo/tview/demos/modal/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/modal/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/pages/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/pages/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/pages/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/postgres.png delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/center.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/code.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/colors.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/cover.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/end.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/flex.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/form.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/grid.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/helloworld.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/inputfield.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/introduction.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/table.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/textview.go delete mode 100644 vendor/github.com/rivo/tview/demos/presentation/treeview.go delete mode 100644 vendor/github.com/rivo/tview/demos/primitive/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/primitive/boxwithcenterline.png delete mode 100644 vendor/github.com/rivo/tview/demos/primitive/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/primitive/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/primitive/textviewwithcenterline.png delete mode 100644 vendor/github.com/rivo/tview/demos/table/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/table/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/table/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/textview/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/textview/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/textview/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/treeview/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/treeview/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/treeview/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/demos/unicode/README.md delete mode 100644 vendor/github.com/rivo/tview/demos/unicode/main.go delete mode 100644 vendor/github.com/rivo/tview/demos/unicode/screenshot.png delete mode 100644 vendor/github.com/rivo/tview/doc.go delete mode 100644 vendor/github.com/rivo/tview/dropdown.go delete mode 100644 vendor/github.com/rivo/tview/flex.go delete mode 100644 vendor/github.com/rivo/tview/form.go delete mode 100644 vendor/github.com/rivo/tview/frame.go delete mode 100644 vendor/github.com/rivo/tview/go.mod delete mode 100644 vendor/github.com/rivo/tview/go.sum delete mode 100644 vendor/github.com/rivo/tview/grid.go delete mode 100644 vendor/github.com/rivo/tview/inputfield.go delete mode 100644 vendor/github.com/rivo/tview/list.go delete mode 100644 vendor/github.com/rivo/tview/modal.go delete mode 100644 vendor/github.com/rivo/tview/pages.go delete mode 100644 vendor/github.com/rivo/tview/primitive.go delete mode 100644 vendor/github.com/rivo/tview/semigraphics.go delete mode 100644 vendor/github.com/rivo/tview/styles.go delete mode 100644 vendor/github.com/rivo/tview/table.go delete mode 100644 vendor/github.com/rivo/tview/textview.go delete mode 100644 vendor/github.com/rivo/tview/treeview.go delete mode 100644 vendor/github.com/rivo/tview/tview.gif delete mode 100644 vendor/github.com/rivo/tview/util.go delete mode 100644 vendor/github.com/rivo/uniseg/README.md delete mode 100644 vendor/github.com/rivo/uniseg/doc.go delete mode 100644 vendor/github.com/rivo/uniseg/go.mod delete mode 100644 vendor/github.com/rivo/uniseg/grapheme.go delete mode 100644 vendor/github.com/rivo/uniseg/grapheme_test.go delete mode 100644 vendor/github.com/rivo/uniseg/properties.go delete mode 100644 vendor/github.com/syndtr/goleveldb/.travis.yml delete mode 100644 vendor/github.com/syndtr/goleveldb/LICENSE delete mode 100644 vendor/github.com/syndtr/goleveldb/README.md delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/batch.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/batch_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/bench_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/cache/bench_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/cache/cache.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/cache/lru.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/comparer.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/comparer/bytes_comparer.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/comparer/comparer.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/corrupt_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_compaction.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_snapshot.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_state.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_transaction.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_util.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/db_write.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/doc.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/errors.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/errors/errors.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/external_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/filter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/filter/bloom.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/filter/bloom_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/filter/filter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/array_iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/array_iter_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/journal/journal.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/key.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/key_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/memdb/bench_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/opt/options.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/options.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/session.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/session_compaction.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/session_record.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/session_record_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/session_util.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_nacl.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/storage/storage.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/block_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/reader.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/table.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/table_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/table/writer.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/db.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/ginkgo.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/iter.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/kv.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/storage.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil/util.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/testutil_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/buffer.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/buffer_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/crc32.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/hash.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/hash_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/range.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/util/util.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/version.go delete mode 100644 vendor/github.com/syndtr/goleveldb/leveldb/version_test.go delete mode 100644 vendor/github.com/syndtr/goleveldb/manualtest/dbstress/key.go delete mode 100644 vendor/github.com/syndtr/goleveldb/manualtest/dbstress/main.go delete mode 100644 vendor/github.com/syndtr/goleveldb/manualtest/filelock/main.go delete mode 100644 vendor/github.com/tinylib/msgp/.gitignore delete mode 100644 vendor/github.com/tinylib/msgp/.travis.yml delete mode 100644 vendor/github.com/tinylib/msgp/LICENSE delete mode 100644 vendor/github.com/tinylib/msgp/Makefile delete mode 100644 vendor/github.com/tinylib/msgp/README.md delete mode 100644 vendor/github.com/tinylib/msgp/_generated/convert.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/convert_test.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/def.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/def_test.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/gen_test.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/issue102.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/issue191.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/issue191_test.go delete mode 100644 vendor/github.com/tinylib/msgp/_generated/issue94.go delete mode 100755 vendor/github.com/tinylib/msgp/_generated/search.sh delete mode 100644 vendor/github.com/tinylib/msgp/gen/decode.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/elem.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/encode.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/marshal.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/size.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/spec.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/testgen.go delete mode 100644 vendor/github.com/tinylib/msgp/gen/unmarshal.go delete mode 100644 vendor/github.com/tinylib/msgp/issue185_test.go delete mode 100644 vendor/github.com/tinylib/msgp/main.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/advise_linux.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/advise_other.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/appengine.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/circular.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/defs.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/defs_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/edit.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/edit_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/elsize.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/errors.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/extension.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/extension_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/file.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/file_port.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/file_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/floatbench_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/integers.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/json.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/json_bytes.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/json_bytes_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/json_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/number.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/number_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/raw_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/read.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/read_bytes.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/read_bytes_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/read_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/size.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/unsafe.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/write.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/write_bytes.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/write_bytes_test.go delete mode 100644 vendor/github.com/tinylib/msgp/msgp/write_test.go delete mode 100644 vendor/github.com/tinylib/msgp/parse/directives.go delete mode 100644 vendor/github.com/tinylib/msgp/parse/getast.go delete mode 100644 vendor/github.com/tinylib/msgp/parse/inline.go delete mode 100644 vendor/github.com/tinylib/msgp/printer/print.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/.travis.yml delete mode 100644 vendor/github.com/vmihailenco/msgpack/CHANGELOG.md delete mode 100644 vendor/github.com/vmihailenco/msgpack/LICENSE delete mode 100644 vendor/github.com/vmihailenco/msgpack/Makefile delete mode 100644 vendor/github.com/vmihailenco/msgpack/README.md delete mode 100644 vendor/github.com/vmihailenco/msgpack/appengine.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/bench_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/codes/codes.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_map.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_number.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_query.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_slice.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_string.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/decode_value.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/encode.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/encode_map.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/encode_number.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/encode_slice.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/encode_value.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/example_CustomEncoder_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/example_registerExt_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/example_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/ext.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/ext_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/msgpack.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/msgpack_test.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/tag.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/time.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/types.go delete mode 100644 vendor/github.com/vmihailenco/msgpack/types_test.go create mode 100644 vendor/github.com/x448/float16/.github/workflows/ci.yml create mode 100644 vendor/github.com/x448/float16/.github/workflows/cover.yml create mode 100644 vendor/github.com/x448/float16/.github/workflows/linters.yml create mode 100644 vendor/github.com/x448/float16/.golangci.yml rename vendor/github.com/{rivo/uniseg/LICENSE.txt => x448/float16/LICENSE} (93%) create mode 100644 vendor/github.com/x448/float16/README.md create mode 100644 vendor/github.com/x448/float16/float16.go create mode 100644 vendor/github.com/x448/float16/float16_bench_test.go create mode 100644 vendor/github.com/x448/float16/float16_test.go create mode 100644 vendor/github.com/x448/float16/go.mod delete mode 100644 vendor/github.com/ybbus/jsonrpc/examples_test.go create mode 100644 vendor/github.com/ybbus/jsonrpc/go.mod create mode 100644 vendor/github.com/ybbus/jsonrpc/go.sum create mode 100644 vendor/golang.org/x/crypto/internal/subtle/aliasing.go create mode 100644 vendor/golang.org/x/crypto/internal/subtle/aliasing_appengine.go create mode 100644 vendor/golang.org/x/crypto/internal/subtle/aliasing_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/README.md create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/aead_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/aes_cbc_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/dsa_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/ecdsa_compat_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/ecdsa_go115_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/ecdsa_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/eddsa_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/hkdf_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/hmac_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/internal/dsa/dsa.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/rsa_pss_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/rsa_signature_test.go create mode 100644 vendor/golang.org/x/crypto/internal/wycheproof/wycheproof_test.go delete mode 100644 vendor/golang.org/x/tools/.directory delete mode 100644 vendor/golang.org/x/tools/.gitattributes delete mode 100644 vendor/golang.org/x/tools/.gitignore delete mode 100644 vendor/golang.org/x/tools/AUTHORS delete mode 100644 vendor/golang.org/x/tools/CONTRIBUTING.md delete mode 100644 vendor/golang.org/x/tools/CONTRIBUTORS delete mode 100644 vendor/golang.org/x/tools/PATENTS delete mode 100644 vendor/golang.org/x/tools/README.md delete mode 100644 vendor/golang.org/x/tools/benchmark/parse/parse.go delete mode 100644 vendor/golang.org/x/tools/benchmark/parse/parse_test.go delete mode 100644 vendor/golang.org/x/tools/blog/atom/atom.go delete mode 100644 vendor/golang.org/x/tools/blog/blog.go delete mode 100644 vendor/golang.org/x/tools/cmd/benchcmp/benchcmp.go delete mode 100644 vendor/golang.org/x/tools/cmd/benchcmp/benchcmp_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/benchcmp/compare.go delete mode 100644 vendor/golang.org/x/tools/cmd/benchcmp/compare_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/benchcmp/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/.gitignore delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/main_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/testdata/out.golden delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/testdata/src/domain.name/importdecl/p.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/testdata/src/initial/a.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/testdata/src/initial/b.go delete mode 100644 vendor/golang.org/x/tools/cmd/bundle/testdata/src/initial/c.go delete mode 100644 vendor/golang.org/x/tools/cmd/callgraph/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/callgraph/main_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/callgraph/testdata/src/pkg/pkg.go delete mode 100644 vendor/golang.org/x/tools/cmd/callgraph/testdata/src/pkg/pkg_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/compilebench/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/README delete mode 100644 vendor/golang.org/x/tools/cmd/cover/cover.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/cover_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/func.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/html.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/testdata/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/cover/testdata/test.go delete mode 100644 vendor/golang.org/x/tools/cmd/digraph/digraph.go delete mode 100644 vendor/golang.org/x/tools/cmd/digraph/digraph_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/eg/eg.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/main_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/fruit.io/banana/banana.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/fruit.io/orange/orange.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/fruit.io/pear/pear.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/new.com/one/one.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/old.com/bad/bad.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/old.com/one/one.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/titanic.biz/bar/bar.go delete mode 100644 vendor/golang.org/x/tools/cmd/fiximports/testdata/src/titanic.biz/foo/foo.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/.dockerignore delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/.gitignore delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/Dockerfile delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/LICENSE delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/README.md delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/download.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/download_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/main_test.go delete mode 100755 vendor/golang.org/x/tools/cmd/getgo/make.bash delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/path.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/path_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/server/README.md delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/server/app.yaml delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/server/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/steps.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/system.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/system_unix.go delete mode 100644 vendor/golang.org/x/tools/cmd/getgo/system_windows.go delete mode 100755 vendor/golang.org/x/tools/cmd/getgo/upload.bash delete mode 100644 vendor/golang.org/x/tools/cmd/go-contrib-init/contrib.go delete mode 100644 vendor/golang.org/x/tools/cmd/go-contrib-init/contrib_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/gc.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/gccgo.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/godex.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/isAlias18.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/isAlias19.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/print.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/source.go delete mode 100644 vendor/golang.org/x/tools/cmd/godex/writetype.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/README.godoc-app delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/appinit.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/autocert.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/blog.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/codewalk.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/dl.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/godoc19_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/godoc_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/handlers.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/index.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/play.go delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/remotesearch.go delete mode 100755 vendor/golang.org/x/tools/cmd/godoc/setup-godoc-app.bash delete mode 100644 vendor/golang.org/x/tools/cmd/godoc/x.go delete mode 100644 vendor/golang.org/x/tools/cmd/goimports/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/goimports/goimports.go delete mode 100644 vendor/golang.org/x/tools/cmd/goimports/goimports_gc.go delete mode 100644 vendor/golang.org/x/tools/cmd/goimports/goimports_not_gc.go delete mode 100644 vendor/golang.org/x/tools/cmd/gomvpkg/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/gorename/cgo_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/gorename/gorename_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/gorename/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/gotype/gotype.go delete mode 100644 vendor/golang.org/x/tools/cmd/gotype/sizesFor18.go delete mode 100644 vendor/golang.org/x/tools/cmd/gotype/sizesFor19.go delete mode 100644 vendor/golang.org/x/tools/cmd/goyacc/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/goyacc/testdata/expr/README delete mode 100644 vendor/golang.org/x/tools/cmd/goyacc/testdata/expr/expr.y delete mode 100644 vendor/golang.org/x/tools/cmd/goyacc/testdata/expr/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/goyacc/yacc.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/callees.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/callers.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/callstack.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/definition.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/describe.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/freevars.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/guru.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/guru_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/implements.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/isAlias18.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/isAlias19.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/peers.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/pointsto.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/pos.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/referrers.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/serial/serial.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/README.txt delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/alias/alias.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/alias/alias.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/calls-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/calls-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/calls/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/calls/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main19.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main19.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/type.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe/main19.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/describe/main19.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/freevars/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/freevars/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-methods-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-methods-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-methods/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements-methods/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/imports/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/imports/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/lib/lib.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/lib/sublib/sublib.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/main/multi.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/peers-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/peers-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/peers/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/peers/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/pointsto-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/pointsto-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/pointsto/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/pointsto/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/ext_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/int_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/reflection/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/reflection/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/softerrs/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/softerrs/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/what-json/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/what-json/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/what/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/what/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/whicherrs/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/whicherrs/main.golden delete mode 100644 vendor/golang.org/x/tools/cmd/guru/unit_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/what.go delete mode 100644 vendor/golang.org/x/tools/cmd/guru/whicherrs.go delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/.clang-format delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/.gitignore delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/README.md delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/main.ts delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/main_test.ts delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/package.json delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/testing/karma.conf.js delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/testing/test_main.js delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/tsconfig.json delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/client/tslint.json delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/internal/core/mmapfile.go delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/internal/core/mmapfile_other.go delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/internal/core/raw.go delete mode 100644 vendor/golang.org/x/tools/cmd/heapview/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/html2article/conv.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/appengine.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/dir.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/doc.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/local.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/play.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/play_http.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/play_socket.go delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/article.css delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/dir.css delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/dir.js delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/favicon.ico delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/jquery-ui.js delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/notes.css delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/notes.js delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/slides.js delete mode 100644 vendor/golang.org/x/tools/cmd/present/static/styles.css delete mode 100644 vendor/golang.org/x/tools/cmd/present/templates/action.tmpl delete mode 100644 vendor/golang.org/x/tools/cmd/present/templates/article.tmpl delete mode 100644 vendor/golang.org/x/tools/cmd/present/templates/dir.tmpl delete mode 100644 vendor/golang.org/x/tools/cmd/present/templates/slides.tmpl delete mode 100644 vendor/golang.org/x/tools/cmd/ssadump/main.go delete mode 100644 vendor/golang.org/x/tools/cmd/stress/stress.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/endtoend_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/golden_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/importer18.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/importer19.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/stringer.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/cgo.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/day.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/gap.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/num.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/number.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/prime.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/unum.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/testdata/unum2.go delete mode 100644 vendor/golang.org/x/tools/cmd/stringer/util_test.go delete mode 100644 vendor/golang.org/x/tools/cmd/tip/Dockerfile delete mode 100644 vendor/golang.org/x/tools/cmd/tip/Makefile delete mode 100644 vendor/golang.org/x/tools/cmd/tip/README delete mode 100644 vendor/golang.org/x/tools/cmd/tip/cert.go delete mode 100644 vendor/golang.org/x/tools/cmd/tip/godoc.go delete mode 100644 vendor/golang.org/x/tools/cmd/tip/godoc.yaml delete mode 100644 vendor/golang.org/x/tools/cmd/tip/talks.go delete mode 100644 vendor/golang.org/x/tools/cmd/tip/talks.yaml delete mode 100644 vendor/golang.org/x/tools/cmd/tip/tip-rc.yaml delete mode 100644 vendor/golang.org/x/tools/cmd/tip/tip-service.yaml delete mode 100644 vendor/golang.org/x/tools/cmd/tip/tip.go delete mode 100644 vendor/golang.org/x/tools/cmd/tip/tip_test.go delete mode 100755 vendor/golang.org/x/tools/cmd/toolstash/buildall delete mode 100644 vendor/golang.org/x/tools/cmd/toolstash/cmp.go delete mode 100644 vendor/golang.org/x/tools/cmd/toolstash/main.go delete mode 100644 vendor/golang.org/x/tools/codereview.cfg delete mode 100644 vendor/golang.org/x/tools/container/intsets/popcnt_amd64.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/popcnt_amd64.s delete mode 100644 vendor/golang.org/x/tools/container/intsets/popcnt_gccgo.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/popcnt_gccgo_c.c delete mode 100644 vendor/golang.org/x/tools/container/intsets/popcnt_generic.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/sparse.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/sparse_test.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/util.go delete mode 100644 vendor/golang.org/x/tools/container/intsets/util_test.go delete mode 100644 vendor/golang.org/x/tools/cover/profile.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing_test.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports_test.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite_test.go delete mode 100644 vendor/golang.org/x/tools/go/ast/astutil/util.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/allpackages.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/allpackages_test.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/fakecontext.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/overlay.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/overlay_test.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/tags.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/tags_test.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/util.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/util_test.go delete mode 100644 vendor/golang.org/x/tools/go/buildutil/util_windows_test.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/callgraph.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/cha.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/cha_test.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/testdata/func.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/testdata/iface.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/testdata/recv.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/rta/rta.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/rta/rta_test.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/rta/testdata/func.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/rta/testdata/iface.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/rta/testdata/rtype.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/static/static.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/static/static_test.go delete mode 100644 vendor/golang.org/x/tools/go/callgraph/util.go delete mode 100644 vendor/golang.org/x/tools/go/gccgoexportdata/gccgoexportdata.go delete mode 100644 vendor/golang.org/x/tools/go/gccgoexportdata/gccgoexportdata_test.go delete mode 100644 vendor/golang.org/x/tools/go/gccgoexportdata/testdata/errors.gox delete mode 100644 vendor/golang.org/x/tools/go/gccgoexportdata/testdata/long.a delete mode 100644 vendor/golang.org/x/tools/go/gccgoexportdata/testdata/short.a delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/example_test.go delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/gcexportdata_test.go delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/importer.go delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/main.go delete mode 100644 vendor/golang.org/x/tools/go/gcexportdata/testdata/errors-ae16.a delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/bexport.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/bexport19_test.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/bexport_test.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/bimport.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/exportdata.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/gcimporter.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/gcimporter_test.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/isAlias18.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/isAlias19.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/a.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/b.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/exports.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/issue15920.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/issue20046.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/p.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/versions/test.go delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/versions/test_go1.7_0.a delete mode 100644 vendor/golang.org/x/tools/go/gcimporter15/testdata/versions/test_go1.7_1.a delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/backdoor.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/gccgoinstallation.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/gccgoinstallation_test.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/importer.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/importer19_test.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/importer_test.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/parser.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/parser_test.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/alias.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/complexnums.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/complexnums.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/conversions.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/conversions.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/imports.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/imports.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/pointer.go delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/pointer.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/time.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testdata/unicode.gox delete mode 100644 vendor/golang.org/x/tools/go/internal/gccgoimporter/testenv_test.go delete mode 100644 vendor/golang.org/x/tools/go/loader/cgo.go delete mode 100644 vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go delete mode 100644 vendor/golang.org/x/tools/go/loader/doc.go delete mode 100644 vendor/golang.org/x/tools/go/loader/example_test.go delete mode 100644 vendor/golang.org/x/tools/go/loader/loader.go delete mode 100644 vendor/golang.org/x/tools/go/loader/loader_test.go delete mode 100644 vendor/golang.org/x/tools/go/loader/stdlib_test.go delete mode 100644 vendor/golang.org/x/tools/go/loader/testdata/a.go delete mode 100644 vendor/golang.org/x/tools/go/loader/testdata/b.go delete mode 100644 vendor/golang.org/x/tools/go/loader/testdata/badpkgdecl.go delete mode 100644 vendor/golang.org/x/tools/go/loader/util.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/TODO delete mode 100644 vendor/golang.org/x/tools/go/pointer/analysis.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/api.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/callgraph.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/constraint.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/doc.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/example_test.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/gen.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/hvn.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/intrinsics.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/labels.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/opt.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/pointer_test.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/print.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/query.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/query_test.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/reflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/solve.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/stdlib_test.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/a_test.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/another.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/arrayreflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/arrays.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/channels.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/chanreflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/chanreflect1.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/context.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/conv.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/extended.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/finalizer.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/flow.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/fmtexcerpt.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/func.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/funcreflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/hello.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/interfaces.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/issue9002.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/mapreflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/maps.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/panic.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/recur.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/reflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/rtti.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/structreflect.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/structs.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/testdata/timer.go delete mode 100644 vendor/golang.org/x/tools/go/pointer/util.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/blockopt.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/builder.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/builder_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/const.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/create.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/doc.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/dom.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/emit.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/example_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/func.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/identical.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/identical_17.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/identical_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/external.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/external_darwin.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/external_unix.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/interp.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/interp_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/map.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/ops.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/reflect.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/a_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/b_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/boundmeth.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/c_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/callstack.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/complit.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/coverage.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/defer.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/fieldprom.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/ifaceconv.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/ifaceprom.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/initorder.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/methprom.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/mrvchain.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/range.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/recover.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/reflect.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/testdata/static.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/interp/value.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/lift.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/lvalue.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/methods.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/mode.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/print.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/sanity.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/source.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/source_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssa.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/load.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/load_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/switch.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/switch_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/testdata/switches.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/visit.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/stdlib_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/testdata/objlookup.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/testdata/structconv.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/testdata/valueforexpr.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/testmain.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/testmain_test.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/util.go delete mode 100644 vendor/golang.org/x/tools/go/ssa/wrappers.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/example_test.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/imports.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/imports_test.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/map.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/map_test.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/ui.go delete mode 100644 vendor/golang.org/x/tools/go/types/typeutil/ui_test.go delete mode 100644 vendor/golang.org/x/tools/go/vcs/discovery.go delete mode 100644 vendor/golang.org/x/tools/go/vcs/env.go delete mode 100644 vendor/golang.org/x/tools/go/vcs/http.go delete mode 100644 vendor/golang.org/x/tools/go/vcs/vcs.go delete mode 100644 vendor/golang.org/x/tools/go/vcs/vcs_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/README.md delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/README delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/analysis.go delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/callgraph.go delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/implements.go delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/json.go delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/peers.go delete mode 100644 vendor/golang.org/x/tools/godoc/analysis/typeinfo.go delete mode 100644 vendor/golang.org/x/tools/godoc/cmdline.go delete mode 100644 vendor/golang.org/x/tools/godoc/cmdline_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/corpus.go delete mode 100644 vendor/golang.org/x/tools/godoc/dirtrees.go delete mode 100644 vendor/golang.org/x/tools/godoc/dl/dl.go delete mode 100644 vendor/golang.org/x/tools/godoc/dl/dl_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/dl/tmpl.go delete mode 100644 vendor/golang.org/x/tools/godoc/format.go delete mode 100644 vendor/golang.org/x/tools/godoc/godoc.go delete mode 100644 vendor/golang.org/x/tools/godoc/godoc17_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/godoc_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/index.go delete mode 100644 vendor/golang.org/x/tools/godoc/index_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/linkify.go delete mode 100644 vendor/golang.org/x/tools/godoc/meta.go delete mode 100644 vendor/golang.org/x/tools/godoc/page.go delete mode 100644 vendor/golang.org/x/tools/godoc/parser.go delete mode 100644 vendor/golang.org/x/tools/godoc/pres.go delete mode 100644 vendor/golang.org/x/tools/godoc/proxy/proxy.go delete mode 100644 vendor/golang.org/x/tools/godoc/redirect/hash.go delete mode 100644 vendor/golang.org/x/tools/godoc/redirect/redirect.go delete mode 100644 vendor/golang.org/x/tools/godoc/redirect/redirect_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/search.go delete mode 100644 vendor/golang.org/x/tools/godoc/server.go delete mode 100644 vendor/golang.org/x/tools/godoc/short/short.go delete mode 100644 vendor/golang.org/x/tools/godoc/short/tmpl.go delete mode 100644 vendor/golang.org/x/tools/godoc/snippet.go delete mode 100644 vendor/golang.org/x/tools/godoc/spec.go delete mode 100644 vendor/golang.org/x/tools/godoc/spot.go delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/call-eg.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/call3.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/callers1.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/callers2.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/chan1.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/chan2a.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/chan2b.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/error1.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/help.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/ident-def.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/ident-field.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/ident-func.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/ipcg-func.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/ipcg-pkg.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/typeinfo-pkg.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/analysis/typeinfo-src.png delete mode 100644 vendor/golang.org/x/tools/godoc/static/callgraph.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/codewalk.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/codewalkdir.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/dirlist.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/doc.go delete mode 100644 vendor/golang.org/x/tools/godoc/static/error.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/example.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/gen.go delete mode 100644 vendor/golang.org/x/tools/godoc/static/godoc.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/godocs.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/minus.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/plus.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-black-line.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-black.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-default-line.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-default.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-gray-line.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/images/treeview-gray.gif delete mode 100644 vendor/golang.org/x/tools/godoc/static/implements.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/jquery.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/jquery.treeview.css delete mode 100644 vendor/golang.org/x/tools/godoc/static/jquery.treeview.edit.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/jquery.treeview.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/makestatic.go delete mode 100644 vendor/golang.org/x/tools/godoc/static/methodset.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/opensearch.xml delete mode 100644 vendor/golang.org/x/tools/godoc/static/package.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/package.txt delete mode 100644 vendor/golang.org/x/tools/godoc/static/play.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/playground.js delete mode 100644 vendor/golang.org/x/tools/godoc/static/search.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/search.txt delete mode 100644 vendor/golang.org/x/tools/godoc/static/searchcode.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/searchdoc.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/searchtxt.html delete mode 100644 vendor/golang.org/x/tools/godoc/static/static.go delete mode 100644 vendor/golang.org/x/tools/godoc/static/style.css delete mode 100644 vendor/golang.org/x/tools/godoc/tab.go delete mode 100644 vendor/golang.org/x/tools/godoc/template.go delete mode 100644 vendor/golang.org/x/tools/godoc/util/throttle.go delete mode 100644 vendor/golang.org/x/tools/godoc/util/util.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/emptyvfs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/emptyvfs_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/gatefs/gatefs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/httpfs/httpfs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/mapfs/mapfs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/mapfs/mapfs_test.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/namespace.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/os.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/vfs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/zipfs/zipfs.go delete mode 100644 vendor/golang.org/x/tools/godoc/vfs/zipfs/zipfs_test.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk_dirent_fileno.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk_dirent_ino.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk_portable.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk_test.go delete mode 100644 vendor/golang.org/x/tools/imports/fastwalk_unix.go delete mode 100644 vendor/golang.org/x/tools/imports/fix.go delete mode 100644 vendor/golang.org/x/tools/imports/fix_test.go delete mode 100644 vendor/golang.org/x/tools/imports/imports.go delete mode 100644 vendor/golang.org/x/tools/imports/mkindex.go delete mode 100644 vendor/golang.org/x/tools/imports/mkstdlib.go delete mode 100644 vendor/golang.org/x/tools/imports/sortimports.go delete mode 100644 vendor/golang.org/x/tools/imports/zstdlib.go delete mode 100644 vendor/golang.org/x/tools/playground/appengine.go delete mode 100644 vendor/golang.org/x/tools/playground/appenginevm.go delete mode 100644 vendor/golang.org/x/tools/playground/common.go delete mode 100644 vendor/golang.org/x/tools/playground/local.go delete mode 100644 vendor/golang.org/x/tools/playground/socket/socket.go delete mode 100644 vendor/golang.org/x/tools/playground/socket/socket_test.go delete mode 100644 vendor/golang.org/x/tools/present/args.go delete mode 100644 vendor/golang.org/x/tools/present/caption.go delete mode 100644 vendor/golang.org/x/tools/present/code.go delete mode 100644 vendor/golang.org/x/tools/present/code_test.go delete mode 100644 vendor/golang.org/x/tools/present/doc.go delete mode 100644 vendor/golang.org/x/tools/present/html.go delete mode 100644 vendor/golang.org/x/tools/present/iframe.go delete mode 100644 vendor/golang.org/x/tools/present/image.go delete mode 100644 vendor/golang.org/x/tools/present/link.go delete mode 100644 vendor/golang.org/x/tools/present/link_test.go delete mode 100644 vendor/golang.org/x/tools/present/parse.go delete mode 100644 vendor/golang.org/x/tools/present/style.go delete mode 100644 vendor/golang.org/x/tools/present/style_test.go delete mode 100644 vendor/golang.org/x/tools/present/video.go delete mode 100644 vendor/golang.org/x/tools/refactor/README delete mode 100644 vendor/golang.org/x/tools/refactor/eg/eg.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/eg_test.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/match.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/rewrite.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/A.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/A1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/A1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/A2.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/A2.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/B.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/B1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/B1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/C.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/C1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/C1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/D.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/D1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/D1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/E.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/E1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/E1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/F.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/F1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/F1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/G.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/G1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/G1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/H.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/H1.go delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/H1.golden delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/bad_type.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/expr_type_mismatch.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/no_after_return.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/no_before.template delete mode 100644 vendor/golang.org/x/tools/refactor/eg/testdata/type_mismatch.template delete mode 100644 vendor/golang.org/x/tools/refactor/importgraph/graph.go delete mode 100644 vendor/golang.org/x/tools/refactor/importgraph/graph_test.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/check.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/mvpkg.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/mvpkg_test.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/rename.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/rename_test.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/spec.go delete mode 100644 vendor/golang.org/x/tools/refactor/rename/util.go delete mode 100644 vendor/golang.org/x/tools/refactor/satisfy/find.go delete mode 100644 vendor/golang.org/x/tools/third_party/moduleloader/LICENSE delete mode 100644 vendor/golang.org/x/tools/third_party/moduleloader/moduleloader.js delete mode 100644 vendor/golang.org/x/tools/third_party/typescript/LICENSE delete mode 100644 vendor/golang.org/x/tools/third_party/typescript/typescript.js delete mode 100644 vendor/golang.org/x/tools/third_party/webcomponents/LICENSE delete mode 100644 vendor/golang.org/x/tools/third_party/webcomponents/customelements.js delete mode 100644 vendor/golang/protobuf/.gitignore delete mode 100644 vendor/golang/protobuf/.travis.yml delete mode 100644 vendor/golang/protobuf/AUTHORS delete mode 100644 vendor/golang/protobuf/CONTRIBUTORS delete mode 100644 vendor/golang/protobuf/LICENSE delete mode 100644 vendor/golang/protobuf/Makefile delete mode 100644 vendor/golang/protobuf/README.md delete mode 100644 vendor/golang/protobuf/conformance/Makefile delete mode 100644 vendor/golang/protobuf/conformance/conformance.go delete mode 100755 vendor/golang/protobuf/conformance/conformance.sh delete mode 100644 vendor/golang/protobuf/conformance/failure_list_go.txt delete mode 100644 vendor/golang/protobuf/conformance/internal/conformance_proto/conformance.pb.go delete mode 100644 vendor/golang/protobuf/conformance/internal/conformance_proto/conformance.proto delete mode 100755 vendor/golang/protobuf/conformance/test.sh delete mode 100644 vendor/golang/protobuf/descriptor/descriptor.go delete mode 100644 vendor/golang/protobuf/descriptor/descriptor_test.go delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb.go delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb_test.go delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.pb.go delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.proto delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.pb.go delete mode 100644 vendor/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.proto delete mode 100644 vendor/golang/protobuf/proto/all_test.go delete mode 100644 vendor/golang/protobuf/proto/any_test.go delete mode 100644 vendor/golang/protobuf/proto/clone.go delete mode 100644 vendor/golang/protobuf/proto/clone_test.go delete mode 100644 vendor/golang/protobuf/proto/decode.go delete mode 100644 vendor/golang/protobuf/proto/decode_test.go delete mode 100644 vendor/golang/protobuf/proto/discard.go delete mode 100644 vendor/golang/protobuf/proto/discard_test.go delete mode 100644 vendor/golang/protobuf/proto/encode.go delete mode 100644 vendor/golang/protobuf/proto/encode_test.go delete mode 100644 vendor/golang/protobuf/proto/equal.go delete mode 100644 vendor/golang/protobuf/proto/equal_test.go delete mode 100644 vendor/golang/protobuf/proto/extensions.go delete mode 100644 vendor/golang/protobuf/proto/extensions_test.go delete mode 100644 vendor/golang/protobuf/proto/lib.go delete mode 100644 vendor/golang/protobuf/proto/map_test.go delete mode 100644 vendor/golang/protobuf/proto/message_set.go delete mode 100644 vendor/golang/protobuf/proto/message_set_test.go delete mode 100644 vendor/golang/protobuf/proto/pointer_reflect.go delete mode 100644 vendor/golang/protobuf/proto/pointer_unsafe.go delete mode 100644 vendor/golang/protobuf/proto/properties.go delete mode 100644 vendor/golang/protobuf/proto/proto3_proto/proto3.pb.go delete mode 100644 vendor/golang/protobuf/proto/proto3_proto/proto3.proto delete mode 100644 vendor/golang/protobuf/proto/proto3_test.go delete mode 100644 vendor/golang/protobuf/proto/size2_test.go delete mode 100644 vendor/golang/protobuf/proto/size_test.go delete mode 100644 vendor/golang/protobuf/proto/table_marshal.go delete mode 100644 vendor/golang/protobuf/proto/table_merge.go delete mode 100644 vendor/golang/protobuf/proto/table_unmarshal.go delete mode 100644 vendor/golang/protobuf/proto/test_proto/test.pb.go delete mode 100644 vendor/golang/protobuf/proto/test_proto/test.proto delete mode 100644 vendor/golang/protobuf/proto/text.go delete mode 100644 vendor/golang/protobuf/proto/text_parser.go delete mode 100644 vendor/golang/protobuf/proto/text_parser_test.go delete mode 100644 vendor/golang/protobuf/proto/text_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/descriptor/descriptor.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/doc.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/generator/generator.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/generator/internal/remap/remap.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/generator/internal/remap/remap_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/generator/name_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/golden_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/grpc/grpc.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/link_grpc.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/main.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/plugin/plugin.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/plugin/plugin.pb.golden delete mode 100644 vendor/golang/protobuf/protoc-gen-go/plugin/plugin.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/deprecated/deprecated.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/deprecated/deprecated.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_base/extension_base.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_base/extension_base.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_extra/extension_extra.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_extra/extension_extra.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_user/extension_user.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/extension_user/extension_user.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/grpc/grpc.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/grpc/grpc.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/a.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/a.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/b.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/b.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/sub/a.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/sub/a.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/sub/b.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public/sub/b.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/import_public_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/fmt/m.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/fmt/m.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_1/m1.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_1/m1.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_1/m2.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_1/m2.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2/m3.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2/m3.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2/m4.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_a_2/m4.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_b_1/m1.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_b_1/m1.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_b_1/m2.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_b_1/m2.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_a1m1.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_a1m1.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_a1m2.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_a1m2.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_all.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/imports/test_import_all.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/main_test.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi1.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi1.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi2.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi2.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi3.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/multi/multi3.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/my_test/test.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/my_test/test.proto delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/proto3/proto3.pb.go delete mode 100644 vendor/golang/protobuf/protoc-gen-go/testdata/proto3/proto3.proto delete mode 100644 vendor/golang/protobuf/ptypes/any.go delete mode 100644 vendor/golang/protobuf/ptypes/any/any.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/any/any.proto delete mode 100644 vendor/golang/protobuf/ptypes/any_test.go delete mode 100644 vendor/golang/protobuf/ptypes/doc.go delete mode 100644 vendor/golang/protobuf/ptypes/duration.go delete mode 100644 vendor/golang/protobuf/ptypes/duration/duration.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/duration/duration.proto delete mode 100644 vendor/golang/protobuf/ptypes/duration_test.go delete mode 100644 vendor/golang/protobuf/ptypes/empty/empty.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/empty/empty.proto delete mode 100644 vendor/golang/protobuf/ptypes/struct/struct.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/struct/struct.proto delete mode 100644 vendor/golang/protobuf/ptypes/timestamp.go delete mode 100644 vendor/golang/protobuf/ptypes/timestamp/timestamp.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/timestamp/timestamp.proto delete mode 100644 vendor/golang/protobuf/ptypes/timestamp_test.go delete mode 100644 vendor/golang/protobuf/ptypes/wrappers/wrappers.pb.go delete mode 100644 vendor/golang/protobuf/ptypes/wrappers/wrappers.proto delete mode 100755 vendor/golang/protobuf/regenerate.sh delete mode 100644 walletapi/rpcserver/rpc_get_bulk_payments.go rename walletapi/{db.go => wallet_disk.go} (94%) rename walletapi/{db_memory.go => wallet_memory.go} (96%) diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..28e67bb --- /dev/null +++ b/Changelog.md @@ -0,0 +1,46 @@ +### Welcome to the DEROHE Testnet +[Explorer](https://testnetexplorer.dero.io) [Source](https://github.com/deroproject/derohe) [Twitter](https://twitter.com/DeroProject) [Discord](https://discord.gg/H95TJDp) [Wiki](https://wiki.dero.io) [Github](https://github.com/deroproject/derohe) [DERO CryptoNote Mainnet Stats](http://network.dero.io) [Mainnet WebWallet](https://wallet.dero.io/) + +### DERO HE Changelog +[From Wikipedia: ](https://en.wikipedia.org/wiki/Homomorphic_encryption) + +###At this point in time, DERO blockchain has the first mover advantage in the following + * Private SCs ( no one knows who owns what tokens and who is transferring to whom and how much is being transferred) + * Homomorphic protocol + * Ability to do instant sync (takes couple of seconds or minutes), depends on network bandwidth. + * Ability to deliver encrypted license keys and other data. + * Pruned chains are the core. + * Ability to model 99.9% earth based financial model of the world. + * Privacy by design, backed by crypto algorithms. Many years of research in place. + + +###3.3 + * Private SCs are now supported. (90% completed). + * Sample Token contract is available with guide. + * Multi-send is now possible. sending to multiple destination per tx + * Few more ideas implemented and will be tested for review in upcoming technology preview. + +###3.2 + * Open SCs are now supported + * Private SCs which have their balance encrypted at all times (under implementation) + * SCs can now update themselves. however, new code will only run on next invocation + * Multi Send is under implementation. + +###3.1 + * TX now have significant savings of around 31 * ringsize bytes for every tx + * Daemon now supports pruned chains. + * Daemon by default bootstraps a pruned chain. + * Daemon currently syncs full node by using --fullnode option. + * P2P has been rewritten for various improvements and easier understanding of state machine + * Address specification now enables to embed various RPC parameters for easier transaction + * DERO blockchain represents transaction finality in a couple of blocks (less than 1 minute), unlike other blockchains. + * Proving and parsing of embedded data is now available in explorer. + * Senders/Receivers both have proofs which confirm data sent on execution. + * All tx now have inbuilt space of 144 bytes for user defined data + * User defined space has inbuilt RPC which can be used to implement most practical use-cases.All user defined data is encrypted. + * The model currrently defines data on chain while execution is referred to wallet extensions. A dummy example of pongserver extension showcases how to enable purchases/delivery of license keys/information privately. + * Burn transactions which burn value are now working. + +###3.0 + * DERO HE implemented + diff --git a/Start.md b/Start.md index 6ae14fa..3e6e5f3 100644 --- a/Start.md +++ b/Start.md @@ -2,7 +2,7 @@ DERO is written in golang and very easy to install both from source and binary. Installation From Source: - Install Golang, minimum Golang 1.10.3 required. + Install Golang, minimum Golang 1.15 required. In go workspace: go get -u github.com/deroproject/derohe/... Check go workspace bin folder for binaries. For example on Linux machine following binaries will be created: diff --git a/block/block.go b/block/block.go index ba52397..5078de2 100644 --- a/block/block.go +++ b/block/block.go @@ -24,10 +24,10 @@ import "runtime/debug" import "encoding/hex" import "encoding/binary" -import "github.com/ebfe/keccak" +import "golang.org/x/crypto/sha3" import "github.com/romana/rlog" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" //import "github.com/deroproject/derosuite/config" import "github.com/deroproject/derohe/astrobwt" @@ -65,7 +65,7 @@ func (bl *Block) GetHash() (hash crypto.Hash) { long_header := bl.GetBlockWork() // keccak hash of this above blob, gives the block id - return crypto.Keccak256(long_header) + return sha3.Sum256(long_header) } // converts a block, into a getwork style work, ready for either submitting the block @@ -78,7 +78,7 @@ func (bl *Block) GetBlockWork() []byte { buf = append(buf, []byte{byte(bl.Major_Version), byte(bl.Minor_Version), 0, 0, 0, 0, 0}...) // 0 first 7 bytes are version in little endia format binary.LittleEndian.PutUint32(buf[2:6], uint32(bl.Timestamp)) - header_hash := crypto.Keccak256(bl.getserializedheaderforwork()) // 0 + 7 + header_hash := sha3.Sum256(bl.getserializedheaderforwork()) // 0 + 7 buf = append(buf, header_hash[:]...) // 0 + 7 + 32 = 39 @@ -251,7 +251,7 @@ func (bl *Block) GetTipsHash() (result crypto.Hash) { }*/ // add all the remaining hashes - h := keccak.New256() + h := sha3.New256() for i := range bl.Tips { h.Write(bl.Tips[i][:]) } @@ -263,7 +263,7 @@ func (bl *Block) GetTipsHash() (result crypto.Hash) { // get block transactions // we have discarded the merkle tree and have shifted to a plain version func (bl *Block) GetTXSHash() (result crypto.Hash) { - h := keccak.New256() + h := sha3.New256() for i := range bl.Tx_hashes { h.Write(bl.Tx_hashes[i][:]) } diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 67f4b72..7b3a3b6 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -47,7 +47,7 @@ import "github.com/golang/groupcache/lru" import hashicorp_lru "github.com/hashicorp/golang-lru" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/errormsg" import "github.com/prometheus/client_golang/prometheus" @@ -57,6 +57,7 @@ import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/blockchain/mempool" import "github.com/deroproject/derohe/blockchain/regpool" +import "github.com/deroproject/derohe/rpc" /* import "github.com/deroproject/derosuite/emission" @@ -80,6 +81,7 @@ type Blockchain struct { Height int64 // chain height is always 1 more than block height_seen int64 // height seen on peers Top_ID crypto.Hash // id of the top block + Pruned int64 // until where the chain has been pruned Tips map[crypto.Hash]crypto.Hash // current tips @@ -108,6 +110,10 @@ type Blockchain struct { RPC_NotifyNewBlock *sync.Cond // used to notify rpc that a new block has been found RPC_NotifyHeightChanged *sync.Cond // used to notify rpc that chain height has changed due to addition of block + Dev_Address_Bytes []byte // used to fund reward every block + + Sync bool // whether the sync is active, used while bootstrapping + sync.RWMutex } @@ -168,6 +174,7 @@ func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) { logger.Fatalf("Failed to add genesis block, we can no longer continue. err %s", err) } } + /* // genesis block not in chain, add it to chain, together with its miner tx @@ -225,9 +232,26 @@ func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) { // hard forks must be initialized asap init_hard_forks(params) + // parse dev address once and for all + if addr, err := rpc.NewAddress(globals.Config.Dev_Address); err != nil { + logger.Fatalf("Could not parse dev address, err:%s", err) + } else { + chain.Dev_Address_Bytes = addr.PublicKey.EncodeCompressed() + } + // load the chain from the disk chain.Initialise_Chain_From_DB() + chain.Sync = true + //if globals.Arguments["--fullnode"] != nil { + if chain.Get_Height() <= 1 { + chain.Sync = false + if globals.Arguments["--fullnode"].(bool) { + chain.Sync = globals.Arguments["--fullnode"].(bool) + } + } + //} + // logger.Fatalf("Testing complete quitting") go clean_up_valid_cache() // clean up valid cache @@ -504,9 +528,11 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } // always check whether the coin base tx is okay - if bl.Height != 0 && !chain.Verify_Transaction_Coinbase(cbl, &bl.Miner_TX) { // if miner address is not registered give error - block_logger.Warnf("Miner address is not registered") - return errormsg.ErrInvalidBlock, false + if bl.Height != 0 { + if err = chain.Verify_Transaction_Coinbase(cbl, &bl.Miner_TX); err != nil { // if miner address is not registered give error + block_logger.Warnf("Error verifying coinbase tx, err :'%s'", err) + return err, false + } } // TODO we need to verify address whether they are valid points on curve or not @@ -563,7 +589,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro // another check, whether the tx is build with the latest snapshot of balance tree { for i := 0; i < len(cbl.Txs); i++ { - if cbl.Txs[i].TransactionType == transaction.NORMAL { + if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX { if cbl.Txs[i].Height+1 != cbl.Bl.Height { block_logger.Warnf("invalid tx mined %s", cbl.Txs[i].GetHash()) return errormsg.ErrTXDoubleSpend, false @@ -579,12 +605,12 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro nonce_map := map[crypto.Hash]bool{} for i := 0; i < len(cbl.Txs); i++ { - if cbl.Txs[i].TransactionType == transaction.NORMAL { - if _, ok := nonce_map[cbl.Txs[i].Proof.Nonce()]; ok { + if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX { + if _, ok := nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()]; ok { block_logger.Warnf("Double Spend attack within block %s", cbl.Txs[i].GetHash()) return errormsg.ErrTXDoubleSpend, false } - nonce_map[cbl.Txs[i].Proof.Nonce()] = true + nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()] = true } } } @@ -613,9 +639,9 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro hf_version := chain.Get_Current_Version_at_Height(chain.Calculate_Height_At_Tips(bl.Tips)) for i := 0; i < len(cbl.Txs); i++ { go func(j int) { - if !chain.Verify_Transaction_NonCoinbase(hf_version, cbl.Txs[j]) { // transaction verification failed + if err := chain.Verify_Transaction_NonCoinbase(hf_version, cbl.Txs[j]); err != nil { // transaction verification failed atomic.AddInt32(&fail_count, 1) // increase fail count by 1 - block_logger.Warnf("Block verification failed rejecting since TX %s verification failed", cbl.Txs[j].GetHash()) + block_logger.Warnf("Block verification failed rejecting since TX %s verification failed, err:'%s'", cbl.Txs[j].GetHash(), err) } wg.Done() }(i) @@ -705,6 +731,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro // any blocks which have not changed their topo will be skipped using graviton trick skip := true + for i := int64(0); i < int64(len(full_order)); i++ { // check whether the new block is at the same position at the last position @@ -752,12 +779,16 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro } - var balance_tree *graviton.Tree - // + var balance_tree, sc_meta *graviton.Tree + _ = sc_meta + + var ss *graviton.Snapshot if bl_current.Height == 0 { // if it's genesis block - if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err != nil { + if ss, err = chain.Store.Balance_store.LoadSnapshot(0); err != nil { panic(err) - } else if balance_tree, err = ss.GetTree(BALANCE_TREE); err != nil { + } else if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { + panic(err) + } else if sc_meta, err = ss.GetTree(config.SC_META); err != nil { panic(err) } } else { // we already have a block before us, use it @@ -772,13 +803,15 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro record_version = toporecord.State_Version } - ss, err := chain.Store.Balance_store.LoadSnapshot(record_version) + ss, err = chain.Store.Balance_store.LoadSnapshot(record_version) if err != nil { panic(err) } - balance_tree, err = ss.GetTree(BALANCE_TREE) - if err != nil { + if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { + panic(err) + } + if sc_meta, err = ss.GetTree(config.SC_META); err != nil { panic(err) } } @@ -790,7 +823,12 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro // their transactions are ignored //chain.Store.Topo_store.Write(i+base_topo_index, full_order[i],0, int64(bl_current.Height)) // write entry so as sideblock could work + var data_trees []*graviton.Tree + if !chain.isblock_SideBlock_internal(full_order[i], current_topo_block, int64(bl_current.Height)) { + + sc_change_cache := map[crypto.Hash]*graviton.Tree{} // cache entire changes for entire block + for _, txhash := range bl_current.Tx_hashes { // execute all the transactions if tx_bytes, err := chain.Store.Block_tx_store.ReadTX(txhash); err != nil { panic(err) @@ -799,23 +837,74 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro if err = tx.DeserializeHeader(tx_bytes); err != nil { panic(err) } + for t := range tx.Payloads { + if !tx.Payloads[t].SCID.IsZero() { + tree, _ := ss.GetTree(string(tx.Payloads[t].SCID[:])) + sc_change_cache[tx.Payloads[t].SCID] = tree + } + } // we have loaded a tx successfully, now lets execute it - fees_collected += chain.process_transaction(tx, balance_tree) + tx_fees := chain.process_transaction(sc_change_cache, tx, balance_tree) + + //fmt.Printf("transaction %s type %s data %+v\n", txhash, tx.TransactionType, tx.SCDATA) + if tx.TransactionType == transaction.SC_TX { + tx_fees, err = chain.process_transaction_sc(sc_change_cache, ss, bl_current.Height, uint64(current_topo_block), bl_current_hash, tx, balance_tree, sc_meta) + + //fmt.Printf("Processsing sc err %s\n", err) + if err == nil { // TODO process gasg here + + } + } + fees_collected += tx_fees } } - chain.process_miner_transaction(bl_current.Miner_TX, bl_current.Height == 0, balance_tree, fees_collected) + // at this point, we must commit all the SCs, so entire tree hash is interlinked + for scid, v := range sc_change_cache { + meta_bytes, err := sc_meta.Get(SC_Meta_Key(scid)) + if err != nil { + panic(err) + } + + var meta SC_META_DATA // the meta contains metadata about SC + if err := meta.UnmarshalBinary(meta_bytes); err != nil { + panic(err) + } + + if meta.DataHash, err = v.Hash(); err != nil { // encode data tree hash + panic(err) + } + + sc_meta.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + data_trees = append(data_trees, v) + + /*fmt.Printf("will commit tree name %x \n", v.GetName()) + c := v.Cursor() + for k, v, err := c.First(); err == nil; k, v, err = c.Next() { + fmt.Printf("key=%x, value=%x\n", k, v) + }*/ + + } + + chain.process_miner_transaction(bl_current.Miner_TX, bl_current.Height == 0, balance_tree, fees_collected, bl_current.Height) } else { rlog.Debugf("this block is a side block block height %d blid %s ", chain.Load_Block_Height(full_order[i]), full_order[i]) } // we are here, means everything is okay, lets commit the update balance tree - commit_version, err := graviton.Commit(balance_tree) + + data_trees = append(data_trees, balance_tree, sc_meta) + + //fmt.Printf("committing data trees %+v\n", data_trees) + + commit_version, err := graviton.Commit(data_trees...) if err != nil { panic(err) } + //fmt.Printf("committed trees version %d\n", commit_version) + chain.Store.Topo_store.Write(current_topo_block, full_order[i], commit_version, chain.Load_Block_Height(full_order[i])) rlog.Debugf("%d %s topo_index %d base topo %d", i, full_order[i], current_topo_block, base_topo_index) @@ -904,7 +993,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro continue } - case transaction.NORMAL: + case transaction.NORMAL, transaction.BURN_TX, transaction.SC_TX: if chain.Mempool.Mempool_TX_Exist(txid) { rlog.Tracef(1, "Deleting TX from mempool txid=%s", txid) chain.Mempool.Mempool_Delete_TX(txid) @@ -920,7 +1009,7 @@ func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err erro // ggive regpool a chance to register if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err == nil { - if balance_tree, err := ss.GetTree(BALANCE_TREE); err == nil { + if balance_tree, err := ss.GetTree(config.BALANCE_TREE); err == nil { chain.Regpool.HouseKeeping(uint64(block_height), func(tx *transaction.Transaction) bool { if tx.TransactionType != transaction.REGISTRATION { // tx not registration so delete @@ -948,6 +1037,11 @@ func (chain *Blockchain) Initialise_Chain_From_DB() { chain.Lock() defer chain.Unlock() + chain.Pruned = chain.LocatePruneTopo() + if chain.Pruned >= 1 { + logger.Debugf("Chain Pruned until %d\n", chain.Pruned) + } + // find the tips from the chain , first by reaching top height // then downgrading to top-10 height // then reworking the chain to get the tip @@ -963,7 +1057,7 @@ func (chain *Blockchain) Initialise_Chain_From_DB() { // get dag unsettled, it's only possible when we have the tips // chain.dag_unsettled = chain.Get_DAG_Unsettled() // directly off the disk - logger.Infof("Chain Tips %+v Height %d", chain.Tips, chain.Height) + logger.Debugf("Reloaded Chain Tips %+v Height %d", chain.Tips, chain.Height) } @@ -1093,19 +1187,38 @@ var block_processing_time = prometheus.NewHistogram(prometheus.HistogramOpts{ // add a transaction to MEMPOOL, // verifying everything means everything possible // this only change mempool, no DB changes -func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) (result bool) { +func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error { var err error + + if tx.IsPremine() { + return fmt.Errorf("premine tx not mineable") + } if tx.IsRegistration() { // registration tx will not go any forward // ggive regpool a chance to register if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err == nil { - if balance_tree, err := ss.GetTree(BALANCE_TREE); err == nil { + if balance_tree, err := ss.GetTree(config.BALANCE_TREE); err == nil { if _, err := balance_tree.Get(tx.MinerAddress[:]); err == nil { // address already registered - return false + return nil + } else { // add to regpool + if chain.Regpool.Regpool_Add_TX(tx, 0) { + return nil + } else { + return fmt.Errorf("registration for address is already pending") + } } + } else { + return err } + } else { + return err } - return chain.Regpool.Regpool_Add_TX(tx, 0) + } + + switch tx.TransactionType { + case transaction.BURN_TX, transaction.NORMAL, transaction.SC_TX: + default: + return fmt.Errorf("such transaction type cannot appear in mempool") } // track counter for the amount of mempool tx @@ -1116,25 +1229,25 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) (result boo // Coin base TX can not come through this path if tx.IsCoinbase() { logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX rejected coinbase tx cannot appear in mempool") - return false + return fmt.Errorf("TX rejected coinbase tx cannot appear in mempool") } chain_height := uint64(chain.Get_Height()) if chain_height > tx.Height { rlog.Tracef(2, "TX %s rejected since chain has already progressed", txhash) - return false + return fmt.Errorf("TX %s rejected since chain has already progressed", txhash) } // quick check without calculating everything whether tx is in pool, if yes we do nothing if chain.Mempool.Mempool_TX_Exist(txhash) { rlog.Tracef(2, "TX %s rejected Already in MEMPOOL", txhash) - return false + return fmt.Errorf("TX %s rejected Already in MEMPOOL", txhash) } // check whether tx is already mined if _, err = chain.Store.Block_tx_store.ReadTX(txhash); err == nil { rlog.Tracef(2, "TX %s rejected Already mined in some block", txhash) - return false + return fmt.Errorf("TX %s rejected Already mined in some block", txhash) } hf_version := chain.Get_Current_Version_at_Height(int64(chain_height)) @@ -1143,12 +1256,12 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) (result boo // currently, limits are as per consensus if uint64(len(tx.Serialize())) > config.STARGATE_HE_MAX_TX_SIZE { logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX rejected Size %d byte Max possible %d", len(tx.Serialize()), config.STARGATE_HE_MAX_TX_SIZE) - return false + return fmt.Errorf("TX rejected Size %d byte Max possible %d", len(tx.Serialize()), config.STARGATE_HE_MAX_TX_SIZE) } // check whether enough fees is provided in the transaction calculated_fee := chain.Calculate_TX_fee(hf_version, uint64(len(tx.Serialize()))) - provided_fee := tx.Statement.Fees // get fee from tx + provided_fee := tx.Fees() // get fee from tx _ = calculated_fee _ = provided_fee @@ -1164,20 +1277,20 @@ func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) (result boo } */ - if chain.Verify_Transaction_NonCoinbase(hf_version, tx) && chain.Verify_Transaction_NonCoinbase_DoubleSpend_Check(tx) { - if chain.Mempool.Mempool_Add_TX(tx, 0) { // new tx come with 0 marker - rlog.Tracef(2, "Successfully added tx %s to pool", txhash) - - mempool_tx_counter.Inc() - return true - } else { - rlog.Tracef(2, "TX %s rejected by pool", txhash) - return false - } + if err := chain.Verify_Transaction_NonCoinbase(hf_version, tx); err != nil { + rlog.Warnf("Incoming TX %s could not be verified, err %s", txhash, err) + return fmt.Errorf("Incoming TX %s could not be verified, err %s", txhash, err) } - rlog.Warnf("Incoming TX %s could not be verified", txhash) - return false + if chain.Mempool.Mempool_Add_TX(tx, 0) { // new tx come with 0 marker + rlog.Tracef(2, "Successfully added tx %s to pool", txhash) + + mempool_tx_counter.Inc() + return nil + } else { + rlog.Tracef(2, "TX %s rejected by pool", txhash) + return fmt.Errorf("TX %s rejected by pool", txhash) + } } @@ -1618,7 +1731,7 @@ func (chain *Blockchain) BuildReachabilityNonces(bl *block.Block) map[crypto.Has } // tx has been loaded, now lets get the nonce - nonce_reach_map[tx.Proof.Nonce()] = true // add element to map for next check + nonce_reach_map[tx.Payloads[0].Proof.Nonce()] = true // add element to map for next check } } return nonce_reach_map diff --git a/blockchain/blockheader.go b/blockchain/blockheader.go index 27b1533..e48dd70 100644 --- a/blockchain/blockheader.go +++ b/blockchain/blockheader.go @@ -17,13 +17,13 @@ package blockchain //import "fmt" -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/rpc" // this function is only used by the RPC and is not used by the core and should be moved to RPC interface /* fill up the above structure from the blockchain */ -func (chain *Blockchain) GetBlockHeader(hash crypto.Hash) (result structures.BlockHeader_Print, err error) { +func (chain *Blockchain) GetBlockHeader(hash crypto.Hash) (result rpc.BlockHeader_Print, err error) { bl, err := chain.Load_BL_FROM_ID(hash) if err != nil { return diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index 6f4bf9d..889f295 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -23,7 +23,7 @@ import "math/big" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/globals" var ( diff --git a/blockchain/genesis.go b/blockchain/genesis.go index 66d620e..9530f28 100644 --- a/blockchain/genesis.go +++ b/blockchain/genesis.go @@ -22,7 +22,7 @@ import "encoding/hex" import "github.com/romana/rlog" //import "github.com/deroproject/derosuite/address" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/globals" diff --git a/blockchain/mempool/mempool.go b/blockchain/mempool/mempool.go index c90471b..f5c3d63 100644 --- a/blockchain/mempool/mempool.go +++ b/blockchain/mempool/mempool.go @@ -31,7 +31,7 @@ import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" // this is only used for sorting and nothing else type TX_Sorting_struct struct { @@ -126,7 +126,7 @@ func (obj *mempool_object) UnmarshalJSON(data []byte) error { err = obj.Tx.DeserializeHeader(tx_bytes) if err == nil { - obj.FEEperBYTE = obj.Tx.Statement.Fees / obj.Size + obj.FEEperBYTE = obj.Tx.Fees() / obj.Size } return err } @@ -283,7 +283,7 @@ func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) var object mempool_object tx_hash := crypto.Hash(tx.GetHash()) - if pool.Mempool_Keyimage_Spent(tx.Proof.Nonce()) { + if pool.Mempool_Keyimage_Spent(tx.Payloads[0].Proof.Nonce()) { rlog.Debugf("Rejecting TX, since nonce already seen %x", tx_hash) return false } @@ -300,7 +300,7 @@ func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) // pool.key_images.Store(tx.Vin[i].(transaction.Txin_to_key).K_image,true) // add element to map for next check // } - pool.key_images.Store(tx.Proof.Nonce(), true) + pool.key_images.Store(tx.Payloads[0].Proof.Nonce(), true) // we are here means we can add it to pool object.Tx = tx @@ -308,7 +308,7 @@ func (pool *Mempool) Mempool_Add_TX(tx *transaction.Transaction, Height uint64) object.Added = uint64(time.Now().UTC().Unix()) object.Size = uint64(len(tx.Serialize())) - object.FEEperBYTE = tx.Statement.Fees / object.Size + object.FEEperBYTE = tx.Fees() / object.Size pool.txs.Store(tx_hash, &object) @@ -367,7 +367,7 @@ func (pool *Mempool) Mempool_Delete_TX(txid crypto.Hash) (tx *transaction.Transa // for i := 0; i < len(object.Tx.Vin); i++ { // pool.key_images.Delete(object.Tx.Vin[i].(transaction.Txin_to_key).K_image) // } - pool.key_images.Delete(tx.Proof.Nonce()) + pool.key_images.Delete(tx.Payloads[0].Proof.Nonce()) //pool.sort_list() // sort and update pool list pool.modified = true // pool has been modified diff --git a/blockchain/miner_block.go b/blockchain/miner_block.go index 17cd685..d362a24 100644 --- a/blockchain/miner_block.go +++ b/blockchain/miner_block.go @@ -31,9 +31,9 @@ import "github.com/romana/rlog" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/rpc" //import "github.com/deroproject/derohe/emission" import "github.com/deroproject/derohe/transaction" @@ -50,7 +50,7 @@ import "github.com/deroproject/graviton" // the top hash over which to do mining now ( it should already be in the chain) // this is work in progress // TODO we need to rework fees algorithm, to improve its quality and lower fees -func (chain *Blockchain) Create_new_miner_block(miner_address address.Address, tx *transaction.Transaction) (cbl *block.Complete_Block, bl block.Block) { +func (chain *Blockchain) Create_new_miner_block(miner_address rpc.Address, tx *transaction.Transaction) (cbl *block.Complete_Block, bl block.Block) { //chain.Lock() //defer chain.Unlock() @@ -88,7 +88,7 @@ func (chain *Blockchain) Create_new_miner_block(miner_address address.Address, t panic(err) } - balance_tree, err := ss.GetTree(BALANCE_TREE) + balance_tree, err := ss.GetTree(config.BALANCE_TREE) if err != nil { panic(err) } @@ -268,7 +268,7 @@ func (chain *Blockchain) Create_new_miner_block(miner_address address.Address, t var cache_block block.Block var cache_block_mutex sync.Mutex -func (chain *Blockchain) Create_new_block_template_mining(top_hash crypto.Hash, miner_address address.Address, reserve_space int) (bl block.Block, blockhashing_blob string, block_template_blob string, reserved_pos int) { +func (chain *Blockchain) Create_new_block_template_mining(top_hash crypto.Hash, miner_address rpc.Address, reserve_space int) (bl block.Block, blockhashing_blob string, block_template_blob string, reserved_pos int) { rlog.Debugf("Mining block will give reward to %s", miner_address) cache_block_mutex.Lock() @@ -310,12 +310,6 @@ var duplicate_height_check = map[uint64]bool{} // otherwise the miner is trying to attack the network func (chain *Blockchain) Accept_new_block(block_template []byte, blockhashing_blob []byte) (blid crypto.Hash, result bool, err error) { - - // check whether we are in lowcpu mode, if yes reject the block - if globals.Arguments["--lowcpuram"].(bool) { - globals.Logger.Warnf("Mining is deactivated since daemon is running in low cpu mode, please check program options.") - return blid, false, fmt.Errorf("Please decativate lowcpuram mode") - } if globals.Arguments["--sync-node"].(bool) { globals.Logger.Warnf("Mining is deactivated since daemon is running with --sync-mode, please check program options.") return blid, false, fmt.Errorf("Please deactivate --sync-node option before mining") @@ -415,7 +409,6 @@ func (chain *Blockchain) Accept_new_block(block_template []byte, blockhashing_bl } err, result = chain.Add_Complete_Block(cbl) - if result { duplicate_height_check[bl.Height] = true diff --git a/blockchain/prune_history.go b/blockchain/prune_history.go index 0e9a320..0d30562 100644 --- a/blockchain/prune_history.go +++ b/blockchain/prune_history.go @@ -28,6 +28,7 @@ import "path/filepath" import "github.com/romana/rlog" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/graviton" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/globals" @@ -158,9 +159,9 @@ func rewrite_graviton_store(store *storage, prune_topoheight int64, max_topoheig var old_ss, write_ss *graviton.Snapshot var old_balance_tree, write_balance_tree *graviton.Tree if old_ss, err = store.Balance_store.LoadSnapshot(toporecord.State_Version); err == nil { - if old_balance_tree, err = old_ss.GetTree(BALANCE_TREE); err == nil { + if old_balance_tree, err = old_ss.GetTree(config.BALANCE_TREE); err == nil { if write_ss, err = write_store.LoadSnapshot(0); err == nil { - if write_balance_tree, err = write_ss.GetTree(BALANCE_TREE); err == nil { + if write_balance_tree, err = write_ss.GetTree(config.BALANCE_TREE); err == nil { var latest_commit_version uint64 latest_commit_version, err = clone_entire_tree(old_balance_tree, write_balance_tree) @@ -195,16 +196,16 @@ func rewrite_graviton_store(store *storage, prune_topoheight int64, max_topoheig if old_toporecord, err = store.Topo_store.Read(old_topo); err == nil { if old_ss, err = store.Balance_store.LoadSnapshot(old_toporecord.State_Version); err == nil { - if old_balance_tree, err = old_ss.GetTree(BALANCE_TREE); err == nil { + if old_balance_tree, err = old_ss.GetTree(config.BALANCE_TREE); err == nil { // fetch new tree data if new_toporecord, err = store.Topo_store.Read(new_topo); err == nil { if new_ss, err = store.Balance_store.LoadSnapshot(new_toporecord.State_Version); err == nil { - if new_balance_tree, err = new_ss.GetTree(BALANCE_TREE); err == nil { + if new_balance_tree, err = new_ss.GetTree(config.BALANCE_TREE); err == nil { // fetch tree where to write it if write_ss, err = write_store.LoadSnapshot(0); err == nil { - if write_tree, err = write_ss.GetTree(BALANCE_TREE); err == nil { + if write_tree, err = write_ss.GetTree(config.BALANCE_TREE); err == nil { // new_balance_tree.Graph("/tmp/original.dot") // write_tree.Graph("/tmp/writable.dot") diff --git a/blockchain/regpool/regpool.go b/blockchain/regpool/regpool.go index b9a2208..b2117a3 100644 --- a/blockchain/regpool/regpool.go +++ b/blockchain/regpool/regpool.go @@ -30,7 +30,7 @@ import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" // this is only used for sorting and nothing else type TX_Sorting_struct struct { @@ -125,7 +125,7 @@ func (obj *regpool_object) UnmarshalJSON(data []byte) error { err = obj.Tx.DeserializeHeader(tx_bytes) if err == nil { - obj.FEEperBYTE = obj.Tx.Statement.Fees / obj.Size + obj.FEEperBYTE = 0 } return err } diff --git a/blockchain/sc.go b/blockchain/sc.go new file mode 100644 index 0000000..85410bd --- /dev/null +++ b/blockchain/sc.go @@ -0,0 +1,300 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package blockchain + +// this file implements necessary structure to SC handling + +import "fmt" +import "runtime/debug" +import "encoding/binary" +import "github.com/deroproject/derohe/cryptography/crypto" + +import "github.com/deroproject/derohe/dvm" + +//import "github.com/deroproject/graviton" +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/transaction" + +import "github.com/romana/rlog" + +// currently we 2 contract types +// 1 OPEN +// 2 PRIVATE +type SC_META_DATA struct { + Type byte // 0 Open, 1 Private + Balance uint64 + DataHash crypto.Hash // hash of SC data tree is here, so as the meta tree verifies all SC DATA +} + +// serialize the structure +func (meta SC_META_DATA) MarshalBinary() (buf []byte) { + buf = make([]byte, 41, 41) + buf[0] = meta.Type + binary.LittleEndian.PutUint64(buf[1:], meta.Balance) + copy(buf[1+8+len(meta.DataHash):], meta.DataHash[:]) + return +} + +func (meta *SC_META_DATA) UnmarshalBinary(buf []byte) (err error) { + if len(buf) != 1+8+32 { + return fmt.Errorf("input buffer should be of 41 bytes in length") + } + meta.Type = buf[0] + meta.Balance = binary.LittleEndian.Uint64(buf[1:]) + copy(meta.DataHash[:], buf[1+8+len(meta.DataHash):]) + return nil +} + +func SC_Meta_Key(scid crypto.Hash) []byte { + return scid[:] +} +func SC_Code_Key(scid crypto.Hash) []byte { + return dvm.Variable{Type: dvm.String, Value: "C"}.MarshalBinaryPanic() +} + +// this will process the SC transaction +// the tx should only be processed , if it has been processed + +func (chain *Blockchain) execute_sc_function(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash, bl_height, bl_topoheight uint64, bl_hash crypto.Hash, tx transaction.Transaction, entrypoint string, hard_fork_version_current int64) (gas uint64, err error) { + defer func() { + // safety so if anything wrong happens, verification fails + if r := recover(); r != nil { + logger.Warnf("Recovered while rewinding chain, Stack trace below block_hash ") + logger.Warnf("Stack trace \n%s", debug.Stack()) + } + }() + + //if !tx.Verify_SC_Signature() { // if tx is not SC TX, or Signature could not be verified skip it + // return + //} + + tx_hash := tx.GetHash() + tx_store := dvm.Initialize_TX_store() + + // used as value loader from disk + // this function is used to load any data required by the SC + + diskloader := func(key dvm.DataKey, found *uint64) (result dvm.Variable) { + var exists bool + if result, exists = chain.LoadSCValue(data_tree, key.SCID, key.MarshalBinaryPanic()); exists { + *found = uint64(1) + } + //fmt.Printf("Loading from disk %+v result %+v found status %+v \n", key, result, exists) + + return + } + + diskloader_raw := func(key []byte) (value []byte, found bool) { + var err error + value, err = data_tree.Get(key[:]) + if err != nil { + return value, false + } + + if len(value) == 0 { + return value, false + } + //fmt.Printf("Loading from disk %+v result %+v found status %+v \n", key, result, exists) + + return value, true + } + + balance, sc_parsed, found := chain.ReadSC(w_sc_tree, data_tree, scid) + if !found { + fmt.Printf("SC not found\n") + return + } + // if we found the SC in parsed form, check whether entrypoint is found + function, ok := sc_parsed.Functions[entrypoint] + if !ok { + rlog.Warnf("stored SC does not contain entrypoint '%s' scid %s \n", entrypoint, scid) + return + } + _ = function + + //fmt.Printf("entrypoint found '%s' scid %s\n", entrypoint, scid) + //if len(sc_tx.Params) == 0 { // initialize params if not initialized earlier + // sc_tx.Params = map[string]string{} + //} + //sc_tx.Params["value"] = fmt.Sprintf("%d", sc_tx.Value) // overide value + + tx_store.DiskLoader = diskloader // hook up loading from chain + tx_store.DiskLoaderRaw = diskloader_raw + tx_store.BalanceAtStart = balance + tx_store.SCID = scid + + //fmt.Printf("tx store %v\n", tx_store) + + // setup block hash, height, topoheight correctly + state := &dvm.Shared_State{ + Store: tx_store, + Chain_inputs: &dvm.Blockchain_Input{ + BL_HEIGHT: bl_height, + BL_TOPOHEIGHT: uint64(bl_topoheight), + SCID: scid, + BLID: bl_hash, + TXID: tx_hash, + Signer: string(tx.MinerAddress[:]), + }, + } + + for p := range tx.Payloads { + if tx.Payloads[p].SCID.IsZero() { + state.DERO_Received += tx.Payloads[p].BurnValue + } + if tx.Payloads[p].SCID == scid { + state.Token_Received += tx.Payloads[p].BurnValue + } + } + + // setup balance correctly + tx_store.ReceiveInternal(scid, state.DERO_Received) + + // we have an entrypoint, now we must setup parameters and dvm + // all parameters are in string form to bypass translation issues in middle layers + params := map[string]interface{}{} + + for _, p := range function.Params { + switch { + case p.Type == dvm.Uint64 && p.Name == "value": + params[p.Name] = fmt.Sprintf("%d", state.DERO_Received) // overide value + case p.Type == dvm.Uint64 && tx.SCDATA.Has(p.Name, rpc.DataUint64): + params[p.Name] = fmt.Sprintf("%d", tx.SCDATA.Value(p.Name, rpc.DataUint64).(uint64)) + case p.Type == dvm.String && tx.SCDATA.Has(p.Name, rpc.DataString): + params[p.Name] = tx.SCDATA.Value(p.Name, rpc.DataString).(string) + + default: + err = fmt.Errorf("entrypoint '%s' parameter type missing or not yet supported (%+v)", entrypoint, p) + return + } + } + + result, err := dvm.RunSmartContract(&sc_parsed, entrypoint, state, params) + + //fmt.Printf("result value %+v\n", result) + + if err != nil { + rlog.Warnf("entrypoint '%s' scid %s err execution '%s' \n", entrypoint, scid, err) + return + } + + if err == nil && result.Type == dvm.Uint64 && result.Value.(uint64) == 0 { // confirm the changes + for k, v := range tx_store.Keys { + chain.StoreSCValue(data_tree, scid, k.MarshalBinaryPanic(), v.MarshalBinaryPanic()) + } + for k, v := range tx_store.RawKeys { + chain.StoreSCValue(data_tree, scid, []byte(k), v) + } + + data_tree.leftover_balance = tx_store.Balance(scid) + data_tree.transfere = append(data_tree.transfere, tx_store.Transfers[scid].TransferE...) + + } else { // discard all changes, since we never write to store immediately, they are purged, however we need to return any value associated + err = fmt.Errorf("Discarded knowingly") + return + } + + //fmt.Printf("SC execution finished amount value %d\n", tx.Value) + return + +} + +// reads SC, balance +func (chain *Blockchain) ReadSC(w_sc_tree *Tree_Wrapper, data_tree *Tree_Wrapper, scid crypto.Hash) (balance uint64, sc dvm.SmartContract, found bool) { + meta_bytes, err := w_sc_tree.Get(SC_Meta_Key(scid)) + if err != nil { + return + } + + var meta SC_META_DATA // the meta contains the link to the SC bytes + if err := meta.UnmarshalBinary(meta_bytes); err != nil { + return + } + balance = meta.Balance + + sc_bytes, err := data_tree.Get(SC_Code_Key(scid)) + if err != nil { + return + } + + var v dvm.Variable + + if err = v.UnmarshalBinary(sc_bytes); err != nil { + return + } + + sc, pos, err := dvm.ParseSmartContract(v.Value.(string)) + if err != nil { + return + } + + _ = pos + + found = true + return +} + +func (chain *Blockchain) LoadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key []byte) (v dvm.Variable, found bool) { + //fmt.Printf("loading fromdb %s %s \n", scid, key) + + object_data, err := data_tree.Get(key[:]) + if err != nil { + return v, false + } + + if len(object_data) == 0 { + return v, false + } + + if err = v.UnmarshalBinary(object_data); err != nil { + return v, false + } + + return v, true +} + +// reads a value from SC, always read balance +func (chain *Blockchain) ReadSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key interface{}) (value interface{}) { + var keybytes []byte + + if key == nil { + return + } + switch k := key.(type) { + case uint64: + keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.Uint64, Value: k}}.MarshalBinaryPanic() + case string: + keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.String, Value: k}}.MarshalBinaryPanic() + case int64: + keybytes = dvm.DataKey{Key: dvm.Variable{Type: dvm.String, Value: k}}.MarshalBinaryPanic() + default: + return + } + + value_var, found := chain.LoadSCValue(data_tree, scid, keybytes) + //fmt.Printf("read value %+v", value_var) + if found && value_var.Type != dvm.Invalid { + value = value_var.Value + } + return +} + +// store the value in the chain +func (chain *Blockchain) StoreSCValue(data_tree *Tree_Wrapper, scid crypto.Hash, key, value []byte) { + data_tree.Put(key, value) + return +} diff --git a/blockchain/store.go b/blockchain/store.go index 8e99875..207a4d6 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -25,15 +25,13 @@ import "path/filepath" import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/graviton" import "github.com/golang/groupcache/lru" -// note we are keeping the tree name small for disk savings, since they will be stored n times (atleast or archival nodes) -const BALANCE_TREE = "B" - // though these can be done within a single DB, these are separated for completely clarity purposes type storage struct { Balance_store *graviton.Store // stores most critical data, only history can be purged, its merkle tree is stored in the block @@ -85,7 +83,7 @@ func (s *storage) IsBalancesIntialized() bool { var balancehash, random_hash [32]byte balance_ss, _ := s.Balance_store.LoadSnapshot(0) // load most recent snapshot - balancetree, _ := balance_ss.GetTree(BALANCE_TREE) + balancetree, _ := balance_ss.GetTree(config.BALANCE_TREE) // avoid hardcoding any hash if balancehash, err = balancetree.Hash(); err == nil { @@ -354,3 +352,42 @@ func (chain *Blockchain) Load_Block_Topological_order_at_index(index_pos int64) } } + +//load store hash from 2 tree +func (chain *Blockchain) Load_Merkle_Hash(index_pos int64) (hash crypto.Hash, err error) { + + toporecord, err := chain.Store.Topo_store.Read(index_pos) + if err != nil { + return hash, err + } + if toporecord.IsClean() { + err = fmt.Errorf("cannot query clean block") + return + } + + ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) + if err != nil { + return + } + + balance_tree, err := ss.GetTree(config.BALANCE_TREE) + if err != nil { + return + } + sc_meta_tree, err := ss.GetTree(config.SC_META) + if err != nil { + return + } + balance_merkle_hash, err := balance_tree.Hash() + if err != nil { + return + } + meta_merkle_hash, err := sc_meta_tree.Hash() + if err != nil { + return + } + for i := range balance_merkle_hash { + hash[i] = balance_merkle_hash[i] ^ meta_merkle_hash[i] + } + return hash, nil +} diff --git a/blockchain/storefs.go b/blockchain/storefs.go index 9aa43a5..7ac163f 100644 --- a/blockchain/storefs.go +++ b/blockchain/storefs.go @@ -35,7 +35,7 @@ type storefs struct { func (s *storefs) ReadBlock(h [32]byte) ([]byte, error) { var dummy [32]byte if h == dummy { - panic("empty block") + return nil, fmt.Errorf("empty block") } dir := filepath.Join(filepath.Join(s.basedir, "bltx_store"), fmt.Sprintf("%02x", h[0]), fmt.Sprintf("%02x", h[1]), fmt.Sprintf("%02x", h[2])) diff --git a/blockchain/storetopo.go b/blockchain/storetopo.go index a52ba67..48bcece 100644 --- a/blockchain/storetopo.go +++ b/blockchain/storetopo.go @@ -23,7 +23,7 @@ import "path/filepath" import "encoding/binary" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" type TopoRecord struct { BLOCK_ID [32]byte @@ -161,8 +161,7 @@ func (s *storetopofs) LocatePruneTopo() int64 { prune_topo-- - // fmt.Printf("pruned topo %d\n",prune_topo) - + pruned_till = prune_topo return prune_topo } @@ -208,7 +207,7 @@ func (s *storetopofs) binarySearchHeight(targetheight int64) (blids []crypto.Has } } - for i, count := midIndex, 0; i <= total_records && count < 100; i, count = i+1, count+1 { + for i, count := midIndex, 0; i < total_records && count < 100; i, count = i+1, count+1 { record, _ := s.Read(i) if record.Height == targetheight { blids = append(blids, record.BLOCK_ID) diff --git a/blockchain/transaction_execute.go b/blockchain/transaction_execute.go index e3c54b7..1e4ecc5 100644 --- a/blockchain/transaction_execute.go +++ b/blockchain/transaction_execute.go @@ -18,16 +18,42 @@ package blockchain // this file implements core execution of all changes to block chain homomorphically +import "fmt" import "math/big" import "golang.org/x/xerrors" -import "github.com/deroproject/derohe/crypto" +import "github.com/romana/rlog" + +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/dvm" import "github.com/deroproject/graviton" -// process the miner tx, giving fees, miner rewatd etc -func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, genesis bool, balance_tree *graviton.Tree, fees uint64) { +// convert bitcoin model to our, but skip initial 4 years of supply, so our total supply gets to 10.5 million +const RewardReductionInterval = 210000 * 600 / config.BLOCK_TIME // 210000 comes from bitcoin +const BaseReward = 50 * 100000 * config.BLOCK_TIME / 600 // convert bitcoin reward system to our block +// CalcBlockSubsidy returns the subsidy amount a block at the provided height +// should have. This is mainly used for determining how much the coinbase for +// newly generated blocks awards as well as validating the coinbase for blocks +// has the expected value. +// +// The subsidy is halved every SubsidyReductionInterval blocks. Mathematically +// this is: baseSubsidy / 2^(height/SubsidyReductionInterval) +// +// At the target block generation rate for the main network, this is +// approximately every 4 years. +// + +// basically out of of the bitcoin supply, we have wiped of initial interval ( this wipes of 10.5 million, so total remaining is around 10.5 million +func CalcBlockReward(height uint64) uint64 { + return BaseReward >> ((height + RewardReductionInterval) / RewardReductionInterval) +} + +// process the miner tx, giving fees, miner rewatd etc +func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, genesis bool, balance_tree *graviton.Tree, fees uint64, height uint64) { var acckey crypto.Point if err := acckey.DecodeCompressed(tx.MinerAddress[:]); err != nil { panic(err) @@ -41,21 +67,39 @@ func (chain *Blockchain) process_miner_transaction(tx transaction.Transaction, g } // general coin base transaction - balance_serialized, err := balance_tree.Get(tx.MinerAddress[:]) - if err != nil { - panic(err) + base_reward := CalcBlockReward(uint64(height)) + full_reward := base_reward + fees + + dev_reward := (full_reward * config.DEVSHARE) / 10000 // take % from reward + miner_reward := full_reward - dev_reward // it's value, do subtraction + + { // giver miner reward + balance_serialized, err := balance_tree.Get(tx.MinerAddress[:]) + if err != nil { + panic(err) + } + balance := new(crypto.ElGamal).Deserialize(balance_serialized) + balance = balance.Plus(new(big.Int).SetUint64(miner_reward)) // add miners reward to miners balance homomorphically + balance_tree.Put(tx.MinerAddress[:], balance.Serialize()) // reserialize and store + } + + { // give devs reward + balance_serialized, err := balance_tree.Get(chain.Dev_Address_Bytes[:]) + if err != nil { + panic(err) + } + balance := new(crypto.ElGamal).Deserialize(balance_serialized) + balance = balance.Plus(new(big.Int).SetUint64(dev_reward)) // add devs reward to devs balance homomorphically + balance_tree.Put(chain.Dev_Address_Bytes[:], balance.Serialize()) // reserialize and store } - balance := new(crypto.ElGamal).Deserialize(balance_serialized) - balance = balance.Plus(new(big.Int).SetUint64(fees + 50000)) // add fees colllected to users balance homomorphically - balance_tree.Put(tx.MinerAddress[:], balance.Serialize()) // reserialize and store return } // process the tx, giving fees, miner rewatd etc // this should be atomic, either all should be done or none at all -func (chain *Blockchain) process_transaction(tx transaction.Transaction, balance_tree *graviton.Tree) uint64 { +func (chain *Blockchain) process_transaction(changed map[crypto.Hash]*graviton.Tree, tx transaction.Transaction, balance_tree *graviton.Tree) uint64 { //fmt.Printf("Processing/Executing transaction %s %s\n", tx.GetHash(), tx.TransactionType.String()) switch tx.TransactionType { @@ -79,25 +123,233 @@ func (chain *Blockchain) process_transaction(tx transaction.Transaction, balance return 0 // registration doesn't give any fees . why & how ? - case transaction.NORMAL: - for i := range tx.Statement.Publickeylist_compressed { - if balance_serialized, err := balance_tree.Get(tx.Statement.Publickeylist_compressed[i][:]); err == nil { + case transaction.BURN_TX, transaction.NORMAL, transaction.SC_TX: // burned amount is not added anywhere and thus lost forever - balance := new(crypto.ElGamal).Deserialize(balance_serialized) - echanges := crypto.ConstructElGamal(tx.Statement.C[i], tx.Statement.D) - - balance = balance.Add(echanges) // homomorphic addition of changes - balance_tree.Put(tx.Statement.Publickeylist_compressed[i][:], balance.Serialize()) // reserialize and store + for t := range tx.Payloads { + var tree *graviton.Tree + if tx.Payloads[t].SCID.IsZero() { + tree = balance_tree } else { - panic(err) // if balance could not be obtained panic ( we can never reach here, otherwise how tx got verified) + tree = changed[tx.Payloads[t].SCID] } + for i := 0; i < int(tx.Payloads[t].Statement.RingSize); i++ { + key_pointer := tx.Payloads[t].Statement.Publickeylist_pointers[i*int(tx.Payloads[t].Statement.Bytes_per_publickey) : (i+1)*int(tx.Payloads[t].Statement.Bytes_per_publickey)] + if _, key_compressed, balance_serialized, err := tree.GetKeyValueFromHash(key_pointer); err == nil { + balance := new(crypto.ElGamal).Deserialize(balance_serialized) + echanges := crypto.ConstructElGamal(tx.Payloads[t].Statement.C[i], tx.Payloads[t].Statement.D) + + balance = balance.Add(echanges) // homomorphic addition of changes + tree.Put(key_compressed, balance.Serialize()) // reserialize and store + } else { + panic(err) // if balance could not be obtained panic ( we can never reach here, otherwise how tx got verified) + } + } } - return tx.Statement.Fees + + return tx.Fees() default: panic("unknown transaction, do not know how to process it") return 0 } - +} + +type Tree_Wrapper struct { + tree *graviton.Tree + entries map[string][]byte + leftover_balance uint64 + transfere []dvm.TransferExternal +} + +func (t *Tree_Wrapper) Get(key []byte) ([]byte, error) { + if value, ok := t.entries[string(key)]; ok { + return value, nil + } else { + return t.tree.Get(key) + } +} + +func (t *Tree_Wrapper) Put(key []byte, value []byte) error { + t.entries[string(key)] = append([]byte{}, value...) + return nil +} + +// does additional processing for SC +func (chain *Blockchain) process_transaction_sc(cache map[crypto.Hash]*graviton.Tree, ss *graviton.Snapshot, bl_height, bl_topoheight uint64, blid crypto.Hash, tx transaction.Transaction, balance_tree *graviton.Tree, sc_tree *graviton.Tree) (gas uint64, err error) { + + if len(tx.SCDATA) == 0 { + return tx.Fees(), nil + } + + success := false + w_balance_tree := &Tree_Wrapper{tree: balance_tree, entries: map[string][]byte{}} + w_sc_tree := &Tree_Wrapper{tree: sc_tree, entries: map[string][]byte{}} + + _ = w_balance_tree + + var sc_data_tree *graviton.Tree // SC data tree + var w_sc_data_tree *Tree_Wrapper + + txhash := tx.GetHash() + scid := txhash + + defer func() { + if success { // merge the trees + + } + }() + + if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) { // but only it is present + action_code := rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) + + switch action_code { + case rpc.SC_INSTALL: // request to install an SC + if !tx.SCDATA.Has(rpc.SCCODE, rpc.DataString) { // but only it is present + break + } + sc_code := tx.SCDATA.Value(rpc.SCCODE, rpc.DataString).(string) + if sc_code == "" { // no code provided nothing to do + err = fmt.Errorf("no code provided") + break + } + + // check whether sc can be parsed + //var sc_parsed dvm.SmartContract + pos := "" + var sc dvm.SmartContract + + sc, pos, err = dvm.ParseSmartContract(sc_code) + if err != nil { + rlog.Warnf("error Parsing sc txid %s err %s pos %s\n", txhash, err, pos) + break + } + + meta := SC_META_DATA{Balance: tx.Value} + + if _, ok := sc.Functions["InitializePrivate"]; ok { + meta.Type = 1 + } + if sc_data_tree, err = ss.GetTree(string(scid[:])); err != nil { + break + } else { + w_sc_data_tree = &Tree_Wrapper{tree: sc_data_tree, entries: map[string][]byte{}} + } + + // install SC, should we check for sanity now, why or why not + w_sc_data_tree.Put(SC_Code_Key(scid), dvm.Variable{Type: dvm.String, Value: sc_code}.MarshalBinaryPanic()) + + w_sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + + // at this point we must trigger the initialize call in the DVM + //fmt.Printf("We must call the SC initialize function\n") + + if meta.Type == 1 { // if its a a private SC + gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, blid, tx, "InitializePrivate", 1) + } else { + gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, blid, tx, "Initialize", 1) + } + + case rpc.SC_CALL: // trigger a CALL + if !tx.SCDATA.Has(rpc.SCID, rpc.DataHash) { // but only it is present + err = fmt.Errorf("no scid provided") + break + } + if !tx.SCDATA.Has("entrypoint", rpc.DataString) { // but only it is present + err = fmt.Errorf("no entrypoint provided") + break + } + + scid = tx.SCDATA.Value(rpc.SCID, rpc.DataHash).(crypto.Hash) + + if _, err = w_sc_tree.Get(SC_Meta_Key(scid)); err != nil { + err = fmt.Errorf("scid %s not installed", scid) + return + } + + if sc_data_tree, err = ss.GetTree(string(scid[:])); err != nil { + + return + } else { + w_sc_data_tree = &Tree_Wrapper{tree: sc_data_tree, entries: map[string][]byte{}} + } + + entrypoint := tx.SCDATA.Value("entrypoint", rpc.DataString).(string) + //fmt.Printf("We must call the SC %s function\n", entrypoint) + + gas, err = chain.execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, blid, tx, entrypoint, 1) + + default: // unknown what to do + err = fmt.Errorf("unknown action what to do", scid) + return + } + } + + if err == nil { // we must commit the changes + var data_tree *graviton.Tree + var ok bool + if data_tree, ok = cache[scid]; !ok { + data_tree = w_sc_data_tree.tree + cache[scid] = w_sc_data_tree.tree + } + + // commit entire data to tree + for k, v := range w_sc_data_tree.entries { + //fmt.Printf("persisting %x %x\n", k, v) + if err = data_tree.Put([]byte(k), v); err != nil { + return + } + } + + for k, v := range w_sc_tree.entries { // these entries are only partial + if err = sc_tree.Put([]byte(k), v); err != nil { + return + } + } + + // at this point, settle the balances, how ?? + var meta_bytes []byte + meta_bytes, err = w_sc_tree.Get(SC_Meta_Key(scid)) + if err != nil { + return + } + + var meta SC_META_DATA // the meta contains the link to the SC bytes + if err = meta.UnmarshalBinary(meta_bytes); err != nil { + return + } + meta.Balance = w_sc_data_tree.leftover_balance + + //fmt.Printf("SC %s balance %d\n", scid, w_sc_data_tree.leftover_balance) + sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary()) + + for _, transfer := range w_sc_data_tree.transfere { // give devs reward + var balance_serialized []byte + addr_bytes := []byte(transfer.Address) + balance_serialized, err = balance_tree.Get(addr_bytes) + if err != nil { + return + } + balance := new(crypto.ElGamal).Deserialize(balance_serialized) + balance = balance.Plus(new(big.Int).SetUint64(transfer.Amount)) // add devs reward to devs balance homomorphically + balance_tree.Put(addr_bytes, balance.Serialize()) // reserialize and store + + //fmt.Printf("%s paid back %d\n", scid, transfer.Amount) + + } + + /* + c := data_tree.Cursor() + for k, v, err := c.First(); err == nil; k, v, err = c.Next() { + fmt.Printf("key=%s (%x), value=%s\n", k, k, v) + } + fmt.Printf("cursor complete\n") + */ + + //h, err := data_tree.Hash() + //fmt.Printf("%s successfully executed sc_call data_tree hash %x %s\n", scid, h, err) + + } + + return tx.Fees(), nil } diff --git a/blockchain/transaction_verify.go b/blockchain/transaction_verify.go index 53dd1be..a3e0eb7 100644 --- a/blockchain/transaction_verify.go +++ b/blockchain/transaction_verify.go @@ -16,7 +16,7 @@ package blockchain -//import "fmt" +import "fmt" import "time" /*import "bytes" @@ -33,11 +33,11 @@ import "github.com/deroproject/graviton" //import "github.com/romana/rlog" import log "github.com/sirupsen/logrus" -//import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/transaction" -import "github.com/deroproject/derohe/crypto/bn256" +import "github.com/deroproject/derohe/cryptography/bn256" //import "github.com/deroproject/derosuite/emission" @@ -71,10 +71,10 @@ func clean_up_valid_cache() { /* Coinbase transactions need to verify registration * */ -func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, minertx *transaction.Transaction) (result bool) { +func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, minertx *transaction.Transaction) (err error) { if !minertx.IsCoinbase() { // transaction is not coinbase, return failed - return false + return fmt.Errorf("tx is not coinbase") } // make sure miner address is registered @@ -82,7 +82,7 @@ func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, _, topos := chain.Store.Topo_store.binarySearchHeight(int64(cbl.Bl.Height - 1)) // load all db versions one by one and check whether the root hash matches the one mentioned in the tx if len(topos) < 1 { - return false + return fmt.Errorf("could not find previous height blocks") } var balance_tree *graviton.Tree @@ -90,29 +90,26 @@ func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, toporecord, err := chain.Store.Topo_store.Read(topos[i]) if err != nil { - log.Infof("Skipping block at height %d due to error while obtaining toporecord %s\n", i, err) - continue + return fmt.Errorf("could not read block at height %d due to error while obtaining toporecord topos %+v processing %d err:%s\n", cbl.Bl.Height-1, topos, i, err) } ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) if err != nil { - panic(err) + return err } - if balance_tree, err = ss.GetTree(BALANCE_TREE); err != nil { - panic(err) + if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { + return err } if _, err := balance_tree.Get(minertx.MinerAddress[:]); err != nil { - //logger.Infof("balance not obtained err %s\n",err) + return fmt.Errorf("balance not obtained err %s\n", err) //return false - } else { - return true } } - return false + return nil // success comes last } // all non miner tx must be non-coinbase tx @@ -122,156 +119,223 @@ func (chain *Blockchain) Verify_Transaction_Coinbase(cbl *block.Complete_Block, // if the transaction has passed the check it can be added to mempool, relayed or added to blockchain // the transaction has already been deserialized thats it // It also expands the transactions, using the repective state trie -func (chain *Blockchain) Verify_Transaction_NonCoinbase(hf_version int64, tx *transaction.Transaction) (result bool) { +func (chain *Blockchain) Verify_Transaction_NonCoinbase(hf_version int64, tx *transaction.Transaction) (err error) { var tx_hash crypto.Hash defer func() { // safety so if anything wrong happens, verification fails if r := recover(); r != nil { logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("Recovered while Verifying transaction, failed verification, Stack trace below") logger.Warnf("Stack trace \n%s", debug.Stack()) - result = false + err = fmt.Errorf("Stack Trace %s", debug.Stack()) } }() if tx.Version != 1 { - return false + return fmt.Errorf("TX should be version 1") } tx_hash = tx.GetHash() if tx.TransactionType == transaction.REGISTRATION { if _, ok := transaction_valid_cache.Load(tx_hash); ok { - return true //logger.Infof("Found in cache %s ",tx_hash) + return nil //logger.Infof("Found in cache %s ",tx_hash) } else { //logger.Infof("TX not found in cache %s len %d ",tx_hash, len(tmp_buffer)) } if tx.IsRegistrationValid() { transaction_valid_cache.Store(tx_hash, time.Now()) // signature got verified, cache it - return true + return nil } - return false + return fmt.Errorf("Registration has invalid signature") } - // currently we allow 2 types of transaction - if !(tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.REGISTRATION) { - return false + // currently we allow following types of transaction + if !(tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.SC_TX || tx.TransactionType == transaction.BURN_TX) { + return fmt.Errorf("Unknown transaction type") } - // check sanity - if tx.Statement.RingSize != uint64(len(tx.Statement.Publickeylist_compressed)) || tx.Statement.RingSize != uint64(len(tx.Statement.Publickeylist)) { - return false + if tx.TransactionType == transaction.BURN_TX { + if tx.Value == 0 { + return fmt.Errorf("Burn Value cannot be zero") + } } // avoid some bugs lurking elsewhere if tx.Height != uint64(int64(tx.Height)) { - return false + return fmt.Errorf("invalid tx height") } - if tx.Statement.RingSize < 2 { // ring size minimum 4 - return false - } - - if tx.Statement.RingSize > 128 { // ring size current limited to 128 - return false - } - - if !crypto.IsPowerOf2(len(tx.Statement.Publickeylist_compressed)) { - return false - } - - // check duplicate ring members within the tx - { - key_map := map[string]bool{} - for i := range tx.Statement.Publickeylist_compressed { - key_map[string(tx.Statement.Publickeylist_compressed[i][:])] = true - } - if len(key_map) != len(tx.Statement.Publickeylist_compressed) { - return false + for t := range tx.Payloads { + // check sanity + if tx.Payloads[t].Statement.RingSize != uint64(len(tx.Payloads[t].Statement.Publickeylist_pointers)/int(tx.Payloads[t].Statement.Bytes_per_publickey)) { + return fmt.Errorf("corrupted key pointers ringsize") } + if tx.Payloads[t].Statement.RingSize < 2 { // ring size minimum 4 + return fmt.Errorf("RingSize cannot be less than 2") + } + + if tx.Payloads[t].Statement.RingSize > 128 { // ring size current limited to 128 + return fmt.Errorf("RingSize cannot be more than 128") + } + + if !crypto.IsPowerOf2(len(tx.Payloads[t].Statement.Publickeylist_pointers) / int(tx.Payloads[t].Statement.Bytes_per_publickey)) { + return fmt.Errorf("corrupted key pointers") + } + + // check duplicate ring members within the tx + { + key_map := map[string]bool{} + for i := 0; i < int(tx.Payloads[t].Statement.RingSize); i++ { + key_map[string(tx.Payloads[t].Statement.Publickeylist_pointers[i*int(tx.Payloads[t].Statement.Bytes_per_publickey):(i+1)*int(tx.Payloads[t].Statement.Bytes_per_publickey)])] = true + } + if len(key_map) != int(tx.Payloads[t].Statement.RingSize) { + return fmt.Errorf("Duplicated ring members") + } + + } + tx.Payloads[t].Statement.CLn = tx.Payloads[t].Statement.CLn[:0] + tx.Payloads[t].Statement.CRn = tx.Payloads[t].Statement.CRn[:0] + } + + match_topo := int64(1) + + // transaction needs to be expanded. this expansion needs balance state + _, topos := chain.Store.Topo_store.binarySearchHeight(int64(tx.Height)) + + // load all db versions one by one and check whether the root hash matches the one mentioned in the tx + if len(topos) < 1 { + return fmt.Errorf("TX could NOT be expanded") + } + + for i := range topos { + hash, err := chain.Load_Merkle_Hash(topos[i]) + if err != nil { + continue + } + + if hash == tx.Payloads[0].Statement.Roothash { + match_topo = topos[i] + break // we have found the balance tree with which it was built now lets verify + } + + } + + if match_topo < 0 { + return fmt.Errorf("mentioned balance tree not found, cannot verify TX") } var balance_tree *graviton.Tree + toporecord, err := chain.Store.Topo_store.Read(match_topo) + if err != nil { + return err + } - tx.Statement.CLn = tx.Statement.CLn[:0] - tx.Statement.CRn = tx.Statement.CRn[:0] + ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) + if err != nil { + return err + } - // this expansion needs balance state - if len(tx.Statement.CLn) == 0 { // transaction needs to be expanded - _, topos := chain.Store.Topo_store.binarySearchHeight(int64(tx.Height)) - - // load all db versions one by one and check whether the root hash matches the one mentioned in the tx - if len(topos) < 1 { - panic("TX could NOT be expanded") - } - - for i := range topos { - toporecord, err := chain.Store.Topo_store.Read(topos[i]) - if err != nil { - //log.Infof("Skipping block at height %d due to error while obtaining toporecord %s\n", i, err) - continue - } - - ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) - if err != nil { - panic(err) - } - - if balance_tree, err = ss.GetTree(BALANCE_TREE); err != nil { - panic(err) - } - - if hash, err := balance_tree.Hash(); err != nil { - panic(err) - } else { - //logger.Infof("dTX balance tree hash from tx %x treehash from blockchain %x", tx.Statement.Roothash, hash) - - if hash == tx.Statement.Roothash { - break // we have found the balance tree with which it was built now lets verify - } - } - balance_tree = nil - } + if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { + return err } if balance_tree == nil { - panic("mentioned balance tree not found, cannot verify TX") + return fmt.Errorf("mentioned balance tree not found, cannot verify TX") } if _, ok := transaction_valid_cache.Load(tx_hash); ok { - return true //logger.Infof("Found in cache %s ",tx_hash) + return nil //logger.Infof("Found in cache %s ",tx_hash) } else { //logger.Infof("TX not found in cache %s len %d ",tx_hash, len(tmp_buffer)) } //logger.Infof("dTX state tree has been found") - // now lets calculate CLn and CRn - for i := range tx.Statement.Publickeylist_compressed { - balance_serialized, err := balance_tree.Get(tx.Statement.Publickeylist_compressed[i][:]) - if err != nil { - //logger.Infof("balance not obtained err %s\n",err) - return false + trees := map[crypto.Hash]*graviton.Tree{} + + var zerohash crypto.Hash + trees[zerohash] = balance_tree // initialize main tree by default + + for t := range tx.Payloads { + tx.Payloads[t].Statement.Publickeylist_compressed = tx.Payloads[t].Statement.Publickeylist_compressed[:0] + tx.Payloads[t].Statement.Publickeylist = tx.Payloads[t].Statement.Publickeylist[:0] + + var tree *graviton.Tree + + if _, ok := trees[tx.Payloads[t].SCID]; ok { + tree = trees[tx.Payloads[t].SCID] + } else { + + // fmt.Printf("SCID loading %s tree\n", tx.Payloads[t].SCID) + tree, _ = ss.GetTree(string(tx.Payloads[t].SCID[:])) + trees[tx.Payloads[t].SCID] = tree } - var ll, rr bn256.G1 - ebalance := new(crypto.ElGamal).Deserialize(balance_serialized) + // now lets calculate CLn and CRn + for i := 0; i < int(tx.Payloads[t].Statement.RingSize); i++ { + key_pointer := tx.Payloads[t].Statement.Publickeylist_pointers[i*int(tx.Payloads[t].Statement.Bytes_per_publickey) : (i+1)*int(tx.Payloads[t].Statement.Bytes_per_publickey)] + _, key_compressed, balance_serialized, err := tree.GetKeyValueFromHash(key_pointer) + if err != nil { + return fmt.Errorf("balance not obtained err %s\n", err) + } - ll.Add(ebalance.Left, tx.Statement.C[i]) - tx.Statement.CLn = append(tx.Statement.CLn, &ll) - rr.Add(ebalance.Right, tx.Statement.D) - tx.Statement.CRn = append(tx.Statement.CRn, &rr) + // decode public key and expand + { + var p bn256.G1 + var pcopy [33]byte + copy(pcopy[:], key_compressed) + if err = p.DecodeCompressed(key_compressed[:]); err != nil { + return fmt.Errorf("key %d could not be decompressed", i) + } + tx.Payloads[t].Statement.Publickeylist_compressed = append(tx.Payloads[t].Statement.Publickeylist_compressed, pcopy) + tx.Payloads[t].Statement.Publickeylist = append(tx.Payloads[t].Statement.Publickeylist, &p) + } + + var ll, rr bn256.G1 + ebalance := new(crypto.ElGamal).Deserialize(balance_serialized) + + ll.Add(ebalance.Left, tx.Payloads[t].Statement.C[i]) + tx.Payloads[t].Statement.CLn = append(tx.Payloads[t].Statement.CLn, &ll) + rr.Add(ebalance.Right, tx.Payloads[t].Statement.D) + tx.Payloads[t].Statement.CRn = append(tx.Payloads[t].Statement.CRn, &rr) + + // prepare for another sub transaction + echanges := crypto.ConstructElGamal(tx.Payloads[t].Statement.C[i], tx.Payloads[t].Statement.D) + ebalance = new(crypto.ElGamal).Deserialize(balance_serialized).Add(echanges) // homomorphic addition of changes + tree.Put(key_compressed, ebalance.Serialize()) // reserialize and store temporarily, tree will be discarded after verification + + } } - if tx.Proof.Verify(&tx.Statement, tx.GetHash()) { - //logger.Infof("dTX verified with proof successfuly") + // at this point has been completely expanded, verify the tx statement + for t := range tx.Payloads { + if !tx.Payloads[t].Proof.Verify(&tx.Payloads[t].Statement, tx.GetHash(), tx.Payloads[t].BurnValue) { + + fmt.Printf("Statement %+v\n", tx.Payloads[t].Statement) + fmt.Printf("Proof %+v\n", tx.Payloads[t].Proof) + + return fmt.Errorf("transaction statement %d verification failed", t) + } + } + + // these transactions are done + if tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.BURN_TX { transaction_valid_cache.Store(tx_hash, time.Now()) // signature got verified, cache it - return true + return nil } - logger.Infof("transaction verification failed\n") - return false + // we reach here if tx proofs are valid + if tx.TransactionType != transaction.SC_TX { + return fmt.Errorf("non sc transaction should never reach here") + } + + if !tx.IsRegistrationValid() { + return fmt.Errorf("SC has invalid signature") + } + + return nil /* var tx_hash crypto.Hash @@ -501,23 +565,5 @@ func (chain *Blockchain) Verify_Transaction_NonCoinbase(hf_version int64, tx *tr //logger.WithFields(log.Fields{"txid": tx_hash}).Debugf("TX successfully verified") */ - return true -} - -// double spend check is separate from the core checks ( due to softforks ) -func (chain *Blockchain) Verify_Transaction_NonCoinbase_DoubleSpend_Check(tx *transaction.Transaction) (result bool) { - return true } - -// verify all non coinbase tx, single threaded for double spending on current active chain -func (chain *Blockchain) Verify_Block_DoubleSpending(cbl *block.Complete_Block) (result bool) { - /* - for i := 0; i < len(cbl.Txs); i++ { - if !chain.Verify_Transaction_NonCoinbase_DoubleSpend_Check(dbtx, cbl.Txs[i]) { - return false - } - } - */ - return true -} diff --git a/build_all.sh b/build_all.sh index b3b4e12..c281a6b 100644 --- a/build_all.sh +++ b/build_all.sh @@ -1,16 +1,14 @@ #!/usr/bin/env bash - - CURDIR=`/bin/pwd` BASEDIR=$(dirname $0) ABSPATH=$(readlink -f $0) ABSDIR=$(dirname $ABSPATH) -cd $ABSDIR/../../../../ -GOPATH=`pwd` -version=`cat src/github.com/deroproject/derohe/config/version.go | grep -i version |cut -d\" -f 2` +unset GOPATH + +version=`cat ./config/version.go | grep -i version |cut -d\" -f 2` cd $CURDIR @@ -18,6 +16,7 @@ bash $ABSDIR/build_package.sh "github.com/deroproject/derohe/cmd/derod" bash $ABSDIR/build_package.sh "github.com/deroproject/derohe/cmd/explorer" bash $ABSDIR/build_package.sh "github.com/deroproject/derohe/cmd/dero-wallet-cli" bash $ABSDIR/build_package.sh "github.com/deroproject/derohe/cmd/dero-miner" +bash $ABSDIR/build_package.sh "github.com/deroproject/derohe/cmd/rpc_examples/pong_server" for d in build/*; do cp Start.md "$d"; done diff --git a/cmd/dero-miner/difficulty.go b/cmd/dero-miner/difficulty.go index b494678..97f363d 100644 --- a/cmd/dero-miner/difficulty.go +++ b/cmd/dero-miner/difficulty.go @@ -18,7 +18,7 @@ package main // ripoff from blockchain folder import "math/big" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" var ( // bigZero is 0 represented as a big.Int. It is defined here to avoid diff --git a/cmd/dero-miner/miner.go b/cmd/dero-miner/miner.go index 607973c..ee91b1d 100644 --- a/cmd/dero-miner/miner.go +++ b/cmd/dero-miner/miner.go @@ -37,9 +37,9 @@ import "strconv" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/astrobwt" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" import log "github.com/sirupsen/logrus" import "github.com/ybbus/jsonrpc" @@ -48,10 +48,10 @@ import "github.com/romana/rlog" import "github.com/chzyer/readline" import "github.com/docopt/docopt-go" -var rpcClient *jsonrpc.RPCClient +var rpcClient jsonrpc.RPCClient var netClient *http.Client var mutex sync.RWMutex -var job structures.GetBlockTemplate_Result +var job rpc.GetBlockTemplate_Result var maxdelay int = 10000 var threads int var iterations int = 100 @@ -423,7 +423,7 @@ func increase_delay() { func getwork() { // create client - rpcClient = jsonrpc.NewRPCClient(daemon_rpc_address + "/json_rpc") + rpcClient = jsonrpc.NewClient(daemon_rpc_address + "/json_rpc") var netTransport = &http.Transport{ Dial: (&net.Dialer{ @@ -450,9 +450,9 @@ func getwork() { for { - response, err = rpcClient.CallNamed("getblocktemplate", map[string]interface{}{"wallet_address": fmt.Sprintf("%s", wallet_address), "reserve_size": 10}) + response, err = rpcClient.Call("getblocktemplate", map[string]interface{}{"wallet_address": fmt.Sprintf("%s", wallet_address), "reserve_size": 10}) if err == nil { - var block_template structures.GetBlockTemplate_Result + var block_template rpc.GetBlockTemplate_Result err = response.GetObject(&block_template) if err == nil { mutex.Lock() diff --git a/cmd/dero-wallet-cli/easymenu_post_open.go b/cmd/dero-wallet-cli/easymenu_post_open.go index 82a5db0..bf65bb2 100644 --- a/cmd/dero-wallet-cli/easymenu_post_open.go +++ b/cmd/dero-wallet-cli/easymenu_post_open.go @@ -17,17 +17,22 @@ package main import "io" -import "os" + +import "time" import "fmt" -import "io/ioutil" + +//import "io/ioutil" import "strings" -import "path/filepath" -import "encoding/hex" + +//import "path/filepath" +//import "encoding/hex" import "github.com/chzyer/readline" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/address" + +//import "github.com/deroproject/derohe/address" //import "github.com/deroproject/derohe/walletapi" import "github.com/deroproject/derohe/transaction" @@ -42,12 +47,12 @@ func display_easymenu_post_open_command(l *readline.Instance) { io.WriteString(w, "\t\033[1m3\033[0m\tDisplay Keys (hex)\n") - if !is_registered(wallet) { + if !wallet.IsRegistered() { io.WriteString(w, "\t\033[1m4\033[0m\tAccount registration to blockchain (registration has no fee requirement and is precondition to use the account)\n") io.WriteString(w, "\n") io.WriteString(w, "\n") } else { // hide some commands, if view only wallet - io.WriteString(w, "\n") + io.WriteString(w, "\t\033[1m4\033[0m\tDisplay wallet pool\n") io.WriteString(w, "\t\033[1m5\033[0m\tTransfer (send DERO) To Another Wallet\n") //io.WriteString(w, "\t\033[1m6\033[0m\tCreate Transaction in offline mode\n") io.WriteString(w, "\n") @@ -55,7 +60,7 @@ func display_easymenu_post_open_command(l *readline.Instance) { io.WriteString(w, "\t\033[1m7\033[0m\tChange wallet password\n") io.WriteString(w, "\t\033[1m8\033[0m\tClose Wallet\n") - if is_registered(wallet) { + if wallet.IsRegistered() { io.WriteString(w, "\t\033[1m12\033[0m\tTransfer all balance (send DERO) To Another Wallet\n") io.WriteString(w, "\t\033[1m13\033[0m\tShow transaction history\n") io.WriteString(w, "\t\033[1m14\033[0m\tRescan transaction history\n") @@ -90,7 +95,7 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce case "1": fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+"\n", wallet.GetAddress()) - if !is_registered(wallet) { + if !wallet.IsRegistered() { reg_tx := wallet.GetRegistrationTX() fmt.Fprintf(l.Stderr(), "Registration TX : "+color_green+"%x"+color_white+"\n", reg_tx.Serialize()) } @@ -118,28 +123,34 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce case "4": // Registration - if !ValidateCurrentPassword(l, wallet) { - globals.Logger.Warnf("Invalid password") - PressAnyKey(l, wallet) - break - } + if !wallet.IsRegistered() { - //if valid_registration_or_display_error(l, wallet) { - // globals.Logger.Warnf("This wallet address is already registered.") - // break - //} - fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.This is a pre-condition for using the online chain.It will take few seconds to register/", wallet.GetAddress()) - - reg_tx := wallet.GetRegistrationTX() + fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.This is a pre-condition for using the online chain.It will take few seconds to register.\n", wallet.GetAddress()) + reg_tx := wallet.GetRegistrationTX() // at this point we must send the registration transaction - fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.Pls wait till the account is registered.", wallet.GetAddress()) + fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.Pls wait till the account is registered.\n", wallet.GetAddress()) - wallet.SendTransaction(reg_tx) + fmt.Printf("sending registration tx err %s\n", wallet.SendTransaction(reg_tx)) + } else { + pool := wallet.GetPool() + fmt.Fprintf(l.Stderr(), "Wallet pool has %d pending/in-progress transactions.\n", len(pool)) + fmt.Fprintf(l.Stderr(), "%5s %9s %8s %64s %s %s\n", "No.", "Amount", "TH", "TXID", "Destination", "Status") + for i := range pool { + var txid, status string + if len(pool[i].Tries) > 0 { + try := pool[i].Tries[len(pool[i].Tries)-1] + txid = try.TXID.String() + status = try.Status + } else { + status = "Will Dispatch in next block" + } + fmt.Fprintf(l.Stderr(), "%5d %9s %8d %64s %s %s\n", i, "-"+globals.FormatMoney(pool[i].Amount()), pool[i].Trigger_Height, txid, "Not implemented", status) + } - + } case "6": offline_tx = true @@ -160,34 +171,129 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce break } - amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (max TODO .00004 hard coded): ")) + var amount_to_transfer uint64 - if amount_str == "" { - amount_str = ".00009" + var arguments = rpc.Arguments{ + // { rpc.RPC_DESTINATION_PORT, rpc.DataUint64,uint64(0x1234567812345678)}, + // { rpc.RPC_VALUE_TRANSFER, rpc.DataUint64,uint64(12345)}, + // { rpc.RPC_EXPIRY , rpc.DataTime, time.Now().Add(time.Hour).UTC()}, + // { rpc.RPC_COMMENT , rpc.DataString, "Purchase XYZ"}, } - amount_to_transfer, err := globals.ParseAmount(amount_str) - if err != nil { - globals.Logger.Warnf("Err :%s", err) - break // invalid amount provided, bail out + if a.IsIntegratedAddress() { // read everything from the address + + if a.Arguments.Validate_Arguments() != nil { + globals.Logger.Warnf("Integrated Address arguments could not be validated, err: %s", err) + break + } + + if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present + globals.Logger.Warnf("Integrated Address does not contain destination port.") + break + } + + arguments = append(arguments, rpc.Argument{rpc.RPC_DESTINATION_PORT, rpc.DataUint64, a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) + // arguments = append(arguments, rpc.Argument{"Comment", rpc.DataString, "holygrail of all data is now working if you can see this"}) + + if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present + + if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { + globals.Logger.Warnf("This address has expired on %s", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + break + } else { + globals.Logger.Infof("This address will expire on %s", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + } + } + + globals.Logger.Infof("Destination port is integreted in address ID:%016x", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + + if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present + globals.Logger.Infof("Integrated Message:%s", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) + } } - var payment_id []byte - _ = payment_id - // if user provided an integrated address donot ask him payment id - if a.IsIntegratedAddress() { - globals.Logger.Infof("Payment ID is integreted in address ID:%x", a.PaymentID) + // arguments have been already validated + for _, arg := range a.Arguments { + if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER) { + switch arg.DataType { + case rpc.DataString: + if v, err := ReadString(l, arg.Name, arg.Value.(string)); err == nil { + arguments = append(arguments, rpc.Argument{arg.Name, arg.DataType, v}) + } else { + globals.Logger.Warnf("%s could not be parsed (type %s),", arg.Name, arg.DataType) + return + } + case rpc.DataInt64: + if v, err := ReadInt64(l, arg.Name, arg.Value.(int64)); err == nil { + arguments = append(arguments, rpc.Argument{arg.Name, arg.DataType, v}) + } else { + globals.Logger.Warnf("%s could not be parsed (type %s),", arg.Name, arg.DataType) + return + } + case rpc.DataUint64: + if v, err := ReadUint64(l, arg.Name, arg.Value.(uint64)); err == nil { + arguments = append(arguments, rpc.Argument{arg.Name, arg.DataType, v}) + } else { + globals.Logger.Warnf("%s could not be parsed (type %s),", arg.Name, arg.DataType) + return + } + case rpc.DataFloat64: + if v, err := ReadFloat64(l, arg.Name, arg.Value.(float64)); err == nil { + arguments = append(arguments, rpc.Argument{arg.Name, arg.DataType, v}) + } else { + globals.Logger.Warnf("%s could not be parsed (type %s),", arg.Name, arg.DataType) + return + } + case rpc.DataTime: + globals.Logger.Warnf("time argument is currently not supported.") + break + + } + } + } + + if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present + globals.Logger.Infof("Transaction Value: %s", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) + amount_to_transfer = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { + + amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (max TODO): ")) + + if amount_str == "" { + amount_str = ".00009" + } + amount_to_transfer, err = globals.ParseAmount(amount_str) + if err != nil { + globals.Logger.Warnf("Err :%s", err) + break // invalid amount provided, bail out + } + } + + // if no arguments, use space by embedding a small comment + if len(arguments) == 0 { // allow user to enter Comment + if v, err := ReadString(l, "Comment", ""); err == nil { + arguments = append(arguments, rpc.Argument{"Comment", rpc.DataString, v}) + } else { + globals.Logger.Warnf("%s could not be parsed (type %s),", "Comment", rpc.DataString) + return + } + } + + if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { + globals.Logger.Warnf("Arguments packing err: %s,", err) + return } if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { - addr_list := []address.Address{*a} - amount_list := []uint64{amount_to_transfer} // transfer 50 dero, 2 dero - fees_per_kb := uint64(0) // fees must be calculated by walletapi - tx, err := wallet.Transfer(addr_list, amount_list, 0, hex.EncodeToString(payment_id), fees_per_kb, 0, false) + + //src_port := uint64(0xffffffffffffffff) + + _, err := wallet.PoolTransfer([]rpc.Transfer{rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}}, rpc.Arguments{}) // empty SCDATA + if err != nil { globals.Logger.Warnf("Error while building Transaction err %s\n", err) break } - build_relay_transaction(l, tx, err, offline_tx, amount_list) + //fmt.Printf("queued tx err %s\n") } case "12": @@ -199,31 +305,34 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce break } - // a , amount_to_transfer, err := collect_transfer_info(l,wallet) - fmt.Printf("dest address %s\n", "deroi1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxq5dph4ufnx5ndq4ltraf (14686f5e2666a4da) dero1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxqpfpaes") - a, err := ReadAddress(l) - if err != nil { - globals.Logger.Warnf("Err :%s", err) - break - } - // if user provided an integrated address donot ask him payment id - if a.IsIntegratedAddress() { - globals.Logger.Infof("Payment ID is integreted in address ID:%x", a.PaymentID) - } + globals.Logger.Warnf("Not supported err %s\n", err) - if ConfirmYesNoDefaultNo(l, "Confirm Transaction to send entire balance (y/N)") { - - addr_list := []address.Address{*a} - amount_list := []uint64{0} // transfer 50 dero, 2 dero - fees_per_kb := uint64(0) // fees must be calculated by walletapi - tx, err := wallet.Transfer(addr_list, amount_list, 0, "", fees_per_kb, 0, true) + /* + // a , amount_to_transfer, err := collect_transfer_info(l,wallet) + fmt.Printf("dest address %s\n", "deroi1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxq5dph4ufnx5ndq4ltraf (14686f5e2666a4da) dero1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxqpfpaes") + a, err := ReadAddress(l) if err != nil { - globals.Logger.Warnf("Error while building Transaction err %s\n", err) + globals.Logger.Warnf("Err :%s", err) break } + // if user provided an integrated address donot ask him payment id + if a.IsIntegratedAddress() { + globals.Logger.Infof("Payment ID is integreted in address ID:%x", a.PaymentID) + } - build_relay_transaction(l, tx, err, offline_tx, amount_list) - } + if ConfirmYesNoDefaultNo(l, "Confirm Transaction to send entire balance (y/N)") { + + addr_list := []address.Address{*a} + amount_list := []uint64{0} // transfer 50 dero, 2 dero + fees_per_kb := uint64(0) // fees must be calculated by walletapi + uid, err := wallet.PoolTransfer(addr_list, amount_list, fees_per_kb, 0, true) + _ = uid + if err != nil { + globals.Logger.Warnf("Error while building Transaction err %s\n", err) + break + } + } + */ //PressAnyKey(l, wallet) // wait for a key press @@ -275,62 +384,3 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce } return } - -// handles the output after building tx, takes feedback, confirms or relays tx -func build_relay_transaction(l *readline.Instance, tx *transaction.Transaction, err error, offline_tx bool, amount_list []uint64) { - - if err != nil { - globals.Logger.Warnf("Error while building Transaction err %s\n", err) - return - } - amount := uint64(0) - for i := range amount_list { - amount += amount_list[i] - } - globals.Logger.Infof("Transfering total amount %s DERO", globals.FormatMoney(amount)) - - globals.Logger.Infof("fees %s DERO", globals.FormatMoney(tx.Statement.Fees)) - globals.Logger.Infof("TX Size %0.1f KiB", float32(len(tx.Serialize()))/1024.0) - - //if input_sum != (amount + change + tx.Fee()) { - // panic(fmt.Sprintf("Inputs %d != outputs ( %d + %d + %d )", input_sum, amount, change, tx.RctSignature.Get_TX_Fee())) - //} - - //if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { - if true { - - if offline_tx { // if its an offline tx, dump it to a file - cur_dir, err := os.Getwd() - if err != nil { - globals.Logger.Warnf("Cannot obtain current directory to save tx") - globals.Logger.Infof("Transaction discarded") - return - } - filename := filepath.Join(cur_dir, tx.GetHash().String()+".tx") - err = ioutil.WriteFile(filename, []byte(hex.EncodeToString(tx.Serialize())), 0600) - if err == nil { - if err == nil { - globals.Logger.Infof("Transaction saved successfully. txid = %s", tx.GetHash()) - globals.Logger.Infof("Saved to %s", filename) - } else { - globals.Logger.Warnf("Error saving tx to %s, err %s", filename, err) - } - } - - } else { - - err = wallet.SendTransaction(tx) // relay tx to daemon/network - if err == nil { - globals.Logger.Infof("Transaction sent successfully. txid = %s", tx.GetHash()) - } else { - globals.Logger.Warnf("Transaction sending failed txid = %s, err %s", tx.GetHash(), err) - } - - } - - PressAnyKey(l, wallet) // wait for a key press - } else { - globals.Logger.Infof("Transaction discarded") - } - -} diff --git a/cmd/dero-wallet-cli/easymenu_pre_open.go b/cmd/dero-wallet-cli/easymenu_pre_open.go index 9e9807f..659aea8 100644 --- a/cmd/dero-wallet-cli/easymenu_pre_open.go +++ b/cmd/dero-wallet-cli/easymenu_pre_open.go @@ -24,7 +24,7 @@ import "encoding/hex" import "github.com/chzyer/readline" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/walletapi" @@ -61,6 +61,8 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { command = strings.ToLower(line_parts[0]) } + var wallett *walletapi.Wallet_Disk + //account_state := account_valid switch command { case "1": // open existing wallet @@ -68,7 +70,7 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { // ask user a password for i := 0; i < 3; i++ { - wallet, err = walletapi.Open_Encrypted_Wallet(filename, ReadPassword(l, filename)) + wallett, err = walletapi.Open_Encrypted_Wallet(filename, ReadPassword(l, filename)) if err != nil { globals.Logger.Warnf("Error occurred while opening wallet file %s. err %s", filename, err) wallet = nil @@ -77,7 +79,9 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { break } } - if wallet != nil { + if wallett != nil { + wallet = wallett + wallett = nil globals.Logger.Infof("Successfully opened wallet") common_processing(wallet) @@ -89,17 +93,20 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { password := ReadConfirmedPassword(l, "Enter password", "Confirm password") - wallet, err = walletapi.Create_Encrypted_Wallet_Random(filename, password) + wallett, err = walletapi.Create_Encrypted_Wallet_Random(filename, password) if err != nil { globals.Logger.Warnf("Error occured while creating new wallet, err: %s", err) wallet = nil break } - err = wallet.Set_Encrypted_Wallet_Password(password) + err = wallett.Set_Encrypted_Wallet_Password(password) if err != nil { globals.Logger.Warnf("Error changing password") } + wallet = wallett + wallett = nil + seed_language := choose_seed_language(l) wallet.SetSeedLanguage(seed_language) globals.Logger.Debugf("Seed Language %s", seed_language) @@ -114,11 +121,13 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { password := ReadConfirmedPassword(l, "Enter password", "Confirm password") electrum_words := read_line_with_prompt(l, "Enter seed (25 words) : ") - wallet, err = walletapi.Create_Encrypted_Wallet_From_Recovery_Words(filename, password, electrum_words) + wallett, err = walletapi.Create_Encrypted_Wallet_From_Recovery_Words(filename, password, electrum_words) if err != nil { globals.Logger.Warnf("Error while recovering wallet using seed err %s\n", err) break } + wallet = wallett + wallett = nil //globals.Logger.Debugf("Seed Language %s", account.SeedLanguage) globals.Logger.Infof("Successfully recovered wallet from seed") common_processing(wallet) @@ -136,12 +145,14 @@ func handle_easymenu_pre_open_command(l *readline.Instance, line string) { break } - wallet, err = walletapi.Create_Encrypted_Wallet(filename, password, new(crypto.BNRed).SetBytes(seed_raw)) + wallett, err = walletapi.Create_Encrypted_Wallet(filename, password, new(crypto.BNRed).SetBytes(seed_raw)) if err != nil { globals.Logger.Warnf("Error while recovering wallet using seed key err %s\n", err) break } globals.Logger.Infof("Successfully recovered wallet from hex seed") + wallet = wallett + wallett = nil seed_language := choose_seed_language(l) wallet.SetSeedLanguage(seed_language) globals.Logger.Debugf("Seed Language %s", seed_language) @@ -233,8 +244,10 @@ func common_processing(wallet *walletapi.Wallet_Disk) { globals.Logger.Warnf("Error starting rpc server err %s", err) } - } time.Sleep(time.Second) + // init_script_engine(wallet) // init script engine + // init_plugins_engine(wallet) // init script engine + } diff --git a/cmd/dero-wallet-cli/main.go b/cmd/dero-wallet-cli/main.go index b82e268..3a74b38 100644 --- a/cmd/dero-wallet-cli/main.go +++ b/cmd/dero-wallet-cli/main.go @@ -119,7 +119,7 @@ func main() { } // init the lookup table one, anyone importing walletapi should init this first, this will take around 1 sec on any recent system - walletapi.Initialize_LookupTable(1, 1<<20) + walletapi.Initialize_LookupTable(1, 1<<17) // We need to initialize readline first, so it changes stderr to ansi processor on windows l, err := readline.NewEx(&readline.Config{ @@ -407,11 +407,13 @@ func update_prompt(l *readline.Instance) { balance_string := "" //balance_unlocked, locked_balance := wallet.Get_Balance_Rescan()// wallet.Get_Balance() - balance_unlocked, locked_balance := wallet.Get_Balance() - balance_string = fmt.Sprintf(color_green+"%s "+color_white+"| "+color_yellow+"%s", globals.FormatMoney(balance_unlocked), globals.FormatMoney(locked_balance)) + balance_unlocked, _ := wallet.Get_Balance() + balance_string = fmt.Sprintf(color_green+"%s "+color_white, globals.FormatMoney(balance_unlocked)) if wallet.Error != nil { balance_string += fmt.Sprintf(color_red+" %s ", wallet.Error) + } else if wallet.PoolCount() > 0 { + balance_string += fmt.Sprintf(color_yellow+"(%d tx pending for -%s)", wallet.PoolCount(), globals.FormatMoney(wallet.PoolBalance())) } testnet_string := "" diff --git a/cmd/dero-wallet-cli/prompt.go b/cmd/dero-wallet-cli/prompt.go index 652c566..515a95c 100644 --- a/cmd/dero-wallet-cli/prompt.go +++ b/cmd/dero-wallet-cli/prompt.go @@ -30,12 +30,13 @@ import "encoding/hex" import "github.com/chzyer/readline" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/crypto" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/address" import "github.com/deroproject/derohe/walletapi" +import "github.com/deroproject/derohe/cryptography/crypto" + var account walletapi.Account // handle all commands while in prompt mode @@ -80,7 +81,32 @@ func handle_prompt_command(l *readline.Instance, line string) { fallthrough case "balance": // give user his balance balance_unlocked, locked_balance := wallet.Get_Balance_Rescan() - fmt.Fprintf(l.Stderr(), "Balance : "+color_green+"%s"+color_white+"\n\n", globals.FormatMoney(locked_balance+balance_unlocked)) + fmt.Fprintf(l.Stderr(), "DERO Balance : "+color_green+"%s"+color_white+"\n", globals.FormatMoney(locked_balance+balance_unlocked)) + + line_parts := line_parts[1:] // remove first part + + switch len(line_parts) { + case 0: + //globals.Logger.Warnf("not implemented") + break + + case 1: // scid balance + scid := crypto.HashHexToHash(line_parts[0]) + + //globals.Logger.Infof("scid1 %s line_parts %+v", scid, line_parts) + balance, err := wallet.GetDecryptedBalanceAtTopoHeight(scid, -1, wallet.GetAddress().String()) + + //globals.Logger.Infof("scid %s", scid) + if err != nil { + globals.Logger.Infof("error %s", err) + } else { + fmt.Fprintf(l.Stderr(), "SCID %s Balance : "+color_green+"%s"+color_white+"\n\n", line_parts[0], globals.FormatMoney(balance)) + } + + case 2: // scid balance at topoheight + globals.Logger.Warnf("not implemented") + break + } case "rescan_bc", "rescan_spent": // rescan from 0 if offline_mode { @@ -123,7 +149,7 @@ func handle_prompt_command(l *readline.Instance, line string) { globals.Logger.Warnf("Error parsing txhash") break } - key := wallet.GetTXKey(crypto.HexToHash(line_parts[1])) + key := wallet.GetTXKey(line_parts[1]) if key != "" { globals.Logger.Infof("TX Proof key \"%s\"", key) } else { @@ -134,7 +160,7 @@ func handle_prompt_command(l *readline.Instance, line string) { globals.Logger.Warnf("eg. get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7") } case "sweep_all", "transfer_all": // transfer everything - Transfer_Everything(l) + //Transfer_Everything(l) case "show_transfers": show_transfers(l, wallet, 100) @@ -153,11 +179,37 @@ func handle_prompt_command(l *readline.Instance, line string) { case "i8", "integrated_address": // user wants a random integrated address 8 bytes a := wallet.GetRandomIAddress8() fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String()) - fmt.Fprintf(l.Stderr(), "Embedded payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID) + fmt.Fprintf(l.Stderr(), "Embedded Arguments : "+color_green+"%s"+color_white+"\n", a.Arguments) case "version": globals.Logger.Infof("Version %s\n", config.Version.String()) + case "burn": + line_parts := line_parts[1:] // remove first part + if len(line_parts) < 2 { + globals.Logger.Warnf("burn needs destination address and amount as input parameter") + break + } + addr := line_parts[0] + send_amount := uint64(1) + burn_amount, err := globals.ParseAmount(line_parts[1]) + if err != nil { + globals.Logger.Warnf("Error Parsing burn amount \"%s\" err %s", line_parts[1], err) + return + } + if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { + + //uid, err := wallet.PoolTransferWithBurn(addr, send_amount, burn_amount, data, rpc.Arguments{}) + + uid, err := wallet.PoolTransfer([]rpc.Transfer{rpc.Transfer{Amount: send_amount, Burn: burn_amount, Destination: addr}}, rpc.Arguments{}) // empty SCDATA + _ = uid + if err != nil { + globals.Logger.Warnf("Error while building Transaction err %s\n", err) + break + } + //fmt.Printf("queued tx err %s\n", err) + //build_relay_transaction(l, uid, err, offline_tx, amount_list) + } case "transfer": // parse the address, amount pair /* @@ -221,16 +273,6 @@ func handle_prompt_command(l *readline.Instance, line string) { } - // if user provided an integrated address donot ask him payment id - // otherwise confirm whether user wants to send without payment id - if payment_id_integrated == false && len(payment_id) == 0 { - payment_id_bytes, err := ReadPaymentID(l) - payment_id = hex.EncodeToString(payment_id_bytes) - if err != nil { - globals.Logger.Warnf("Err :%s", err) - break - } - } offline := false tx, inputs, input_sum, change, err := wallet.Transfer(addr_list, amount_list, 0, payment_id, 0, 0) @@ -242,6 +284,10 @@ func handle_prompt_command(l *readline.Instance, line string) { if wallet != nil { wallet.Close_Encrypted_Wallet() // overwrite previous instance } + case "flush": // flush wallet pool + if wallet != nil { + fmt.Fprintf(l.Stderr(), "Flushed %d transactions from wallet pool\n", wallet.PoolClear()) + } case "": // blank enter key just loop default: @@ -317,85 +363,8 @@ func handle_set_command(l *readline.Instance, line string) { } } -func Transfer_Everything(l *readline.Instance) { - /* - if wallet.Is_View_Only() { - fmt.Fprintf(l.Stderr(), color_yellow+"View Only wallet cannot transfer."+color_white) - } - - if !ValidateCurrentPassword(l, wallet) { - globals.Logger.Warnf("Invalid password") - return - } - - // a , amount_to_transfer, err := collect_transfer_info(l,wallet) - addr, err := ReadAddress(l) - if err != nil { - globals.Logger.Warnf("Err :%s", err) - return - } - - var payment_id []byte - // if user provided an integrated address donot ask him payment id - if !addr.IsIntegratedAddress() { - payment_id, err = ReadPaymentID(l) - if err != nil { - globals.Logger.Warnf("Err :%s", err) - return - } - } else { - globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr.PaymentID) - } - - fees_per_kb := uint64(0) // fees must be calculated by walletapi - - tx, inputs, input_sum, err := wallet.Transfer_Everything(*addr, hex.EncodeToString(payment_id), 0, fees_per_kb, 5) - - _ = inputs - if err != nil { - globals.Logger.Warnf("Error while building Transaction err %s\n", err) - return - } - globals.Logger.Infof("%d Inputs Selected for %s DERO", len(inputs), globals.FormatMoney12(input_sum)) - globals.Logger.Infof("fees %s DERO", globals.FormatMoneyPrecision(tx.RctSignature.Get_TX_Fee(), 12)) - globals.Logger.Infof("TX Size %0.1f KiB (should be < 240 KiB)", float32(len(tx.Serialize()))/1024.0) - offline_tx := false - if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { - - if offline_tx { // if its an offline tx, dump it to a file - cur_dir, err := os.Getwd() - if err != nil { - globals.Logger.Warnf("Cannot obtain current directory to save tx") - return - } - filename := filepath.Join(cur_dir, tx.GetHash().String()+".tx") - err = ioutil.WriteFile(filename, []byte(hex.EncodeToString(tx.Serialize())), 0600) - if err == nil { - if err == nil { - globals.Logger.Infof("Transaction saved successfully. txid = %s", tx.GetHash()) - globals.Logger.Infof("Saved to %s", filename) - } else { - globals.Logger.Warnf("Error saving tx to %s , err %s", filename, err) - } - } - - } else { - - err = wallet.SendTransaction(tx) // relay tx to daemon/network - if err == nil { - globals.Logger.Infof("Transaction sent successfully. txid = %s", tx.GetHash()) - } else { - globals.Logger.Warnf("Transaction sending failed txid = %s, err %s", tx.GetHash(), err) - } - - } - } - */ - -} - // read an address with all goodies such as color encoding and other things in prompt -func ReadAddress(l *readline.Instance) (a *address.Address, err error) { +func ReadAddress(l *readline.Instance) (a *rpc.Address, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false @@ -435,18 +404,10 @@ func ReadAddress(l *readline.Instance) (a *address.Address, err error) { return } -/* -// read an payment with all goodies such as color encoding and other things in prompt -func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) { +func ReadFloat64(l *readline.Instance, cprompt string, default_value float64) (a float64, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false - // ask user whether he want to enter a payment ID - - if !ConfirmYesNoDefaultNo(l, "Provide Payment ID (y/N)") { // user doesnot want to provide payment it, skip - return - } - prompt_mutex.Lock() defer prompt_mutex.Unlock() @@ -455,19 +416,17 @@ func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) { color := color_green if len(line) >= 1 { - _, err := hex.DecodeString(string(line)) - if (len(line) == 16 || len(line) == 64) && err == nil { - error_message = "" - } else { + _, err := strconv.ParseFloat(string(line), 64) + if err != nil { error_message = " " //err.Error() } } if error_message != "" { color = color_red // Should we display the error message here?? - l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color)) + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %f): ", color, cprompt, default_value)) } else { - l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color)) + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %f): ", color, cprompt, default_value)) } @@ -479,21 +438,128 @@ func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) { if err != nil { return } - payment_id, err = hex.DecodeString(string(line)) + a, err = strconv.ParseFloat(string(line), 64) + l.SetPrompt(cprompt) + l.Refresh() + return +} + +func ReadUint64(l *readline.Instance, cprompt string, default_value uint64) (a uint64, err error) { + setPasswordCfg := l.GenPasswordConfig() + setPasswordCfg.EnableMask = false + + prompt_mutex.Lock() + defer prompt_mutex.Unlock() + + setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + error_message := "" + color := color_green + + if len(line) >= 1 { + _, err := strconv.ParseUint(string(line), 0, 64) + if err != nil { + error_message = " " //err.Error() + } + } + + if error_message != "" { + color = color_red // Should we display the error message here?? + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) + } else { + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) + + } + + l.Refresh() + return nil, 0, false + }) + + line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } - l.SetPrompt(prompt) + a, err = strconv.ParseUint(string(line), 0, 64) + l.SetPrompt(cprompt) + l.Refresh() + return +} + +func ReadInt64(l *readline.Instance, cprompt string, default_value int64) (a int64, err error) { + setPasswordCfg := l.GenPasswordConfig() + setPasswordCfg.EnableMask = false + + prompt_mutex.Lock() + defer prompt_mutex.Unlock() + + setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + error_message := "" + color := color_green + + if len(line) >= 1 { + _, err := strconv.ParseInt(string(line), 0, 64) + if err != nil { + error_message = " " //err.Error() + } + } + + if error_message != "" { + color = color_red // Should we display the error message here?? + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) + } else { + l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) + + } + + l.Refresh() + return nil, 0, false + }) + + line, err := l.ReadPasswordWithConfig(setPasswordCfg) + if err != nil { + return + } + a, err = strconv.ParseInt(string(line), 0, 64) + l.SetPrompt(cprompt) + l.Refresh() + return +} + +func ReadString(l *readline.Instance, cprompt string, default_value string) (a string, err error) { + setPasswordCfg := l.GenPasswordConfig() + setPasswordCfg.EnableMask = false + + prompt_mutex.Lock() + defer prompt_mutex.Unlock() + + setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + error_message := "" + color := color_green + + if len(line) < 1 { + error_message = " " //err.Error() + } + + if error_message != "" { + color = color_red // Should we display the error message here?? + l.SetPrompt(fmt.Sprintf("%sEnter %s (default '%s'): ", color, cprompt, default_value)) + } else { + l.SetPrompt(fmt.Sprintf("%sEnter %s (default '%s'): ", color, cprompt, default_value)) + + } + + l.Refresh() + return nil, 0, false + }) + + line, err := l.ReadPasswordWithConfig(setPasswordCfg) + if err != nil { + return + } + a = string(line) + l.SetPrompt(cprompt) l.Refresh() - - if len(payment_id) == 8 || len(payment_id) == 32 { - return - } - - err = fmt.Errorf("Invalid Payment ID") return } -*/ // confirms whether the user wants to confirm yes func ConfirmYesNoDefaultYes(l *readline.Instance, prompt_temporary string) bool { @@ -656,32 +722,6 @@ func PressAnyKey(l *readline.Instance, wallet *walletapi.Wallet_Disk) { return } -/* -// if we are in offline, scan default or user provided file -// this function will replay the blockchain data in offline mode -func trigger_offline_data_scan() { - filename := default_offline_datafile - - if globals.Arguments["--offline_datafile"] != nil { - filename = globals.Arguments["--offline_datafile"].(string) - } - - f, err := os.Open(filename) - if err != nil { - globals.Logger.Warnf("Cannot read offline data file=\"%s\" err: %s ", filename, err) - return - } - w := bufio.NewReader(f) - gzipreader, err := gzip.NewReader(w) - if err != nil { - globals.Logger.Warnf("Error while decompressing offline data file=\"%s\" err: %s ", filename, err) - return - } - defer gzipreader.Close() - io.Copy(pipe_writer, gzipreader) -} -*/ - // this completer is used to complete the commands at the prompt // BUG, this needs to be disabled in menu mode var completer = readline.NewPrefixCompleter( @@ -733,6 +773,7 @@ func usage(w io.Writer) { io.WriteString(w, "\t\033[1mtransfer\033[0m\tTransfer/Send DERO to another address\n") io.WriteString(w, "\t\t\tEg. transfer
\n") io.WriteString(w, "\t\033[1mtransfer_all\033[0m\tTransfer everything to another address\n") + io.WriteString(w, "\t\033[1mflush\033[0m\tFlush local wallet pool (for testing purposes)\n") io.WriteString(w, "\t\033[1mversion\033[0m\t\tShow version\n") io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit wallet\n") io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit wallet\n") @@ -754,7 +795,7 @@ func display_seed(l *readline.Instance, wallet *walletapi.Wallet_Disk) { func display_spend_key(l *readline.Instance, wallet *walletapi.Wallet_Disk) { keys := wallet.Get_Keys() - h := "0000000000000000000000000000000000000000000000"+keys.Secret.Text(16) + h := "0000000000000000000000000000000000000000000000" + keys.Secret.Text(16) fmt.Fprintf(os.Stderr, "secret key: "+color_red+"%s"+color_white+"\n", h[len(h)-64:]) fmt.Fprintf(os.Stderr, "public key: %s\n", keys.Public.StringHex()) @@ -769,15 +810,8 @@ func rescan_bc(wallet *walletapi.Wallet_Disk) { } -func is_registered(wallet *walletapi.Wallet_Disk) bool { - if wallet.Get_Registration_TopoHeight() == -1 { - return false - } - return true -} - func valid_registration_or_display_error(l *readline.Instance, wallet *walletapi.Wallet_Disk) bool { - if !is_registered(wallet) { + if !wallet.IsRegistered() { globals.Logger.Warnf("Your account is not registered.Please register.") } return true @@ -786,11 +820,9 @@ func valid_registration_or_display_error(l *readline.Instance, wallet *walletapi // show the transfers to the user originating from this account func show_transfers(l *readline.Instance, wallet *walletapi.Wallet_Disk, limit uint64) { - available := true in := true out := true - pool := true // this is not processed still TODO list - failed := false // this is not processed still TODO list + coinbase := true min_height := uint64(0) max_height := uint64(0) @@ -798,37 +830,18 @@ func show_transfers(l *readline.Instance, wallet *walletapi.Wallet_Disk, limit u line_parts := strings.Fields(line) if len(line_parts) >= 2 { switch strings.ToLower(line_parts[1]) { - case "available": - available = true - in = false + case "coinbase": out = false - pool = false - failed = false + in = false + case "in": - available = true + coinbase = false in = true out = false - pool = false - failed = false case "out": - available = false + coinbase = false in = false out = true - pool = false - failed = false - case "pool": - available = false - in = false - out = false - pool = true - failed = false - case "failed": - available = false - in = false - out = false - pool = false - failed = true - } } @@ -851,7 +864,7 @@ func show_transfers(l *readline.Instance, wallet *walletapi.Wallet_Disk, limit u } // request payments without payment id - transfers := wallet.Show_Transfers(available, in, out, pool, failed, false, min_height, max_height) // receives sorted list of transfers + transfers := wallet.Show_Transfers(coinbase, in, out, min_height, max_height, "", "", 0, 0) // receives sorted list of transfers if len(transfers) == 0 { globals.Logger.Warnf("No transfers available") @@ -872,16 +885,42 @@ func show_transfers(l *readline.Instance, wallet *walletapi.Wallet_Disk, limit u if transfers[i].Coinbase { io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d Coinbase (miner reward) received %s DERO"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, globals.FormatMoney(transfers[i].Amount))) - } else if len(transfers[i].PaymentID) == 0 { - io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount))) } else { - payment_id := fmt.Sprintf("%x", transfers[i].PaymentID) - io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO"+color_white+" PAYMENT ID:%s\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), payment_id)) + + args, err := transfers[i].ProcessPayload() + if err != nil { + io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof)) + + io.WriteString(l.Stderr(), fmt.Sprintf("Full Entry\n", transfers[i])) // dump entire entry for debugging purposes + + } else if len(args) == 0 { // no rpc + + io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s NO RPC CALL"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof)) + + } else { // yes, its rpc + io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s RPC CALL arguments %s "+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof, args)) + + } + } case 1: - payment_id := fmt.Sprintf("%x", transfers[i].PaymentID) - io.WriteString(l.Stderr(), fmt.Sprintf(color_magenta+"%s Height %d TopoHeight %d transaction %s spent %s DERO"+color_white+" PAYMENT ID: %s Proof:%s\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), payment_id, transfers[i].Proof)) + + args, err := transfers[i].ProcessPayload() + if err != nil { + io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s\n"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof)) + + io.WriteString(l.Stderr(), fmt.Sprintf("Err decoding entry %s\nFull Entry %+v\n", err, transfers[i])) // dump entire entry for debugging purposes + + } else if len(args) == 0 { // no rpc + + io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s NO RPC CALL"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof)) + + } else { // yes, its rpc + io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s RPC CALL arguments %s "+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof, args)) + + } + case 2: fallthrough default: diff --git a/cmd/derod/main.go b/cmd/derod/main.go index 387f169..fd86db2 100644 --- a/cmd/derod/main.go +++ b/cmd/derod/main.go @@ -45,12 +45,12 @@ import "github.com/deroproject/derohe/p2p" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/blockchain" import "github.com/deroproject/derohe/transaction" //import "github.com/deroproject/derosuite/checkpoints" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" //import "github.com/deroproject/derosuite/cryptonight" @@ -62,7 +62,7 @@ var command_line string = `derod DERO : A secure, private blockchain with smart-contracts Usage: - derod [--help] [--version] [--testnet] [--debug] [--sync-node] [--disable-checkpoints] [--socks-proxy=] [--data-dir=] [--p2p-bind=<0.0.0.0:18089>] [--add-exclusive-node=]... [--add-priority-node=]... [--min-peers=<11>] [--rpc-bind=<127.0.0.1:9999>] [--lowcpuram] [--mining-address=] [--mining-threads=] [--node-tag=] [--prune-history=<50>] + derod [--help] [--version] [--testnet] [--debug] [--sync-node] [--fullnode] [--disable-checkpoints] [--socks-proxy=] [--data-dir=] [--p2p-bind=<0.0.0.0:18089>] [--add-exclusive-node=]... [--add-priority-node=]... [--min-peers=<11>] [--rpc-bind=<127.0.0.1:9999>] [--node-tag=] [--prune-history=<50>] derod -h | --help derod --version @@ -71,6 +71,7 @@ Options: --version Show version. --testnet Run in testnet mode. --debug Debug mode enabled, print log messages + --fullnode Full node mode (this option has effect only while bootstrapping) --socks-proxy= Use a proxy to connect to network. --data-dir= Store blockchain data at this location --rpc-bind=<127.0.0.1:9999> RPC listens on this ip:port @@ -78,10 +79,6 @@ Options: --add-exclusive-node= Connect to specific peer only --add-priority-node= Maintain persistant connection to specified peer --sync-node Sync node automatically with the seeds nodes. This option is for rare use. - --min-peers=<11> Number of connections the daemon tries to maintain - --lowcpuram Disables some RAM consuming sections (deactivates mining/ultra compact protocol etc). - --mining-address= This address is rewarded when a block is mined sucessfully - --mining-threads= Number of CPU threads for mining --node-tag= Unique name of node, visible to everyone --prune-history=<50> prunes blockchain history until the specific topo_height @@ -188,18 +185,18 @@ func main() { p2p.P2P_Init(params) - rpc, _ := RPCServer_Start(params) + rpcserver, _ := RPCServer_Start(params) // setup function pointers // these pointers need to fixed chain.Mempool.P2P_TX_Relayer = func(tx *transaction.Transaction, peerid uint64) (count int) { - count += p2p.Broadcast_Tx(tx, peerid) + count += int(p2p.Broadcast_Tx(tx, peerid)) return } chain.Regpool.P2P_TX_Relayer = func(tx *transaction.Transaction, peerid uint64) (count int) { - count += p2p.Broadcast_Tx(tx, peerid) + count += int(p2p.Broadcast_Tx(tx, peerid)) return } @@ -207,47 +204,6 @@ func main() { p2p.Broadcast_Block(cbl, peerid) } - if globals.Arguments["--lowcpuram"].(bool) == false && globals.Arguments["--sync-node"].(bool) == false { // enable v1 of protocol only if requested - - // if an address has been provided, verify that it satisfies //mainnet/testnet criteria - if globals.Arguments["--mining-address"] != nil { - - addr, err := globals.ParseValidateAddress(globals.Arguments["--mining-address"].(string)) - if err != nil { - globals.Logger.Fatalf("Mining address is invalid: err %s", err) - } - params["mining-address"] = addr - - //log.Debugf("Setting up proxy using %s", Arguments["--socks-proxy"].(string)) - } - - if globals.Arguments["--mining-threads"] != nil { - thread_count := 0 - if s, err := strconv.Atoi(globals.Arguments["--mining-threads"].(string)); err == nil { - //fmt.Printf("%T, %v", s, s) - thread_count = s - - } else { - globals.Logger.Fatalf("Mining threads argument cannot be parsed: err %s", err) - } - - if thread_count > runtime.GOMAXPROCS(0) { - globals.Logger.Fatalf("Mining threads (%d) is more than available CPUs (%d). This is NOT optimal", thread_count, runtime.GOMAXPROCS(0)) - - } - params["mining-threads"] = thread_count - - if _, ok := params["mining-address"]; !ok { - globals.Logger.Fatalf("Mining threads require a valid wallet address") - } - - globals.Logger.Infof("System will mine to %s with %d threads. Good Luck!!", globals.Arguments["--mining-address"].(string), thread_count) - - go start_miner(chain, params["mining-address"].(*address.Address), nil, thread_count) - } - - } - //go time_check_routine() // check whether server time is in sync // This tiny goroutine continuously updates status as required @@ -397,7 +353,7 @@ func main() { break } // - case command == "import_chain": // this migrates existing chain from DERO to DERO atlantis + case command == "import_chain": // this migrates existing chain from DERO atlantis to DERO HE /* f, err := os.Open("/tmp/raw_export.txt") if err != nil { @@ -553,27 +509,14 @@ func main() { diff = chain.Load_Block_Difficulty(current_block_id) } - toporecord, err := chain.Store.Topo_store.Read(i) - if err != nil { - log.Infof("Skipping block at height %d due to error while obtaining toporecord %s\n", i, err) - continue - } - - ss, err := chain.Store.Balance_store.LoadSnapshot(uint64(toporecord.State_Version)) - if err != nil { - panic(err) - } - - balance_tree, err := ss.GetTree(blockchain.BALANCE_TREE) + balance_hash, err := chain.Load_Merkle_Hash(i) if err != nil { panic(err) } - balance_hash, _ := balance_tree.Hash() - log.Infof("topo height: %10d, height %d, timestamp: %10d, difficulty: %s cdiff: %s", i, chain.Load_Height_for_BL_ID(current_block_id), timestamp, diff.String(), cdiff.String()) - log.Infof("Block Id: %s , balance_tree hash %x \n", current_block_id, balance_hash) + log.Infof("Block Id: %s , balance_tree hash %s \n", current_block_id, balance_hash) log.Infof("") } @@ -625,24 +568,19 @@ func main() { case command == "start_mining": // it needs 2 parameters, one dero address, second number of threads var tx *transaction.Transaction - var addr *address.Address + var addr *rpc.Address if mining { fmt.Printf("Mining is already started\n") continue } - if globals.Arguments["--lowcpuram"].(bool) { - globals.Logger.Warnf("Mining is deactivated since daemon is running in low cpu mode, please check program options.") - continue - } - if globals.Arguments["--sync-node"].(bool) { globals.Logger.Warnf("Mining is deactivated since daemon is running with --sync-mode, please check program options.") continue } if len(line_parts) != 3 { - fmt.Printf("This function requires 2 parameters 1) dero address or registration TX 2) number of threads\n") + fmt.Printf("This function requires 2 parameters 1) dero address 2) number of threads\n") continue } @@ -661,40 +599,16 @@ func main() { } - hexdecoded, err := hex.DecodeString(line_parts[1]) - if err == nil { - tx = &transaction.Transaction{} - if err = tx.DeserializeHeader(hexdecoded); err == nil { + var err error - if tx.IsRegistration() { - - if tx.IsRegistrationValid() { - - addr = &address.Address{ - PublicKey: new(crypto.Point), - } - - err = addr.PublicKey.DecodeCompressed(tx.MinerAddress[0:33]) - - } else { - err = fmt.Errorf("Registration TX is invalid") - } - } else { - err = fmt.Errorf("TX is not registration") - } - } - } else { - err = nil - - addr, err = globals.ParseValidateAddress(line_parts[1]) - if err != nil { - globals.Logger.Warnf("Mining address is invalid: err %s", err) - continue - } - - } + addr, err = globals.ParseValidateAddress(line_parts[1]) if err != nil { - globals.Logger.Warnf("Registration TX/Mining address is invalid: err %s", err) + globals.Logger.Warnf("Mining address is invalid: err %s", err) + continue + } + + if err != nil { + globals.Logger.Warnf("Mining address is invalid: err %s", err) continue } @@ -757,25 +671,12 @@ func main() { fmt.Printf("Height: %d\n", chain.Load_Height_for_BL_ID(hash)) fmt.Printf("TopoHeight: %d\n", s) - toporecord, err := chain.Store.Topo_store.Read(s) - if err != nil { - log.Infof("Skipping block at topo height %d due to error while obtaining toporecord %s\n", s, err) - panic(err) - continue - } + bhash, err := chain.Load_Merkle_Hash(s) - ss, err := chain.Store.Balance_store.LoadSnapshot(uint64(toporecord.State_Version)) if err != nil { panic(err) } - balance_tree, err := ss.GetTree(blockchain.BALANCE_TREE) - if err != nil { - panic(err) - } - - bhash, _ := balance_tree.Hash() - fmt.Printf("BALANCE_TREE : %s\n", bhash) fmt.Printf("PoW: %s\n", bl.GetPoWHash()) @@ -1124,7 +1025,7 @@ exit: globals.Logger.Infof("Exit in Progress, Please wait") time.Sleep(100 * time.Millisecond) // give prompt update time to finish - rpc.RPCServer_Stop() + rpcserver.RPCServer_Stop() p2p.P2P_Shutdown() // shutdown p2p subsystem chain.Shutdown() // shutdown chain subsysem @@ -1155,7 +1056,7 @@ func writenode(chain *blockchain.Blockchain, w *bufio.Writer, blid crypto.Hash, panic(err) } - addr := address.NewAddressFromKeys(&acckey) + addr := rpc.NewAddressFromKeys(&acckey) addr.Mainnet = globals.IsMainnet() w.WriteString(fmt.Sprintf("L%s [ fillcolor=%s label = \"%s %d height %d score %d stored %d order %d\nminer %s\" ];\n", blid.String(), color, blid.String(), 0, chain.Load_Height_for_BL_ID(blid), 0, chain.Load_Block_Cumulative_Difficulty(blid), chain.Load_Block_Topological_order(blid), addr.String())) diff --git a/cmd/derod/miner.go b/cmd/derod/miner.go index 357da36..c608f9b 100644 --- a/cmd/derod/miner.go +++ b/cmd/derod/miner.go @@ -63,9 +63,9 @@ import "sync/atomic" import "encoding/binary" import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/blockchain" import "github.com/deroproject/derohe/transaction" @@ -76,38 +76,41 @@ var counter uint64 = 0 // used to track speeds of current miner var mining bool // whether system is mining // request block chain template, see if the tip changes, then continously mine -func start_miner(chain *blockchain.Blockchain, addr *address.Address, tx *transaction.Transaction, threads int) { +func start_miner(chain *blockchain.Blockchain, addr *rpc.Address, tx *transaction.Transaction, threads int) { - mining = true - counter = 0 //tip_counter := 0 - for { - //time.Sleep(50 * time.Millisecond) + for { // once started keep generating blocks after every 10 secs + mining = true + counter = 0 + for { + //time.Sleep(50 * time.Millisecond) - if !mining { - break + if !mining { + break + } + + if chain.MINING_BLOCK == true { + time.Sleep(10 * time.Millisecond) + continue + } + + cbl, bl := chain.Create_new_miner_block(*addr, tx) + + difficulty := chain.Get_Difficulty_At_Tips(bl.Tips) + + //globals.Logger.Infof("Difficulty of new block is %s", difficulty.String()) + // calculate difficulty once + // update job from chain + wg := sync.WaitGroup{} + wg.Add(threads) // add total number of tx as work + + for i := 0; i < threads; i++ { + go generate_valid_PoW(chain, 0, cbl, cbl.Bl, difficulty, &wg) // work should be complete in approx 100 ms, on a 12 cpu system, this would add cost of launching 12 g routine per second + } + wg.Wait() } - - if chain.MINING_BLOCK == true { - time.Sleep(10 * time.Millisecond) - continue - } - - cbl, bl := chain.Create_new_miner_block(*addr, tx) - - difficulty := chain.Get_Difficulty_At_Tips(bl.Tips) - - //globals.Logger.Infof("Difficulty of new block is %s", difficulty.String()) - // calculate difficulty once - // update job from chain - wg := sync.WaitGroup{} - wg.Add(threads) // add total number of tx as work - - for i := 0; i < threads; i++ { - go generate_valid_PoW(chain, 0, cbl, cbl.Bl, difficulty, &wg) // work should be complete in approx 100 ms, on a 12 cpu system, this would add cost of launching 12 g routine per second - } - wg.Wait() + time.Sleep(10 * time.Second) } // g @@ -150,7 +153,7 @@ func generate_valid_PoW(chain *blockchain.Blockchain, hf_version uint64, cbl *bl if _, ok := chain.Add_Complete_Block(cbl); ok { globals.Logger.Infof("Block %s successfully accepted diff %s", bl.GetHash(), current_difficulty.String()) - //chain.P2P_Block_Relayer(cbl, 0) // broadcast block to network ASAP + chain.P2P_Block_Relayer(cbl, 0) // broadcast block to network ASAP mining = false // this line enables single block mining in 1 go diff --git a/cmd/derod/rpc_dero_getblock.go b/cmd/derod/rpc_dero_getblock.go index 06864ac..3c8b6fa 100644 --- a/cmd/derod/rpc_dero_getblock.go +++ b/cmd/derod/rpc_dero_getblock.go @@ -21,16 +21,12 @@ import "context" import "encoding/hex" import "encoding/json" import "runtime/debug" - -//import "log" -//import "net/http" - -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/rpc" //import "github.com/deroproject/derosuite/blockchain" -func (DERO_RPC_APIS) GetBlock(ctx context.Context, p structures.GetBlock_Params) (result structures.GetBlock_Result, err error) { +func (DERO_RPC_APIS) GetBlock(ctx context.Context, p rpc.GetBlock_Params) (result rpc.GetBlock_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -70,7 +66,7 @@ func (DERO_RPC_APIS) GetBlock(ctx context.Context, p structures.GetBlock_Params) if err != nil { // if err return err return } - return structures.GetBlock_Result{ // return success + return rpc.GetBlock_Result{ // return success Block_Header: block_header, Blob: hex.EncodeToString(bl.Serialize()), Json: string(json_encoded_bytes), diff --git a/cmd/derod/rpc_dero_getblockcount.go b/cmd/derod/rpc_dero_getblockcount.go index b4868fa..9bdb056 100644 --- a/cmd/derod/rpc_dero_getblockcount.go +++ b/cmd/derod/rpc_dero_getblockcount.go @@ -17,10 +17,10 @@ package main import "context" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" -func (DERO_RPC_APIS) GetBlockCount(ctx context.Context) structures.GetBlockCount_Result { - return structures.GetBlockCount_Result{ +func (DERO_RPC_APIS) GetBlockCount(ctx context.Context) rpc.GetBlockCount_Result { + return rpc.GetBlockCount_Result{ Count: uint64(chain.Get_Height()), Status: "OK", } diff --git a/cmd/derod/rpc_dero_getblockheaderbyhash.go b/cmd/derod/rpc_dero_getblockheaderbyhash.go index e0bf3b9..f4f092d 100644 --- a/cmd/derod/rpc_dero_getblockheaderbyhash.go +++ b/cmd/derod/rpc_dero_getblockheaderbyhash.go @@ -19,14 +19,10 @@ package main import "fmt" import "context" import "runtime/debug" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/rpc" -//import "log" -//import "net/http" - -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/structures" - -func (DERO_RPC_APIS) GetBlockHeaderByHash(ctx context.Context, p structures.GetBlockHeaderByHash_Params) (result structures.GetBlockHeaderByHash_Result, err error) { +func (DERO_RPC_APIS) GetBlockHeaderByHash(ctx context.Context, p rpc.GetBlockHeaderByHash_Params) (result rpc.GetBlockHeaderByHash_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { err = fmt.Errorf("panic occured. stack trace %s", debug.Stack()) @@ -34,7 +30,7 @@ func (DERO_RPC_APIS) GetBlockHeaderByHash(ctx context.Context, p structures.GetB }() hash := crypto.HashHexToHash(p.Hash) if block_header, err := chain.GetBlockHeader(hash); err == nil { // if err return err - return structures.GetBlockHeaderByHash_Result{ // return success + return rpc.GetBlockHeaderByHash_Result{ // return success Block_Header: block_header, Status: "OK", }, nil diff --git a/cmd/derod/rpc_dero_getblockheaderbytopoheight.go b/cmd/derod/rpc_dero_getblockheaderbytopoheight.go index ed89ac3..84591c8 100644 --- a/cmd/derod/rpc_dero_getblockheaderbytopoheight.go +++ b/cmd/derod/rpc_dero_getblockheaderbytopoheight.go @@ -19,13 +19,9 @@ package main import "fmt" import "context" import "runtime/debug" +import "github.com/deroproject/derohe/rpc" -//import "log" -//import "net/http" -//import "github.com/deroproject/derosuite/crypto" -import "github.com/deroproject/derohe/structures" - -func (DERO_RPC_APIS) GetBlockHeaderByTopoHeight(ctx context.Context, p structures.GetBlockHeaderByTopoHeight_Params) (result structures.GetBlockHeaderByHeight_Result, err error) { +func (DERO_RPC_APIS) GetBlockHeaderByTopoHeight(ctx context.Context, p rpc.GetBlockHeaderByTopoHeight_Params) (result rpc.GetBlockHeaderByHeight_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -51,7 +47,7 @@ func (DERO_RPC_APIS) GetBlockHeaderByTopoHeight(ctx context.Context, p structure return } - return structures.GetBlockHeaderByHeight_Result{ // return success + return rpc.GetBlockHeaderByHeight_Result{ // return success Block_Header: block_header, Status: "OK", }, nil diff --git a/cmd/derod/rpc_dero_getblocktemplate.go b/cmd/derod/rpc_dero_getblocktemplate.go index a58a027..e5decaf 100644 --- a/cmd/derod/rpc_dero_getblocktemplate.go +++ b/cmd/derod/rpc_dero_getblocktemplate.go @@ -20,23 +20,14 @@ import "fmt" import "time" import "context" import "runtime/debug" - -//import "log" -//import "net/http" - import "golang.org/x/time/rate" - -//import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/address" -import "github.com/deroproject/derohe/structures" - -//import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/derohe/rpc" // rate limiter is deployed, in case RPC is exposed over internet // someone should not be just giving fake inputs and delay chain syncing var get_block_limiter = rate.NewLimiter(16.0, 8) // 16 req per sec, burst of 8 req is okay -func (DERO_RPC_APIS) GetBlockTemplate(ctx context.Context, p structures.GetBlockTemplate_Params) (result structures.GetBlockTemplate_Result, err error) { +func (DERO_RPC_APIS) GetBlockTemplate(ctx context.Context, p rpc.GetBlockTemplate_Params) (result rpc.GetBlockTemplate_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -57,7 +48,7 @@ func (DERO_RPC_APIS) GetBlockTemplate(ctx context.Context, p structures.GetBlock */ // validate address - miner_address, err := address.NewAddress(p.Wallet_Address) + miner_address, err := rpc.NewAddress(p.Wallet_Address) if err != nil { return result, fmt.Errorf("Address could not be parsed, err:%s", err) } @@ -72,7 +63,7 @@ func (DERO_RPC_APIS) GetBlockTemplate(ctx context.Context, p structures.GetBlock for i := range bl.Tips { prev_hash = prev_hash + bl.Tips[i].String() } - return structures.GetBlockTemplate_Result{ + return rpc.GetBlockTemplate_Result{ Blocktemplate_blob: block_template_hex, Blockhashing_blob: block_hashing_blob_hex, Reserved_Offset: uint64(reserved_pos), diff --git a/cmd/derod/rpc_dero_getencryptedbalance.go b/cmd/derod/rpc_dero_getencryptedbalance.go index 611b980..88dcac7 100644 --- a/cmd/derod/rpc_dero_getencryptedbalance.go +++ b/cmd/derod/rpc_dero_getencryptedbalance.go @@ -19,25 +19,23 @@ package main import "fmt" import "math" import "context" -import "encoding/hex" import "runtime/debug" -//import "log" -//import "net/http" import "golang.org/x/xerrors" - import "github.com/deroproject/graviton" - import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/blockchain" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/errormsg" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" -func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p structures.GetEncryptedBalance_Params) (result structures.GetEncryptedBalance_Result, err error) { +//import "github.com/deroproject/derohe/dvm" +//import "github.com/deroproject/derohe/cryptography/crypto" + +func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p rpc.GetEncryptedBalance_Params) (result rpc.GetEncryptedBalance_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { err = fmt.Errorf("panic occured. stack trace %s", debug.Stack()) + fmt.Printf("panic stack trace %s\n", debug.Stack()) } }() @@ -66,33 +64,23 @@ func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p structures.GetEn var balance_tree *graviton.Tree - if p.Merkle_Balance_TreeHash != "" { // user requested a specific tree hash version - - hash, err := hex.DecodeString(p.Merkle_Balance_TreeHash) - if err != nil { - panic(err) - } - - if len(hash) != 32 { - panic("corruted hash") - } - - balance_tree, err = ss.GetTreeWithRootHash(hash) - - } else { - - balance_tree, err = ss.GetTree(blockchain.BALANCE_TREE) - + treename := config.BALANCE_TREE + keyname := uaddress.Compressed() + if !p.SCID.IsZero() { + treename = string(p.SCID[:]) } - if err != nil { + + if balance_tree, err = ss.GetTree(treename); err != nil { panic(err) } - balance_serialized, err := balance_tree.Get(uaddress.Compressed()) + bits, _, balance_serialized, err := balance_tree.GetKeyValueFromKey(keyname) + + //fmt.Printf("balance_serialized %x err %s, scid %s keyname %x treename %x\n", balance_serialized,err,p.SCID, keyname, treename) if err != nil { if xerrors.Is(err, graviton.ErrNotFound) { // address needs registration - return structures.GetEncryptedBalance_Result{ // return success + return rpc.GetEncryptedBalance_Result{ // return success Registration: registration, Status: errormsg.ErrAccountUnregistered.Error(), }, errormsg.ErrAccountUnregistered @@ -101,7 +89,7 @@ func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p structures.GetEn panic(err) } } - merkle_hash, err := balance_tree.Hash() + merkle_hash, err := chain.Load_Merkle_Hash(topoheight) if err != nil { panic(err) } @@ -109,29 +97,15 @@ func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p structures.GetEn // calculate top height merkle tree hash //var dmerkle_hash crypto.Hash - toporecord, err = chain.Store.Topo_store.Read(chain.Load_TOPO_HEIGHT()) + dmerkle_hash, err := chain.Load_Merkle_Hash(chain.Load_TOPO_HEIGHT()) if err != nil { panic(err) } - ss, err = chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) - if err != nil { - panic(err) - } - - balance_tree, err = ss.GetTree(blockchain.BALANCE_TREE) - if err != nil { - panic(err) - } - - dmerkle_hash, err := balance_tree.Hash() - if err != nil { - panic(err) - } - - return structures.GetEncryptedBalance_Result{ // return success + return rpc.GetEncryptedBalance_Result{ // return success Data: fmt.Sprintf("%x", balance_serialized), Registration: registration, + Bits: bits, // no. of bbits required Height: toporecord.Height, Topoheight: topoheight, BlockHash: fmt.Sprintf("%x", toporecord.BLOCK_ID), @@ -144,7 +118,7 @@ func (DERO_RPC_APIS) GetEncryptedBalance(ctx context.Context, p structures.GetEn } // if address is unregistered, returns negative numbers -func LocatePointOfRegistration(uaddress *address.Address) int64 { +func LocatePointOfRegistration(uaddress *rpc.Address) int64 { addr := uaddress.Compressed() @@ -192,7 +166,7 @@ func IsRegisteredAtTopoHeight(addr []byte, topoheight int64) bool { } var balance_tree *graviton.Tree - balance_tree, err = ss.GetTree(blockchain.BALANCE_TREE) + balance_tree, err = ss.GetTree(config.BALANCE_TREE) if err != nil { panic(err) } diff --git a/cmd/derod/rpc_dero_getheight.go b/cmd/derod/rpc_dero_getheight.go index da26341..7b6bec9 100644 --- a/cmd/derod/rpc_dero_getheight.go +++ b/cmd/derod/rpc_dero_getheight.go @@ -17,14 +17,10 @@ package main import "context" +import "github.com/deroproject/derohe/rpc" -//import "log" -//import "net/http" - -import "github.com/deroproject/derohe/structures" - -func (DERO_RPC_APIS) GetHeight(ctx context.Context) structures.Daemon_GetHeight_Result { - return structures.Daemon_GetHeight_Result{ +func (DERO_RPC_APIS) GetHeight(ctx context.Context) rpc.Daemon_GetHeight_Result { + return rpc.Daemon_GetHeight_Result{ Height: uint64(chain.Get_Height()), StableHeight: chain.Get_Stable_Height(), TopoHeight: chain.Load_TOPO_HEIGHT(), diff --git a/cmd/derod/rpc_dero_getinfo.go b/cmd/derod/rpc_dero_getinfo.go index ce6a6c7..abac92f 100644 --- a/cmd/derod/rpc_dero_getinfo.go +++ b/cmd/derod/rpc_dero_getinfo.go @@ -17,20 +17,15 @@ package main import "fmt" - -//import "time" import "context" import "runtime/debug" - -//import "log" -//import "net/http" - import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/structures" -import "github.com/deroproject/derohe/blockchain" +import "github.com/deroproject/derohe/rpc" -func (DERO_RPC_APIS) GetInfo(ctx context.Context) (result structures.GetInfo_Result, err error) { +//import "github.com/deroproject/derohe/blockchain" + +func (DERO_RPC_APIS) GetInfo(ctx context.Context) (result rpc.GetInfo_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -44,28 +39,11 @@ func (DERO_RPC_APIS) GetInfo(ctx context.Context) (result structures.GetInfo_Res result.TopoHeight = chain.Load_TOPO_HEIGHT() { - - toporecord, err := chain.Store.Topo_store.Read(result.TopoHeight) - - //fmt.Printf("current block %d previous topo %d record %+v err %s\n", i+base_topo_index, i+base_topo_index-1, toporecord,err) + balance_merkle_hash, err := chain.Load_Merkle_Hash(result.TopoHeight) if err != nil { panic(err) } - ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) - if err != nil { - panic(err) - } - - balance_tree, err := ss.GetTree(blockchain.BALANCE_TREE) - if err != nil { - panic(err) - } - merkle_hash, err := balance_tree.Hash() - if err != nil { - panic(err) - } - result.Merkle_Balance_TreeHash = fmt.Sprintf("%X", merkle_hash[:]) - + result.Merkle_Balance_TreeHash = fmt.Sprintf("%X", balance_merkle_hash[:]) } blid, err := chain.Load_Block_Topological_order_at_index(result.TopoHeight) @@ -78,7 +56,7 @@ func (DERO_RPC_APIS) GetInfo(ctx context.Context) (result structures.GetInfo_Res result.Top_block_hash = blid.String() result.Target = chain.Get_Current_BlockTime() - if result.TopoHeight > 50 { + if result.TopoHeight-chain.LocatePruneTopo() > 100 { blid50, err := chain.Load_Block_Topological_order_at_index(result.TopoHeight - 50) if err == nil { now := chain.Load_Block_Timestamp(blid) diff --git a/cmd/derod/rpc_dero_getrandomaddress.go b/cmd/derod/rpc_dero_getrandomaddress.go index fb04563..a00769a 100644 --- a/cmd/derod/rpc_dero_getrandomaddress.go +++ b/cmd/derod/rpc_dero_getrandomaddress.go @@ -17,18 +17,16 @@ package main import "fmt" - import "context" import "runtime/debug" - import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/address" -import "github.com/deroproject/derohe/structures" -import "github.com/deroproject/derohe/blockchain" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/rpc" -func (DERO_RPC_APIS) GetRandomAddress(ctx context.Context) (result structures.GetRandomAddress_Result, err error) { +//import "github.com/deroproject/derohe/blockchain" + +func (DERO_RPC_APIS) GetRandomAddress(ctx context.Context, p rpc.GetRandomAddress_Params) (result rpc.GetRandomAddress_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { err = fmt.Errorf("panic occured. stack trace %s", debug.Stack()) @@ -54,7 +52,12 @@ func (DERO_RPC_APIS) GetRandomAddress(ctx context.Context) (result structures.Ge panic(err) } - balance_tree, err := ss.GetTree(blockchain.BALANCE_TREE) + treename := config.BALANCE_TREE + if p.SCID.IsZero() { + treename = string(p.SCID[:]) + } + + balance_tree, err := ss.GetTree(treename) if err != nil { panic(err) } @@ -70,10 +73,10 @@ func (DERO_RPC_APIS) GetRandomAddress(ctx context.Context) (result structures.Ge var acckey crypto.Point if err := acckey.DecodeCompressed(k[:]); err != nil { - panic(err) + continue } - addr := address.NewAddressFromKeys(&acckey) + addr := rpc.NewAddressFromKeys(&acckey) addr.Mainnet = true if globals.Config.Name != config.Mainnet.Name { // anything other than mainnet is testnet at this point in time addr.Mainnet = false diff --git a/cmd/derod/rpc_dero_getsc.go b/cmd/derod/rpc_dero_getsc.go new file mode 100644 index 0000000..36f4bfe --- /dev/null +++ b/cmd/derod/rpc_dero_getsc.go @@ -0,0 +1,165 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import "fmt" +import "context" + +//import "encoding/hex" +import "runtime/debug" + +//import "github.com/romana/rlog" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/dvm" + +//import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/derohe/blockchain" + +import "github.com/deroproject/graviton" + +func (DERO_RPC_APIS) GetSC(ctx context.Context, p rpc.GetSC_Params) (result rpc.GetSC_Result, err error) { + + defer func() { // safety so if anything wrong happens, we return error + if r := recover(); r != nil { + err = fmt.Errorf("panic occured. stack trace %s", debug.Stack()) + } + }() + + scid := crypto.HashHexToHash(p.SCID) + + topoheight := chain.Load_TOPO_HEIGHT() + + if p.TopoHeight >= 1 { + topoheight = p.TopoHeight + } + + toporecord, err := chain.Store.Topo_store.Read(topoheight) + // we must now fill in compressed ring members + if err == nil { + var ss *graviton.Snapshot + ss, err = chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version) + if err == nil { + var sc_meta_tree *graviton.Tree + if sc_meta_tree, err = ss.GetTree(config.SC_META); err == nil { + var meta_bytes []byte + if meta_bytes, err = sc_meta_tree.Get(blockchain.SC_Meta_Key(scid)); err == nil { + var meta blockchain.SC_META_DATA + if err = meta.UnmarshalBinary(meta_bytes); err == nil { + result.Balance = meta.Balance + } + } + } else { + return + } + + if sc_data_tree, err := ss.GetTree(string(scid[:])); err == nil { + if p.Code { // give SC code + var code_bytes []byte + var v dvm.Variable + if code_bytes, err = sc_data_tree.Get(blockchain.SC_Code_Key(scid)); err == nil { + if err = v.UnmarshalBinary(code_bytes); err != nil { + result.Code = "Unmarshal error" + } else { + result.Code = v.Value.(string) + } + } + } + + // give any uint64 keys data if any + for _, value := range p.KeysUint64 { + var v dvm.Variable + key, _ := dvm.Variable{Type: dvm.Uint64, Value: value}.MarshalBinary() + + var value_bytes []byte + if value_bytes, err = sc_data_tree.Get(key); err != nil { + result.ValuesUint64 = append(result.ValuesUint64, fmt.Sprintf("NOT AVAILABLE err: %s", err)) + continue + } + if err = v.UnmarshalBinary(value_bytes); err != nil { + result.ValuesUint64 = append(result.ValuesUint64, "Unmarshal error") + continue + } + switch v.Type { + case dvm.Uint64: + result.ValuesUint64 = append(result.ValuesUint64, fmt.Sprintf("%d", v.Value)) + case dvm.String: + result.ValuesUint64 = append(result.ValuesUint64, fmt.Sprintf("%s", v.Value)) + default: + result.ValuesUint64 = append(result.ValuesUint64, "UNKNOWN Data type") + } + } + for _, value := range p.KeysString { + var v dvm.Variable + key, _ := dvm.Variable{Type: dvm.String, Value: value}.MarshalBinary() + + var value_bytes []byte + if value_bytes, err = sc_data_tree.Get(key); err != nil { + fmt.Printf("Getting key %x\n", key) + result.ValuesString = append(result.ValuesString, fmt.Sprintf("NOT AVAILABLE err: %s", err)) + continue + } + if err = v.UnmarshalBinary(value_bytes); err != nil { + result.ValuesString = append(result.ValuesString, "Unmarshal error") + continue + } + switch v.Type { + case dvm.Uint64: + result.ValuesString = append(result.ValuesUint64, fmt.Sprintf("%d", v.Value)) + case dvm.String: + result.ValuesString = append(result.ValuesString, fmt.Sprintf("%s", v.Value)) + default: + result.ValuesString = append(result.ValuesString, "UNKNOWN Data type") + } + } + + for _, value := range p.KeysBytes { + var v dvm.Variable + key, _ := dvm.Variable{Type: dvm.String, Value: string(value)}.MarshalBinary() + + var value_bytes []byte + if value_bytes, err = sc_data_tree.Get(key); err != nil { + result.ValuesBytes = append(result.ValuesBytes, "NOT AVAILABLE") + continue + } + if err = v.UnmarshalBinary(value_bytes); err != nil { + result.ValuesBytes = append(result.ValuesBytes, "Unmarshal error") + continue + } + switch v.Type { + case dvm.Uint64: + result.ValuesBytes = append(result.ValuesBytes, fmt.Sprintf("%d", v.Value)) + case dvm.String: + result.ValuesBytes = append(result.ValuesBytes, fmt.Sprintf("%s", v.Value)) + default: + result.ValuesBytes = append(result.ValuesBytes, "UNKNOWN Data type") + } + } + + } + + } + + } + + result.Status = "OK" + err = nil + + //logger.Debugf("result %+v\n", result); + return +} diff --git a/cmd/derod/rpc_dero_gettransactions.go b/cmd/derod/rpc_dero_gettransactions.go index 52594ae..c0b024e 100644 --- a/cmd/derod/rpc_dero_gettransactions.go +++ b/cmd/derod/rpc_dero_gettransactions.go @@ -22,15 +22,14 @@ import "encoding/hex" import "runtime/debug" //import "github.com/romana/rlog" -//import "github.com/vmihailenco/msgpack" - -import "github.com/deroproject/derohe/crypto" - -//import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/transaction" +import "github.com/deroproject/derohe/blockchain" +import "github.com/deroproject/graviton" -func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransaction_Params) (result structures.GetTransaction_Result, err error) { +func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p rpc.GetTransaction_Params) (result rpc.GetTransaction_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -48,7 +47,7 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransac // logger.Debugf("checking tx in pool %+v", tx); if tx != nil { // found the tx in the mempool - var related structures.Tx_Related_Info + var related rpc.Tx_Related_Info related.Block_Height = -1 // not mined related.In_pool = true @@ -63,8 +62,11 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransac { // check if tx is from blockchain var tx transaction.Transaction var tx_bytes []byte - if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(hash); err != nil { - return + if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(hash); err != nil { // if tx not found return empty rpc + var related rpc.Tx_Related_Info + result.Txs_as_hex = append(result.Txs_as_hex, "") // a not found tx will return "" + result.Txs = append(result.Txs, related) + continue } else { //fmt.Printf("txhash %s loaded %d bytes\n", hash, len(tx_bytes)) @@ -75,7 +77,7 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransac } if err == nil { - var related structures.Tx_Related_Info + var related rpc.Tx_Related_Info // check whether tx is orphan @@ -100,8 +102,69 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransac if valid { related.ValidBlock = valid_blid.String() // topo height at which it was mined - related.Block_Height = int64(chain.Load_Block_Topological_order(valid_blid)) + topo_height := int64(chain.Load_Block_Topological_order(valid_blid)) + related.Block_Height = topo_height + if tx.TransactionType != transaction.REGISTRATION { + // we must now fill in compressed ring members + if toporecord, err := chain.Store.Topo_store.Read(topo_height); err == nil { + if ss, err := chain.Store.Balance_store.LoadSnapshot(toporecord.State_Version); err == nil { + + if tx.TransactionType == transaction.SC_TX { + scid := tx.GetHash() + if tx.SCDATA.Has(rpc.SCACTION, rpc.DataUint64) && rpc.SC_INSTALL == rpc.SC_ACTION(tx.SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64)) { + if sc_meta_tree, err := ss.GetTree(config.SC_META); err == nil { + var meta_bytes []byte + if meta_bytes, err = sc_meta_tree.Get(blockchain.SC_Meta_Key(scid)); err == nil { + var meta blockchain.SC_META_DATA // the meta contains the link to the SC bytes + if err = meta.UnmarshalBinary(meta_bytes); err == nil { + related.Balance = meta.Balance + } + } + } + if sc_data_tree, err := ss.GetTree(string(scid[:])); err == nil { + var code_bytes []byte + if code_bytes, err = sc_data_tree.Get(blockchain.SC_Code_Key(scid)); err == nil { + related.Code = string(code_bytes) + + } + + } + + } + } + + for t := range tx.Payloads { + var ring [][]byte + + var tree *graviton.Tree + + if tx.Payloads[t].SCID.IsZero() { + tree, err = ss.GetTree(config.BALANCE_TREE) + + } else { + tree, err = ss.GetTree(string(tx.Payloads[t].SCID[:])) + } + + if err != nil { + fmt.Printf("no such SC %s\n", tx.Payloads[t].SCID) + } + + for j := 0; j < int(tx.Payloads[t].Statement.RingSize); j++ { + key_pointer := tx.Payloads[t].Statement.Publickeylist_pointers[j*int(tx.Payloads[t].Statement.Bytes_per_publickey) : (j+1)*int(tx.Payloads[t].Statement.Bytes_per_publickey)] + _, key_compressed, _, err := tree.GetKeyValueFromHash(key_pointer) + if err == nil { + ring = append(ring, key_compressed) + } else { // we should some how report error + fmt.Printf("Error expanding member for txid %s t %d err %s key_compressed %x\n", hash, t, err, key_compressed) + } + } + related.Ring = append(related.Ring, ring) + } + + } + } + } } for i := range invalid_blid { related.InvalidBlock = append(related.InvalidBlock, invalid_blid[i].String()) @@ -125,6 +188,6 @@ func (DERO_RPC_APIS) GetTransaction(ctx context.Context, p structures.GetTransac result.Status = "OK" err = nil - //logger.Debugf("result %+v\n", result); + logger.Debugf("result %+v\n", result) return } diff --git a/cmd/derod/rpc_dero_gettxpool.go b/cmd/derod/rpc_dero_gettxpool.go index f2c267a..7a216b6 100644 --- a/cmd/derod/rpc_dero_gettxpool.go +++ b/cmd/derod/rpc_dero_gettxpool.go @@ -18,10 +18,9 @@ package main import "fmt" import "context" +import "github.com/deroproject/derohe/rpc" -import "github.com/deroproject/derohe/structures" - -func (DERO_RPC_APIS) GetTxPool(ctx context.Context) (result structures.GetTxPool_Result) { +func (DERO_RPC_APIS) GetTxPool(ctx context.Context) (result rpc.GetTxPool_Result) { result.Status = "OK" pool_list := chain.Mempool.Mempool_List_TX() diff --git a/cmd/derod/rpc_dero_sendrawtransaction.go b/cmd/derod/rpc_dero_sendrawtransaction.go index f5d3396..c92b0f8 100644 --- a/cmd/derod/rpc_dero_sendrawtransaction.go +++ b/cmd/derod/rpc_dero_sendrawtransaction.go @@ -20,14 +20,12 @@ import "fmt" import "context" import "encoding/hex" import "runtime/debug" - import "github.com/romana/rlog" - -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/transaction" //NOTE: finally we have shifted to json api -func (DERO_RPC_APIS) SendRawTransaction(ctx context.Context, p structures.SendRawTransaction_Params) (result structures.SendRawTransaction_Result, err error) { +func (DERO_RPC_APIS) SendRawTransaction(ctx context.Context, p rpc.SendRawTransaction_Params) (result rpc.SendRawTransaction_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -61,15 +59,12 @@ func (DERO_RPC_APIS) SendRawTransaction(ctx context.Context, p structures.SendRa rlog.Debugf("Incoming TXID %s from RPC Server", tx.GetHash()) // lets try to add it to pool - success := chain.Add_TX_To_Pool(&tx) - if success { + if err = chain.Add_TX_To_Pool(&tx); err == nil { result.Status = "OK" - err = nil rlog.Debugf("Incoming TXID %s from RPC Server successfully accepted by MEMPOOL", tx.GetHash()) } else { - - err = fmt.Errorf("Transaction %s rejected by daemon, check daemon msgs", tx.GetHash()) + err = fmt.Errorf("Transaction %s rejected by daemon err '%s'", tx.GetHash(), err) rlog.Warnf("Incoming TXID %s from RPC Server rejected by POOL", tx.GetHash()) } return diff --git a/cmd/derod/rpc_dero_submitblock.go b/cmd/derod/rpc_dero_submitblock.go index a220760..009090e 100644 --- a/cmd/derod/rpc_dero_submitblock.go +++ b/cmd/derod/rpc_dero_submitblock.go @@ -20,10 +20,9 @@ import "fmt" import "context" import "encoding/hex" import "runtime/debug" +import "github.com/deroproject/derohe/rpc" -import "github.com/deroproject/derohe/structures" - -func (DERO_RPC_APIS) SubmitBlock(ctx context.Context, block_data [2]string) (result structures.SubmitBlock_Result, err error) { +func (DERO_RPC_APIS) SubmitBlock(ctx context.Context, block_data [2]string) (result rpc.SubmitBlock_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { @@ -47,7 +46,7 @@ func (DERO_RPC_APIS) SubmitBlock(ctx context.Context, block_data [2]string) (res if sresult { logger.Infof("Submitted block %s accepted", blid) - return structures.SubmitBlock_Result{ + return rpc.SubmitBlock_Result{ BLID: blid.String(), Status: "OK", }, nil @@ -59,7 +58,7 @@ func (DERO_RPC_APIS) SubmitBlock(ctx context.Context, block_data [2]string) (res } logger.Infof("Submitting block rejected err %s", err) - return structures.SubmitBlock_Result{ + return rpc.SubmitBlock_Result{ Status: "REJECTED", }, nil diff --git a/cmd/derod/rpc_get_getlastblockheader.go b/cmd/derod/rpc_get_getlastblockheader.go index 8fe038b..d43f28a 100644 --- a/cmd/derod/rpc_get_getlastblockheader.go +++ b/cmd/derod/rpc_get_getlastblockheader.go @@ -21,9 +21,9 @@ package main import "fmt" import "context" import "runtime/debug" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" -func (DERO_RPC_APIS) GetLastBlockHeader(ctx context.Context) (result structures.GetLastBlockHeader_Result, err error) { +func (DERO_RPC_APIS) GetLastBlockHeader(ctx context.Context) (result rpc.GetLastBlockHeader_Result, err error) { defer func() { // safety so if anything wrong happens, we return error if r := recover(); r != nil { err = fmt.Errorf("panic occured. stack trace %s", debug.Stack()) @@ -35,7 +35,7 @@ func (DERO_RPC_APIS) GetLastBlockHeader(ctx context.Context) (result structures. return } - return structures.GetLastBlockHeader_Result{ + return rpc.GetLastBlockHeader_Result{ Block_Header: block_header, Status: "OK", }, nil diff --git a/cmd/derod/websocket_server.go b/cmd/derod/websocket_server.go index 84d0a5b..cbf0d0b 100644 --- a/cmd/derod/websocket_server.go +++ b/cmd/derod/websocket_server.go @@ -136,65 +136,6 @@ func (r *RPCServer) RPCServer_Stop() { // setup handlers func (r *RPCServer) Run() { - /* - mr := jsonrpc.NewMethodRepository() - - if err := mr.RegisterMethod("Main.Echo", EchoHandler{}, EchoParams{}, EchoResult{}); err != nil { - log.Fatalln(err) - } - - // install getblockcount handler - if err := mr.RegisterMethod("getblockcount", GetBlockCount_Handler{}, structures.GetBlockCount_Params{}, structures.GetBlockCount_Result{}); err != nil { - log.Fatalln(err) - } - - // install on_getblockhash - if err := mr.RegisterMethod("on_getblockhash", On_GetBlockHash_Handler{}, structures.On_GetBlockHash_Params{}, structures.On_GetBlockHash_Result{}); err != nil { - log.Fatalln(err) - } - - // install getblocktemplate handler - //if err := mr.RegisterMethod("getblocktemplate", GetBlockTemplate_Handler{}, structures.GetBlockTemplate_Params{}, structures.GetBlockTemplate_Result{}); err != nil { - // log.Fatalln(err) - //} - - // submitblock handler - if err := mr.RegisterMethod("submitblock", SubmitBlock_Handler{}, structures.SubmitBlock_Params{}, structures.SubmitBlock_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("getlastblockheader", GetLastBlockHeader_Handler{}, structures.GetLastBlockHeader_Params{}, structures.GetLastBlockHeader_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("getblockheaderbyhash", GetBlockHeaderByHash_Handler{}, structures.GetBlockHeaderByHash_Params{}, structures.GetBlockHeaderByHash_Result{}); err != nil { - log.Fatalln(err) - } - - //if err := mr.RegisterMethod("getblockheaderbyheight", GetBlockHeaderByHeight_Handler{}, structures.GetBlockHeaderByHeight_Params{}, structures.GetBlockHeaderByHeight_Result{}); err != nil { - // log.Fatalln(err) - //} - - if err := mr.RegisterMethod("getblockheaderbytopoheight", GetBlockHeaderByTopoHeight_Handler{}, structures.GetBlockHeaderByTopoHeight_Params{}, structures.GetBlockHeaderByHeight_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("getblock", GetBlock_Handler{}, structures.GetBlock_Params{}, structures.GetBlock_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("get_info", GetInfo_Handler{}, structures.GetInfo_Params{}, structures.GetInfo_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("getencryptedbalance", GetEncryptedBalance_Handler{}, structures.GetEncryptedBalance_Params{}, structures.GetEncryptedBalance_Result{}); err != nil { - log.Fatalln(err) - } - - if err := mr.RegisterMethod("gettxpool", GetTxPool_Handler{}, structures.GetTxPool_Params{}, structures.GetTxPool_Result{}); err != nil { - log.Fatalln(err) - } - */ // create a new mux r.mux = http.NewServeMux() @@ -344,7 +285,8 @@ var historical_apis = handler.Map{"getinfo": handler.New(dero_apis.GetInfo), "getblockcount": handler.New(dero_apis.GetBlockCount), "getlastblockheader": handler.New(dero_apis.GetLastBlockHeader), "getblocktemplate": handler.New(dero_apis.GetBlockTemplate), - "getencryptedbalance": handler.New(dero_apis.GetEncryptedBalance)} + "getencryptedbalance": handler.New(dero_apis.GetEncryptedBalance), + "getsc": handler.New(dero_apis.GetSC)} func translate_http_to_jsonrpc_and_vice_versa(w http.ResponseWriter, r *http.Request) { bridge.ServeHTTP(w, r) diff --git a/cmd/explorer/explorer.go b/cmd/explorer/explorer.go index cdabc83..c27d503 100644 --- a/cmd/explorer/explorer.go +++ b/cmd/explorer/explorer.go @@ -20,9 +20,6 @@ package main // this needs only RPC access // NOTE: Only use data exported from within the RPC interface, do direct use of exported variables fom packages // NOTE: we can use structs defined within the RPCserver package -// This is being developed to track down and confirm some bugs -// NOTE: This is NO longer entirely compliant with the xyz RPC interface ( the pool part is not compliant), currently and can be used as it for their chain, -// atleast for the last 1 year // TODO: error handling is non-existant ( as this was built up in hrs ). Add proper error handling // @@ -31,7 +28,9 @@ import "time" import "fmt" //import "net" -//import "bytes" +import "bytes" +import "unicode" +import "unsafe" // need to avoid this, but only used by byteviewer import "strings" import "strconv" import "context" @@ -48,11 +47,11 @@ import log "github.com/sirupsen/logrus" //import "github.com/ybbus/jsonrpc" import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/cryptography/bn256" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/transaction" -import "github.com/deroproject/derohe/structures" +import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/proof" import "github.com/deroproject/derohe/glue/rwc" @@ -164,7 +163,7 @@ func Connect() (err error) { fmt.Printf("Ping Received %s\n", result) } - var info structures.GetInfo_Result + var info rpc.GetInfo_Result // collect all the data afresh, execute rpc to service if err = rpc_client.Call("DERO.GetInfo", nil, &info); err != nil { @@ -248,6 +247,7 @@ type txinfo struct { Version int // version of tx Size string // size of tx in KB Sizeuint64 uint64 // size of tx in bytes + Burn_Value string // value of burned amount Fee string // fee in TX Feeuint64 uint64 // fee in atomic units In int // inputs counts @@ -263,18 +263,37 @@ type txinfo struct { InvalidBlock []string // the tx is invalid in which block Skipped bool // this is only valid, when a block is being listed Ring_size int - //Ring [][]globals.TX_Output_Data + Ring [][][]byte // contains entire ring in raw form TXpublickey string PayID32 string // 32 byte payment ID PayID8 string // 8 byte encrypted payment ID - Proof_address string // address agains which which the proving ran - Proof_index int64 // proof satisfied for which index - Proof_amount string // decoded amount - Proof_PayID8 string // decrypted 8 byte payment id - Proof_error string // error if any while decoding proof + Proof_address string // address agains which which the proving ran + Proof_index int64 // proof satisfied for which index + Proof_amount string // decoded amount + Proof_Payload_raw string // payload raw bytes + Proof_Payload string // if proof decoded, decoded , else decode error + Proof_error string // error if any while decoding proof + SC_TX_Available string //bool // whether this contains an SC TX + SC_Signer string // whether SC signer + SC_Signer_verified string // whether SC signer can be verified successfully + SC_Balance uint64 // SC SC_Balance in atomic units + SC_Balance_string string // SC_Balance in DERO + SC_Keys map[string]string // SC key value of + SC_Args rpc.Arguments // rpc.Arguments + SC_Code string // install SC + + Assets []Asset +} + +type Asset struct { + SCID crypto.Hash + Fees string + Burn string + Ring []string + Ring_size int } // any information for block which needs to be printed @@ -306,7 +325,7 @@ type block_info struct { // if hash is less than 64 bytes then it is considered a height parameter func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (err error) { var bl block.Block - var bresult structures.GetBlock_Result + var bresult rpc.GetBlock_Result var block_height int var block_bin []byte @@ -314,7 +333,7 @@ func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (e fmt.Sscanf(block_hash, "%d", &block_height) // user requested block height log.Debugf("User requested block at topoheight %d user input %s", block_height, block_hash) - if err = rpc_client.Call("DERO.GetBlock", structures.GetBlock_Params{Height: uint64(block_height)}, &bresult); err != nil { + if err = rpc_client.Call("DERO.GetBlock", rpc.GetBlock_Params{Height: uint64(block_height)}, &bresult); err != nil { return fmt.Errorf("getblock rpc failed") } @@ -322,7 +341,7 @@ func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (e log.Debugf("User requested block using hash %s", block_hash) - if err = rpc_client.Call("DERO.GetBlock", structures.GetBlock_Params{Hash: block_hash}, &bresult); err != nil { + if err = rpc_client.Call("DERO.GetBlock", rpc.GetBlock_Params{Hash: block_hash}, &bresult); err != nil { return fmt.Errorf("getblock rpc failed") } } @@ -405,11 +424,18 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) info.Sizeuint64 = uint64(len(tx.Serialize())) info.Version = int(tx.Version) //info.Extra = fmt.Sprintf("%x", tx.Extra) - info.RootHash = fmt.Sprintf("%x", tx.Statement.Roothash[:]) + + if len(tx.Payloads) >= 1 { + info.RootHash = fmt.Sprintf("%x", tx.Payloads[0].Statement.Roothash[:]) + } info.HeightBuilt = tx.Height //info.In = len(tx.Vin) //info.Out = len(tx.Vout) + if tx.TransactionType == transaction.BURN_TX { + info.Burn_Value = fmt.Sprintf(" %.05f", float64(tx.Value)/100000) + } + switch tx.TransactionType { case transaction.PREMINE: @@ -418,7 +444,7 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) panic(err) } - astring := address.NewAddressFromKeys(&acckey) + astring := rpc.NewAddressFromKeys(&acckey) astring.Mainnet = mainnet info.OutAddress = append(info.OutAddress, astring.String()) info.Amount = globals.FormatMoney(tx.Value) @@ -429,7 +455,7 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) panic(err) } - astring := address.NewAddressFromKeys(&acckey) + astring := rpc.NewAddressFromKeys(&acckey) astring.Mainnet = mainnet info.OutAddress = append(info.OutAddress, astring.String()) @@ -440,23 +466,15 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) if err := acckey.DecodeCompressed(tx.MinerAddress[:]); err != nil { panic(err) } - astring := address.NewAddressFromKeys(&acckey) + astring := rpc.NewAddressFromKeys(&acckey) astring.Mainnet = mainnet info.OutAddress = append(info.OutAddress, astring.String()) - case transaction.NORMAL: + case transaction.NORMAL, transaction.BURN_TX, transaction.SC_TX: - info.Fee = fmt.Sprintf("%.05f", float64(tx.Statement.Fees)/100000) - info.Feeuint64 = tx.Statement.Fees - info.Amount = "?" + } - info.Ring_size = len(tx.Statement.Publickeylist_compressed) //len(tx.Vin[0].(transaction.Txin_to_key).Key_offsets) - - for i := range tx.Statement.Publickeylist { - astring := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[i])) - astring.Mainnet = mainnet - info.OutAddress = append(info.OutAddress, astring.String()) - - } + if tx.TransactionType == transaction.SC_TX { + info.SC_Args = tx.SCDATA } // if outputs cannot be located, do not panic @@ -468,21 +486,11 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) switch 0 { case 0: info.Type = "DERO_HOMOMORPHIC" - /*case 1: - info.Type = "RingCT/1 MG" - case 2: - info.Type = "RingCT/2 Simple" - case 3: - info.Type = "RingCT/3 Full bulletproof" - case 4: - info.Type = "RingCT/4 Simple Bulletproof" - */ - default: - panic("not implement") + panic("not implemented") } - if !info.In_Pool && !info.CoinBase && tx.TransactionType == transaction.NORMAL { // find the age of block and other meta + if !info.In_Pool && !info.CoinBase && (tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.BURN_TX || tx.TransactionType == transaction.SC_TX) { // find the age of block and other meta var blinfo block_info err := load_block_from_rpc(&blinfo, fmt.Sprintf("%s", info.Height), false) // we only need block data and not data of txs if err != nil { @@ -504,8 +512,8 @@ func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) // load and setup txinfo from rpc func load_tx_from_rpc(info *txinfo, txhash string) (err error) { - var tx_params structures.GetTransaction_Params - var tx_result structures.GetTransaction_Result + var tx_params rpc.GetTransaction_Params + var tx_result rpc.GetTransaction_Result //fmt.Printf("Requesting tx data %s", txhash); tx_params.Tx_Hashes = append(tx_params.Tx_Hashes, txhash) @@ -541,13 +549,46 @@ func load_tx_from_rpc(info *txinfo, txhash string) (err error) { } if tx.IsCoinbase() { // fill miner tx reward from what the chain tells us - info.Amount = fmt.Sprintf("%.012f", float64(uint64(tx_result.Txs[0].Reward))/1000000000000) + info.Amount = fmt.Sprintf("%.05f", float64(uint64(tx_result.Txs[0].Reward))/100000) } info.ValidBlock = tx_result.Txs[0].ValidBlock info.InvalidBlock = tx_result.Txs[0].InvalidBlock - //info.Ring = tx_result.Txs[0].Ring + info.Ring = tx_result.Txs[0].Ring + + if tx.TransactionType == transaction.NORMAL || tx.TransactionType == transaction.BURN_TX || tx.TransactionType == transaction.SC_TX { + + for t := range tx.Payloads { + var a Asset + a.SCID = tx.Payloads[t].SCID + a.Fees = fmt.Sprintf("%.05f", float64(tx.Payloads[t].Statement.Fees)/100000) + a.Burn = fmt.Sprintf("%.05f", float64(tx.Payloads[t].BurnValue)/100000) + a.Ring_size = len(tx_result.Txs[0].Ring[t]) + + for i := range tx_result.Txs[0].Ring[t] { + + point_compressed := tx_result.Txs[0].Ring[t][i] + var p bn256.G1 + if err = p.DecodeCompressed(point_compressed[:]); err != nil { + continue + } + + astring := rpc.NewAddressFromKeys((*crypto.Point)(&p)) + astring.Mainnet = mainnet + a.Ring = append(a.Ring, astring.String()) + } + + info.Assets = append(info.Assets, a) + + } + //fmt.Printf("assets now %+v\n", info.Assets) + } + + info.SC_Balance = tx_result.Txs[0].Balance + info.SC_Balance_string = fmt.Sprintf("%.05f", float64(uint64(info.SC_Balance)/100000)) + info.SC_Code = tx_result.Txs[0].Code + //info.Ring = strings.Join(info.OutAddress, " ") //fmt.Printf("tx_result %+v\n",tx_result.Txs) // fmt.Printf("response contained tx %s \n", tx.GetHash()) @@ -607,14 +648,14 @@ func tx_handler(w http.ResponseWriter, r *http.Request) { if tx_proof != "" { // there may be more than 1 amounts, only first one is shown - addresses, amounts, payids, err := proof.Prove(tx_proof, info.Hex, mainnet) + addresses, amounts, raw, decoded, err := proof.Prove(tx_proof, info.Hex, info.Ring, mainnet) if err == nil { //&& len(amounts) > 0 && len(indexes) > 0{ - log.Debugf("Successfully proved transaction %s len(payids) %d", tx_hex, len(payids)) + log.Debugf("Successfully proved transaction %s len(payids) %d", tx_hex, len(decoded)) info.Proof_address = addresses[0] info.Proof_amount = globals.FormatMoney(amounts[0]) - if len(payids) >= 1 { - info.Proof_PayID8 = fmt.Sprintf("%x", payids[0]) // decrypted payment ID - } + info.Proof_Payload_raw = BytesViewer(raw[0]).String() // raw payload + info.Proof_Payload = decoded[0] + } else { log.Debugf("err while proving %s", err) if err != nil { @@ -675,7 +716,7 @@ func fill_tx_structure(pos int, size_in_blocks int) (data []block_info) { func show_page(w http.ResponseWriter, page int) { data := map[string]interface{}{} - var info structures.GetInfo_Result + var info rpc.GetInfo_Result data["title"] = "DERO HE BlockChain Explorer(v1)" data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") @@ -747,7 +788,7 @@ exit_error: func txpool_handler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{} - var info structures.GetInfo_Result + var info rpc.GetInfo_Result data["title"] = "DERO HE BlockChain Explorer(v1)" data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") @@ -801,7 +842,7 @@ func root_handler(w http.ResponseWriter, r *http.Request) { // search handler, finds the items using rpc bruteforce func search_handler(w http.ResponseWriter, r *http.Request) { - var info structures.GetInfo_Result + var info rpc.GetInfo_Result var err error log.Debugf("Showing search page") @@ -860,7 +901,7 @@ func search_handler(w http.ResponseWriter, r *http.Request) { { // show error page data := map[string]interface{}{} - var info structures.GetInfo_Result + var info rpc.GetInfo_Result data["title"] = "DERO HE BlockChain Explorer(v1)" data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") @@ -901,7 +942,7 @@ func fill_tx_pool_info(data map[string]interface{}, max_count int) error { var err error var txs []txinfo - var txpool structures.GetTxPool_Result + var txpool rpc.GetTxPool_Result data["mempool"] = txs // initialize with empty data if err = rpc_client.Call("DERO.GetTxPool", nil, &txpool); err != nil { @@ -923,5 +964,56 @@ func fill_tx_pool_info(data map[string]interface{}, max_count int) error { data["mempool"] = txs return nil - +} + +// BytesViewer bytes viewer +type BytesViewer []byte + +// String returns view in hexadecimal +func (b BytesViewer) String() string { + if len(b) == 0 { + return "invlaid string" + } + const head = ` +| Address | Hex | Text | +| -------: | :---------------------------------------------- | :--------------- | +` + const row = 16 + result := make([]byte, 0, len(head)/2*(len(b)/16+3)) + result = append(result, head...) + for i := 0; i < len(b); i += row { + result = append(result, "| "...) + result = append(result, fmt.Sprintf("%08x", i)...) + result = append(result, " | "...) + + k := i + row + more := 0 + if k >= len(b) { + more = k - len(b) + k = len(b) + } + for j := i; j != k; j++ { + if b[j] < 16 { + result = append(result, '0') + } + result = strconv.AppendUint(result, uint64(b[j]), 16) + result = append(result, ' ') + } + for j := 0; j != more; j++ { + result = append(result, " "...) + } + result = append(result, "| "...) + buf := bytes.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + return r + }, b[i:k]) + result = append(result, buf...) + for j := 0; j != more; j++ { + result = append(result, ' ') + } + result = append(result, " |\n"...) + } + return *(*string)(unsafe.Pointer(&result)) } diff --git a/cmd/explorer/templates.go b/cmd/explorer/templates.go index f6442a3..2fcb59b 100644 --- a/cmd/explorer/templates.go +++ b/cmd/explorer/templates.go @@ -303,7 +303,10 @@ var tx_template string = `{{define "tx"}} {{ template "header" . }}

Tx hash: {{.info.Hash}} Type {{.info.TransactionType }}

-
Tx prefix hash: {{.info.PrefixHash}}
+ + {{if eq .info.TransactionType "BURN" }} +

Burns: {{.info.Burn_Value }} DERO

+ {{end}}
Block: {{.info.ValidBlock}} (VALID)
@@ -329,7 +332,7 @@ var tx_template string = `{{define "tx"}} {{end}} -{{if eq .info.TransactionType "NORMAL"}} +{{if or (eq .info.TransactionType "NORMAL") (eq .info.TransactionType "BURN") (eq .info.TransactionType "SC") }}
Tx RootHash: {{.info.RootHash}} built height : {{.info.HeightBuilt}}
@@ -360,23 +363,40 @@ var tx_template string = `{{define "tx"}} Extra: {{.info.Extra}} -

{{len .info.OutAddress}} inputs/outputs (RING size)

-
+ + {{range $ii, $ee := .info.Assets}} +
SCID: {{$ee.SCID}} {{$ee.Ring_size}} inputs/outputs (RING size) Fees {{$ee.Fees}} Burned {{$ee.Burn}}
+ +
- - {{range $i, $e := .info.OutAddress}} + {{range $i, $e := $ee.Ring}} - {{end}}
addressamount
{{ $e }}{{$.info.Amount}}
+ + {{end}} +{{if eq .info.TransactionType "SC"}} + + + + + + + + + + +
SC Balance: {{ .info.SC_Balance_string }} DERO
SC CODE:
  {{  .info.SC_Code }}
SC Arguments: {{ .info.SC_Args }}
+ +{{end}}

@@ -415,8 +435,13 @@ var tx_template string = `{{define "tx"}}

{{.info.Proof_address}} Received {{.info.Proof_amount}} DERO - {{if .info.Proof_PayID8}} -
Decrypted Payment ID {{ .info.Proof_PayID8}} + + {{if .info.Proof_Payload}} +
Decoded Data {{ .info.Proof_Payload}} + +
Raw Data +
{{ .info.Proof_Payload_raw}}
+ {{end}}

diff --git a/cmd/rpc_examples/pong_server/pong_server.go b/cmd/rpc_examples/pong_server/pong_server.go new file mode 100644 index 0000000..1d704a1 --- /dev/null +++ b/cmd/rpc_examples/pong_server/pong_server.go @@ -0,0 +1,182 @@ +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +//import "os" +import "fmt" +import "time" +import "crypto/sha1" +import "github.com/romana/rlog" + +import "etcd.io/bbolt" + +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/walletapi" +import "github.com/ybbus/jsonrpc" + +const PLUGIN_NAME = "pong_server" + +const DEST_PORT = uint64(0x1234567812345678) + +var expected_arguments = rpc.Arguments{ + {rpc.RPC_DESTINATION_PORT, rpc.DataUint64, DEST_PORT}, + // { rpc.RPC_EXPIRY , rpc.DataTime, time.Now().Add(time.Hour).UTC()}, + {rpc.RPC_COMMENT, rpc.DataString, "Purchase PONG"}, + //{"float64", rpc.DataFloat64, float64(0.12345)}, // in atomic units + {rpc.RPC_VALUE_TRANSFER, rpc.DataUint64, uint64(12345)}, // in atomic units + +} + +// currently the interpreter seems to have a glitch if this gets initialized within the code +// see limitations github.com/traefik/yaegi +var response = rpc.Arguments{ + {rpc.RPC_DESTINATION_PORT, rpc.DataUint64, uint64(0)}, + {rpc.RPC_SOURCE_PORT, rpc.DataUint64, DEST_PORT}, + {rpc.RPC_COMMENT, rpc.DataString, "Successfully purchased pong (this could be serial/license key or download link or further)"}, +} + +var rpcClient = jsonrpc.NewClient("http://127.0.0.1:40403/json_rpc") + +// empty place holder + +func main() { + var err error + fmt.Printf("Pong Server to demonstrate RPC over dero chain.\n") + var addr *rpc.Address + var addr_result rpc.GetAddress_Result + err = rpcClient.CallFor(&addr_result, "GetAddress") + if err != nil || addr_result.Address == "" { + fmt.Printf("Could not obtain address from wallet err %s\n", err) + return + } + + if addr, err = rpc.NewAddress(addr_result.Address); err != nil { + fmt.Printf("address could not be parsed: addr:%s err:%s\n", addr_result.Address, err) + return + } + + shasum := fmt.Sprintf("%x", sha1.Sum([]byte(addr.String()))) + + db_name := fmt.Sprintf("%s_%s.bbolt.db", PLUGIN_NAME, shasum) + db, err := bbolt.Open(db_name, 0600, nil) + if err != nil { + fmt.Printf("could not open db err:%s\n", err) + return + } + //defer db.Close() + + err = db.Update(func(tx *bbolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte("SALE")) + return err + }) + if err != nil { + fmt.Printf("err creating bucket. err %s\n", err) + } + + fmt.Printf("Persistant store created in '%s'\n", db_name) + + fmt.Printf("Wallet Address: %s\n", addr) + service_address_without_amount := addr.Clone() + service_address_without_amount.Arguments = expected_arguments[:len(expected_arguments)-1] + fmt.Printf("Integrated address to activate '%s', (without hardcoded amount) service: \n%s\n", PLUGIN_NAME, service_address_without_amount.String()) + + // service address can be created client side for now + service_address := addr.Clone() + service_address.Arguments = expected_arguments + fmt.Printf("Integrated address to activate '%s', service: \n%s\n", PLUGIN_NAME, service_address.String()) + + processing_thread(db) // rkeep processing + + //time.Sleep(time.Second) + //return +} + +func processing_thread(db *bbolt.DB) { + + var err error + + for { // currently we traverse entire history + + time.Sleep(time.Second) + + var transfers rpc.Get_Transfers_Result + err = rpcClient.CallFor(&transfers, "GetTransfers", rpc.Get_Transfers_Params{In: true, DestinationPort: DEST_PORT}) + if err != nil { + rlog.Warnf("Could not obtain gettransfers from wallet err %s\n", err) + continue + } + + for _, e := range transfers.Entries { + if e.Coinbase || !e.Incoming { // skip coinbase or outgoing, self generated transactions + continue + } + + // check whether the entry has been processed before, if yes skip it + var already_processed bool + db.View(func(tx *bbolt.Tx) error { + if b := tx.Bucket([]byte("SALE")); b != nil { + if ok := b.Get([]byte(e.TXID)); ok != nil { // if existing in bucket + already_processed = true + } + } + return nil + }) + + if already_processed { // if already processed skip it + continue + } + + // check whether this service should handle the transfer + if !e.Payload_RPC.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) || + DEST_PORT != e.Payload_RPC.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64) { // this service is expecting value to be specfic + continue + + } + + rlog.Infof("tx should be processed %s\n", e.TXID) + + if expected_arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // this service is expecting value to be specfic + value_expected := expected_arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + if e.Amount != value_expected { // TODO we should mark it as faulty + rlog.Warnf("user transferred %d, we were expecting %d. so we will not do anything\n", e.Amount, value_expected) // this is an unexpected situation + continue + } + // value received is what we are expecting, so time for response + + response[0].Value = e.SourcePort // source port now becomes destination port, similar to TCP + response[2].Value = fmt.Sprintf("Sucessfully purchased pong (could be serial, license or download link or anything).You sent %s at height %d", walletapi.FormatMoney(e.Amount), e.Height) + + //_, err := response.CheckPack(transaction.PAYLOAD0_LIMIT)) // we only have 144 bytes for RPC + + // sender of ping now becomes destination + var str string + tparams := rpc.Transfer_Params{Transfers: []rpc.Transfer{{Destination: e.Sender, Amount: uint64(1), Payload_RPC: response}}} + err = rpcClient.CallFor(&str, "Transfer", tparams) + if err != nil { + rlog.Warnf("sending reply tx err %s\n", err) + continue + } + + err = db.Update(func(tx *bbolt.Tx) error { + b := tx.Bucket([]byte("SALE")) + return b.Put([]byte(e.TXID), []byte("done")) + }) + if err != nil { + rlog.Warnf("err updating db to err %s\n", err) + } else { + rlog.Infof("ping replied successfully with pong") + } + + } + } + + } +} diff --git a/cmd/rpc_examples/readme.txt b/cmd/rpc_examples/readme.txt new file mode 100644 index 0000000..09aaa26 --- /dev/null +++ b/cmd/rpc_examples/readme.txt @@ -0,0 +1,2 @@ +Various RPC servers can be developed, which can represent various activitities not representable on any existing blockchain. + diff --git a/config/config.go b/config/config.go index 5c33e0c..2aab3d2 100644 --- a/config/config.go +++ b/config/config.go @@ -17,7 +17,7 @@ package config import "github.com/satori/go.uuid" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" // all global configuration variables are picked from here @@ -28,27 +28,12 @@ import "github.com/deroproject/derohe/crypto" // since most mining nodes will be running in datacenter, 3 secs blocks c const BLOCK_TIME = uint64(18) -// we are ignoring leap seconds from calculations - -// coin emiision related settings -const COIN_MONEY_SUPPLY = uint64(18446744073709551615) // 2^64-1 -const COIN_EMISSION_SPEED_FACTOR = uint64(20) -const COIN_DIFFICULTY_TARGET = uint64(120) // this is a feeder to emission formula -const COIN_FINAL_SUBSIDY_PER_MINUTE = uint64(300000000000) // 0.3 DERO per minute = 157680 per year roughly - -// these are used to configure mainnet hard fork -const HARDFORK_1_END = int64(1) - -//const HARDFORK_1_TOTAL_SUPPLY = uint64(2000000000000000000 ) // this is used to mark total supply -// till 95532 (includind) 4739519967524007940 -// 95543 4739807553788105597 -// 95549 4739964392976757069 -// 95550 4739990536584241377 -const MAINNET_HARDFORK_1_TOTAL_SUPPLY = uint64(4739990536584241377) - -const TESTNET_HARDFORK_1_TOTAL_SUPPLY = uint64(4319584000000000000) - -const MAX_CHAIN_HEIGHT = uint64(2147483648) // 2^31 +// note we are keeping the tree name small for disk savings, since they will be stored n times (atleast or archival nodes) +// this is used by graviton +const BALANCE_TREE = "B" // keeps main balance +const SC_META = "M" // keeps all SCs balance, their state, their OWNER, their data tree top hash is stored here +// one are open SCs, which provide i/o privacy +// one are private SCs which are truly private, in which no one has visibility of io or functionality // 1.25 MB block every 12 secs is equal to roughly 75 TX per second // if we consider side blocks, TPS increase to > 100 TPS @@ -65,8 +50,8 @@ const MAX_MIXIN = 128 // <= 128, mixin will be accepted // ATLANTIS FEE calculation constants are here const FEE_PER_KB = uint64(1000000000) // .001 dero per kb -const MAINNET_BOOTSTRAP_DIFFICULTY = uint64(10 * BLOCK_TIME) // atlantis mainnet botstrapped at 200 MH/s -const MAINNET_MINIMUM_DIFFICULTY = uint64(10 * BLOCK_TIME) // 5 KH/s +const MAINNET_BOOTSTRAP_DIFFICULTY = uint64(800 * BLOCK_TIME) // atlantis mainnet botstrapped at 200 MH/s +const MAINNET_MINIMUM_DIFFICULTY = uint64(800 * BLOCK_TIME) // 5 KH/s // testnet bootstraps at 1 MH //const TESTNET_BOOTSTRAP_DIFFICULTY = uint64(1000*1000*BLOCK_TIME) @@ -79,6 +64,9 @@ const TESTNET_MINIMUM_DIFFICULTY = uint64(800 * BLOCK_TIME) // 800 H // gives immense scalability, const STABLE_LIMIT = int64(8) +// reward percent that is shared between miners/dev +const DEVSHARE = uint64(600) // it's out of 10000, 600*100/10000 = 6%, 3% dev, 3% foundation + // we can have number of chains running for testing reasons type CHAIN_CONFIG struct { Name string @@ -88,6 +76,8 @@ type CHAIN_CONFIG struct { RPC_Default_Port int Wallet_RPC_Default_Port int + Dev_Address string // to which address the dev's share of fees must go + Genesis_Nonce uint32 Genesis_Block_Hash crypto.Hash @@ -96,7 +86,7 @@ type CHAIN_CONFIG struct { } var Mainnet = CHAIN_CONFIG{Name: "mainnet", - Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x9a, 0x44, 0x44, 0x0}), + Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x9a, 0x44, 0x45, 0x0}), P2P_Default_Port: 10101, RPC_Default_Port: 10102, Wallet_RPC_Default_Port: 10103, @@ -110,10 +100,11 @@ var Mainnet = CHAIN_CONFIG{Name: "mainnet", "8fff7f" + // PREMINE_VALUE "a01f9bcc1208dee302769931ad378a4c0c4b2c21b0cfb3e752607e12d2b6fa6425", // miners public key + Dev_Address: "deto1qxsplx7vzgydacczw6vnrtfh3fxqcjevyxcvlvl82fs8uykjkmaxgfgulfha5", } var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 bytes 0 - Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x24, 0x00, 0x00, 0x00}), + Network_ID: uuid.FromBytesOrNil([]byte{0x59, 0xd7, 0xf7, 0xe9, 0xdd, 0x48, 0xd5, 0xfd, 0x13, 0x0a, 0xf6, 0xe0, 0x25, 0x00, 0x00, 0x00}), P2P_Default_Port: 40401, RPC_Default_Port: 40402, Wallet_RPC_Default_Port: 40403, @@ -127,6 +118,7 @@ var Testnet = CHAIN_CONFIG{Name: "testnet", // testnet will always have last 3 b "8fff7f" + // PREMINE_VALUE "a01f9bcc1208dee302769931ad378a4c0c4b2c21b0cfb3e752607e12d2b6fa6425", // miners public key + Dev_Address: "deto1qxsplx7vzgydacczw6vnrtfh3fxqcjevyxcvlvl82fs8uykjkmaxgfgulfha5", } // mainnet has a remote daemon node, which can be used be default, if user provides a --remote flag diff --git a/config/version.go b/config/version.go index 60f370a..5ffe2cc 100644 --- a/config/version.go +++ b/config/version.go @@ -20,4 +20,4 @@ import "github.com/blang/semver" // right now it has to be manually changed // do we need to include git commitsha?? -var Version = semver.MustParse("3.0.0-25.DEROHE.alpha+30122020") +var Version = semver.MustParse("3.2.0-12.DEROHE.STARGATE+22022021") diff --git a/cryptography/bn256/CODE_OF_CONDUCT.md b/cryptography/bn256/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..61069f8 --- /dev/null +++ b/cryptography/bn256/CODE_OF_CONDUCT.md @@ -0,0 +1,47 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [opensource@clearmatics.com][email]. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[email]: mailto:opensource@clearmatics.com +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/golang.org/x/tools/LICENSE b/cryptography/bn256/LICENSE similarity index 100% rename from vendor/golang.org/x/tools/LICENSE rename to cryptography/bn256/LICENSE diff --git a/cryptography/bn256/Makefile b/cryptography/bn256/Makefile new file mode 100644 index 0000000..464399b --- /dev/null +++ b/cryptography/bn256/Makefile @@ -0,0 +1,34 @@ +SHELL = bash +GO_FILES = $(shell find . -name "*.go" | grep -vE ".git") +GO_COVER_FILE = `find . -name "coverage.out"` + +.PHONY: all test format cover-clean check fmt vet lint + +test: $(GO_FILES) + go test ./... + +format: + gofmt -s -w ${GO_FILES} + +cover: $(GO_FILES) + go test -coverprofile=coverage.out ./... + go tool cover -html=coverage.out + +cover-clean: + rm -f $(GO_COVER_FILE) + +deps: + go mod download + +check: + if [ -n "$(shell gofmt -l ${GO_FILES})" ]; then \ + echo 1>&2 'The following files need to be formatted:'; \ + gofmt -l .; \ + exit 1; \ + fi + +vet: + go vet $(GO_FILES) + +lint: + golint $(GO_FILES) diff --git a/cryptography/bn256/README.md b/cryptography/bn256/README.md new file mode 100644 index 0000000..e4a77fd --- /dev/null +++ b/cryptography/bn256/README.md @@ -0,0 +1,33 @@ +# BN256 + +[![Build Status](https://travis-ci.org/clearmatics/bn256.svg?branch=master)](https://travis-ci.org/clearmatics/bn256) + +This package implements a [particular](https://eprint.iacr.org/2013/507.pdf) bilinear group. +The code is imported from https://github.com/ethereum/go-ethereum/tree/master/crypto/bn256/cloudflare + +:rotating_light: **WARNING** This package originally claimed to operate at a 128-bit level. However, [recent work](https://ellipticnews.wordpress.com/2016/05/02/kim-barbulescu-variant-of-the-number-field-sieve-to-compute-discrete-logarithms-in-finite-fields/) suggest that **this is no longer the case**. + +## A note on the selection of the bilinear group + +The parameters defined in the `constants.go` file follow the parameters used in [alt-bn128 (libff)](https://github.com/scipr-lab/libff/blob/master/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp). These parameters were selected so that `r−1` has a high 2-adic order. This is key to improve efficiency of the key and proof generation algorithms of the SNARK used. + +## Installation + + go get github.com/clearmatics/bn256 + +## Development + +This project uses [go modules](https://github.com/golang/go/wiki/Modules). +If you develop in your `GOPATH` and use GO 1.11, make sure to run: +```bash +export GO111MODULE=on +``` + +In fact: +> (Inside $GOPATH/src, for compatibility, the go command still runs in the old GOPATH mode, even if a go.mod is found.) +See: https://blog.golang.org/using-go-modules + +> For more fine-grained control, the module support in Go 1.11 respects a temporary environment variable, GO111MODULE, which can be set to one of three string values: off, on, or auto (the default). If GO111MODULE=off, then the go command never uses the new module support. Instead it looks in vendor directories and GOPATH to find dependencies; we now refer to this as "GOPATH mode." If GO111MODULE=on, then the go command requires the use of modules, never consulting GOPATH. We refer to this as the command being module-aware or running in "module-aware mode". If GO111MODULE=auto or is unset, then the go command enables or disables module support based on the current directory. Module support is enabled only when the current directory is outside GOPATH/src and itself contains a go.mod file or is below a directory containing a go.mod file. +See: https://golang.org/cmd/go/#hdr-Preliminary_module_support + +The project follows standard Go conventions using `gofmt`. If you wish to contribute to the project please follow standard Go conventions. The CI server automatically runs these checks. diff --git a/cryptography/bn256/bn256.go b/cryptography/bn256/bn256.go new file mode 100644 index 0000000..ff58584 --- /dev/null +++ b/cryptography/bn256/bn256.go @@ -0,0 +1,490 @@ +// Package bn256 implements a particular bilinear group at the 128-bit security +// level. +// +// Bilinear groups are the basis of many of the new cryptographic protocols that +// have been proposed over the past decade. They consist of a triplet of groups +// (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ +// is a generator of the respective group). That function is called a pairing +// function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible +// with the implementation described in that paper. +package bn256 + +import ( + "crypto/rand" + "errors" + "io" + "math/big" +) + +func randomK(r io.Reader) (k *big.Int, err error) { + for { + k, err = rand.Int(r, Order) + if k.Sign() > 0 || err != nil { + return + } + } +} + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 struct { + p *curvePoint +} + +// RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r. +func RandomG1(r io.Reader) (*big.Int, *G1, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G1).ScalarBaseMult(k), nil +} + +func (e *G1) String() string { + return "bn256.G1" + e.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns e. +func (e *G1) ScalarBaseMult(k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(curveGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G1) Add(a, b *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G1) Neg(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G1) Set(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e to a byte slice. +func (e *G1) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*2) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x) + temp.Marshal(ret) + montDecode(temp, &e.p.y) + temp.Marshal(ret[numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G1) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 2*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &curvePoint{} + } else { + e.p.x, e.p.y = gfP{0}, gfP{0} + } + var err error + if err = e.p.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x, &e.p.x) + montEncode(&e.p.y, &e.p.y) + + zero := gfP{0} + if e.p.x == zero && e.p.y == zero { + // This is the point at infinity. + e.p.y = *newGFp(1) + e.p.z = gfP{0} + e.p.t = gfP{0} + } else { + e.p.z = *newGFp(1) + e.p.t = *newGFp(1) + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[2*numBytes:], nil +} + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 struct { + p *twistPoint +} + +// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r. +func RandomG2(r io.Reader) (*big.Int, *G2, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G2).ScalarBaseMult(k), nil +} + +func (e *G2) String() string { + return "bn256.G2" + e.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns out. +func (e *G2) ScalarBaseMult(k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(twistGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G2) Add(a, b *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G2) Neg(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G2) Set(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e into a byte slice. +func (e *G2) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &twistPoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*4) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.y.y) + temp.Marshal(ret[3*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G2) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 4*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &twistPoint{} + } + var err error + if err = e.p.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x.x, &e.p.x.x) + montEncode(&e.p.x.y, &e.p.x.y) + montEncode(&e.p.y.x, &e.p.y.x) + montEncode(&e.p.y.y, &e.p.y.y) + + if e.p.x.IsZero() && e.p.y.IsZero() { + // This is the point at infinity. + e.p.y.SetOne() + e.p.z.SetZero() + e.p.t.SetZero() + } else { + e.p.z.SetOne() + e.p.t.SetOne() + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[4*numBytes:], nil +} + +// GT is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type GT struct { + p *gfP12 +} + +// Pair calculates an Optimal Ate pairing. +func Pair(g1 *G1, g2 *G2) *GT { + return >{optimalAte(g2.p, g1.p)} +} + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + acc := new(gfP12) + acc.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].p.IsInfinity() || b[i].p.IsInfinity() { + continue + } + acc.Mul(acc, miller(b[i].p, a[i].p)) + } + return finalExponentiation(acc).IsOne() +} + +// Miller applies Miller's algorithm, which is a bilinear function from the +// source groups to F_p^12. Miller(g1, g2).Finalize() is equivalent to Pair(g1, +// g2). +func Miller(g1 *G1, g2 *G2) *GT { + return >{miller(g2.p, g1.p)} +} + +func (e *GT) String() string { + return "bn256.GT" + e.p.String() +} + +// ScalarMult sets e to a*k and then returns e. +func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Exp(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *GT) Add(a, b *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Mul(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *GT) Neg(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Conjugate(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *GT) Set(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Set(a.p) + return e +} + +// Finalize is a linear function from F_p^12 to GT. +func (e *GT) Finalize() *GT { + ret := finalExponentiation(e.p) + e.p.Set(ret) + return e +} + +// Marshal converts e into a byte slice. +func (e *GT) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &gfP12{} + e.p.SetOne() + } + + ret := make([]byte, numBytes*12) + temp := &gfP{} + + montDecode(temp, &e.p.x.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.x.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.x.y.y) + temp.Marshal(ret[3*numBytes:]) + montDecode(temp, &e.p.x.z.x) + temp.Marshal(ret[4*numBytes:]) + montDecode(temp, &e.p.x.z.y) + temp.Marshal(ret[5*numBytes:]) + montDecode(temp, &e.p.y.x.x) + temp.Marshal(ret[6*numBytes:]) + montDecode(temp, &e.p.y.x.y) + temp.Marshal(ret[7*numBytes:]) + montDecode(temp, &e.p.y.y.x) + temp.Marshal(ret[8*numBytes:]) + montDecode(temp, &e.p.y.y.y) + temp.Marshal(ret[9*numBytes:]) + montDecode(temp, &e.p.y.z.x) + temp.Marshal(ret[10*numBytes:]) + montDecode(temp, &e.p.y.z.y) + temp.Marshal(ret[11*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *GT) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if len(m) < 12*numBytes { + return nil, errors.New("bn256: not enough data") + } + + if e.p == nil { + e.p = &gfP12{} + } + + var err error + if err = e.p.x.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.x.Unmarshal(m[4*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.y.Unmarshal(m[5*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.x.Unmarshal(m[6*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.y.Unmarshal(m[7*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.x.Unmarshal(m[8*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.y.Unmarshal(m[9*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.x.Unmarshal(m[10*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.y.Unmarshal(m[11*numBytes:]); err != nil { + return nil, err + } + montEncode(&e.p.x.x.x, &e.p.x.x.x) + montEncode(&e.p.x.x.y, &e.p.x.x.y) + montEncode(&e.p.x.y.x, &e.p.x.y.x) + montEncode(&e.p.x.y.y, &e.p.x.y.y) + montEncode(&e.p.x.z.x, &e.p.x.z.x) + montEncode(&e.p.x.z.y, &e.p.x.z.y) + montEncode(&e.p.y.x.x, &e.p.y.x.x) + montEncode(&e.p.y.x.y, &e.p.y.x.y) + montEncode(&e.p.y.y.x, &e.p.y.y.x) + montEncode(&e.p.y.y.y, &e.p.y.y.y) + montEncode(&e.p.y.z.x, &e.p.y.z.x) + montEncode(&e.p.y.z.y, &e.p.y.z.y) + + return m[12*numBytes:], nil +} diff --git a/cryptography/bn256/bn256_test.go b/cryptography/bn256/bn256_test.go new file mode 100644 index 0000000..0c8016d --- /dev/null +++ b/cryptography/bn256/bn256_test.go @@ -0,0 +1,116 @@ +package bn256 + +import ( + "bytes" + "crypto/rand" + "testing" +) + +func TestG1Marshal(t *testing.T) { + _, Ga, err := RandomG1(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G1) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestG2Marshal(t *testing.T) { + _, Ga, err := RandomG2(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G2) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestBilinearity(t *testing.T) { + for i := 0; i < 2; i++ { + a, p1, _ := RandomG1(rand.Reader) + b, p2, _ := RandomG2(rand.Reader) + e1 := Pair(p1, p2) + + e2 := Pair(&G1{curveGen}, &G2{twistGen}) + e2.ScalarMult(e2, a) + e2.ScalarMult(e2, b) + + if *e1.p != *e2.p { + t.Fatalf("bad pairing result: %s", e1) + } + } +} + +func TestTripartiteDiffieHellman(t *testing.T) { + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + pa, pb, pc := new(G1), new(G1), new(G1) + qa, qb, qc := new(G2), new(G2), new(G2) + + pa.Unmarshal(new(G1).ScalarBaseMult(a).Marshal()) + qa.Unmarshal(new(G2).ScalarBaseMult(a).Marshal()) + pb.Unmarshal(new(G1).ScalarBaseMult(b).Marshal()) + qb.Unmarshal(new(G2).ScalarBaseMult(b).Marshal()) + pc.Unmarshal(new(G1).ScalarBaseMult(c).Marshal()) + qc.Unmarshal(new(G2).ScalarBaseMult(c).Marshal()) + + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + k1Bytes := k1.Marshal() + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + k2Bytes := k2.Marshal() + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + k3Bytes := k3.Marshal() + + if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) { + t.Errorf("keys didn't agree") + } +} + +func BenchmarkG1(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G1).ScalarBaseMult(x) + } +} + +func BenchmarkG2(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G2).ScalarBaseMult(x) + } +} +func BenchmarkPairing(b *testing.B) { + for i := 0; i < b.N; i++ { + Pair(&G1{curveGen}, &G2{twistGen}) + } +} diff --git a/cryptography/bn256/constants.go b/cryptography/bn256/constants.go new file mode 100644 index 0000000..08cfc8d --- /dev/null +++ b/cryptography/bn256/constants.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +func bigFromBase10(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 10) + return n +} + +// u is the BN parameter. +var u = bigFromBase10("4965661367192848881") + +// Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. +var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") + +// p2 is p, represented as little-endian 64-bit words. +var p2 = [4]uint64{0x3c208c16d87cfd47, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + +// np is the negative inverse of p, mod 2^256. +var np = [4]uint64{0x87d20782e4866389, 0x9ede7d651eca6ac9, 0xd8afcbd01833da80, 0xf57a22b791888c6b} + +// +// p = 21888242871839275222246405745257275088696311157297823662689037894645226208583; Fp = GF(p) +// r = Fp(2^256) # 6350874878119819312338956282401532409788428879151445726012394534686998597021 +// rInv = 1/r # 20988524275117001072002809824448087578619730785600314334253784976379291040311 +// hex(20988524275117001072002809824448087578619730785600314334253784976379291040311) +// # 2e67157159e5c639 cf63e9cfb74492d9 eb2022850278edf8 ed84884a014afa37 +// <\sage> +// +// rN1 is R^-1 where R = 2^256 mod p. +var rN1 = &gfP{0xed84884a014afa37, 0xeb2022850278edf8, 0xcf63e9cfb74492d9, 0x2e67157159e5c639} + +// +// r2 = r^2 # 3096616502983703923843567936837374451735540968419076528771170197431451843209 +// hex(3096616502983703923843567936837374451735540968419076528771170197431451843209) +// # 06d89f71cab8351f 47ab1eff0a417ff6 b5e71911d44501fb f32cfc5b538afa89 +// <\sage> +// +// r2 is R^2 where R = 2^256 mod p. +var r2 = &gfP{0xf32cfc5b538afa89, 0xb5e71911d44501fb, 0x47ab1eff0a417ff6, 0x06d89f71cab8351f} + +// r3 is R^3 where R = 2^256 mod p. +var r3 = &gfP{0xb1cd6dafda1530df, 0x62f210e6a7283db6, 0xef7f0b0c0ada0afb, 0x20fd6e902d592544} + +// +// xiToPMinus1Over6 = Fp2(i + 9) ^ ((p-1)/6); xiToPMinus1Over6 +// # 16469823323077808223889137241176536799009286646108169935659301613961712198316*i + 8376118865763821496583973867626364092589906065868298776909617916018768340080 +// <\sage> +// +// The value of `xiToPMinus1Over6` below is the same as the one obtained in sage, but where every field element is montgomery encoded +// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. +var xiToPMinus1Over6 = &gfP2{gfP{0xa222ae234c492d72, 0xd00f02a4565de15b, 0xdc2ff3a253dfc926, 0x10a75716b3899551}, gfP{0xaf9ba69633144907, 0xca6b1d7387afb78a, 0x11bded5ef08a2087, 0x02f34d751a1f3a7c}} + +// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. +var xiToPMinus1Over3 = &gfP2{gfP{0x6e849f1ea0aa4757, 0xaa1c7b6d89f89141, 0xb6e713cdfae0ca3a, 0x26694fbb4e82ebc3}, gfP{0xb5773b104563ab30, 0x347f91c8a9aa6454, 0x7a007127242e0991, 0x1956bcd8118214ec}} + +// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+9. +var xiToPMinus1Over2 = &gfP2{gfP{0xa1d77ce45ffe77c7, 0x07affd117826d1db, 0x6d16bd27bb7edc6b, 0x2c87200285defecc}, gfP{0xe4bbdd0c2936b629, 0xbb30f162e133bacb, 0x31a9d1b6f9645366, 0x253570bea500f8dd}} + +// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. +var xiToPSquaredMinus1Over3 = &gfP{0x3350c88e13e80b9c, 0x7dce557cdb5e56b9, 0x6001b4b8b615564a, 0x2682e617020217e0} + +// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). +var xiTo2PSquaredMinus2Over3 = &gfP{0x71930c11d782e155, 0xa6bb947cffbe3323, 0xaa303344d4741444, 0x2c3b3f0d26594943} + +// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). +var xiToPSquaredMinus1Over6 = &gfP{0xca8d800500fa1bf2, 0xf0c5d61468b39769, 0x0e201271ad0d4418, 0x04290f65bad856e6} + +// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. +var xiTo2PMinus2Over3 = &gfP2{gfP{0x5dddfd154bd8c949, 0x62cb29a5a4445b60, 0x37bc870a0c7dd2b9, 0x24830a9d3171f0fd}, gfP{0x7361d77f843abe92, 0xa5bb2bd3273411fb, 0x9c941f314b3e2399, 0x15df9cddbb9fd3ec}} diff --git a/cryptography/bn256/curve.go b/cryptography/bn256/curve.go new file mode 100644 index 0000000..d017915 --- /dev/null +++ b/cryptography/bn256/curve.go @@ -0,0 +1,318 @@ +package bn256 + +import ( + "math/big" +) + +// curvePoint implements the elliptic curve y²=x³+3. Points are kept in Jacobian +// form and t=z² when valid. G₁ is the set of points of this curve on GF(p). +type curvePoint struct { + x, y, z, t gfP +} + +var curveB = newGFp(3) + +// curveGen is the generator of G₁. +var curveGen = &curvePoint{ + x: *newGFp(1), + y: *newGFp(2), + z: *newGFp(1), + t: *newGFp(1), +} + +func (c *curvePoint) String() string { + c.MakeAffine() + x, y := &gfP{}, &gfP{} + montDecode(x, &c.x) + montDecode(y, &c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *curvePoint) Set(a *curvePoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *curvePoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP{}, &gfP{} + gfpMul(y2, &c.y, &c.y) + gfpMul(x3, &c.x, &c.x) + gfpMul(x3, x3, &c.x) + gfpAdd(x3, x3, curveB) + + return *y2 == *x3 +} + +func (c *curvePoint) SetInfinity() { + c.x = gfP{0} + c.y = *newGFp(1) + c.z = gfP{0} + c.t = gfP{0} +} + +func (c *curvePoint) IsInfinity() bool { + return c.z == gfP{0} +} + +func (c *curvePoint) Add(a, b *curvePoint) { + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + + // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2] + // by [u1:s1:z1·z2] and [u2:s2:z1·z2] + // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ + z12, z22 := &gfP{}, &gfP{} + gfpMul(z12, &a.z, &a.z) + gfpMul(z22, &b.z, &b.z) + + u1, u2 := &gfP{}, &gfP{} + gfpMul(u1, &a.x, z22) + gfpMul(u2, &b.x, z12) + + t, s1 := &gfP{}, &gfP{} + gfpMul(t, &b.z, z22) + gfpMul(s1, &a.y, t) + + s2 := &gfP{} + gfpMul(t, &a.z, z12) + gfpMul(s2, &b.y, t) + + // Compute x = (2h)²(s²-u1-u2) + // where s = (s2-s1)/(u2-u1) is the slope of the line through + // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below. + // This is also: + // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1) + // = r² - j - 2v + // with the notations below. + h := &gfP{} + gfpSub(h, u2, u1) + xEqual := *h == gfP{0} + + gfpAdd(t, h, h) + // i = 4h² + i := &gfP{} + gfpMul(i, t, t) + // j = 4h³ + j := &gfP{} + gfpMul(j, h, i) + + gfpSub(t, s2, s1) + yEqual := *t == gfP{0} + if xEqual && yEqual { + c.Double(a) + return + } + r := &gfP{} + gfpAdd(r, t, t) + + v := &gfP{} + gfpMul(v, u1, i) + + // t4 = 4(s2-s1)² + t4, t6 := &gfP{}, &gfP{} + gfpMul(t4, r, r) + gfpAdd(t, v, v) + gfpSub(t6, t4, j) + + gfpSub(&c.x, t6, t) + + // Set y = -(2h)³(s1 + s*(x/4h²-u1)) + // This is also + // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j + gfpSub(t, v, &c.x) // t7 + gfpMul(t4, s1, j) // t8 + gfpAdd(t6, t4, t4) // t9 + gfpMul(t4, r, t) // t10 + gfpSub(&c.y, t4, t6) + + // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2 + gfpAdd(t, &a.z, &b.z) // t11 + gfpMul(t4, t, t) // t12 + gfpSub(t, t4, z12) // t13 + gfpSub(t4, t, z22) // t14 + gfpMul(&c.z, t4, h) +} + +func (c *curvePoint) Double(a *curvePoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A, B, C := &gfP{}, &gfP{}, &gfP{} + gfpMul(A, &a.x, &a.x) + gfpMul(B, &a.y, &a.y) + gfpMul(C, B, B) + + t, t2 := &gfP{}, &gfP{} + gfpAdd(t, &a.x, B) + gfpMul(t2, t, t) + gfpSub(t, t2, A) + gfpSub(t2, t, C) + + d, e, f := &gfP{}, &gfP{}, &gfP{} + gfpAdd(d, t2, t2) + gfpAdd(t, A, A) + gfpAdd(e, t, A) + gfpMul(f, e, e) + + gfpAdd(t, d, d) + gfpSub(&c.x, f, t) + + gfpAdd(t, C, C) + gfpAdd(t2, t, t) + gfpAdd(t, t2, t2) + gfpSub(&c.y, d, &c.x) + gfpMul(t2, e, &c.y) + gfpSub(&c.y, t2, t) + + gfpMul(t, &a.y, &a.z) + gfpAdd(&c.z, t, t) +} + +func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int) { + precomp := [1 << 2]*curvePoint{nil, {}, {}, {}} + precomp[1].Set(a) + precomp[2].Set(a) + gfpMul(&precomp[2].x, &precomp[2].x, xiTo2PSquaredMinus2Over3) + precomp[3].Add(precomp[1], precomp[2]) + + multiScalar := curveLattice.Multi(scalar) + + sum := &curvePoint{} + sum.SetInfinity() + t := &curvePoint{} + + for i := len(multiScalar) - 1; i >= 0; i-- { + t.Double(sum) + if multiScalar[i] == 0 { + sum.Set(t) + } else { + sum.Add(t, precomp[multiScalar[i]]) + } + } + c.Set(sum) +} + +// Transforms Jacobian coordinates to Affine coordinates +// (X' : Y' : Z) -> (X'/(Z^2) : Y'/(Z^3) : 1) +func (c *curvePoint) MakeAffine() { + // point0 := *newGFp(0) + // point1 := *newGFp(1) + + if c.z == point1 { + return + } else if c.z == point0 { // return point at infinity if z = 0 + c.x = gfP{0} + c.y = point1 + c.t = gfP{0} + return + } + + zInv := &gfP{} + zInv.Invert(&c.z) + + t, zInv2 := &gfP{}, &gfP{} + gfpMul(t, &c.y, zInv) // t = y/z + gfpMul(zInv2, zInv, zInv) // zInv2 = 1/(z^2) + + gfpMul(&c.x, &c.x, zInv2) // x = x/(z^2) + gfpMul(&c.y, t, zInv2) // y = y/(z^3) + + c.z = point1 + c.t = point1 +} + +func (c *curvePoint) Neg(a *curvePoint) { + c.x.Set(&a.x) + gfpNeg(&c.y, &a.y) + c.z.Set(&a.z) + c.t = gfP{0} +} + +var point0 = *newGFp(0) +var point1 = *newGFp(1) + +// this will do batch inversions and thus optimize lookup table generation +// Montgomery Batch Inversion based trick +type G1Array []*G1 + +func (points G1Array) MakeAffine() { + // point0 := *newGFp(0) + // point1 := *newGFp(1) + + accum := newGFp(1) + + var scratch_backup [256]gfP + + var scratch []gfP + if len(points) <= 256 { + scratch = scratch_backup[:0] // avoid allocation is possible + } + for _, e := range points { + if e.p == nil { + e.p = &curvePoint{} + } + scratch = append(scratch, *accum) + if e.p.z == point1 { + continue + } else if e.p.z == point0 { // return point at infinity if z = 0 + e.p.x = gfP{0} + e.p.y = point1 + e.p.t = gfP{0} + continue + } + + gfpMul(accum, accum, &e.p.z) // accum *= z + + /* + zInv := &gfP{} + zInv.Invert(&e.p.z) + fmt.Printf("%d inv %s\n",i, zInv) + */ + } + + zInv_accum := gfP{} + zInv_accum.Invert(accum) + + tmp := gfP{} + zInv := &gfP{} + + for i := len(points) - 1; i >= 0; i-- { + e := points[i] + + if e.p.z == point1 { + continue + } else if e.p.z == point0 { // return point at infinity if z = 0 + continue + } + + tmp = gfP{} + gfpMul(&tmp, &zInv_accum, &e.p.z) + gfpMul(zInv, &zInv_accum, &scratch[i]) + zInv_accum = tmp + // fmt.Printf("%d inv %s\n",i, zInv) + + t, zInv2 := &gfP{}, &gfP{} + gfpMul(t, &e.p.y, zInv) // t = y/z + gfpMul(zInv2, zInv, zInv) // zInv2 = 1/(z^2) + + gfpMul(&e.p.x, &e.p.x, zInv2) // x = x/(z^2) + gfpMul(&e.p.y, t, zInv2) // y = y/(z^3) + + e.p.z = point1 + e.p.t = point1 + } +} diff --git a/cryptography/bn256/curve_test.go b/cryptography/bn256/curve_test.go new file mode 100644 index 0000000..3f4f884 --- /dev/null +++ b/cryptography/bn256/curve_test.go @@ -0,0 +1,66 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestG1Array(t *testing.T) { + count := 8 + + var g1array G1Array + var g1array_opt G1Array + + for i := 0; i < count; i++ { + a, _ := rand.Int(rand.Reader, Order) + g1array = append(g1array, new(G1).ScalarBaseMult(a)) + g1array_opt = append(g1array_opt, new(G1).ScalarBaseMult(a)) + } + g1array_opt.MakeAffine() + for i := range g1array_opt { + require.Equal(t, g1array_opt[i].p.z, *newGFp(1)) // current we are not testing points of infinity + } +} + +func benchmarksingleinverts(count int, b *testing.B) { + var g1array, g1backup G1Array + + for i := 0; i < count; i++ { + a, _ := rand.Int(rand.Reader, Order) + g1backup = append(g1backup, new(G1).ScalarBaseMult(a)) + } + + for n := 0; n < b.N; n++ { + g1array = g1array[:0] + for i := range g1backup { + g1array = append(g1array, new(G1).Set(g1backup[i])) + g1array[i].p.MakeAffine() + } + } +} + +func benchmarkbatchedinverts(count int, b *testing.B) { + var g1array, g1backup G1Array + + for i := 0; i < count; i++ { + a, _ := rand.Int(rand.Reader, Order) + g1backup = append(g1backup, new(G1).ScalarBaseMult(a)) + } + + for n := 0; n < b.N; n++ { + g1array = g1array[:0] + for i := range g1backup { + g1array = append(g1array, new(G1).Set(g1backup[i])) + } + g1array.MakeAffine() + } +} + +func BenchmarkInverts_Single_256(b *testing.B) { benchmarksingleinverts(256, b) } +func BenchmarkInverts_Batched_256(b *testing.B) { benchmarkbatchedinverts(256, b) } diff --git a/cryptography/bn256/example_test.go b/cryptography/bn256/example_test.go new file mode 100644 index 0000000..6c28599 --- /dev/null +++ b/cryptography/bn256/example_test.go @@ -0,0 +1,51 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExamplePair(t *testing.T) { + // This implements the tripartite Diffie-Hellman algorithm from "A One + // Round Protocol for Tripartite Diffie-Hellman", A. Joux. + // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf + + // Each of three parties, a, b and c, generate a private value. + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + // Then each party calculates g₁ and g₂ times their private value. + pa := new(G1).ScalarBaseMult(a) + qa := new(G2).ScalarBaseMult(a) + + pb := new(G1).ScalarBaseMult(b) + qb := new(G2).ScalarBaseMult(b) + + pc := new(G1).ScalarBaseMult(c) + qc := new(G2).ScalarBaseMult(c) + + // Now each party exchanges its public values with the other two and + // all parties can calculate the shared key. + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + + // k1, k2 and k3 will all be equal. + + require.Equal(t, k1, k2) + require.Equal(t, k1, k3) + + require.Equal(t, len(np), 4) //Avoid gometalinter varcheck err on np +} diff --git a/cryptography/bn256/g1_serialization.go b/cryptography/bn256/g1_serialization.go new file mode 100644 index 0000000..c508984 --- /dev/null +++ b/cryptography/bn256/g1_serialization.go @@ -0,0 +1,424 @@ +// Package bn256 implements a particular bilinear group at the 128-bit security +// level. +// +// Bilinear groups are the basis of many of the new cryptographic protocols that +// have been proposed over the past decade. They consist of a triplet of groups +// (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ +// is a generator of the respective group). That function is called a pairing +// function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible +// with the implementation described in that paper. +package bn256 + +// This file implement some util functions for the MPC +// especially the serialization and deserialization functions for points in G1 +import ( + "errors" + "math/big" +) + +// Constants related to the bn256 pairing friendly curve +const ( + FqElementSize = 32 + G1CompressedSize = FqElementSize + 1 // + 1 accounts for the additional byte used for masking + G1UncompressedSize = 2*FqElementSize + 1 // + 1 accounts for the additional byte used for masking +) + +// https://github.com/ebfull/pairing/tree/master/src/bls12_381#serialization +// Bytes used to detect the formatting. By reading the first byte of the encoded point we can know it's nature +// ie: we can know if the point is the point at infinity, if it is encoded uncompressed or if it is encoded compressed +// Bit masking used to detect the serialization of the points and their nature +// +// The BSL12-381 curve is built over a 381-bit prime field. +// Thus each point coordinate is represented over 381 bits = 47bytes + 5bits +// Thus, to represent a point we need to have 48bytes, but the last 3 bits of the 48th byte will be set to 0 +// These are these bits that are used to implement the masking, hence why the masking proposed by ebfull was: +const ( + serializationMask = (1 << 5) - 1 // 0001 1111 // Enable to pick the 3 MSB corresponding to the serialization flag + serializationCompressed = 1 << 7 // 1000 0000 + serializationInfinity = 1 << 6 // 0100 0000 + serializationBigY = 1 << 5 // 0010 0000 +) + +// IsHigherY is used to distinguish between the 2 points of E +// that have the same x-coordinate +// The point e is assumed to be given in the affine form +func (e *G1) IsHigherY() bool { + // Check nil pointers + if e.p == nil { + e.p = &curvePoint{} + } + + var yCoord gfP + //yCoord.Set(&e.p.y) + yCoord = e.p.y + + var yCoordNeg gfP + gfpNeg(&yCoordNeg, &yCoord) + + res := gfpCmp(&yCoord, &yCoordNeg) + if res == 1 { // yCoord > yCoordNeg + return true + } else if res == -1 { + return false + } + + return false +} + +// EncodeCompressed converts the compressed point e into bytes +// This function takes a point in the Jacobian form +// This function does not modify the point e +// (the variable `temp` is introduced to avoid to modify e) +func (e *G1) EncodeCompressed() []byte { + // Check nil pointers + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + ret := make([]byte, G1CompressedSize) + + // Flag the encoding with the compressed flag + ret[0] |= serializationCompressed + + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return ret + } + + if e.IsHigherY() { + // Flag the encoding with the bigY flag + ret[0] |= serializationBigY + } + + // We start the serializagtion of the coordinates at the index 1 + // Since the index 0 in the `ret` corresponds to the masking + temp := &gfP{} + montDecode(temp, &e.p.x) + temp.Marshal(ret[1:]) + + return ret +} + +// returns to buffer rather than allocation from GC +func (e *G1) EncodeCompressedToBuf(ret []byte) { + // Check nil pointers + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + //ret := make([]byte, G1CompressedSize) + + // Flag the encoding with the compressed flag + ret[0] |= serializationCompressed + + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return + } + + if e.IsHigherY() { + // Flag the encoding with the bigY flag + ret[0] |= serializationBigY + } + + // We start the serializagtion of the coordinates at the index 1 + // Since the index 0 in the `ret` corresponds to the masking + temp := &gfP{} + montDecode(temp, &e.p.x) + temp.Marshal(ret[1:]) + + return +} + +// EncodeUncompressed converts the compressed point e into bytes +// Take a point P in Jacobian form (where each coordinate is MontEncoded) +// and encodes it by going back to affine coordinates and montDecode all coordinates +// This function does not modify the point e +// (the variable `temp` is introduced to avoid to modify e) +/* +func (e *G1) EncodeUncompressed() []byte { + // Check nil pointers + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + ret := make([]byte, G1UncompressedSize) + + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return ret + } + + // We start the serialization of the coordinates at the index 1 + // Since the index 0 in the `ret` corresponds to the masking + temp := &gfP{} + montDecode(temp, &e.p.x) // Store the montgomery decoding in temp + temp.Marshal(ret[1:33]) // Write temp in the `ret` slice, this is the x-coordinate + montDecode(temp, &e.p.y) + temp.Marshal(ret[33:]) // this is the y-coordinate + + return ret +} +*/ +func (e *G1) EncodeUncompressed() []byte { + // Check nil pointers + if e.p == nil { + e.p = &curvePoint{} + } + + // Set the right flags + ret := make([]byte, G1UncompressedSize) + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return ret + } + + // Marshal + marshal := e.Marshal() + // The encoding = flags || marshalledPoint + copy(ret[1:], marshal) + + return ret +} + +// Takes a MontEncoded x and finds the corresponding y (one of the two possible y's) +func getYFromMontEncodedX(x *gfP) (*gfP, error) { + // Check nil pointers + if x == nil { + return nil, errors.New("Cannot retrieve the y-coordinate form a nil pointer") + } + + // Operations on montgomery encoded field elements + x2 := &gfP{} + gfpMul(x2, x, x) + + x3 := &gfP{} + gfpMul(x3, x2, x) + + rhs := &gfP{} + gfpAdd(rhs, x3, curveB) // curveB is MontEncoded, since it is create with newGFp + + // Montgomery decode rhs + // Needed because when we create a GFp element + // with gfP{}, then it is not montEncoded. However + // if we create an element of GFp by using `newGFp()` + // then this field element is Montgomery encoded + // Above, we have been working on Montgomery encoded field elements + // here we solve the quad. resid. over F (not encoded) + // and then we encode back and return the encoded result + // + // Eg: + // - Px := &gfP{1} => 0000000000000000000000000000000000000000000000000000000000000001 + // - PxNew := newGFp(1) => 0e0a77c19a07df2f666ea36f7879462c0a78eb28f5c70b3dd35d438dc58f0d9d + montDecode(rhs, rhs) + rhsBig, err := rhs.gFpToBigInt() + if err != nil { + return nil, err + } + + // Note, if we use the ModSqrt method, we don't need the exponent, so we can comment these lines + yCoord := big.NewInt(0) + res := yCoord.ModSqrt(rhsBig, P) + if res == nil { + return nil, errors.New("not a square mod P") + } + + yCoordGFp := newGFpFromBigInt(yCoord) + montEncode(yCoordGFp, yCoordGFp) + + return yCoordGFp, nil +} + +// DecodeCompressed decodes a point in the compressed form +// Take a point P encoded (ie: written in affine form where each coordinate is MontDecoded) +// and encodes it by going back to Jacobian coordinates and montEncode all coordinates +func (e *G1) DecodeCompressed(encoding []byte) error { + if len(encoding) != G1CompressedSize { + return errors.New("wrong encoded point size") + } + if encoding[0]&serializationCompressed == 0 { // Also test the length of the encoding to make sure it is 33bytes + return errors.New("point isn't compressed") + } + + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &curvePoint{} + } + { + e.p.x, e.p.y = gfP{0}, gfP{0} + e.p.z, e.p.t = *newGFp(1), *newGFp(1) + } + + // Removes the bits of the masking (This does a bitwise AND with `0001 1111`) + // And thus removes the first 3 bits corresponding to the masking + bin := make([]byte, G1CompressedSize) + copy(bin, encoding) + bin[0] &= serializationMask + + // Decode the point at infinity in the compressed form + if encoding[0]&serializationInfinity != 0 { + if encoding[0]&serializationBigY != 0 { + return errors.New("high Y bit improperly set") + } + + // Similar to `for i:=0; i 0 { // GaNeg.p.y > Ga.p.y + assert.True(t, GaNeg.IsHigherY(), "GaNeg.IsHigherY should be true if GaNeg.p.y > Ga.p.y") + // Test the comparision of the big int also, should be the same result + assert.Equal(t, GaNegYBig.Cmp(GaYBig), 1, "GaNegYBig should be bigger than GaYBig") + } else if res < 0 { // GaNeg.p.y < Ga.p.y + assert.False(t, GaNeg.IsHigherY(), "GaNeg.IsHigherY should be false if GaNeg.p.y < Ga.p.y") + // Test the comparision of the big int also, should be the same result + assert.Equal(t, GaYBig.Cmp(GaNegYBig), 1, "GaYBig should be bigger than GaNegYBig") + } +} + +func TestGetYFromMontEncodedX(t *testing.T) { + // We know that the generator of the curve is P = (x: 1, y: 2, z: 1, t: 1) + // We take x = 1 and we see if we retrieve P such that y = 2 or -P such that y' = Inv(2) + + // Create the GFp element 1 and MontEncode it + PxMontEncoded := newGFp(1) + yRetrieved, err := getYFromMontEncodedX(PxMontEncoded) + assert.Nil(t, err) + + smallYMontEncoded := newGFp(2) + bigYMontEncoded := &gfP{} + gfpNeg(bigYMontEncoded, smallYMontEncoded) + + testCondition := (*yRetrieved == *smallYMontEncoded) || (*yRetrieved == *bigYMontEncoded) + assert.True(t, testCondition, "The retrieved Y should either equal 2 or Inv(2)") +} + +func TestEncodeUncompressed(t *testing.T) { + // Case1: Create random point (Jacobian form) + _, GaInit, err := RandomG1(rand.Reader) + if err != nil { + t.Fatal(err) + } + + // Affine form of GaInit + GaAffine := new(G1) + GaAffine.Set(GaInit) + GaAffine.p.MakeAffine() + + // Encode GaCopy1 with the EncodeUncompress function + GaCopy1 := new(G1) + GaCopy1.Set(GaInit) + encoded := GaCopy1.EncodeUncompressed() + + // Encode GaCopy2 with the Marshal function + GaCopy2 := new(G1) + GaCopy2.Set(GaInit) + marshalled := GaCopy2.Marshal() // Careful Marshal modifies the point since it makes it an affine point! + + // Make sure that the x-coordinate is encoded as it is when we call the Marshal function + assert.Equal( + t, + encoded[1:], // Ignore the masking byte + marshalled[:], + "The EncodeUncompressed and Marshal function yield different results") + + // Unmarshal the point Ga with the unmarshal function + Gb1 := new(G1) + _, err = Gb1.Unmarshal(marshalled) + assert.Nil(t, err) + assert.Equal(t, GaAffine.p.x.String(), Gb1.p.x.String(), "The x-coord of the unmarshalled point should equal the x-coord of the intial point") + assert.Equal(t, GaAffine.p.y.String(), Gb1.p.y.String(), "The y-coord of the unmarshalled point should equal the y-coord of the intial point") + + // Decode the point Ga with the decodeUncompress function + Gb2 := new(G1) + err = Gb2.DecodeUncompressed(encoded) + assert.Nil(t, err) + assert.Equal(t, GaAffine.p.x.String(), Gb2.p.x.String(), "The x-coord of the decoded point should equal the x-coord of the intial point") + assert.Equal(t, GaAffine.p.y.String(), Gb2.p.y.String(), "The y-coord of the decoded point should equal the y-coord of the intial point") + + // Case2: Encode the point at infinity + GInfinity := new(G1) + GInfinity.p = &curvePoint{} + GInfinity.p.SetInfinity() + + // Get the point in affine form + GInfinityAffine := new(G1) + GInfinityAffine.Set(GInfinity) + GInfinityAffine.p.MakeAffine() + + // Encode GaCopy1 with the EncodeUncompress function + GInfinityCopy1 := new(G1) + GInfinityCopy1.Set(GInfinity) + encoded = GInfinityCopy1.EncodeUncompressed() + + // Encode GaCopy2 with the Marshal function + GInfinityCopy2 := new(G1) + GInfinityCopy2.Set(GInfinity) + marshalled = GInfinityCopy2.Marshal() // Careful Marshal modifies the point since it makes it an affine point! + + // Make sure that the x-coordinate is encoded as it is when we call the Marshal function + assert.Equal( + t, + encoded[1:], // Ignore the masking byte + marshalled[:], + "The EncodeUncompressed and Marshal function yield different results") + + // Unmarshal the point Ga with the unmarshal function + Gb1 = new(G1) + _, err = Gb1.Unmarshal(marshalled) + assert.Nil(t, err) + assert.Equal(t, GInfinityAffine.p.x.String(), Gb1.p.x.String(), "The x-coord of the unmarshalled point should equal the x-coord of the intial point") + assert.Equal(t, GInfinityAffine.p.y.String(), Gb1.p.y.String(), "The y-coord of the unmarshalled point should equal the y-coord of the intial point") + + // Decode the point Ga with the decodeCompress function + Gb2 = new(G1) + err = Gb2.DecodeUncompressed(encoded) + assert.Nil(t, err) + assert.Equal(t, GInfinityAffine.p.x.String(), Gb2.p.x.String(), "The x-coord of the decompressed point should equal the x-coord of the intial point") + assert.Equal(t, GInfinityAffine.p.y.String(), Gb2.p.y.String(), "The y-coord of the decompressed point should equal the y-coord of the intial point") +} diff --git a/cryptography/bn256/g2_serialization.go b/cryptography/bn256/g2_serialization.go new file mode 100644 index 0000000..b020933 --- /dev/null +++ b/cryptography/bn256/g2_serialization.go @@ -0,0 +1,266 @@ +// Package bn256 implements a particular bilinear group at the 128-bit security +// level. +// +// Bilinear groups are the basis of many of the new cryptographic protocols that +// have been proposed over the past decade. They consist of a triplet of groups +// (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ +// is a generator of the respective group). That function is called a pairing +// function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible +// with the implementation described in that paper. +package bn256 + +import ( + "errors" +) + +// This file implement some util functions for the MPC +// especially the serialization and deserialization functions for points in G1 + +// Constants related to the bn256 pairing friendly curve +const ( + Fq2ElementSize = 2 * FqElementSize + G2CompressedSize = Fq2ElementSize + 1 // + 1 accounts for the additional byte used for masking + G2UncompressedSize = 2*Fq2ElementSize + 1 // + 1 accounts for the additional byte used for masking +) + +// EncodeUncompressed converts the compressed point e into bytes +// Take a point P in Jacobian form (where each coordinate is MontEncoded) +// and encodes it by going back to affine coordinates and montDecode all coordinates +// This function does not modify the point e +// (the variable `temp` is introduced to avoid to modify e) +func (e *G2) EncodeUncompressed() []byte { + // Check nil pointers + if e.p == nil { + e.p = &twistPoint{} + } + + // Set the right flags + ret := make([]byte, G2UncompressedSize) + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return ret + } + + // Marshal + marshal := e.Marshal() + // The encoding = flags || marshalledPoint + copy(ret[1:], marshal) + + return ret +} + +// DecodeUncompressed decodes a point in the uncompressed form +// Take a point P encoded (ie: written in affine form where each coordinate is MontDecoded) +// and encodes it by going back to Jacobian coordinates and montEncode all coordinates +func (e *G2) DecodeUncompressed(encoding []byte) error { + if len(encoding) != G2UncompressedSize { + return errors.New("wrong encoded point size") + } + if encoding[0]&serializationCompressed != 0 { // Also test the length of the encoding to make sure it is 65bytes + return errors.New("point is compressed") + } + if encoding[0]&serializationBigY != 0 { // Also test that the bigY flag if not set + return errors.New("bigY flag should not be set") + } + + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &twistPoint{} + } + + // Removes the bits of the masking (This does a bitwise AND with `0001 1111`) + // And thus removes the first 3 bits corresponding to the masking + // Useless for now because in bn256, we added a full byte to enable masking + // However, this is needed if we work over BLS12 and its underlying field + bin := make([]byte, G2UncompressedSize) + copy(bin, encoding) + bin[0] &= serializationMask + + // Decode the point at infinity in the compressed form + if encoding[0]&serializationInfinity != 0 { + // Makes sense to check that all bytes of bin are 0x0 since we removed the masking above} + for i := range bin { + if bin[i] != 0 { + return errors.New("invalid infinity encoding") + } + } + e.p.SetInfinity() + return nil + } + + // We remove the flags and unmarshal the data + _, err := e.Unmarshal(encoding[1:]) + return err +} + +func (e *G2) IsHigherY() bool { + // Check nil pointers + if e.p == nil { + e.p = &twistPoint{} + e.p.MakeAffine() + } + + // Note: the structures attributes are quite confusing here + // In fact, each element of Fp2 is a polynomial with 2 terms + // the `x` and `y` denote these coefficients, ie: xi + y + // However, `x` and `y` are also used to denote the x and y **coordinates** + // of an elliptic curve point. Hence, e.p.y represents the y-coordinate of the + // point e, and e.p.y.y represents the **coefficient** y of the y-coordinate + // of the elliptic curve point e. + // + // TODO: Rename the coefficients of the elements of Fp2 as c0 and c1 to clarify the code + yCoordY := &gfP{} + yCoordY.Set(&e.p.y.y) + yCoordYNeg := &gfP{} + gfpNeg(yCoordYNeg, yCoordY) + + res := gfpCmp(yCoordY, yCoordYNeg) + if res == 1 { // yCoordY > yCoordNegY + return true + } else if res == -1 { + return false + } + + return false +} + +func (e *G2) EncodeCompressed() []byte { + // Check nil pointers + if e.p == nil { + e.p = &twistPoint{} + } + + e.p.MakeAffine() + ret := make([]byte, G2CompressedSize) + + // Flag the encoding with the compressed flag + ret[0] |= serializationCompressed + + if e.p.IsInfinity() { + // Flag the encoding with the infinity flag + ret[0] |= serializationInfinity + return ret + } + + if e.IsHigherY() { + // Flag the encoding with the bigY flag + ret[0] |= serializationBigY + } + + // We start the serialization of the coordinates at the index 1 + // Since the index 0 in the `ret` corresponds to the masking + // + // `temp` contains the the x-coordinate of the point + // Thus, to fully encode `temp`, we need to Marshal it's x coefficient and y coefficient + temp := gfP2Decode(&e.p.x) + temp.x.Marshal(ret[1:]) + temp.y.Marshal(ret[FqElementSize+1:]) + + return ret +} + +// Takes a MontEncoded x and finds the corresponding y (one of the two possible y's) +func getYFromMontEncodedXG2(x *gfP2) (*gfP2, error) { + // Check nil pointers + if x == nil { + return nil, errors.New("Cannot retrieve the y-coordinate from a nil pointer") + } + + x2 := new(gfP2).Mul(x, x) + x3 := new(gfP2).Mul(x2, x) + rhs := new(gfP2).Add(x3, twistB) // twistB is MontEncoded, since it is create with newGFp + + yCoord, err := rhs.Sqrt() + if err != nil { + return nil, err + } + + return yCoord, nil +} + +// DecodeCompressed decodes a point in the compressed form +// Take a point P in G2 decoded (ie: written in affine form where each coordinate is MontDecoded) +// and encodes it by going back to Jacobian coordinates and montEncode all coordinates +func (e *G2) DecodeCompressed(encoding []byte) error { + if len(encoding) != G2CompressedSize { + return errors.New("wrong encoded point size") + } + if encoding[0]&serializationCompressed == 0 { // Also test the length of the encoding to make sure it is 33bytes + return errors.New("point isn't compressed") + } + + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &twistPoint{} + } else { + e.p.x.SetZero() + e.p.y.SetZero() + e.p.z.SetOne() + e.p.t.SetOne() + } + + // Removes the bits of the masking (This does a bitwise AND with `0001 1111`) + // And thus removes the first 3 bits corresponding to the masking + bin := make([]byte, G2CompressedSize) + copy(bin, encoding) + bin[0] &= serializationMask + + // Decode the point at infinity in the compressed form + if encoding[0]&serializationInfinity != 0 { + if encoding[0]&serializationBigY != 0 { + return errors.New("high Y bit improperly set") + } + + // Similar to `for i:=0; i= 0 { + out = &gfP{uint64(x)} + } else { + out = &gfP{uint64(-x)} + gfpNeg(out, out) + } + + montEncode(out, out) + return out +} + +func (e *gfP) String() string { + return fmt.Sprintf("%16.16x%16.16x%16.16x%16.16x", e[3], e[2], e[1], e[0]) +} + +/* +func byteToUint64(in []byte) (uint64, error) { + if len(in) > 8 { + return 0, errors.New("the input bytes length should be equal to 8 (or smaller)") + } + + // Takes the bytes in the little endian order + // The byte 0x64 translate in a uint64 of the shape 0x64 (= 0x0000000000000064) rather than 0x6400000000000000 + res := binary.LittleEndian.Uint64(in) + return res, nil +} +*/ + +// Makes sure that the +func padBytes(bb []byte) ([]byte, error) { + if len(bb) > 32 { + return []byte{}, errors.New("Cannot pad the given byte slice as the length exceed the padding length") + } + + if len(bb) == 32 { + return bb, nil + } + + padSlice := make([]byte, 32) + index := len(padSlice) - len(bb) + copy(padSlice[index:], bb) + return padSlice, nil +} + +// Convert a big.Int into gfP +func newGFpFromBigInt(in *big.Int) (out *gfP) { + // in >= P, so we mod it to get back in the field + // (ie: we get the smallest representative of the equivalence class mod P) + if res := in.Cmp(P); res >= 0 { + // We need to mod P to get back into the field + in.Mod(in, P) + } + + inBytes := in.Bytes() + // We want to work on byte slices of length 32 to re-assemble our GFpe element + if len(inBytes) < 32 { + // Safe to ignore the err as we are in the if so the condition is satisfied + inBytes, _ = padBytes(inBytes) + } + + out = &gfP{} + var n uint64 + // Now we have the guarantee that inBytes has length 32 so it makes sense to run this for + // loop safely (we won't exceed the boundaries of the container) + for i := 0; i < FpUint64Size; i++ { + buf := bytes.NewBuffer(inBytes[i*8 : (i+1)*8]) + binary.Read(buf, binary.BigEndian, &n) + out[(FpUint64Size-1)-i] = n // In gfP field elements are represented as little-endian 64-bit words + } + + return out +} + +// Returns a new element of GFp montgomery encoded +func newMontEncodedGFpFromBigInt(in *big.Int) *gfP { + res := newGFpFromBigInt(in) + montEncode(res, res) + + return res +} + +// Convert a gfP into a big.Int +func (e *gfP) gFpToBigInt() (*big.Int, error) { + str := e.String() + + out := new(big.Int) + _, ok := out.SetString(str, 16) + if !ok { + return nil, errors.New("couldn't create big.Int from gfP element") + } + + return out, nil +} + +func (e *gfP) Set(f *gfP) { + e[0] = f[0] + e[1] = f[1] + e[2] = f[2] + e[3] = f[3] +} + +func (e *gfP) Invert(f *gfP) { + bits := [4]uint64{0x3c208c16d87cfd45, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + + sum, power := &gfP{}, &gfP{} + sum.Set(rN1) + power.Set(f) + + for word := 0; word < 4; word++ { + for bit := uint(0); bit < 64; bit++ { + if (bits[word]>>bit)&1 == 1 { + gfpMul(sum, sum, power) + } + gfpMul(power, power, power) + } + } + + gfpMul(sum, sum, r3) + e.Set(sum) +} + +func (e *gfP) Marshal(out []byte) { + for w := uint(0); w < 4; w++ { + for b := uint(0); b < 8; b++ { + out[8*w+b] = byte(e[3-w] >> (56 - 8*b)) + } + } +} + +func (e *gfP) Unmarshal(in []byte) error { + // Unmarshal the bytes into little endian form + for w := uint(0); w < 4; w++ { + for b := uint(0); b < 8; b++ { + e[3-w] += uint64(in[8*w+b]) << (56 - 8*b) + } + } + // Ensure the point respects the curve modulus + for i := 3; i >= 0; i-- { + if e[i] < p2[i] { + return nil + } + if e[i] > p2[i] { + return errors.New("bn256: coordinate exceeds modulus") + } + } + return errors.New("bn256: coordinate equals modulus") +} + +// Note: This function is only used to distinguish between points with the same x-coordinates +// when doing point compression. +// An ordered field must be infinite and we are working over a finite field here +func gfpCmp(a, b *gfP) int { + for i := FpUint64Size - 1; i >= 0; i-- { // Remember that the gfP elements are written as little-endian 64-bit words + if a[i] > b[i] { // As soon as we figure out that the MSByte of A > MSByte of B, we return + return 1 + } else if a[i] == b[i] { // If the current bytes are equal we continue as we cannot conclude on A and B relation + continue + } else { // a[i] < b[i] so we can directly conclude and we return + return -1 + } + } + + return 0 +} + +// In Montgomery representation, an element x is represented by xR mod p, where +// R is a power of 2 corresponding to the number of machine-words that can contain p. +// (where p is the characteristic of the prime field we work over) +// See: https://web.wpi.edu/Pubs/ETD/Available/etd-0430102-120529/unrestricted/thesis.pdf +func montEncode(c, a *gfP) { gfpMul(c, a, r2) } +func montDecode(c, a *gfP) { gfpMul(c, a, &gfP{1}) } diff --git a/cryptography/bn256/gfp12.go b/cryptography/bn256/gfp12.go new file mode 100644 index 0000000..287b8be --- /dev/null +++ b/cryptography/bn256/gfp12.go @@ -0,0 +1,160 @@ +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP12 implements the field of size p¹² as a quadratic extension of gfP6 +// where ω²=τ. +type gfP12 struct { + x, y gfP6 // value is xω + y +} + +func (e *gfP12) String() string { + return "(" + e.x.String() + "," + e.y.String() + ")" +} + +func (e *gfP12) Set(a *gfP12) *gfP12 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) SetZero() *gfP12 { + e.x.SetZero() + e.y.SetZero() + return e +} + +func (e *gfP12) SetOne() *gfP12 { + e.x.SetZero() + e.y.SetOne() + return e +} + +func (e *gfP12) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() +} + +func (e *gfP12) IsOne() bool { + return e.x.IsZero() && e.y.IsOne() +} + +func (e *gfP12) Conjugate(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) Neg(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + return e +} + +// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p +func (e *gfP12) Frobenius(a *gfP12) *gfP12 { + e.x.Frobenius(&a.x) + e.y.Frobenius(&a.y) + e.x.MulScalar(&e.x, xiToPMinus1Over6) + return e +} + +// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² +func (e *gfP12) FrobeniusP2(a *gfP12) *gfP12 { + e.x.FrobeniusP2(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over6) + e.y.FrobeniusP2(&a.y) + return e +} + +func (e *gfP12) FrobeniusP4(a *gfP12) *gfP12 { + e.x.FrobeniusP4(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over3) + e.y.FrobeniusP4(&a.y) + return e +} + +func (e *gfP12) Add(a, b *gfP12) *gfP12 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + return e +} + +func (e *gfP12) Sub(a, b *gfP12) *gfP12 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + return e +} + +func (e *gfP12) Mul(a, b *gfP12) *gfP12 { + tx := (&gfP6{}).Mul(&a.x, &b.y) + t := (&gfP6{}).Mul(&b.x, &a.y) + tx.Add(tx, t) + + ty := (&gfP6{}).Mul(&a.y, &b.y) + t.Mul(&a.x, &b.x).MulTau(t) + + e.x.Set(tx) + e.y.Add(ty, t) + return e +} + +func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 { + e.x.Mul(&e.x, b) + e.y.Mul(&e.y, b) + return e +} + +func (e *gfP12) Exp(a *gfP12, power *big.Int) *gfP12 { + sum := (&gfP12{}).SetOne() + t := &gfP12{} + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum) + if power.Bit(i) != 0 { + sum.Mul(t, a) + } else { + sum.Set(t) + } + } + + e.Set(sum) + return e +} + +func (e *gfP12) Square(a *gfP12) *gfP12 { + // Complex squaring algorithm + v0 := (&gfP6{}).Mul(&a.x, &a.y) + + t := (&gfP6{}).MulTau(&a.x) + t.Add(&a.y, t) + ty := (&gfP6{}).Add(&a.x, &a.y) + ty.Mul(ty, t).Sub(ty, v0) + t.MulTau(v0) + ty.Sub(ty, t) + + e.x.Add(v0, v0) + e.y.Set(ty) + return e +} + +func (e *gfP12) Invert(a *gfP12) *gfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP6{}, &gfP6{} + + t1.Square(&a.x) + t2.Square(&a.y) + t1.MulTau(t1).Sub(t2, t1) + t2.Invert(t1) + + e.x.Neg(&a.x) + e.y.Set(&a.y) + e.MulScalar(e, t2) + return e +} diff --git a/cryptography/bn256/gfp2.go b/cryptography/bn256/gfp2.go new file mode 100644 index 0000000..766b177 --- /dev/null +++ b/cryptography/bn256/gfp2.go @@ -0,0 +1,327 @@ +package bn256 + +import ( + "errors" + "math" + "math/big" +) + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP2 implements a field of size p² as a quadratic extension of the base field +// where i²=-1. +type gfP2 struct { + x, y gfP // value is xi+y. +} + +func gfP2Decode(in *gfP2) *gfP2 { + out := &gfP2{} + montDecode(&out.x, &in.x) + montDecode(&out.y, &in.y) + return out +} + +func (e *gfP2) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ")" +} + +func (e *gfP2) Set(a *gfP2) *gfP2 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP2) SetZero() *gfP2 { + e.x = gfP{0} + e.y = gfP{0} + return e +} + +func (e *gfP2) SetOne() *gfP2 { + e.x = gfP{0} + e.y = *newGFp(1) + return e +} + +func (e *gfP2) IsZero() bool { + zero := gfP{0} + return e.x == zero && e.y == zero +} + +func (e *gfP2) IsOne() bool { + zero, one := gfP{0}, *newGFp(1) + return e.x == zero && e.y == one +} + +func (e *gfP2) Conjugate(a *gfP2) *gfP2 { + e.y.Set(&a.y) + gfpNeg(&e.x, &a.x) + return e +} + +func (e *gfP2) Neg(a *gfP2) *gfP2 { + gfpNeg(&e.x, &a.x) + gfpNeg(&e.y, &a.y) + return e +} + +func (e *gfP2) Add(a, b *gfP2) *gfP2 { + gfpAdd(&e.x, &a.x, &b.x) + gfpAdd(&e.y, &a.y, &b.y) + return e +} + +func (e *gfP2) Sub(a, b *gfP2) *gfP2 { + gfpSub(&e.x, &a.x, &b.x) + gfpSub(&e.y, &a.y, &b.y) + return e +} + +// See "Multiplication and Squaring in Pairing-Friendly Fields", +// http://eprint.iacr.org/2006/471.pdf Section 3 "Schoolbook method" +func (e *gfP2) Mul(a, b *gfP2) *gfP2 { + tx, t := &gfP{}, &gfP{} + gfpMul(tx, &a.x, &b.y) // tx = a.x * b.y + gfpMul(t, &b.x, &a.y) // t = b.x * a.y + gfpAdd(tx, tx, t) // tx = a.x * b.y + b.x * a.y + + ty := &gfP{} + gfpMul(ty, &a.y, &b.y) // ty = a.y * b.y + gfpMul(t, &a.x, &b.x) // t = a.x * b.x + // We do a subtraction in the field since β = -1 in our case + // In fact, Fp2 is built using the irreducible polynomial X^2 - β, where β = -1 = p-1 + gfpSub(ty, ty, t) // ty = a.y * b.y - a.x * b.x + + e.x.Set(tx) // e.x = a.x * b.y + b.x * a.y + e.y.Set(ty) // e.y = a.y * b.y - a.x * b.x + return e +} + +func (e *gfP2) MulScalar(a *gfP2, b *gfP) *gfP2 { + gfpMul(&e.x, &a.x, b) + gfpMul(&e.y, &a.y, b) + return e +} + +// MulXi sets e=ξa where ξ=i+9 and then returns e. +func (e *gfP2) MulXi(a *gfP2) *gfP2 { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + tx := &gfP{} + gfpAdd(tx, &a.x, &a.x) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, &a.x) + + gfpAdd(tx, tx, &a.y) + + ty := &gfP{} + gfpAdd(ty, &a.y, &a.y) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, &a.y) + + gfpSub(ty, ty, &a.x) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Square(a *gfP2) *gfP2 { + // Complex squaring algorithm: + // (xi+y)² = (x+y)(y-x) + 2*i*x*y + // - "Devegili OhEig Scott Dahab --- Multiplication and Squaring on Pairing-Friendly Fields.pdf"; Section 3 (Complex squaring) + // - URL: https://eprint.iacr.org/2006/471.pdf + // Here, since the non residue used is β = -1 in Fp, then we have: + // c0 = (a0 + a1)(a0 + βa1) - v0 - βv0 => c0 = (a0 + a1)(a0 - a1) + // c1 = 2v0, where v0 is a0 * a1 (= x * y, with our notations) + tx, ty := &gfP{}, &gfP{} + gfpSub(tx, &a.y, &a.x) // a.y - a.x + gfpAdd(ty, &a.x, &a.y) // a.x + a.y + gfpMul(ty, tx, ty) + + gfpMul(tx, &a.x, &a.y) + gfpAdd(tx, tx, tx) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Invert(a *gfP2) *gfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP{}, &gfP{} + gfpMul(t1, &a.x, &a.x) + gfpMul(t2, &a.y, &a.y) + gfpAdd(t1, t1, t2) + + inv := &gfP{} + inv.Invert(t1) + + gfpNeg(t1, &a.x) + + gfpMul(&e.x, t1, inv) + gfpMul(&e.y, &a.y, inv) + return e +} + +// Exp is a function to exponentiate field elements +// This function navigates the big.Int binary representation +// from left to right (assumed to be in big endian) +// When going from left to right, each bit is checked, and when the first `1` bit is found +// the `foundOne` flag is set, and the "exponentiation begins" +// +// Eg: Let's assume that we want to exponentiate 3^5 +// then the exponent is 5 = 0000 0101 +// We navigate 0000 0101 from left to right until we reach 0000 0101 +// ^ +// | +// When this bit is reached, the flag `foundOne` is set, and and we do: +// res = res * 3 = 3 +// Then, we move on to the left to read the next bit, and since `foundOne` is set (ie: +// the exponentiation has started), then we square the result, and do: +// res = res * res = 3*3 = 3^2 +// The bit is `0`, so we continue +// Next bit is `1`, so we do: res = res * res = 3^2 * 3^2 = 3^4 +// and because the bit is `1`, then, we do res = res * 3 = 3^4 * 3 = 3^5 +// We reached the end of the bit string, so we can stop. +// +// The binary representation of the exponent is assumed to be binary big endian +// +// Careful, since `res` is initialized with SetOne() and since this function +// initializes the calling gfP2 to the one element of the Gfp2 which is montEncoded +// then, we need to make sure that the `e` element of gfP2 used to call the Exp function +// is also montEncoded (ie; both x and y are montEncoded) +/* +TODO: Refactor this function like this: +func (e *gfP2) Exp(a *gfP2, exponent *big.Int) *gfP2 { + sum := (&gfP2{}).SetOne() + t := &gfP2{} + + for i := exponent.BitLen() - 1; i >= 0; i-- { + t.Square(sum) + if exponent.Bit(i) != 0 { + sum.Mul(t, a) + } else { + sum.Set(t) + } + } + + e.Set(sum) + return e +} +*/ +func (e *gfP2) Exp(exponent *big.Int) *gfP2 { + res := &gfP2{} + res = res.SetOne() + + base := &gfP2{} + base = base.Set(e) + + foundOne := false + exponentBytes := exponent.Bytes() // big endian bytes slice + + for i := 0; i < len(exponentBytes); i++ { // for each byte (remember the slice is big endian) + for j := 0; j <= 7; j++ { // A byte contains the powers of 2 to 2^7 to 2^0 from left to right + if foundOne { + res = res.Mul(res, res) + } + + if uint(exponentBytes[i])&uint(math.Pow(2, float64(7-j))) != uint(0) { // a byte contains the powers of 2 from 2^7 to 2^0 hence why we do 2^(7-j) (big-endian assumed) + foundOne = true + res = res.Mul(res, base) + } + } + } + + e.Set(res) + return e +} + +// Sqrt returns the square root of e in GFp2 +// See: +// - "A High-Speed Square Root Algorithm for Extension Fields - Especially for Fast Extension Fields" +// - URL: https://core.ac.uk/download/pdf/12530172.pdf +// +// - "Square Roots Modulo p" +// - URL: http://www.cmat.edu.uy/~tornaria/pub/Tornaria-2002.pdf +// +// - "Faster square roots in annoying finite fields" +// - URL: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.9172 +func (e *gfP2) Sqrt() (*gfP2, error) { + // In GF(p^m), Euler's Criterion is defined like EC(x) = x^((p^m -1) / 2), if EC(x) == 1, x is a QR; if EC(x) == -1, x is a QNR + // `euler` here, is the exponent used in Euler's criterion, thus, euler = (p^m -1) / 2 for GF(p^m) + // here, we work over GF(p^2), so euler = (p^2 -1) / 2, where p = 21888242871839275222246405745257275088696311157297823662689037894645226208583 + ////euler := bigFromBase10("239547588008311421220994022608339370399626158265550411218223901127035046843189118723920525909718935985594116157406550130918127817069793474323196511433944") + + // modulus^2 = 2^s * t + 1 => p^2 = 2^s * t + 1, where t is odd + // In our case, p^2 = 2^s * t + 1, where s = 4, t = 29943448501038927652624252826042421299953269783193801402277987640879380855398639840490065738714866998199264519675818766364765977133724184290399563929243 + ////t := bigFromBase10("29943448501038927652624252826042421299953269783193801402277987640879380855398639840490065738714866998199264519675818766364765977133724184290399563929243") + ////s := bigFromBase10("4") + s := 4 + + // tMinus1Over2 = (t-1) / 2 + tMinus1Over2 := bigFromBase10("14971724250519463826312126413021210649976634891596900701138993820439690427699319920245032869357433499099632259837909383182382988566862092145199781964621") + + // A non quadratic residue in Fp + ////nonResidueFp := bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208582") + + // A non quadratic residue in Fp2. Here nonResidueFp2 = i + 2 (Euler Criterion applied to this element of Fp2 shows that this element is a QNR) + ////nonResidueFp2 := &gfP2{*newGFp(1), *newGFp(2)} + + // nonResidueFp2 ^ t + nonResidueFp2ToTXCoord := bigFromBase10("314498342015008975724433667930697407966947188435857772134235984660852259084") + nonResidueFp2ToTYCoord := bigFromBase10("5033503716262624267312492558379982687175200734934877598599011485707452665730") + nonResidueFp2ToT := &gfP2{*newMontEncodedGFpFromBigInt(nonResidueFp2ToTXCoord), *newMontEncodedGFpFromBigInt(nonResidueFp2ToTYCoord)} + + // Start algorithm + // Initialize the algorithm variables + v := s + z := nonResidueFp2ToT + w := new(gfP2).Set(e) + w = w.Exp(tMinus1Over2) + x := new(gfP2).Mul(e, w) + b := new(gfP2).Mul(x, w) // contains e^t + + // Check if the element is a QR + // Since p^2 = 2^s * t + 1 => t = (p^2 - 1)/2 + // Thus, since we have b = e^t, and since we want to test if e is a QR + // we need to square b (s-1) times. That way we'd have + // (e^t)^{2^(s-1)} which equals e^{(p^2 - 1)/2} => Euler criterion + bCheck := new(gfP2).Set(b) + for i := 0; i < s-1; i++ { // s-1 == 3 here (see comment above) + bCheck = bCheck.Square(bCheck) + } + + if !bCheck.IsOne() { + return nil, errors.New("Cannot extract a root. The element is not a QR in Fp2") + } + + // Extract the root of the quadratic residue using the Tonelli-Shanks algorithm + for !b.IsOne() { + m := 0 + b2m := new(gfP2).Set(b) + for !b2m.IsOne() { + /* invariant: b2m = b^(2^m) after entering this loop */ + b2m = b2m.Square(b2m) + m++ + } + + j := v - m - 1 + w = z + for j > 0 { + w = w.Square(w) + j-- + } // w = z^2^(v-m-1) + + z = new(gfP2).Square(w) + b = b.Mul(b, z) + x = x.Mul(x, w) + v = m + } + + return x, nil +} diff --git a/cryptography/bn256/gfp2_test.go b/cryptography/bn256/gfp2_test.go new file mode 100644 index 0000000..cfbdb29 --- /dev/null +++ b/cryptography/bn256/gfp2_test.go @@ -0,0 +1,164 @@ +package bn256 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Tests that the exponentiation in gfp2 works correctly +// SageMath test vector: +/* +p = 21888242871839275222246405745257275088696311157297823662689037894645226208583 +Fp = GF(p) +Fpx. = PolynomialRing(Fp, 'j') +// The modulus is in the form `j^2 - non-residue-in-Fp` +// Fp2. = GF(p^2, modulus=j^2 - 3) // 3 is a quadratic non-residue in Fp +// See: https://github.com/scipr-lab/libff/blob/master/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp#L95 +// The quadratic non-residue used in -1, so the modulus is +Fp2. = GF(p^2, modulus=j^2 + 1) + +// Quad. Non. Resid. test (Euler's criterion) +eulerExp = (Fp(p-1)/Fp(2)) +Fp(-1)^eulerExp + Fp(1) == p // Should return true, then we see that -1 (ie: p-1) is a nqr (non quadratic residue mod p) + +// Element taken randomly in the field Fp2 +// we denote an element of Fp2 as: e = x*i + y +baseElement = 8192512702373747571754527085437364828369119615795326562285198594140975111129*i + 14719814144181528030533020377409648968040866053797156997322427920899698335369 + +// baseElementXHex = hex(8192512702373747571754527085437364828369119615795326562285198594140975111129) +baseElementXHex = 121ccc410d6339f7 bbc9a8f5b577c5c5 96c9dfdd6233cbac 34a8ddeafedf9bd9 +baseElementXHexLittleEndian = 34a8ddeafedf9bd9 96c9dfdd6233cbac bbc9a8f5b577c5c5 121ccc410d6339f7 + +// baseElementYHex = hex(14719814144181528030533020377409648968040866053797156997322427920899698335369) +baseElementYHex = 208b1e9b9b11a98c 30a84b2641e87244 9a54780d0e482cfb 146adf9eb7641e89 +baseElementYHexLittleEndian = 146adf9eb7641e89 9a54780d0e482cfb 30a84b2641e87244 208b1e9b9b11a98c + +// We run in Sage, resExponentiation = baseElement ^ 5, and we get +baseElementTo5 = baseElement ^ 5 +baseElementTo5 = 1919494216989370714282264091499504460829540920627494019318177103740489354093*i + 3944397571509712892671395330294281468555185483198747137614153093242854529958 +baseElementTo5XHex = 043e652d8f044857 c4cbfe9636928309 44288a2a00432390 7fa7e33a3e5acb6d +baseElementTo5XHexLittleEndian = 7fa7e33a3e5acb6d 44288a2a00432390 c4cbfe9636928309 043e652d8f044857 + +baseElementTo5YHex = 08b8732d547b1cda b5c82ff0bfaa42c1 54e7b24b65223fc2 88b3e8a6de535ba6 +baseElementTo5XHexLittleEndian = 88b3e8a6de535ba6 54e7b24b65223fc2 b5c82ff0bfaa42c1 08b8732d547b1cda +*/ +func TestExp(t *testing.T) { + // Case 1: Exponent = 5 (= 0x05) + baseElementX := &gfP{0x34a8ddeafedf9bd9, 0x96c9dfdd6233cbac, 0xbbc9a8f5b577c5c5, 0x121ccc410d6339f7} + baseElementY := &gfP{0x146adf9eb7641e89, 0x9a54780d0e482cfb, 0x30a84b2641e87244, 0x208b1e9b9b11a98c} + // montEncode each Fp element + // Important to do since the field arithmetic uses montgomery encoding in the library + montEncode(baseElementX, baseElementX) + montEncode(baseElementY, baseElementY) + baseElement := &gfP2{*baseElementX, *baseElementY} + + // We keep the expected result non encoded + // Will need to decode the obtained result to be able to assert it with this + baseElementTo5X := &gfP{0x7fa7e33a3e5acb6d, 0x44288a2a00432390, 0xc4cbfe9636928309, 0x043e652d8f044857} + baseElementTo5Y := &gfP{0x88b3e8a6de535ba6, 0x54e7b24b65223fc2, 0xb5c82ff0bfaa42c1, 0x08b8732d547b1cda} + baseElementTo5 := &gfP2{*baseElementTo5X, *baseElementTo5Y} + + // Manual multiplication, to make sure the results are all coherent with each other + manual := &gfP2{} + manual = manual.Set(baseElement) + manual = manual.Mul(manual, manual) // manual ^ 2 + manual = manual.Mul(manual, manual) // manual ^ 4 + manual = manual.Mul(manual, baseElement) // manual ^ 5 + manualDecoded := gfP2Decode(manual) + + // Expected result (obtained with sagemath, after some type conversions) + w := &gfP2{} + w = w.Set(baseElementTo5) + + // Result returned by the Exp function + exponent5 := bigFromBase10("5") + h := &gfP2{} + h = h.Set(baseElement) + h = h.Exp(exponent5) + + // We decode the result of the exponentiation to be able to compare with the + // non-encoded/sagemath generated expected result + hDecoded := gfP2Decode(h) + + assert.Equal(t, *w, *hDecoded, "The result of the exponentiation is not coherent with the Sagemath test vector") + assert.Equal(t, *manualDecoded, *hDecoded, "The result of the exponentiation is not coherent with the manual repeated multiplication") + + // Case 2: Exponent = bigExponent = 39028236692093846773374607431768211455 + 2^128 - 2^64 = 379310603613032310218302470789826871295 = 0x11d5c90a20486bd1c40686b493777ffff + // This exponent can be encoded on 3 words/uint64 => 0x1 0x1d5c90a20486bd1c 0x40686b493777ffff if 64bit machine or + // on 5 words/uint32 => 0x1 0x1d5c90a2 0x0486bd1c 0x40686b49 0x3777ffff if 32bit machine + baseElementX = &gfP{0x34a8ddeafedf9bd9, 0x96c9dfdd6233cbac, 0xbbc9a8f5b577c5c5, 0x121ccc410d6339f7} + baseElementY = &gfP{0x146adf9eb7641e89, 0x9a54780d0e482cfb, 0x30a84b2641e87244, 0x208b1e9b9b11a98c} + // montEncode each Fp element + // Important to do since the field arithmetic uses montgomery encoding in the library + montEncode(baseElementX, baseElementX) + montEncode(baseElementY, baseElementY) + baseElement = &gfP2{*baseElementX, *baseElementY} + + // We keep the expected result non encoded + // Will need to decode the obtained result to be able to assert it with this + // Sagemath: + // baseElementToBigExp = baseElement ^ bigExponent + // baseElementToBigExp => 7379142427977467878031119988604583496475317621776403696479934226513132928021*i + 17154720713365092794088637301427106756251681045968150072197181728711103784706 + // baseElementToBigExpXHex = 10507254ce787236 62cf3f84eb21adee 30ec827a799a519a 1464fc2ec9263c15 + // baseElementToBigExpYHex = 25ed3a53d558db9a 07da01cc9d10c5d5 ff7b1e4f41b874d7 debbc13409c8a702 + baseElementToBigExpX := &gfP{0x1464fc2ec9263c15, 0x30ec827a799a519a, 0x62cf3f84eb21adee, 0x10507254ce787236} + baseElementToBigExpY := &gfP{0xdebbc13409c8a702, 0xff7b1e4f41b874d7, 0x07da01cc9d10c5d5, 0x25ed3a53d558db9a} + baseElementToBigExp := &gfP2{*baseElementToBigExpX, *baseElementToBigExpY} + + // Expected result (obtained with sagemath, after some type conversions) + w = &gfP2{} + w = w.Set(baseElementToBigExp) + + // Result returned by the Exp function + bigExp := bigFromBase10("379310603613032310218302470789826871295") + h = &gfP2{} + h = h.Set(baseElement) + h = h.Exp(bigExp) + + // We decode the result of the exponentiation to be able to compare with the + // non-encoded/sagemath generated expected result + hDecoded = gfP2Decode(h) + + assert.Equal(t, *w, *hDecoded, "The result of the exponentiation is not coherent with the Sagemath test vector") +} + +func TestSqrt(t *testing.T) { + // Case 1: Valid QR + // qr = 8192512702373747571754527085437364828369119615795326562285198594140975111129*i + 14719814144181528030533020377409648968040866053797156997322427920899698335369 + // This is a QR in Fp2 + qrXBig := bigFromBase10("8192512702373747571754527085437364828369119615795326562285198594140975111129") + qrYBig := bigFromBase10("14719814144181528030533020377409648968040866053797156997322427920899698335369") + qr := &gfP2{*newMontEncodedGFpFromBigInt(qrXBig), *newMontEncodedGFpFromBigInt(qrYBig)} + res, err := qr.Sqrt() + assert.NoError(t, err, "An error shouldn't be returned as we try to get the sqrt of a QR") + // We decode the result of the squaring to compare the result with the Sagemath test vector + // To get the sqrt of `r` in Sage, we run: `r.sqrt()`, and we get: + // 838738240039331261565244756819667559640832302782323121523807597830118111128*i + 701115843855913009657260259360827182296091347204618857804078039211229345012 + resDecoded := gfP2Decode(res) + expectedXBig := bigFromBase10("838738240039331261565244756819667559640832302782323121523807597830118111128") + expectedYBig := bigFromBase10("701115843855913009657260259360827182296091347204618857804078039211229345012") + expected := &gfP2{*newGFpFromBigInt(expectedXBig), *newGFpFromBigInt(expectedYBig)} + + assert.Equal(t, *expected, *resDecoded, "The result of the sqrt is not coherent with the Sagemath test vector") + + // Case 2: Valid QR + // qr = -1 = 0 * i + 21888242871839275222246405745257275088696311157297823662689037894645226208582 + // The sqrt of qr is: sqrt = 21888242871839275222246405745257275088696311157297823662689037894645226208582 * i + 0 + qr = &gfP2{*newGFp(0), *newMontEncodedGFpFromBigInt(bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208582"))} + res, err = qr.Sqrt() + assert.NoError(t, err, "An error shouldn't be returned as we try to get the sqrt of a QR") + + resDecoded = gfP2Decode(res) + expected = &gfP2{*newGFpFromBigInt(bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208582")), *newGFp(0)} + assert.Equal(t, *expected, *resDecoded, "The result of the sqrt is not coherent with the Sagemath test vector") + + // Case 3: Get the sqrt of a QNR + // qnr = 10142231111593789910248975994434553601587001629804098271704323146176084338608*i + 13558357083504759335548106329923635779485621365040524539176938811542516618464 + qnrXBig := bigFromBase10("10142231111593789910248975994434553601587001629804098271704323146176084338608") + qnrYBig := bigFromBase10("13558357083504759335548106329923635779485621365040524539176938811542516618464") + qnr := &gfP2{*newMontEncodedGFpFromBigInt(qnrXBig), *newMontEncodedGFpFromBigInt(qnrYBig)} + res, err = qnr.Sqrt() + assert.Error(t, err, "An error should have been returned as we try to get the sqrt of a QNR") + assert.Nil(t, res, "The result of sqrt should be nil as we try to get the sqrt of a QNR") +} diff --git a/cryptography/bn256/gfp6.go b/cryptography/bn256/gfp6.go new file mode 100644 index 0000000..83d61b7 --- /dev/null +++ b/cryptography/bn256/gfp6.go @@ -0,0 +1,213 @@ +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP6 implements the field of size p⁶ as a cubic extension of gfP2 where τ³=ξ +// and ξ=i+3. +type gfP6 struct { + x, y, z gfP2 // value is xτ² + yτ + z +} + +func (e *gfP6) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ", " + e.z.String() + ")" +} + +func (e *gfP6) Set(a *gfP6) *gfP6 { + e.x.Set(&a.x) + e.y.Set(&a.y) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) SetZero() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetZero() + return e +} + +func (e *gfP6) SetOne() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetOne() + return e +} + +func (e *gfP6) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsZero() +} + +func (e *gfP6) IsOne() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsOne() +} + +func (e *gfP6) Neg(a *gfP6) *gfP6 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + e.z.Neg(&a.z) + return e +} + +func (e *gfP6) Frobenius(a *gfP6) *gfP6 { + e.x.Conjugate(&a.x) + e.y.Conjugate(&a.y) + e.z.Conjugate(&a.z) + + e.x.Mul(&e.x, xiTo2PMinus2Over3) + e.y.Mul(&e.y, xiToPMinus1Over3) + return e +} + +// FrobeniusP2 computes (xτ²+yτ+z)^(p²) = xτ^(2p²) + yτ^(p²) + z +func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { + // τ^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + e.x.MulScalar(&a.x, xiTo2PSquaredMinus2Over3) + // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) + e.y.MulScalar(&a.y, xiToPSquaredMinus1Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) FrobeniusP4(a *gfP6) *gfP6 { + e.x.MulScalar(&a.x, xiToPSquaredMinus1Over3) + e.y.MulScalar(&a.y, xiTo2PSquaredMinus2Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) Add(a, b *gfP6) *gfP6 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + e.z.Add(&a.z, &b.z) + return e +} + +func (e *gfP6) Sub(a, b *gfP6) *gfP6 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + e.z.Sub(&a.z, &b.z) + return e +} + +func (e *gfP6) Mul(a, b *gfP6) *gfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + v0 := (&gfP2{}).Mul(&a.z, &b.z) + v1 := (&gfP2{}).Mul(&a.y, &b.y) + v2 := (&gfP2{}).Mul(&a.x, &b.x) + + t0 := (&gfP2{}).Add(&a.x, &a.y) + t1 := (&gfP2{}).Add(&b.x, &b.y) + tz := (&gfP2{}).Mul(t0, t1) + tz.Sub(tz, v1).Sub(tz, v2).MulXi(tz).Add(tz, v0) + + t0.Add(&a.y, &a.z) + t1.Add(&b.y, &b.z) + ty := (&gfP2{}).Mul(t0, t1) + t0.MulXi(v2) + ty.Sub(ty, v0).Sub(ty, v1).Add(ty, t0) + + t0.Add(&a.x, &a.z) + t1.Add(&b.x, &b.z) + tx := (&gfP2{}).Mul(t0, t1) + tx.Sub(tx, v0).Add(tx, v1).Sub(tx, v2) + + e.x.Set(tx) + e.y.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) MulScalar(a *gfP6, b *gfP2) *gfP6 { + e.x.Mul(&a.x, b) + e.y.Mul(&a.y, b) + e.z.Mul(&a.z, b) + return e +} + +func (e *gfP6) MulGFP(a *gfP6, b *gfP) *gfP6 { + e.x.MulScalar(&a.x, b) + e.y.MulScalar(&a.y, b) + e.z.MulScalar(&a.z, b) + return e +} + +// MulTau computes τ·(aτ²+bτ+c) = bτ²+cτ+aξ +func (e *gfP6) MulTau(a *gfP6) *gfP6 { + tz := (&gfP2{}).MulXi(&a.x) + ty := (&gfP2{}).Set(&a.y) + + e.y.Set(&a.z) + e.x.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) Square(a *gfP6) *gfP6 { + v0 := (&gfP2{}).Square(&a.z) + v1 := (&gfP2{}).Square(&a.y) + v2 := (&gfP2{}).Square(&a.x) + + c0 := (&gfP2{}).Add(&a.x, &a.y) + c0.Square(c0).Sub(c0, v1).Sub(c0, v2).MulXi(c0).Add(c0, v0) + + c1 := (&gfP2{}).Add(&a.y, &a.z) + c1.Square(c1).Sub(c1, v0).Sub(c1, v1) + xiV2 := (&gfP2{}).MulXi(v2) + c1.Add(c1, xiV2) + + c2 := (&gfP2{}).Add(&a.x, &a.z) + c2.Square(c2).Sub(c2, v0).Add(c2, v1).Sub(c2, v2) + + e.x.Set(c2) + e.y.Set(c1) + e.z.Set(c0) + return e +} + +func (e *gfP6) Invert(a *gfP6) *gfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = (xτ² + yτ + z)(Cτ²+Bτ+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + τ(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + t1 := (&gfP2{}).Mul(&a.x, &a.y) + t1.MulXi(t1) + + A := (&gfP2{}).Square(&a.z) + A.Sub(A, t1) + + B := (&gfP2{}).Square(&a.x) + B.MulXi(B) + t1.Mul(&a.y, &a.z) + B.Sub(B, t1) + + C := (&gfP2{}).Square(&a.y) + t1.Mul(&a.x, &a.z) + C.Sub(C, t1) + + F := (&gfP2{}).Mul(C, &a.y) + F.MulXi(F) + t1.Mul(A, &a.z) + F.Add(F, t1) + t1.Mul(B, &a.x).MulXi(t1) + F.Add(F, t1) + + F.Invert(F) + + e.x.Mul(C, F) + e.y.Mul(B, F) + e.z.Mul(A, F) + return e +} diff --git a/cryptography/bn256/gfp_amd64.s b/cryptography/bn256/gfp_amd64.s new file mode 100644 index 0000000..bdb4ffb --- /dev/null +++ b/cryptography/bn256/gfp_amd64.s @@ -0,0 +1,129 @@ +// +build amd64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVQ a0, 0+r \ + MOVQ a1, 8+r \ + MOVQ a2, 16+r \ + MOVQ a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVQ 0+r, a0 \ + MOVQ 8+r, a1 \ + MOVQ 16+r, a2 \ + MOVQ 24+r, a3 + +#define gfpCarry(a0,a1,a2,a3,a4, b0,b1,b2,b3,b4) \ + \ // b = a-p + MOVQ a0, b0 \ + MOVQ a1, b1 \ + MOVQ a2, b2 \ + MOVQ a3, b3 \ + MOVQ a4, b4 \ + \ + SUBQ ·p2+0(SB), b0 \ + SBBQ ·p2+8(SB), b1 \ + SBBQ ·p2+16(SB), b2 \ + SBBQ ·p2+24(SB), b3 \ + SBBQ $0, b4 \ + \ + \ // if b is negative then return a + \ // else return b + CMOVQCC b0, a0 \ + CMOVQCC b1, a1 \ + CMOVQCC b2, a2 \ + CMOVQCC b3, a3 + +#include "mul_amd64.h" +#include "mul_bmi2_amd64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVQ ·p2+0(SB), R8 + MOVQ ·p2+8(SB), R9 + MOVQ ·p2+16(SB), R10 + MOVQ ·p2+24(SB), R11 + + MOVQ a+8(FP), DI + SUBQ 0(DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + + MOVQ $0, AX + gfpCarry(R8,R9,R10,R11,AX, R12,R13,R14,R15,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + MOVQ $0, R12 + + ADDQ 0(SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ $0, R12 + + gfpCarry(R8,R9,R10,R11,R12, R13,R14,R15,AX,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + + MOVQ ·p2+0(SB), R12 + MOVQ ·p2+8(SB), R13 + MOVQ ·p2+16(SB), R14 + MOVQ ·p2+24(SB), R15 + MOVQ $0, AX + + SUBQ 0(SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + + CMOVQCC AX, R12 + CMOVQCC AX, R13 + CMOVQCC AX, R14 + CMOVQCC AX, R15 + + ADDQ R12, R8 + ADCQ R13, R9 + ADCQ R14, R10 + ADCQ R15, R11 + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpMul(SB),0,$160-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // Jump to a slightly different implementation if MULX isn't supported. + CMPB ·hasBMI2(SB), $0 + JE nobmi2Mul + + mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI)) + storeBlock( R8, R9,R10,R11, 0(SP)) + storeBlock(R12,R13,R14,R15, 32(SP)) + gfpReduceBMI2() + JMP end + +nobmi2Mul: + mul(0(DI),8(DI),16(DI),24(DI), 0(SI), 0(SP)) + gfpReduce(0(SP)) + +end: + MOVQ c+0(FP), DI + storeBlock(R12,R13,R14,R15, 0(DI)) + RET diff --git a/cryptography/bn256/gfp_arm64.s b/cryptography/bn256/gfp_arm64.s new file mode 100644 index 0000000..c65e801 --- /dev/null +++ b/cryptography/bn256/gfp_arm64.s @@ -0,0 +1,113 @@ +// +build arm64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVD a0, 0+r \ + MOVD a1, 8+r \ + MOVD a2, 16+r \ + MOVD a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVD 0+r, a0 \ + MOVD 8+r, a1 \ + MOVD 16+r, a2 \ + MOVD 24+r, a3 + +#define loadModulus(p0,p1,p2,p3) \ + MOVD ·p2+0(SB), p0 \ + MOVD ·p2+8(SB), p1 \ + MOVD ·p2+16(SB), p2 \ + MOVD ·p2+24(SB), p3 + +#include "mul_arm64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + loadModulus(R5,R6,R7,R8) + + SUBS R1, R5, R1 + SBCS R2, R6, R2 + SBCS R3, R7, R3 + SBCS R4, R8, R4 + + SUBS R5, R1, R5 + SBCS R6, R2, R6 + SBCS R7, R3, R7 + SBCS R8, R4, R8 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + MOVD ZR, R0 + + ADDS R5, R1 + ADCS R6, R2 + ADCS R7, R3 + ADCS R8, R4 + ADCS ZR, R0 + + SUBS R9, R1, R5 + SBCS R10, R2, R6 + SBCS R11, R3, R7 + SBCS R12, R4, R8 + SBCS ZR, R0, R0 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + + SUBS R5, R1 + SBCS R6, R2 + SBCS R7, R3 + SBCS R8, R4 + + CSEL CS, ZR, R9, R9 + CSEL CS, ZR, R10, R10 + CSEL CS, ZR, R11, R11 + CSEL CS, ZR, R12, R12 + + ADDS R9, R1 + ADCS R10, R2 + ADCS R11, R3 + ADCS R12, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpMul(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + + mul(R9,R10,R11,R12,R13,R14,R15,R16) + gfpReduce() + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET diff --git a/cryptography/bn256/gfp_decl.go b/cryptography/bn256/gfp_decl.go new file mode 100644 index 0000000..fdea5c1 --- /dev/null +++ b/cryptography/bn256/gfp_decl.go @@ -0,0 +1,25 @@ +// +build amd64,!generic arm64,!generic + +package bn256 + +// This file contains forward declarations for the architecture-specific +// assembly implementations of these functions, provided that they exist. + +import ( + "golang.org/x/sys/cpu" +) + +//nolint:varcheck +var hasBMI2 = cpu.X86.HasBMI2 + +// go:noescape +func gfpNeg(c, a *gfP) + +//go:noescape +func gfpAdd(c, a, b *gfP) + +//go:noescape +func gfpSub(c, a, b *gfP) + +//go:noescape +func gfpMul(c, a, b *gfP) diff --git a/cryptography/bn256/gfp_generic.go b/cryptography/bn256/gfp_generic.go new file mode 100644 index 0000000..d902aa7 --- /dev/null +++ b/cryptography/bn256/gfp_generic.go @@ -0,0 +1,209 @@ +// +build !amd64,!arm64 generic + +package bn256 + +func gfpCarry(a *gfP, head uint64) { + b := &gfP{} + + var carry uint64 + for i, pi := range p2 { + ai := a[i] + bi := ai - pi - carry + b[i] = bi + carry = (pi&^ai | (pi|^ai)&bi) >> 63 + } + carry = carry &^ head + + // If b is negative, then return a. + // Else return b. + carry = -carry + ncarry := ^carry + for i := 0; i < 4; i++ { + a[i] = (a[i] & carry) | (b[i] & ncarry) + } +} + +func gfpNeg(c, a *gfP) { + var carry uint64 + for i, pi := range p2 { // p2 being the prime that defines the base/prime field + ai := a[i] + ci := pi - ai - carry + c[i] = ci + carry = (ai&^pi | (ai|^pi)&ci) >> 63 + } + gfpCarry(c, 0) +} + +func gfpAdd(c, a, b *gfP) { + var carry uint64 + for i, ai := range a { + bi := b[i] + ci := ai + bi + carry + c[i] = ci + carry = (ai&bi | (ai|bi)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func gfpSub(c, a, b *gfP) { + t := &gfP{} + + var carry uint64 + for i, pi := range p2 { + bi := b[i] + ti := pi - bi - carry + t[i] = ti + carry = (bi&^pi | (bi|^pi)&ti) >> 63 + } + + carry = 0 + for i, ai := range a { + ti := t[i] + ci := ai + ti + carry + c[i] = ci + carry = (ai&ti | (ai|ti)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func mul(a, b [4]uint64) [8]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [32]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 8; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [8]uint64{buff[0], buff[4], buff[8], buff[12], buff[16], buff[20], buff[24], buff[28]} +} + +func halfMul(a, b [4]uint64) [4]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [18]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + if i+j > 3 { + break + } + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 4; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [4]uint64{buff[0], buff[4], buff[8], buff[12]} +} + +func gfpMul(c, a, b *gfP) { + T := mul(*a, *b) + m := halfMul([4]uint64{T[0], T[1], T[2], T[3]}, np) + t := mul([4]uint64{m[0], m[1], m[2], m[3]}, p2) + + var carry uint64 + for i, Ti := range T { + ti := t[i] + zi := Ti + ti + carry + T[i] = zi + carry = (Ti&ti | (Ti|ti)&^zi) >> 63 + } + + *c = gfP{T[4], T[5], T[6], T[7]} + gfpCarry(c, carry) +} + +// Util function to compare field elements. Should be defined in gfP files +// Compares 2 GFp elements +// Returns 1 if a > b; 0 if a == b; -1 if a < b +/* +func gfpCmp(a, b *gfP) int { + for i := FpUint64Size - 1; i >= 0; i-- { // Remember that the gfP elements are written as little-endian 64-bit words + if a[i] > b[i] { // As soon as we figure out that the MSByte of A > MSByte of B, we return + return 1 + } else if a[i] == b[i] { // If the current bytes are equal we continue as we cannot conclude on A and B relation + continue + } else { // a[i] < b[i] so we can directly conclude and we return + return -1 + } + } + + return 0 +} +*/ + +// TODO: Optimize these functions as for now all it's doing is to convert in big.Int +// and use big integer arithmetic +// Computes c = a^{exp} in Fp (so mod p) +/* +func gfpExp(a *gfP, exp, mod *big.Int) *gfP { + // Convert the field elements to big.Int + aBig := a.gFpToBigInt() + + // Run the big.Int Exp algorithm + resBig := new(big.Int).Exp(aBig, exp, mod) + + // Convert the big.Int result back to field element + res := newGFpFromBigInt(resBig) + return res +} +*/ diff --git a/cryptography/bn256/gfp_test.go b/cryptography/bn256/gfp_test.go new file mode 100644 index 0000000..6fe2872 --- /dev/null +++ b/cryptography/bn256/gfp_test.go @@ -0,0 +1,116 @@ +package bn256 + +import ( + "testing" +) + +// Tests that negation works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpNeg(t *testing.T) { + n := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + w := &gfP{0xfedcba9876543211, 0x0123456789abcdef, 0x2152411021524110, 0x0114251201142512} + h := &gfP{} + + gfpNeg(h, n) + if *h != *w { + t.Errorf("negation mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that addition works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpAdd(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xc3df73e9278302b8, 0x687e956e978e3572, 0x254954275c18417f, 0xad354b6afc67f9b4} + h := &gfP{} + + gfpAdd(h, a, b) + if *h != *w { + t.Errorf("addition mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that subtraction works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpSub(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0x02468acf13579bdf, 0xfdb97530eca86420, 0xdfc1e401dfc1e402, 0x203e1bfe203e1bfd} + h := &gfP{} + + gfpSub(h, a, b) + if *h != *w { + t.Errorf("subtraction mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that multiplication works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpMul(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xcbcbd377f7ad22d3, 0x3b89ba5d849379bf, 0x87b61627bd38b6d2, 0xc44052a2a0e654b2} + h := &gfP{} + + gfpMul(h, a, b) + if *h != *w { + t.Errorf("multiplication mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests the conversion from big.Int to GFp element +func TestNewGFpFromBigInt(t *testing.T) { + // Case 1 + twoBig := bigFromBase10("2") + h := *newGFpFromBigInt(twoBig) + twoHex := [4]uint64{0x0000000000000002, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000} + w := gfP(twoHex) + + if h != w { + t.Errorf("conversion mismatch: have %s, want %s", h.String(), w.String()) + } + + // Case 2 + pMinus1Big := bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208582") + h = *newGFpFromBigInt(pMinus1Big) + pMinus1Hex := [4]uint64{0x3c208c16d87cfd46, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + w = gfP(pMinus1Hex) + + if h != w { + t.Errorf("conversion mismatch: have %s, want %s", h.String(), w.String()) + } +} + +// Tests the conversion from GFp element to big.Int +func TestGFpToBigInt(t *testing.T) { + // Case 1 + twoHex := [4]uint64{0x0000000000000002, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000} + twoBig := bigFromBase10("2") + twoGFp := gfP(twoHex) // Not MontEncoded! + w := twoBig + h, err := twoGFp.gFpToBigInt() + + if err != nil { + t.Errorf("Couldn't convert GFp to big.Int: %s", err) + } + + if r := h.Cmp(w); r != 0 { + t.Errorf("conversion mismatch: have %s, want %s", h.String(), w.String()) + } + + // Case 2 + pMinus1Hex := [4]uint64{0x3c208c16d87cfd46, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + pMinus1Big := bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208582") + pMinus1GFp := gfP(pMinus1Hex) // Not MontEncoded! + w = pMinus1Big + h, err = pMinus1GFp.gFpToBigInt() + + if err != nil { + t.Errorf("Couldn't convert GFp to big.Int: %s", err) + } + + if r := h.Cmp(w); r != 0 { + t.Errorf("conversion mismatch: have %s, want %s", h.String(), w.String()) + } +} diff --git a/cryptography/bn256/lattice.go b/cryptography/bn256/lattice.go new file mode 100644 index 0000000..f9ace4d --- /dev/null +++ b/cryptography/bn256/lattice.go @@ -0,0 +1,115 @@ +package bn256 + +import ( + "math/big" +) + +var half = new(big.Int).Rsh(Order, 1) + +var curveLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("147946756881789319000765030803803410728"), bigFromBase10("147946756881789319010696353538189108491")}, + {bigFromBase10("147946756881789319020627676272574806254"), bigFromBase10("-147946756881789318990833708069417712965")}, + }, + inverse: []*big.Int{ + bigFromBase10("147946756881789318990833708069417712965"), + bigFromBase10("147946756881789319010696353538189108491"), + }, + det: bigFromBase10("43776485743678550444492811490514550177096728800832068687396408373151616991234"), +} + +var targetLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697763"), bigFromBase10("9931322734385697764")}, + {bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848882"), bigFromBase10("-9931322734385697762")}, + {bigFromBase10("-9931322734385697762"), bigFromBase10("-4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("-4965661367192848882")}, + {bigFromBase10("9931322734385697763"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881")}, + }, + inverse: []*big.Int{ + bigFromBase10("734653495049373973658254490726798021314063399421879442165"), + bigFromBase10("147946756881789319000765030803803410728"), + bigFromBase10("-147946756881789319005730692170996259609"), + bigFromBase10("1469306990098747947464455738335385361643788813749140841702"), + }, + det: new(big.Int).Set(Order), +} + +type lattice struct { + vectors [][]*big.Int + inverse []*big.Int + det *big.Int +} + +// decompose takes a scalar mod Order as input and finds a short, positive decomposition of it wrt to the lattice basis. +func (l *lattice) decompose(k *big.Int) []*big.Int { + n := len(l.inverse) + + // Calculate closest vector in lattice to with Babai's rounding. + c := make([]*big.Int, n) + for i := 0; i < n; i++ { + c[i] = new(big.Int).Mul(k, l.inverse[i]) + round(c[i], l.det) + } + + // Transform vectors according to c and subtract . + out := make([]*big.Int, n) + temp := new(big.Int) + + for i := 0; i < n; i++ { + out[i] = new(big.Int) + + for j := 0; j < n; j++ { + temp.Mul(c[j], l.vectors[j][i]) + out[i].Add(out[i], temp) + } + + out[i].Neg(out[i]) + out[i].Add(out[i], l.vectors[0][i]).Add(out[i], l.vectors[0][i]) + } + out[0].Add(out[0], k) + + return out +} + +func (l *lattice) Precompute(add func(i, j uint)) { + n := uint(len(l.vectors)) + total := uint(1) << n + + for i := uint(0); i < n; i++ { + for j := uint(0); j < total; j++ { + if (j>>i)&1 == 1 { + add(i, j) + } + } + } +} + +func (l *lattice) Multi(scalar *big.Int) []uint8 { + decomp := l.decompose(scalar) + + maxLen := 0 + for _, x := range decomp { + if x.BitLen() > maxLen { + maxLen = x.BitLen() + } + } + + out := make([]uint8, maxLen) + for j, x := range decomp { + for i := 0; i < maxLen; i++ { + out[i] += uint8(x.Bit(i)) << uint(j) + } + } + + return out +} + +// round sets num to num/denom rounded to the nearest integer. +func round(num, denom *big.Int) { + r := new(big.Int) + num.DivMod(num, denom, r) + + if r.Cmp(half) == 1 { + num.Add(num, big.NewInt(1)) + } +} diff --git a/cryptography/bn256/lattice_test.go b/cryptography/bn256/lattice_test.go new file mode 100644 index 0000000..4d52ad9 --- /dev/null +++ b/cryptography/bn256/lattice_test.go @@ -0,0 +1,29 @@ +package bn256 + +import ( + "crypto/rand" + + "testing" +) + +func TestLatticeReduceCurve(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := curveLattice.decompose(k) + + if ks[0].BitLen() > 130 || ks[1].BitLen() > 130 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} + +func TestLatticeReduceTarget(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := targetLattice.decompose(k) + + if ks[0].BitLen() > 66 || ks[1].BitLen() > 66 || ks[2].BitLen() > 66 || ks[3].BitLen() > 66 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 || ks[2].Sign() < 0 || ks[3].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} diff --git a/cryptography/bn256/main_test.go b/cryptography/bn256/main_test.go new file mode 100644 index 0000000..c0c8545 --- /dev/null +++ b/cryptography/bn256/main_test.go @@ -0,0 +1,71 @@ +package bn256 + +import ( + "testing" + + "crypto/rand" +) + +func TestRandomG2Marshal(t *testing.T) { + for i := 0; i < 10; i++ { + n, g2, err := RandomG2(rand.Reader) + if err != nil { + t.Error(err) + continue + } + t.Logf("%v: %x\n", n, g2.Marshal()) + } +} + +func TestPairings(t *testing.T) { + a1 := new(G1).ScalarBaseMult(bigFromBase10("1")) + a2 := new(G1).ScalarBaseMult(bigFromBase10("2")) + a37 := new(G1).ScalarBaseMult(bigFromBase10("37")) + an1 := new(G1).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + b0 := new(G2).ScalarBaseMult(bigFromBase10("0")) + b1 := new(G2).ScalarBaseMult(bigFromBase10("1")) + b2 := new(G2).ScalarBaseMult(bigFromBase10("2")) + b27 := new(G2).ScalarBaseMult(bigFromBase10("27")) + b999 := new(G2).ScalarBaseMult(bigFromBase10("999")) + bn1 := new(G2).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + p1 := Pair(a1, b1) + pn1 := Pair(a1, bn1) + np1 := Pair(an1, b1) + if pn1.String() != np1.String() { + t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") + } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false negative!") + } + p0 := new(GT).Add(p1, pn1) + p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { + t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") + } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + if p0.String() != p0_3.String() { + t.Error("Pairing mismatch: e(a, b) has wrong order") + } + p2 := Pair(a2, b1) + p2_2 := Pair(a1, b2) + p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") + } + if p2.String() != p2_3.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") + } + if p2.String() == p1.String() { + t.Error("Pairing is degenerate!") + } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false positive!") + } + p999 := Pair(a37, b27) + p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { + t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") + } +} diff --git a/cryptography/bn256/mul_amd64.h b/cryptography/bn256/mul_amd64.h new file mode 100644 index 0000000..bab5da8 --- /dev/null +++ b/cryptography/bn256/mul_amd64.h @@ -0,0 +1,181 @@ +#define mul(a0,a1,a2,a3, rb, stack) \ + MOVQ a0, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a0, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a0, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a0, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + storeBlock(R8,R9,R10,R11, 0+stack) \ + MOVQ R12, 32+stack \ + \ + MOVQ a1, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a1, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a1, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a1, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 8+stack, R8 \ + ADCQ 16+stack, R9 \ + ADCQ 24+stack, R10 \ + ADCQ 32+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 8+stack) \ + MOVQ R12, 40+stack \ + \ + MOVQ a2, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a2, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a2, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a2, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 16+stack, R8 \ + ADCQ 24+stack, R9 \ + ADCQ 32+stack, R10 \ + ADCQ 40+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 16+stack) \ + MOVQ R12, 48+stack \ + \ + MOVQ a3, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a3, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a3, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a3, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 24+stack, R8 \ + ADCQ 32+stack, R9 \ + ADCQ 40+stack, R10 \ + ADCQ 48+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 24+stack) \ + MOVQ R12, 56+stack + +#define gfpReduce(stack) \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ ·np+0(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ ·np+0(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ ·np+0(SB), AX \ + MULQ 24+stack \ + ADDQ AX, R11 \ + \ + MOVQ ·np+8(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+8(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + MOVQ ·np+8(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R14 \ + \ + ADDQ R12, R9 \ + ADCQ R13, R10 \ + ADCQ R14, R11 \ + \ + MOVQ ·np+16(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+16(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + \ + ADDQ R12, R10 \ + ADCQ R13, R11 \ + \ + MOVQ ·np+24(SB), AX \ + MULQ 0+stack \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64+stack) \ + \ + \ // m * N + mul(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64+stack, 96+stack) \ + \ + \ // Add the 512-bit intermediate to m*N + loadBlock(96+stack, R8,R9,R10,R11) \ + loadBlock(128+stack, R12,R13,R14,R15) \ + \ + MOVQ $0, AX \ + ADDQ 0+stack, R8 \ + ADCQ 8+stack, R9 \ + ADCQ 16+stack, R10 \ + ADCQ 24+stack, R11 \ + ADCQ 32+stack, R12 \ + ADCQ 40+stack, R13 \ + ADCQ 48+stack, R14 \ + ADCQ 56+stack, R15 \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,R15,AX, R8,R9,R10,R11,BX) diff --git a/cryptography/bn256/mul_arm64.h b/cryptography/bn256/mul_arm64.h new file mode 100644 index 0000000..d405eb8 --- /dev/null +++ b/cryptography/bn256/mul_arm64.h @@ -0,0 +1,133 @@ +#define mul(c0,c1,c2,c3,c4,c5,c6,c7) \ + MUL R1, R5, c0 \ + UMULH R1, R5, c1 \ + MUL R1, R6, R0 \ + ADDS R0, c1 \ + UMULH R1, R6, c2 \ + MUL R1, R7, R0 \ + ADCS R0, c2 \ + UMULH R1, R7, c3 \ + MUL R1, R8, R0 \ + ADCS R0, c3 \ + UMULH R1, R8, c4 \ + ADCS ZR, c4 \ + \ + MUL R2, R5, R1 \ + UMULH R2, R5, R26 \ + MUL R2, R6, R0 \ + ADDS R0, R26 \ + UMULH R2, R6, R27 \ + MUL R2, R7, R0 \ + ADCS R0, R27 \ + UMULH R2, R7, R29 \ + MUL R2, R8, R0 \ + ADCS R0, R29 \ + UMULH R2, R8, c5 \ + ADCS ZR, c5 \ + ADDS R1, c1 \ + ADCS R26, c2 \ + ADCS R27, c3 \ + ADCS R29, c4 \ + ADCS ZR, c5 \ + \ + MUL R3, R5, R1 \ + UMULH R3, R5, R26 \ + MUL R3, R6, R0 \ + ADDS R0, R26 \ + UMULH R3, R6, R27 \ + MUL R3, R7, R0 \ + ADCS R0, R27 \ + UMULH R3, R7, R29 \ + MUL R3, R8, R0 \ + ADCS R0, R29 \ + UMULH R3, R8, c6 \ + ADCS ZR, c6 \ + ADDS R1, c2 \ + ADCS R26, c3 \ + ADCS R27, c4 \ + ADCS R29, c5 \ + ADCS ZR, c6 \ + \ + MUL R4, R5, R1 \ + UMULH R4, R5, R26 \ + MUL R4, R6, R0 \ + ADDS R0, R26 \ + UMULH R4, R6, R27 \ + MUL R4, R7, R0 \ + ADCS R0, R27 \ + UMULH R4, R7, R29 \ + MUL R4, R8, R0 \ + ADCS R0, R29 \ + UMULH R4, R8, c7 \ + ADCS ZR, c7 \ + ADDS R1, c3 \ + ADCS R26, c4 \ + ADCS R27, c5 \ + ADCS R29, c6 \ + ADCS ZR, c7 + +#define gfpReduce() \ + \ // m = (T * N') mod R, store m in R1:R2:R3:R4 + MOVD ·np+0(SB), R17 \ + MOVD ·np+8(SB), R25 \ + MOVD ·np+16(SB), R19 \ + MOVD ·np+24(SB), R20 \ + \ + MUL R9, R17, R1 \ + UMULH R9, R17, R2 \ + MUL R9, R25, R0 \ + ADDS R0, R2 \ + UMULH R9, R25, R3 \ + MUL R9, R19, R0 \ + ADCS R0, R3 \ + UMULH R9, R19, R4 \ + MUL R9, R20, R0 \ + ADCS R0, R4 \ + \ + MUL R10, R17, R21 \ + UMULH R10, R17, R22 \ + MUL R10, R25, R0 \ + ADDS R0, R22 \ + UMULH R10, R25, R23 \ + MUL R10, R19, R0 \ + ADCS R0, R23 \ + ADDS R21, R2 \ + ADCS R22, R3 \ + ADCS R23, R4 \ + \ + MUL R11, R17, R21 \ + UMULH R11, R17, R22 \ + MUL R11, R25, R0 \ + ADDS R0, R22 \ + ADDS R21, R3 \ + ADCS R22, R4 \ + \ + MUL R12, R17, R21 \ + ADDS R21, R4 \ + \ + \ // m * N + loadModulus(R5,R6,R7,R8) \ + mul(R17,R25,R19,R20,R21,R22,R23,R24) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVD ZR, R0 \ + ADDS R9, R17 \ + ADCS R10, R25 \ + ADCS R11, R19 \ + ADCS R12, R20 \ + ADCS R13, R21 \ + ADCS R14, R22 \ + ADCS R15, R23 \ + ADCS R16, R24 \ + ADCS ZR, R0 \ + \ + \ // Our output is R21:R22:R23:R24. Reduce mod p if necessary. + SUBS R5, R21, R10 \ + SBCS R6, R22, R11 \ + SBCS R7, R23, R12 \ + SBCS R8, R24, R13 \ + \ + CSEL CS, R10, R21, R1 \ + CSEL CS, R11, R22, R2 \ + CSEL CS, R12, R23, R3 \ + CSEL CS, R13, R24, R4 diff --git a/cryptography/bn256/mul_bmi2_amd64.h b/cryptography/bn256/mul_bmi2_amd64.h new file mode 100644 index 0000000..71ad049 --- /dev/null +++ b/cryptography/bn256/mul_bmi2_amd64.h @@ -0,0 +1,112 @@ +#define mulBMI2(a0,a1,a2,a3, rb) \ + MOVQ a0, DX \ + MOVQ $0, R13 \ + MULXQ 0+rb, R8, R9 \ + MULXQ 8+rb, AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16+rb, AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24+rb, AX, R12 \ + ADCQ AX, R11 \ + ADCQ $0, R12 \ + ADCQ $0, R13 \ + \ + MOVQ a1, DX \ + MOVQ $0, R14 \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R11 \ + ADCQ BX, R12 \ + ADCQ $0, R13 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + \ + MOVQ a2, DX \ + MOVQ $0, R15 \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, R15 \ + \ + MOVQ a3, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, R15 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R15 + +#define gfpReduceBMI2() \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), DX \ + MULXQ 0(SP), R8, R9 \ + MULXQ 8(SP), AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16(SP), AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24(SP), AX, BX \ + ADCQ AX, R11 \ + \ + MOVQ ·np+8(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16(SP), AX, BX \ + ADCQ AX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + \ + MOVQ ·np+16(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R11 \ + \ + MOVQ ·np+24(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64(SP)) \ + \ + \ // m * N + mulBMI2(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64(SP)) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVQ $0, AX \ + ADDQ 0(SP), R8 \ + ADCQ 8(SP), R9 \ + ADCQ 16(SP), R10 \ + ADCQ 24(SP), R11 \ + ADCQ 32(SP), R12 \ + ADCQ 40(SP), R13 \ + ADCQ 48(SP), R14 \ + ADCQ 56(SP), R15 \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,R15,AX, R8,R9,R10,R11,BX) diff --git a/cryptography/bn256/optate.go b/cryptography/bn256/optate.go new file mode 100644 index 0000000..b71e50e --- /dev/null +++ b/cryptography/bn256/optate.go @@ -0,0 +1,271 @@ +package bn256 + +func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2) (a, b, c *gfP2, rOut *twistPoint) { + // See the mixed addition algorithm from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + B := (&gfP2{}).Mul(&p.x, &r.t) + + D := (&gfP2{}).Add(&p.y, &r.z) + D.Square(D).Sub(D, r2).Sub(D, &r.t).Mul(D, &r.t) + + H := (&gfP2{}).Sub(B, &r.x) + I := (&gfP2{}).Square(H) + + E := (&gfP2{}).Add(I, I) + E.Add(E, E) + + J := (&gfP2{}).Mul(H, E) + + L1 := (&gfP2{}).Sub(D, &r.y) + L1.Sub(L1, &r.y) + + V := (&gfP2{}).Mul(&r.x, E) + + rOut = &twistPoint{} + rOut.x.Square(L1).Sub(&rOut.x, J).Sub(&rOut.x, V).Sub(&rOut.x, V) + + rOut.z.Add(&r.z, H).Square(&rOut.z).Sub(&rOut.z, &r.t).Sub(&rOut.z, I) + + t := (&gfP2{}).Sub(V, &rOut.x) + t.Mul(t, L1) + t2 := (&gfP2{}).Mul(&r.y, J) + t2.Add(t2, t2) + rOut.y.Sub(t, t2) + + rOut.t.Square(&rOut.z) + + t.Add(&p.y, &rOut.z).Square(t).Sub(t, r2).Sub(t, &rOut.t) + + t2.Mul(L1, &p.x) + t2.Add(t2, t2) + a = (&gfP2{}).Sub(t2, t) + + c = (&gfP2{}).MulScalar(&rOut.z, &q.y) + c.Add(c, c) + + b = (&gfP2{}).Neg(L1) + b.MulScalar(b, &q.x).Add(b, b) + + return +} + +func lineFunctionDouble(r *twistPoint, q *curvePoint) (a, b, c *gfP2, rOut *twistPoint) { + // See the doubling algorithm for a=0 from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + A := (&gfP2{}).Square(&r.x) + B := (&gfP2{}).Square(&r.y) + C := (&gfP2{}).Square(B) + + D := (&gfP2{}).Add(&r.x, B) + D.Square(D).Sub(D, A).Sub(D, C).Add(D, D) + + E := (&gfP2{}).Add(A, A) + E.Add(E, A) + + G := (&gfP2{}).Square(E) + + rOut = &twistPoint{} + rOut.x.Sub(G, D).Sub(&rOut.x, D) + + rOut.z.Add(&r.y, &r.z).Square(&rOut.z).Sub(&rOut.z, B).Sub(&rOut.z, &r.t) + + rOut.y.Sub(D, &rOut.x).Mul(&rOut.y, E) + t := (&gfP2{}).Add(C, C) + t.Add(t, t).Add(t, t) + rOut.y.Sub(&rOut.y, t) + + rOut.t.Square(&rOut.z) + + t.Mul(E, &r.t).Add(t, t) + b = (&gfP2{}).Neg(t) + b.MulScalar(b, &q.x) + + a = (&gfP2{}).Add(&r.x, E) + a.Square(a).Sub(a, A).Sub(a, G) + t.Add(B, B).Add(t, t) + a.Sub(a, t) + + c = (&gfP2{}).Mul(&rOut.z, &r.t) + c.Add(c, c).MulScalar(c, &q.y) + + return +} + +func mulLine(ret *gfP12, a, b, c *gfP2) { + a2 := &gfP6{} + a2.y.Set(a) + a2.z.Set(b) + a2.Mul(a2, &ret.x) + t3 := (&gfP6{}).MulScalar(&ret.y, c) + + t := (&gfP2{}).Add(b, c) + t2 := &gfP6{} + t2.y.Set(a) + t2.z.Set(t) + ret.x.Add(&ret.x, &ret.y) + + ret.y.Set(t3) + + ret.x.Mul(&ret.x, t2).Sub(&ret.x, a2).Sub(&ret.x, &ret.y) + a2.MulTau(a2) + ret.y.Add(&ret.y, a2) +} + +// sixuPlus2NAF is 6u+2 in non-adjacent form. +var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1} + +// miller implements the Miller loop for calculating the Optimal Ate pairing. +// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf +func miller(q *twistPoint, p *curvePoint) *gfP12 { + ret := (&gfP12{}).SetOne() + + aAffine := &twistPoint{} + aAffine.Set(q) + aAffine.MakeAffine() + + bAffine := &curvePoint{} + bAffine.Set(p) + bAffine.MakeAffine() + + minusA := &twistPoint{} + minusA.Neg(aAffine) + + r := &twistPoint{} + r.Set(aAffine) + + r2 := (&gfP2{}).Square(&aAffine.y) + + for i := len(sixuPlus2NAF) - 1; i > 0; i-- { + a, b, c, newR := lineFunctionDouble(r, bAffine) + if i != len(sixuPlus2NAF)-1 { + ret.Square(ret) + } + + mulLine(ret, a, b, c) + r = newR + + switch sixuPlus2NAF[i-1] { + case 1: + a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2) + case -1: + a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2) + default: + continue + } + + mulLine(ret, a, b, c) + r = newR + } + + // In order to calculate Q1 we have to convert q from the sextic twist + // to the full GF(p^12) group, apply the Frobenius there, and convert + // back. + // + // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just + // x for a moment, then after applying the Frobenius, we have x̄ω^(2p) + // where x̄ is the conjugate of x. If we are going to apply the inverse + // isomorphism we need a value with a single coefficient of ω² so we + // rewrite this as x̄ω^(2p-2)ω². ξ⁶ = ω and, due to the construction of + // p, 2p-2 is a multiple of six. Therefore we can rewrite as + // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the + // ω². + // + // A similar argument can be made for the y value. + + q1 := &twistPoint{} + q1.x.Conjugate(&aAffine.x).Mul(&q1.x, xiToPMinus1Over3) + q1.y.Conjugate(&aAffine.y).Mul(&q1.y, xiToPMinus1Over2) + q1.z.SetOne() + q1.t.SetOne() + + // For Q2 we are applying the p² Frobenius. The two conjugations cancel + // out and we are left only with the factors from the isomorphism. In + // the case of x, we end up with a pure number which is why + // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We + // ignore this to end up with -Q2. + + minusQ2 := &twistPoint{} + minusQ2.x.MulScalar(&aAffine.x, xiToPSquaredMinus1Over3) + minusQ2.y.Set(&aAffine.y) + minusQ2.z.SetOne() + minusQ2.t.SetOne() + + r2.Square(&q1.y) + a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2) + mulLine(ret, a, b, c) + r = newR + + r2.Square(&minusQ2.y) + a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2) + mulLine(ret, a, b, c) + r = newR + + return ret +} + +// finalExponentiation computes the (p¹²-1)/Order-th power of an element of +// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from +// http://cryptojedi.org/papers/dclxvi-20100714.pdf) +func finalExponentiation(in *gfP12) *gfP12 { + t1 := &gfP12{} + + // This is the p^6-Frobenius + t1.x.Neg(&in.x) + t1.y.Set(&in.y) + + inv := &gfP12{} + inv.Invert(in) + t1.Mul(t1, inv) + + t2 := (&gfP12{}).FrobeniusP2(t1) + t1.Mul(t1, t2) + + fp := (&gfP12{}).Frobenius(t1) + fp2 := (&gfP12{}).FrobeniusP2(t1) + fp3 := (&gfP12{}).Frobenius(fp2) + + fu := (&gfP12{}).Exp(t1, u) + fu2 := (&gfP12{}).Exp(fu, u) + fu3 := (&gfP12{}).Exp(fu2, u) + + y3 := (&gfP12{}).Frobenius(fu) + fu2p := (&gfP12{}).Frobenius(fu2) + fu3p := (&gfP12{}).Frobenius(fu3) + y2 := (&gfP12{}).FrobeniusP2(fu2) + + y0 := &gfP12{} + y0.Mul(fp, fp2).Mul(y0, fp3) + + y1 := (&gfP12{}).Conjugate(t1) + y5 := (&gfP12{}).Conjugate(fu2) + y3.Conjugate(y3) + y4 := (&gfP12{}).Mul(fu, fu2p) + y4.Conjugate(y4) + + y6 := (&gfP12{}).Mul(fu3, fu3p) + y6.Conjugate(y6) + + t0 := (&gfP12{}).Square(y6) + t0.Mul(t0, y4).Mul(t0, y5) + t1.Mul(y3, y5).Mul(t1, t0) + t0.Mul(t0, y2) + t1.Square(t1).Mul(t1, t0).Square(t1) + t0.Mul(t1, y1) + t1.Mul(t1, y0) + t0.Square(t0).Mul(t0, t1) + + return t0 +} + +func optimalAte(a *twistPoint, b *curvePoint) *gfP12 { + e := miller(a, b) + ret := finalExponentiation(e) + + if a.IsInfinity() || b.IsInfinity() { + ret.SetOne() + } + return ret +} diff --git a/cryptography/bn256/twist.go b/cryptography/bn256/twist.go new file mode 100644 index 0000000..865a930 --- /dev/null +++ b/cryptography/bn256/twist.go @@ -0,0 +1,217 @@ +package bn256 + +import ( + "math/big" +) + +// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are +// kept in Jacobian form and t=z² when valid. The group G₂ is the set of +// n-torsion points of this curve over GF(p²) (where n = Order) +type twistPoint struct { + x, y, z, t gfP2 +} + +// +// btwist = 3 / Fp2(i + 9); btwist +// # 266929791119991161246907387137283842545076965332900288569378510910307636690*i + 19485874751759354771024239261021720505790618469301721065564631296452457478373 +// hex(266929791119991161246907387137283842545076965332900288569378510910307636690) +// # 009713b03af0fed4 cd2cafadeed8fdf4 a74fa084e52d1852 e4a2bd0685c315d2 +// hex(19485874751759354771024239261021720505790618469301721065564631296452457478373) +// # 2b149d40ceb8aaae 81be18991be06ac3 b5b4c5e559dbefa3 3267e6dc24a138e5 +// <\sage> +// +// c0 = 19485874751759354771024239261021720505790618469301721065564631296452457478373 +// c1 = 266929791119991161246907387137283842545076965332900288569378510910307636690 +// +// twistB is the montgomery encoding of the btwist obtained above +var twistB = &gfP2{ + gfP{0x38e7ecccd1dcff67, 0x65f0b37d93ce0d3e, 0xd749d0dd22ac00aa, 0x0141b9ce4a688d4d}, + gfP{0x3bf938e377b802a8, 0x020b1b273633535d, 0x26b7edf049755260, 0x2514c6324384a86d}, +} + +// twistGen is the generator of group G₂. +var twistGen = &twistPoint{ + gfP2{ + gfP{0xafb4737da84c6140, 0x6043dd5a5802d8c4, 0x09e950fc52a02f86, 0x14fef0833aea7b6b}, + gfP{0x8e83b5d102bc2026, 0xdceb1935497b0172, 0xfbb8264797811adf, 0x19573841af96503b}, + }, + gfP2{ + gfP{0x64095b56c71856ee, 0xdc57f922327d3cbb, 0x55f935be33351076, 0x0da4a0e693fd6482}, + gfP{0x619dfa9d886be9f6, 0xfe7fd297f59e9b78, 0xff9e1a62231b7dfe, 0x28fd7eebae9e4206}, + }, + gfP2{*newGFp(0), *newGFp(1)}, + gfP2{*newGFp(0), *newGFp(1)}, +} + +func (c *twistPoint) String() string { + c.MakeAffine() + x, y := gfP2Decode(&c.x), gfP2Decode(&c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *twistPoint) Set(a *twistPoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *twistPoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP2{}, &gfP2{} + y2.Square(&c.y) + x3.Square(&c.x).Mul(x3, &c.x).Add(x3, twistB) + + if *y2 != *x3 { + return false + } + cneg := &twistPoint{} + cneg.Mul(c, Order) + return cneg.z.IsZero() +} + +func (c *twistPoint) SetInfinity() { + c.x.SetZero() + c.y.SetOne() + c.z.SetZero() + c.t.SetZero() +} + +func (c *twistPoint) IsInfinity() bool { + return c.z.IsZero() +} + +func (c *twistPoint) Add(a, b *twistPoint) { + // For additional comments, see the same function in curve.go. + + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + z12 := (&gfP2{}).Square(&a.z) + z22 := (&gfP2{}).Square(&b.z) + u1 := (&gfP2{}).Mul(&a.x, z22) + u2 := (&gfP2{}).Mul(&b.x, z12) + + t := (&gfP2{}).Mul(&b.z, z22) + s1 := (&gfP2{}).Mul(&a.y, t) + + t.Mul(&a.z, z12) + s2 := (&gfP2{}).Mul(&b.y, t) + + h := (&gfP2{}).Sub(u2, u1) + xEqual := h.IsZero() + + t.Add(h, h) + i := (&gfP2{}).Square(t) + j := (&gfP2{}).Mul(h, i) + + t.Sub(s2, s1) + yEqual := t.IsZero() + if xEqual && yEqual { + c.Double(a) + return + } + r := (&gfP2{}).Add(t, t) + + v := (&gfP2{}).Mul(u1, i) + + t4 := (&gfP2{}).Square(r) + t.Add(v, v) + t6 := (&gfP2{}).Sub(t4, j) + c.x.Sub(t6, t) + + t.Sub(v, &c.x) // t7 + t4.Mul(s1, j) // t8 + t6.Add(t4, t4) // t9 + t4.Mul(r, t) // t10 + c.y.Sub(t4, t6) + + t.Add(&a.z, &b.z) // t11 + t4.Square(t) // t12 + t.Sub(t4, z12) // t13 + t4.Sub(t, z22) // t14 + c.z.Mul(t4, h) +} + +func (c *twistPoint) Double(a *twistPoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := (&gfP2{}).Square(&a.x) + B := (&gfP2{}).Square(&a.y) + C := (&gfP2{}).Square(B) + + t := (&gfP2{}).Add(&a.x, B) + t2 := (&gfP2{}).Square(t) + t.Sub(t2, A) + t2.Sub(t, C) + d := (&gfP2{}).Add(t2, t2) + t.Add(A, A) + e := (&gfP2{}).Add(t, A) + f := (&gfP2{}).Square(e) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C, C) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, &c.x) + t2.Mul(e, &c.y) + c.y.Sub(t2, t) + + t.Mul(&a.y, &a.z) + c.z.Add(t, t) +} + +func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int) { + sum, t := &twistPoint{}, &twistPoint{} + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum) + if scalar.Bit(i) != 0 { + sum.Add(t, a) + } else { + sum.Set(t) + } + } + + c.Set(sum) +} + +func (c *twistPoint) MakeAffine() { + if c.z.IsOne() { + return + } else if c.z.IsZero() { + c.x.SetZero() + c.y.SetOne() + c.t.SetZero() + return + } + + zInv := (&gfP2{}).Invert(&c.z) + t := (&gfP2{}).Mul(&c.y, zInv) + zInv2 := (&gfP2{}).Square(zInv) + c.y.Mul(t, zInv2) + t.Mul(&c.x, zInv2) + c.x.Set(t) + c.z.SetOne() + c.t.SetOne() +} + +func (c *twistPoint) Neg(a *twistPoint) { + c.x.Set(&a.x) + c.y.Neg(&a.y) + c.z.Set(&a.z) + c.t.SetZero() +} diff --git a/cryptography/crypto/LICENSE b/cryptography/crypto/LICENSE new file mode 100644 index 0000000..26fca3f --- /dev/null +++ b/cryptography/crypto/LICENSE @@ -0,0 +1,90 @@ +RESEARCH LICENSE + + +Version 1.1.2 + +I. DEFINITIONS. + +"Licensee " means You and any other party that has entered into and has in effect a version of this License. + +“Licensor” means DERO PROJECT(GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8) and its successors and assignees. + +"Modifications" means any (a) change or addition to the Technology or (b) new source or object code implementing any portion of the Technology. + +"Research Use" means research, evaluation, or development for the purpose of advancing knowledge, teaching, learning, or customizing the Technology for personal use. Research Use expressly excludes use or distribution for direct or indirect commercial (including strategic) gain or advantage. + +"Technology" means the source code, object code and specifications of the technology made available by Licensor pursuant to this License. + +"Technology Site" means the website designated by Licensor for accessing the Technology. + +"You" means the individual executing this License or the legal entity or entities represented by the individual executing this License. + +II. PURPOSE. + +Licensor is licensing the Technology under this Research License (the "License") to promote research, education, innovation, and development using the Technology. + +COMMERCIAL USE AND DISTRIBUTION OF TECHNOLOGY AND MODIFICATIONS IS PERMITTED ONLY UNDER AN APPROPRIATE COMMERCIAL USE LICENSE AVAILABLE FROM LICENSOR AT . + +III. RESEARCH USE RIGHTS. + +A. Subject to the conditions contained herein, Licensor grants to You a non-exclusive, non-transferable, worldwide, and royalty-free license to do the following for Your Research Use only: + +1. reproduce, create Modifications of, and use the Technology alone, or with Modifications; +2. share source code of the Technology alone, or with Modifications, with other Licensees; + +3. distribute object code of the Technology, alone, or with Modifications, to any third parties for Research Use only, under a license of Your choice that is consistent with this License; and + +4. publish papers and books discussing the Technology which may include relevant excerpts that do not in the aggregate constitute a significant portion of the Technology. + +B. Residual Rights. You may use any information in intangible form that you remember after accessing the Technology, except when such use violates Licensor's copyrights or patent rights. + +C. No Implied Licenses. Other than the rights granted herein, Licensor retains all rights, title, and interest in Technology , and You retain all rights, title, and interest in Your Modifications and associated specifications, subject to the terms of this License. + +D. Open Source Licenses. Portions of the Technology may be provided with notices and open source licenses from open source communities and third parties that govern the use of those portions, and any licenses granted hereunder do not alter any rights and obligations you may have under such open source licenses, however, the disclaimer of warranty and limitation of liability provisions in this License will apply to all Technology in this distribution. + +IV. INTELLECTUAL PROPERTY REQUIREMENTS + +As a condition to Your License, You agree to comply with the following restrictions and responsibilities: + +A. License and Copyright Notices. You must include a copy of this License in a Readme file for any Technology or Modifications you distribute. You must also include the following statement, "Use and distribution of this technology is subject to the Java Research License included herein", (a) once prominently in the source code tree and/or specifications for Your source code distributions, and (b) once in the same file as Your copyright or proprietary notices for Your binary code distributions. You must cause any files containing Your Modification to carry prominent notice stating that You changed the files. You must not remove or alter any copyright or other proprietary notices in the Technology. + +B. Licensee Exchanges. Any Technology and Modifications You receive from any Licensee are governed by this License. + +V. GENERAL TERMS. + +A. Disclaimer Of Warranties. + +TECHNOLOGY IS PROVIDED "AS IS", WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT ANY SUCH TECHNOLOGY IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING OF THIRD PARTY RIGHTS. YOU AGREE THAT YOU BEAR THE ENTIRE RISK IN CONNECTION WITH YOUR USE AND DISTRIBUTION OF ANY AND ALL TECHNOLOGY UNDER THIS LICENSE. + +B. Infringement; Limitation Of Liability. + +1. If any portion of, or functionality implemented by, the Technology becomes the subject of a claim or threatened claim of infringement ("Affected Materials"), Licensor may, in its unrestricted discretion, suspend Your rights to use and distribute the Affected Materials under this License. Such suspension of rights will be effective immediately upon Licensor's posting of notice of suspension on the Technology Site. + +2. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING OUT OF THIS LICENSE (INCLUDING, WITHOUT LIMITATION, LOSS OF PROFITS, USE, DATA, OR ECONOMIC ADVANTAGE OF ANY SORT), HOWEVER IT ARISES AND ON ANY THEORY OF LIABILITY (including negligence), WHETHER OR NOT LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. LIABILITY UNDER THIS SECTION V.B.2 SHALL BE SO LIMITED AND EXCLUDED, NOTWITHSTANDING FAILURE OF THE ESSENTIAL PURPOSE OF ANY REMEDY. + +C. Termination. + +1. You may terminate this License at any time by notifying Licensor in writing. + +2. All Your rights will terminate under this License if You fail to comply with any of its material terms or conditions and do not cure such failure within thirty (30) days after becoming aware of such noncompliance. + +3. Upon termination, You must discontinue all uses and distribution of the Technology , and all provisions of this Section V shall survive termination. + +D. Miscellaneous. + +1. Trademark. You agree to comply with Licensor's Trademark & Logo Usage Requirements, if any and as modified from time to time, available at the Technology Site. Except as expressly provided in this License, You are granted no rights in or to any Licensor's trademarks now or hereafter used or licensed by Licensor. + +2. Integration. This License represents the complete agreement of the parties concerning the subject matter hereof. + +3. Severability. If any provision of this License is held unenforceable, such provision shall be reformed to the extent necessary to make it enforceable unless to do so would defeat the intent of the parties, in which case, this License shall terminate. + +4. Governing Law. This License is governed by the laws of the United States and the State of California, as applied to contracts entered into and performed in California between California residents. In no event shall this License be construed against the drafter. + +5. Export Control. You agree to comply with the U.S. export controlsand trade laws of other countries that apply to Technology and Modifications. + +READ ALL THE TERMS OF THIS LICENSE CAREFULLY BEFORE ACCEPTING. + +BY CLICKING ON THE YES BUTTON BELOW OR USING THE TECHNOLOGY, YOU ARE ACCEPTING AND AGREEING TO ABIDE BY THE TERMS AND CONDITIONS OF THIS LICENSE. YOU MUST BE AT LEAST 18 YEARS OF AGE AND OTHERWISE COMPETENT TO ENTER INTO CONTRACTS. + +IF YOU DO NOT MEET THESE CRITERIA, OR YOU DO NOT AGREE TO ANY OF THE TERMS OF THIS LICENSE, DO NOT USE THIS SOFTWARE IN ANY FORM. + diff --git a/cryptography/crypto/algebra_elgamal.go b/cryptography/crypto/algebra_elgamal.go new file mode 100644 index 0000000..35098b3 --- /dev/null +++ b/cryptography/crypto/algebra_elgamal.go @@ -0,0 +1,177 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +// a ZERO +var ElGamal_ZERO *bn256.G1 +var ElGamal_ZERO_string string +var ElGamal_BASE_G *bn256.G1 + +type ElGamal struct { + G *bn256.G1 + Randomness *big.Int + Left *bn256.G1 + Right *bn256.G1 +} + +func NewElGamal() (p *ElGamal) { + return &ElGamal{G: global_pedersen_values.G} +} +func CommitElGamal(key *bn256.G1, value *big.Int) *ElGamal { + e := NewElGamal() + e.Randomness = RandomScalarFixed() + e.Left = new(bn256.G1).Add(new(bn256.G1).ScalarMult(e.G, value), new(bn256.G1).ScalarMult(key, e.Randomness)) + e.Right = new(bn256.G1).ScalarMult(G, e.Randomness) + return e +} + +func ConstructElGamal(left, right *bn256.G1) *ElGamal { + e := NewElGamal() + + if left != nil { + e.Left = new(bn256.G1).Set(left) + } + e.Right = new(bn256.G1).Set(right) + return e +} + +func (e *ElGamal) IsZero() bool { + if e.Left != nil && e.Right != nil && e.Left.String() == ElGamal_ZERO_string && e.Right.String() == ElGamal_ZERO_string { + return true + } + return false +} + +func (e *ElGamal) Add(addendum *ElGamal) *ElGamal { + if e.Left == nil { + return ConstructElGamal(nil, new(bn256.G1).Add(e.Right, addendum.Right)) + } + return ConstructElGamal(new(bn256.G1).Add(e.Left, addendum.Left), new(bn256.G1).Add(e.Right, addendum.Right)) +} + +func (e *ElGamal) Mul(scalar *big.Int) *ElGamal { + return ConstructElGamal(new(bn256.G1).ScalarMult(e.Left, scalar), new(bn256.G1).ScalarMult(e.Right, scalar)) +} + +func (e *ElGamal) Plus(value *big.Int) *ElGamal { + if e.Right == nil { + return ConstructElGamal(new(bn256.G1).Add(e.Left, new(bn256.G1).ScalarMult(e.G, value)), nil) + } + return ConstructElGamal(new(bn256.G1).Add(e.Left, new(bn256.G1).ScalarMult(e.G, value)), new(bn256.G1).Set(e.Right)) +} + +func (e *ElGamal) Serialize() (data []byte) { + if e.Left == nil || e.Right == nil { + panic("elgamal has nil pointer") + } + data = append(data, e.Left.EncodeUncompressed()...) + data = append(data, e.Right.EncodeUncompressed()...) + return data +} + +func (e *ElGamal) Deserialize(data []byte) *ElGamal { + + if len(data) != 130 { + panic("insufficient buffer size") + } + //var left,right *bn256.G1 + left := new(bn256.G1) + right := new(bn256.G1) + + if err := left.DecodeUncompressed(data[:65]); err != nil { + panic(err) + } + if err := right.DecodeUncompressed(data[65:130]); err != nil { + panic(err) + } + e = ConstructElGamal(left, right) + return e +} + +func (e *ElGamal) Neg() *ElGamal { + + var left, right *bn256.G1 + if e.Left != nil { + left = new(bn256.G1).Neg(e.Left) + } + if e.Right != nil { + right = new(bn256.G1).Neg(e.Right) + } + return ConstructElGamal(left, right) +} + +type ElGamalVector struct { + vector []*ElGamal +} + +func (e *ElGamalVector) MultiExponentiate(exponents *FieldVector) *ElGamal { + accumulator := ConstructElGamal(ElGamal_ZERO, ElGamal_ZERO) + for i := range exponents.vector { + accumulator = accumulator.Add(e.vector[i].Mul(exponents.vector[i])) + } + return accumulator +} + +func (e *ElGamalVector) Sum() *ElGamal { + r := ConstructElGamal(ElGamal_ZERO, ElGamal_ZERO) + for i := range e.vector { + r = r.Add(e.vector[i]) + } + return r +} + +func (e *ElGamalVector) Add(other *ElGamalVector) *ElGamalVector { + var r ElGamalVector + for i := range e.vector { + r.vector = append(r.vector, ConstructElGamal(e.vector[i].Left, e.vector[i].Right)) + } + + for i := range other.vector { + r.vector[i] = r.vector[i].Add(other.vector[i]) + } + return &r +} + +func (e *ElGamalVector) Hadamard(exponents FieldVector) *ElGamalVector { + var r ElGamalVector + for i := range e.vector { + r.vector = append(r.vector, ConstructElGamal(e.vector[i].Left, e.vector[i].Right)) + } + + for i := range exponents.vector { + r.vector[i] = r.vector[i].Mul(exponents.vector[i]) + } + return &r +} + +func (e *ElGamalVector) Times(scalar *big.Int) *ElGamalVector { + var r ElGamalVector + for i := range e.vector { + r.vector = append(r.vector, e.vector[i].Mul(scalar)) + } + return &r +} diff --git a/cryptography/crypto/algebra_fieldvector.go b/cryptography/crypto/algebra_fieldvector.go new file mode 100644 index 0000000..97a6892 --- /dev/null +++ b/cryptography/crypto/algebra_fieldvector.go @@ -0,0 +1,201 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +type FieldVector struct { + vector []*big.Int +} + +func NewFieldVector(input []*big.Int) *FieldVector { + return &FieldVector{vector: input} +} + +func NewFieldVectorRandomFilled(capacity int) *FieldVector { + fv := &FieldVector{vector: make([]*big.Int, capacity, capacity)} + for i := range fv.vector { + fv.vector[i] = RandomScalarFixed() + } + return fv +} + +func (fv *FieldVector) Length() int { + return len(fv.vector) +} + +// slice and return +func (fv *FieldVector) Slice(start, end int) *FieldVector { + var result FieldVector + for i := start; i < end; i++ { + result.vector = append(result.vector, new(big.Int).Set(fv.vector[i])) + } + return &result +} + +//copy and return +func (fv *FieldVector) Clone() *FieldVector { + return fv.Slice(0, len(fv.vector)) +} + +func (fv *FieldVector) SliceRaw(start, end int) []*big.Int { + var result FieldVector + for i := start; i < end; i++ { + result.vector = append(result.vector, new(big.Int).Set(fv.vector[i])) + } + return result.vector +} + +func (fv *FieldVector) Flip() *FieldVector { + var result FieldVector + for i := range fv.vector { + result.vector = append(result.vector, new(big.Int).Set(fv.vector[(len(fv.vector)-i)%len(fv.vector)])) + } + return &result +} + +func (fv *FieldVector) Sum() *big.Int { + var accumulator big.Int + + for i := range fv.vector { + var accopy big.Int + + accopy.Add(&accumulator, fv.vector[i]) + accumulator.Mod(&accopy, bn256.Order) + } + + return &accumulator +} + +func (fv *FieldVector) Add(addendum *FieldVector) *FieldVector { + var result FieldVector + + if len(fv.vector) != len(addendum.vector) { + panic("mismatched number of elements") + } + + for i := range fv.vector { + var ri big.Int + ri.Mod(new(big.Int).Add(fv.vector[i], addendum.vector[i]), bn256.Order) + result.vector = append(result.vector, &ri) + } + + return &result +} + +func (gv *FieldVector) AddConstant(c *big.Int) *FieldVector { + var result FieldVector + + for i := range gv.vector { + var ri big.Int + ri.Mod(new(big.Int).Add(gv.vector[i], c), bn256.Order) + result.vector = append(result.vector, &ri) + } + + return &result +} + +func (fv *FieldVector) Hadamard(exponent *FieldVector) *FieldVector { + var result FieldVector + + if len(fv.vector) != len(exponent.vector) { + panic("mismatched number of elements") + } + for i := range fv.vector { + result.vector = append(result.vector, new(big.Int).Mod(new(big.Int).Mul(fv.vector[i], exponent.vector[i]), bn256.Order)) + } + + return &result +} + +func (fv *FieldVector) InnerProduct(exponent *FieldVector) *big.Int { + if len(fv.vector) != len(exponent.vector) { + panic("mismatched number of elements") + } + + accumulator := new(big.Int) + for i := range fv.vector { + tmp := new(big.Int).Mod(new(big.Int).Mul(fv.vector[i], exponent.vector[i]), bn256.Order) + accumulator.Add(accumulator, tmp) + accumulator.Mod(accumulator, bn256.Order) + } + + return accumulator +} + +func (fv *FieldVector) Negate() *FieldVector { + var result FieldVector + for i := range fv.vector { + result.vector = append(result.vector, new(big.Int).Mod(new(big.Int).Neg(fv.vector[i]), bn256.Order)) + } + return &result +} + +func (fv *FieldVector) Times(multiplier *big.Int) *FieldVector { + var result FieldVector + for i := range fv.vector { + res := new(big.Int).Mul(fv.vector[i], multiplier) + res.Mod(res, bn256.Order) + result.vector = append(result.vector, res) + } + return &result +} + +func (fv *FieldVector) Invert() *FieldVector { + var result FieldVector + for i := range fv.vector { + result.vector = append(result.vector, new(big.Int).ModInverse(fv.vector[i], bn256.Order)) + } + return &result +} + +func (fv *FieldVector) Concat(addendum *FieldVector) *FieldVector { + var result FieldVector + for i := range fv.vector { + result.vector = append(result.vector, new(big.Int).Set(fv.vector[i])) + } + + for i := range addendum.vector { + result.vector = append(result.vector, new(big.Int).Set(addendum.vector[i])) + } + + return &result +} + +func (fv *FieldVector) Extract(parity bool) *FieldVector { + var result FieldVector + + remainder := 0 + if parity { + remainder = 1 + } + for i := range fv.vector { + if i%2 == remainder { + + result.vector = append(result.vector, new(big.Int).Set(fv.vector[i])) + } + } + return &result +} diff --git a/cryptography/crypto/algebra_pedersen.go b/cryptography/crypto/algebra_pedersen.go new file mode 100644 index 0000000..ae17ea7 --- /dev/null +++ b/cryptography/crypto/algebra_pedersen.go @@ -0,0 +1,110 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +var G *bn256.G1 +var global_pedersen_values PedersenVectorCommitment + +func init() { + var zeroes [64]byte + var gs, hs []*bn256.G1 + + global_pedersen_values.G = HashToPoint(HashtoNumber([]byte(PROTOCOL_CONSTANT + "G"))) // this is same as mybase or vice-versa + global_pedersen_values.H = HashToPoint(HashtoNumber([]byte(PROTOCOL_CONSTANT + "H"))) + + global_pedersen_values.GSUM = new(bn256.G1) + global_pedersen_values.GSUM.Unmarshal(zeroes[:]) + + for i := 0; i < 128; i++ { + gs = append(gs, HashToPoint(HashtoNumber(append([]byte(PROTOCOL_CONSTANT+"G"), hextobytes(makestring64(fmt.Sprintf("%x", i)))...)))) + hs = append(hs, HashToPoint(HashtoNumber(append([]byte(PROTOCOL_CONSTANT+"H"), hextobytes(makestring64(fmt.Sprintf("%x", i)))...)))) + + global_pedersen_values.GSUM = new(bn256.G1).Add(global_pedersen_values.GSUM, gs[i]) + } + global_pedersen_values.Gs = NewPointVector(gs) + global_pedersen_values.Hs = NewPointVector(hs) + + // also initialize elgamal_zero + ElGamal_ZERO = new(bn256.G1).ScalarMult(global_pedersen_values.G, new(big.Int).SetUint64(0)) + ElGamal_ZERO_string = ElGamal_ZERO.String() + ElGamal_BASE_G = global_pedersen_values.G + G = global_pedersen_values.G + ((*bn256.G1)(&GPoint)).Set(G) // setup base point + + // fmt.Printf("basepoint %s on %x\n", G.String(), G.Marshal()) +} + +type PedersenCommitmentNew struct { + G *bn256.G1 + H *bn256.G1 + Randomness *big.Int + Result *bn256.G1 +} + +func NewPedersenCommitmentNew() (p *PedersenCommitmentNew) { + return &PedersenCommitmentNew{G: global_pedersen_values.G, H: global_pedersen_values.H} +} + +// commit a specific value to specific bases +func (p *PedersenCommitmentNew) Commit(value *big.Int) *PedersenCommitmentNew { + p.Randomness = RandomScalarFixed() + point := new(bn256.G1).Add(new(bn256.G1).ScalarMult(p.G, value), new(bn256.G1).ScalarMult(p.H, p.Randomness)) + p.Result = new(bn256.G1).Set(point) + return p +} + +type PedersenVectorCommitment struct { + G *bn256.G1 + H *bn256.G1 + GSUM *bn256.G1 + + Gs *PointVector + Hs *PointVector + Randomness *big.Int + Result *bn256.G1 + + gvalues *FieldVector + hvalues *FieldVector +} + +func NewPedersenVectorCommitment() (p *PedersenVectorCommitment) { + p = &PedersenVectorCommitment{} + *p = global_pedersen_values + return +} + +// commit a specific value to specific bases +func (p *PedersenVectorCommitment) Commit(gvalues, hvalues *FieldVector) *PedersenVectorCommitment { + + p.Randomness = RandomScalarFixed() + point := new(bn256.G1).ScalarMult(p.H, p.Randomness) + point = new(bn256.G1).Add(point, p.Gs.MultiExponentiate(gvalues)) + point = new(bn256.G1).Add(point, p.Hs.MultiExponentiate(hvalues)) + + p.Result = new(bn256.G1).Set(point) + return p +} diff --git a/cryptography/crypto/algebra_pointvector.go b/cryptography/crypto/algebra_pointvector.go new file mode 100644 index 0000000..7cfffa6 --- /dev/null +++ b/cryptography/crypto/algebra_pointvector.go @@ -0,0 +1,192 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +// ToDO evaluate others curves such as BLS12 used by zcash, also BLS24 or others , providing ~200 bits of security , may be required for long time use( say 50 years) +type PointVector struct { + vector []*bn256.G1 +} + +func NewPointVector(input []*bn256.G1) *PointVector { + return &PointVector{vector: input} +} + +func (gv *PointVector) Length() int { + return len(gv.vector) +} + +// slice and return +func (gv *PointVector) Slice(start, end int) *PointVector { + var result PointVector + for i := start; i < end; i++ { + var ri bn256.G1 + ri.Set(gv.vector[i]) + result.vector = append(result.vector, &ri) + } + return &result +} + +func (gv *PointVector) Commit(exponent []*big.Int) *bn256.G1 { + var accumulator, zero bn256.G1 + var zeroes [64]byte + accumulator.Unmarshal(zeroes[:]) // obtain zero element, this should be static and + zero.Unmarshal(zeroes[:]) + + accumulator.ScalarMult(G, new(big.Int)) + + //fmt.Printf("zero %s\n", accumulator.String()) + + if len(gv.vector) != len(exponent) { + panic("mismatched number of elements") + } + for i := range gv.vector { // TODO a bug exists somewhere deep here + var tmp, accopy bn256.G1 + tmp.ScalarMult(gv.vector[i], exponent[i]) + + accopy.Set(&accumulator) + accumulator.Add(&accopy, &tmp) + } + + return &accumulator +} + +func (gv *PointVector) Sum() *bn256.G1 { + var accumulator bn256.G1 + + accumulator.ScalarMult(G, new(big.Int)) // set it to zero + + for i := range gv.vector { + var accopy bn256.G1 + + accopy.Set(&accumulator) + accumulator.Add(&accopy, gv.vector[i]) + } + + return &accumulator +} + +func (gv *PointVector) Add(addendum *PointVector) *PointVector { + var result PointVector + + if len(gv.vector) != len(addendum.vector) { + panic("mismatched number of elements") + } + + for i := range gv.vector { + var ri bn256.G1 + + ri.Add(gv.vector[i], addendum.vector[i]) + result.vector = append(result.vector, &ri) + } + + return &result +} + +func (gv *PointVector) Hadamard(exponent []*big.Int) *PointVector { + var result PointVector + + if len(gv.vector) != len(exponent) { + panic("mismatched number of elements") + } + for i := range gv.vector { + var ri bn256.G1 + ri.ScalarMult(gv.vector[i], exponent[i]) + result.vector = append(result.vector, &ri) + + } + + return &result +} + +func (gv *PointVector) Negate() *PointVector { + var result PointVector + for i := range gv.vector { + var ri bn256.G1 + ri.Neg(gv.vector[i]) + result.vector = append(result.vector, &ri) + } + return &result +} + +func (gv *PointVector) Times(multiplier *big.Int) *PointVector { + var result PointVector + for i := range gv.vector { + var ri bn256.G1 + ri.ScalarMult(gv.vector[i], multiplier) + result.vector = append(result.vector, &ri) + } + return &result +} + +func (gv *PointVector) Extract(parity bool) *PointVector { + var result PointVector + + remainder := 0 + if parity { + remainder = 1 + } + for i := range gv.vector { + if i%2 == remainder { + var ri bn256.G1 + ri.Set(gv.vector[i]) + result.vector = append(result.vector, &ri) + } + } + return &result +} + +func (gv *PointVector) Concat(addendum *PointVector) *PointVector { + var result PointVector + for i := range gv.vector { + var ri bn256.G1 + ri.Set(gv.vector[i]) + result.vector = append(result.vector, &ri) + } + + for i := range addendum.vector { + var ri bn256.G1 + ri.Set(addendum.vector[i]) + result.vector = append(result.vector, &ri) + } + + return &result +} + +func (pv *PointVector) MultiExponentiate(fv *FieldVector) *bn256.G1 { + var accumulator bn256.G1 + + accumulator.ScalarMult(G, new(big.Int)) // set it to zero + + for i := range fv.vector { + var accopy bn256.G1 + + accopy.Set(&accumulator) + accumulator.Add(&accopy, new(bn256.G1).ScalarMult(pv.vector[i], fv.vector[i])) + } + + return &accumulator +} diff --git a/cryptography/crypto/bnred.go b/cryptography/crypto/bnred.go new file mode 100644 index 0000000..1e22b16 --- /dev/null +++ b/cryptography/crypto/bnred.go @@ -0,0 +1,102 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "math/big" + +//import "encoding/binary" + +//import "crypto/rand" +//import "github.com/deroproject/derohe/crypto/bn256" + +// this file implements Big Number Reduced form with bn256's Order + +type BNRed big.Int + +func RandomScalarBNRed() *BNRed { + return (*BNRed)(RandomScalar()) +} + +// converts big.Int to BNRed +func GetBNRed(x *big.Int) *BNRed { + result := new(BNRed) + + ((*big.Int)(result)).Set(x) + return result +} + +// convert BNRed to BigInt +func (x *BNRed) BigInt() *big.Int { + return new(big.Int).Set(((*big.Int)(x))) +} + +func (x *BNRed) SetBytes(buf []byte) *BNRed { + ((*big.Int)(x)).SetBytes(buf) + return x +} + +func (x *BNRed) String() string { + return ((*big.Int)(x)).Text(16) +} + +func (x *BNRed) Text(base int) string { + return ((*big.Int)(x)).Text(base) +} + +func (x *BNRed) MarshalText() ([]byte, error) { + return []byte(((*big.Int)(x)).Text(16)), nil +} + +func (x *BNRed) UnmarshalText(text []byte) error { + _, err := fmt.Sscan("0x"+string(text), ((*big.Int)(x))) + return err +} + +func FillBytes(x *big.Int, xbytes []byte) { + // FillBytes not available pre 1.15 + bb := x.Bytes() + + if len(bb) > 32 { + panic(fmt.Sprintf("number not representable in 32 bytes %d %x", len(bb), bb)) + } + + for i := range xbytes { // optimized to memclr + xbytes[i] = 0 + } + + j := 32 + for i := len(bb) - 1; i >= 0; i-- { + j-- + xbytes[j] = bb[i] + } +} + +/* +// this will return fixed random scalar +func RandomScalarFixed() *big.Int { + //return new(big.Int).Set(fixed) + + return RandomScalar() +} + + +type KeyPair struct { + x *big.Int // secret key + y *bn256.G1 // public key +} +*/ diff --git a/cryptography/crypto/const.go b/cryptography/crypto/const.go new file mode 100644 index 0000000..a0deb40 --- /dev/null +++ b/cryptography/crypto/const.go @@ -0,0 +1,57 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" + +const POINT_SIZE = 33 // this can be optimized to 33 bytes +const FIELDELEMENT_SIZE = 32 // why not have bigger curves + +// protocol supports amounts upto this amounts +const MAX_AMOUNT = 18446744073709551616 // 2^64 - 1, + +const PROTOCOL_CONSTANT = "DERO" + +// checks a number is power of 2 +func IsPowerOf2(num int) bool { + for num >= 2 { + if num%2 != 0 { + return false + } + num = num / 2 + } + return num == 1 +} + +// tell what power a number is +func GetPowerof2(num int) int { + + if num <= 0 { + panic("number cannot be less than 0") + } + + if !IsPowerOf2(num) { + panic(fmt.Sprintf("number(%d) must be power of 2", num)) + } + + power := 0 + calculated := 1 + for ; calculated != num; power++ { + calculated *= 2 + } + return power +} diff --git a/cryptography/crypto/fieldvector.go b/cryptography/crypto/fieldvector.go new file mode 100644 index 0000000..6ac101c --- /dev/null +++ b/cryptography/crypto/fieldvector.go @@ -0,0 +1,297 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +type FieldVectorPolynomial struct { + coefficients []*FieldVector +} + +func NewFieldVectorPolynomial(inputs ...*FieldVector) *FieldVectorPolynomial { + fv := &FieldVectorPolynomial{} + for _, input := range inputs { + fv.coefficients = append(fv.coefficients, input.Clone()) + } + return fv +} + +func (fv *FieldVectorPolynomial) Length() int { + return len(fv.coefficients) +} + +func (fv *FieldVectorPolynomial) Evaluate(x *big.Int) *FieldVector { + + result := fv.coefficients[0].Clone() + + accumulator := new(big.Int).Set(x) + + for i := 1; i < len(fv.coefficients); i++ { + result = result.Add(fv.coefficients[i].Times(accumulator)) + accumulator.Mul(accumulator, x) + accumulator.Mod(accumulator, bn256.Order) + + } + return result +} + +func (fv *FieldVectorPolynomial) InnerProduct(other *FieldVectorPolynomial) []*big.Int { + + var result []*big.Int + + result_length := fv.Length() + other.Length() - 1 + for i := 0; i < result_length; i++ { + result = append(result, new(big.Int)) // 0 value fill + } + + for i := range fv.coefficients { + for j := range other.coefficients { + tmp := new(big.Int).Set(result[i+j]) + result[i+j].Add(tmp, fv.coefficients[i].InnerProduct(other.coefficients[j])) + result[i+j].Mod(result[i+j], bn256.Order) + } + } + return result +} + +/* + +type PedersenCommitment struct { + X *big.Int + R *big.Int + Params *GeneratorParams +} + +func NewPedersenCommitment(params *GeneratorParams, x, r *big.Int) *PedersenCommitment { + pc := &PedersenCommitment{Params: params, X: new(big.Int).Set(x), R: new(big.Int).Set(r)} + return pc +} +func (pc *PedersenCommitment) Commit() *bn256.G1 { + var left, right, result bn256.G1 + left.ScalarMult(pc.Params.G, pc.X) + right.ScalarMult(pc.Params.H, pc.R) + result.Add(&left, &right) + return &result +} +func (pc *PedersenCommitment) Add(other *PedersenCommitment) *PedersenCommitment { + var x, r big.Int + x.Mod(new(big.Int).Add(pc.X, other.X), bn256.Order) + r.Mod(new(big.Int).Add(pc.R, other.R), bn256.Order) + return NewPedersenCommitment(pc.Params, &x, &r) +} +func (pc *PedersenCommitment) Times(constant *big.Int) *PedersenCommitment { + var x, r big.Int + x.Mod(new(big.Int).Mul(pc.X, constant), bn256.Order) + r.Mod(new(big.Int).Mul(pc.R, constant), bn256.Order) + return NewPedersenCommitment(pc.Params, &x, &r) +} + +type PolyCommitment struct { + coefficient_commitments []*PedersenCommitment + Params *GeneratorParams +} + +func NewPolyCommitment(params *GeneratorParams, coefficients []*big.Int) *PolyCommitment { + pc := &PolyCommitment{Params: params} + pc.coefficient_commitments = append(pc.coefficient_commitments, NewPedersenCommitment(params, coefficients[0], new(big.Int).SetUint64(0))) + + for i := 1; i < len(coefficients); i++ { + pc.coefficient_commitments = append(pc.coefficient_commitments, NewPedersenCommitment(params, coefficients[i], RandomScalarFixed())) + + } + return pc +} + +func (pc *PolyCommitment) GetCommitments() []*bn256.G1 { + var result []*bn256.G1 + for i := 1; i < len(pc.coefficient_commitments); i++ { + result = append(result, pc.coefficient_commitments[i].Commit()) + } + return result +} + +func (pc *PolyCommitment) Evaluate(constant *big.Int) *PedersenCommitment { + result := pc.coefficient_commitments[0] + + accumulator := new(big.Int).Set(constant) + + for i := 1; i < len(pc.coefficient_commitments); i++ { + + tmp := new(big.Int).Set(accumulator) + result = result.Add(pc.coefficient_commitments[i].Times(accumulator)) + accumulator.Mod(new(big.Int).Mul(tmp, constant), bn256.Order) + } + + return result +} +*/ + +/* +// bother FieldVector and GeneratorVector satisfy this +type Vector interface{ + Length() int + Extract(parity bool) Vector + Add(other Vector)Vector + Hadamard( []*big.Int) Vector + Times (*big.Int) Vector + Negate() Vector +} +*/ + +// check this https://pdfs.semanticscholar.org/d38d/e48ee4127205a0f25d61980c8f241718b66e.pdf +// https://arxiv.org/pdf/1802.03932.pdf + +var unity *big.Int + +func init() { + // primitive 2^28th root of unity modulo q + unity, _ = new(big.Int).SetString("14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd", 16) + +} + +func fft_FieldVector(input *FieldVector, inverse bool) *FieldVector { + length := input.Length() + if length == 1 { + return input + } + + // lngth must be multiple of 2 ToDO + if length%2 != 0 { + panic("length must be multiple of 2") + } + + //unity,_ := new(big.Int).SetString("14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd",16) + + omega := new(big.Int).Exp(unity, new(big.Int).SetUint64((1<<28)/uint64(length)), bn256.Order) + if inverse { + omega = new(big.Int).ModInverse(omega, bn256.Order) + } + + even := fft_FieldVector(input.Extract(false), inverse) + odd := fft_FieldVector(input.Extract(true), inverse) + + omegas := []*big.Int{new(big.Int).SetUint64(1)} + + for i := 1; i < length/2; i++ { + omegas = append(omegas, new(big.Int).Mod(new(big.Int).Mul(omegas[i-1], omega), bn256.Order)) + } + + omegasv := NewFieldVector(omegas) + result := even.Add(odd.Hadamard(omegasv)).Concat(even.Add(odd.Hadamard(omegasv).Negate())) + if inverse { + result = result.Times(new(big.Int).ModInverse(new(big.Int).SetUint64(2), bn256.Order)) + } + + return result + +} + +// this is exactly same as fft_FieldVector, alternate implementation +func fftints(input []*big.Int) (result []*big.Int) { + size := len(input) + if size == 1 { + return input + } + //require(size % 2 == 0, "Input size is not a power of 2!"); + + unity, _ := new(big.Int).SetString("14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd", 16) + + omega := new(big.Int).Exp(unity, new(big.Int).SetUint64((1<<28)/uint64(size)), bn256.Order) + + even := fftints(extractbits(input, 0)) + odd := fftints(extractbits(input, 1)) + omega_run := new(big.Int).SetUint64(1) + result = make([]*big.Int, len(input), len(input)) + for i := 0; i < len(input)/2; i++ { + temp := new(big.Int).Mod(new(big.Int).Mul(odd[i], omega_run), bn256.Order) + result[i] = new(big.Int).Mod(new(big.Int).Add(even[i], temp), bn256.Order) + result[i+size/2] = new(big.Int).Mod(new(big.Int).Sub(even[i], temp), bn256.Order) + omega_run = new(big.Int).Mod(new(big.Int).Mul(omega, omega_run), bn256.Order) + } + return result +} + +func extractbits(input []*big.Int, parity int) (result []*big.Int) { + result = make([]*big.Int, len(input)/2, len(input)/2) + for i := 0; i < len(input)/2; i++ { + result[i] = new(big.Int).Set(input[2*i+parity]) + } + return +} + +func fft_GeneratorVector(input *PointVector, inverse bool) *PointVector { + length := input.Length() + if length == 1 { + return input + } + + // lngth must be multiple of 2 ToDO + if length%2 != 0 { + panic("length must be multiple of 2") + } + + // unity,_ := new(big.Int).SetString("14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd",16) + + omega := new(big.Int).Exp(unity, new(big.Int).SetUint64((1<<28)/uint64(length)), bn256.Order) + if inverse { + omega = new(big.Int).ModInverse(omega, bn256.Order) + } + + even := fft_GeneratorVector(input.Extract(false), inverse) + + //fmt.Printf("exponent_fft %d %s \n",i, exponent_fft.vector[i].Text(16)) + + odd := fft_GeneratorVector(input.Extract(true), inverse) + + omegas := []*big.Int{new(big.Int).SetUint64(1)} + + for i := 1; i < length/2; i++ { + omegas = append(omegas, new(big.Int).Mod(new(big.Int).Mul(omegas[i-1], omega), bn256.Order)) + } + + omegasv := omegas + result := even.Add(odd.Hadamard(omegasv)).Concat(even.Add(odd.Hadamard(omegasv).Negate())) + if inverse { + result = result.Times(new(big.Int).ModInverse(new(big.Int).SetUint64(2), bn256.Order)) + } + + return result + +} + +func Convolution(exponent *FieldVector, base *PointVector) *PointVector { + size := base.Length() + + exponent_fft := fft_FieldVector(exponent.Flip(), false) + + /*exponent_fft2 := fftints( exponent.Flip().vector) // aternate implementation proof checking + for i := range exponent_fft.vector{ + fmt.Printf("exponent_fft %d %s \n",i, exponent_fft.vector[i].Text(16)) + fmt.Printf("exponent_ff2 %d %s \n",i, exponent_fft2[i].Text(16)) + } + */ + + temp := fft_GeneratorVector(base, false).Hadamard(exponent_fft.vector) + return fft_GeneratorVector(temp.Slice(0, size/2).Add(temp.Slice(size/2, size)).Times(new(big.Int).ModInverse(new(big.Int).SetUint64(2), bn256.Order)), true) + // using the optimization described here https://dsp.stackexchange.com/a/30699 +} diff --git a/cryptography/crypto/generatorparams.go b/cryptography/crypto/generatorparams.go new file mode 100644 index 0000000..a06801e --- /dev/null +++ b/cryptography/crypto/generatorparams.go @@ -0,0 +1,72 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +func NewGeneratorParams(count int) *GeneratorParams { + GP := &GeneratorParams{} + var zeroes [64]byte + + GP.G = HashToPoint(HashtoNumber([]byte(PROTOCOL_CONSTANT + "G"))) // this is same as mybase or vice-versa + GP.H = HashToPoint(HashtoNumber([]byte(PROTOCOL_CONSTANT + "H"))) + + var gs, hs []*bn256.G1 + + GP.GSUM = new(bn256.G1) + GP.GSUM.Unmarshal(zeroes[:]) + + for i := 0; i < count; i++ { + gs = append(gs, HashToPoint(HashtoNumber(append([]byte(PROTOCOL_CONSTANT+"G"), hextobytes(makestring64(fmt.Sprintf("%x", i)))...)))) + hs = append(hs, HashToPoint(HashtoNumber(append([]byte(PROTOCOL_CONSTANT+"H"), hextobytes(makestring64(fmt.Sprintf("%x", i)))...)))) + + GP.GSUM = new(bn256.G1).Add(GP.GSUM, gs[i]) + } + GP.Gs = NewPointVector(gs) + GP.Hs = NewPointVector(hs) + + return GP +} + +func NewGeneratorParams3(h *bn256.G1, gs, hs *PointVector) *GeneratorParams { + GP := &GeneratorParams{} + + GP.G = HashToPoint(HashtoNumber([]byte(PROTOCOL_CONSTANT + "G"))) // this is same as mybase or vice-versa + GP.H = h + GP.Gs = gs + GP.Hs = hs + return GP +} + +func (gp *GeneratorParams) Commit(blind *big.Int, gexps, hexps *FieldVector) *bn256.G1 { + result := new(bn256.G1).ScalarMult(gp.H, blind) + for i := range gexps.vector { + result = new(bn256.G1).Add(result, new(bn256.G1).ScalarMult(gp.Gs.vector[i], gexps.vector[i])) + } + if hexps != nil { + for i := range hexps.vector { + result = new(bn256.G1).Add(result, new(bn256.G1).ScalarMult(gp.Hs.vector[i], hexps.vector[i])) + } + } + return result +} diff --git a/cryptography/crypto/group.go b/cryptography/crypto/group.go new file mode 100644 index 0000000..31131fe --- /dev/null +++ b/cryptography/crypto/group.go @@ -0,0 +1,73 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" +import "encoding/hex" + +//import "crypto/rand" +import "github.com/deroproject/derohe/cryptography/bn256" + +// this file implements Big Number Reduced form with bn256's Order + +type Point bn256.G1 + +var GPoint Point + +// ScalarMult with chainable API +func (p *Point) ScalarMult(r *BNRed) (result *Point) { + result = new(Point) + ((*bn256.G1)(result)).ScalarMult(((*bn256.G1)(p)), ((*big.Int)(r))) + return result +} + +func (p *Point) EncodeCompressed() []byte { + return ((*bn256.G1)(p)).EncodeCompressed() +} + +func (p *Point) DecodeCompressed(i []byte) error { + return ((*bn256.G1)(p)).DecodeCompressed(i) +} + +func (p *Point) G1() *bn256.G1 { + return ((*bn256.G1)(p)) +} +func (p *Point) Set(x *Point) *Point { + return ((*Point)(((*bn256.G1)(p)).Set(((*bn256.G1)(x))))) +} + +func (p *Point) String() string { + return string(((*bn256.G1)(p)).EncodeCompressed()) +} + +func (p *Point) StringHex() string { + return string(hex.EncodeToString(((*bn256.G1)(p)).EncodeCompressed())) +} + +func (p *Point) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(((*bn256.G1)(p)).EncodeCompressed())), nil +} + +func (p *Point) UnmarshalText(text []byte) error { + + tmp, err := hex.DecodeString(string(text)) + if err != nil { + return err + } + return p.DecodeCompressed(tmp) +} diff --git a/cryptography/crypto/hash.go b/cryptography/crypto/hash.go new file mode 100644 index 0000000..3223456 --- /dev/null +++ b/cryptography/crypto/hash.go @@ -0,0 +1,68 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "encoding/hex" + +const HashLength = 32 + +type Hash [HashLength]byte + +var ZEROHASH Hash + +func (h Hash) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%x", h[:])), nil +} + +func (h *Hash) UnmarshalText(data []byte) (err error) { + byteSlice, _ := hex.DecodeString(string(data)) + if len(byteSlice) != 32 { + return fmt.Errorf("Incorrect hash size") + } + copy(h[:], byteSlice) + return +} + +// stringifier +func (h Hash) String() string { + return fmt.Sprintf("%064x", h[:]) +} + +func (h Hash) IsZero() bool { + var zerohash Hash + return h == zerohash +} + +// convert a hash of hex form to binary form, returns a zero hash if any error +// TODO this should be in crypto +func HashHexToHash(hash_hex string) (hash Hash) { + hash_raw, err := hex.DecodeString(hash_hex) + + if err != nil { + //panic(fmt.Sprintf("Cannot hex decode checkpint hash \"%s\"", hash_hex)) + return hash + } + + if len(hash_raw) != 32 { + //panic(fmt.Sprintf(" hash not 32 byte size Cannot hex decode checkpint hash \"%s\"", hash_hex)) + return hash + } + + copy(hash[:], hash_raw) + return +} diff --git a/cryptography/crypto/hashtopoint.go b/cryptography/crypto/hashtopoint.go new file mode 100644 index 0000000..1fd0a89 --- /dev/null +++ b/cryptography/crypto/hashtopoint.go @@ -0,0 +1,260 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "math/big" + +//import "crypto/rand" +import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" +import "github.com/deroproject/derohe/cryptography/sha3" + +// the original try and increment method A Note on Hashing to BN Curves https://www.normalesup.org/~tibouchi/papers/bnhash-scis.pdf +// see this for a simplified version https://github.com/clearmatics/mobius/blob/7ad988b816b18e22424728329fc2b166d973a120/contracts/bn256g1.sol + +var FIELD_MODULUS, w = new(big.Int).SetString("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", 16) +var GROUP_MODULUS, w1 = new(big.Int).SetString("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", 16) + +// this file basically implements curve based items + +type GeneratorParams struct { + G *bn256.G1 + H *bn256.G1 + GSUM *bn256.G1 + + Gs *PointVector + Hs *PointVector +} + +// converts a big int to 32 bytes, prepending zeroes +func ConvertBigIntToByte(x *big.Int) []byte { + var dummy [128]byte + joined := append(dummy[:], x.Bytes()...) + return joined[len(joined)-32:] +} + +// the number if already reduced +func HashtoNumber(input []byte) *big.Int { + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(input) + + hash := hasher.Sum(nil) + return new(big.Int).SetBytes(hash[:]) +} + +// calculate hash and reduce it by curve's order +func reducedhash(input []byte) *big.Int { + return new(big.Int).Mod(HashtoNumber(input), bn256.Order) +} + +func ReducedHash(input []byte) *big.Int { + return new(big.Int).Mod(HashtoNumber(input), bn256.Order) +} + +func makestring64(input string) string { + for len(input) != 64 { + input = "0" + input + } + return input +} + +func makestring66(input string) string { + for len(input) != 64 { + input = "0" + input + } + return input + "00" +} + +func hextobytes(input string) []byte { + ibytes, err := hex.DecodeString(input) + if err != nil { + panic(err) + } + return ibytes +} + +// p = p(u) = 36u^4 + 36u^3 + 24u^2 + 6u + 1 +var FIELD_ORDER, _ = new(big.Int).SetString("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", 16) + +// Number of elements in the field (often called `q`) +// n = n(u) = 36u^4 + 36u^3 + 18u^2 + 6u + 1 +var GEN_ORDER, _ = new(big.Int).SetString("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", 16) + +var CURVE_B = new(big.Int).SetUint64(3) + +// a = (p+1) / 4 +var CURVE_A, _ = new(big.Int).SetString("c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52", 16) + +func HashToPointNew(seed *big.Int) *bn256.G1 { + y_squared := new(big.Int) + one := new(big.Int).SetUint64(1) + + x := new(big.Int).Set(seed) + x.Mod(x, GEN_ORDER) + for { + beta, y := findYforX(x) + + if y != nil { + + // fmt.Printf("beta %s y %s\n", beta.String(),y.String()) + + // y^2 == beta + y_squared.Mul(y, y) + y_squared.Mod(y_squared, FIELD_ORDER) + + if beta.Cmp(y_squared) == 0 { + + // fmt.Printf("liesoncurve test %+v\n", isOnCurve(x,y)) + // fmt.Printf("x %s\n",x.Text(16)) + // fmt.Printf("y %s\n",y.Text(16)) + + xstring := x.Text(16) + ystring := y.Text(16) + + var point bn256.G1 + xbytes, err := hex.DecodeString(makestring64(xstring)) + if err != nil { + panic(err) + } + ybytes, err := hex.DecodeString(makestring64(ystring)) + if err != nil { + panic(err) + } + + if _, err := point.Unmarshal(append(xbytes, ybytes...)); err == nil { + return &point + } else { + + panic(fmt.Sprintf("not found err %s\n", err)) + } + } + } + + x.Add(x, one) + x.Mod(x, FIELD_ORDER) + } + +} + +/* + * Given X, find Y + * + * where y = sqrt(x^3 + b) + * + * Returns: (x^3 + b), y +**/ +func findYforX(x *big.Int) (*big.Int, *big.Int) { + // beta = (x^3 + b) % p + xcube := new(big.Int).Exp(x, CURVE_B, FIELD_ORDER) + xcube.Add(xcube, CURVE_B) + beta := new(big.Int).Mod(xcube, FIELD_ORDER) + //beta := addmod(mulmod(mulmod(x, x, FIELD_ORDER), x, FIELD_ORDER), CURVE_B, FIELD_ORDER); + + // y^2 = x^3 + b + // this acts like: y = sqrt(beta) + + //ymod := new(big.Int).ModSqrt(beta,FIELD_ORDER) // this can return nil in some cases + y := new(big.Int).Exp(beta, CURVE_A, FIELD_ORDER) + return beta, y +} + +/* + * Verify if the X and Y coordinates represent a valid Point on the Curve + * + * Where the G1 curve is: x^2 = x^3 + b +**/ +func isOnCurve(x, y *big.Int) bool { + //p_squared := new(big.Int).Exp(x, new(big.Int).SetUint64(2), FIELD_ORDER); + p_cubed := new(big.Int).Exp(x, new(big.Int).SetUint64(3), FIELD_ORDER) + + p_cubed.Add(p_cubed, CURVE_B) + p_cubed.Mod(p_cubed, FIELD_ORDER) + + // return addmod(p_cubed, CURVE_B, FIELD_ORDER) == mulmod(p.Y, p.Y, FIELD_ORDER); + return p_cubed.Cmp(new(big.Int).Exp(y, new(big.Int).SetUint64(2), FIELD_ORDER)) == 0 +} + +// this should be merged , simplified just as simple as 25519 +func HashToPoint(seed *big.Int) *bn256.G1 { + /* + var x, _ = new(big.Int).SetString("0d36fdf1852f1563df9c904374055bb2a4d351571b853971764b9561ae203a9e",16) + var y, _ = new(big.Int).SetString("06efda2e606d7bafec34b82914953fa253d21ca3ced18db99c410e9057dccd50",16) + + + + fmt.Printf("hardcode point on curve %+v\n", isOnCurve(x,y)) + panic("done") + */ + return HashToPointNew(seed) + + seed_reduced := new(big.Int) + seed_reduced.Mod(seed, FIELD_MODULUS) + + counter := 0 + + p_1_4 := new(big.Int).Add(FIELD_MODULUS, new(big.Int).SetInt64(1)) + p_1_4 = p_1_4.Div(p_1_4, new(big.Int).SetInt64(4)) + + for { + tmp := new(big.Int) + y, y_squared, y_resquare := new(big.Int), new(big.Int), new(big.Int) // basically y_sqaured = seed ^3 + 3 mod group order + tmp.Exp(seed_reduced, new(big.Int).SetInt64(3), FIELD_MODULUS) + y_squared.Add(tmp, new(big.Int).SetInt64(3)) + y_squared.Mod(y_squared, FIELD_MODULUS) + + y = y.Exp(y_squared, p_1_4, FIELD_MODULUS) + + y_resquare = y_resquare.Exp(y, new(big.Int).SetInt64(2), FIELD_MODULUS) + + if y_resquare.Cmp(y_squared) == 0 { // seed becomes x and y iis usy + xstring := seed_reduced.Text(16) + ystring := y.Text(16) + + var point bn256.G1 + xbytes, err := hex.DecodeString(makestring64(xstring)) + if err != nil { + panic(err) + } + ybytes, err := hex.DecodeString(makestring64(ystring)) + if err != nil { + panic(err) + } + + if _, err = point.Unmarshal(append(xbytes, ybytes...)); err == nil { + return &point + } else { + // continue finding + counter++ + + if counter%10000 == 0 { + fmt.Printf("tried %d times\n", counter) + } + + } + + } + seed_reduced.Add(seed_reduced, new(big.Int).SetInt64(1)) + seed_reduced.Mod(seed_reduced, FIELD_MODULUS) + } + + return nil +} diff --git a/cryptography/crypto/keccak.go b/cryptography/crypto/keccak.go new file mode 100644 index 0000000..528a6d2 --- /dev/null +++ b/cryptography/crypto/keccak.go @@ -0,0 +1,41 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "golang.org/x/crypto/sha3" +import "github.com/deroproject/derohe/cryptography/sha3" + +// quick keccak wrapper +func Keccak256(data ...[]byte) (result Hash) { + h := sha3.NewLegacyKeccak256() + for _, b := range data { + h.Write(b) + } + r := h.Sum(nil) + copy(result[:], r) + return +} + +func Keccak512(data ...[]byte) (result Hash) { + h := sha3.NewLegacyKeccak512() + for _, b := range data { + h.Write(b) + } + r := h.Sum(nil) + copy(result[:], r) + return +} diff --git a/cryptography/crypto/keccak_test.go b/cryptography/crypto/keccak_test.go new file mode 100644 index 0000000..bc614e8 --- /dev/null +++ b/cryptography/crypto/keccak_test.go @@ -0,0 +1,57 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "testing" +import "encoding/hex" + +func TestKeccak256(t *testing.T) { + tests := []struct { + name string + messageHex string + wantHex string + }{ + { + name: "from monero 1", + messageHex: "c8fedd380dbae40ffb52", + wantHex: "8e41962058b7422e7404253121489a3e63d186ed115086919a75105661483ba9", + }, + { + name: "from monero 2", + messageHex: "5020c4d530b6ec6cb4d9", + wantHex: "8a597f11961935e32e0adeab2ce48b3df2d907c9b26619dad22f42ff65ab7593", + }, + { + name: "hello", + messageHex: "68656c6c6f", + wantHex: "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8", + }, + { + name: "from monero cryptotest.pl", + messageHex: "0f3fe9c20b24a11bf4d6d1acd335c6a80543f1f0380590d7323caf1390c78e88", + wantHex: "73b7a236f2a97c4e1805f7a319f1283e3276598567757186c526caf9a49e0a92", + }, + } + for _, test := range tests { + message, _ := hex.DecodeString(test.messageHex) + got := Keccak256(message) + want := HexToHash(test.wantHex) + if want != got { + t.Errorf("want %x, got %x", want, got) + } + } +} diff --git a/cryptography/crypto/key_old.go b/cryptography/crypto/key_old.go new file mode 100644 index 0000000..d4ab96c --- /dev/null +++ b/cryptography/crypto/key_old.go @@ -0,0 +1,70 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "encoding/hex" + +const KeyLength = 32 + +// Key can be a Scalar or a Point +type Key [KeyLength]byte + +func (k Key) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%x", k[:])), nil +} + +func (k *Key) UnmarshalText(data []byte) (err error) { + byteSlice, _ := hex.DecodeString(string(data)) + if len(byteSlice) != 32 { + return fmt.Errorf("Incorrect key size") + } + copy(k[:], byteSlice) + return +} + +func (k Key) String() string { + return fmt.Sprintf("%x", k[:]) +} + +func (p *Key) FromBytes(b [KeyLength]byte) { + *p = b +} + +func (p *Key) ToBytes() (result [KeyLength]byte) { + result = [KeyLength]byte(*p) + return +} + +// convert a hex string to a key +func HexToKey(h string) (result Key) { + byteSlice, _ := hex.DecodeString(h) + if len(byteSlice) != 32 { + panic("Incorrect key size") + } + copy(result[:], byteSlice) + return +} + +func HexToHash(h string) (result Hash) { + byteSlice, _ := hex.DecodeString(h) + if len(byteSlice) != 32 { + panic("Incorrect key size") + } + copy(result[:], byteSlice) + return +} diff --git a/cryptography/crypto/polynomial.go b/cryptography/crypto/polynomial.go new file mode 100644 index 0000000..ae99c28 --- /dev/null +++ b/cryptography/crypto/polynomial.go @@ -0,0 +1,91 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math/big" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +type Polynomial struct { + coefficients []*big.Int +} + +func NewPolynomial(input []*big.Int) *Polynomial { + if input == nil { + return &Polynomial{coefficients: []*big.Int{new(big.Int).SetInt64(1)}} + } + return &Polynomial{coefficients: input} +} + +func (p *Polynomial) Length() int { + return len(p.coefficients) +} + +func (p *Polynomial) Mul(m *Polynomial) *Polynomial { + var product []*big.Int + for i := range p.coefficients { + product = append(product, new(big.Int).Mod(new(big.Int).Mul(p.coefficients[i], m.coefficients[0]), bn256.Order)) + } + product = append(product, new(big.Int)) // add 0 element + + if m.coefficients[1].IsInt64() && m.coefficients[1].Int64() == 1 { + for i := range product { + if i > 0 { + tmp := new(big.Int).Add(product[i], p.coefficients[i-1]) + + product[i] = new(big.Int).Mod(tmp, bn256.Order) + + } else { // do nothing + + } + } + } + return NewPolynomial(product) +} + +type dummy struct { + list [][]*big.Int +} + +func RecursivePolynomials(list [][]*big.Int, accum *Polynomial, a, b []*big.Int) (rlist [][]*big.Int) { + var d dummy + d.recursivePolynomialsinternal(accum, a, b) + + return d.list +} + +func (d *dummy) recursivePolynomialsinternal(accum *Polynomial, a, b []*big.Int) { + if len(a) == 0 { + d.list = append(d.list, accum.coefficients) + return + } + + atop := a[len(a)-1] + btop := b[len(b)-1] + + left := NewPolynomial([]*big.Int{new(big.Int).Mod(new(big.Int).Neg(atop), bn256.Order), new(big.Int).Mod(new(big.Int).Sub(new(big.Int).SetInt64(1), btop), bn256.Order)}) + right := NewPolynomial([]*big.Int{atop, btop}) + + d.recursivePolynomialsinternal(accum.Mul(left), a[:len(a)-1], b[:len(b)-1]) + d.recursivePolynomialsinternal(accum.Mul(right), a[:len(a)-1], b[:len(b)-1]) +} diff --git a/cryptography/crypto/proof_generate.go b/cryptography/crypto/proof_generate.go new file mode 100644 index 0000000..e37d572 --- /dev/null +++ b/cryptography/crypto/proof_generate.go @@ -0,0 +1,1206 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "fmt" +import "math" +import "math/big" +import "bytes" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +//import "github.com/kubernetes/klog" + +type Proof struct { + BA *bn256.G1 + BS *bn256.G1 + A *bn256.G1 + B *bn256.G1 + + CLnG, CRnG, C_0G, DG, y_0G, gG, C_XG, y_XG []*bn256.G1 + + u *bn256.G1 + + f *FieldVector + + z_A *big.Int + + T_1 *bn256.G1 + T_2 *bn256.G1 + + that *big.Int + mu *big.Int + + c *big.Int + s_sk, s_r, s_b, s_tau *big.Int + + ip *InnerProduct +} + +type IPStatement struct { + PrimeBase *GeneratorParams + P *bn256.G1 +} + +type IPWitness struct { + L *FieldVector + R *FieldVector +} + +// this is based on roothash and user's secret key and thus is the basis of protection from a number of double spending attacks +func (p *Proof) Nonce() Hash { + return Keccak256(p.u.EncodeCompressed()) +} + +func (p *Proof) Serialize(w *bytes.Buffer) { + if p == nil { + return + } + w.Write(p.BA.EncodeCompressed()) + w.Write(p.BS.EncodeCompressed()) + w.Write(p.A.EncodeCompressed()) + w.Write(p.B.EncodeCompressed()) + + //w.WriteByte(byte(len(p.CLnG))) // we can skip this byte also, why not skip it + + // fmt.Printf("CLnG byte %d\n",len(p.CLnG)) + for i := 0; i < len(p.CLnG); i++ { + w.Write(p.CLnG[i].EncodeCompressed()) + w.Write(p.CRnG[i].EncodeCompressed()) + w.Write(p.C_0G[i].EncodeCompressed()) + w.Write(p.DG[i].EncodeCompressed()) + w.Write(p.y_0G[i].EncodeCompressed()) + w.Write(p.gG[i].EncodeCompressed()) + w.Write(p.C_XG[i].EncodeCompressed()) + w.Write(p.y_XG[i].EncodeCompressed()) + } + + w.Write(p.u.EncodeCompressed()) + + if len(p.CLnG) != len(p.f.vector) { + /// panic(fmt.Sprintf("different size %d %d", len(p.CLnG), len(p.f.vector))) + } + + /*if len(p.f.vector) != 2 { + panic(fmt.Sprintf("f could not be serialized length %d", len(p.CLnG), len(p.f.vector))) + } + */ + + // fmt.Printf("writing %d fvector points\n", len(p.f.vector)); + for i := 0; i < len(p.f.vector); i++ { + w.Write(ConvertBigIntToByte(p.f.vector[i])) + } + + w.Write(ConvertBigIntToByte(p.z_A)) + + w.Write(p.T_1.EncodeCompressed()) + w.Write(p.T_2.EncodeCompressed()) + + w.Write(ConvertBigIntToByte(p.that)) + w.Write(ConvertBigIntToByte(p.mu)) + + w.Write(ConvertBigIntToByte(p.c)) + w.Write(ConvertBigIntToByte(p.s_sk)) + w.Write(ConvertBigIntToByte(p.s_r)) + w.Write(ConvertBigIntToByte(p.s_b)) + w.Write(ConvertBigIntToByte(p.s_tau)) + + p.ip.Serialize(w) + +} + +func (proof *Proof) Deserialize(r *bytes.Reader, length int) error { + + var buf [32]byte + var bufp [33]byte + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.BA = &p + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.BS = &p + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.A = &p + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.B = &p + } else { + return err + } + + proof.CLnG = proof.CLnG[:0] + proof.CRnG = proof.CRnG[:0] + proof.C_0G = proof.C_0G[:0] + proof.DG = proof.DG[:0] + proof.y_0G = proof.y_0G[:0] + proof.gG = proof.gG[:0] + proof.C_XG = proof.C_XG[:0] + proof.y_XG = proof.y_XG[:0] + + for i := 0; i < length; i++ { + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.CLnG = append(proof.CLnG, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + //fmt.Printf("CRnG point bytes2 %x\n", bufp[:]) + if err = p.DecodeCompressed(bufp[:]); err != nil { + //fmt.Printf("CRng point bytes1 %x\n", bufp[:]) + return err + } + proof.CRnG = append(proof.CRnG, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.C_0G = append(proof.C_0G, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.DG = append(proof.DG, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.y_0G = append(proof.y_0G, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.gG = append(proof.gG, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.C_XG = append(proof.C_XG, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.y_XG = append(proof.y_XG, &p) + } else { + return err + } + + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.u = &p + } else { + return err + } + + proof.f = &FieldVector{} + + //fmt.Printf("flen %d\n", flen ) + for j := 0; j < length*2; j++ { + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.f.vector = append(proof.f.vector, new(big.Int).SetBytes(buf[:])) + } else { + return err + } + + } + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.z_A = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.T_1 = &p + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + proof.T_2 = &p + } else { + return err + } + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.that = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.mu = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.c = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.s_sk = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.s_r = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.s_b = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + if n, err := r.Read(buf[:]); n == 32 && err == nil { + proof.s_tau = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + proof.ip = &InnerProduct{} + + return proof.ip.Deserialize(r) + +} + +/* +// statement hash +func (s *Statement) Hash() *big.Int { + var input []byte + for i := range s.CLn { + input = append(input, s.CLn[i].Marshal()...) + } + for i := range s.CRn { + input = append(input, s.CRn[i].Marshal()...) + } + for i := range s.C { + input = append(input, s.C[i].Marshal()...) + } + input = append(input, s.D.Marshal()...) + for i := range s.Publickeylist { + input = append(input, s.Publickeylist[i].Marshal()...) + } + input = append(input, s.Roothash[:]...) + + return reducedhash(input) +} +*/ + +func (p *Proof) Size() int { + size := 4*POINT_SIZE + (len(p.CLnG)+len(p.CRnG)+len(p.C_0G)+len(p.DG)+len(p.y_0G)+len(p.gG)+len(p.C_XG)+len(p.y_XG))*POINT_SIZE + size += POINT_SIZE + size += len(p.f.vector) * FIELDELEMENT_SIZE + size += FIELDELEMENT_SIZE + size += 2 * POINT_SIZE // T_1 ,T_2 + size += 7 * FIELDELEMENT_SIZE + size += p.ip.Size() + return size +} + +func (proof *Proof) hashmash1(v *big.Int) *big.Int { + var input []byte + input = append(input, ConvertBigIntToByte(v)...) + for i := range proof.CLnG { + input = append(input, proof.CLnG[i].Marshal()...) + } + for i := range proof.CRnG { + input = append(input, proof.CRnG[i].Marshal()...) + } + for i := range proof.C_0G { + input = append(input, proof.C_0G[i].Marshal()...) + } + for i := range proof.DG { + input = append(input, proof.DG[i].Marshal()...) + } + for i := range proof.y_0G { + input = append(input, proof.y_0G[i].Marshal()...) + } + for i := range proof.gG { + input = append(input, proof.gG[i].Marshal()...) + } + for i := range proof.C_XG { + input = append(input, proof.C_XG[i].Marshal()...) + } + for i := range proof.y_XG { + input = append(input, proof.y_XG[i].Marshal()...) + } + return reducedhash(input) +} + +// function, which takes a string as +// argument and return the reverse of string. +func reverse(s string) string { + rns := []rune(s) // convert to rune + for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 { + + // swap the letters of the string, + // like first with last and so on. + rns[i], rns[j] = rns[j], rns[i] + } + + // return the reversed string. + return string(rns) +} + +var params = NewGeneratorParams(128) // these can be pregenerated similarly as in DERO project + +func GenerateProof(s *Statement, witness *Witness, u *bn256.G1, txid Hash, burn_value uint64) *Proof { + + var proof Proof + proof.u = u + + statementhash := reducedhash(txid[:]) + + // statement should be constructed from these, however these are being simplified + var C []*ElGamal + var Cn ElGamalVector + for i := range s.C { + C = append(C, ConstructElGamal(s.C[i], s.D)) + Cn.vector = append(Cn.vector, ConstructElGamal(s.CLn[i], s.CRn[i])) + } + + btransfer := new(big.Int).SetInt64(int64(witness.TransferAmount)) // this should be reduced + bdiff := new(big.Int).SetInt64(int64(witness.Balance)) // this should be reduced + + number := btransfer.Add(btransfer, bdiff.Lsh(bdiff, 64)) // we are placing balance and left over balance, and doing a range proof of 128 bits + + number_string := reverse("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + number.Text(2)) + number_string_left_128bits := string(number_string[0:128]) + + var aL, aR FieldVector // convert the amount to make sure it cannot be negative + + //klog.V(2).Infof("reverse %s\n", number_string_left_128bits) + for _, b := range []byte(number_string_left_128bits) { + if b == '1' { + aL.vector = append(aL.vector, new(big.Int).SetInt64(1)) + aR.vector = append(aR.vector, new(big.Int).SetInt64(0)) + } else { + aL.vector = append(aL.vector, new(big.Int).SetInt64(0)) + aR.vector = append(aR.vector, new(big.Int).Mod(new(big.Int).SetInt64(-1), bn256.Order)) + } + } + + //klog.V(2).Infof("aRa %+v\n", aRa) + + proof_BA_internal := NewPedersenVectorCommitment().Commit(&aL, &aR) + proof.BA = proof_BA_internal.Result + + sL := NewFieldVectorRandomFilled(len(aL.vector)) + sR := NewFieldVectorRandomFilled(len(aL.vector)) + proof_BS_internal := NewPedersenVectorCommitment().Commit(sL, sR) + proof.BS = proof_BS_internal.Result + + //klog.V(2).Infof("Proof BA %s\n", proof.BA.String()) + //klog.V(2).Infof("Proof BS %s\n", proof.BS.String()) + + if len(s.Publickeylist) >= 1 && len(s.Publickeylist)&(len(s.Publickeylist)-1) != 0 { + fmt.Printf("len of Publickeylist %d\n", len(s.Publickeylist)) + panic("we need power of 2") + } + + N := len(s.Publickeylist) + m := int(math.Log2(float64(N))) + + if math.Pow(2, float64(m)) != float64(N) { + panic("log failed") + } + + var aa, ba, bspecial []*big.Int + for i := 0; i < 2*m; i++ { + aa = append(aa, RandomScalarFixed()) + } + + witness_index := reverse(fmt.Sprintf("%0"+fmt.Sprintf("%db", m)+"%0"+fmt.Sprintf("%db", m), witness.Index[1], witness.Index[0])) + + for _, b := range []byte(witness_index) { + if b == '1' { + ba = append(ba, new(big.Int).SetUint64(1)) + bspecial = append(bspecial, new(big.Int).Mod(new(big.Int).SetInt64(-1), bn256.Order)) + } else { + ba = append(ba, new(big.Int).SetUint64(0)) + bspecial = append(bspecial, new(big.Int).Mod(new(big.Int).SetInt64(1), bn256.Order)) + } + } + + a := NewFieldVector(aa) + b := NewFieldVector(ba) + + // klog.V(1).Infof("witness_index of sender/receiver %s\n", witness_index) + + c := a.Hadamard(NewFieldVector(bspecial)) + d := a.Hadamard(a).Negate() + + // klog.V(2).Infof("d %s\n", d.vector[0].Text(16)) + + e := NewFieldVector([]*big.Int{new(big.Int).Mod(new(big.Int).Mul(a.vector[0], a.vector[m]), bn256.Order), + new(big.Int).Mod(new(big.Int).Mul(a.vector[0], a.vector[m]), bn256.Order)}) + + second := new(big.Int).Set(a.vector[b.vector[m].Uint64()*uint64(m)]) + second.Neg(second) + + f := NewFieldVector([]*big.Int{a.vector[b.vector[0].Uint64()*uint64(m)], new(big.Int).Mod(second, bn256.Order)}) + + // for i := range f.vector { + // klog.V(2).Infof("f %d %s\n", i, f.vector[i].Text(16)) + // } + + proof_A_internal := NewPedersenVectorCommitment().Commit(a, d.Concat(e)) + proof.A = proof_A_internal.Result + proof_B_internal := NewPedersenVectorCommitment().Commit(b, c.Concat(f)) + proof.B = proof_B_internal.Result + + // klog.V(2).Infof("Proof A %s\n", proof.A.String()) + // klog.V(2).Infof("Proof B %s\n", proof.B.String()) + + var v *big.Int + + { // hash mash + var input []byte + input = append(input, ConvertBigIntToByte(statementhash)...) + input = append(input, proof.BA.Marshal()...) + input = append(input, proof.BS.Marshal()...) + input = append(input, proof.A.Marshal()...) + input = append(input, proof.B.Marshal()...) + v = reducedhash(input) + } + + var P, Q, Pi, Qi [][]*big.Int + Pi = RecursivePolynomials(Pi, NewPolynomial(nil), a.SliceRaw(0, m), b.SliceRaw(0, m)) + Qi = RecursivePolynomials(Qi, NewPolynomial(nil), a.SliceRaw(m, 2*m), b.SliceRaw(m, 2*m)) + + // transpose the matrices + for i := 0; i < m; i++ { + P = append(P, []*big.Int{}) + Q = append(Q, []*big.Int{}) + for j := range Pi { + P[i] = append(P[i], Pi[j][i]) + Q[i] = append(Q[i], Qi[j][i]) + } + } + + // for i := range P { + // for j := range P[i] { + // klog.V(2).Infof("P%d,%d %s\n", i, j, P[i][j].Text(16)) + // } + // } + + //phi := NewFieldVectorRandomFilled(m) + //chi := NewFieldVectorRandomFilled(m) + //psi := NewFieldVectorRandomFilled(m) + + var phi, chi, psi ElGamalVector + for i := 0; i < m; i++ { + phi.vector = append(phi.vector, CommitElGamal(s.Publickeylist[witness.Index[0]], new(big.Int).SetUint64(0))) + chi.vector = append(chi.vector, CommitElGamal(s.Publickeylist[witness.Index[0]], new(big.Int).SetUint64(0))) + psi.vector = append(psi.vector, CommitElGamal(s.Publickeylist[witness.Index[0]], new(big.Int).SetUint64(0))) + } + + var CnG, C_0G, y_0G ElGamalVector + for i := 0; i < m; i++ { + + CnG.vector = append(CnG.vector, Cn.MultiExponentiate(NewFieldVector(P[i])).Add(phi.vector[i])) + + { + var pvector PointVector + for j := range C { + pvector.vector = append(pvector.vector, C[j].Left) + } + left := pvector.MultiExponentiate(NewFieldVector(P[i])) //.Add(chi.vector[i].Left) + left = new(bn256.G1).Add(new(bn256.G1).Set(left), chi.vector[i].Left) + C_0G.vector = append(C_0G.vector, ConstructElGamal(left, chi.vector[i].Right)) + } + + { + + left := NewPointVector(s.Publickeylist).MultiExponentiate(NewFieldVector(P[i])) + left = new(bn256.G1).Add(new(bn256.G1).Set(left), psi.vector[i].Left) + y_0G.vector = append(y_0G.vector, ConstructElGamal(left, psi.vector[i].Right)) + } + + /* + { // y_0G + var rightp, result bn256.G1 + leftp := NewPointVector(s.Publickeylist).Commit(P[i]) + rightp.ScalarMult(s.Publickeylist[witness.Index[0]], psi.vector[i]) + result.Add(leftp, &rightp) + proof.y_0G = append(proof.y_0G, &result) + //klog.V(2).Infof("y_0G %d %s\n",i, result.String()) + } + + { // gG + var result bn256.G1 + result.ScalarMult(params.G, psi.vector[i]) + proof.gG = append(proof.gG, &result) + //klog.V(2).Infof("gG %d %s\n",i, result.String()) + } + */ + + /* + { // C_XG + var result bn256.G1 + result.ScalarMult(s.D, omega.vector[i]) + proof.C_XG = append(proof.C_XG, &result) + //klog.V(2).Infof("C_XG %d %s\n",i, result.String()) + } + + { // y_XG + var result bn256.G1 + result.ScalarMult(params.G, omega.vector[i]) + proof.y_XG = append(proof.y_XG, &result) + klog.V(2).Infof("y_XG %d %s\n", i, result.String()) + } + */ + + } + + /* + for i := range proof.CLnG { + klog.V(2).Infof("CLnG %d %s\n", i, proof.CLnG[i].String()) + } + for i := range proof.CRnG { + klog.V(2).Infof("CRnG %d %s\n", i, proof.CRnG[i].String()) + } + for i := range proof.C_0G { + klog.V(2).Infof("C_0G %d %s\n", i, proof.C_0G[i].String()) + } + for i := range proof.DG { + klog.V(2).Infof("DG %d %s\n", i, proof.DG[i].String()) + } + for i := range proof.y_0G { + klog.V(2).Infof("y_0G %d %s\n", i, proof.y_0G[i].String()) + } + for i := range proof.gG { + klog.V(2).Infof("gG %d %s\n", i, proof.gG[i].String()) + } + for i := range proof.C_XG { + klog.V(2).Infof("C_XG %d %s\n", i, proof.C_XG[i].String()) + } + for i := range proof.y_XG { + klog.V(2).Infof("y_XG %d %s\n", i, proof.y_XG[i].String()) + } + */ + var C_XG []*ElGamal + for i := 0; i < m; i++ { + C_XG = append(C_XG, CommitElGamal(C[0].Right, new(big.Int).SetUint64(0))) + } + + vPow := new(big.Int).SetInt64(1) // doesn't need reduction, since it' alredy reduced + + for i := 0; i < N; i++ { + + var poly [][]*big.Int + if i%2 == 0 { + poly = P + } else { + poly = Q + } + + // klog.V(2).Infof("\n\n") + // for i := range proof.C_XG { + // klog.V(2).Infof("C_XG before %d %s\n", i, proof.C_XG[i].String()) + // } + + // klog.V(2).Infof("loop %d pos in poly sender %d receiver %d\n", i, (witness.Index[0]+N-(i-i%2))%N, (witness.Index[1]+N-(i-i%2))%N) + + for j := range C_XG { + + amount := new(big.Int).SetUint64(uint64(witness.TransferAmount)) + amount_neg := new(big.Int).Neg(amount) + amount_fees := new(big.Int).SetUint64(s.Fees + burn_value) + left := new(big.Int).Sub(amount_neg, amount_fees) + left = new(big.Int).Mod(new(big.Int).Mul(new(big.Int).Set(left), poly[j][(witness.Index[0]+N-(i-i%2))%N]), bn256.Order) + + right := new(big.Int).Mod(new(big.Int).Mul(new(big.Int).Set(amount), poly[j][(witness.Index[1]+N-(i-i%2))%N]), bn256.Order) + + joined := new(big.Int).Mod(new(big.Int).Add(left, right), bn256.Order) + + mul := new(big.Int).Mod(new(big.Int).Mul(vPow, joined), bn256.Order) + + C_XG[j] = C_XG[j].Plus(mul) + } + + if i != 0 { + vPow.Mul(vPow, v) + vPow.Mod(vPow, bn256.Order) + } + + //klog.V(2).Infof("vPow %d %s\n", i, vPow.Text(16))) + + } + + for i := range C_XG { + proof.C_XG = append(proof.C_XG, C_XG[i].Left) + proof.y_XG = append(proof.y_XG, C_XG[i].Right) + + proof.CLnG = append(proof.CLnG, CnG.vector[i].Left) + proof.CRnG = append(proof.CRnG, CnG.vector[i].Right) + + proof.C_0G = append(proof.C_0G, C_0G.vector[i].Left) + proof.DG = append(proof.DG, C_0G.vector[i].Right) + + proof.y_0G = append(proof.y_0G, y_0G.vector[i].Left) + proof.gG = append(proof.gG, y_0G.vector[i].Right) + } + + //klog.V(2).Infof("\n\n") + // for i := range proof.C_XG { + // klog.V(2).Infof("C_XG after %d %s\n", i, proof.C_XG[i].String()) + // } + + // calculate w hashmash + + w := proof.hashmash1(v) + + { + var input []byte + + input = append(input, ConvertBigIntToByte(v)...) + for i := range proof.CLnG { + input = append(input, proof.CLnG[i].Marshal()...) + } + for i := range proof.CRnG { + input = append(input, proof.CRnG[i].Marshal()...) + } + + for i := range proof.C_0G { + input = append(input, proof.C_0G[i].Marshal()...) + } + for i := range proof.DG { + input = append(input, proof.DG[i].Marshal()...) + } + for i := range proof.y_0G { + input = append(input, proof.y_0G[i].Marshal()...) + } + for i := range proof.gG { + input = append(input, proof.gG[i].Marshal()...) + } + for i := range C_XG { + input = append(input, C_XG[i].Left.Marshal()...) + } + for i := range proof.y_XG { + input = append(input, C_XG[i].Right.Marshal()...) + } + //fmt.Printf("whash %s %s\n", reducedhash(input).Text(16), w.Text(16)) + + } + + proof.f = b.Times(w).Add(a) + + // for i := range proof.f.vector { + // klog.V(2).Infof("proof.f %d %s\n", i, proof.f.vector[i].Text(16)) + // } + + ttttt := new(big.Int).Mod(new(big.Int).Mul(proof_B_internal.Randomness, w), bn256.Order) + proof.z_A = new(big.Int).Mod(new(big.Int).Add(ttttt, proof_A_internal.Randomness), bn256.Order) + + // klog.V(2).Infof("proofz_A %s\n", proof.z_A.Text(16)) + + y := reducedhash(ConvertBigIntToByte(w)) + + // klog.V(2).Infof("yyyyyyyyyy %s\n", y.Text(16)) + + ys_raw := []*big.Int{new(big.Int).SetUint64(1)} + for i := 1; i < 128; i++ { + var tt big.Int + tt.Mul(ys_raw[len(ys_raw)-1], y) + tt.Mod(&tt, bn256.Order) + ys_raw = append(ys_raw, &tt) + } + ys := NewFieldVector(ys_raw) + + z := reducedhash(ConvertBigIntToByte(y)) + // klog.V(2).Infof("zzzzzzzzzz %s \n", z.Text(16)) + + zs := []*big.Int{new(big.Int).Exp(z, new(big.Int).SetUint64(2), bn256.Order), new(big.Int).Exp(z, new(big.Int).SetUint64(3), bn256.Order)} + // for i := range zs { + // klog.V(2).Infof("zs %d %s\n", i, zs[i].Text(16)) + // } + + twos := []*big.Int{new(big.Int).SetUint64(1)} + for i := 1; i < 64; i++ { + var tt big.Int + tt.Mul(twos[len(twos)-1], new(big.Int).SetUint64(2)) + tt.Mod(&tt, bn256.Order) + twos = append(twos, &tt) + } + + twoTimesZs := []*big.Int{} + for i := 0; i < 2; i++ { + for j := 0; j < 64; j++ { + var tt big.Int + tt.Mul(zs[i], twos[j]) + tt.Mod(&tt, bn256.Order) + twoTimesZs = append(twoTimesZs, &tt) + + // klog.V(2).Infof("twoTimesZssss ============= %d %s\n", i*32+j, twoTimesZs[i*32+j].Text(16)) + + } + } + + tmp := aL.AddConstant(new(big.Int).Mod(new(big.Int).Neg(z), bn256.Order)) + lPoly := NewFieldVectorPolynomial(tmp, sL) + //for i := range lPoly.coefficients { + // for j := range lPoly.coefficients[i].vector { + // klog.V(2).Infof("lPoly %d,%d %s\n", i, j, lPoly.coefficients[i].vector[j].Text(16)) + // } + //} + + rPoly := NewFieldVectorPolynomial(ys.Hadamard(aR.AddConstant(z)).Add(NewFieldVector(twoTimesZs)), sR.Hadamard(ys)) + //for i := range rPoly.coefficients { + // for j := range rPoly.coefficients[i].vector { + // klog.V(2).Infof("rPoly %d,%d %s\n", i, j, rPoly.coefficients[i].vector[j].Text(16)) + // } + //} + + tPolyCoefficients := lPoly.InnerProduct(rPoly) // just an array of BN Reds... should be length 3 + //for j := range tPolyCoefficients { + // klog.V(2).Infof("tPolyCoefficients %d,%d %s\n", 0, j, tPolyCoefficients[j].Text(16)) + //} + + proof_T1 := NewPedersenCommitmentNew().Commit(tPolyCoefficients[1]) + proof_T2 := NewPedersenCommitmentNew().Commit(tPolyCoefficients[2]) + proof.T_1 = proof_T1.Result + proof.T_2 = proof_T2.Result + + //polyCommitment := NewPolyCommitment(params, tPolyCoefficients) + /*proof.tCommits = NewPointVector(polyCommitment.GetCommitments()) + + for j := range proof.tCommits.vector { + klog.V(2).Infof("tCommits %d %s\n", j, proof.tCommits.vector[j].String()) + } + */ + + x := new(big.Int) + + { + var input []byte + input = append(input, ConvertBigIntToByte(z)...) // tie intermediates/commit + //for j := range proof.tCommits.vector { + // input = append(input, proof.tCommits.vector[j].Marshal()...) + //} + input = append(input, proof_T1.Result.Marshal()...) + input = append(input, proof_T2.Result.Marshal()...) + x = reducedhash(input) + } + + //klog.V(2).Infof("x %s\n", x.Text(16)) + + //evalCommit := polyCommitment.Evaluate(x) + + //klog.V(2).Infof("evalCommit.X %s\n", j, evalCommit.X.Text(16)) + //klog.V(2).Infof("evalCommit.R %s\n", j, evalCommit.R.Text(16)) + + //proof.that = evalCommit.X + + xsquare := new(big.Int).Mod(new(big.Int).Mul(x, x), bn256.Order) + + proof.that = tPolyCoefficients[0] + proof.that = new(big.Int).Mod(new(big.Int).Add(proof.that, new(big.Int).Mod(new(big.Int).Mul(tPolyCoefficients[1], x), bn256.Order)), bn256.Order) + proof.that = new(big.Int).Mod(new(big.Int).Add(proof.that, new(big.Int).Mod(new(big.Int).Mul(tPolyCoefficients[2], xsquare), bn256.Order)), bn256.Order) + + /* + accumulator := new(big.Int).Set(x) + for i := 1; i < 3; i++ { + tmp := new(big.Int).Set(accumulator) + proof.that = proof.that.Add(new(bn256.G1).Set(proof.that), tPolyCoefficients[i].Times(accumulator)) + accumulator.Mod(new(big.Int).Mul(tmp, x), bn256.Order) + } + */ + + //klog.V(2).Infof("evalCommit.that %s\n", proof.that.Text(16)) + + //tauX := evalCommit.R + tauX_left := new(big.Int).Mod(new(big.Int).Mul(proof_T1.Randomness, x), bn256.Order) + tauX_right := new(big.Int).Mod(new(big.Int).Mul(proof_T2.Randomness, xsquare), bn256.Order) + tauX := new(big.Int).Mod(new(big.Int).Add(tauX_left, tauX_right), bn256.Order) + + proof.mu = new(big.Int).Mod(new(big.Int).Mul(proof_BS_internal.Randomness, x), bn256.Order) + proof.mu.Add(proof.mu, proof_BA_internal.Randomness) + proof.mu.Mod(proof.mu, bn256.Order) + + //klog.V(2).Infof("proof.mu %s\n", proof.mu.Text(16)) + + var CrnR, y_0R, y_XR bn256.G1 + // var gR bn256.G1 + CrnR.ScalarMult(params.G, new(big.Int)) + y_0R.ScalarMult(params.G, new(big.Int)) + y_XR.ScalarMult(params.G, new(big.Int)) + //DR.ScalarMult(params.G, new(big.Int)) + //gR.ScalarMult(params.G, new(big.Int)) + + CnR := ConstructElGamal(nil, ElGamal_ZERO) // only right side is needer + chi_bigint := new(big.Int).SetUint64(0) + psi_bigint := new(big.Int).SetUint64(0) + C_XR := ConstructElGamal(nil, ElGamal_ZERO) // only right side is needer + + var p_, q_ []*big.Int + for i := 0; i < N; i++ { + p_ = append(p_, new(big.Int)) + q_ = append(q_, new(big.Int)) + } + p := NewFieldVector(p_) + q := NewFieldVector(q_) + + wPow := new(big.Int).SetUint64(1) // already reduced + + for i := 0; i < m; i++ { + + { + CnR = CnR.Add(phi.vector[i].Neg().Mul(wPow)) + } + + { + mm := new(big.Int).Mod(new(big.Int).Mul(chi.vector[i].Randomness, wPow), bn256.Order) + chi_bigint = new(big.Int).Mod(new(big.Int).Add(chi_bigint, mm), bn256.Order) + } + + { + mm := new(big.Int).Mod(new(big.Int).Mul(psi.vector[i].Randomness, wPow), bn256.Order) + psi_bigint = new(big.Int).Mod(new(big.Int).Add(psi_bigint, mm), bn256.Order) + } + + /* { + tmp := new(bn256.G1) + mm := new(big.Int).Mod(new(big.Int).Neg(chi.vector[i]), bn256.Order) + mm = mm.Mod(new(big.Int).Mul(mm, wPow), bn256.Order) + tmp.ScalarMult(params.G, mm) + DR.Add(new(bn256.G1).Set(&DR), tmp) + } + + { + tmp := new(bn256.G1) + mm := new(big.Int).Mod(new(big.Int).Neg(psi.vector[i]), bn256.Order) + mm = mm.Mod(new(big.Int).Mul(mm, wPow), bn256.Order) + tmp.ScalarMult(s.Publickeylist[witness.Index[0]], mm) + y_0R.Add(new(bn256.G1).Set(&y_0R), tmp) + } + */ + /* + { + tmp := new(bn256.G1) + mm := new(big.Int).Mod(new(big.Int).Neg(psi.vector[i]), bn256.Order) + mm = mm.Mod(new(big.Int).Mul(mm, wPow), bn256.Order) + tmp.ScalarMult(params.G, mm) + gR.Add(new(bn256.G1).Set(&gR), tmp) + } + + { + tmp := new(bn256.G1) + tmp.ScalarMult(proof.y_XG[i], new(big.Int).Neg(wPow)) + y_XR.Add(new(bn256.G1).Set(&y_XR), tmp) + } + */ + + { + C_XR = C_XR.Add(C_XG[i].Neg().Mul(wPow)) + } + + //fmt.Printf("y_XG[%d] %s\n",i, proof.y_XG[i].String()) + //fmt.Printf("C_XG[%d] %s\n",i, C_XG[i].Right.String()) + + //fmt.Printf("G %s\n",G.String()) + //fmt.Printf("elgamalG %s\n",C_XG[0].G.String()) + + p = p.Add(NewFieldVector(P[i]).Times(wPow)) + q = q.Add(NewFieldVector(Q[i]).Times(wPow)) + wPow = new(big.Int).Mod(new(big.Int).Mul(wPow, w), bn256.Order) + + // klog.V(2).Infof("wPow %s\n", wPow.Text(16)) + + } + + CnR = CnR.Add(Cn.vector[witness.Index[0]].Mul(wPow)) + + //for i := range CnR{ + // proof.CLnG = append(proof.CLnG, CnR[i].Left) + // proof.CRnG = append(proof.CRnG, CnR[i].Right) + //} + + //CrnR.Add(new(bn256.G1).Set(&CrnR), new(bn256.G1).ScalarMult(s.CRn[witness.Index[0]], wPow)) + //y_0R.Add(new(bn256.G1).Set(&y_0R), new(bn256.G1).ScalarMult(s.Publickeylist[witness.Index[0]], wPow)) + //DR.Add(new(bn256.G1).Set(&DR), new(bn256.G1).ScalarMult(s.D, wPow)) + + DR := new(bn256.G1).ScalarMult(C[0].Right, wPow) + DR = new(bn256.G1).Add(new(bn256.G1).Set(DR), new(bn256.G1).ScalarMult(global_pedersen_values.G, new(big.Int).Mod(new(big.Int).Neg(chi_bigint), bn256.Order))) + + gR := new(bn256.G1).ScalarMult(global_pedersen_values.G, new(big.Int).Mod(new(big.Int).Sub(wPow, psi_bigint), bn256.Order)) + + //gR.Add(new(bn256.G1).Set(&gR), new(bn256.G1).ScalarMult(params.G, wPow)) + + var p__, q__ []*big.Int + for i := 0; i < N; i++ { + + if i == witness.Index[0] { + p__ = append(p__, new(big.Int).Set(wPow)) + } else { + p__ = append(p__, new(big.Int)) + } + + if i == witness.Index[1] { + q__ = append(q__, new(big.Int).Set(wPow)) + } else { + q__ = append(q__, new(big.Int)) + } + } + p = p.Add(NewFieldVector(p__)) + q = q.Add(NewFieldVector(q__)) + + // klog.V(2).Infof("CrnR %s\n", CrnR.String()) + // klog.V(2).Infof("DR %s\n", DR.String()) + // klog.V(2).Infof("y_0R %s\n", y_0R.String()) + // klog.V(2).Infof("gR %s\n", gR.String()) + // klog.V(2).Infof("y_XR %s\n", y_XR.String()) + + // for i := range p.vector { + // klog.V(2).Infof("p %d %s \n", i, p.vector[i].Text(16)) + // } + + // for i := range q.vector { + // klog.V(2).Infof("q %d %s \n", i, q.vector[i].Text(16)) + // } + + y_p := Convolution(p, NewPointVector(s.Publickeylist)) + y_q := Convolution(q, NewPointVector(s.Publickeylist)) + + // for i := range y_p.vector { + // klog.V(2).Infof("y_p %d %s \n", i, y_p.vector[i].String()) + // } + // for i := range y_q.vector { + // klog.V(2).Infof("y_q %d %s \n", i, y_q.vector[i].String()) + // } + + vPow = new(big.Int).SetUint64(1) // already reduced + for i := 0; i < N; i++ { + + ypoly := y_p + if i%2 == 1 { + ypoly = y_q + } + y_XR.Add(new(bn256.G1).Set(&y_XR), new(bn256.G1).ScalarMult(ypoly.vector[i/2], vPow)) + + C_XR = C_XR.Add(ConstructElGamal(nil, new(bn256.G1).ScalarMult(ypoly.vector[i/2], vPow))) + + //fmt.Printf("y_XR[%d] %s\n",i, y_XR.String()) + //fmt.Printf("C_XR[%d] %s\n",i, C_XR.Right.String()) + if i > 0 { + vPow = new(big.Int).Mod(new(big.Int).Mul(vPow, v), bn256.Order) + } + } + + // klog.V(2).Infof("y_XR %s\n", y_XR.String()) + // klog.V(2).Infof("vPow %s\n", vPow.Text(16)) + // klog.V(2).Infof("v %s\n", v.Text(16)) + + k_sk := RandomScalarFixed() + k_r := RandomScalarFixed() + k_b := RandomScalarFixed() + k_tau := RandomScalarFixed() + + A_y := new(bn256.G1).ScalarMult(gR, k_sk) + A_D := new(bn256.G1).ScalarMult(params.G, k_r) + A_b := new(bn256.G1).ScalarMult(params.G, k_b) + t1 := new(bn256.G1).ScalarMult(CnR.Right, zs[1]) + d1 := new(bn256.G1).ScalarMult(DR, new(big.Int).Mod(new(big.Int).Neg(zs[0]), bn256.Order)) + d1 = new(bn256.G1).Add(d1, t1) + d1 = new(bn256.G1).ScalarMult(d1, k_sk) + A_b = new(bn256.G1).Add(A_b, d1) + + A_X := new(bn256.G1).ScalarMult(C_XR.Right, k_r) + + A_t := new(bn256.G1).ScalarMult(params.G, new(big.Int).Mod(new(big.Int).Neg(k_b), bn256.Order)) + A_t = new(bn256.G1).Add(A_t, new(bn256.G1).ScalarMult(params.H, k_tau)) + + A_u := new(bn256.G1) + + { + var input []byte + input = append(input, []byte(PROTOCOL_CONSTANT)...) + input = append(input, s.Roothash[:]...) + + point := HashToPoint(HashtoNumber(input)) + + A_u = new(bn256.G1).ScalarMult(point, k_sk) + } + + // klog.V(2).Infof("A_y %s\n", A_y.String()) + // klog.V(2).Infof("A_D %s\n", A_D.String()) + // klog.V(2).Infof("A_b %s\n", A_b.String()) + // klog.V(2).Infof("A_X %s\n", A_X.String()) + // klog.V(2).Infof("A_t %s\n", A_t.String()) + // klog.V(2).Infof("A_u %s\n", A_u.String()) + + { + var input []byte + input = append(input, ConvertBigIntToByte(x)...) + input = append(input, A_y.Marshal()...) + input = append(input, A_D.Marshal()...) + input = append(input, A_b.Marshal()...) + input = append(input, A_X.Marshal()...) + input = append(input, A_t.Marshal()...) + input = append(input, A_u.Marshal()...) + proof.c = reducedhash(input) + } + + proof.s_sk = new(big.Int).Mod(new(big.Int).Mul(proof.c, witness.SecretKey), bn256.Order) + proof.s_sk = new(big.Int).Mod(new(big.Int).Add(proof.s_sk, k_sk), bn256.Order) + + proof.s_r = new(big.Int).Mod(new(big.Int).Mul(proof.c, witness.R), bn256.Order) + proof.s_r = new(big.Int).Mod(new(big.Int).Add(proof.s_r, k_r), bn256.Order) + + // proof_c_neg := new(big.Int).Mod(new(big.Int).Neg(proof.c), bn256.Order) + // dummyA_X := new(bn256.G1).ScalarMult(&y_XR, proof.s_r) //, new(bn256.G1).ScalarMult(anonsupport.C_XR, proof_c_neg) ) + + // klog.V(2).Infof("dummyA_X %s\n", dummyA_X.String()) + // klog.V(2).Infof("s_r %s\n", proof.s_r.Text(16)) + // klog.V(2).Infof("C %s\n", proof.c.Text(16)) + // klog.V(2).Infof("C_neg %s\n", proof_c_neg.Text(16)) + + w_transfer := new(big.Int).Mod(new(big.Int).Mul(new(big.Int).SetUint64(uint64(witness.TransferAmount)), zs[0]), bn256.Order) + w_balance := new(big.Int).Mod(new(big.Int).Mul(new(big.Int).SetUint64(uint64(witness.Balance)), zs[1]), bn256.Order) + w_tmp := new(big.Int).Mod(new(big.Int).Add(w_transfer, w_balance), bn256.Order) + w_tmp = new(big.Int).Mod(new(big.Int).Mul(w_tmp, wPow), bn256.Order) + w_tmp = new(big.Int).Mod(new(big.Int).Mul(w_tmp, proof.c), bn256.Order) + proof.s_b = new(big.Int).Mod(new(big.Int).Add(w_tmp, k_b), bn256.Order) + + proof.s_tau = new(big.Int).Mod(new(big.Int).Mul(tauX, wPow), bn256.Order) + proof.s_tau = new(big.Int).Mod(new(big.Int).Mul(proof.s_tau, proof.c), bn256.Order) + proof.s_tau = new(big.Int).Mod(new(big.Int).Add(proof.s_tau, k_tau), bn256.Order) + + // klog.V(2).Infof("proof.c %s\n", proof.c.Text(16)) + // klog.V(2).Infof("proof.s_sk %s\n", proof.s_sk.Text(16)) + // klog.V(2).Infof("proof.s_r %s\n", proof.s_r.Text(16)) + // klog.V(2).Infof("proof.s_b %s\n", proof.s_b.Text(16)) + // klog.V(2).Infof("proof.s_tau %s\n", proof.s_tau.Text(16)) + + o := reducedhash(ConvertBigIntToByte(proof.c)) + + pvector := NewPedersenVectorCommitment() + pvector.H = new(bn256.G1).ScalarMult(pvector.H, o) + pvector.Hs = pvector.Hs.Hadamard(ys.Invert().vector) + pvector.gvalues = lPoly.Evaluate(x) + pvector.hvalues = rPoly.Evaluate(x) + proof.ip = NewInnerProductProofNew(pvector, o) + /* + + u_x := new(bn256.G1).ScalarMult(params.G, o) + P1 = new(bn256.G1).Add(P1, new(bn256.G1).ScalarMult(u_x, proof.that)) + klog.V(2).Infof("o %s\n", o.Text(16)) + klog.V(2).Infof("x %s\n", x.Text(16)) + klog.V(2).Infof("u_x %s\n", u_x.String()) + klog.V(2).Infof("p %s\n", P1.String()) + klog.V(2).Infof("hPrimes length %d\n", len(hPrimes.vector)) + + primebase := NewGeneratorParams3(u_x, params.Gs, hPrimes) // trigger sigma protocol + ipstatement := &IPStatement{PrimeBase: primebase, P: P1} + ipwitness := &IPWitness{L: lPoly.Evaluate(x), R: rPoly.Evaluate(x)} + + for i := range ipwitness.L.vector { + klog.V(2).Infof("L %d %s \n", i, ipwitness.L.vector[i].Text(16)) + } + + for i := range ipwitness.R.vector { + klog.V(2).Infof("R %d %s \n", i, ipwitness.R.vector[i].Text(16)) + } + + proof.ip = NewInnerProductProof(ipstatement, ipwitness, o) + */ + + return &proof + +} diff --git a/cryptography/crypto/proof_innerproduct.go b/cryptography/crypto/proof_innerproduct.go new file mode 100644 index 0000000..b8a3f32 --- /dev/null +++ b/cryptography/crypto/proof_innerproduct.go @@ -0,0 +1,313 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +//import "fmt" +import "math" +import "math/big" +import "bytes" + +//import "crypto/rand" +//import "encoding/hex" + +import "github.com/deroproject/derohe/cryptography/bn256" + +//import "golang.org/x/crypto/sha3" + +// basically the Σ-protocol +type InnerProduct struct { + a, b *big.Int + ls, rs []*bn256.G1 +} + +func (ip *InnerProduct) Size() int { + return FIELDELEMENT_SIZE + FIELDELEMENT_SIZE + 1 + len(ip.ls)*POINT_SIZE + len(ip.rs)*POINT_SIZE +} + +// since our bulletproofs are 128 bits, we can get away hard coded 7 entries +func (ip *InnerProduct) Serialize(w *bytes.Buffer) { + w.Write(ConvertBigIntToByte(ip.a)) + w.Write(ConvertBigIntToByte(ip.b)) + // w.WriteByte(byte(len(ip.ls))) // we can skip this byte also, why not skip it + + // fmt.Printf("inner proof length byte %d\n",len(ip.ls)) + for i := range ip.ls { + w.Write(ip.ls[i].EncodeCompressed()) + w.Write(ip.rs[i].EncodeCompressed()) + } +} + +func (ip *InnerProduct) Deserialize(r *bytes.Reader) (err error) { + + var buf [32]byte + var bufp [33]byte + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + ip.a = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + + if n, err := r.Read(buf[:]); n == 32 && err == nil { + ip.b = new(big.Int).SetBytes(buf[:]) + } else { + return err + } + + length := 7 + + ip.ls = ip.ls[:0] + ip.rs = ip.rs[:0] + for i := 0; i < length; i++ { + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + + ip.ls = append(ip.ls, &p) + } else { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + + ip.rs = append(ip.rs, &p) + } else { + return err + } + + } + + return err +} + +func NewInnerProductProof(ips *IPStatement, witness *IPWitness, salt *big.Int) *InnerProduct { + var ip InnerProduct + + ip.generateInnerProductProof(ips.PrimeBase, ips.P, witness.L, witness.R, salt) + return &ip +} + +func (ip *InnerProduct) generateInnerProductProof(base *GeneratorParams, P *bn256.G1, as, bs *FieldVector, prev_challenge *big.Int) { + n := as.Length() + + if n == 1 { // the proof is done, ls,rs are already in place + ip.a = as.vector[0] + ip.b = bs.vector[0] + return + } + + nPrime := n / 2 + asLeft := as.Slice(0, nPrime) + asRight := as.Slice(nPrime, n) + bsLeft := bs.Slice(0, nPrime) + bsRight := bs.Slice(nPrime, n) + gLeft := base.Gs.Slice(0, nPrime) + gRight := base.Gs.Slice(nPrime, n) + hLeft := base.Hs.Slice(0, nPrime) + hRight := base.Hs.Slice(nPrime, n) + + cL := asLeft.InnerProduct(bsRight) + cR := asRight.InnerProduct(bsLeft) + + u := base.H + L := new(bn256.G1).Add(gRight.Commit(asLeft.vector), hLeft.Commit(bsRight.vector)) + L = new(bn256.G1).Add(L, new(bn256.G1).ScalarMult(u, cL)) + + R := new(bn256.G1).Add(gLeft.Commit(asRight.vector), hRight.Commit(bsLeft.vector)) + R = new(bn256.G1).Add(R, new(bn256.G1).ScalarMult(u, cR)) + + ip.ls = append(ip.ls, L) + ip.rs = append(ip.rs, R) + + var input []byte + input = append(input, ConvertBigIntToByte(prev_challenge)...) + input = append(input, L.Marshal()...) + input = append(input, R.Marshal()...) + x := reducedhash(input) + + xinv := new(big.Int).ModInverse(x, bn256.Order) + + gPrime := gLeft.Times(xinv).Add(gRight.Times(x)) + hPrime := hLeft.Times(x).Add(hRight.Times(xinv)) + aPrime := asLeft.Times(x).Add(asRight.Times(xinv)) + bPrime := bsLeft.Times(xinv).Add(bsRight.Times(x)) + + basePrime := NewGeneratorParams3(u, gPrime, hPrime) + + PPrimeL := new(bn256.G1).ScalarMult(L, new(big.Int).Mod(new(big.Int).Mul(x, x), bn256.Order)) //L * (x*x) + PPrimeR := new(bn256.G1).ScalarMult(R, new(big.Int).Mod(new(big.Int).Mul(xinv, xinv), bn256.Order)) //R * (xinv*xinv) + + PPrime := new(bn256.G1).Add(PPrimeL, PPrimeR) + PPrime = new(bn256.G1).Add(PPrime, P) + + ip.generateInnerProductProof(basePrime, PPrime, aPrime, bPrime, x) + + return + +} + +func NewInnerProductProofNew(p *PedersenVectorCommitment, salt *big.Int) *InnerProduct { + var ip InnerProduct + + ip.generateInnerProductProofNew(p, p.gvalues, p.hvalues, salt) + return &ip +} + +func (ip *InnerProduct) generateInnerProductProofNew(p *PedersenVectorCommitment, as, bs *FieldVector, prev_challenge *big.Int) { + n := as.Length() + + if n == 1 { // the proof is done, ls,rs are already in place + ip.a = as.vector[0] + ip.b = bs.vector[0] + return + } + + nPrime := n / 2 + asLeft := as.Slice(0, nPrime) + asRight := as.Slice(nPrime, n) + bsLeft := bs.Slice(0, nPrime) + bsRight := bs.Slice(nPrime, n) + + gsLeft := p.Gs.Slice(0, nPrime) + gsRight := p.Gs.Slice(nPrime, n) + hsLeft := p.Hs.Slice(0, nPrime) + hsRight := p.Hs.Slice(nPrime, n) + + cL := asLeft.InnerProduct(bsRight) + cR := asRight.InnerProduct(bsLeft) + + /*u := base.H + L := new(bn256.G1).Add(gRight.Commit(asLeft.vector), hLeft.Commit(bsRight.vector)) + L = new(bn256.G1).Add(L, new(bn256.G1).ScalarMult(u, cL)) + + R := new(bn256.G1).Add(gLeft.Commit(asRight.vector), hRight.Commit(bsLeft.vector)) + R = new(bn256.G1).Add(R, new(bn256.G1).ScalarMult(u, cR)) + */ + + Lpart := new(bn256.G1).Add(gsRight.MultiExponentiate(asLeft), hsLeft.MultiExponentiate(bsRight)) + L := new(bn256.G1).Add(Lpart, new(bn256.G1).ScalarMult(p.H, cL)) + + Rpart := new(bn256.G1).Add(gsLeft.MultiExponentiate(asRight), hsRight.MultiExponentiate(bsLeft)) + R := new(bn256.G1).Add(Rpart, new(bn256.G1).ScalarMult(p.H, cR)) + + ip.ls = append(ip.ls, L) + ip.rs = append(ip.rs, R) + + var input []byte + input = append(input, ConvertBigIntToByte(prev_challenge)...) + input = append(input, L.Marshal()...) + input = append(input, R.Marshal()...) + x := reducedhash(input) + + xInv := new(big.Int).ModInverse(x, bn256.Order) + + p.Gs = gsLeft.Times(xInv).Add(gsRight.Times(x)) + p.Hs = hsLeft.Times(x).Add(hsRight.Times(xInv)) + asPrime := asLeft.Times(x).Add(asRight.Times(xInv)) + bsPrime := bsLeft.Times(xInv).Add(bsRight.Times(x)) + + ip.generateInnerProductProofNew(p, asPrime, bsPrime, x) + + return + +} + +func (ip *InnerProduct) Verify(hs []*bn256.G1, u, P *bn256.G1, salt *big.Int, gp *GeneratorParams) bool { + log_n := uint(len(ip.ls)) + + if len(ip.ls) != len(ip.rs) { // length must be same + return false + } + n := uint(math.Pow(2, float64(log_n))) + + o := salt + var challenges []*big.Int + for i := uint(0); i < log_n; i++ { + + var input []byte + input = append(input, ConvertBigIntToByte(o)...) + input = append(input, ip.ls[i].Marshal()...) + input = append(input, ip.rs[i].Marshal()...) + o = reducedhash(input) + challenges = append(challenges, o) + + o_inv := new(big.Int).ModInverse(o, bn256.Order) + + PPrimeL := new(bn256.G1).ScalarMult(ip.ls[i], new(big.Int).Mod(new(big.Int).Mul(o, o), bn256.Order)) //L * (x*x) + PPrimeR := new(bn256.G1).ScalarMult(ip.rs[i], new(big.Int).Mod(new(big.Int).Mul(o_inv, o_inv), bn256.Order)) //L * (x*x) + + PPrime := new(bn256.G1).Add(PPrimeL, PPrimeR) + P = new(bn256.G1).Add(PPrime, P) + } + + exp := new(big.Int).SetUint64(1) + for i := uint(0); i < log_n; i++ { + exp = new(big.Int).Mod(new(big.Int).Mul(exp, challenges[i]), bn256.Order) + } + + exp_inv := new(big.Int).ModInverse(exp, bn256.Order) + + exponents := make([]*big.Int, n, n) + + exponents[0] = exp_inv // initializefirst element + + bits := make([]bool, n, n) + for i := uint(0); i < n/2; i++ { + for j := uint(0); (1< 0 { + anonsupport.vPow = new(big.Int).Mod(new(big.Int).Mul(anonsupport.vPow, anonsupport.v), bn256.Order) + // klog.V(2).Infof("vPow %s\n", anonsupport.vPow.Text(16)) + } + } + + // klog.V(2).Infof("vPow %s\n", anonsupport.vPow.Text(16)) + // klog.V(2).Infof("v %s\n", anonsupport.v.Text(16)) + + anonsupport.wPow = new(big.Int).SetUint64(1) + anonsupport.gR = new(bn256.G1) + anonsupport.gR.Unmarshal(zeroes[:]) + anonsupport.DR = new(bn256.G1) + anonsupport.DR.Unmarshal(zeroes[:]) + + for i := 0; i < m; i++ { + wPow_neg := new(big.Int).Mod(new(big.Int).Neg(anonsupport.wPow), bn256.Order) + anonsupport.CLnR.Add(new(bn256.G1).Set(anonsupport.CLnR), new(bn256.G1).ScalarMult(proof.CLnG[i], wPow_neg)) + anonsupport.CRnR.Add(new(bn256.G1).Set(anonsupport.CRnR), new(bn256.G1).ScalarMult(proof.CRnG[i], wPow_neg)) + + anonsupport.CR[0][0].Add(new(bn256.G1).Set(anonsupport.CR[0][0]), new(bn256.G1).ScalarMult(proof.C_0G[i], wPow_neg)) + anonsupport.DR.Add(new(bn256.G1).Set(anonsupport.DR), new(bn256.G1).ScalarMult(proof.DG[i], wPow_neg)) + anonsupport.yR[0][0].Add(new(bn256.G1).Set(anonsupport.yR[0][0]), new(bn256.G1).ScalarMult(proof.y_0G[i], wPow_neg)) + anonsupport.gR.Add(new(bn256.G1).Set(anonsupport.gR), new(bn256.G1).ScalarMult(proof.gG[i], wPow_neg)) + + anonsupport.C_XR.Add(new(bn256.G1).Set(anonsupport.C_XR), new(bn256.G1).ScalarMult(proof.C_XG[i], wPow_neg)) + anonsupport.y_XR.Add(new(bn256.G1).Set(anonsupport.y_XR), new(bn256.G1).ScalarMult(proof.y_XG[i], wPow_neg)) + + anonsupport.wPow = new(big.Int).Mod(new(big.Int).Mul(anonsupport.wPow, anonsupport.w), bn256.Order) + + } + // klog.V(2).Infof("qCrnR %s\n", anonsupport.CRnR.String()) + + anonsupport.DR.Add(new(bn256.G1).Set(anonsupport.DR), new(bn256.G1).ScalarMult(s.D, anonsupport.wPow)) + anonsupport.gR.Add(new(bn256.G1).Set(anonsupport.gR), new(bn256.G1).ScalarMult(gparams.G, anonsupport.wPow)) + anonsupport.C_XR.Add(new(bn256.G1).Set(anonsupport.C_XR), new(bn256.G1).ScalarMult(gparams.G, new(big.Int).Mod(new(big.Int).Mul(new(big.Int).SetUint64(total_open_value), anonsupport.wPow), bn256.Order))) + + //anonAuxiliaries.C_XR = anonAuxiliaries.C_XR.add(Utils.g().mul(Utils.fee().mul(anonAuxiliaries.wPow))); // this line is new + + // at this point, these parameters are comparable with proof generator + // klog.V(2).Infof("CLnR %s\n", anonsupport.CLnR.String()) + // klog.V(2).Infof("qCrnR %s\n", anonsupport.CRnR.String()) + // klog.V(2).Infof("DR %s\n", anonsupport.DR.String()) + // klog.V(2).Infof("gR %s\n", anonsupport.gR.String()) + // klog.V(2).Infof("C_XR %s\n", anonsupport.C_XR.String()) + // klog.V(2).Infof("y_XR %s\n", anonsupport.y_XR.String()) + + protsupport.y = reducedhash(ConvertBigIntToByte(anonsupport.w)) + protsupport.ys = append(protsupport.ys, new(big.Int).SetUint64(1)) + protsupport.k = new(big.Int).SetUint64(1) + for i := 1; i < 128; i++ { + protsupport.ys = append(protsupport.ys, new(big.Int).Mod(new(big.Int).Mul(protsupport.ys[i-1], protsupport.y), bn256.Order)) + protsupport.k = new(big.Int).Mod(new(big.Int).Add(protsupport.k, protsupport.ys[i]), bn256.Order) + } + + protsupport.z = reducedhash(ConvertBigIntToByte(protsupport.y)) + protsupport.zs = []*big.Int{new(big.Int).Exp(protsupport.z, new(big.Int).SetUint64(2), bn256.Order), new(big.Int).Exp(protsupport.z, new(big.Int).SetUint64(3), bn256.Order)} + + protsupport.zSum = new(big.Int).Mod(new(big.Int).Add(protsupport.zs[0], protsupport.zs[1]), bn256.Order) + protsupport.zSum = new(big.Int).Mod(new(big.Int).Mul(new(big.Int).Set(protsupport.zSum), protsupport.z), bn256.Order) + + // klog.V(2).Infof("zsum %s\n ", protsupport.zSum.Text(16)) + + z_z0 := new(big.Int).Mod(new(big.Int).Sub(protsupport.z, protsupport.zs[0]), bn256.Order) + protsupport.k = new(big.Int).Mod(new(big.Int).Mul(protsupport.k, z_z0), bn256.Order) + + proof_2_64, _ := new(big.Int).SetString("18446744073709551616", 10) + zsum_pow := new(big.Int).Mod(new(big.Int).Mul(protsupport.zSum, proof_2_64), bn256.Order) + zsum_pow = new(big.Int).Mod(new(big.Int).Sub(zsum_pow, protsupport.zSum), bn256.Order) + protsupport.k = new(big.Int).Mod(new(big.Int).Sub(protsupport.k, zsum_pow), bn256.Order) + + protsupport.t = new(big.Int).Mod(new(big.Int).Sub(proof.that, protsupport.k), bn256.Order) // t = tHat - delta(y, z) + + // klog.V(2).Infof("that %s\n ", proof.that.Text(16)) + // klog.V(2).Infof("zk %s\n ", protsupport.k.Text(16)) + + for i := 0; i < 64; i++ { + protsupport.twoTimesZSquared[i] = new(big.Int).Mod(new(big.Int).Mul(protsupport.zs[0], new(big.Int).SetUint64(uint64(math.Pow(2, float64(i))))), bn256.Order) + protsupport.twoTimesZSquared[64+i] = new(big.Int).Mod(new(big.Int).Mul(protsupport.zs[1], new(big.Int).SetUint64(uint64(math.Pow(2, float64(i))))), bn256.Order) + } + + // for i := 0; i < 128; i++ { + // klog.V(2).Infof("zsq %d %s", i, protsupport.twoTimesZSquared[i].Text(16)) + // } + + x := new(big.Int) + + { + var input []byte + input = append(input, ConvertBigIntToByte(protsupport.z)...) // tie intermediates/commit + input = append(input, proof.T_1.Marshal()...) + input = append(input, proof.T_2.Marshal()...) + x = reducedhash(input) + } + + xsq := new(big.Int).Mod(new(big.Int).Mul(x, x), bn256.Order) + protsupport.tEval = new(bn256.G1).ScalarMult(proof.T_1, x) + protsupport.tEval.Add(new(bn256.G1).Set(protsupport.tEval), new(bn256.G1).ScalarMult(proof.T_2, xsq)) + + // klog.V(2).Infof("protsupport.tEval %s\n", protsupport.tEval.String()) + + proof_c_neg := new(big.Int).Mod(new(big.Int).Neg(proof.c), bn256.Order) + + sigmasupport.A_y = new(bn256.G1).Add(new(bn256.G1).ScalarMult(anonsupport.gR, proof.s_sk), new(bn256.G1).ScalarMult(anonsupport.yR[0][0], proof_c_neg)) + sigmasupport.A_D = new(bn256.G1).Add(new(bn256.G1).ScalarMult(gparams.G, proof.s_r), new(bn256.G1).ScalarMult(s.D, proof_c_neg)) + + zs0_neg := new(big.Int).Mod(new(big.Int).Neg(protsupport.zs[0]), bn256.Order) + + left := new(bn256.G1).ScalarMult(anonsupport.DR, zs0_neg) + left.Add(new(bn256.G1).Set(left), new(bn256.G1).ScalarMult(anonsupport.CRnR, protsupport.zs[1])) + left = new(bn256.G1).ScalarMult(new(bn256.G1).Set(left), proof.s_sk) + + // TODO mid seems wrong + amount_fees := new(big.Int).SetUint64(total_open_value) + mid := new(bn256.G1).ScalarMult(G, new(big.Int).Mod(new(big.Int).Mul(amount_fees, anonsupport.wPow), bn256.Order)) + mid.Add(new(bn256.G1).Set(mid), new(bn256.G1).Set(anonsupport.CR[0][0])) + + right := new(bn256.G1).ScalarMult(mid, zs0_neg) + right.Add(new(bn256.G1).Set(right), new(bn256.G1).ScalarMult(anonsupport.CLnR, protsupport.zs[1])) + right = new(bn256.G1).ScalarMult(new(bn256.G1).Set(right), proof_c_neg) + + sigmasupport.A_b = new(bn256.G1).ScalarMult(gparams.G, proof.s_b) + temp := new(bn256.G1).Add(left, right) + sigmasupport.A_b.Add(new(bn256.G1).Set(sigmasupport.A_b), temp) + + //- sigmaAuxiliaries.A_b = Utils.g().mul(proof.s_b).add(anonAuxiliaries.DR.mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CRnR.mul(zetherAuxiliaries.zs[1])).mul(proof.s_sk).add(anonAuxiliaries.CR[0][0] .mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CLnR.mul(zetherAuxiliaries.zs[1])).mul(proof.c.neg()))); + //+ sigmaAuxiliaries.A_b = Utils.g().mul(proof.s_b).add(anonAuxiliaries.DR.mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CRnR.mul(zetherAuxiliaries.zs[1])).mul(proof.s_sk).add(anonAuxiliaries.CR[0][0].add(Utils.g().mul(Utils.fee().mul(anonAuxiliaries.wPow))).mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CLnR.mul(zetherAuxiliaries.zs[1])).mul(proof.c.neg()))); + + //var fees bn256.G1 + //fees.ScalarMult(G, new(big.Int).SetInt64(int64( -1 ))) + //anonsupport.C_XR.Add( new(bn256.G1).Set(anonsupport.C_XR), &fees) + + sigmasupport.A_X = new(bn256.G1).Add(new(bn256.G1).ScalarMult(anonsupport.y_XR, proof.s_r), new(bn256.G1).ScalarMult(anonsupport.C_XR, proof_c_neg)) + + proof_s_b_neg := new(big.Int).Mod(new(big.Int).Neg(proof.s_b), bn256.Order) + + sigmasupport.A_t = new(bn256.G1).ScalarMult(gparams.G, protsupport.t) + sigmasupport.A_t.Add(new(bn256.G1).Set(sigmasupport.A_t), new(bn256.G1).Neg(protsupport.tEval)) + sigmasupport.A_t = new(bn256.G1).ScalarMult(sigmasupport.A_t, new(big.Int).Mod(new(big.Int).Mul(proof.c, anonsupport.wPow), bn256.Order)) + sigmasupport.A_t.Add(new(bn256.G1).Set(sigmasupport.A_t), new(bn256.G1).ScalarMult(gparams.H, proof.s_tau)) + sigmasupport.A_t.Add(new(bn256.G1).Set(sigmasupport.A_t), new(bn256.G1).ScalarMult(gparams.G, proof_s_b_neg)) + // klog.V(2).Infof("t %s\n ", protsupport.t.Text(16)) + // klog.V(2).Infof("protsupport.tEval %s\n", protsupport.tEval.String()) + + { + var input []byte + input = append(input, []byte(PROTOCOL_CONSTANT)...) + input = append(input, s.Roothash[:]...) + + point := HashToPoint(HashtoNumber(input)) + + sigmasupport.A_u = new(bn256.G1).ScalarMult(point, proof.s_sk) + sigmasupport.A_u.Add(new(bn256.G1).Set(sigmasupport.A_u), new(bn256.G1).ScalarMult(proof.u, proof_c_neg)) + } + + // klog.V(2).Infof("A_y %s\n", sigmasupport.A_y.String()) + // klog.V(2).Infof("A_D %s\n", sigmasupport.A_D.String()) + // klog.V(2).Infof("A_b %s\n", sigmasupport.A_b.String()) + // klog.V(2).Infof("A_X %s\n", sigmasupport.A_X.String()) + // klog.V(2).Infof("A_t %s\n", sigmasupport.A_t.String()) + // klog.V(2).Infof("A_u %s\n", sigmasupport.A_u.String()) + + { + var input []byte + input = append(input, ConvertBigIntToByte(x)...) + input = append(input, sigmasupport.A_y.Marshal()...) + input = append(input, sigmasupport.A_D.Marshal()...) + input = append(input, sigmasupport.A_b.Marshal()...) + input = append(input, sigmasupport.A_X.Marshal()...) + input = append(input, sigmasupport.A_t.Marshal()...) + input = append(input, sigmasupport.A_u.Marshal()...) + // fmt.Printf("C calculation expected %s actual %s\n",proof.c.Text(16), reducedhash(input).Text(16) ) + + if reducedhash(input).Text(16) != proof.c.Text(16) { // we must fail here + + // klog.Warning("C calculation failed") + return false + } + } + + o := reducedhash(ConvertBigIntToByte(proof.c)) + + u_x := new(bn256.G1).ScalarMult(gparams.H, o) + + var hPrimes []*bn256.G1 + hPrimeSum := new(bn256.G1) + + hPrimeSum.Unmarshal(zeroes[:]) + for i := 0; i < 128; i++ { + hPrimes = append(hPrimes, new(bn256.G1).ScalarMult(gparams.Hs.vector[i], new(big.Int).ModInverse(protsupport.ys[i], bn256.Order))) + + // klog.V(2).Infof("hPrimes %d %s\n", i, hPrimes[i].String()) + + tmp := new(big.Int).Mod(new(big.Int).Mul(protsupport.ys[i], protsupport.z), bn256.Order) + tmp = new(big.Int).Mod(new(big.Int).Add(tmp, protsupport.twoTimesZSquared[i]), bn256.Order) + + hPrimeSum = new(bn256.G1).Add(hPrimeSum, new(bn256.G1).ScalarMult(hPrimes[i], tmp)) + + } + + P := new(bn256.G1).Add(proof.BA, new(bn256.G1).ScalarMult(proof.BS, x)) + P = new(bn256.G1).Add(P, new(bn256.G1).ScalarMult(gparams.GSUM, new(big.Int).Mod(new(big.Int).Neg(protsupport.z), bn256.Order))) + P = new(bn256.G1).Add(P, hPrimeSum) + + P = new(bn256.G1).Add(P, new(bn256.G1).ScalarMult(gparams.H, new(big.Int).Mod(new(big.Int).Neg(proof.mu), bn256.Order))) + P = new(bn256.G1).Add(P, new(bn256.G1).ScalarMult(u_x, new(big.Int).Mod(new(big.Int).Set(proof.that), bn256.Order))) + + // klog.V(2).Infof("P %s\n", P.String()) + + if !proof.ip.Verify(hPrimes, u_x, P, o, gparams) { + // klog.Warning("inner proof failed") + return false + } + + // klog.V(2).Infof("proof %s\n", proof.String()) + // panic("proof successful") + + // klog.V(2).Infof("Proof successful verified\n") + + return true + +} + +/* +func (proof *Proof) String() string { + klog.V(1).Infof("proof BA %s\n", proof.BA.String()) + klog.V(1).Infof("proof BS %s\n", proof.BS.String()) + klog.V(1).Infof("proof A %s\n", proof.A.String()) + klog.V(1).Infof("proof B %s\n", proof.B.String()) + + for i := range proof.CLnG { + klog.V(1).Infof("CLnG %d %s \n", i, proof.CLnG[i].String()) + } + for i := range proof.CRnG { + klog.V(1).Infof("CRnG %d %s \n", i, proof.CRnG[i].String()) + } + + for i := range proof.C_0G { + klog.V(1).Infof("C_0G %d %s \n", i, proof.C_0G[i].String()) + } + for i := range proof.DG { + klog.V(1).Infof("DG %d %s \n", i, proof.DG[i].String()) + } + for i := range proof.y_0G { + klog.V(1).Infof("y_0G %d %s \n", i, proof.y_0G[i].String()) + } + for i := range proof.gG { + klog.V(1).Infof("gG %d %s \n", i, proof.gG[i].String()) + } + + for i := range proof.C_XG { + klog.V(1).Infof("C_XG %d %s \n", i, proof.C_XG[i].String()) + } + for i := range proof.y_XG { + klog.V(1).Infof("y_XG %d %s \n", i, proof.y_XG[i].String()) + } + + //for i := range proof.tCommits.vector { + // klog.V(1).Infof("tCommits %d %s \n", i, proof.tCommits.vector[i].String()) + //} + + klog.V(1).Infof("proof z_A %s\n", proof.z_A.Text(16)) + klog.V(1).Infof("proof that %s\n", proof.that.Text(16)) + klog.V(1).Infof("proof mu %s\n", proof.mu.Text(16)) + klog.V(1).Infof("proof C %s\n", proof.c.Text(16)) + klog.V(1).Infof("proof s_sk %s\n", proof.s_sk.Text(16)) + klog.V(1).Infof("proof s_r %s\n", proof.s_r.Text(16)) + klog.V(1).Infof("proof s_b %s\n", proof.s_b.Text(16)) + klog.V(1).Infof("proof s_tau %s\n", proof.s_tau.Text(16)) + + return "" + +} +*/ +func assemblepolynomials(f [][2]*big.Int) [][2]*big.Int { + m := len(f) / 2 + N := int(math.Pow(2, float64(m))) + result := make([][2]*big.Int, N, N) + + for i := 0; i < 2; i++ { + half := recursivepolynomials(i*m, (i+1)*m, new(big.Int).SetInt64(1), f) + for j := 0; j < N; j++ { + result[j][i] = half[j] + } + } + return result +} + +func recursivepolynomials(baseline, current int, accum *big.Int, f [][2]*big.Int) []*big.Int { + size := int(math.Pow(2, float64(current-baseline))) + + result := make([]*big.Int, size, size) + if current == baseline { + result[0] = accum + return result + } + current-- + + left := recursivepolynomials(baseline, current, new(big.Int).Mod(new(big.Int).Mul(accum, f[current][0]), bn256.Order), f) + right := recursivepolynomials(baseline, current, new(big.Int).Mod(new(big.Int).Mul(accum, f[current][1]), bn256.Order), f) + for i := 0; i < size/2; i++ { + result[i] = left[i] + result[i+size/2] = right[i] + } + + return result +} diff --git a/cryptography/crypto/protocol_structures.go b/cryptography/crypto/protocol_structures.go new file mode 100644 index 0000000..ecfa123 --- /dev/null +++ b/cryptography/crypto/protocol_structures.go @@ -0,0 +1,225 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "bytes" +import "encoding/binary" +import "math/big" +import "github.com/deroproject/derohe/cryptography/bn256" +import "github.com/deroproject/graviton" + +type Statement struct { + RingSize uint64 + CLn []*bn256.G1 + CRn []*bn256.G1 + Bytes_per_publickey byte // number of bytes need per public key, it will be from 1 to 32 bytes long, but will rarely be more than 4 byte + Publickeylist_pointers []byte // all the public keys are hashed and there necessary bits taken from the start to reach graviton leaf + Publickeylist []*bn256.G1 // Todo these can be skipped and collected back later on from the chain, this will save ringsize * POINTSIZE bytes + Publickeylist_compressed [][33]byte // compressed format for public keys NOTE: only valid in deserialized transactions + C []*bn256.G1 // commitments + D *bn256.G1 + Fees uint64 + + Roothash [32]byte // note roothash contains the merkle root hash of chain, when it was build +} + +type Witness struct { + SecretKey *big.Int + R *big.Int + TransferAmount uint64 // total value being transferred + Balance uint64 // whatever is the the amount left after transfer + Index []int // index of sender in the public key list + +} + +func (s *Statement) Serialize(w *bytes.Buffer) { + buf := make([]byte, binary.MaxVarintLen64) + //n := binary.PutUvarint(buf, uint64(len(s.Publickeylist))) + //w.Write(buf[:n]) + + if len(s.Publickeylist_pointers) == 0 { + power := byte(GetPowerof2(len(s.Publickeylist))) // len(s.Publickeylist) is always power of 2 + w.WriteByte(power) + w.WriteByte(s.Bytes_per_publickey) + + n := binary.PutUvarint(buf, s.Fees) + w.Write(buf[:n]) + + w.Write(s.D.EncodeCompressed()) + s.Publickeylist_pointers = s.Publickeylist_pointers[:0] + for i := 0; i < len(s.Publickeylist); i++ { + hashed_key := graviton.Sum(s.Publickeylist[i].EncodeCompressed()) + w.Write(hashed_key[:s.Bytes_per_publickey]) + s.Publickeylist_pointers = append(s.Publickeylist_pointers, hashed_key[:s.Bytes_per_publickey]...) + } + } else { + power := byte(GetPowerof2(len(s.Publickeylist_pointers) / int(s.Bytes_per_publickey))) // len(s.Publickeylist) is always power of 2 + w.WriteByte(power) + w.WriteByte(s.Bytes_per_publickey) + n := binary.PutUvarint(buf, s.Fees) + w.Write(buf[:n]) + w.Write(s.D.EncodeCompressed()) + w.Write(s.Publickeylist_pointers[:]) + } + + for i := 0; i < len(s.Publickeylist_pointers)/int(s.Bytes_per_publickey); i++ { + // w.Write( s.CLn[i].EncodeCompressed()) /// this is expanded from graviton store + // w.Write( s.CRn[i].EncodeCompressed()) /// this is expanded from graviton store + //w.Write(s.Publickeylist[i].EncodeCompressed()) /// this is expanded from graviton store + w.Write(s.C[i].EncodeCompressed()) + } + + w.Write(s.Roothash[:]) + +} + +func (s *Statement) Deserialize(r *bytes.Reader) error { + + var err error + //var buf [32]byte + var bufp [33]byte + + length, err := r.ReadByte() + if err != nil { + return err + } + s.RingSize = 1 << length + + s.Bytes_per_publickey, err = r.ReadByte() + if err != nil { + return err + } + + s.Fees, err = binary.ReadUvarint(r) + if err != nil { + return err + } + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + s.D = &p + } else { + return err + } + + s.CLn = s.CLn[:0] + s.CRn = s.CRn[:0] + s.Publickeylist = s.Publickeylist[:0] + s.Publickeylist_compressed = s.Publickeylist_compressed[:0] + s.Publickeylist_pointers = s.Publickeylist_pointers[:0] + s.C = s.C[:0] + + s.Publickeylist_pointers = make([]byte, s.RingSize*uint64(s.Bytes_per_publickey), s.RingSize*uint64(s.Bytes_per_publickey)) + + // read all compressed pointers in 1 go + if n, err := r.Read(s.Publickeylist_pointers); n == int(s.RingSize*uint64(s.Bytes_per_publickey)) && err == nil { + + } else { + return err + } + + for i := uint64(0); i < s.RingSize; i++ { + + /* + if n,err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + s.CLn = append(s.CLn,&p) + }else{ + return err + } + + if n,err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + s.CRn = append(s.CRn,&p) + }else{ + return err + } + */ + + /* + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + var pcopy [33]byte + copy(pcopy[:], bufp[:]) + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + s.Publickeylist_compressed = append(s.Publickeylist_compressed, pcopy) + s.Publickeylist = append(s.Publickeylist, &p) + } else { + return err + } + */ + + if n, err := r.Read(bufp[:]); n == 33 && err == nil { + var p bn256.G1 + if err = p.DecodeCompressed(bufp[:]); err != nil { + return err + } + s.C = append(s.C, &p) + } else { + return err + } + + } + + if n, err := r.Read(s.Roothash[:]); n == 32 && err == nil { + + } else { + return err + } + + return nil + +} + +/* +type Proof struct { + BA *bn256.G1 + BS *bn256.G1 + A *bn256.G1 + B *bn256.G1 + + CLnG, CRnG, C_0G, DG, y_0G, gG, C_XG, y_XG []*bn256.G1 + + u *bn256.G1 + + f *FieldVector + + z_A *big.Int + + T_1 *bn256.G1 + T_2 *bn256.G1 + + that *big.Int + mu *big.Int + + c *big.Int + s_sk, s_r, s_b, s_tau *big.Int + + //ip *InnerProduct +} +*/ diff --git a/cryptography/crypto/random.go b/cryptography/crypto/random.go new file mode 100644 index 0000000..aea2957 --- /dev/null +++ b/cryptography/crypto/random.go @@ -0,0 +1,44 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "math/big" +import "crypto/rand" +import "github.com/deroproject/derohe/cryptography/bn256" + +func RandomScalar() *big.Int { + + for { + a, _ := rand.Int(rand.Reader, bn256.Order) + if a.Sign() > 0 { + return a + } + + } +} + +// this will return fixed random scalar +func RandomScalarFixed() *big.Int { + //return new(big.Int).Set(fixed) + + return RandomScalar() +} + +type KeyPair struct { + x *big.Int // secret key + y *bn256.G1 // public key +} diff --git a/cryptography/crypto/userdata.go b/cryptography/crypto/userdata.go new file mode 100644 index 0000000..03ee80f --- /dev/null +++ b/cryptography/crypto/userdata.go @@ -0,0 +1,43 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package crypto + +import "golang.org/x/crypto/chacha20" +import "github.com/deroproject/derohe/cryptography/bn256" + +// this function is used to encrypt/decrypt payment id,srcid and other userdata +// as the operation is symmetric XOR, is the same in both direction +// +func EncryptDecryptUserData(blinder *bn256.G1, inputs ...[]byte) { + blinder_compressed := blinder.EncodeCompressed() + if len(blinder_compressed) != 33 { + panic("point compression needs to be fixed") + } + + key := Keccak256(blinder_compressed[:]) + var nonce [24]byte // nonce is 24 bytes, we will use xchacha20 + + cipher, err := chacha20.NewUnauthenticatedCipher(key[:], nonce[:]) + if err != nil { + panic(err) + } + + for _, input := range inputs { + cipher.XORKeyStream(input, input) + } + return +} diff --git a/cryptography/sha3/doc.go b/cryptography/sha3/doc.go new file mode 100644 index 0000000..c06a330 --- /dev/null +++ b/cryptography/sha3/doc.go @@ -0,0 +1,66 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA-3 fixed-output-length hash functions and +// the SHAKE variable-output-length hash functions defined by FIPS-202. +// +// Both types of hash function use the "sponge" construction and the Keccak +// permutation. For a detailed specification see http://keccak.noekeon.org/ +// +// +// Guidance +// +// If you aren't sure what function you need, use SHAKE256 with at least 64 +// bytes of output. The SHAKE instances are faster than the SHA3 instances; +// the latter have to allocate memory to conform to the hash.Hash interface. +// +// If you need a secret-key MAC (message authentication code), prepend the +// secret key to the input, hash with SHAKE256 and read at least 32 bytes of +// output. +// +// +// Security strengths +// +// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security +// strength against preimage attacks of x bits. Since they only produce "x" +// bits of output, their collision-resistance is only "x/2" bits. +// +// The SHAKE-256 and -128 functions have a generic security strength of 256 and +// 128 bits against all attacks, provided that at least 2x bits of their output +// is used. Requesting more than 64 or 32 bytes of output, respectively, does +// not increase the collision-resistance of the SHAKE functions. +// +// +// The sponge construction +// +// A sponge builds a pseudo-random function from a public pseudo-random +// permutation, by applying the permutation to a state of "rate + capacity" +// bytes, but hiding "capacity" of the bytes. +// +// A sponge starts out with a zero state. To hash an input using a sponge, up +// to "rate" bytes of the input are XORed into the sponge's state. The sponge +// is then "full" and the permutation is applied to "empty" it. This process is +// repeated until all the input has been "absorbed". The input is then padded. +// The digest is "squeezed" from the sponge in the same way, except that output +// is copied out instead of input being XORed in. +// +// A sponge is parameterized by its generic security strength, which is equal +// to half its capacity; capacity + rate is equal to the permutation's width. +// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means +// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. +// +// +// Recommendations +// +// The SHAKE functions are recommended for most new uses. They can produce +// output of arbitrary length. SHAKE256, with an output length of at least +// 64 bytes, provides 256-bit security against all attacks. The Keccak team +// recommends it for most applications upgrading from SHA2-512. (NIST chose a +// much stronger, but much slower, sponge instance for SHA3-512.) +// +// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions. +// They produce output of the same length, with the same security strengths +// against all attacks. This means, in particular, that SHA3-256 only has +// 128-bit collision resistance, because its output length is 32 bytes. +package sha3 diff --git a/cryptography/sha3/hashes.go b/cryptography/sha3/hashes.go new file mode 100644 index 0000000..0d8043f --- /dev/null +++ b/cryptography/sha3/hashes.go @@ -0,0 +1,97 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This file provides functions for creating instances of the SHA-3 +// and SHAKE hash functions, as well as utility functions for hashing +// bytes. + +import ( + "hash" +) + +// New224 creates a new SHA3-224 hash. +// Its generic security strength is 224 bits against preimage attacks, +// and 112 bits against collision attacks. +func New224() hash.Hash { + if h := new224Asm(); h != nil { + return h + } + return &state{rate: 144, outputLen: 28, dsbyte: 0x06} +} + +// New256 creates a new SHA3-256 hash. +// Its generic security strength is 256 bits against preimage attacks, +// and 128 bits against collision attacks. +func New256() hash.Hash { + if h := new256Asm(); h != nil { + return h + } + return &state{rate: 136, outputLen: 32, dsbyte: 0x06} +} + +// New384 creates a new SHA3-384 hash. +// Its generic security strength is 384 bits against preimage attacks, +// and 192 bits against collision attacks. +func New384() hash.Hash { + if h := new384Asm(); h != nil { + return h + } + return &state{rate: 104, outputLen: 48, dsbyte: 0x06} +} + +// New512 creates a new SHA3-512 hash. +// Its generic security strength is 512 bits against preimage attacks, +// and 256 bits against collision attacks. +func New512() hash.Hash { + if h := new512Asm(); h != nil { + return h + } + return &state{rate: 72, outputLen: 64, dsbyte: 0x06} +} + +// NewLegacyKeccak256 creates a new Keccak-256 hash. +// +// Only use this function if you require compatibility with an existing cryptosystem +// that uses non-standard padding. All other users should use New256 instead. +func NewLegacyKeccak256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x01} } + +// NewLegacyKeccak512 creates a new Keccak-512 hash. +// +// Only use this function if you require compatibility with an existing cryptosystem +// that uses non-standard padding. All other users should use New512 instead. +func NewLegacyKeccak512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x01} } + +// Sum224 returns the SHA3-224 digest of the data. +func Sum224(data []byte) (digest [28]byte) { + h := New224() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum256 returns the SHA3-256 digest of the data. +func Sum256(data []byte) (digest [32]byte) { + h := New256() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum384 returns the SHA3-384 digest of the data. +func Sum384(data []byte) (digest [48]byte) { + h := New384() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum512 returns the SHA3-512 digest of the data. +func Sum512(data []byte) (digest [64]byte) { + h := New512() + h.Write(data) + h.Sum(digest[:0]) + return +} diff --git a/cryptography/sha3/hashes_generic.go b/cryptography/sha3/hashes_generic.go new file mode 100644 index 0000000..f455147 --- /dev/null +++ b/cryptography/sha3/hashes_generic.go @@ -0,0 +1,27 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build gccgo appengine !s390x + +package sha3 + +import ( + "hash" +) + +// new224Asm returns an assembly implementation of SHA3-224 if available, +// otherwise it returns nil. +func new224Asm() hash.Hash { return nil } + +// new256Asm returns an assembly implementation of SHA3-256 if available, +// otherwise it returns nil. +func new256Asm() hash.Hash { return nil } + +// new384Asm returns an assembly implementation of SHA3-384 if available, +// otherwise it returns nil. +func new384Asm() hash.Hash { return nil } + +// new512Asm returns an assembly implementation of SHA3-512 if available, +// otherwise it returns nil. +func new512Asm() hash.Hash { return nil } diff --git a/cryptography/sha3/keccakf.go b/cryptography/sha3/keccakf.go new file mode 100644 index 0000000..46d03ed --- /dev/null +++ b/cryptography/sha3/keccakf.go @@ -0,0 +1,412 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !amd64 appengine gccgo + +package sha3 + +// rc stores the round constants for use in the ι step. +var rc = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// keccakF1600 applies the Keccak permutation to a 1600b-wide +// state represented as a slice of 25 uint64s. +func keccakF1600(a *[25]uint64) { + // Implementation translated from Keccak-inplace.c + // in the keccak reference code. + var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 + + for i := 0; i < 24; i += 4 { + // Combines the 5 steps in each round into 2 steps. + // Unrolls 4 rounds per loop and spreads some steps across rounds. + + // Round 1 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[6] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[12] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[18] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[24] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] + a[6] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[16] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[22] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[3] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[10] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[1] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[7] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[19] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[20] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[11] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[23] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[4] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[5] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[2] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[8] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[14] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[15] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + // Round 2 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[16] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[7] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[23] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[14] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] + a[16] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[11] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[2] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[18] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[20] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[6] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[22] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[4] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[15] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[1] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[8] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[24] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[10] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[12] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[3] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[19] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[5] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + // Round 3 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[11] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[22] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[8] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[19] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] + a[11] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[1] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[12] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[23] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[15] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[16] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[2] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[24] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[5] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[6] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[3] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[14] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[20] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[7] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[18] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[4] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[10] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + // Round 4 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[1] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[2] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[3] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[4] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] + a[1] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[6] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[7] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[8] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[5] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[11] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[12] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[14] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[10] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[16] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[18] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[19] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[15] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[22] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[23] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[24] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[20] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + } +} diff --git a/vendor/golang.org/x/tools/godoc/appengine.go b/cryptography/sha3/keccakf_amd64.go similarity index 52% rename from vendor/golang.org/x/tools/godoc/appengine.go rename to cryptography/sha3/keccakf_amd64.go index 2a68558..7886795 100644 --- a/vendor/golang.org/x/tools/godoc/appengine.go +++ b/cryptography/sha3/keccakf_amd64.go @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build appengine +// +build amd64,!appengine,!gccgo -package godoc +package sha3 -import "appengine" +// This function is implemented in keccakf_amd64.s. -func init() { - onAppengine = !appengine.IsDevAppServer() -} +//go:noescape + +func keccakF1600(a *[25]uint64) diff --git a/cryptography/sha3/keccakf_amd64.s b/cryptography/sha3/keccakf_amd64.s new file mode 100644 index 0000000..f88533a --- /dev/null +++ b/cryptography/sha3/keccakf_amd64.s @@ -0,0 +1,390 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!appengine,!gccgo + +// This code was translated into a form compatible with 6a from the public +// domain sources at https://github.com/gvanas/KeccakCodePackage + +// Offsets in state +#define _ba (0*8) +#define _be (1*8) +#define _bi (2*8) +#define _bo (3*8) +#define _bu (4*8) +#define _ga (5*8) +#define _ge (6*8) +#define _gi (7*8) +#define _go (8*8) +#define _gu (9*8) +#define _ka (10*8) +#define _ke (11*8) +#define _ki (12*8) +#define _ko (13*8) +#define _ku (14*8) +#define _ma (15*8) +#define _me (16*8) +#define _mi (17*8) +#define _mo (18*8) +#define _mu (19*8) +#define _sa (20*8) +#define _se (21*8) +#define _si (22*8) +#define _so (23*8) +#define _su (24*8) + +// Temporary registers +#define rT1 AX + +// Round vars +#define rpState DI +#define rpStack SP + +#define rDa BX +#define rDe CX +#define rDi DX +#define rDo R8 +#define rDu R9 + +#define rBa R10 +#define rBe R11 +#define rBi R12 +#define rBo R13 +#define rBu R14 + +#define rCa SI +#define rCe BP +#define rCi rBi +#define rCo rBo +#define rCu R15 + +#define MOVQ_RBI_RCE MOVQ rBi, rCe +#define XORQ_RT1_RCA XORQ rT1, rCa +#define XORQ_RT1_RCE XORQ rT1, rCe +#define XORQ_RBA_RCU XORQ rBa, rCu +#define XORQ_RBE_RCU XORQ rBe, rCu +#define XORQ_RDU_RCU XORQ rDu, rCu +#define XORQ_RDA_RCA XORQ rDa, rCa +#define XORQ_RDE_RCE XORQ rDe, rCe + +#define mKeccakRound(iState, oState, rc, B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, S_RDE_RCE) \ + /* Prepare round */ \ + MOVQ rCe, rDa; \ + ROLQ $1, rDa; \ + \ + MOVQ _bi(iState), rCi; \ + XORQ _gi(iState), rDi; \ + XORQ rCu, rDa; \ + XORQ _ki(iState), rCi; \ + XORQ _mi(iState), rDi; \ + XORQ rDi, rCi; \ + \ + MOVQ rCi, rDe; \ + ROLQ $1, rDe; \ + \ + MOVQ _bo(iState), rCo; \ + XORQ _go(iState), rDo; \ + XORQ rCa, rDe; \ + XORQ _ko(iState), rCo; \ + XORQ _mo(iState), rDo; \ + XORQ rDo, rCo; \ + \ + MOVQ rCo, rDi; \ + ROLQ $1, rDi; \ + \ + MOVQ rCu, rDo; \ + XORQ rCe, rDi; \ + ROLQ $1, rDo; \ + \ + MOVQ rCa, rDu; \ + XORQ rCi, rDo; \ + ROLQ $1, rDu; \ + \ + /* Result b */ \ + MOVQ _ba(iState), rBa; \ + MOVQ _ge(iState), rBe; \ + XORQ rCo, rDu; \ + MOVQ _ki(iState), rBi; \ + MOVQ _mo(iState), rBo; \ + MOVQ _su(iState), rBu; \ + XORQ rDe, rBe; \ + ROLQ $44, rBe; \ + XORQ rDi, rBi; \ + XORQ rDa, rBa; \ + ROLQ $43, rBi; \ + \ + MOVQ rBe, rCa; \ + MOVQ rc, rT1; \ + ORQ rBi, rCa; \ + XORQ rBa, rT1; \ + XORQ rT1, rCa; \ + MOVQ rCa, _ba(oState); \ + \ + XORQ rDu, rBu; \ + ROLQ $14, rBu; \ + MOVQ rBa, rCu; \ + ANDQ rBe, rCu; \ + XORQ rBu, rCu; \ + MOVQ rCu, _bu(oState); \ + \ + XORQ rDo, rBo; \ + ROLQ $21, rBo; \ + MOVQ rBo, rT1; \ + ANDQ rBu, rT1; \ + XORQ rBi, rT1; \ + MOVQ rT1, _bi(oState); \ + \ + NOTQ rBi; \ + ORQ rBa, rBu; \ + ORQ rBo, rBi; \ + XORQ rBo, rBu; \ + XORQ rBe, rBi; \ + MOVQ rBu, _bo(oState); \ + MOVQ rBi, _be(oState); \ + B_RBI_RCE; \ + \ + /* Result g */ \ + MOVQ _gu(iState), rBe; \ + XORQ rDu, rBe; \ + MOVQ _ka(iState), rBi; \ + ROLQ $20, rBe; \ + XORQ rDa, rBi; \ + ROLQ $3, rBi; \ + MOVQ _bo(iState), rBa; \ + MOVQ rBe, rT1; \ + ORQ rBi, rT1; \ + XORQ rDo, rBa; \ + MOVQ _me(iState), rBo; \ + MOVQ _si(iState), rBu; \ + ROLQ $28, rBa; \ + XORQ rBa, rT1; \ + MOVQ rT1, _ga(oState); \ + G_RT1_RCA; \ + \ + XORQ rDe, rBo; \ + ROLQ $45, rBo; \ + MOVQ rBi, rT1; \ + ANDQ rBo, rT1; \ + XORQ rBe, rT1; \ + MOVQ rT1, _ge(oState); \ + G_RT1_RCE; \ + \ + XORQ rDi, rBu; \ + ROLQ $61, rBu; \ + MOVQ rBu, rT1; \ + ORQ rBa, rT1; \ + XORQ rBo, rT1; \ + MOVQ rT1, _go(oState); \ + \ + ANDQ rBe, rBa; \ + XORQ rBu, rBa; \ + MOVQ rBa, _gu(oState); \ + NOTQ rBu; \ + G_RBA_RCU; \ + \ + ORQ rBu, rBo; \ + XORQ rBi, rBo; \ + MOVQ rBo, _gi(oState); \ + \ + /* Result k */ \ + MOVQ _be(iState), rBa; \ + MOVQ _gi(iState), rBe; \ + MOVQ _ko(iState), rBi; \ + MOVQ _mu(iState), rBo; \ + MOVQ _sa(iState), rBu; \ + XORQ rDi, rBe; \ + ROLQ $6, rBe; \ + XORQ rDo, rBi; \ + ROLQ $25, rBi; \ + MOVQ rBe, rT1; \ + ORQ rBi, rT1; \ + XORQ rDe, rBa; \ + ROLQ $1, rBa; \ + XORQ rBa, rT1; \ + MOVQ rT1, _ka(oState); \ + K_RT1_RCA; \ + \ + XORQ rDu, rBo; \ + ROLQ $8, rBo; \ + MOVQ rBi, rT1; \ + ANDQ rBo, rT1; \ + XORQ rBe, rT1; \ + MOVQ rT1, _ke(oState); \ + K_RT1_RCE; \ + \ + XORQ rDa, rBu; \ + ROLQ $18, rBu; \ + NOTQ rBo; \ + MOVQ rBo, rT1; \ + ANDQ rBu, rT1; \ + XORQ rBi, rT1; \ + MOVQ rT1, _ki(oState); \ + \ + MOVQ rBu, rT1; \ + ORQ rBa, rT1; \ + XORQ rBo, rT1; \ + MOVQ rT1, _ko(oState); \ + \ + ANDQ rBe, rBa; \ + XORQ rBu, rBa; \ + MOVQ rBa, _ku(oState); \ + K_RBA_RCU; \ + \ + /* Result m */ \ + MOVQ _ga(iState), rBe; \ + XORQ rDa, rBe; \ + MOVQ _ke(iState), rBi; \ + ROLQ $36, rBe; \ + XORQ rDe, rBi; \ + MOVQ _bu(iState), rBa; \ + ROLQ $10, rBi; \ + MOVQ rBe, rT1; \ + MOVQ _mi(iState), rBo; \ + ANDQ rBi, rT1; \ + XORQ rDu, rBa; \ + MOVQ _so(iState), rBu; \ + ROLQ $27, rBa; \ + XORQ rBa, rT1; \ + MOVQ rT1, _ma(oState); \ + M_RT1_RCA; \ + \ + XORQ rDi, rBo; \ + ROLQ $15, rBo; \ + MOVQ rBi, rT1; \ + ORQ rBo, rT1; \ + XORQ rBe, rT1; \ + MOVQ rT1, _me(oState); \ + M_RT1_RCE; \ + \ + XORQ rDo, rBu; \ + ROLQ $56, rBu; \ + NOTQ rBo; \ + MOVQ rBo, rT1; \ + ORQ rBu, rT1; \ + XORQ rBi, rT1; \ + MOVQ rT1, _mi(oState); \ + \ + ORQ rBa, rBe; \ + XORQ rBu, rBe; \ + MOVQ rBe, _mu(oState); \ + \ + ANDQ rBa, rBu; \ + XORQ rBo, rBu; \ + MOVQ rBu, _mo(oState); \ + M_RBE_RCU; \ + \ + /* Result s */ \ + MOVQ _bi(iState), rBa; \ + MOVQ _go(iState), rBe; \ + MOVQ _ku(iState), rBi; \ + XORQ rDi, rBa; \ + MOVQ _ma(iState), rBo; \ + ROLQ $62, rBa; \ + XORQ rDo, rBe; \ + MOVQ _se(iState), rBu; \ + ROLQ $55, rBe; \ + \ + XORQ rDu, rBi; \ + MOVQ rBa, rDu; \ + XORQ rDe, rBu; \ + ROLQ $2, rBu; \ + ANDQ rBe, rDu; \ + XORQ rBu, rDu; \ + MOVQ rDu, _su(oState); \ + \ + ROLQ $39, rBi; \ + S_RDU_RCU; \ + NOTQ rBe; \ + XORQ rDa, rBo; \ + MOVQ rBe, rDa; \ + ANDQ rBi, rDa; \ + XORQ rBa, rDa; \ + MOVQ rDa, _sa(oState); \ + S_RDA_RCA; \ + \ + ROLQ $41, rBo; \ + MOVQ rBi, rDe; \ + ORQ rBo, rDe; \ + XORQ rBe, rDe; \ + MOVQ rDe, _se(oState); \ + S_RDE_RCE; \ + \ + MOVQ rBo, rDi; \ + MOVQ rBu, rDo; \ + ANDQ rBu, rDi; \ + ORQ rBa, rDo; \ + XORQ rBi, rDi; \ + XORQ rBo, rDo; \ + MOVQ rDi, _si(oState); \ + MOVQ rDo, _so(oState) \ + +// func keccakF1600(state *[25]uint64) +TEXT ·keccakF1600(SB), 0, $200-8 + MOVQ state+0(FP), rpState + + // Convert the user state into an internal state + NOTQ _be(rpState) + NOTQ _bi(rpState) + NOTQ _go(rpState) + NOTQ _ki(rpState) + NOTQ _mi(rpState) + NOTQ _sa(rpState) + + // Execute the KeccakF permutation + MOVQ _ba(rpState), rCa + MOVQ _be(rpState), rCe + MOVQ _bu(rpState), rCu + + XORQ _ga(rpState), rCa + XORQ _ge(rpState), rCe + XORQ _gu(rpState), rCu + + XORQ _ka(rpState), rCa + XORQ _ke(rpState), rCe + XORQ _ku(rpState), rCu + + XORQ _ma(rpState), rCa + XORQ _me(rpState), rCe + XORQ _mu(rpState), rCu + + XORQ _sa(rpState), rCa + XORQ _se(rpState), rCe + MOVQ _si(rpState), rDi + MOVQ _so(rpState), rDo + XORQ _su(rpState), rCu + + mKeccakRound(rpState, rpStack, $0x0000000000000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x0000000000008082, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x800000000000808a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000080008000, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x000000000000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000000008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x000000000000008a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x0000000000000088, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x0000000080008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x000000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x000000008000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x800000000000008b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x8000000000008089, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000000008003, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x8000000000008002, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000000000080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x000000000000800a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x800000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000000008080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpState, rpStack, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + mKeccakRound(rpStack, rpState, $0x8000000080008008, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP) + + // Revert the internal state to the user state + NOTQ _be(rpState) + NOTQ _bi(rpState) + NOTQ _go(rpState) + NOTQ _ki(rpState) + NOTQ _mi(rpState) + NOTQ _sa(rpState) + + RET diff --git a/cryptography/sha3/register.go b/cryptography/sha3/register.go new file mode 100644 index 0000000..3cf6a22 --- /dev/null +++ b/cryptography/sha3/register.go @@ -0,0 +1,18 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.4 + +package sha3 + +import ( + "crypto" +) + +func init() { + crypto.RegisterHash(crypto.SHA3_224, New224) + crypto.RegisterHash(crypto.SHA3_256, New256) + crypto.RegisterHash(crypto.SHA3_384, New384) + crypto.RegisterHash(crypto.SHA3_512, New512) +} diff --git a/cryptography/sha3/sha3.go b/cryptography/sha3/sha3.go new file mode 100644 index 0000000..ba269a0 --- /dev/null +++ b/cryptography/sha3/sha3.go @@ -0,0 +1,193 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// spongeDirection indicates the direction bytes are flowing through the sponge. +type spongeDirection int + +const ( + // spongeAbsorbing indicates that the sponge is absorbing input. + spongeAbsorbing spongeDirection = iota + // spongeSqueezing indicates that the sponge is being squeezed. + spongeSqueezing +) + +const ( + // maxRate is the maximum size of the internal buffer. SHAKE-256 + // currently needs the largest buffer. + maxRate = 168 +) + +type state struct { + // Generic sponge components. + a [25]uint64 // main state of the hash + buf []byte // points into storage + rate int // the number of bytes of state to use + + // dsbyte contains the "domain separation" bits and the first bit of + // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the + // SHA-3 and SHAKE functions by appending bitstrings to the message. + // Using a little-endian bit-ordering convention, these are "01" for SHA-3 + // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the + // padding rule from section 5.1 is applied to pad the message to a multiple + // of the rate, which involves adding a "1" bit, zero or more "0" bits, and + // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, + // giving 00000110b (0x06) and 00011111b (0x1f). + // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf + // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and + // Extendable-Output Functions (May 2014)" + dsbyte byte + + storage storageBuf + + // Specific to SHA-3 and SHAKE. + outputLen int // the default output size in bytes + state spongeDirection // whether the sponge is absorbing or squeezing +} + +// BlockSize returns the rate of sponge underlying this hash function. +func (d *state) BlockSize() int { return d.rate } + +// Size returns the output size of the hash function in bytes. +func (d *state) Size() int { return d.outputLen } + +// Reset clears the internal state by zeroing the sponge state and +// the byte buffer, and setting Sponge.state to absorbing. +func (d *state) Reset() { + // Zero the permutation's state. + for i := range d.a { + d.a[i] = 0 + } + d.state = spongeAbsorbing + d.buf = d.storage.asBytes()[:0] +} + +func (d *state) clone() *state { + ret := *d + if ret.state == spongeAbsorbing { + ret.buf = ret.storage.asBytes()[:len(ret.buf)] + } else { + ret.buf = ret.storage.asBytes()[d.rate-cap(d.buf) : d.rate] + } + + return &ret +} + +// permute applies the KeccakF-1600 permutation. It handles +// any input-output buffering. +func (d *state) permute() { + switch d.state { + case spongeAbsorbing: + // If we're absorbing, we need to xor the input into the state + // before applying the permutation. + xorIn(d, d.buf) + d.buf = d.storage.asBytes()[:0] + keccakF1600(&d.a) + case spongeSqueezing: + // If we're squeezing, we need to apply the permutatin before + // copying more output. + keccakF1600(&d.a) + d.buf = d.storage.asBytes()[:d.rate] + copyOut(d, d.buf) + } +} + +// pads appends the domain separation bits in dsbyte, applies +// the multi-bitrate 10..1 padding rule, and permutes the state. +func (d *state) padAndPermute(dsbyte byte) { + if d.buf == nil { + d.buf = d.storage.asBytes()[:0] + } + // Pad with this instance's domain-separator bits. We know that there's + // at least one byte of space in d.buf because, if it were full, + // permute would have been called to empty it. dsbyte also contains the + // first one bit for the padding. See the comment in the state struct. + d.buf = append(d.buf, dsbyte) + zerosStart := len(d.buf) + d.buf = d.storage.asBytes()[:d.rate] + for i := zerosStart; i < d.rate; i++ { + d.buf[i] = 0 + } + // This adds the final one bit for the padding. Because of the way that + // bits are numbered from the LSB upwards, the final bit is the MSB of + // the last byte. + d.buf[d.rate-1] ^= 0x80 + // Apply the permutation + d.permute() + d.state = spongeSqueezing + d.buf = d.storage.asBytes()[:d.rate] + copyOut(d, d.buf) +} + +// Write absorbs more data into the hash's state. It produces an error +// if more data is written to the ShakeHash after writing +func (d *state) Write(p []byte) (written int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: write to sponge after read") + } + if d.buf == nil { + d.buf = d.storage.asBytes()[:0] + } + written = len(p) + + for len(p) > 0 { + if len(d.buf) == 0 && len(p) >= d.rate { + // The fast path; absorb a full "rate" bytes of input and apply the permutation. + xorIn(d, p[:d.rate]) + p = p[d.rate:] + keccakF1600(&d.a) + } else { + // The slow path; buffer the input until we can fill the sponge, and then xor it in. + todo := d.rate - len(d.buf) + if todo > len(p) { + todo = len(p) + } + d.buf = append(d.buf, p[:todo]...) + p = p[todo:] + + // If the sponge is full, apply the permutation. + if len(d.buf) == d.rate { + d.permute() + } + } + } + + return +} + +// Read squeezes an arbitrary number of bytes from the sponge. +func (d *state) Read(out []byte) (n int, err error) { + // If we're still absorbing, pad and apply the permutation. + if d.state == spongeAbsorbing { + d.padAndPermute(d.dsbyte) + } + + n = len(out) + + // Now, do the squeezing. + for len(out) > 0 { + n := copy(out, d.buf) + d.buf = d.buf[n:] + out = out[n:] + + // Apply the permutation if we've squeezed the sponge dry. + if len(d.buf) == 0 { + d.permute() + } + } + + return +} + +// Sum applies padding to the hash state and then squeezes out the desired +// number of output bytes. +func (d *state) Sum(in []byte) []byte { + // Make a copy of the original hash so that caller can keep writing + // and summing. + dup := d.clone() + hash := make([]byte, dup.outputLen) + dup.Read(hash) + return append(in, hash...) +} diff --git a/cryptography/sha3/sha3_s390x.go b/cryptography/sha3/sha3_s390x.go new file mode 100644 index 0000000..259ff4d --- /dev/null +++ b/cryptography/sha3/sha3_s390x.go @@ -0,0 +1,284 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !gccgo,!appengine + +package sha3 + +// This file contains code for using the 'compute intermediate +// message digest' (KIMD) and 'compute last message digest' (KLMD) +// instructions to compute SHA-3 and SHAKE hashes on IBM Z. + +import ( + "hash" + + "golang.org/x/sys/cpu" +) + +// codes represent 7-bit KIMD/KLMD function codes as defined in +// the Principles of Operation. +type code uint64 + +const ( + // function codes for KIMD/KLMD + sha3_224 code = 32 + sha3_256 = 33 + sha3_384 = 34 + sha3_512 = 35 + shake_128 = 36 + shake_256 = 37 + nopad = 0x100 +) + +// kimd is a wrapper for the 'compute intermediate message digest' instruction. +// src must be a multiple of the rate for the given function code. +//go:noescape +func kimd(function code, chain *[200]byte, src []byte) + +// klmd is a wrapper for the 'compute last message digest' instruction. +// src padding is handled by the instruction. +//go:noescape +func klmd(function code, chain *[200]byte, dst, src []byte) + +type asmState struct { + a [200]byte // 1600 bit state + buf []byte // care must be taken to ensure cap(buf) is a multiple of rate + rate int // equivalent to block size + storage [3072]byte // underlying storage for buf + outputLen int // output length if fixed, 0 if not + function code // KIMD/KLMD function code + state spongeDirection // whether the sponge is absorbing or squeezing +} + +func newAsmState(function code) *asmState { + var s asmState + s.function = function + switch function { + case sha3_224: + s.rate = 144 + s.outputLen = 28 + case sha3_256: + s.rate = 136 + s.outputLen = 32 + case sha3_384: + s.rate = 104 + s.outputLen = 48 + case sha3_512: + s.rate = 72 + s.outputLen = 64 + case shake_128: + s.rate = 168 + case shake_256: + s.rate = 136 + default: + panic("sha3: unrecognized function code") + } + + // limit s.buf size to a multiple of s.rate + s.resetBuf() + return &s +} + +func (s *asmState) clone() *asmState { + c := *s + c.buf = c.storage[:len(s.buf):cap(s.buf)] + return &c +} + +// copyIntoBuf copies b into buf. It will panic if there is not enough space to +// store all of b. +func (s *asmState) copyIntoBuf(b []byte) { + bufLen := len(s.buf) + s.buf = s.buf[:len(s.buf)+len(b)] + copy(s.buf[bufLen:], b) +} + +// resetBuf points buf at storage, sets the length to 0 and sets cap to be a +// multiple of the rate. +func (s *asmState) resetBuf() { + max := (cap(s.storage) / s.rate) * s.rate + s.buf = s.storage[:0:max] +} + +// Write (via the embedded io.Writer interface) adds more data to the running hash. +// It never returns an error. +func (s *asmState) Write(b []byte) (int, error) { + if s.state != spongeAbsorbing { + panic("sha3: write to sponge after read") + } + length := len(b) + for len(b) > 0 { + if len(s.buf) == 0 && len(b) >= cap(s.buf) { + // Hash the data directly and push any remaining bytes + // into the buffer. + remainder := len(b) % s.rate + kimd(s.function, &s.a, b[:len(b)-remainder]) + if remainder != 0 { + s.copyIntoBuf(b[len(b)-remainder:]) + } + return length, nil + } + + if len(s.buf) == cap(s.buf) { + // flush the buffer + kimd(s.function, &s.a, s.buf) + s.buf = s.buf[:0] + } + + // copy as much as we can into the buffer + n := len(b) + if len(b) > cap(s.buf)-len(s.buf) { + n = cap(s.buf) - len(s.buf) + } + s.copyIntoBuf(b[:n]) + b = b[n:] + } + return length, nil +} + +// Read squeezes an arbitrary number of bytes from the sponge. +func (s *asmState) Read(out []byte) (n int, err error) { + n = len(out) + + // need to pad if we were absorbing + if s.state == spongeAbsorbing { + s.state = spongeSqueezing + + // write hash directly into out if possible + if len(out)%s.rate == 0 { + klmd(s.function, &s.a, out, s.buf) // len(out) may be 0 + s.buf = s.buf[:0] + return + } + + // write hash into buffer + max := cap(s.buf) + if max > len(out) { + max = (len(out)/s.rate)*s.rate + s.rate + } + klmd(s.function, &s.a, s.buf[:max], s.buf) + s.buf = s.buf[:max] + } + + for len(out) > 0 { + // flush the buffer + if len(s.buf) != 0 { + c := copy(out, s.buf) + out = out[c:] + s.buf = s.buf[c:] + continue + } + + // write hash directly into out if possible + if len(out)%s.rate == 0 { + klmd(s.function|nopad, &s.a, out, nil) + return + } + + // write hash into buffer + s.resetBuf() + if cap(s.buf) > len(out) { + s.buf = s.buf[:(len(out)/s.rate)*s.rate+s.rate] + } + klmd(s.function|nopad, &s.a, s.buf, nil) + } + return +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (s *asmState) Sum(b []byte) []byte { + if s.outputLen == 0 { + panic("sha3: cannot call Sum on SHAKE functions") + } + + // Copy the state to preserve the original. + a := s.a + + // Hash the buffer. Note that we don't clear it because we + // aren't updating the state. + klmd(s.function, &a, nil, s.buf) + return append(b, a[:s.outputLen]...) +} + +// Reset resets the Hash to its initial state. +func (s *asmState) Reset() { + for i := range s.a { + s.a[i] = 0 + } + s.resetBuf() + s.state = spongeAbsorbing +} + +// Size returns the number of bytes Sum will return. +func (s *asmState) Size() int { + return s.outputLen +} + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (s *asmState) BlockSize() int { + return s.rate +} + +// Clone returns a copy of the ShakeHash in its current state. +func (s *asmState) Clone() ShakeHash { + return s.clone() +} + +// new224Asm returns an assembly implementation of SHA3-224 if available, +// otherwise it returns nil. +func new224Asm() hash.Hash { + if cpu.S390X.HasSHA3 { + return newAsmState(sha3_224) + } + return nil +} + +// new256Asm returns an assembly implementation of SHA3-256 if available, +// otherwise it returns nil. +func new256Asm() hash.Hash { + if cpu.S390X.HasSHA3 { + return newAsmState(sha3_256) + } + return nil +} + +// new384Asm returns an assembly implementation of SHA3-384 if available, +// otherwise it returns nil. +func new384Asm() hash.Hash { + if cpu.S390X.HasSHA3 { + return newAsmState(sha3_384) + } + return nil +} + +// new512Asm returns an assembly implementation of SHA3-512 if available, +// otherwise it returns nil. +func new512Asm() hash.Hash { + if cpu.S390X.HasSHA3 { + return newAsmState(sha3_512) + } + return nil +} + +// newShake128Asm returns an assembly implementation of SHAKE-128 if available, +// otherwise it returns nil. +func newShake128Asm() ShakeHash { + if cpu.S390X.HasSHA3 { + return newAsmState(shake_128) + } + return nil +} + +// newShake256Asm returns an assembly implementation of SHAKE-256 if available, +// otherwise it returns nil. +func newShake256Asm() ShakeHash { + if cpu.S390X.HasSHA3 { + return newAsmState(shake_256) + } + return nil +} diff --git a/cryptography/sha3/sha3_s390x.s b/cryptography/sha3/sha3_s390x.s new file mode 100644 index 0000000..8a4458f --- /dev/null +++ b/cryptography/sha3/sha3_s390x.s @@ -0,0 +1,33 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !gccgo,!appengine + +#include "textflag.h" + +// func kimd(function code, chain *[200]byte, src []byte) +TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40 + MOVD function+0(FP), R0 + MOVD chain+8(FP), R1 + LMG src+16(FP), R2, R3 // R2=base, R3=len + +continue: + WORD $0xB93E0002 // KIMD --, R2 + BVS continue // continue if interrupted + MOVD $0, R0 // reset R0 for pre-go1.8 compilers + RET + +// func klmd(function code, chain *[200]byte, dst, src []byte) +TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64 + // TODO: SHAKE support + MOVD function+0(FP), R0 + MOVD chain+8(FP), R1 + LMG dst+16(FP), R2, R3 // R2=base, R3=len + LMG src+40(FP), R4, R5 // R4=base, R5=len + +continue: + WORD $0xB93F0024 // KLMD R2, R4 + BVS continue // continue if interrupted + MOVD $0, R0 // reset R0 for pre-go1.8 compilers + RET diff --git a/cryptography/sha3/sha3_test.go b/cryptography/sha3/sha3_test.go new file mode 100644 index 0000000..005a242 --- /dev/null +++ b/cryptography/sha3/sha3_test.go @@ -0,0 +1,483 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// Tests include all the ShortMsgKATs provided by the Keccak team at +// https://github.com/gvanas/KeccakCodePackage +// +// They only include the zero-bit case of the bitwise testvectors +// published by NIST in the draft of FIPS-202. + +import ( + "bytes" + "compress/flate" + "encoding/hex" + "encoding/json" + "fmt" + "hash" + "os" + "strings" + "testing" +) + +const ( + testString = "brekeccakkeccak koax koax" + katFilename = "testdata/keccakKats.json.deflate" +) + +// testDigests contains functions returning hash.Hash instances +// with output-length equal to the KAT length for SHA-3, Keccak +// and SHAKE instances. +var testDigests = map[string]func() hash.Hash{ + "SHA3-224": New224, + "SHA3-256": New256, + "SHA3-384": New384, + "SHA3-512": New512, + "Keccak-256": NewLegacyKeccak256, + "Keccak-512": NewLegacyKeccak512, +} + +// testShakes contains functions that return sha3.ShakeHash instances for +// with output-length equal to the KAT length. +var testShakes = map[string]struct { + constructor func(N []byte, S []byte) ShakeHash + defAlgoName string + defCustomStr string +}{ + // NewCShake without customization produces same result as SHAKE + "SHAKE128": {NewCShake128, "", ""}, + "SHAKE256": {NewCShake256, "", ""}, + "cSHAKE128": {NewCShake128, "CSHAKE128", "CustomStrign"}, + "cSHAKE256": {NewCShake256, "CSHAKE256", "CustomStrign"}, +} + +// decodeHex converts a hex-encoded string into a raw byte string. +func decodeHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +// structs used to marshal JSON test-cases. +type KeccakKats struct { + Kats map[string][]struct { + Digest string `json:"digest"` + Length int64 `json:"length"` + Message string `json:"message"` + + // Defined only for cSHAKE + N string `json:"N"` + S string `json:"S"` + } +} + +func testUnalignedAndGeneric(t *testing.T, testf func(impl string)) { + xorInOrig, copyOutOrig := xorIn, copyOut + xorIn, copyOut = xorInGeneric, copyOutGeneric + testf("generic") + if xorImplementationUnaligned != "generic" { + xorIn, copyOut = xorInUnaligned, copyOutUnaligned + testf("unaligned") + } + xorIn, copyOut = xorInOrig, copyOutOrig +} + +// TestKeccakKats tests the SHA-3 and Shake implementations against all the +// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage +// (The testvectors are stored in keccakKats.json.deflate due to their length.) +func TestKeccakKats(t *testing.T) { + testUnalignedAndGeneric(t, func(impl string) { + // Read the KATs. + deflated, err := os.Open(katFilename) + if err != nil { + t.Errorf("error opening %s: %s", katFilename, err) + } + file := flate.NewReader(deflated) + dec := json.NewDecoder(file) + var katSet KeccakKats + err = dec.Decode(&katSet) + if err != nil { + t.Errorf("error decoding KATs: %s", err) + } + + for algo, function := range testDigests { + d := function() + for _, kat := range katSet.Kats[algo] { + d.Reset() + in, err := hex.DecodeString(kat.Message) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + d.Write(in[:kat.Length/8]) + got := strings.ToUpper(hex.EncodeToString(d.Sum(nil))) + if got != kat.Digest { + t.Errorf("function=%s, implementation=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s", + algo, impl, kat.Length, kat.Message, got, kat.Digest) + t.Logf("wanted %+v", kat) + t.FailNow() + } + continue + } + } + + for algo, v := range testShakes { + for _, kat := range katSet.Kats[algo] { + N, err := hex.DecodeString(kat.N) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + + S, err := hex.DecodeString(kat.S) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + d := v.constructor(N, S) + in, err := hex.DecodeString(kat.Message) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + + d.Write(in[:kat.Length/8]) + out := make([]byte, len(kat.Digest)/2) + d.Read(out) + got := strings.ToUpper(hex.EncodeToString(out)) + if got != kat.Digest { + t.Errorf("function=%s, implementation=%s, length=%d N:%s\n S:%s\nmessage:\n %s \ngot:\n %s\nwanted:\n %s", + algo, impl, kat.Length, kat.N, kat.S, kat.Message, got, kat.Digest) + t.Logf("wanted %+v", kat) + t.FailNow() + } + continue + } + } + }) +} + +// TestKeccak does a basic test of the non-standardized Keccak hash functions. +func TestKeccak(t *testing.T) { + tests := []struct { + fn func() hash.Hash + data []byte + want string + }{ + { + NewLegacyKeccak256, + []byte("abc"), + "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", + }, + { + NewLegacyKeccak512, + []byte("abc"), + "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", + }, + } + + for _, u := range tests { + h := u.fn() + h.Write(u.data) + got := h.Sum(nil) + want := decodeHex(u.want) + if !bytes.Equal(got, want) { + t.Errorf("unexpected hash for size %d: got '%x' want '%s'", h.Size()*8, got, u.want) + } + } +} + +// TestUnalignedWrite tests that writing data in an arbitrary pattern with +// small input buffers. +func TestUnalignedWrite(t *testing.T) { + testUnalignedAndGeneric(t, func(impl string) { + buf := sequentialBytes(0x10000) + for alg, df := range testDigests { + d := df() + d.Reset() + d.Write(buf) + want := d.Sum(nil) + d.Reset() + for i := 0; i < len(buf); { + // Cycle through offsets which make a 137 byte sequence. + // Because 137 is prime this sequence should exercise all corner cases. + offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} + for _, j := range offsets { + if v := len(buf) - i; v < j { + j = v + } + d.Write(buf[i : i+j]) + i += j + } + } + got := d.Sum(nil) + if !bytes.Equal(got, want) { + t.Errorf("Unaligned writes, implementation=%s, alg=%s\ngot %q, want %q", impl, alg, got, want) + } + } + + // Same for SHAKE + for alg, df := range testShakes { + want := make([]byte, 16) + got := make([]byte, 16) + d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr)) + + d.Reset() + d.Write(buf) + d.Read(want) + d.Reset() + for i := 0; i < len(buf); { + // Cycle through offsets which make a 137 byte sequence. + // Because 137 is prime this sequence should exercise all corner cases. + offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} + for _, j := range offsets { + if v := len(buf) - i; v < j { + j = v + } + d.Write(buf[i : i+j]) + i += j + } + } + d.Read(got) + if !bytes.Equal(got, want) { + t.Errorf("Unaligned writes, implementation=%s, alg=%s\ngot %q, want %q", impl, alg, got, want) + } + } + }) +} + +// TestAppend checks that appending works when reallocation is necessary. +func TestAppend(t *testing.T) { + testUnalignedAndGeneric(t, func(impl string) { + d := New224() + + for capacity := 2; capacity <= 66; capacity += 64 { + // The first time around the loop, Sum will have to reallocate. + // The second time, it will not. + buf := make([]byte, 2, capacity) + d.Reset() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("got %s, want %s", got, expected) + } + } + }) +} + +// TestAppendNoRealloc tests that appending works when no reallocation is necessary. +func TestAppendNoRealloc(t *testing.T) { + testUnalignedAndGeneric(t, func(impl string) { + buf := make([]byte, 1, 200) + d := New224() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("%s: got %s, want %s", impl, got, expected) + } + }) +} + +// TestSqueezing checks that squeezing the full output a single time produces +// the same output as repeatedly squeezing the instance. +func TestSqueezing(t *testing.T) { + testUnalignedAndGeneric(t, func(impl string) { + for algo, v := range testShakes { + d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d0.Write([]byte(testString)) + ref := make([]byte, 32) + d0.Read(ref) + + d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d1.Write([]byte(testString)) + var multiple []byte + for range ref { + one := make([]byte, 1) + d1.Read(one) + multiple = append(multiple, one...) + } + if !bytes.Equal(ref, multiple) { + t.Errorf("%s (%s): squeezing %d bytes one at a time failed", algo, impl, len(ref)) + } + } + }) +} + +// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. +func sequentialBytes(size int) []byte { + result := make([]byte, size) + for i := range result { + result[i] = byte(i) + } + return result +} + +func TestReset(t *testing.T) { + out1 := make([]byte, 32) + out2 := make([]byte, 32) + + for _, v := range testShakes { + // Calculate hash for the first time + c := v.constructor(nil, []byte{0x99, 0x98}) + c.Write(sequentialBytes(0x100)) + c.Read(out1) + + // Calculate hash again + c.Reset() + c.Write(sequentialBytes(0x100)) + c.Read(out2) + + if !bytes.Equal(out1, out2) { + t.Error("\nExpected:\n", out1, "\ngot:\n", out2) + } + } +} + +func TestClone(t *testing.T) { + out1 := make([]byte, 16) + out2 := make([]byte, 16) + + // Test for sizes smaller and larger than block size. + for _, size := range []int{0x1, 0x100} { + in := sequentialBytes(size) + for _, v := range testShakes { + h1 := v.constructor(nil, []byte{0x01}) + h1.Write([]byte{0x01}) + + h2 := h1.Clone() + + h1.Write(in) + h1.Read(out1) + + h2.Write(in) + h2.Read(out2) + + if !bytes.Equal(out1, out2) { + t.Error("\nExpected:\n", hex.EncodeToString(out1), "\ngot:\n", hex.EncodeToString(out2)) + } + } + } +} + +// BenchmarkPermutationFunction measures the speed of the permutation function +// with no input data. +func BenchmarkPermutationFunction(b *testing.B) { + b.SetBytes(int64(200)) + var lanes [25]uint64 + for i := 0; i < b.N; i++ { + keccakF1600(&lanes) + } +} + +// benchmarkHash tests the speed to hash num buffers of buflen each. +func benchmarkHash(b *testing.B, h hash.Hash, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + b.SetBytes(int64(size * num)) + b.StartTimer() + + var state []byte + for i := 0; i < b.N; i++ { + for j := 0; j < num; j++ { + h.Write(data) + } + state = h.Sum(state[:0]) + } + b.StopTimer() + h.Reset() +} + +// benchmarkShake is specialized to the Shake instances, which don't +// require a copy on reading output. +func benchmarkShake(b *testing.B, h ShakeHash, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + d := make([]byte, 32) + + b.SetBytes(int64(size * num)) + b.StartTimer() + + for i := 0; i < b.N; i++ { + h.Reset() + for j := 0; j < num; j++ { + h.Write(data) + } + h.Read(d) + } +} + +func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) } +func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) } +func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) } +func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) } + +func BenchmarkShake128_MTU(b *testing.B) { benchmarkShake(b, NewShake128(), 1350, 1) } +func BenchmarkShake256_MTU(b *testing.B) { benchmarkShake(b, NewShake256(), 1350, 1) } +func BenchmarkShake256_16x(b *testing.B) { benchmarkShake(b, NewShake256(), 16, 1024) } +func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewShake256(), 1024, 1024) } + +func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) } + +func Example_sum() { + buf := []byte("some data to hash") + // A hash needs to be 64 bytes long to have 256-bit collision resistance. + h := make([]byte, 64) + // Compute a 64-byte hash of buf and put it in h. + ShakeSum256(h, buf) + fmt.Printf("%x\n", h) + // Output: 0f65fe41fc353e52c55667bb9e2b27bfcc8476f2c413e9437d272ee3194a4e3146d05ec04a25d16b8f577c19b82d16b1424c3e022e783d2b4da98de3658d363d +} + +func Example_mac() { + k := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long") + buf := []byte("and this is some data to authenticate") + // A MAC with 32 bytes of output has 256-bit security strength -- if you use at least a 32-byte-long key. + h := make([]byte, 32) + d := NewShake256() + // Write the key into the hash. + d.Write(k) + // Now write the data. + d.Write(buf) + // Read 32 bytes of output from the hash into h. + d.Read(h) + fmt.Printf("%x\n", h) + // Output: 78de2974bd2711d5549ffd32b753ef0f5fa80a0db2556db60f0987eb8a9218ff +} + +func ExampleNewCShake256() { + out := make([]byte, 32) + msg := []byte("The quick brown fox jumps over the lazy dog") + + // Example 1: Simple cshake + c1 := NewCShake256([]byte("NAME"), []byte("Partition1")) + c1.Write(msg) + c1.Read(out) + fmt.Println(hex.EncodeToString(out)) + + // Example 2: Different customization string produces different digest + c1 = NewCShake256([]byte("NAME"), []byte("Partition2")) + c1.Write(msg) + c1.Read(out) + fmt.Println(hex.EncodeToString(out)) + + // Example 3: Longer output length produces longer digest + out = make([]byte, 64) + c1 = NewCShake256([]byte("NAME"), []byte("Partition1")) + c1.Write(msg) + c1.Read(out) + fmt.Println(hex.EncodeToString(out)) + + // Example 4: Next read produces different result + c1.Read(out) + fmt.Println(hex.EncodeToString(out)) + + // Output: + //a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b + //a8db03e71f3e4da5c4eee9d28333cdd355f51cef3c567e59be5beb4ecdbb28f0 + //a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b9dd98c6ee866ca7dc5a39d53e960f400bcd5a19c8a2d6ec6459f63696543a0d8 + //85e73a72228d08b46515553ca3a29d47df3047e5d84b12d6c2c63e579f4fd1105716b7838e92e981863907f434bfd4443c9e56ea09da998d2f9b47db71988109 +} diff --git a/cryptography/sha3/shake.go b/cryptography/sha3/shake.go new file mode 100644 index 0000000..d7be295 --- /dev/null +++ b/cryptography/sha3/shake.go @@ -0,0 +1,173 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This file defines the ShakeHash interface, and provides +// functions for creating SHAKE and cSHAKE instances, as well as utility +// functions for hashing bytes to arbitrary-length output. +// +// +// SHAKE implementation is based on FIPS PUB 202 [1] +// cSHAKE implementations is based on NIST SP 800-185 [2] +// +// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf +// [2] https://doi.org/10.6028/NIST.SP.800-185 + +import ( + "encoding/binary" + "io" +) + +// ShakeHash defines the interface to hash functions that +// support arbitrary-length output. +type ShakeHash interface { + // Write absorbs more data into the hash's state. It panics if input is + // written to it after output has been read from it. + io.Writer + + // Read reads more output from the hash; reading affects the hash's + // state. (ShakeHash.Read is thus very different from Hash.Sum) + // It never returns an error. + io.Reader + + // Clone returns a copy of the ShakeHash in its current state. + Clone() ShakeHash + + // Reset resets the ShakeHash to its initial state. + Reset() +} + +// cSHAKE specific context +type cshakeState struct { + *state // SHA-3 state context and Read/Write operations + + // initBlock is the cSHAKE specific initialization set of bytes. It is initialized + // by newCShake function and stores concatenation of N followed by S, encoded + // by the method specified in 3.3 of [1]. + // It is stored here in order for Reset() to be able to put context into + // initial state. + initBlock []byte +} + +// Consts for configuring initial SHA-3 state +const ( + dsbyteShake = 0x1f + dsbyteCShake = 0x04 + rate128 = 168 + rate256 = 136 +) + +func bytepad(input []byte, w int) []byte { + // leftEncode always returns max 9 bytes + buf := make([]byte, 0, 9+len(input)+w) + buf = append(buf, leftEncode(uint64(w))...) + buf = append(buf, input...) + padlen := w - (len(buf) % w) + return append(buf, make([]byte, padlen)...) +} + +func leftEncode(value uint64) []byte { + var b [9]byte + binary.BigEndian.PutUint64(b[1:], value) + // Trim all but last leading zero bytes + i := byte(1) + for i < 8 && b[i] == 0 { + i++ + } + // Prepend number of encoded bytes + b[i-1] = 9 - i + return b[i-1:] +} + +func newCShake(N, S []byte, rate int, dsbyte byte) ShakeHash { + c := cshakeState{state: &state{rate: rate, dsbyte: dsbyte}} + + // leftEncode returns max 9 bytes + c.initBlock = make([]byte, 0, 9*2+len(N)+len(S)) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(N)*8))...) + c.initBlock = append(c.initBlock, N...) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(S)*8))...) + c.initBlock = append(c.initBlock, S...) + c.Write(bytepad(c.initBlock, c.rate)) + return &c +} + +// Reset resets the hash to initial state. +func (c *cshakeState) Reset() { + c.state.Reset() + c.Write(bytepad(c.initBlock, c.rate)) +} + +// Clone returns copy of a cSHAKE context within its current state. +func (c *cshakeState) Clone() ShakeHash { + b := make([]byte, len(c.initBlock)) + copy(b, c.initBlock) + return &cshakeState{state: c.clone(), initBlock: b} +} + +// Clone returns copy of SHAKE context within its current state. +func (c *state) Clone() ShakeHash { + return c.clone() +} + +// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 128 bits against all attacks if at +// least 32 bytes of its output are used. +func NewShake128() ShakeHash { + if h := newShake128Asm(); h != nil { + return h + } + return &state{rate: rate128, dsbyte: dsbyteShake} +} + +// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. +// Its generic security strength is 256 bits against all attacks if +// at least 64 bytes of its output are used. +func NewShake256() ShakeHash { + if h := newShake256Asm(); h != nil { + return h + } + return &state{rate: rate256, dsbyte: dsbyteShake} +} + +// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash, +// a customizable variant of SHAKE128. +// N is used to define functions based on cSHAKE, it can be empty when plain cSHAKE is +// desired. S is a customization byte string used for domain separation - two cSHAKE +// computations on same input with different S yield unrelated outputs. +// When N and S are both empty, this is equivalent to NewShake128. +func NewCShake128(N, S []byte) ShakeHash { + if len(N) == 0 && len(S) == 0 { + return NewShake128() + } + return newCShake(N, S, rate128, dsbyteCShake) +} + +// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash, +// a customizable variant of SHAKE256. +// N is used to define functions based on cSHAKE, it can be empty when plain cSHAKE is +// desired. S is a customization byte string used for domain separation - two cSHAKE +// computations on same input with different S yield unrelated outputs. +// When N and S are both empty, this is equivalent to NewShake256. +func NewCShake256(N, S []byte) ShakeHash { + if len(N) == 0 && len(S) == 0 { + return NewShake256() + } + return newCShake(N, S, rate256, dsbyteCShake) +} + +// ShakeSum128 writes an arbitrary-length digest of data into hash. +func ShakeSum128(hash, data []byte) { + h := NewShake128() + h.Write(data) + h.Read(hash) +} + +// ShakeSum256 writes an arbitrary-length digest of data into hash. +func ShakeSum256(hash, data []byte) { + h := NewShake256() + h.Write(data) + h.Read(hash) +} diff --git a/cryptography/sha3/shake_generic.go b/cryptography/sha3/shake_generic.go new file mode 100644 index 0000000..add4e73 --- /dev/null +++ b/cryptography/sha3/shake_generic.go @@ -0,0 +1,19 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build gccgo appengine !s390x + +package sha3 + +// newShake128Asm returns an assembly implementation of SHAKE-128 if available, +// otherwise it returns nil. +func newShake128Asm() ShakeHash { + return nil +} + +// newShake256Asm returns an assembly implementation of SHAKE-256 if available, +// otherwise it returns nil. +func newShake256Asm() ShakeHash { + return nil +} diff --git a/cryptography/sha3/testdata/keccakKats.json.deflate b/cryptography/sha3/testdata/keccakKats.json.deflate new file mode 100644 index 0000000000000000000000000000000000000000..7a94c2f8bce62c70664cda859a30845cab713c24 GIT binary patch literal 540828 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

r46_VrdCQ_>b^q8k=CXV-{AK%%}5RO9u(dzXPu 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 z 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

FTjCk4EfUM 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%0DqQa){X-aj;KFxu|u-~Y19U#!gK<<*(t+rgptqG^ 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


;$zL+MxI{G7r<YyJ|ZNOj7 zgc{S*ed5kuF+8O3*_$a4Fhk{sYV=rCZfBgeT*$G{OsM}kM)dV-d%s6LX9(LS=9{}- z@tjFHaqqbCH^?;J6`559oNbPkMAtS_&M^zUiIs6YR#57$pStsQ>Uk7lynz=?H8U(7 z%a3hA_Np+Qs+D!iJ!!kBV*VNX$^rhY4a5L`<^!RaUU8`*E1&vjxKnUW?_2TW-aE9K zBwEBi1bFj+-C*EXBo=H@q&NE;jf8?%>_zcy;jo<;u6RpkM`|_jl*QA1>!SBj|Gg%f zW{cmTFy*1ovf95u0%&E}rQXOvjz(a=2jSX>D|VU` z)bbMUbq9REbVS{dtFV<|Y^OKBLGnXPjcT=&VpTWA9&U&ZQQQLRO&QUpw_px3bUgrW zqkWYU9kW{I5Rmd0XpLq-tTgu(y0V;mQ$H{I6ZZI65omPZqsX+!OHn?NwX%JKzJ`Iq z(KyulF6>0#w4+xZ?WT8AD{ALyVL6=CRTSrHG8i5Ti&qKky51ehraea12^GiIH~0(h z`lcqVR3`o5%9CJ{H0=XxQ)llvLV7{Yk>S;jeE^XC&85_ir6xOaem!pUF6TT_ebohg zoA28Uui#vU?9p5AQMPr?ynT&j010{m)awFBHm~C6Fjh|>4oUT8)^SySyNFrki)xB* zqd3}wm9sQ-Jm{$oJP&Y(w5V8Mf+cGE<|x9aA`!iA$E8X9CUyEe4x@JM^NA!F^s#)b z@%|YbA@S$&s4R$o3-$ogWKTbu~{#l zS+u?`Pq)*%`h}G5045(wSK5Hd5`n5cJPRi_%+yqv>67Y|6f=Z${3xDD=8QFFfE>B= zwM7Z~Mi$4l*nc)L5`Bs8Io-JDsxGoqR$tW{Pw1S-*9GYjbj!e3PAXXM69rW!nS~87 z%1WOtd-|O|lTOKKk7BtzN*)c3Gm*KaTd_Qr^(8LXjr}~PoH8E1eU2>nwUXo!XH@6$ zWHDMux>KxkAN5)@lz6%iDV{KG%r@u|R{5jlCcHH7GBZ+bH z9_GIG8GkL*qhs>fRy^{He#IaZ^0n7%S-!>0FS^RYb*IXQse%)>He>%9w$bFSkEh2$fk zFvl~CNz;!W|6Wqxyeud{-*oJaVe8Rv5#@k$hkKu7nztu~m+NBB0{L&Fn4lZ8QEXR33GSn>2s^t=zCZjzHC)fIk^L9~w%siXQ{n~-=P;YvXA0+`9 z*?XPiqKYlwPv%5vZ%qj%`HNl&=Q# zjoooQ2Aq*?Bmx6?j)d#!9^@=HLc>RJ_^!RWC2403`HOfuJ)hB8cOEN+8?%`2FD*-c zMk;!ceZLeW(}CM2t0a<1lan=6U&|6u#}-Z|?^)Fl7*!IQkIvc?m*td)GK6;h*6eNd#O$gdp zk69ia&lq2}&+mM-{7V$u$l15Ez4q?fFxRXG*{`u6z||?r?4K66H?DZ_iSrQ}&y0tQ z?)G-(#-ZCRrz?2mdn4m{-!~r?O2Sk)SMkJy&t#sP)k@qfeR!pV^{K}PwZWgIaC_=y zb?k6imbTEW$@dS4rifG3uP8oI6n3^5OZzA(4fw3xbMSK|&kWr)Iz8C&2w^qE#kr4r znty&I4S4R{IdziRhl)T_Xc-`uX{dr{7wJk z(Gj&GJAH@yrhGp8k4<9_J-K!3o}I(vRvB~kk#rbMs>krO2{c7EOYV5qDT6uV(;XJ4 zP>0wtL#`1f9lp@bo)jX}zXj}?X~m;_wBKU!kkGn*C1n*7vx#&lI(QX6H<`~M5bq5M zn!q~kF@SF|>-KtFc@(srT6HC_R-M$O>#w52B>|)!Txx817fT2C*O9R5t8!x!=iyLJ z>#f+8#~ZsZR5Z6#+mk8xYwlDEhMG&~+-q)|+I{LO*a~?}=@IWEweP{{=c4-A8upd9 z5I=lgjx~dk32L`FdU?Lw$t}E>RF>A_fRgB2Nr5wheZNc>X2-#M-{$T#fX7g;BooZe zNjLQ`IQB@hsLQZnEk2qb^>&XlM*ALi0m&ldk00W;e!)!eC0AT| z1v4!%`Nq=Ra=1ex(!7tzsCM&(;~9q{+Rpg`K`s>vv1F*w(r2^^kOdXcn$d`$%-D%H zjBfRNM8DIlZF~5YxRw*Q&`I&+aRcowRvfv|D*SaHwCGoF0hsAFyf3+@2VV944$hKS zcP{CVcMGnLepO5V+2FW_^te0OozL)UYG!DG>olCGV=_H1mp!J}!l523^T*u>AA$vc zukk#9_-ipgQkM!eBiv$J=^;7r*5f1h$kR(ri5mukX>qyU_wc=egTSg6ttPABRV~T$ z3MA263tw(vyIqcRXut4rDuw2(9D&cI(U30A<1??69g1(F!0-ZxF$-YJCZ4WuCjQZf zdrlz0cFk!+mRESR+dp3?>@+w9xgqX(2IwZI^7S=kS?;=$?n> z-(Bc9n9%PbUyZU;Iq7jqG=LDVLm`~+{N!PI-D`G6y`U=P>zHU$)6a_E136vjEvr-b z6qr9Sgt41?@8O6+R8}LLeykwa6y8%Zy_DkpWl}N4G*lhE({nBQ_L2`7O@mo@3X~sJ zxd*i`Rwd0m?tUu{zS6meiwy2DM!JK23J25ADg4OPK)mJlNS=cq(U|H7)dBG;m%dUN zPaQ>Jd5@`KgVFl?K?}~?bRtnPo}7JxmrGnp03^llWJ`St9*)dg1+Nr7=7Lo1ofYv# zF11qCRk9hfx7*Q1Y(RQQZVGNWA2U7G@5X{!=>zRL*RbO2V)6v-FIO7vcHlz1-IJ32 zkm{tE$#9-8Tb(%@dzf!UyDA6++D#}(Wc)-kuwKJ>G*CvZC2X#)opYnFf-F{VKEWSZ zhhDwQ_Z7akLAwWGK4-d0w#xH)w_VI*3UJW&YTPKLh(FD_6#hIsEd}C@OB2xT=N#kH z=fwMmA|D56c|}lIGmPsNcoVZSR;JvRIF#$$;&Y`Ej1IKS{mkEPA31TWXq$F7;63@E z-1DB@D{Ku!ehrzM&zxZ-MKmdVYU}bP^4PcC9?RPhpBnTbNZuVRS@86)GkeQU$Y5==+5V{s9rm)k5PZ;K%WRelA&yvrORuf5#j6p1kZ#xRol0o9`H9&tXyw?g#mw?M+zSN4kZSJTQ?%di}!8KW)3nQi$)> zw~>7P6)OZ}WfwO;157D%`r2n`7E@ruGT?^b+UrMvgi>?u(0yAVkVpZi)hC1mzx?z_ z1={+&QSCaBxbK73UYu-@?8Me-yEiqjxf(+m%aiSTt=PuoHaXjLofTAbbEZ4KS^+{dJp(ASE&Zg)$ed;AZH;dgJB*oWsBJw ztm|PnNK982>c-XP)d}{5Z#}y;jAL&%%-4VMZpN;Lmb6gd@)IyD-#KYZ)nb9TN{ov> zUGRu>7ZO>5=Pj`y8NSyCpe^e&FWbhC0y%@Z}J!U{km} zuDC|Ha2M4;$aBaHzc{BIUm-fjjbM>HN>0Z@GhH_MhGE?HbKMJx#fpx`Y ziZ2C#+za|QcgMeHOTXcsii=PFr3LIGqCQ^VfyPnP|NBs$HcES>!jXXbQEu2I+J{?0 z4kTZJ5zo=<554lzqf|PdEt_uQ3S0)&r#Yb)4PAV(^^n=Qe8O#&AB$$UT#nmvDFVLv ztVzr-ZCO(#i5WOlf+&o>7;8IEGf9Ach8EM8aWMwY*RuuYvRa17^F{Z?6C8ba6$DC7 zky+~rkqD$d@#t~tIma<7K(o~2D=4q>LqyR<{Nje?oCGOHamF(Qd=atkIj1g%Jd@jD`d5i|;IeFOnUwhf$dP0!bwsR$Xb07Zpg>1BB( zAA-pu9x=&aiZ5$DfO5bQP@?I0N?&fFr=R=Dt_LFeO?@ah*Jlnqgaask+x81D$c&OD z69WdKkaD7XujsoS_7pq=V5m2@{DtFTps8?)~AmXeD9kY zs}S!!P#%4R0xr~bXRh&DIKu3Kl8t24tW6BIf#AAJG16^F2U2-K{6z^4d_@AiWl81j;JKo>*Qj?ooYM{CxfI z0as4XyPGH1c#Nxf{r7t8HZ@%NY&aqMo~-K(ZS_bM=WstB?DA^bSA`F5t_)j0ncTQX zi1vMtZFU+8l?k;Q7UcLfHFgTg|BMNsOxUl1F_nN|EXIhB7&IYtFO4vI(9UJ0<-V7*>+Fuy>&z@)K^Ot(S3 zPl2Mq9jNLrsVBeAnJIh_dKay_XdZmrxSuz@?>Y8eYV+3wobb>`v{YT_9<@={kT7qY zM+#8R?&@vefVIkdqU^IwMC912R_duIjuJi(DIV0WX!9UEe;Rrz{du6uPYCXAUamVS zXl%3ny?#62%*`v1qE4M7PL`~Niznk2T^;58-bpDZ+GFrnvmsvjrbGG& z{tl$?)yI?{j>_~|0KRg@i-xaFht|2Z^8l;VksV!+8qmbb4 zR}tb^bR63*CZF~YjMffCz-^JN!S4-G%{F~PPL45Y5Zs<@4xQ5B^h4r#AOC#>jp?8M z={_ql>^os~OgZ8Mpy0ZnTSEJs*#l==60r@U}9X1bibnnc~x-mc`x)G1!6FiAn=iV5qlEc$| z*P5I{04g72!8E*-emT}cIeBrW?K{QC&q!rx-sWWca+zIDJX%`Z_mvRZ44>n&aBP$5 z;VY<3_2Sz%l6N$q{UWt;kKjJEc1mh~`*!krW`S9-9Sapr7A~vzKVG-R6vG&muho-A zeT(-S#l_FoN^R+I#8G%M&X!v~gV4fgh;eO^BN3VWHSE|ydBLZt8Vxcb+SBhIF&mO75(Bt;#2lMee=TSsZ zJ$*{PHe@86LlDI1ye?&#^hnF5PO%0UY-Wx7y4<(xZD-$4WG8G?W`0R;ZT1;Tt0yh2 zr`T=Wrh>TLJjyp1en<9U1*HgLlt2Bl4YxgF#$t?89je>wUoO z+9d+8q+Ma7YPo;_Y`Vo$%HLQ{#yoyB-ytzPFu8YUO_2vy-OKFOHIWJ_oyXGKC%R9m z+}-zMk3Wj)o|5odoBn%bN%_yC)p?le+nd~}2zIuQ8ro`Dzbm5E1CQbko5PW&TP6n= zuSYG7ujq8MMjsA8mgN4a>e;$3dm}?m2u^G|m*-A3-7&}JP6wk`EXOBu*_}K8o9zExvIyvClsnBg3CyNe8&4C4@le-&DF@{mucw#?%Uw z*7LDXHO_m$6Fw7H?%;aDf5n%cvKK0NjO05x)vKWGlJB9^7w;Tkmh^q;38ocl6hJ~6 z8H-llSRjag6tB`BTqoJ*_YQ*iePn@l#GHY#cj0wuy6rl=?d723CR;b!m>4xrxs=cb1@Iz5M;bn_~`c>HsdUYI<~j;ZNPgqZ{e_~*$8v?7hV6o5L)JQQOj>M z%C)=9!MVo|UM_a*^n(Ix4PxJL3fbE)kzrrBbJ;05WJ^RXRF^IaW=AH_WZI3M&qfb< zAEY!`vPa%%3jE$HpI5*v#^k2PH{tcg^DDOAwpsB&Q_|@Tnbkcacn-?ts!AkvNmpEC z&Y~|l1=<r$+$9;~bN?>#e;%@q z^3rhLktfjn{lU3dR~nNJb-yJ?Y@|bVdJW>lFS_k$Pp;s%osmFqenYAIy@L~QzdcwK z_XcM&KO{{XOdndwX#J#Ad4syg);&{uf*xF~(jm%;9IVc=uagP2wHjrH^~}IqG+XCA zPJDYh_A{cVK4D)YtkP6PiMvfN-94rUzX0x*iZ84T$RL|`0HJxJmKiY~h|XQf^Et&- za?71Ebn`64OYgr*DheUx)wfd=meXNB&lNU!EGa~2;W(S@V4P+e&9D88-i2(bG2%LK zs$Z_5@%zs>X?%htcegK`7GC48$Y0L_gV&{J$#2VqBZgs{H4!ie!gU(hY;t6mItLN&qzY+BLvcxC1FS_Sp2pOKiFR z`LBh|cV>`cgNt3^_ZA<91f9XJaq|v75`|Y=;Wk~yD)|ms+kUaOI|8^k%9jcptfQ_t zpU+HqZDjC^0C1^wIF{Z0z%R-LPVWF9Vh_{+-Fq>m(uLIB5+F}Lre6*nWElNruY!uK z5Y%$!C~|=FfmdVhiHDo}>Bwpq4VW`$X7X~1v%i}^-tVFinzn8wv1FOZ<#A@1HkY?H z^k#mI--e!+mx4ntJ%*DR(KjC3jM?G8%e?$~-1?;!f60aJ*GmOXW zsc}B%%Zk&YO%3N?G-37$dLl}hU2Uls$R^IICeP@dEN)=a%W)WRZ``;Fe_ zJ<*TsGaUeZ_-K#Ktv`aCv-B!X6-(dmZ9s4F6Oldx@z~LJ(akAaicd_{>en4^BTrc~ z7+f33Mm<^QJDk+Z4P>PRT5_^(+&iatDQ|YNRh4Va&HTe1zNdo<8WJhcNXvoNRRZXKv|``qBK{vP}I| zv-Xn>@VP$3fqg3(v`yH1XrCl42&IFRLBS7N!Bht=a!5{m4e(~v0DXM{^6?D0reLYNXGvgzY z_Bp~?PkWHgNg6}wk;{*}jicM;I$x&OscOsX>zJ)wxh7G&iBY8E3_6uizAWf6OUt3F!8!xo zW8rswrOG_c83}WdFgVb8W3G&>+&Oz%>+6$Z@HDBRlg_|zce6)ap7QRyZuyyYBM#Er zg>&5Cc1D_SG76N z+p@`Q`K>~}wR{Ds5%zm2@+AFoJu2O|qQJgcdJ0uS#$CFl)1yh6Psw#reFGZp3PsMP zJy*Qyg!aitfLj=Q!Q_KyG1wB!QC)1;#8pR5zngjM!K-m#_FVhi9HmDurErnlB>_-K?dG=*O{n)sqpBwes3TWKfz;g7)tQ#i z8GGPNHS|1iO`{c9=U#e}tnX-%eZCcM1z&x)$cXP9%mNg!%^}~jB!S5qgaWsC8FFnL z&%)?3;jQ@mOL6at#e0_9XCkHOq6=eIXU0{5S;!+vE=S<;V?uINWRv{N!92Jejc3UU zkybSc?`i5~Sy~8S;Js(u&4*gt1&eQ2JL+dJZwJmzj>~kuH^R3^4UU~BufC$mOWga# z_6<55#>u6A3V4JrN-8YXc}y&Z=}1L_T{d$wwtljWb^OWe;D@8zpjFIstzA(L9kZt4 zp(|1e341#ctc6xC;+JXRwAJTxd0lceEn+aDb*!1U4dF>Az4SY(d|Dbg1ZwN884(@Q z_wq%{LHxAyJ#!&S4vx>E2nt_%dYtvZDr8jB`Mva+X32=U zAScsZpYA_(fVdN-(D=*j-;QWUDbD&RkM#m zyiIVmo+|ft0+nz&SCA=lP>DDmIXb(Ku!Y%H=Xdf5Wbz+jSCS;zZo{s`Q6!ff#QPt~ zp_hMIj^5ZE@w>CKG=P9a0le93moa+w?$7HVjB*V_)8MAb6v@>GG-uAH`S&In!FAxv zBRh|_Nh}_K-&9jv5#Z^oa{=cq^gp5A{?8SxS=c3yDEm^8D^+XXhRp6qFzW% zXUW*eu4aLxkTHs-4SWCMAiNs4sP`$)Gdd-BL@NAy@&C$uA6cbL;05y2o=4yqz_E0> zfEM)jim*>%)t5WA_Zp)Cp^$!A19O+o;N3CXcI9$(l{ZwM(IdH7ocr*~&OLigI-2TjB9rH=BkeFGI+Y`XdX;v*4Ux9bZdSbY9nc|=R?%d|hUl32Q9w5dv zOg~SHoO6^<5$T~t1tQT1yu3?(&+pnwiv7Ew#Erx=GLul8_IdQe7yWO2l~+=&ktu^< zud%PA-*AJ_MtaQ1?fo)zk=CSE=w2kp74T`gW7YQ-ZJvEPw=4lOR9WA5c|@bAx9aGn zQzjZ5k68ZFOz7xs&5@eixp-3`M?t5Z<7eN7yc z(E~f*jk`8(kf0Q?5dmoCK8X(aS*?6ot_fFUZdD}2u`nwTu!k@upQ!`PiD=&ln1?r- zbRkYa9+Iht{Shu-S8#o~cs&gd_2$l*$`F3FH(_rzV7E>tLr70MdOcy8Pab*zA`7@4 zO+!)jIyL<^25Sv&$6lAulWWW`e2o-w9^Ez4J-*=YlgX^C><%+P;=ZM^M%j@TyFqqk z?PM)f5_87=-sEaCje!c7=Sj)K`=(u`fBh7iL=m%Aw@Wt=o3zG6neMbZ?Fu6e<(D!d z1A8pg^NC5s?|qJM47;2L1So05xcMsAp@;e*uckb=`xTNHr6j0HF+d5ck_8!L>5R42oQFH1rS9PF&A zxWl$2Sex~T8w`^PufUfxp3>dy`j{)Ob3^^&?qI+7&cDY=f{1t#mR2rGumzcFL@G^# zN3rgV5=%jES)DhJHF~%cH;-~_!!KTOH3;rm^xE8cD{mnSfE=0mjzYzQd6j5vU+=?X zhfa$#(G3e6-8G^Rkf^zV*LeSVjDu;M&c1aiZ?*;rb6IEB4jLR+guJT}NI81SjOVCfcP9F$Ffd&H?wgY!J^#u9~$|KH8gW=Z(8sp2L z!Wg_Bmz0n8eYhVGZf}0`;8BvC<7(0mm&Hj_{ENkb5+A|Gd!>}?J{4CZ%x}J)j-gh_g`H0N^rT}3kxPcM#X&xvagYYtK6tNoI30R-=l8#tTH%V5(&2V^2~Ra&<*0J)OV@ivrmv_Gfr&az7M4yBSN)2N%w2W zKi?rF-fq6q3wZbQ48M8sO5Rr~`#dRqm)_0>6|y33>k2ZzWLfI1n>1RG^1U-R>cS>| zAWG0dhfzYO8Ky4ea-9&_2Tu4<^XwZssf1Hb{Z!+Zq3uU@_-}6Se_qSxMUCZ6=7Rx1 zc~5m;@2VLa@~d}Z4l0X=wsfp;3s6QCKb^^dT|1J(eWgiyw8JI34{6-afbYHJ1>CJ{ zuu8ZnwkdS%B+hx;9iEyy>{f3PXvFoeBk~TGCkD2rn2PPeCz|MC?jucIs(g6p@i-BI zr8b@_#n|tel%~g_yQ6zZsUKTf40t*#$G*cjdwS`_E}wGoNUc*}py0KbkyU6ointoF zdfMX@YX@2#u4L`A>kw4AHg93yCp$vtqa}QANdqqP0=wB`G$wWZ^O%oZk?oStsPiluCLAeP8{hYa8B|@tpJ#n8jBHDZ1>Jrf0-kS^!GLVkPn4yn1F z8vP<+*%5q;%TJwGN4!e(!Kj_hy66vBPbZ3aV*yP*N;~3u4WiS(`$@x4IzTrHYt(fQ z2A}bnW8MJ|O#E@hli>DcA7P_PF^_ZgVA0b7i?eA@BeF7{x{-VdZr_qy#94MGZs?Kb zW7T9rGI=@dvR2|r?~%$^FUuzGy;T19d)WB%f|ko~SnF=nccQgvb8Y)>2P_v-hrqQg zL1n^AaqkJ5&9;aFh>n<^DFffWnrPnItlyV8X!!O?OAC~Ip=<{~Ni-AOH!TM7Yuf>p zIC~$x_;O~O64y=%aE6UFN6hDVCvS<$#Y1xX*c|6$YBl%kkqj6e0lcS?{bVs&SnjDJ z#T!NAdrhUq#Hoxf)Yn;Cb^;I5**SjG=IE{9(O4~M%@$Z|F1BmSCuAvvUYX1F_Jz9| zJu$WT$|XDnVG()OymN5m*Zwm$_evT(FuoD!WGLYupp3}a>JipBBm2+(dJC*a{cwd& z4=X&)P?!z(1W?3LIrH-Xj|)5x;|-AW^7HQM+Fg zr|kjPhsML2e&)^k6TkY_0G8tjL?gB5sF__7&2B!CReCeVCloEO?VDr1TUGri+Rpi0 zu@v1hJ;~biDt=dqa_0V=<|0f%(uo7^DR52EN2jjR_V;Gd&ZMPn0MY3?!!7qIOn+tK zbLX1LX5#B=Y|EiC{YWU?@Kc2@FmMaxAW#Fc;}ivII2Q5TzVbmd)|V4J49%LzlZ1VS z@)1!B#RYMY@H0(Fwzkaul;ymwZEf{s4zAs~A#!&BP>*BN?}68&D-RRygrC@>AnC3FEVgJ12~FPYmgjz`8xoC_NTW zM;}%`j|7~|)*RDbIn_sws70-oy&`OaJ=p!$Zs)l^YO%EI9G4Dvd&mU?iS_r_ zfP0F+C;E4f{%Kui@aI)63(IZBdS8COZvTdvQxkF>)+%E3E2m`>GO9H(?x$J({1IX=6Mq z{U1R@-x877Jh`vv@f1}HA>Cc&=wrR;kEb~uWmSLT9&wCox34hPt3MKS?eGLy=z+{+ zzeUq1Nl0VF8}T*D9X-1;gQALk&W!5Bu{V%Z6>`4NoQya)3kPu zeC|LWLDLlZ@}{$4RPiMCaT|UtcWJ9Oy7i10|FaR(8c3|368esGbX>R`4@$!$Vi{SO zQ>V#KJoaz%s0|$=cmf3PVh6y9N&Wnb;lKyYl){17C!FgPpU|Z-;D2} z3IJqu6x>I)yzWv5`NNC-()bEij%GHJlwJ$jdEyw=kvRev!%x5*U8#P?#1SFG}mI|^y3Nd7h&;c zAkZ-_z%TVHV_pHV-kE#LF58i}tN{2@CeG}4y%(M{SM=r)2ZIQHRPPiNHD7#Fqa6Mvw<@QCC2MKbqpCHRznh=J= z&Ih8l=*)T3zW^A1VeqXrLcM7IIZ(xZ3}79mPZ`-wvyZXtX9_ljpCUYTQX>0YmgYQn z4#Nf`tOszAfh6@@fbYC&*ZRi7#f2^@9B4e)`Suqve9uSY#(*J^%)zVlU3gr}M<0?$ z?8(X1!q~XTJ3T!t%X90gO-9ANj^6^1)$NPqpan-f^w|61Y{zmnc?ftQzOJV;lJsY= zpW)v|#BK33C$YzTYI`=X@yc6N}NaS*$t@Z2dmwd`a-LEZS{}c9k_0GC>-S}ebKa;Ub87I+^@D=zN?k@Wt{m01m z+=aK*%P8%Mo)eEISzc%|rRp<7>a8GI&^R0M@u-rkSZP-~szawO+*c#CR%Sgx*FeW}h~ z((YIrYp2bVm_42rfG;E`&K?_NHDdX?&EFJLd?DDR4qFZY(DjPyy*>pra`%mYeW%Yd z1w9p)@oRbsh9Or2n!vB2!{G~jZont(0V&efMhxj3f5^phD8J|C8Mmn=?Mh~k%&)oX zF__SpitK@{v+&5shW5ODzXHEk!w2r0Be=om-e!?Ed?(dU#RyGB+;#G+KIDkxTX3@x zUF#Z1cMP7(8uxNF^1(DabH#y?$%!^%?TVQl=@9S&Q4>XlEKhr-?+Js@?@e&0Wk}dc zC%U-5pQ(3{tSlvU6PEbk7eDJ)=HfjVDKK(Xv!BZO-ZL)N2-PE_OB-|eFm$%-y`4;v zkmzRD#kVMrpdCR>+isdUJm*niQ8I_BD>sWiWBasv3I-RyAlvV#6E#7$vyNNYNWBuM zb~#4xHrnz@Ot?JvsY!FcuU_^W$x51O=B$-w*7!0-*#o!E^d*=9j{Nib*67dc+s~4J zW#?@p9D2(c^J=byk4YIR-A5$8)UdhR&|A|U6L%h_S2 zaa6u1rG38SCRzjRwy%H7yh-&u;6o&UTZe3_DWWs&J4;eu)?lTGF9uaHZWJ({GrNfz z2)vuDPo~>iL;C4ca`qxmJ-Is*(_5e4_)PbOwDgOr>$rwAeq-;ESDJ?Bb1B3cjxPp| zxro00)x&`Cw0uzT4!o*GbvUOclsKvZ89(QasblV@#qD@OIR#c7d+RnyqJR`0!^CpW zGjNcADD>%?$GM~vp4PqpY|>l?p4;Co`w0PPW$sHC4>@}1V`h7_LGzdjNUtBZol;vS z#hoS>`PTHEr!RrtKDu_~f{}(JoEsB1LI7^yHodx@y$E7yC5nEJ^VRG#i+Ax0<+T*_ zMY`-~_P1B>yfx&VS7nZ97GrQYt?=Q=W3af@_UHqKByg1-epu*}*RJfYG3VL$5BWw* zx8CAa1s?wx-s9-NUZ6+6fTkXi0}!x%_;Sls+b1j#Z2bmk5q3w#U2bK~gz@{-t=nNV zk#$JW&Q87xaXdlS^+p$rnNDuE;bR;V++K)q)i-i*9nAVmmNiA))`nhioU1$1d(X`? zL(7~rOGts9ynd?6vr=>%ysD{ld*qAQ5 zA;NGRsdLTrA@-sf!fC4S8sh4kG>X&L4V1oA?{ zi$B{+xCa?8GC1`3+xTVSCqBcWBNZOus{QH~tSK*1A6tC2h(y1D7m=O^y?5?t1U=E5 zp>M)Mua@hp?p7-|ayGuJna6V1QN zwj-BQZ~)rl`u0ll;m$jS*`@mRJD*40f$F;$Dfe^d75M|q7LJUQ1zJLAk6&h{RifT@ zf-3Fz{qf8kft$BA$p&<1J7HOiQyB^6_?H|AWLiDn!B2e^jpc;tl%BIu-&3uXdl8OE zQ_|!)^SFYt&?P<++We*eDS7QM-GE%_iK+l!7C@@Ex#~N|@ovvk8kGf41o+Jwq^Ir^ zn=TsMfBM&BH-p9}c|lsDVb9GqvvsNu6Qma&FCl1~pZh)seF^y>}wYTn~-U!5VL zYXUx19;)Mu4TaKY?U;A=6It9%`3Qys^Yu2URuoM5-ez9Y$nkq;*FjF%v!?@% zs}GMu!{U@3-B|6pMR%7_1aU)ithojEaMI@0NP5!rVpy+Sy*R-qSRT>q04(*Fbm~~>;aQBnw})(u?``gjcwEkUx~{_iL(TV zeKQYM+4L>;0~PhS;#={F*Ww!AJuO0!8OtI$eQhG2^2kSUpyMc4;3=SJ341k6&sLi0 zVxYZ+ewx3>oK5@X!7lqy9Rl^Y6>XBQf3FI}7Bg39q77mR>t;xOS!DuSC(863G+Z7T$(t&*P$10FwxP(Kt9@B z2Okv9nQq|NQ%O1cw5315*+y6cy{F7T@k5|ZiPP8yo?lcU34<(d3k2dd5gaVNW)=Dc z85ZD(>pz|L|eb-h>@zd#z!I&)#gY<&D22y{5`H9}Y1=%<_r z44BI;9cgM+MTX-(O$rDPc~4(>tnm8o*~`uT*|3yxQ#t28&CjBT;tf+gLauK3Sr1)) z-149OJQHW$CW1$+uix9`9<)QX?!K$S3b?eMSL@WRV}{>2xgWzT#SStj)EIUKx_5ZDA5C>Ht%F)=!4`#Bz9b1kr@pwIZa z*W>)9mUTl0zvZ^Zpm)DF4zg`-p(mhDbln7eJXtfnxFwqg9Tn}A8XTGy6Pmku92}RB zUAEnnIaVP2Y0i9oA`b*i?cJ%EyOJ#<>dT^NXimtuEob=gK`{*AKB>C#@+w z;C72?c&d9T_W_M$mt-8cV2;=N_$eq(Pqg{O|wq8rmBc4z<+Hv7Fi(0*9qaltG&ATLspsW(vB(vK`OXV?^ zqBr+(S&7bmmnW{bR%c%Q`kIa2PT*SCS6?M`V;bE~pA^T(jUS^Q;p1UaqmN)(z5)&d z<~2ugksg9)R@RvU?clF}a?(Vjo+3jWW$vckOKkFXKpCTxe03(0$TC=OOvNPf;MfqD5e#81=l z?9UZ=Jg&_f0XzydsdJ8(zlFH%_k4*vvND{J^f;gF>VjQpsP>Gd3q|?0=!}0h#o(Kd zw0)kzxs*auwd}*KXv3w%d3@L5^XICFfIEDM!R4TXRPyDt8#KPO zk5xA9_u?&|L?&xUTEiMc9NLQ((cjZ}EPC5fzm;~cw&W~jJ@TFkMI5I6PyhQ1%-Xe& zQBT6mSb$NDt(=>OzgXLQm@BCXtZPn)6I*{TEn^DKzFLea6$KRth^Rj7!JgB2h2$0V z@q7yBW~)6?Ng<4?hhz_b7Ik&))=B4Mq2Lz+Ve32!@dOTIK*5ElF!P>;yf4AZNC zDM2XaDQ}#-tvyQQTu|FHEhh;5bT&sX_$%8B9ip}?GWj!oy2zM#siePDgc&*VonsfA z7>v`JS0C+Fb6^L;b#G?UrM`82>lAcBRl?N4_yq-bJ)(nfM+ov=S`V6rq}(> z@WGwbOu1;49&wnujEL^c=;t!9$4@&fnc0NwlRQHH8NEN!2`WQ#8(T6?KAH?G00dAgh^yE@N- z5z@JIMau3Wp>zhb8WJGQ8eLdg!q4snCi1+pM9raAL;(IcATD20r?+8ixTzL0@H$m)ku zdhShFinHNtG1~$D7TMENZ?0poQMm4P$cs$=0wvE18n7Mz4$t*%oql|n z08*Yv^Gw#Ga;=kNx!JV*u6JEimZ?5cO;C4B(0UKt4%)*L_Zr7P4}VO`?@6LB(?po2 z;PG}jn=oe&w`+n{Y;iG!w=ZSp1bg51bYjY;Zt1a`u1Qf^u?`7*RtiC)i?COorj`qtb zCz&h6QTYV?^)fXK0($(Je3bp|vS=3_xsrqjI^j-?pDTu|dpZHk9LXba!cubK;>sy2L zhAk~_>ety^clCPdL@3{hH980lBda&(Sg{b^07F226}k#FG-hu+Y3y(NW$(TTngQpU zRq*kxj5SU4Jw%b=3Um28 zYq<|_Uv>YEGxG|eL%zE37D86WcMhes9C89I(Lb-4S#bgyp-PHa3@yI#h@5g55FG;bFyD+@c$K#EGryQm8g|Q?r?E{`ZaD}O_u&+Q<*L@`_$4I{| zFv`s}z6k#J;m`T|!uRcuM3OS0&u*WHk@&k&uQURzZgiqyGMWpZb(}aH<232o)oxny z*jf=m-4oGedylC-osS~?;E^9EICy6dF>5@g^x4-&t-B~Zs2ae|)LY-dOSAcMV5eSg zx%$Mpk=M+~cM|`&8ccfC$0Y32fWjdIBOWSYZs!XlgL>0ru+#}o$2jSI9rvlei%4eV z<2@eLZ>!o3e8PARAnei2x7Ov>m1CRwmCKP3uj9wYBx4@ExqVg{x|Z%e*!L`)dNAwf zVWFzC*(t_ZZ`BfQj9Bc?N_`xB|@)4rOxt|do+7}x6u(uvpZR*6Ow``U+q?LpGi>zlcEWKDjPqIBh z@-?i~+bOVoUDYqJkk#@l|8Oh21D8Lm42k;sFqm5@0M5NYpiioninB;VbX%T2EwFvH zza#C7b;|+8sh&IMzE4*>jd-ddf;_~9x)-{c)x8*dv zEyO6UU!0_zY^p`WiP-Azb@y!LmHZw}3hfnaz4wWbLI0xJ9ICM7HqDoZHE^0bOd$`z zy+4-$0*|75HEznm3)3ra34aH(?NC7R6Xq(~T5<4Kq$GJuS81*%!efYf{g#m9@T0~y z`$bC3D4o+f!tZa+mC6CQ@<^u=BJ}cc1Iwhn_E;|ZgIJzaB0Mbh{+_e=65}f?h7X$K zj6n(q#os01&#PbC*_`eeipn9*3+#RRXis9^H8mnEccVKzrn^3ClW@p}&bdT#9iKp2 z3+MZ}cYrmK z00MWsvBq*}6Yi6)R95oY^>QWQay4o>I|*AJ7ZtPU~jw_jDFnQIr82|t`pxrcE-35rH+MB7i*rv16ZU}EB;R%aDqXO8KWA(sGkF>gqP$q zxX{@w_})u4@uR5Pg5ivfA;MMq`y(0S2H6c{%VANH`1CnzJW!0+iz&g9|!Bkg@BswzZ%+W7i zHd>ak&kdg0a07?wG8Met-7Z`{hKgIU(^ z;qz7y;_N(%UDPob6f1=80%4n9coNDCLA|B)ePjgA(A_@q_Q3_z(+!vR<{-rJuOU_@ zoho+tb3qrra7~@*p7D8hx#|( z`XOC1UnB&VobcSISTKlncRy!Fe5Bq%Hy!p1QF3b9(faI>w$smEBKc~R- zne!lanzkQjILGZN@$V)bjvW+E(SYLucmigFkT3cH0rhJFh;O791wp^g-C@#WPlEts zl%}MitvU>T&vsexny;Zx8$I+nz3cGJRU=dqueL^~&GiV)aMuWlV*;R46f1<(LfHqf$%52hvwYUM(!- zr-SXSH8^yv62Xo>5Xpe{4hZ>NlMdnF>csF)Xx+jndLPmCc^y9$i^InQm7@H!Q{>LX z%F>gP`}vVb9GZP~*5@I4x#(TS$JO~FEap05;^T6sZdsCh=GEX^gOA7_l1xnNle2)A z2%vb3#yvUjFn)P?^c|DjZMG91%D$!29=hPL->D0Jzrw%DKs`pT{%EB#6#{yJR?JS( zm$@oHYJOIY-%l$EiB+rhGOdPF@+TF+atD%*j9p!vS0MXYrt zm(SiOA6Z7^3MRTZua&#COoP(l5R&VwVr-@C|?Vz2`kmSs=9X-#j32L z=BZ|K^PTD4`Ojn~^57ge*03z$ck9hV86QV}wCmD$jc~=1XAb76jhG^5nL1o|C(osT zo1vUuaW>hGJ{^JpP88yS?Z<-%)fJK@7w?6m@pK8}Y_(__oFAr^yc)b+pdn zU_{?o)he~Ox^DFx*UzKb;_ZSh1d_n|OoLc4cj&xIWNo7R_@FgL_CD}Aw>NvF6{)%- z{T_(9y4!PM-h`PQ82_Sai2<3vMl0p6C-Xy`(T)xh5q1E5YU6QoiF=v}NiKA%s`{H@ z2Vn@S$jcMe>k*qzLB4u~b{q1G+`nwSZ?08)r6X=E!U;S;f;40sL<^5X=*x%d15vg>)p7a~4-Y6Ow z{y-3})cO1-xW^723`0-E`=tBo@wAag#_Y)jP%od)vY~Vie(pm8O7Rf$2}3s9&r|5Q1)J!Kx48dY8@c4EtQ5lIvhlJc z8NFWXoWQ+FNgJY*F{L6fdD%-*FnYVnBFH| z95oIZyKNK>Zd~+>Q+A$cNaITw0q}G+>07tA=H+>RK|aHqsc8n;h=oMIegE- zN$4K?HoGrvlYyBPSnE?V>=TOL_o=%H2uC0z@Eiv5Q)Rg>55-cF5oI+7&Z(zF!LLn4 zynC^jnnapw#q6JofnysIho2ZX_r5sITJ%=qd~?E(7H;h~pP1?~x!t{@6#aFM@Ubp0 zck(ELv#h#vR0{Hu#7OIz7egs0DPlsyA2E|ozsKM=j|9P$rE|8lnD_QPC44||nZOQR zne6etQR68Oq32e~{dqKo+2x~8xWBZXz;UVFA1dYL?IIz@U@xMZaFpK#n#y7E0ig4$ z3IG+$Xxv%2QD^Uvd+mHo@MsKs^A)Jf@{=#;OdL4)zQn5ubs+W!>ia&(OgdZvk>^-Lz{0Jp+>;NjMnR_OJh`c z>-#j-%5=x|kq*zhPv%F^FDqaFrXu9Z0i%+BV(yh?BslerXWLUOPiSt`XBDB3kG>90 z@rmARn$>3b4IS~bfw%YaD(FFv3D~{fClG~hy3O20j#B{2cgT()i#{5COAQHk!XH{b zyr=ibH?ds4CbocAS@v_4+#FL8MN~WWXp;%FZd_m;i89p3y6W7q?=rsD zs~VXM@FpB!MIgZ3QayP|0OH6E9Doha8*Oc}Cdm@B-`LnQSNi?$m)b1*B`Lq{fm9D~ zXo?J!*|VdgI%wPS{&U9X3Z#U_Z8I^dlku?V0$Z!0$zVk5)n)+(V89#Sx6(Ttx$~XH zoz>I+>Su`Fig30I1Q#7m4w;KYG#_(?R1J6vmqrSGNu-P_p%JoQW%b%LFOtm~i^VQY zd0asmNTpZcg0#5<+o(_J*ne=4{f#yfDA1bakyI z*ZRr~4o{rnZy*nx&*jC*4fWfQ-GO=;O5;G|IhYI^Z12AM6hX)TN7N2==iLQ{_bLmP zrV->i5_%A&6~>3U6=hk(hIAJq0G&I_IF$W2^Ri0f8PJ3@*5Fp!DsW z&Ep+);8Vn}?zrWomrKV$h`&-Bs<=;2nd~5#P0GN!iXT=wVQ%@&#h*$Nn@6FlY@m9J zrqRyUQHwb?c=E)#k=rJ&3NABWfihVpv2q&ZM-~RvuYQ^8hs1yfxF7c5Rwv9_#l1W) zlIIAgJyqXBGMjV`+8iJHre0&)V-=1f(`<~4!!7nrp;K;gUO#E32$i+kPaZ+E^5zp3 zVeG~0w&$s02cpv$9erOpwms!-timU}ykK&Od_!FFti0SFPIcKRlFdpM4mm#QxMWqr zg6BI-0eXb_;bNrYp(Y%;=45dKFFkdq$(KEz*o*VIJMEr+B#F*cn$A(IbBHbJm09?v8BfnFUunF2 z9sn{wdA#w`2vyBzoqYtaJ_(Qc4FuLB!t_MeU?86oLOjVX2_$wjD2$(MZM{Z0k|ILv zM{m`XB$~7wvG2UXYM|5d*RP*J!xXTjcNwclFZ!e%lGB3cWv&}V9iPdIWB51c&HFB1 z)eympjrxQ(7JsUlT%w*}sL7*xnw4JSl;({0t_F^UF)~}>{f6=K8qVP5+ybX7D(l&o ztY=(GV=_QFJO3;OPJVDAE}_3i9o4OTE`2U^PgzR}S75&NcnGqNDsc1PEW*j=3&%etsH9IkaGk$Y`-yR zae4uWUDHP7bEG=q;kOx{BG|JrdvC!O$XiKRx5U3mt=H)~{95dS)(!+*z2jIX?`;dg zimfMKs;=4)!I;Q31%H*C&bN!WTFX0LRk<3c^2LnC^h>pHJqi|S)r>VRw!IMGm&)h7 zSBd04Qxszwd9O6Iv7FM48!Sk~A>5gEq8}O?Xc7l!4kKOSoIYYJ^^z0Mklhm^VZj$) zTH2?|#&tGAfaZo;R)9~3#g+GT(zZIL`%WFHCrUNdQ;aNjl5{wz_%i(#CLRzI1T> z>HG&tI`=kQq@xrQyI5b8s9UE;@FD4vScwotud`m6BN%~ChC@apE#9fU#@PGTXCNr;~FPEQegA0EJ{5VVK$~{ zfz&GRclvEoPN$nCI+rz{F%P!e^K=;n^%K^OZ z&NQ$^eMb=qg^{;_?q%+SN&OA5QPqyr(&af2@5YVmQe^*K4F2}vjcT}lztbS;&h9TG z)r<2d6r>aLh=rX78Q^B8F@pE6b>_Z~=XrVkrcCc)tvaI8za6Rd(wqGlUFlqsq4L;Gptt&>x@ov_p27B5yaEY4fYDY zUy-XX8A)<)khBbP zx~SW<9*eE!Ue*k+EeV3rx4?X%N=XNn53<26%er_(znN-t2q84Gv-1J5!^Gsq_#}My&K*nc*D|Bz5%0g z*daS+_$m*cmu2!h*=j;@d2!$boV{@yeamdm)dcZPVa zHavWqc*OV|k5~Tb^m}^&vIv)T9#6pzvFNdv96z_T{myacw9f?3Ir!dH%2}wG0S~O; z?ysO_3fPne^5OEM<+pp z77*--34vGh?o|ScM_#bUGYTIfrTCynrhT1Q8IbQuOGz1bU%9eLNT1;6>rd4qBpJzzsWb620=9hIV&gLb4O zeGz)|y~~D%N*Xe|+ww`umZ%Q%>motBbqqceAoTK6OA$g{$3(AGp75-5^`y6U@45Cl zsNh-ab~+JAx!)J~Q82nG4w<)?yF-@(g})EB8d(n9_QY*m$A`0IC}Exk20;c2N3E75 z3}W=L&^NyvcB5Y`8(yo_P`u6eiEya`^a~*&_#Ld{?i$hQ%#g^(H_=BA`-2r-VMOy9 zD=g+grk$8?3OL?SEM)+se-#9H2$8lkl6gF(+1sqwpCUL{Nd?@cIT+w+k>kPhbF*c{ zZ~Kecw`7mk6GR<3-&XgG1GbUBLdHT^(ni*cBo=C`k>4l+yRrS4tc$K{4_rjxL=g~J zpCvj@$Dd{nt@(~`w8!U>u%;hdVcF1OYwW4H=Arj!;5x7r_^*ldsdESveD78k@Pv7f z3V*HTQxZvs0WP~ah2fL^Tw-l;jMxddUb?n1(@P8^nrS)v4S?`?&Uc6YPoImA@o38dJwHkAEdsJ1KV$Yai^WqM_pxkE`oyJ_;$!`{T64B)pof8 z2tFp7uT|TdZk#K*$|yJdCZ$--VCi-+EvL&P*)8CS6gw$@_7fQWxq%FLsrNEHX!I5{ zRWCj^EdEHP@@ejufV1#BL8c`MQ%L@5J_$t)_~M~0C6>XQv52(lqhl$igIy2CKGU|M z_0wU$c{H)tW@y?dIId5@&(DNiXOtp3P1Zu*-+HSmxNu(XWuT{#!+;1Rc530l#pr#Q z@{~%G9z@cD`1Wfr7>-b*TEe5&i~iW0gpQ94!I5vX>t5IUeWjoCZK9nEV8;|~lr-gK ztu;Sc^->}pkPsg@+fN1EiQ&dI#~b@3)>QN>ncubo;!Zg~20@m0WeN$V0!KWE>a!7I zjG8MC8pDWgK$Duc)C+tC)oZ=!^iNC3tB#%uO%~_gC$G_A>uZQ+f=9^0`ZBpgThL0} zcuEv4WY1#7Jse#L5rEJF%+ff=((*U+0}qZeIvqG?#Q?ehTT~ly%wk;5k;8gEcY#aE zI`|D*#nYh~oQDTVF28yMUMGU?sp#uxfQ{_|`IXSbV7{OK6AmLt^>AG^cp{B^D5}i) z>_Mf*_qVxfo*nXU#4GU_z&+;$CSQDI@yRkNZU>NFGb9b-bvURDJ-!t$TyiDwaeVEm zzI**rs_Y{Zn-lkCgBhb2I{Y2PJyMM{b??w{6?nw&o3e6;NRw~f^#{_okl=o2c5KJe zubqiJCv}EdkxY4O8&%)iO5}@YuYbooFW3@=$9EohO=g9V&cU{=gyT+nAs(*Ho+_3F z6DCm-ES~{Hxld;gjONkz-)X?G3GsKB;Q&o}gMEj{;rh+Cv)WE@)dYwty2xlARd9ZG zQ3pg0+ylsq$#g-UfYK49sQGAj*kPl_#v* zqpJtbF;sn}orZcDf_ninGblzl^zOl_SI&~}0JLBKwu+WxgC$;E*A_g0;d0v?i8E-2 zy@8`P&|cD$YNpCIiTcdYzajD2=gyK{Sk}6T4p2pW_}djm?|LQo$hq0OWa)W&?P{KL zwTka1Ly}{EAG<)3^#!J0xn1oP_$2!}6EAOA^2Ft+*L~mIVHDE1B({ zbP}lLfv`OO;N}SI=fR7$XeO>hMXzw11=$_(UBOxF5DlWM3IX^MZQ{wHQ)HYK#x$TlX7?a-Vm-EF!xyT?f0+5!FOS^ z1<$NaIzONi-zVfP#r?{Qp0X!BTQB?Rgn?sMcAxO+o-X0SXrG&jR$k7o;!wPJKqQZ(Tl0 zzZ;d+Bi z?!w9s$Fq}gazg+HEtkxL5=HN^Z*Hwb5CGZTyXVZg_I`apE)<``*nDz~B>Ee>GdFN6 z;Ik{H)1yPTwKNy?<*@J6AKrP;-~L%TwH`Jzbf@jCaSj>$x}T4(G|@gCnld2Sc*bM= zIMid%xiKu7rqGy@Xc(?s*ww7jVkbgO-t`EwUGpNl5_!xlFAQB9hVz;9?w6S?-*Q7@5d{uXf21=Yd>Ld99^Rb1nI~|BZz==IwC=0NtH@^-&F4E^%20Hk{8G-_ zR*OFV3a(Gm>UYXOXueU8A$CPDz3_gMlOU8!5Eq+wrT8q4`3p;E{YE<^MqeB~#_Vqy zv9sLc-+6I05IBkDsl$#>&6ga6uP@Z~9N!@YDPW7!tID>o;+nyL$xSmTRI-(uv*Hht zpcqTO4e8bQ{%t4B4UTptUetO^52NDq-fGX}OX1wKHU6G*MP9gbMHbIy+98IWrxfC? zjA8}#C&2m|Hdb9l=N4ijpMy0|!3DLV8oa!`{k?p;>>{)-{rOT5806FqI{uyUZco{( zOK}hJXQ1)r5Zks34rhZfKljs

%y|=lM+9bx#X7Na5GWAL^|x9M}^ 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 literal 0 HcmV?d00001 diff --git a/cryptography/sha3/xor.go b/cryptography/sha3/xor.go new file mode 100644 index 0000000..079b650 --- /dev/null +++ b/cryptography/sha3/xor.go @@ -0,0 +1,23 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !amd64,!386,!ppc64le appengine + +package sha3 + +// A storageBuf is an aligned array of maxRate bytes. +type storageBuf [maxRate]byte + +func (b *storageBuf) asBytes() *[maxRate]byte { + return (*[maxRate]byte)(b) +} + +var ( + xorIn = xorInGeneric + copyOut = copyOutGeneric + xorInUnaligned = xorInGeneric + copyOutUnaligned = copyOutGeneric +) + +const xorImplementationUnaligned = "generic" diff --git a/cryptography/sha3/xor_generic.go b/cryptography/sha3/xor_generic.go new file mode 100644 index 0000000..fd35f02 --- /dev/null +++ b/cryptography/sha3/xor_generic.go @@ -0,0 +1,28 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import "encoding/binary" + +// xorInGeneric xors the bytes in buf into the state; it +// makes no non-portable assumptions about memory layout +// or alignment. +func xorInGeneric(d *state, buf []byte) { + n := len(buf) / 8 + + for i := 0; i < n; i++ { + a := binary.LittleEndian.Uint64(buf) + d.a[i] ^= a + buf = buf[8:] + } +} + +// copyOutGeneric copies ulint64s to a byte buffer. +func copyOutGeneric(d *state, b []byte) { + for i := 0; len(b) >= 8; i++ { + binary.LittleEndian.PutUint64(b, d.a[i]) + b = b[8:] + } +} diff --git a/cryptography/sha3/xor_unaligned.go b/cryptography/sha3/xor_unaligned.go new file mode 100644 index 0000000..a3d0686 --- /dev/null +++ b/cryptography/sha3/xor_unaligned.go @@ -0,0 +1,65 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64 386 ppc64le +// +build !appengine + +package sha3 + +import "unsafe" + +// A storageBuf is an aligned array of maxRate bytes. +type storageBuf [maxRate / 8]uint64 + +func (b *storageBuf) asBytes() *[maxRate]byte { + return (*[maxRate]byte)(unsafe.Pointer(b)) +} + +func xorInUnaligned(d *state, buf []byte) { + n := len(buf) + bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] + if n >= 72 { + d.a[0] ^= bw[0] + d.a[1] ^= bw[1] + d.a[2] ^= bw[2] + d.a[3] ^= bw[3] + d.a[4] ^= bw[4] + d.a[5] ^= bw[5] + d.a[6] ^= bw[6] + d.a[7] ^= bw[7] + d.a[8] ^= bw[8] + } + if n >= 104 { + d.a[9] ^= bw[9] + d.a[10] ^= bw[10] + d.a[11] ^= bw[11] + d.a[12] ^= bw[12] + } + if n >= 136 { + d.a[13] ^= bw[13] + d.a[14] ^= bw[14] + d.a[15] ^= bw[15] + d.a[16] ^= bw[16] + } + if n >= 144 { + d.a[17] ^= bw[17] + } + if n >= 168 { + d.a[18] ^= bw[18] + d.a[19] ^= bw[19] + d.a[20] ^= bw[20] + } +} + +func copyOutUnaligned(d *state, buf []byte) { + ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) + copy(buf, ab[:]) +} + +var ( + xorIn = xorInUnaligned + copyOut = copyOutUnaligned +) + +const xorImplementationUnaligned = "unaligned" diff --git a/dvm/deterministic_random_number.go b/dvm/deterministic_random_number.go new file mode 100644 index 0000000..7a76c44 --- /dev/null +++ b/dvm/deterministic_random_number.go @@ -0,0 +1,68 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "encoding/binary" +import "golang.org/x/crypto/salsa20/salsa" + +import "github.com/deroproject/derohe/cryptography/crypto" + +/* this file implements a deterministic random number generator + the random number space is quite large but still unattackable, since the seeds are random + the seeds depend on the BLID and the TXID, if an attacker can somehow control the TXID, + he will not be able to control BLID +*/ + +type RND struct { + Key [32]byte + Pos uint64 // we will wrap around in 2^64 times but this is per TX,BLOCK,SCID +} + +// make sure 2 SCs cannot ever generate same series of random numbers +func Initialize_RND(SCID, BLID, TXID crypto.Hash) (r *RND) { + r = &RND{} + tmp := crypto.Keccak256(SCID[:], BLID[:], TXID[:]) + copy(r.Key[:], tmp[:]) + r.Pos = 1 // we start at 1 to eliminate an edge case + return // TODO we must reinitialize using blid and other parameters +} + +func (r *RND) Random() uint64 { + + var out [32]byte + var in [16]byte + var key [32]byte + + copy(key[:], r.Key[:]) + + binary.BigEndian.PutUint64(in[:], r.Pos) + + salsa.HSalsa20(&out, &in, &key, &in) + + deterministic_value := binary.BigEndian.Uint64(out[:]) + r.Pos++ + + return deterministic_value +} + +// range to (input-1) +func (r *RND) Random_MAX(input uint64) uint64 { + if input == 0 { + panic("RNG cannot generate RND with 0 as max") + } + return r.Random() % input +} diff --git a/dvm/deterministic_random_number_test.go b/dvm/deterministic_random_number_test.go new file mode 100644 index 0000000..89b4a03 --- /dev/null +++ b/dvm/deterministic_random_number_test.go @@ -0,0 +1,58 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +//import "fmt" +//import "reflect" +import "testing" + +import "github.com/deroproject/derohe/cryptography/crypto" + +// run the test +func Test_RND_execution(t *testing.T) { + + var SCID, BLID, TXID crypto.Hash + SCID[0] = 22 + rnd := Initialize_RND(SCID, BLID, TXID) + + //get a random number + result_initial := rnd.Random() + + // lets tweak , each parameter by a byte and check whether it affects the output + SCID[0] = 23 + if Initialize_RND(SCID, BLID, TXID).Random() == result_initial { + t.Fatalf("RND not dependent on SCID") + } + SCID[0] = 0 + + BLID[0] = 22 + if Initialize_RND(SCID, BLID, TXID).Random() == result_initial { + t.Fatalf("RND not dependent on BLID") + } + BLID[0] = 0 + + TXID[0] = 22 + if Initialize_RND(SCID, BLID, TXID).Random() == result_initial { + t.Fatalf("RND not dependent on TXID") + } + TXID[0] = 22 + + if Initialize_RND(SCID, BLID, TXID).Random_MAX(10000) >= 10000 { + t.Fatalf("RND cannot be generated within a range") + } + +} diff --git a/dvm/dvm.go b/dvm/dvm.go new file mode 100644 index 0000000..6afe955 --- /dev/null +++ b/dvm/dvm.go @@ -0,0 +1,1120 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "fmt" +import "text/scanner" +import "strings" +import "strconv" +import "unicode" +import "unicode/utf8" +import "go/ast" +import "go/parser" +import "go/token" +import "math" + +import "runtime/debug" +import "github.com/deroproject/derohe/cryptography/crypto" + +//import "github.com/deroproject/derohe/rpc" + +type Vtype int + +const ( + Invalid Vtype = iota // default is invalid + Uint64 // uint64 data type + String // string + Blob +) + +var replacer = strings.NewReplacer("< =", "<=", "> =", ">=", "= =", "==", "! =", "!=", "& &", "&&", "| |", "||", "< <", "<<", "> >", ">>", "< >", "!=") + +// Some global variables are always accessible, namely +// SCID TXID which installed the SC +// TXID current TXID under which this SC is currently executing +// BLID current BLID under which TXID is found, THIS CAN be used as deterministic RND Generator, if the SC needs secure randomness +// BL_HEIGHT current height of blockchain + +type Variable struct { + Name string `cbor:"N,omitempty" json:"N,omitempty"` + Type Vtype `cbor:"T,omitempty" json:"T,omitempty"` // we have only 4 data types + Value interface{} `cbor:"V,omitempty" json:"V,omitempty"` +} + +type Function struct { + Name string `cbor:"N,omitempty" json:"N,omitempty"` + Params []Variable `cbor:"P,omitempty" json:"P,omitempty"` + ReturnValue Variable `cbor:"R,omitempty" json:"R,omitempty"` + Lines map[uint64][]string `cbor:"L,omitempty" json:"L,omitempty"` + // map from line number to array index below + LinesNumberIndex map[uint64]uint64 `cbor:"LI,omitempty" json:"LI,omitempty"` // a map is used to avoid sorting/searching + LineNumbers []uint64 `cbor:"LN,omitempty" json:"LN,omitempty"` +} + +const LIMIT_interpreted_lines = 2000 // testnet has hardcoded limit +const LIMIT_evals = 11000 // testnet has hardcoded limit eval limit + +// each smart code is nothing but a collection of functions +type SmartContract struct { + Functions map[string]Function `cbor:"F,omitempty" json:"F,omitempty"` +} + +// we have a rudimentary line by line parser +// SC authors must make sure code coverage is 100 % +// we are doing away with AST +func ParseSmartContract(src_code string) (SC SmartContract, pos string, err error) { + + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("Recovered in function %+v", r) + } + }() + + var s scanner.Scanner + s.Init(strings.NewReader(src_code)) + s.Filename = "code" + s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.SkipComments | scanner.ScanComments // skip comments + + skip_line := int32(-1) + var current_line int32 = -1 + var line_tokens []string + var current_function *Function + + SC.Functions = map[string]Function{} + + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + pos := s.Position.String() + txt := s.TokenText() + + if strings.HasPrefix(txt, ";") || strings.HasPrefix(txt, "REM") { // skip line, if this is the first word + skip_line = int32(s.Position.Line) + } + if skip_line == int32(s.Position.Line) { + continue + } + + /*if strings.HasPrefix(txt, "//") || strings.HasPrefix(txt, "/*") { // skip comments + continue + }*/ + + process_token: + if current_line == -1 { + current_line = int32(s.Position.Line) + } + + if current_line == int32(s.Position.Line) { // collect a complete line + line_tokens = append(line_tokens, txt) + } else { // if new line found, process previous line + if err = parse_function_line(&SC, ¤t_function, line_tokens); err != nil { + return SC, pos, err + } + line_tokens = line_tokens[:0] + + current_line = -1 + goto process_token + } + // fmt.Printf("%s: %s line %+v\n", s.Position, txt, line_tokens) + } + + if len(line_tokens) > 0 { // last line is processed here + if err = parse_function_line(&SC, ¤t_function, line_tokens); err != nil { + return SC, pos, err + } + } + + if current_function != nil { + err = fmt.Errorf("EOF reached but End Function is missing \"%s\"", current_function.Name) + return SC, pos, err + } + + return +} + +// checks whether a function name is valid +// a valid name starts with a non digit and does not contain . +func check_valid_name(name string) bool { + r, size := utf8.DecodeRuneInString(name) + if r == utf8.RuneError || size == 0 { + return false + } + return unicode.IsLetter(r) +} + +func check_valid_type(name string) Vtype { + switch name { + case "Uint64": + return Uint64 + case "Blob": + return Blob + case "String": + return String + } + return Invalid +} + +// this will parse 1 line at a time, if there is an error, it is returned +func parse_function_line(SC *SmartContract, function **Function, line []string) (err error) { + pos := 0 + //fmt.Printf("parsing function line %+v\n", line) + + if *function == nil { //if no current function, only legal is Sub + if !strings.EqualFold(line[pos], "Function") { + return fmt.Errorf("Expecting declaration of function but found \"%s\"", line[0]) + } + pos++ + + var f Function + f.Lines = map[uint64][]string{} // initialize line map + f.LinesNumberIndex = map[uint64]uint64{} + + if len(line) < (pos + 1) { + return fmt.Errorf("function name missing") + } + + if !check_valid_name(line[pos]) { + return fmt.Errorf("function name \"%s\" contains invalid characters", line[pos]) + } + f.Name = line[pos] + + pos++ + if len(line) < (pos+1) || line[pos] != "(" { + return fmt.Errorf("function \"%s\" missing '('", f.Name) + } + + parse_params: // now lets parse function params, but lets filter out , + pos++ + if len(line) < (pos + 1) { + return fmt.Errorf("function \"%s\" missing function parameters", f.Name) + } + + if line[pos] == "," { + goto parse_params + } + if line[pos] == ")" { + // function does not have any parameters + // or all parameters have been parsed + + } else { // we must parse param name, param type as pairs + if len(line) < (pos + 2) { + return fmt.Errorf("function \"%s\" missing function parameters", f.Name) + } + + param_name := line[pos] + param_type := check_valid_type(line[pos+1]) + + if !check_valid_name(param_name) { + return fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", f.Name, param_name) + } + + if param_type == Invalid { + return fmt.Errorf("function name \"%s\", variable type \"%s\" is invalid", f.Name, line[pos+1]) + } + f.Params = append(f.Params, Variable{Name: param_name, Type: param_type}) + + pos++ + goto parse_params + } + + pos++ + + // check if we have return value + if len(line) < (pos + 1) { // we do not have return value + f.ReturnValue.Type = Invalid + } else { + return_type := check_valid_type(line[pos]) + if return_type == Invalid { + return fmt.Errorf("function name \"%s\", return type \"%s\" is invalid", f.Name, line[pos]) + } + f.ReturnValue.Type = return_type + } + + *function = &f + return nil + } else if strings.EqualFold(line[pos], "End") && strings.EqualFold(line[pos+1], "Function") { + SC.Functions[(*function).Name] = **function + *function = nil + } else if strings.EqualFold(line[pos], "Function") { + return fmt.Errorf("Nested functions are not allowed") + } else { // add line to current function, provided line numbers are not duplicated, line numbers are mandatory + line_number, err := strconv.ParseUint(line[pos], 10, 64) + if err != nil { + return fmt.Errorf("Error Parsing line number \"%s\" in function \"%s\" ", line[pos], (*function).Name) + } + if line_number == 0 || line_number == math.MaxUint64 { + return fmt.Errorf("Error: line number cannot be %d in function \"%s\" ", line_number, (*function).Name) + } + + if _, ok := (*function).Lines[line_number]; ok { // duplicate line number + return fmt.Errorf("Error: duplicate line number within function \"%s\" ", (*function).Name) + } + if len((*function).LineNumbers) >= 1 && line_number < (*function).LineNumbers[len((*function).LineNumbers)-1] { + return fmt.Errorf("Error: line number must be ascending within function \"%s\" ", (*function).Name) + } + + line_copy := make([]string, len(line)-1, len(line)-1) + copy(line_copy, line[1:]) + (*function).Lines[line_number] = line_copy // we need to copy and add ( since line is reused) + (*function).LinesNumberIndex[line_number] = uint64(len((*function).LineNumbers)) + (*function).LineNumbers = append((*function).LineNumbers, line_number) + + // fmt.Printf("%s %d %+v\n", (*function).Name, line_number, line[0:]) + // fmt.Printf("%s %d %+v\n", (*function).Name, line_number, (*function).Lines[line_number]) + + } + + return nil +} + +// this will run a function from a loaded SC and execute it if possible +// it can run all internal functions +// parameters must be passed as strings +func runSmartContract_internal(SC *SmartContract, EntryPoint string, state *Shared_State, params map[string]interface{}) (result Variable, err error) { + // if smart contract does not contain function, trigger exception + function_call, ok := SC.Functions[EntryPoint] + if !ok { + err = fmt.Errorf("function \"%s\" is not available in SC", EntryPoint) + return + } + + var dvm DVM_Interpreter + dvm.SC = SC + dvm.f = function_call + dvm.Locals = map[string]Variable{} + + dvm.State = state // set state to execute current function + + // parse parameters, rename them, make them available as local variables + for _, p := range function_call.Params { + variable := Variable{Name: p.Name, Type: p.Type} + value, ok := params[p.Name] + if !ok { // necessary parameter is missing from arguments + err = fmt.Errorf("Argument \"%s\" is missing while invoking \"%s\"", p.Name, EntryPoint) + return + } + + // now lets parse the data,Uint64,Address,String,Blob + switch p.Type { + case Uint64: + variable.Value, err = strconv.ParseUint(value.(string), 0, 64) + if err != nil { + return + } + case String: + variable.Value = value.(string) + + case Blob: + variable.Value = value.(string) + //panic("address and blob cannot have parameters") + + } + + dvm.Locals[variable.Name] = variable + } + + // all variables have been collected, start interpreter + dvm.ReturnValue = dvm.f.ReturnValue // enforce return value to be of same type + + dvm.State.Monitor_recursion++ // higher recursion + + err = dvm.interpret_SmartContract() + if err != nil { + return + } + + result = dvm.ReturnValue + + return +} + +// it is similar to internal functions, however it enforces the condition that only Exportable functions are callable +// any function which has first character ASCII and upper case is considered an exported function +func RunSmartContract(SC *SmartContract, EntryPoint string, state *Shared_State, params map[string]interface{}) (result Variable, err error) { + // if smart contract does not contain function, trigger exception + + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("Recovered in function %+v", r) + fmt.Printf("%+v ", err) + fmt.Printf("%s\n", debug.Stack()) + } + //debug.Stack() + + }() + + r, size := utf8.DecodeRuneInString(EntryPoint) + + if r == utf8.RuneError || size == 0 { + return result, fmt.Errorf("Invalid function name") + + } + + if r >= unicode.MaxASCII { + return result, fmt.Errorf("Invalid function name, First character must be ASCII alphabet") + } + + if !unicode.IsLetter(r) { + return result, fmt.Errorf("Invalid function name, First character must be ASCII Letter") + } + + if !unicode.IsUpper(r) { + return result, fmt.Errorf("Invalid function name, First character must be Capital/Upper Case") + } + + // initialize RND + if state.Monitor_recursion == 0 { + state.RND = Initialize_RND(state.Chain_inputs.SCID, state.Chain_inputs.BLID, state.Chain_inputs.TXID) + //state.store = Initialize_TX_store() + state.DERO_Transfer = map[string]uint64{} + } + + result, err = runSmartContract_internal(SC, EntryPoint, state, params) + + if err != nil { + return result, err + } + if state.Monitor_recursion != 0 { // recursion must be zero at end + return result, fmt.Errorf("Invalid recursion level %d", state.Monitor_recursion) + } + + // if recursion level is zero, we should check return value and persist the state changes + + return result, err +} + +// this structure is all the inputs that are available to SC during execution +type Blockchain_Input struct { + SCID crypto.Hash // current smart contract which is executing + BLID crypto.Hash // BLID + TXID crypto.Hash // current TXID under which TX + Signer string // address which signed this, inn 33 byte form + BL_HEIGHT uint64 // current chain height under which current tx is valid + BL_TOPOHEIGHT uint64 // current block topo height which can be used to uniquely pinpoint the block +} + +// all DVMs triggered by the first call, will share this structure +// sharing this structure means RND number attacks are not possible +// all storage state is shared, this means something similar to solidity delegatecall +// this is necessary to prevent number of attacks +type Shared_State struct { + Persistance bool // whether the results will be persistant or it's just a demo/test call + + Chain_inputs *Blockchain_Input // all blockchain info is available here + + DERO_Balance uint64 // DERO balance of this smart contract, this is loaded + // this includes any DERO that has arrived with this TX + DERO_Received uint64 // amount of DERO received with this TX + Token_Received uint64 // token received + + DERO_Transfer map[string]uint64 // any DERO that this TX wants to send OUT + // transfers are only processed after the contract has terminated successfully + + RND *RND // this is initialized only once while invoking entrypoint + Store *TX_Storage // mechanism to access a data store, can discard changes + + Monitor_recursion int64 // used to control recursion amount 64 calls are more than necessary + Monitor_lines_interpreted int64 // number of lines interpreted + Monitor_ops int64 // number of ops evaluated, for expressions, variables + +} + +type DVM_Interpreter struct { + SCID string + SC *SmartContract + EntryPoint string + f Function + IP uint64 // current line number + ReturnValue Variable // Result of current function call + Locals map[string]Variable // all local variables + + Chain_inputs *Blockchain_Input // all blockchain info is available here + + State *Shared_State // all shared state between DVM is available here + + RND *RND // this is initialized only once while invoking entrypoint + + store *TX_Storage // mechanism to access a data store, can discard changes + +} + +func (i *DVM_Interpreter) incrementIP(newip uint64) (line []string, err error) { + var ok bool +try_again: + + // increase cost here + + if newip == 0 { // we are simply falling through + index := i.f.LinesNumberIndex[i.IP] // find the pos in the line numbers and find the current line_number + index++ + if i.IP == 0 { + index = 0 // start from first line + } + if uint64(len(i.f.LineNumbers)) <= index { // if function does not contain more lines to execute, return + err = fmt.Errorf("No lines after line number (%d) in SC %s in function %s", i.IP, i.SCID, i.f.Name) + return + } + + i.IP = i.f.LineNumbers[index] + + } else { // we have a GOTO and must jump to the line numbr mentioned + i.IP = newip + } + line, ok = i.f.Lines[i.IP] + if !ok { + err = fmt.Errorf("No such line number (%d) in SC %s in function %s", i.IP, i.SCID, i.f.Name) + return + } + i.State.Monitor_lines_interpreted++ // increment line interpreted + + if len(line) == 0 { // increment to next line + goto try_again + } + return +} + +// this runs a smart contract function with specific params +func (i *DVM_Interpreter) interpret_SmartContract() (err error) { + + newIP := uint64(0) + for { + var line []string + line, err = i.incrementIP(newIP) + if err != nil { + return + } + + newIP = 0 // this is necessary otherwise, it will trigger an infinite loop in the case given below + + /* + * Function SetOwner(value Uint64, newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",newowner) + 40 RETURN 0 + End Function + */ + + if i.State.Monitor_lines_interpreted > LIMIT_interpreted_lines { + panic(fmt.Sprintf("%d lines interpreted, reached limit %d", LIMIT_interpreted_lines, LIMIT_interpreted_lines)) + } + + //fmt.Printf("interpreting line %+v\n", line) + + //fmt.Printf("received line to interpret %+v err\n", line, err) + switch { + case strings.EqualFold(line[0], "DIM"): + newIP, err = i.interpret_DIM(line[1:]) + case strings.EqualFold(line[0], "LET"): + newIP, err = i.interpret_LET(line[1:]) + case strings.EqualFold(line[0], "GOTO"): + newIP, err = i.interpret_GOTO(line[1:]) + case strings.EqualFold(line[0], "IF"): + newIP, err = i.interpret_IF(line[1:]) + case strings.EqualFold(line[0], "RETURN"): + newIP, err = i.interpret_RETURN(line[1:]) + + //ability to print something for debugging purpose + case strings.EqualFold(line[0], "PRINT"): + fallthrough + case strings.EqualFold(line[0], "PRINTF"): + newIP, err = i.interpret_PRINT(line[1:]) + + // if we are here, the first part is unknown + + default: + + // we should try to evaluate expression and make sure it's a function call + // now lets evaluate the expression + + expr, err1 := parser.ParseExpr(replacer.Replace(strings.Join(line, " "))) + if err1 != nil { + err = err1 + return + } + + if _, ok := expr.(*ast.CallExpr); !ok { + return fmt.Errorf("not a function call line %+v\n", line) + } + i.eval(expr) + + } + + if err != nil { + err = fmt.Errorf("err while interpreting line %+v err %s\n", line, err) + return + } + if newIP == math.MaxUint64 { + break + } + } + return +} + +// this is very limited and can be used print only variables +func (dvm *DVM_Interpreter) interpret_PRINT(args []string) (newIP uint64, err error) { + var variable Variable + var ok bool + if len(args) > 0 { + params := []interface{}{} + for i := 1; i < len(args); i++ { + if variable, ok = dvm.Locals[args[i]]; !ok { // TODO what about printing globals + /*if variable,ok := dvm.Locals[exp.Name];!ok{ + + }*/ + + } + if ok { + params = append(params, variable.Value) + } else { + params = append(params, fmt.Sprintf("unknown variable %s", args[i])) + } + } + + _, err = fmt.Printf(strings.Trim(args[0], "\"")+"\n", params...) + } + return +} + +// process DIM line +func (dvm *DVM_Interpreter) interpret_DIM(line []string) (newIP uint64, err error) { + + if len(line) <= 2 || !strings.EqualFold(line[len(line)-2], "as") { + return 0, fmt.Errorf("Invalid DIM syntax") + } + + // check last data type + data_type := check_valid_type(line[len(line)-1]) + if data_type == Invalid { + return 0, fmt.Errorf("function name \"%s\", No such Data type \"%s\"", dvm.f.Name, line[len(line)-1]) + } + + for i := 0; i < len(line)-2; i++ { + if line[i] != "," { // ignore separators + + if !check_valid_name(line[i]) { + return 0, fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", dvm.f.Name, line[i]) + } + + // check whether variable is already defined + if _, ok := dvm.Locals[line[i]]; ok { + return 0, fmt.Errorf("function name \"%s\", variable name \"%s\" contains invalid characters", dvm.f.Name, line[i]) + } + + // all data variables are pre-initialized + + switch data_type { + case Uint64: + dvm.Locals[line[i]] = Variable{Name: line[i], Type: Uint64, Value: uint64(0)} + case String: + dvm.Locals[line[i]] = Variable{Name: line[i], Type: String, Value: ""} + case Blob: + dvm.Locals[line[i]] = Variable{Name: line[i], Type: Blob, Value: ""} + // return 0, fmt.Errorf("function name \"%s\", variable name \"%s\" blobs are currently not supported", dvm.f.Name, line[i]) + + } + // fmt.Printf("Initialising variable %s %+v\n",line[i],dvm.Locals[line[i]]) + + } + } + + return +} + +// process LET statement +func (dvm *DVM_Interpreter) interpret_LET(line []string) (newIP uint64, err error) { + + if len(line) <= 2 || !strings.EqualFold(line[1], "=") { + err = fmt.Errorf("Invalid LET syntax") + return + } + + if _, ok := dvm.Locals[line[0]]; !ok { + err = fmt.Errorf("function name \"%s\", variable name \"%s\" is used without definition", dvm.f.Name, line[0]) + return + } + result := dvm.Locals[line[0]] + + expr, err := parser.ParseExpr(strings.Join(line[2:], " ")) + if err != nil { + return + } + + expr_result := dvm.eval(expr) + //fmt.Printf("expression %s = %+v\n", line[0],expr_result) + + //fmt.Printf(" %+v \n", dvm.Locals[line[0]]) + switch result.Type { + case Uint64: + result.Value = expr_result.(uint64) + case String: + result.Value = expr_result.(string) + + case Blob: + result.Value = expr_result // FIXME address validation should be provided + } + + dvm.Locals[line[0]] = result + // fmt.Printf(" %+v \n", dvm.Locals[line[0]]) + + return +} + +// process GOTO line +func (dvm *DVM_Interpreter) interpret_GOTO(line []string) (newIP uint64, err error) { + + if len(line) != 1 { + err = fmt.Errorf("GOTO contains 1 mandatory line number as argument") + return + } + + newIP, err = strconv.ParseUint(line[0], 0, 64) + if err != nil { + return + } + + if newIP == 0 || newIP == math.MaxUint64 { + return 0, fmt.Errorf("GOTO has invalid line number \"%d\"", newIP) + } + return +} + +// process IF line +// IF has two forms vis x,y are line numbers +// IF expr THEN GOTO x +// IF expr THEN GOTO x ELSE GOTO y +func (dvm *DVM_Interpreter) interpret_IF(line []string) (newIP uint64, err error) { + + thenip := uint64(0) + elseip := uint64(0) + + // first form of IF + if len(line) >= 4 && strings.EqualFold(line[len(line)-3], "THEN") && strings.EqualFold(line[len(line)-2], "GOTO") { + + thenip, err = strconv.ParseUint(line[len(line)-1], 0, 64) + if err != nil { + return + } + line = line[:len(line)-3] + + } else if len(line) >= 7 && strings.EqualFold(line[len(line)-6], "THEN") && strings.EqualFold(line[len(line)-5], "GOTO") && strings.EqualFold(line[len(line)-3], "ELSE") && strings.EqualFold(line[len(line)-2], "GOTO") { + + thenip, err = strconv.ParseUint(line[len(line)-4], 0, 64) + if err != nil { + return + } + + elseip, err = strconv.ParseUint(line[len(line)-1], 0, 64) + if err != nil { + return + } + + if elseip == 0 || elseip == math.MaxUint64 { + return 0, fmt.Errorf("ELSE GOTO has invalid line number \"%d\"", thenip) + } + + line = line[:len(line)-6] + } else { + err = fmt.Errorf("Invalid IF syntax") + return + } + + if thenip == 0 || thenip == math.MaxUint64 { + return 0, fmt.Errorf("THEN GOTO has invalid line number \"%d\"", thenip) + } + + // now lets evaluate the expression + + expr, err := parser.ParseExpr(replacer.Replace(strings.Join(line, " "))) + if err != nil { + return + } + + expr_result := dvm.eval(expr) + //fmt.Printf("if %d %T expr( %s)\n", expr_result, expr_result, replacer.Replace(strings.Join(line, " "))) + if result, ok := expr_result.(uint64); ok { + if result != 0 { + newIP = thenip + } else { + newIP = elseip + } + } else { + + err = fmt.Errorf("Invalid IF expression \"%s\"", replacer.Replace(strings.Join(line, " "))) + } + + return + +} + +// process RETURN line +func (dvm *DVM_Interpreter) interpret_RETURN(line []string) (newIP uint64, err error) { + + if dvm.ReturnValue.Type == Invalid { + if len(line) != 0 { + err = fmt.Errorf("function name \"%s\" cannot return anything", dvm.f.Name) + return + } + + dvm.State.Monitor_recursion-- // lower recursion + newIP = math.MaxUint64 // simple return + return + } + + if len(line) == 0 { + err = fmt.Errorf("function name \"%s\" should return a value", dvm.f.Name) + return + } + + // we may be returning an expression which must be solved + expr, err := parser.ParseExpr(replacer.Replace(strings.Join(line, " "))) + if err != nil { + return + } + + expr_result := dvm.eval(expr) + //fmt.Printf("expression %+v %T\n", expr_result, expr_result) + + switch dvm.ReturnValue.Type { + case Uint64: + dvm.ReturnValue.Value = expr_result.(uint64) + case String: + dvm.ReturnValue.Value = expr_result.(string) + case Blob: + dvm.ReturnValue.Value = expr_result.(string) + } + + dvm.State.Monitor_recursion-- // lower recursion + newIP = math.MaxUint64 // simple return + + return +} + +// only returns identifiers +func (dvm *DVM_Interpreter) eval_identifier(exp ast.Expr) string { + + switch exp := exp.(type) { + case *ast.Ident: // it's a variable, + return exp.Name + default: + panic("expecting identifier") + + } + +} + +func (dvm *DVM_Interpreter) eval(exp ast.Expr) interface{} { + + dvm.State.Monitor_ops++ // maintain counter + + if dvm.State.Monitor_ops > LIMIT_evals { + panic(fmt.Sprintf("%d lines interpreted, evals reached limit %d", dvm.State.Monitor_lines_interpreted, LIMIT_evals)) + } + + //fmt.Printf("exp %+v %T\n", exp, exp) + switch exp := exp.(type) { + case *ast.ParenExpr: + return dvm.eval(exp.X) + + case *ast.UnaryExpr: // there are 2 unary operators, one is binary NOT , second is logical not + switch exp.Op { + case token.XOR: + return ^(dvm.eval(exp.X).(uint64)) + case token.NOT: + x := dvm.eval(exp.X) + switch x := x.(type) { + case uint64: + return ^x + case string: + if IsZero(x) == 1 { + return uint64(1) + } + return uint64(0) + + } + } + + case *ast.BinaryExpr: + return dvm.evalBinaryExpr(exp) + case *ast.Ident: // it's a variable, + if _, ok := dvm.Locals[exp.Name]; !ok { + panic(fmt.Sprintf("function name \"%s\", variable name \"%s\" is used without definition", dvm.f.Name, exp.Name)) + + } + //fmt.Printf("value %s %d\n",exp.Name, dvm.Locals[exp.Name].Value) + return dvm.Locals[exp.Name].Value + + // there are 2 types of calls, one within the smartcontract + // other one crosses smart contract boundaries + case *ast.CallExpr: + func_name := dvm.eval_identifier(exp.Fun) + //fmt.Printf("Call expression %+v %s \"%s\" \n",exp,exp.Fun, func_name) + // if call is internal + // + + // try to handle internal functions, SC function cannot overide internal functions + if ok, result := dvm.Handle_Internal_Function(exp, func_name); ok { + return result + } + function_call, ok := dvm.SC.Functions[func_name] + if !ok { + panic(fmt.Sprintf("Unknown function called \"%s\"", exp.Fun)) + } + if len(function_call.Params) != len(exp.Args) { + panic(fmt.Sprintf("function \"%s\" called with incorrect number of arguments , expected %d , actual %d", func_name, len(function_call.Params), len(exp.Args))) + } + + arguments := map[string]interface{}{} + for i, p := range function_call.Params { + switch p.Type { + case Uint64: + arguments[p.Name] = fmt.Sprintf("%d", dvm.eval(exp.Args[i]).(uint64)) + case String: + arguments[p.Name] = dvm.eval(exp.Args[i]).(string) + + case Blob: + arguments[p.Name] = dvm.eval(exp.Args[i]).(string) + } + } + + // allow calling unexported functions + result, err := runSmartContract_internal(dvm.SC, func_name, dvm.State, arguments) + if err != nil { + panic(err) + } + if function_call.ReturnValue.Type != Invalid { + return result.Value + } + return nil + + case *ast.BasicLit: + switch exp.Kind { + case token.INT: + i, err := strconv.ParseUint(exp.Value, 0, 64) + if err != nil { + panic(err) + } + return i + case token.STRING: + unquoted, err := strconv.Unquote(exp.Value) + if err != nil { + panic(err) + } + return unquoted + } + default: + panic(fmt.Sprintf("Unhandled expression type %+v", exp)) + + } + + panic("We should never reach here while evaluating expressions") + return 0 +} + +// this can be used to check whether variable has a default value +// for uint64 , it is 0 +// for string , it is "" +// TODO Address, Blob +func IsZero(value interface{}) uint64 { + switch v := value.(type) { + case uint64: + if v == 0 { + return 1 + } + case string: + if v == "" { + return 1 + } + + default: + panic("IsZero not being handled") + + } + + return 0 +} + +func (dvm *DVM_Interpreter) evalBinaryExpr(exp *ast.BinaryExpr) interface{} { + + left := dvm.eval(exp.X) + right := dvm.eval(exp.Y) + + //fmt.Printf("left '%+v %T' %+v right '%+v %T'\n", left, left, exp.Op, right, right) + + // special case to append uint64 to strings + if fmt.Sprintf("%T", left) == "string" && fmt.Sprintf("%T", right) == "uint64" { + return left.(string) + fmt.Sprintf("%d", right) + } + + if fmt.Sprintf("%T", left) != fmt.Sprintf("%T", right) { + panic(fmt.Sprintf("Expressions cannot be different type(String/Uint64) left (val %+v %+v) right (%+v %+v)", left, exp.X, right, exp.Y)) + } + + // logical ops are handled differently + switch exp.Op { + case token.LAND: + if (IsZero(left) == 0) && (IsZero(right) == 0) { // both sides should be set + return uint64(1) + } + + return uint64(0) + case token.LOR: + //fmt.Printf("left %d right %d\n", left,right) + //fmt.Printf("left %v right %v\n", (IsZero(left) != 0),(IsZero(right) != 0)) + if (IsZero(left) == 0) || (IsZero(right) == 0) { + return uint64(1) + } + return uint64(0) + + } + + // handle string operands + if fmt.Sprintf("%T", left) == "string" { + left_string := left.(string) + right_string := right.(string) + + switch exp.Op { + case token.ADD: + return left_string + right_string + case token.EQL: + if left_string == right_string { + return uint64(1) + } + return uint64(0) + case token.NEQ: + if left_string != right_string { + return uint64(1) + } + return uint64(0) + default: + panic(fmt.Sprintf("String data type only support addition operation ('%s') not supported", exp.Op)) + } + + } + + left_uint64 := left.(uint64) + right_uint64 := right.(uint64) + + switch exp.Op { + case token.ADD: + return left_uint64 + right_uint64 // TODO : can we add rounding case here and raise exception + case token.SUB: + return left_uint64 - right_uint64 // TODO : can we add rounding case here and raise exception + case token.MUL: + return left_uint64 * right_uint64 + case token.QUO: + return left_uint64 / right_uint64 + case token.REM: + return left_uint64 % right_uint64 + + //bitwise ops + case token.AND: + return left_uint64 & right_uint64 + case token.OR: + return left_uint64 | right_uint64 + case token.XOR: + return left_uint64 ^ right_uint64 + case token.SHL: + return left_uint64 << right_uint64 + case token.SHR: + return left_uint64 >> right_uint64 + + case token.EQL: + if left_uint64 == right_uint64 { + return uint64(1) + } + case token.NEQ: + if left_uint64 != right_uint64 { + return uint64(1) + } + case token.LEQ: + if left_uint64 <= right_uint64 { + return uint64(1) + } + case token.GEQ: + if left_uint64 >= right_uint64 { + return uint64(1) + } + case token.LSS: + if left_uint64 < right_uint64 { + return uint64(1) + } + case token.GTR: + if left_uint64 > right_uint64 { + return uint64(1) + } + default: + panic("This operation cannot be handled") + } + return uint64(0) +} + +/* +func main() { + + const src = ` + Function HelloWorld(s Uint64) Uint64 + + 5 + 10 Dim x1, x2 as Uint64 + 20 LET x1 = 3 + 25 LET x1 = 3 + 5 - 1 + 27 LET x2 = x1 + 3 + 28 RETURN HelloWorld2(s*s) + 30 Printf "x1=%d x2=%d s = %d" x1 x2 s + 35 IF x1 == 7 THEN GOTO 100 ELSE GOTO 38 + 38 Dim y1, y2 as String + 40 LET y1 = "first string" + "second string" + + + 60 GOTO 100 + 80 GOTO 10 + 100 RETURN 0 + 500 LET y = 45 + 501 y = 45 + + End Function + + + Function HelloWorld2(s Uint64) Uint64 + + 900 Return s + 950 y = 45 + // Comment begins at column 5. + + ; This line should not be included in the output. +REM jj + + + +7000 let x.ku[1+1]=0x55 +End Function + +` + + // we should be build an AST here + + sc, pos, err := ParseSmartContract(src) + if err != nil { + fmt.Printf("Error while parsing smart contract pos %s err : %s\n", pos, err) + return + } + + result, err := RunSmartContract(&sc, "HelloWorld", map[string]interface{}{"s": "9999"}) + + fmt.Printf("result %+v err %s\n", result, err) + +} +*/ diff --git a/dvm/dvm_execution_test.go b/dvm/dvm_execution_test.go new file mode 100644 index 0000000..155364a --- /dev/null +++ b/dvm/dvm_execution_test.go @@ -0,0 +1,1757 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "fmt" +import "reflect" +import "testing" + +//import "github.com/deroproject/derosuite/crypto" + +var execution_tests = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "demo 1", + `Function TestRun(s Uint64) Uint64 + 10 Return s + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, + { + "invalid return expression", + `Function TestRun(s Uint64) Uint64 + 10 Return 1s + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function with 2 params", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 + a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "4"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function with 2 string params", + `Function TestRun(a1 String,a2 String) String + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 + a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "4"}, + nil, + Variable{Type: String, Value: "44"}, + }, + + // test all arithmetic operations + { + "valid function testing substraction ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 - a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "12", "a2": "4"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing multiplication ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 * a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "4"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing division ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 / a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "25", "a2": "3"}, // it is 25 to confirm we are doing integer division + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing modulus ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 % a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "35", "a2": "9"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing SHL ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 << a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing SHR ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 >> a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "32", "a2": "2"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing OR ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 | a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "8", "a2": "8"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing AND ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 & a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "valid function testing NOT ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return !a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint64, Value: uint64(18446744073709551606)}, //NOT 9 == 18446744073709551606 + }, { + "valid function testing ^XOR ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return ^a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "9", "a2": "10"}, + nil, + Variable{Type: Uint64, Value: uint64(18446744073709551606)}, //NOT 9 == 18446744073709551606 + }, { + "valid function testing XOR ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 ^ a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "60", "a2": "13"}, + nil, + Variable{Type: Uint64, Value: uint64(49)}, + }, { + "valid function testing || ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 || a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing && 1 ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 && a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "0", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(0)}, + }, { + "valid function testing && 2", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return (a1 && a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(1)}, + }, + + { + "valid function testing && 3", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 + 30 return 0 || (a1 && a2) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(1)}, + }, + { + "valid function testing LET ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "valid function testing IF THEN form1 fallthrough", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "valid function testing IF THEN form1 THEN case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(0)}, + }, + { + "valid function testing IF THEN ELSE form1 THEN case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(0)}, + }, { + "valid function testing IF THEN ELSE form1 ELSE case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "valid function testing != success ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 != 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, + { + "valid function testing != failed", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 != 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "3", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + + { + "valid function testing <> ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 <> 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "invalid operator testing = ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 = 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(99)}, + }, + { + "valid function testing > success ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 > 1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing > failed ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if 1 > a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing >= success", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 >= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing >= failed", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 >= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing < success ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 < 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing < failed ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 < 3 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "5", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing <= success ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 <= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing <= success ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 <= 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing string == success", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 == a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing string == success", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 == "asdf" then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing string == failed", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 == a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing !string success ", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if !a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "", "a2": "asdf1"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing !string fail ", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if !a1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "a1", "a2": "asdf1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing string != ", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 != a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdfz"}, + nil, + Variable{Type: Uint64, Value: uint64(99)}, + }, { + "valid function testing LOR ", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 != a2 || a1 != a2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdf"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing comparision of uint64 /string ", + `Function TestRun(a1 String,a2 String) Uint64 + 10 dim s1, s2 as Uint64 + 13 LET s1 = 99 + 15 if a1 != s1 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "asdf", "a2": "asdfz"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(99)}, + }, + + { + "valid function arbitrary function evaluation", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 fact_recursive(10) + 20 LET s1 = fact_recursive(10) + 30 return s1 + End Function + Function fact_recursive(s Uint64) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + + { + "Invalid function with 2 string params substractions", + `Function TestRun(a1 String,a2 String) String + 10 dim s1, s2 as Uint64 + 20 + 30 return a1 - a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "4", "a2": "4"}, + fmt.Errorf("dummy"), + Variable{Type: String, Value: "44"}, + }, +} + +// run the test +func Test_execution(t *testing.T) { + for _, test := range execution_tests { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// ensure 100% coverage of IF +var execution_tests_if = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing IF THEN form1 fallthrough", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "valid function testing IF THEN form1 THEN case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(0)}, + }, + { + "valid function testing IF THEN ELSE form1 THEN case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "2", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(0)}, + }, { + "valid function testing IF THEN ELSE form1 ELSE case", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, + + { + "invalid function testing IF THEN form1 malformed if then", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 = != 2 thenn GOTO 30 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 malformed else", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 30 ELSEE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 malformed then line number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 0 ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 malformed then else number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 20 ELSE GOTO 0 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 malformed unparseable then line number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO ewr + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN ELSE form1 malformed unparseable then line number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO ewr ELSE GOTO 20 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 malformed unparseable else number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 20 ELSE GOTO ewr + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + { + "invalid function testing IF THEN form1 unknownelse number", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 if a1 == 2 then GOTO 20 ELSE GOTO 43535 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing IF THEN invalid IF expression", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 13 dim x1 as String + 15 if x1 then GOTO 20 ELSE GOTO 43535 + 20 RETURN 2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "77", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, +} + +// run the test +func Test_IF_execution(t *testing.T) { + for _, test := range execution_tests_if { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// ensure 100% coverage of DIM +var execution_tests_dim = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing DIM ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing DIM address", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Address + 15 GOTO 30 + 17 RETURN 0 + 20 + 30 return a1 + a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing DIM blob", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Blob + 15 GOTO 30 + 17 RETURN 0 + 20 + 30 return a1 + a2 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid dim expression collision between arguments and local variable", + `Function TestRun(s Uint64) Uint64 + 10 dim s as Uint64 + 20 + 30 return 8 + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "invalid dim expression invalid variable name", + `Function TestRun(s Uint64) Uint64 + 10 dim 1s as Uint64 + 20 + 30 return 8 + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "invalid dim expression invalid 2nd variable name", + `Function TestRun(s Uint64) Uint64 + 10 dim s1,2s2 as Uint64 + 20 + 30 return 8 + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "invalid dim expression invalid dim syntax", + `Function TestRun(s Uint64) Uint64 + 10 dim tu ajs Uint64 + 20 + 30 return 8 + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, { + "invalid dim expression invalid dim syntax", + `Function TestRun(s Uint64) Uint64 + 10 dim tu as UUint64 + 20 + 30 return 8 + End Function + `, + "TestRun", + map[string]interface{}{"s": "8"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(8)}, + }, +} + +// run the test +func Test_DIM_execution(t *testing.T) { + for _, test := range execution_tests_dim { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// ensure 100% coverage of LET +var execution_tests_let = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing LET ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing LET Address", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 12 dim add1,add2 as Address + 13 let add1 = add2 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "valid function testing LET blob", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 12 dim add1, add2 as Blob + 13 let add1 = add2 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing LET ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 LET + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing LET undefined local variable", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 LET s3 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing LET malformed expression", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 20 LET s1 = ((a1) + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing LET putting int into string", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as String + 20 LET s1 = (a1) + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, +} + +// run the test +func Test_LET_execution(t *testing.T) { + for _, test := range execution_tests_let { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// ensure 100% coverage of PRINT +var execution_tests_print = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing PRINT ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 + 17 PRINT "%d %d" s1 s2 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing PRINT ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 + 17 PRINT "%d %d" s1 s2 s3 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, +} + +// run the test +func Test_PRINT_execution(t *testing.T) { + for _, test := range execution_tests_print { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// generic cases of coding mistakes +var execution_tests_generic = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "invalid function testing Generic case if function does not return ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 + 17 PRINT "%d %d" s1 s2 + 20 LET s1 = a1 + a2 + // 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing Generic case if invalid expression evaluated ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 + 17 dummy (s1 s2 s3 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing Generic case if valid expression but not a function call ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 + 17 2 +3 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, + + // these case check callability of unexported functions etc + { + "valid function testing Invalid Rune ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + string([]byte{0x80}), // EntryPoint checking + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing executing first non-ascii function ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "☺☻TestRun", // EntryPoint checking + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing executing first non-letter function", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "1TestRun", // EntryPoint checking + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing executing unexported function ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "testRun", // EntryPoint checking + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "invalid function testing executing non-existing function ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "TestRun1", // no function exists + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, + + { + "valid function testing parameter missing ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a3": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, + + { + "valid function testing Invalid parameter uint64 ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "-1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, + { + "valid function testing use unknown variable ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1*a3 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing use uint64 larger than 64 bits ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1* 118446744073709551615 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, + + { + "valid function testing recursive FACTORIAL ", + `Function fact_recursive(s Uint64) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) + End Function + Function TestRun(a1 Uint64) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive(a1) + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + + { + "invalid function testing paramter skipped ", + `Function fact_recursive(s Uint64) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) + End Function + Function TestRun(a1 Uint64) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive() + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + { + "invalid function testing unknown function called ", + `Function fact_recursive(s Uint64) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) + End Function + Function TestRun(a1 Uint64) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive_unknown(a1) + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + { + "invalid function testing error withing sub function call", + `Function fact_recursive(s Uint64) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1) * ( + End Function + Function TestRun(a1 Uint64) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive(a1) + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + + { + "valid function testing sub function call without return", + `Function dummy(s Uint64) + + 20 RETURN + End Function + Function TestRun(a1 Uint64) Uint64 + 10 dim result as Uint64 + 20 LET result = a1 + 60 dummy(a1) + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(10)}, + }, + { + "valid function testing recursive FACTORIAL ", + `Function fact_recursive(s Uint64, a Address, b Blob, str String) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1,a,b,str) + End Function + Function TestRun(a1 Uint64,a Address, b Blob, str String) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive(a1,a,b,str) + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1", "a": "address param", "b": "blob param", "str": "str_param"}, + nil, + Variable{Type: Uint64, Value: uint64(3628800)}, + }, + { + "valid function testing recursive FACTORIAL ", + `Function fact_recursive(s Uint64, a Address, b Blob, str String) Uint64 + 10 IF s == 1 THEN GOTO 20 ELSE GOTO 30 + 20 RETURN 1 + 30 RETURN s * fact_recursive(s -1,a,b,str) + End Function + Function TestRun(a1 Uint64,a Address, b Blob, str String) Uint64 + 10 dim result as Uint64 + 20 LET result = fact_recursive(a1,a,b,str) + 60 printf "FACTORIAL of %d = %d" s result + 70 RETURN result + End Function + `, + "TestRun", + map[string]interface{}{"a1": "10", "a2": "1", "a": "address param", "b": "blob param", "str": "str_param"}, + nil, + Variable{Type: Uint64, Value: uint64(3628800)}, + }, +} + +// run the test +func Test_Generic_execution(t *testing.T) { + for _, test := range execution_tests_generic { + + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + + } +} + +// ensure 100% coverage of RETURN +var execution_tests_return = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing RETURN uint64 ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "valid function testing RETURN String ", + `Function TestRun(a1 String,a2 Uint64) String + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: String, Value: "1"}, + }, + { + "valid function testing RETURN Address ", + `Function TestRun(a1 Address,a2 Uint64) Address + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Address, Value: "1"}, + }, { + "valid function testing RETURN Blob ", + `Function TestRun(a1 Blob,a2 Uint64) Blob + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Blob, Value: "1"}, + }, { + "valid function testing non returning function returning nothing ", + `Function TestRun(a1 Uint64,a2 Uint64) + 30 return + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Invalid}, + }, + { + "valid function testing non returning function returning something ", + `Function TestRun(a1 Uint64,a2 Uint64) + 30 return a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: "1"}, + }, { + "invalid function testing RETURN uint64 ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, { + "invalid function testing RETURN contains unparseable function ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 30 return ( a1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(1)}, + }, +} + +// run the test +func Test_RETURN_execution(t *testing.T) { + for _, test := range execution_tests_return { + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + } +} + +// ensure 100% coverage of GOTO +var execution_tests_goto = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing GOTO ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing GOTO ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO qeee + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing GOTO ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, { + "invalid function testing GOTO ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO 0 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return s1 + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + fmt.Errorf("dummy"), + Variable{Type: Uint64, Value: uint64(2)}, + }, +} + +// run the test +func Test_GOTO_execution(t *testing.T) { + for _, test := range execution_tests_goto { + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + } +} diff --git a/dvm/dvm_functions.go b/dvm/dvm_functions.go new file mode 100644 index 0000000..80437a5 --- /dev/null +++ b/dvm/dvm_functions.go @@ -0,0 +1,332 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "go/ast" +import "strings" +import "math/big" + +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/cryptography/crypto" + +// this files defines external functions which can be called in DVM +// for example to load and store data from the blockchain and other basic functions + +// random number generator is the basis +// however, further investigation is needed if we would like to enable users to use pederson commitments +// they can be used like +// original SC developers delivers a pederson commitment to SC as external oracle +// after x users have played lottery, dev reveals the commitment using which the winner is finalised +// this needs more investigation +// also, more investigation is required to enable predetermined external oracles + +// this will handle all internal functions which may be required/necessary to expand DVM functionality +func (dvm *DVM_Interpreter) Handle_Internal_Function(expr *ast.CallExpr, func_name string) (handled bool, result interface{}) { + var err error + _ = err + switch { + + // TODO evaluate why not use a blackbox function which can be used for as many returns as possible + // the function should behave similar to how RDMSR intel instruction works. + // this can allow as future compatibility etc + case strings.EqualFold(func_name, "MAJOR_VERSION"): + if len(expr.Args) != 0 { // expression without limit + panic("MAJOR_VERSION expects no parameters") + } else { + return true, 0 + } + + case strings.EqualFold(func_name, "Load"): + if len(expr.Args) != 1 { + panic("Load function expects a single varible as parameter") + } + // evaluate the argument and use the result + key := dvm.eval(expr.Args[0]) + switch k := key.(type) { + + case uint64: + return true, dvm.Load(Variable{Type: Uint64, Value: k}) + case string: + return true, dvm.Load(Variable{Type: String, Value: k}) + default: + panic("This variable cannot be loaded") + } + case strings.EqualFold(func_name, "Exists"): + if len(expr.Args) != 1 { + panic("Exists function expects a single varible as parameter") + } + // evaluate the argument and use the result + key := dvm.eval(expr.Args[0]) + switch k := key.(type) { + + case uint64: + return true, dvm.Exists(Variable{Type: Uint64, Value: k}) + case string: + return true, dvm.Exists(Variable{Type: String, Value: k}) + default: + panic("This variable cannot be loaded") + } + + case strings.EqualFold(func_name, "Store"): + if len(expr.Args) != 2 { + panic("Store function expects 2 variables as parameter") + } + key_eval := dvm.eval(expr.Args[0]) + value_eval := dvm.eval(expr.Args[1]) + var key, value Variable + switch k := key_eval.(type) { + case uint64: + key = Variable{Type: Uint64, Value: k} + + case string: + key = Variable{Type: String, Value: k} + default: + panic("This variable cannot be stored") + } + + switch k := value_eval.(type) { + case uint64: + value = Variable{Type: Uint64, Value: k} + case string: + value = Variable{Type: String, Value: k} + default: + panic("This variable cannot be stored") + } + + dvm.Store(key, value) + return true, nil + + case strings.EqualFold(func_name, "RANDOM"): + if len(expr.Args) >= 2 { + panic("RANDOM function expects 0 or 1 number as parameter") + } + + if len(expr.Args) == 0 { // expression without limit + return true, dvm.State.RND.Random() + } + + range_eval := dvm.eval(expr.Args[0]) + switch k := range_eval.(type) { + case uint64: + return true, dvm.State.RND.Random_MAX(k) + default: + panic("This variable cannot be randomly generated") + } + case strings.EqualFold(func_name, "SCID"): + if len(expr.Args) != 0 { + panic("SCID function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.SCID.String() + case strings.EqualFold(func_name, "BLID"): + if len(expr.Args) != 0 { + panic("BLID function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.BLID.String() + case strings.EqualFold(func_name, "TXID"): + if len(expr.Args) != 0 { + panic("TXID function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.TXID.String() + + case strings.EqualFold(func_name, "BLOCK_HEIGHT"): + if len(expr.Args) != 0 { + panic("BLOCK_HEIGHT function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.BL_HEIGHT + case strings.EqualFold(func_name, "BLOCK_TOPOHEIGHT"): + if len(expr.Args) != 0 { + panic("BLOCK_TOPOHEIGHT function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.BL_TOPOHEIGHT + + case strings.EqualFold(func_name, "SIGNER"): + if len(expr.Args) != 0 { + panic("SIGNER function expects 0 parameters") + } + return true, dvm.State.Chain_inputs.Signer + + case strings.EqualFold(func_name, "UPDATE_SC_CODE"): + if len(expr.Args) != 1 { + panic("UPDATE_SC_CODE function expects 1 parameters") + } + + code_eval := dvm.eval(expr.Args[0]) + switch k := code_eval.(type) { + case string: + dvm.State.Store.Keys[DataKey{Key: Variable{Type: String, Value: "C"}}] = Variable{Type: String, Value: k} // TODO verify code authenticity how + return true, uint64(1) + + return true, uint64(0) // fallthrough not supported in type switch + + default: + return true, uint64(0) + } + + case strings.EqualFold(func_name, "IS_ADDRESS_VALID"): // checks whether the address is valid DERO address + if len(expr.Args) != 1 { + panic("IS_ADDRESS_VALID function expects 1 parameters") + } + + addr_eval := dvm.eval(expr.Args[0]) + switch k := addr_eval.(type) { + case string: + + signer_raw := new(crypto.Point) + if err = signer_raw.DecodeCompressed([]byte(k)); err == nil { + return true, uint64(1) + } + return true, uint64(0) // fallthrough not supported in type switch + + default: + return true, uint64(0) + } + + case strings.EqualFold(func_name, "ADDRESS_RAW"): // returns a string of 33 bytes if everything is okay + if len(expr.Args) != 1 { + panic("ADDRESS_RAW function expects 1 parameters") + } + + addr_eval := dvm.eval(expr.Args[0]) + switch k := addr_eval.(type) { + case string: + if addr, err := rpc.NewAddress(k); err == nil { + return true, string(addr.Compressed()) + } + + return true, nil // fallthrough not supported in type switch + default: + return true, nil + } + + case strings.EqualFold(func_name, "ADDRESS_STRING"): // returns a DERO mainnet address if everything is okay + if len(expr.Args) != 1 { + panic("ADDRESS_STRING function expects 1 parameters") + } + + addr_eval := dvm.eval(expr.Args[0]) + switch k := addr_eval.(type) { + case string: + p := new(crypto.Point) + if err = p.DecodeCompressed([]byte(k)); err == nil { + + addr := rpc.NewAddressFromKeys(p) + return true, addr.String() + } + + return true, nil // fallthrough not supported in type switch + default: + return true, nil + } + + case strings.EqualFold(func_name, "SEND_DERO_TO_ADDRESS"): + if len(expr.Args) != 2 { + panic("SEND_DERO_TO_ADDRESS function expects 2 parameters") + } + + addr_eval := dvm.eval(expr.Args[0]) + amount_eval := dvm.eval(expr.Args[1]) + + p := new(crypto.Point) + if err = p.DecodeCompressed([]byte(addr_eval.(string))); err != nil { + panic("address must be valid DERO network address") + } + + if _, ok := amount_eval.(uint64); !ok { + panic("amount must be valid uint64") + } + + if amount_eval.(uint64) == 0 { + return true, amount_eval + } + + dvm.State.Store.SendExternal(dvm.State.Chain_inputs.SCID, addr_eval.(string), amount_eval.(uint64)) // add record for external transfer + + return true, amount_eval + + case strings.EqualFold(func_name, "DEROVALUE"): + if len(expr.Args) != 0 { // expression without limit + panic("DEROVALUE expects no parameters") + } else { + return true, dvm.State.DERO_Received + } + case strings.EqualFold(func_name, "TOKENVALUE"): + if len(expr.Args) != 0 { // expression without limit + panic("TOKENVALUE expects no parameters") + } else { + return true, dvm.State.Token_Received + } + + case strings.EqualFold(func_name, "ADD_VALUE"): + if len(expr.Args) != 2 { + panic("ADD_VALUE function expects 2 parameters") + } + + addr_eval := dvm.eval(expr.Args[0]) + amount_eval := dvm.eval(expr.Args[1]) + + p := new(crypto.Point) + if err = p.DecodeCompressed([]byte(addr_eval.(string))); err != nil { + panic("address must be valid DERO network address") + } + + if _, ok := amount_eval.(uint64); !ok { + panic("amount must be valid uint64") + } + + if amount_eval.(uint64) > 21*100000 { + panic("pls test with small amounts, for better debug reasons ") + } + + // if exists value, load it + var ebalance *crypto.ElGamal + if ebalance_bytes, found := dvm.State.Store.RawLoad([]byte(addr_eval.(string))); found { + ebalance = new(crypto.ElGamal).Deserialize(ebalance_bytes) + + } else { + + ebalance = crypto.ConstructElGamal(p.G1(), crypto.ElGamal_BASE_G) // init zero balance + + } + + ebalance = ebalance.Plus(new(big.Int).SetUint64(amount_eval.(uint64))) // add value to users balance homomorphically + + dvm.State.Store.RawStore([]byte(addr_eval.(string)), ebalance.Serialize()) + + return true, amount_eval + + } + return false, nil +} + +// the load/store functions are sandboxed and thus cannot affect any other SC storage +// loads a variable from store +func (dvm *DVM_Interpreter) Load(key Variable) interface{} { + var found uint64 + result := dvm.State.Store.Load(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}, &found) + return result.Value +} + +// whether a variable exists in store or not +func (dvm *DVM_Interpreter) Exists(key Variable) uint64 { + var found uint64 + dvm.State.Store.Load(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}, &found) + return found +} + +func (dvm *DVM_Interpreter) Store(key Variable, value Variable) { + dvm.State.Store.Store(DataKey{SCID: dvm.State.Chain_inputs.SCID, Key: key}, value) +} diff --git a/dvm/dvm_functions_test.go b/dvm/dvm_functions_test.go new file mode 100644 index 0000000..e974614 --- /dev/null +++ b/dvm/dvm_functions_test.go @@ -0,0 +1,132 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +//import "fmt" +import "reflect" +import "testing" + +import "github.com/deroproject/derohe/cryptography/crypto" + +// ensure 100% coverage of functions execution +var execution_tests_functions = []struct { + Name string + Code string + EntryPoint string + Args map[string]interface{} + Eerr error // execute error + result Variable // execution result +}{ + { + "valid function testing BLOCK_HEIGHT() ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return BLOCK_HEIGHT() + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(5)}, + }, + { + "valid function testing BLOCK_TOPOHEIGHT() ", + `Function TestRun(a1 Uint64,a2 Uint64) Uint64 + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return BLOCK_TOPOHEIGHT() + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: Uint64, Value: uint64(9)}, + }, + { + "valid function testing SCID() ", + `Function TestRun(a1 Uint64,a2 Uint64) String + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return SCID() + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: String, Value: crypto.ZEROHASH.String()}, + }, { + "valid function testing BLID() ", + `Function TestRun(a1 Uint64,a2 Uint64) String + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return BLID() + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: String, Value: crypto.ZEROHASH.String()}, + }, { + "valid function testing TXID() ", + `Function TestRun(a1 Uint64,a2 Uint64) String + 10 dim s1, s2 as Uint64 + 15 GOTO 20 + 17 RETURN 0 + 20 LET s1 = a1 + a2 + 30 return TXID() + End Function + `, + "TestRun", + map[string]interface{}{"a1": "1", "a2": "1"}, + nil, + Variable{Type: String, Value: crypto.ZEROHASH.String()}, + }, +} + +// run the test +func Test_FUNCTION_execution(t *testing.T) { + for _, test := range execution_tests_functions { + sc, _, err := ParseSmartContract(test.Code) + if err != nil { + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected nil\nActual %s\n", test.Name, err) + } + + state := &Shared_State{Chain_inputs: &Blockchain_Input{BL_HEIGHT: 5, BL_TOPOHEIGHT: 9, SCID: crypto.ZEROHASH, + BLID: crypto.ZEROHASH, TXID: crypto.ZEROHASH}} + result, err := RunSmartContract(&sc, test.EntryPoint, state, test.Args) + switch { + case test.Eerr == nil && err == nil: + if !reflect.DeepEqual(result, test.result) { + t.Fatalf("Error while executing smart contract \"%s\"\nExpected result %v\nActual result %v\n", test.Name, test.result, result) + } + case test.Eerr != nil && err != nil: // pass + case test.Eerr == nil && err != nil: + fallthrough + case test.Eerr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Eerr, err) + } + } +} diff --git a/dvm/dvm_parse_test.go b/dvm/dvm_parse_test.go new file mode 100644 index 0000000..b101f23 --- /dev/null +++ b/dvm/dvm_parse_test.go @@ -0,0 +1,239 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "testing" +import "fmt" + +var evalList = []struct { + Name string + Code string + Perr error // parse error +}{ + // some basics + { + "demo 1", + `Function HelloWorld(s Uint64) Uint64 + 900 Return s + End Function + `, + nil, + }, { + "function beginning skipped", + `Functionq HelloWorld(s Uint64) Uint64 + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, + { + "Invalid function name", + `Function 1Hello(s Uint64) Uint64 + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid argument variable name", + `Function HelloWorld(1s Uint64) Uint64 + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid variable type", + `Function HelloWorld(a1 Uint64, a2 String1,) Uint64 + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid line number minimum", + `Function HelloWorld(s Uint64) Uint64 + 0 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid line number maximum", + `Function HelloWorld(s Uint64) Uint64 + 18446744073709551615 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid line number duplicate", + `Function HelloWorld(s Uint64) Uint64 + 10 Return s + 10 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid line number descending", + `Function HelloWorld(s Uint64) Uint64 + 10 Return s + 5 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "Function End missing", + `Function HelloWorld(s Uint64) Uint64 + 10 dim 1s as Uint64 + 20 Return s + Function + `, + fmt.Errorf("dummy"), + }, { + "Function Nesting now allowed", + `Function HelloWorld(s Uint64) Uint64 + 10 dim 1s as Uint64 + 20 Return s + Function HelloWorld() + + End Function + `, + fmt.Errorf("dummy"), + }, + { + "Function \" \" between arguments", + `Function HelloWorld(s Uint64 s2 String ) Uint64 + 10 dim s as Uint64 + 20 Return s + End Function + `, + nil, + }, + { + "Function , between arguments", + `Function HelloWorld(s Uint64 , s2 String ) Uint64 + 10 dim s as Uint64 + 20 Return s + End Function + `, + nil, + }, + { + "Missing end function", + `Function HelloWorld(s Uint64) Uint64 + 900 Return s + + `, + fmt.Errorf("dummy"), + }, { + "negative line number", + `Function HelloWorld(s Uint64) Uint64 + -900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "negative line number", + `Function HelloWorld(s Uint64) Uint64 + -900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "REM line number", + `Function HelloWorld(s Uint64) Uint64 + REM line number + 900 Return s + End Function + `, + nil, + }, { + "// comments are skipped", + `Function HelloWorld(s Uint64) Uint64 + // line number + 900 Return s + End Function + `, + nil, + }, { + "invalid function name", + `Function ` + string([]byte{0x80, 0x80, 0x80, 0x80, 0x80}) + `HelloWorld(s Uint64) Uint64 + // line number + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "function name missin", + `Function + // line number + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "function name missin (", + `Function HelloWorld s Uint64) Uint64 + // line number + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "function name missin )", + `Function HelloWorld ( + // line number + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "function name missin argument typ)", + `Function HelloWorld ( s1 + // line number + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, { + "function name unknonw argument type", + `Function HelloWorld ( s1 monkey ) + // line number + 900 Return + End Function + `, + fmt.Errorf("dummy"), + }, { + "Invalid return type", + `Function HelloWorld(s Uint64) Stream + 900 Return s + End Function + `, + fmt.Errorf("dummy"), + }, +} + +// run the test +func TestEval(t *testing.T) { + for _, test := range evalList { + _, _, err := ParseSmartContract(test.Code) + switch { + case test.Perr == nil && err == nil: // pass + case test.Perr != nil && err != nil: // pass + case test.Perr == nil && err != nil: + fallthrough + case test.Perr != nil && err == nil: + t.Fatalf("Error while parsing smart contract \"%s\"\nExpected %s\nActual %s\n", test.Name, test.Perr, err) + } + } +} diff --git a/dvm/dvm_store.go b/dvm/dvm_store.go new file mode 100644 index 0000000..8d17ded --- /dev/null +++ b/dvm/dvm_store.go @@ -0,0 +1,324 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "fmt" +import "encoding/binary" +import "github.com/deroproject/derohe/cryptography/crypto" + +const DVAL = "DERO_BALANCE" // DERO Values are stored in this variable +const CHANGELOG = "CHANGELOG" + +// this package exports an interface which is used by blockchain to persist/query data + +type DataKey struct { + SCID crypto.Hash // tx which created the the contract or contract ID + Key Variable +} + +type DataAtom struct { + Key DataKey + + Prev_Value Variable // previous Value if any + Value Variable // current value if any +} + +type TransferInternal struct { + Received []uint64 + Sent []uint64 +} + +// any external tranfers +type TransferExternal struct { + Address string `cbor:"A,omitempty" json:"A,omitempty"` // transfer to this blob + Amount uint64 `cbor:"V,omitempty" json:"V,omitempty"` // Amount in Atomic units +} + +type SC_Transfers struct { + BalanceAtStart uint64 // value at start + TransferI TransferInternal // all internal transfers, SC to other SC + TransferE []TransferExternal // all external transfers, SC to external wallets +} + +// all SC load and store operations will go though this +type TX_Storage struct { + DiskLoader func(DataKey, *uint64) Variable + DiskLoaderRaw func([]byte) ([]byte, bool) + SCID crypto.Hash + BalanceAtStart uint64 // at runtime this will be fed balance + Keys map[DataKey]Variable // this keeps the in-transit DB updates, just in case we have to discard instantly + RawKeys map[string][]byte + + Transfers map[crypto.Hash]SC_Transfers // all transfers ( internal/external ) +} + +var DVM_STORAGE_BACKEND DVM_Storage_Loader // this variable can be hijacked at runtime to offer different stores such as RAM/file/DB etc + +type DVM_Storage_Loader interface { + Load(DataKey, *uint64) Variable + Store(DataKey, Variable) + RawLoad([]byte) ([]byte, bool) + RawStore([]byte, []byte) +} + +// initialize tx store +func Initialize_TX_store() (tx_store *TX_Storage) { + tx_store = &TX_Storage{Keys: map[DataKey]Variable{}, RawKeys: map[string][]byte{}, Transfers: map[crypto.Hash]SC_Transfers{}} + return +} + +func (tx_store *TX_Storage) RawLoad(key []byte) (value []byte, found bool) { + value, found = tx_store.RawKeys[string(key)] + + if !found { + if tx_store.DiskLoaderRaw == nil { + return + } + value, found = tx_store.DiskLoaderRaw(key) + } + return +} + +func (tx_store *TX_Storage) RawStore(key []byte, value []byte) { + tx_store.RawKeys[string(key)] = value + return +} + +// this will load the variable, and if the key is found +func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Variable) { + + //fmt.Printf("Loading %+v \n", dkey) + + *found_value = 0 + if result, ok := tx_store.Keys[dkey]; ok { // if it was modified in current TX, use it + *found_value = 1 + return result + } + + if tx_store.DiskLoader == nil { + panic("DVM_STORAGE_BACKEND is not ready") + } + + value = tx_store.DiskLoader(dkey, found_value) + + return +} + +// store variable +func (tx_store *TX_Storage) Store(dkey DataKey, v Variable) { + //fmt.Printf("Storing request %+v : %+v\n", dkey, v) + + tx_store.Keys[dkey] = v +} + +// store variable +func (tx_store *TX_Storage) SendExternal(sender_scid crypto.Hash, addr_str string, amount uint64) { + + //fmt.Printf("Transfer to external address : %+v\n", addr_str) + + tx_store.Balance(sender_scid) // load from disk if required + transfer := tx_store.Transfers[sender_scid] + transfer.TransferE = append(transfer.TransferE, TransferExternal{Address: addr_str, Amount: amount}) + tx_store.Transfers[sender_scid] = transfer + tx_store.Balance(sender_scid) // recalculate balance panic if any issues + +} + +// if TXID is not already loaded, load it +func (tx_store *TX_Storage) ReceiveInternal(scid crypto.Hash, amount uint64) { + + tx_store.Balance(scid) // load from disk if required + transfer := tx_store.Transfers[scid] + transfer.TransferI.Received = append(transfer.TransferI.Received, amount) + tx_store.Transfers[scid] = transfer + tx_store.Balance(scid) // recalculate balance panic if any issues +} + +func (tx_store *TX_Storage) SendInternal(sender_scid crypto.Hash, receiver_scid crypto.Hash, amount uint64) { + + //sender side + { + tx_store.Balance(sender_scid) // load from disk if required + transfer := tx_store.Transfers[sender_scid] + transfer.TransferI.Sent = append(transfer.TransferI.Sent, amount) + tx_store.Transfers[sender_scid] = transfer + tx_store.Balance(sender_scid) // recalculate balance panic if any issues + } + + { + tx_store.Balance(receiver_scid) // load from disk if required + transfer := tx_store.Transfers[receiver_scid] + transfer.TransferI.Received = append(transfer.TransferI.Received, amount) + tx_store.Transfers[receiver_scid] = transfer + tx_store.Balance(receiver_scid) // recalculate balance panic if any issues + } + +} + +func GetBalanceKey(scid crypto.Hash) (x DataKey) { + x.SCID = scid + x.Key = Variable{Type: String, Value: DVAL} + return x +} + +/* +func GetNormalKey(scid crypto.Key, v Variable) (x DataKey) { + x.SCID = scid + x.Key = Variable {Type:v.Type, Value: v.Value} + return x +} +*/ + +// this will give the balance, will load the balance from disk +func (tx_store *TX_Storage) Balance(scid crypto.Hash) uint64 { + + if scid != tx_store.SCID { + fmt.Printf("scid %s SCID %s\n", scid, tx_store.SCID) + fmt.Printf("tx_store internal %+v\n", tx_store) + panic("cross SC balance calls are not supported") + } + if _, ok := tx_store.Transfers[scid]; !ok { + + var transfer SC_Transfers + /* + found_value := uint64(0) + value := tx_store.Load(GetBalanceKey(scid), &found_value) + + if found_value == 0 { + panic(fmt.Sprintf("SCID %s is not loaded", scid)) // we must load it from disk + } + + if value.Type != Uint64 { + panic(fmt.Sprintf("SCID %s balance is not uint64, HOW ??", scid)) // we must load it from disk + } + */ + + transfer.BalanceAtStart = tx_store.BalanceAtStart + tx_store.Transfers[scid] = transfer + } + + transfers := tx_store.Transfers[scid] + balance := transfers.BalanceAtStart + + // replay all receives/sends + + // handle all internal receives + for _, amt_received := range transfers.TransferI.Received { + c := balance + amt_received + + if c >= balance { + balance = c + } else { + panic("uint64 overflow wraparound attack") + } + } + + // handle all internal sends + for _, amt_sent := range transfers.TransferI.Sent { + if amt_sent >= balance { + panic("uint64 underflow wraparound attack") + } + balance = balance - amt_sent + } + + // handle all external sends + for _, trans := range transfers.TransferE { + if trans.Amount >= balance { + panic("uint64 underflow wraparound attack") + } + balance = balance - trans.Amount + } + + return balance +} + +// whether the scid has enough balance +func (tx_store *TX_Storage) HasBalance(scid crypto.Key, amount uint64) { + +} + +// why should we not hash the return value to return a hash value +// using entire key could be useful, if DB can somehow link between them in the form of buckets and all +func (dkey DataKey) MarshalBinary() (ser []byte, err error) { + ser, err = dkey.Key.MarshalBinary() + return +} + +func (dkey DataKey) MarshalBinaryPanic() (ser []byte) { + var err error + if ser, err = dkey.Key.MarshalBinary(); err != nil { + panic(err) + } + return +} + +// these are used by lowest layers +func (v Variable) MarshalBinary() (data []byte, err error) { + data = append(data, byte(v.Type)) // add object type + switch v.Type { + case Invalid: + return + case Uint64: + var buf [binary.MaxVarintLen64]byte + done := binary.PutUvarint(buf[:], v.Value.(uint64)) // uint64 data type + data = append(data, buf[:done]...) + case Blob: + panic("not implemented") + case String: + data = append(data, ([]byte(v.Value.(string)))...) // string + default: + panic("unknown variable type not implemented") + } + return +} +func (v Variable) MarshalBinaryPanic() (ser []byte) { + var err error + if ser, err = v.MarshalBinary(); err != nil { + panic(err) + } + return +} + +func (v *Variable) UnmarshalBinary(buf []byte) (err error) { + if len(buf) < 1 || Vtype(buf[0]) == Invalid { + return fmt.Errorf("invalid, probably corruption") + } + + switch Vtype(buf[0]) { + case Invalid: + return fmt.Errorf("Invalid cannot be deserialized") + case Uint64: + v.Type = Uint64 + var n int + v.Value, n = binary.Uvarint(buf[1:]) // uint64 data type + if n <= 0 { + panic("corruption in DB") + return fmt.Errorf("corruption in DB") + } + case String: + v.Type = String + v.Value = string(buf[1:]) + return nil + case Blob: + panic("blob not implemented") // an encrypted blob, used to add data to blockchain without knowing address + + default: + panic("unknown variable type not implemented") + + } + return +} diff --git a/dvm/dvm_store_memory.go b/dvm/dvm_store_memory.go new file mode 100644 index 0000000..480fc99 --- /dev/null +++ b/dvm/dvm_store_memory.go @@ -0,0 +1,77 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package dvm + +import "fmt" + +// this file implements a RAM store backend for testing purposes + +type Memory_Storage struct { + Atoms []DataAtom // all modification operations have to played/reverse in this order + Keys map[DataKey]Variable + RawKeys map[string][]byte +} + +var Memory_Backend Memory_Storage + +func init() { + DVM_STORAGE_BACKEND = &Memory_Backend +} + +func (mem_store *Memory_Storage) RawLoad(key []byte) (value []byte, found bool) { + value, found = mem_store.RawKeys[string(key)] + return +} + +func (mem_store *Memory_Storage) RawStore(key []byte, value []byte) { + mem_store.RawKeys[string(key)] = value + return +} + +// this will load the variable, and if the key is found +func (mem_store *Memory_Storage) Load(dkey DataKey, found_value *uint64) (value Variable) { + + *found_value = 0 + // if it was modified in current TX, use it + if result, ok := mem_store.Keys[dkey]; ok { + *found_value = 1 + return result + } + + return +} + +// store variable +func (mem_store *Memory_Storage) Store(dkey DataKey, v Variable) { + + fmt.Printf("Storing %+v : %+v\n", dkey, v) + var found uint64 + old_value := mem_store.Load(dkey, &found) + + var atom DataAtom + atom.Key = dkey + atom.Value = v + if found != 0 { + atom.Prev_Value = old_value + } else { + atom.Prev_Value = Variable{} + } + + mem_store.Keys[atom.Key] = atom.Value + mem_store.Atoms = append(mem_store.Atoms, atom) + +} diff --git a/globals/globals.go b/globals/globals.go index 6315e7b..d4d9d42 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -24,12 +24,14 @@ import "strings" import "strconv" import "math/big" import "path/filepath" +import "runtime/debug" import "golang.org/x/net/proxy" +import "github.com/romana/rlog" import "github.com/sirupsen/logrus" import log "github.com/sirupsen/logrus" import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/rpc" type ChainState int // block chain can only be in 2 state, either SYNCRONISED or syncing @@ -66,17 +68,20 @@ var Dialer proxy.Dialer = proxy.Direct // for proxy and direct connections // all program arguments are available here var Arguments map[string]interface{} +func InitNetwork() { + Config = config.Mainnet // default is mainnnet + if Arguments["--testnet"].(bool) == true { // setup testnet if requested + Config = config.Testnet + } + +} func Initialize() { var err error _ = err Arguments["--testnet"] = true // force testnet every where - Config = config.Mainnet // default is mainnnet - if Arguments["--testnet"].(bool) == true { // setup testnet if requested - Config = config.Testnet - } - + InitNetwork() // formatter := &logrus.TextFormatter{DisableColors : true} //Logger= &logrus.Logger{Formatter:formatter} @@ -119,6 +124,14 @@ func Initialize() { } +// used to recover in case of panics +func Recover() { + if r := recover(); r != nil { + rlog.Warnf("Recovered while handling connection, Stack trace below", r) + rlog.Warnf("Stack trace \n%s", debug.Stack()) + } +} + // tells whether we are in mainnet mode // if we are not mainnet, we are a testnet, // we will only have a single mainnet ,( but we may have one or more testnets ) @@ -163,7 +176,7 @@ func CTXString(entry *logrus.Entry) string { // newbies, see type the next in python interpretor "3.33-3.13" // func FormatMoney(amount uint64) string { - return FormatMoneyPrecision(amount, 5) // default is 8 precision after floating point + return FormatMoneyPrecision(amount, 5) // default is 5 precision after floating point } // 0 @@ -192,12 +205,12 @@ func FormatMoneyPrecision(amount uint64, precision int) string { float_amount, _, _ := big.ParseFloat(fmt.Sprintf("%d", amount), 10, 0, big.ToZero) result := new(big.Float) result.Quo(float_amount, hard_coded_decimals) - return result.Text('f', precision) // 8 is display precision after floating point + return result.Text('f', precision) // 5 is display precision after floating point } // this will parse and validate an address, in reference to the current main/test mode -func ParseValidateAddress(str string) (addr *address.Address, err error) { - addr, err = address.NewAddress(strings.TrimSpace(str)) +func ParseValidateAddress(str string) (addr *rpc.Address, err error) { + addr, err = rpc.NewAddress(strings.TrimSpace(str)) if err != nil { return } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6b44278 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/deroproject/derohe + +go 1.14 diff --git a/guide/README.md b/guide/README.md new file mode 100644 index 0000000..4141dce --- /dev/null +++ b/guide/README.md @@ -0,0 +1,61 @@ +# DERO Virtual Machine (DVM) + +DERO Virtual Machine represents entire DERO Smart Contracts eco-system which runs on the DERO block chain. + +Documentation + +DVM is a decentralized platform that runs both public and private smart contracts: applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third-party interference.Public Smart contracts are open versions. However, the DVM is being designed to support Private Smart Contracts where everything is hidden, eg parties, and information involved. Smart Contracts are nothing but rules which apply on transacting parties. + +Current version of DVM is an interpretor based system to avoid security vulneribilities, issues and compiler backdoors. This also allows easy audits of Smart Contracts for quality,bug-testing and security assurances. DVM supports a new language DVM-BASIC. + +----- + + + +DVM apps run on a from scratch custom built privacy supporting, encrypted blockchain, an enormously powerful shared global infrastructure that can move value around and represent the ownership of assets/property without leaking any information.No one knows who owns what and who transferred to whom. + +* This enables developers to create puzzles, games, voting, markets, store registries of debts or promises, move funds in accordance with instructions given long in the past (like a will or a futures contract) and many other ideas/things that have not been invented yet, all without a middleman or counterparty risk. + + +* DVM-BASIC is a contract-oriented, high-level language for implementing smart contracts. It is influenced by GW-BASIC, Visual Basic and C and is designed to target the DERO Virtual Machine (DVM). It is very easy to program and very readable. + +* DVM runs Smart Contracts which are a collection of functions written in DVM-BASIC. +These functions can be invoked over the blockchain to do something. SCs can act as libraries for other SCs. + + +* DVM supports number of comments formats such as ', // , /* */ as good documentation is necessary. + +Example Factorial program + +``` +' This is a comment +// This comment is supported +/* this is multi-line comment */ +// printf is not supported in latest DVM. Please comment or remove printf from all old Smart Contracts. + Function Factorial(s Uint64) Uint64 // this is a commment + 10 DIM result,scopy as Uint64 /* this is also comment */ + 15 LET scopy = s + 20 LET result = 1 + 30 LET result = result * s + 40 LET s = s - 1 + 50 IF s >= 2 THEN GOTO 30 + //60 PRINTF "FACTORIAL of %d = %d" scopy result // printf is not supported in latest DVM. + 70 RETURN result +End Function +``` + +### DVM are written in a DVM-BASIC custom BASIC style language with line numbers. +#### DVM supports uint64 and string data-types. +#### DVM interprets the smart-contract and processes the SC line-line + +* uint64 supports almost all operators namely +,-,*,/,% +* uint64 support following bitwise operators & ,|, ^, ! , >> , << +* uint64 supports following logical operators >, >= , <, <=, == , != + +* string supports only + operator. string support concatenation with a uint64. +* string supports ==, != logical operators. + +* All DVM variables are mandatory to define and are initialized to default values namely 0 and "". + + +A SC execution must return 0 to persist any changes made during execution. During execution, no panics should occur. diff --git a/guide/dim.md b/guide/dim.md new file mode 100644 index 0000000..818a6e9 --- /dev/null +++ b/guide/dim.md @@ -0,0 +1,11 @@ +# DIM statement +DIM stands for data in memory and is used to define variable names within a function + +syntax + +10 DIM variable1 as type +20 DIM variable1,variable2 as type + +type can be any type supported by DVM + +Defining a varible initializes a variable to its ZERO value. \ No newline at end of file diff --git a/guide/examples/lottery.bas b/guide/examples/lottery.bas new file mode 100644 index 0000000..b5e8274 --- /dev/null +++ b/guide/examples/lottery.bas @@ -0,0 +1,90 @@ +/* Lottery Smart Contract Example in DVM-BASIC. + This lottery smart contract will give lottery wins every xth try. +*/ + + + + Function Lottery(value Uint64) Uint64 + 10 dim deposit_count,winner as Uint64 + 20 LET deposit_count = LOAD("deposit_count")+1 + 25 IF value == 0 THEN GOTO 110 // if deposit amount is 0, simply return + 30 STORE("depositor_address" + (deposit_count-1), SIGNER()) // store address for later on payment + 40 STORE("deposit_total", LOAD("deposit_total") + value ) + 50 STORE("deposit_count",deposit_count) + 60 IF LOAD("lotteryeveryXdeposit") > deposit_count THEN GOTO 110 // we will wait till X players join in + // we are here means all players have joined in, roll the DICE, + 70 LET winner = RANDOM() % deposit_count // we have a winner + 80 SEND_DERO_TO_ADDRESS(LOAD("depositor_address" + winner) , LOAD("lotterygiveback")*LOAD("deposit_total")/10000) + // Re-Initialize for another round + 90 STORE("deposit_count", 0) // initial players + 100 STORE("deposit_total", 0) // total deposit of all players + 110 RETURN 0 + End Function + + + // This function is used to initialize parameters during install time + Function Initialize() Uint64 + 10 STORE("owner", SIGNER()) // store in DB ["owner"] = address + 20 STORE("lotteryeveryXdeposit", 2) // lottery will reward every X deposits + // How much will lottery giveback in 1/10000 parts, granularity .01 % + 30 STORE("lotterygiveback", 9900) // lottery will give reward 99% of deposits, 1 % is accumulated for owner to withdraw + 33 STORE("deposit_count", 0) // initial players + 34 STORE("deposit_total", 0) // total deposit of all players + // 35 printf "Initialize executed" + 40 RETURN 0 + End Function + + + + // Function to tune lottery parameters + Function TuneLotteryParameters(input Uint64, lotteryeveryXdeposit Uint64, lotterygiveback Uint64) Uint64 + 10 dim key,stored_owner as String + 20 dim value_uint64 as Uint64 + 30 IF LOAD("owner") == SIGNER() THEN GOTO 100 // check whether owner is real owner + 40 RETURN 1 + + 100 STORE("lotteryeveryXdeposit", lotteryeveryXdeposit) // lottery will reward every X deposits + 130 STORE("lotterygiveback", value_uint64) // how much will lottery giveback in 1/10000 parts, granularity .01 % + 140 RETURN 0 // return success + End Function + + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // If signer is owner, withdraw any requested funds + // If everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // If signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/guide/examples/lottery_sc_guide.md b/guide/examples/lottery_sc_guide.md new file mode 100644 index 0000000..4efcbf5 --- /dev/null +++ b/guide/examples/lottery_sc_guide.md @@ -0,0 +1,76 @@ +## Dero Stargate DVM Smart Contracts guide to install and test various function of lottery Smart Contract. + +**Download** Dero Stargate testnet [source](https://github.com/deroproject/derohe) and [binaries](https://github.com/deroproject/derohe/releases). + +**Start Dero daemon in testnet mode.** +``` +./derod-linux-amd64 --testnet +``` + +**Start Dero wallet in testnet.** +``` +dero-wallet-cli-linux-amd64 --rpc-server --wallet-file testnet.wallet --testnet +``` + +**Start Dero wallet second instance to test transfer/ownership functions etc.** +``` +dero-wallet-cli-linux-amd64 --wallet-file testnet2.wallet --testnet --rpc-server --rpc-bind=127.0.0.1:40403 +``` + +**Dero testnet Explorer** +``` +./explorer-linux-amd64 --rpc-server-address 127.0.0.1:30306 --http-address=0.0.0.0:8080 +``` + +**Dero Stargate Testnet Explorer** +[https://testnetexplorer.dero.io/ ](https://testnetexplorer.dero.io/) + + +**Installing Smart Contract.** + [Download lottery.bas](https://git.dero.io/DeroProject/derosuite_stargate/src/master/cmd/dvm/lottery.bas) +``` +curl --request POST --data-binary @lottery.bas http://127.0.0.1:40403/install_sc +``` + + +** Download SC Code,check balance and variables from chain** +```curl http://127.0.0.1:40402/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af" , "code":true}}' -H 'Content-Type: application/json' + + +curl http://127.0.0.1:40402/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af" , "code":false, "keysstring":["deposit_count"]}}' -H 'Content-Type: application/json' +``` + + +**Examples of various lottery Smart Contract functions** +**Eg: To play lottery** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af","sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Lottery"}] }}' -H 'Content-Type: application/json' + +``` + +**Eg: Withdraw balance only for smart contract owner** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Withdraw"}, {"name":"amount","datatype":"U","value":2 }] }}' -H 'Content-Type: application/json' +``` + +**Eg: To transfer ownership** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"TransferOwnership"}, {"name":"newowner","datatype":"S","value":"deto1qxsplx7vzgydacczw6vnrtfh3fxqcjevyxcvlvl82fs8uykjkmaxgfgulfha5" }] }}' -H 'Content-Type: application/json' + +``` + + +``` +**Eg: To claim ownership** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"ClaimOwnership"}] }}' -H 'Content-Type: application/json' +``` + + +**Eg: To update code** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"UpdateCode"}, {"name":"code","datatype":"S","value":"new code should be placed here" }] }}' -H 'Content-Type: application/json' + +``` + + diff --git a/guide/examples/token.bas b/guide/examples/token.bas new file mode 100644 index 0000000..f2d8bde --- /dev/null +++ b/guide/examples/token.bas @@ -0,0 +1,70 @@ +/* Private Token Smart Contract Example in DVM-BASIC. + DERO Smart Contracts Tokens privacy can be understood just like banks handle cash. Once cash is out from the bank, bank is not aware about it (who owns what value), until it is deposited back. + Smart contract only maintains supply and other necessary items to keep it working. + DERO Tokens can be tranfered to other wallets just like native DERO with Homomorphic Encryption and without involvement of issuing Smart Contracts. + Token issuing Smart Contract cannot hold/freeze/control their tokens once they are issued and sent to any wallet. + This token is Private. Use Function InitializePrivate() Uint64 to make any Smart Contract private. + +*/ + + + // Issue tokens after depositing DERO (Convert DERO to TOKENX) + Function IssueTOKENX() Uint64 + 10 ADD_VALUE(SIGNER(), DEROVALUE()) // Increment balance of user, without knowing original balance, this is done homomorphically + 20 RETURN 0 + End Function + + // Convert TOKENX to DERO after depositing TOKENX. Smart Contract can give DERO, Only if DERO balance exists. + Function ConvertTOKENX() Uint64 + 10 SEND_DERO_TO_ADDRESS(SIGNER(),TOKENVALUE()) // Increment balance of user, without knowing original balance, this is done using Homomorphic Encryption. + 20 RETURN 0 + End Function + + // This function is used to initialize parameters during install time + // InitializePrivate initializes a private SC + Function InitializePrivate() Uint64 + 10 STORE("owner", SIGNER()) // Store in DB ["owner"] = address + 20 ADD_VALUE(ADDRESS_RAW("deto1qxsplx7vzgydacczw6vnrtfh3fxqcjevyxcvlvl82fs8uykjkmaxgfgulfha5"), 1900000) // Gives initial encrypted balance. + 30 ADD_VALUE(SIGNER(), 1600000) // Gives initial encrypted balance of 1600000. + 40 RETURN 0 + End Function + + + // This function is used to change owner + // owner is an string form of address + Function TransferOwnership(newowner String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("tmpowner",ADDRESS_RAW(newowner)) + 40 RETURN 0 + End Function + + // Until the new owner claims ownership, existing owner remains owner + Function ClaimOwnership() Uint64 + 10 IF LOAD("tmpowner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 STORE("owner",SIGNER()) // ownership claim successful + 40 RETURN 0 + End Function + + // if signer is owner, withdraw any requested funds + // if everthing is okay, they will be showing in signers wallet + Function Withdraw( amount Uint64) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 SEND_DERO_TO_ADDRESS(SIGNER(),amount) + 40 RETURN 0 + End Function + + // if signer is owner, provide him rights to update code anytime + // make sure update is always available to SC + Function UpdateCode( code String) Uint64 + 10 IF LOAD("owner") == SIGNER() THEN GOTO 30 + 20 RETURN 1 + 30 UPDATE_SC_CODE(code) + 40 RETURN 0 + End Function + + + + diff --git a/guide/examples/token_sc_guide.md b/guide/examples/token_sc_guide.md new file mode 100644 index 0000000..7e0007b --- /dev/null +++ b/guide/examples/token_sc_guide.md @@ -0,0 +1,100 @@ +## Dero Stargate DVM Smart Contracts guide to install and test various function of private token Smart Contract. + +**Download** Dero Stargate testnet [source](https://git.dero.io/DeroProject/derosuite_stargate) and [binaries](https://git.dero.io/DeroProject/Dero_Stargate_testnet_binaries). + +**Start Dero daemon in testnet mode.** +``` +./derod-linux-amd64 --testnet +``` + +**Start Dero wallet in testnet.** +``` +dero-wallet-cli-linux-amd64 --rpc-server --wallet-file testnet.wallet --testnet +``` + +**Start Dero wallet second instance to test transfer/ownership functions etc.** +``` +dero-wallet-cli-linux-amd64 --wallet-file testnet2.wallet --testnet --rpc-server --rpc-bind=127.0.0.1:40403 +``` + +**Dero testnet Explorer, Not required but if you want to host your own explorer for privacy reasons.** +``` +./explorer-linux-amd64 --http-address=0.0.0.0:8080 +``` +Connect to explorer using browser on localhost:8080 + + +**Dero Stargate Testnet Explorer** +[https://testnetexplorer.dero.io/ ](https://testnetexplorer.dero.io/) + + +**To send DERO to multiple users in one transaction** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":100000,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"},{"amount":300000,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}] }}' -H 'Content-Type: application/json' +``` + +**DERO has 2 types of SCs, public and private.** +1. Public SCs are public with all data/code/exchanges are public. +1. Private SCs have their smart contract data public. But no one knows how many tokens a particular user own or how much is he sending or how much is he receiving. Best example is to understand private SCs as banks and private tokens as cash. Once cash is out from the bank, Bank doesn't know "who has what amount or how is it being used/sent/received etc.". This makes all private tokens completely private. + +**Installing Private Smart Contract.** + [Download token.bas](https://git.dero.io/DeroProject/derosuite_stargate/src/master/cmd/dvm/token.bas) +``` +curl --request POST --data-binary @token.bas http://127.0.0.1:40403/install_sc +``` + +**To check private token balance in wallet, type this command in wallet.** +``` +balance SCID +``` + +**Download SC Code,check SC balance and variables from chain** +``` +curl http://127.0.0.1:40402/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"getsc","params":{ "scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af" , "code":true}}' -H 'Content-Type: application/json' +``` + + +**Examples of various private token Smart Contract functions** +**Eg: To send private tokens from one wallet to another wallet, this does not involve SC** +**Eg: this also showcases to send multiple assets( DERO and other tokens on DERO Network) within a single transaction** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"},{"amount":333333,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu","scid": "aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af" }] }}' -H 'Content-Type: application/json' +``` + + +**Eg: Convert DERO to tokens 1:1 swap, we are swapping 44 DERO atomic units to get some tokens** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{"transfers":[{"amount":1,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu", "burn":44}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"IssueTOKENX"}] }}' -H 'Content-Type: application/json' +``` + + +**Convert tokens to DERO 1:1 swap, we are swapping 9 token atomic units to get 9 DERO atomic units** +**This tx shows transferring tokens natively, no dero fees etc, this is under evaluation,** +**Currently these show as coinbase rewards ** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{"transfers":[{"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "amount":1,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu", "burn":9}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"ConvertTOKENX"}] }}' -H 'Content-Type: application/json' +``` + +**Eg: To withdraw balance only for smart contract owner** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"Withdraw"}, {"name":"amount","datatype":"U","value":2 }] }}' -H 'Content-Type: application/json' +``` + +**Eg: To transfer ownership of smart contract to new address/owner** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"TransferOwnership"}, {"name":"newowner","datatype":"S","value":"deto1qxsplx7vzgydacczw6vnrtfh3fxqcjevyxcvlvl82fs8uykjkmaxgfgulfha5" }] }}' -H 'Content-Type: application/json' + +``` + +**Eg: To claim ownership of smart contract** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"ClaimOwnership"}] }}' -H 'Content-Type: application/json' +``` + + +**Eg: To update smart contract code** +``` +curl http://127.0.0.1:40403/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"transfer","params":{ "transfers":[{"amount":111111,"destination":"deto1qxqqen6lqmksmzmxmfqmxp2y8zvkldtcq8jhkzqflmyczepjw9dp46gc3cczu"}],"scid":"aacaa7bb2388d06e523e5bc0783e4e131738270641406c12978155ba033373af", "sc_rpc":[{"name":"entrypoint","datatype":"S","value":"UpdateCode"}, {"name":"code","datatype":"S","value":"new code should be placed here" }] }}' -H 'Content-Type: application/json' + + +``` \ No newline at end of file diff --git a/guide/function.md b/guide/function.md new file mode 100644 index 0000000..ff267c6 --- /dev/null +++ b/guide/function.md @@ -0,0 +1,27 @@ +# Function statement + +Function statement is used to define a function. See eg, below for a function which adds 2 numbers + +``` +Function ADD(x uint64, y uint64) uint64 +10 RETURN x + y +End Function + +``` + +Function syntax is of 2 types + +1. Function function_name( 0 or more arguments ) +2. Function function_name( 0 or more arguments ) return type + +The rules for functions are as follows +* All functions begin with Function keyword +* Function name should be alpha-numeric. If the first letter of the function is Upper case alphabet, it can be invoked by blockchain and other smart-contracts. Otherwise the function can only be called by other functions within the smart contract. +* Function may or may not have a return type +* All functions must use RETURN to return from function or to return a value. RETURN is mandatory. +* All functions must end with End Function. End Function is mandatory +* A function can have a implicit parameter value of type uint64, which contains amount of DERO value sent with the transaction. + +Any error caused during processing will immediately stop execution and discard all changes that occur during SC execution. + +Any Entrypoint which returns uint64 value 0 is termed as success and will make transaction to commit all state changes. diff --git a/guide/goto.md b/guide/goto.md new file mode 100644 index 0000000..d4ffc3a --- /dev/null +++ b/guide/goto.md @@ -0,0 +1,6 @@ +# GOTO statement +It is used to jump to any point within the function. It cannot cross function-boundary + +syntax +GOTO line-number + diff --git a/guide/if.md b/guide/if.md new file mode 100644 index 0000000..1bddd65 --- /dev/null +++ b/guide/if.md @@ -0,0 +1,8 @@ +# IF +If statement is used to evaluate expression and make decisions.It has following forms +1. IF expr1 condition expr2 THEN GOTO line number +2. IF expr1 condition expr2 THEN GOTO line number ELSE GOTO line number + + +This is used to change execution flow based on conditions. +Conditions can be as complex expressions \ No newline at end of file diff --git a/guide/let.md b/guide/let.md new file mode 100644 index 0000000..96951c6 --- /dev/null +++ b/guide/let.md @@ -0,0 +1,14 @@ +# LET statement +LET is used to assign a value to a variable. +value can be as complex as possible and can contain complex expression + +syntax +``` +line number LET variable_name = expression +``` + +expression can invoke other functions,eg + +10 LET x = 2 + 3 + ADD(2,3) + +ANY assignments within DVM can only be done using LET \ No newline at end of file diff --git a/guide/return.md b/guide/return.md new file mode 100644 index 0000000..95ce516 --- /dev/null +++ b/guide/return.md @@ -0,0 +1,8 @@ +# RETURN statement +it is used to return from a function and can be used anywhere within a function + +syntax +1. RETURN ( return nil ) +2. RETURN expression ( evaluates expression and returns value ) + +any return value must match with the type defined while declaring function diff --git a/guide/support_functions.md b/guide/support_functions.md new file mode 100644 index 0000000..c28a453 --- /dev/null +++ b/guide/support_functions.md @@ -0,0 +1,69 @@ +Support Functions are inbuilt functions which provide some functionality or expose internals for speed and technical reasons. + + +LOAD(variable) +============== + +LOAD loads a variable which was previously stored in the blockchain using STORE function. Return type will be Uint64/String depending on what is stored. +It will panic if the value does NOT exists + +Uint64 EXISTS(variable) +======================= +EXISTS return 1 if the variable is store in DB and 0 otherwise + +STORE(key variable, value variable) +=================================== +STORE stores key and value in the DB. All storage state of the SC is accessible only from the SC which created it. + +Uint64 RANDOM() +Uint64 RANDOM(limit Uin64) +============================ +RANDOM returns a random using a PRNG seeded on BLID,SCID,TXID. First form gives a uint64, second form returns +random number in the range 0 - (limit), 0 is inclusive, limit is exclusive + +String SCID() +============== +Returns SMART CONTRACT ID which is currently running + +String BLID() +============== +Returns current BLOCK ID which contains current execution-in-progress TXID + +String TXID() +============= +Returns current TXID which is execution-in-progress. + +Uint64 BLOCK_HEIGHT() +===================== +Returns current chain height of BLID() + +Uint64 BLOCK_TOPOHEIGHT() +=========================== +Returns current topoheight of BLID() + +String SIGNER() +================= +Returns address of who signed this transaction + +Uint64 IS_ADDRESS_VALID(p String) +================================= +Returns 1 if address is valid, 0 otherwise + + +String ADDRESS_RAW(p String) +============================ +Returns address in RAW form as 33 byte keys, stripping away textual/presentation form. 2 address should always be compared in RAW form + +SEND_DERO_TO_ADDRESS(a String, amount Uint64) +============================================== +Sends amount DERO from SC DERO balance to a address which should be raw form. address must in string form DERO/DETO form +If the SC does not have enough balance, it will panic + + +ADD_VALUE(a String, amount Uint64) +==================================== +Send specific number of token to specific account. +If account is bring touched for the first time, it is done simply. +If account is already initialized ( it already has some balance, but SC does not know how much). So, it gives additional balance homomorphically + + diff --git a/p2p/chain_bootstrap.go b/p2p/chain_bootstrap.go new file mode 100644 index 0000000..760865e --- /dev/null +++ b/p2p/chain_bootstrap.go @@ -0,0 +1,345 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package p2p + +import "fmt" + +//import "net" +import "time" +import "math/big" +import "math/bits" +import "sync/atomic" +import "encoding/binary" + +//import "container/list" + +//import log "github.com/sirupsen/logrus" +import "github.com/romana/rlog" + +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/block" + +//import "github.com/deroproject/derohe/errormsg" +import "github.com/deroproject/derohe/transaction" + +import "github.com/deroproject/graviton" + +import "github.com/deroproject/derohe/cryptography/crypto" + +//import "github.com/deroproject/derosuite/blockchain" + +// we are expecting other side to have a heavier PoW chain +// this is for the case when the chain only moves in pruned state +// if after bootstraping the chain can continousky sync for few minutes, this means we have got the job done +func (connection *Connection) bootstrap_chain() { + + var request ChangeList + var response Changes + // var err error + var zerohash crypto.Hash + + // we will request top 60 blocks + ctopo := connection.TopoHeight + var topos []int64 + for i := ctopo - 20; i < ctopo; i++ { + topos = append(topos, i) + } + + connection.logger.Infof("Bootstrap Initiated") + + for i := range topos { + request.TopoHeights = append(request.TopoHeights, topos[i]) + } + + fill_common(&request.Common) // fill common info + if err := connection.RConn.Client.Call("Peer.ChangeSet", request, &response); err != nil { + rlog.Errorf("Call faileda ChangeSet: %v\n", err) + return + } + // we have a response, see if its valid and try to add to get the blocks + rlog.Infof("changeset received, estimated keys : %d SC Keys : %d \n", response.KeyCount, response.SCKeyCount) + + commit_version := uint64(0) + + { // fetch and commit balance tree + + chunksize := int64(640) + chunks_estm := response.KeyCount / chunksize + chunks := int64(1) // chunks need to be in power of 2 + path_length := 0 + for chunks < chunks_estm { + chunks = chunks * 2 + path_length++ + } + + if chunks < 2 { + chunks = 2 + path_length = 1 + } + + var section [8]byte + + total_keys := 0 + + for i := int64(0); i < chunks; i++ { + binary.BigEndian.PutUint64(section[:], bits.Reverse64(uint64(i))) // place reverse path + ts_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: []byte(config.BALANCE_TREE), Section: section[:], SectionLength: uint64(path_length)} + var ts_response Response_Tree_Section_Struct + fill_common(&ts_response.Common) + if err := connection.RConn.Client.Call("Peer.TreeSection", ts_request, &ts_response); err != nil { + connection.logger.Warnf("Call failed TreeSection: %v\n", err) + return + } else { + // now we must write all the state changes to gravition + var balance_tree *graviton.Tree + if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err != nil { + panic(err) + } else if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { + panic(err) + } + + if len(ts_response.Keys) != len(ts_response.Values) { + rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(ts_response.Keys), len(ts_response.Values), globals.CTXString(connection.logger)) + connection.exit() + return + } + rlog.Debugf("chunk %d Will write %d keys\n", i, len(ts_response.Keys)) + + for j := range ts_response.Keys { + balance_tree.Put(ts_response.Keys[j], ts_response.Values[j]) + } + total_keys += len(ts_response.Keys) + + commit_version, err = graviton.Commit(balance_tree) + if err != nil { + panic(err) + } + + h, err := balance_tree.Hash() + rlog.Debugf("total keys %d hash %x err %s\n", total_keys, h, err) + + } + connection.logger.Infof("Bootstrap %3.1f%% completed", float32(i*100)/float32(chunks)) + } + } + + { // fetch and commit SC tree + + chunksize := int64(640) + chunks_estm := response.SCKeyCount / chunksize + chunks := int64(1) // chunks need to be in power of 2 + path_length := 0 + for chunks < chunks_estm { + chunks = chunks * 2 + path_length++ + } + + if chunks < 2 { + chunks = 2 + path_length = 1 + } + + var section [8]byte + + total_keys := 0 + + for i := int64(0); i < chunks; i++ { + binary.BigEndian.PutUint64(section[:], bits.Reverse64(uint64(i))) // place reverse path + ts_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: []byte(config.SC_META), Section: section[:], SectionLength: uint64(path_length)} + var ts_response Response_Tree_Section_Struct + fill_common(&ts_response.Common) + if err := connection.RConn.Client.Call("Peer.TreeSection", ts_request, &ts_response); err != nil { + connection.logger.Warnf("Call failed TreeSection: %v\n", err) + return + } else { + // now we must write all the state changes to gravition + var changed_trees []*graviton.Tree + var sc_tree *graviton.Tree + //var changed_trees []*graviton.Tree + ss, err := chain.Store.Balance_store.LoadSnapshot(0) + if err != nil { + panic(err) + } else if sc_tree, err = ss.GetTree(config.SC_META); err != nil { + panic(err) + } + + if len(ts_response.Keys) != len(ts_response.Values) { + rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(ts_response.Keys), len(ts_response.Values), globals.CTXString(connection.logger)) + connection.exit() + return + } + rlog.Debugf("SC chunk %d Will write %d keys\n", i, len(ts_response.Keys)) + + for j := range ts_response.Keys { + sc_tree.Put(ts_response.Keys[j], ts_response.Values[j]) + + // we must fetch each individual SC tree + + sc_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: ts_response.Keys[j], Section: section[:], SectionLength: uint64(0)} + var sc_response Response_Tree_Section_Struct + fill_common(&sc_response.Common) + if err := connection.RConn.Client.Call("Peer.TreeSection", sc_request, &sc_response); err != nil { + connection.logger.Warnf("Call failed TreeSection: %v\n", err) + return + } else { + var sc_data_tree *graviton.Tree + if sc_data_tree, err = ss.GetTree(string(ts_response.Keys[j])); err != nil { + panic(err) + } else { + for j := range sc_response.Keys { + sc_data_tree.Put(sc_response.Keys[j], sc_response.Values[j]) + } + changed_trees = append(changed_trees, sc_data_tree) + + } + + } + + } + total_keys += len(ts_response.Keys) + changed_trees = append(changed_trees, sc_tree) + commit_version, err = graviton.Commit(changed_trees...) + if err != nil { + panic(err) + } + + h, err := sc_tree.Hash() + rlog.Debugf("total SC keys %d hash %x err %s\n", total_keys, h, err) + + } + connection.logger.Infof("Bootstrap %3.1f%% completed", float32(i*100)/float32(chunks)) + } + } + + for i := int64(0); i <= request.TopoHeights[0]; i++ { + chain.Store.Topo_store.Write(i, zerohash, commit_version, 0) // commit everything + } + + for i := range response.CBlocks { // we must store the blocks + + var cbl block.Complete_Block // parse incoming block and deserialize it + var bl block.Block + // lets deserialize block first and see whether it is the requested object + cbl.Bl = &bl + err := bl.Deserialize(response.CBlocks[i].Block) + if err != nil { // we have a block which could not be deserialized ban peer + connection.logger.Warnf("Error Incoming block could not be deserilised err %s %s", err, connection.logid) + connection.exit() + return + } + + // give the chain some more time to respond + atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) + + if i == 0 { // whatever datastore we have written, its state hash must match + // ToDo + + } + + // complete the txs + for j := range response.CBlocks[i].Txs { + var tx transaction.Transaction + err = tx.DeserializeHeader(response.CBlocks[i].Txs[j]) + if err != nil { // we have a tx which could not be deserialized ban peer + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, connection.logid) + connection.exit() + return + } + if bl.Tx_hashes[j] != tx.GetHash() { + rlog.Warnf("Error Incoming TX has mismatch err %s %s", err, connection.logid) + connection.exit() + return + } + + cbl.Txs = append(cbl.Txs, &tx) + } + + { // first lets save all the txs, together with their link to this block as height + for i := 0; i < len(cbl.Txs); i++ { + if err = chain.Store.Block_tx_store.WriteTX(bl.Tx_hashes[i], cbl.Txs[i].Serialize()); err != nil { + panic(err) + } + } + } + + diff := new(big.Int) + if _, ok := diff.SetString(response.CBlocks[i].Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection + rlog.Warnf("Could not Parse Difficulty in common %s \"%s\" ", connection.Cumulative_Difficulty, globals.CTXString(connection.logger)) + connection.exit() + return + } + + cdiff := new(big.Int) + if _, ok := cdiff.SetString(response.CBlocks[i].Cumulative_Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection + rlog.Warnf("Could not Parse Cumulative_Difficulty in common %s \"%s\" ", connection.Cumulative_Difficulty, globals.CTXString(connection.logger)) + connection.exit() + return + } + + if err = chain.Store.Block_tx_store.WriteBlock(bl.GetHash(), bl.Serialize(), diff, cdiff); err != nil { + panic(fmt.Sprintf("error while writing block")) + } + + // now we must write all the state changes to gravition + + var ss *graviton.Snapshot + if ss, err = chain.Store.Balance_store.LoadSnapshot(0); err != nil { + panic(err) + } + + /*if len(response.CBlocks[i].Keys) != len(response.CBlocks[i].Values) { + rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(response.CBlocks[i].Keys), len(response.CBlocks[i].Values), globals.CTXString(connection.logger)) + connection.exit() + return + }*/ + + write_count := 0 + commit_version := ss.GetVersion() + if i != 0 { + + var changed_trees []*graviton.Tree + + for _, change := range response.CBlocks[i].Changes { + var tree *graviton.Tree + if tree, err = ss.GetTree(string(change.TreeName)); err != nil { + panic(err) + } + + for j := range change.Keys { + tree.Put(change.Keys[j], change.Values[j]) + write_count++ + } + changed_trees = append(changed_trees, tree) + } + commit_version, err = graviton.Commit(changed_trees...) + if err != nil { + panic(err) + } + } + + rlog.Debugf("%d wrote %d keys commit version %d\n", request.TopoHeights[i], write_count, commit_version) + + chain.Store.Topo_store.Write(request.TopoHeights[i], bl.GetHash(), commit_version, int64(bl.Height)) // commit everything + } + + connection.logger.Infof("Bootstrap completed successfully") + // load the chain from the disk + chain.Initialise_Chain_From_DB() + chain.Sync = true + return +} diff --git a/p2p/chain_response.go b/p2p/chain_response.go deleted file mode 100644 index 515553e..0000000 --- a/p2p/chain_response.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -//import "fmt" -//import "net" -//import "sync" -//import "time" - -//import "container/list" - -//import log "github.com/sirupsen/logrus" -import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/config" - -//import "github.com/deroproject/derosuite/blockchain" - -// peer has responded with chain response and we must process the response -func (connection *Connection) Handle_ChainResponse(buf []byte) { - var response Chain_Response_Struct - - err := msgpack.Unmarshal(buf, &response) - if err != nil { - rlog.Warnf("Error while decoding incoming chain response err %s %s", err, connection.logid) - connection.Exit() - return - } - - // check what we we queued is what what we got back - // for chain request we queue an empty hash - - var expected Queued_Command - - select { - case expected = <-connection.Objects: - - default: // if nothing is on queue the peer sent us bogus request, - rlog.Warnf("Peer sent us a chain response, when we didnot request chain, Exiting, may be block the peer %s", connection.logid) - connection.Exit() - } - - if expected.Command != V2_COMMAND_CHAIN_RESPONSE { - rlog.Warnf("We were waiting for a different object, but peer sent something else, Exiting, may be block the peer %s", connection.logid) - connection.Exit() - } - - // we were expecting something else ban - if len(response.Block_list) < 1 || len(response.TopBlocks) > 100 { - rlog.Warnf("Malformed chain response %s", err, connection.logid) - connection.Exit() - return - } - - rlog.Tracef(2, "Peer wants to give chain from topoheight %d ", response.Start_height) - _ = config.STABLE_LIMIT - - // we do not need reorganisation if deviation is less than or equak to 7 blocks - // only pop blocks if the system has somehow deviated more than 7 blocks - // if the deviation is less than 7 blocks, we internally reorganise everything - if chain.Get_Height() -response.Start_height > config.STABLE_LIMIT && connection.SyncNode { - // get our top block - rlog.Infof("rewinding status our %d peer %d", chain.Load_TOPO_HEIGHT(), response.Start_topoheight) - pop_count := chain.Load_TOPO_HEIGHT() - response.Start_topoheight - chain.Rewind_Chain(int(pop_count)) // pop as many blocks as necessary - - // we should NOT queue blocks, instead we sent our chain request again - connection.Send_ChainRequest() - return - - }else if chain.Get_Height()-response.Start_height <= config.STABLE_LIMIT { - pop_count := chain.Load_TOPO_HEIGHT() - response.Start_topoheight - chain.Rewind_Chain(int(pop_count)) // pop as many blocks as necessary, assumming peer has given us good chain - }else{ // we must somehow notify that deviation is way too much and manual interaction is necessary, so as any bug for chain deviationmay be detected - - } - - // response only 128 blocks at a time - max_blocks_to_queue := 128 - // check whether the objects are in our db or not - // until we put in place a parallel object tracker, do it one at a time - - rlog.Infof("response block list %d\n", len(response.Block_list)) - for i := range response.Block_list { - our_topo_order := chain.Load_Block_Topological_order(response.Block_list[i]) - if our_topo_order != (int64(i)+ response.Start_topoheight) || chain.Load_Block_Topological_order(response.Block_list[i]) == -1 { // if block is not in our chain, add it to request list - //queue_block(request.Block_list[i]) - if max_blocks_to_queue >= 0 { - max_blocks_to_queue-- - connection.Send_ObjectRequest([]crypto.Hash{response.Block_list[i]}, []crypto.Hash{}) - rlog.Tracef(2, "Queuing block %x height %d %s", response.Block_list[i], response.Start_height+int64(i), connection.logid) - } - } else { - rlog.Tracef(3, "We must have queued %x, but we skipped it at height %d", response.Block_list[i], response.Start_height+int64(i)) - } - } - - // request alt-tips ( blocks if we are nearing the main tip ) - if (response.Common.TopoHeight - chain.Load_TOPO_HEIGHT()) <= 5 { - for i := range response.TopBlocks { - if chain.Load_Block_Topological_order(response.TopBlocks[i]) == -1 { - connection.Send_ObjectRequest([]crypto.Hash{response.TopBlocks[i]}, []crypto.Hash{}) - rlog.Tracef(2, "Queuing ALT-TIP block %x %s", response.TopBlocks[i], connection.logid) - - } - - } - } -} diff --git a/p2p/chain_sync.go b/p2p/chain_sync.go new file mode 100644 index 0000000..f1b9f36 --- /dev/null +++ b/p2p/chain_sync.go @@ -0,0 +1,234 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package p2p + +import "fmt" + +//import "net" +import "time" +import "sync/atomic" + +//import "container/list" + +//import log "github.com/sirupsen/logrus" +import "github.com/romana/rlog" + +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/block" +import "github.com/deroproject/derohe/errormsg" +import "github.com/deroproject/derohe/transaction" + +//import "github.com/deroproject/derohe/cryptography/crypto" + +//import "github.com/deroproject/derosuite/blockchain" + +// we are expecting other side to have a heavier PoW chain, try to sync now +func (connection *Connection) sync_chain() { + + var request Chain_Request_Struct + var response Chain_Response_Struct + +try_again: + + // send our blocks, first 10 blocks directly, then decreasing in powers of 2 + start_point := chain.Load_TOPO_HEIGHT() + for i := int64(0); i < start_point; { + + tr, err := chain.Store.Topo_store.Read(start_point - i) // commit everything + if err != nil { + continue + } + if tr.IsClean() { + break + } + + request.Block_list = append(request.Block_list, tr.BLOCK_ID) + request.TopoHeights = append(request.TopoHeights, start_point-i) + switch { + case len(request.Block_list) < 10: + i++ + default: + i = i * 2 + } + } + // add genesis block at the end + request.Block_list = append(request.Block_list, globals.Config.Genesis_Block_Hash) + request.TopoHeights = append(request.TopoHeights, 0) + fill_common(&request.Common) // fill common info + if err := connection.RConn.Client.Call("Peer.Chain", request, &response); err != nil { + fmt.Printf("Call failed Peer.Chain : %v \n", err) + return + } + // we have a response, see if its valid and try to add to get the blocks + + rlog.Infof("Peer wants to give chain from topoheight %d ", response.Start_height) + _ = config.STABLE_LIMIT + + // we do not need reorganisation if deviation is less than or equak to 7 blocks + // only pop blocks if the system has somehow deviated more than 7 blocks + // if the deviation is less than 7 blocks, we internally reorganise everything + if chain.Get_Height()-response.Start_height > config.STABLE_LIMIT && connection.SyncNode { + // get our top block + rlog.Infof("rewinding status our %d peer %d", chain.Load_TOPO_HEIGHT(), response.Start_topoheight) + pop_count := chain.Load_TOPO_HEIGHT() - response.Start_topoheight + chain.Rewind_Chain(int(pop_count)) // pop as many blocks as necessary + + // we should NOT queue blocks, instead we sent our chain request again + goto try_again + + } else if chain.Get_Height()-response.Start_height <= config.STABLE_LIMIT { + pop_count := chain.Load_TOPO_HEIGHT() - response.Start_topoheight + chain.Rewind_Chain(int(pop_count)) // pop as many blocks as necessary, assumming peer has given us good chain + } else { // we must somehow notify that deviation is way too much and manual interaction is necessary, so as any bug for chain deviationmay be detected + return + } + + // response only 128 blocks at a time + max_blocks_to_queue := 128 + // check whether the objects are in our db or not + // until we put in place a parallel object tracker, do it one at a time + + rlog.Infof("response block list %d\n", len(response.Block_list)) + for i := range response.Block_list { + our_topo_order := chain.Load_Block_Topological_order(response.Block_list[i]) + if our_topo_order != (int64(i)+response.Start_topoheight) || chain.Load_Block_Topological_order(response.Block_list[i]) == -1 { // if block is not in our chain, add it to request list + //queue_block(request.Block_list[i]) + if max_blocks_to_queue >= 0 { + max_blocks_to_queue-- + //connection.Send_ObjectRequest([]crypto.Hash{response.Block_list[i]}, []crypto.Hash{}) + var orequest ObjectList + var oresponse Objects + + orequest.Block_list = append(orequest.Block_list, response.Block_list[i]) + fill_common(&orequest.Common) + if err := connection.RConn.Client.Call("Peer.GetObject", orequest, &oresponse); err != nil { + fmt.Printf("Call faileda Peer.GetObject: %v\n", err) + return + } else { // process the response + if err = connection.process_object_response(oresponse); err != nil { + return + } + } + + //fmt.Printf("Queuing block %x height %d %s", response.Block_list[i], response.Start_height+int64(i), connection.logid) + } + } else { + rlog.Tracef(3, "We must have queued %x, but we skipped it at height %d", response.Block_list[i], response.Start_height+int64(i)) + } + } + + // request alt-tips ( blocks if we are nearing the main tip ) + if (response.Common.TopoHeight - chain.Load_TOPO_HEIGHT()) <= 5 { + for i := range response.TopBlocks { + if chain.Load_Block_Topological_order(response.TopBlocks[i]) == -1 { + //connection.Send_ObjectRequest([]crypto.Hash{response.TopBlocks[i]}, []crypto.Hash{}) + rlog.Tracef(2, "Queuing ALT-TIP block %x %s", response.TopBlocks[i], connection.logid) + + } + + } + } + +} + +func (connection *Connection) process_object_response(response Objects) error { + var err error + + // make sure connection does not timeout and be killed while processing huge blocks + processing_complete := make(chan bool) + go func() { + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-processing_complete: + return // complete the loop + case <-ticker.C: // give the chain some more time to respond + atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) + } + } + }() + + defer func() { + processing_complete <- true + }() + + for i := 0; i < len(response.CBlocks); i++ { // process incoming full blocks + var cbl block.Complete_Block // parse incoming block and deserialize it + var bl block.Block + // lets deserialize block first and see whether it is the requested object + cbl.Bl = &bl + err := bl.Deserialize(response.CBlocks[i].Block) + if err != nil { // we have a block which could not be deserialized ban peer + rlog.Warnf("Error Incoming block could not be deserilised err %s %s", err, connection.logid) + connection.exit() + return nil + } + + // give the chain some more time to respond + atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) + + // check whether the object was requested one + + // complete the txs + for j := range response.CBlocks[i].Txs { + var tx transaction.Transaction + err = tx.DeserializeHeader(response.CBlocks[i].Txs[j]) + if err != nil { // we have a tx which could not be deserialized ban peer + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, connection.logid) + connection.exit() + + return nil + } + cbl.Txs = append(cbl.Txs, &tx) + } + + // check if we can add ourselves to chain + err, ok := chain.Add_Complete_Block(&cbl) + if !ok && err == errormsg.ErrInvalidPoW { + connection.logger.Warnf("This peer should be banned") + connection.exit() + return nil + } + + if !ok && err == errormsg.ErrPastMissing { + rlog.Warnf("Error Incoming Block coould not be added due to missing past, so skipping future block err %s %s", err, connection.logid) + return nil + } + + // add the object to object pool from where it will be consume + // queue_block_received(bl.GetHash(),&cbl) + + } + + for i := range response.Txs { // process incoming txs for mempool + var tx transaction.Transaction + err = tx.DeserializeHeader(response.Txs[i]) + if err != nil { // we have a tx which could not be deserialized ban peer + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, connection.logid) + connection.exit() + + return nil + } + if !chain.Mempool.Mempool_TX_Exist(tx.GetHash()) { // we still donot have it, so try to process it + + chain.Add_TX_To_Pool(&tx) // currently we are ignoring error + } + } + return nil +} diff --git a/p2p/common.go b/p2p/common.go new file mode 100644 index 0000000..12109d3 --- /dev/null +++ b/p2p/common.go @@ -0,0 +1,72 @@ +package p2p + +import "math/big" +import "sync/atomic" +import "github.com/romana/rlog" + +import "github.com/deroproject/derohe/globals" +import "github.com/deroproject/derohe/cryptography/crypto" + +// fill the common part from our chain +func fill_common(common *Common_Struct) { + common.Height = chain.Get_Height() + //common.StableHeight = chain.Get_Stable_Height() + common.TopoHeight = chain.Load_TOPO_HEIGHT() + //common.Top_ID, _ = chain.Load_BL_ID_at_Height(common.Height - 1) + + high_block, err := chain.Load_Block_Topological_order_at_index(common.TopoHeight) + if err != nil { + common.Cumulative_Difficulty = "0" + } else { + common.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(high_block).String() + } + + if common.StateHash, err = chain.Load_Merkle_Hash(common.TopoHeight); err != nil { + panic(err) + } + + common.Top_Version = uint64(chain.Get_Current_Version_at_Height(int64(common.Height))) // this must be taken from the hardfork + +} + +// used while sendint TX ASAP +// this also skips statehash +func fill_common_skip_topoheight(common *Common_Struct) { + fill_common(common) + return + +} + +// update some common properties quickly +func (connection *Connection) update(common *Common_Struct) { + //connection.Lock() + //defer connection.Unlock() + var hash crypto.Hash + atomic.StoreInt64(&connection.Height, common.Height) // satify race detector GOD + if common.StableHeight != 0 { + atomic.StoreInt64(&connection.StableHeight, common.StableHeight) // satify race detector GOD + } + atomic.StoreInt64(&connection.TopoHeight, common.TopoHeight) // satify race detector GOD + + //connection.Top_ID = common.Top_ID + if common.Cumulative_Difficulty != "" { + connection.Cumulative_Difficulty = common.Cumulative_Difficulty + + var x *big.Int + x = new(big.Int) + if _, ok := x.SetString(connection.Cumulative_Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection + rlog.Warnf("Could not Parse Cumulative_Difficulty in common '%s' \"%s\" ", connection.Cumulative_Difficulty, globals.CTXString(connection.logger)) + connection.exit() + } + + connection.CDIFF.Store(x) // do it atomically + } + + if connection.Top_Version != common.Top_Version { + atomic.StoreUint64(&connection.Top_Version, common.Top_Version) // satify race detector GOD + } + if common.StateHash != hash { + connection.StateHash = common.StateHash + } + +} diff --git a/p2p/connection_handler.go b/p2p/connection_handler.go deleted file mode 100644 index 1dba0aa..0000000 --- a/p2p/connection_handler.go +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -import "io" - -//import "fmt" -import "net" - -//import "sync" -//import "math/rand" -import "time" -import "math/big" -import "runtime/debug" -import "sync/atomic" -import "encoding/binary" - -//import "container/list" - -import "github.com/romana/rlog" -import log "github.com/sirupsen/logrus" -import "github.com/paulbellamy/ratecounter" -import "github.com/vmihailenco/msgpack" - -import "github.com/deroproject/derohe/config" - -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/globals" - -import "github.com/deroproject/derohe/blockchain" - -// This file defines what all needs to be responded to become a server ( handling incoming requests) - -// fill the common part from our chain -func fill_common(common *Common_Struct) { - common.Height = chain.Get_Height() - //common.StableHeight = chain.Get_Stable_Height() - common.TopoHeight = chain.Load_TOPO_HEIGHT() - //common.Top_ID, _ = chain.Load_BL_ID_at_Height(common.Height - 1) - - high_block, err := chain.Load_Block_Topological_order_at_index(common.TopoHeight) - if err != nil { - common.Cumulative_Difficulty = "0" - } else { - common.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(high_block).String() - } - - if toporecord, err := chain.Store.Topo_store.Read(common.TopoHeight); err == nil { - if ss, err := chain.Store.Balance_store.LoadSnapshot(uint64(toporecord.State_Version)); err == nil { - if balance_tree, err := ss.GetTree(blockchain.BALANCE_TREE); err == nil { - if bhash, err := balance_tree.Hash(); err == nil { - common.StateHash = bhash - } - } - } - } - - common.Top_Version = uint64(chain.Get_Current_Version_at_Height(int64(common.Height))) // this must be taken from the hardfork - -} - -// used while sendint TX ASAP -// this also skips statehash -func fill_common_skip_topoheight(common *Common_Struct) { - fill_common(common) - return - /* - common.Height = chain.Get_Height() - //common.StableHeight = chain.Get_Stable_Height() - common.TopoHeight = chain.Load_TOPO_HEIGHT() - //common.Top_ID, _ = chain.Load_BL_ID_at_Height(common.Height - 1) - - high_block, err := chain.Load_Block_Topological_order_at_index(common.TopoHeight) - if err != nil { - common.Cumulative_Difficulty = "0" - } else { - common.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(high_block).String() - } - common.Top_Version = uint64(chain.Get_Current_Version_at_Height(int64(common.Height))) // this must be taken from the hardfork - */ - -} - -// update some common properties quickly -func (connection *Connection) Update(common *Common_Struct) { - //connection.Lock() - //defer connection.Unlock() - var hash crypto.Hash - atomic.StoreInt64(&connection.Height, common.Height) // satify race detector GOD - if common.StableHeight != 0 { - atomic.StoreInt64(&connection.StableHeight, common.StableHeight) // satify race detector GOD - } - atomic.StoreInt64(&connection.TopoHeight, common.TopoHeight) // satify race detector GOD - - //connection.Top_ID = common.Top_ID - connection.Cumulative_Difficulty = common.Cumulative_Difficulty - - var x *big.Int - x = new(big.Int) - if _, ok := x.SetString(connection.Cumulative_Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection - rlog.Warnf("Could not Parse Cumulative_Difficulty in common %s \"%s\" ", connection.Cumulative_Difficulty, globals.CTXString(connection.logger)) - - connection.Exit() - } - - connection.CDIFF.Store(x) // do it atomically - - if connection.Top_Version != common.Top_Version { - atomic.StoreUint64(&connection.Top_Version, common.Top_Version) // satify race detector GOD - } - if common.StateHash != hash { - connection.StateHash = common.StateHash - } - -} - -// sets timeout based on connection state, so as stale connections are cleared quickly -// if these timeouts are met connection is closed -func (connection *Connection) set_timeout() { - if atomic.LoadUint32(&connection.State) == HANDSHAKE_PENDING { - connection.Conn.SetReadDeadline(time.Now().Add(8 * time.Second)) // new connections have 8 seconds to handshake - } else { - // if we have queued something, time should be less than 30 sec assumming 10KB/sec BW is available - connection.Conn.SetReadDeadline(time.Now().Add(40 * time.Second)) // good connections have 120 secs to communicate - } - -} - -// marks connection as exit in progress -func (conn *Connection) Exit() { - atomic.AddInt32(&conn.ExitCounter, 1) -} - -func (conn *Connection) IsExitInProgress() bool { - if atomic.AddInt32(&conn.ExitCounter, 0) != 0 { - return true - } - return false -} - -// the message is sent as follows - -func (conn *Connection) Send_Message(data_bytes []byte) { - - // measure parameters - conn.SpeedOut.Incr(int64(len(data_bytes)) + 4) - atomic.StoreUint64(&conn.BytesOut, atomic.LoadUint64(&conn.BytesOut)+(uint64(len(data_bytes))+4)) - - if atomic.LoadUint32(&conn.State) != HANDSHAKE_PENDING { - atomic.StoreUint32(&conn.State, ACTIVE) - } - conn.Send_Message_prelocked(data_bytes) -} - -// used when we have command queue -// assumingin peer lock has already been taken -func (conn *Connection) Send_Message_prelocked(data_bytes []byte) { - - conn.writelock.Lock() - defer conn.writelock.Unlock() - - defer func() { - if r := recover(); r != nil { - conn.logger.Warnf("Recovered while handling connection, Stack trace below %+v", r) - conn.logger.Warnf("Stack trace \n%s", debug.Stack()) - conn.Exit() - } - }() - - if conn.IsExitInProgress() { - return - } - - var length_bytes [4]byte - binary.LittleEndian.PutUint32(length_bytes[:], uint32(len(data_bytes))) - - // each connection has a write deadline of 60 secs - conn.Conn.SetWriteDeadline(time.Now().Add(60 * time.Second)) - if _, err := conn.Conn.Write(length_bytes[:]); err != nil { // send the length prefix - conn.Exit() - return - } - - conn.Conn.SetWriteDeadline(time.Now().Add(60 * time.Second)) - if _, err := conn.Conn.Write(data_bytes[:]); err != nil { // send the message itself - conn.Exit() - return - } - -} - -// reads our data, length prefix blocks -func (connection *Connection) Read_Data_Frame(timeout int64, max_block_size uint32) (data_buf []byte) { - - var frame_length_buf [4]byte - - connection.set_timeout() - nbyte, err := io.ReadFull(connection.Conn, frame_length_buf[:]) - if err != nil || nbyte != 4 { - // error while reading from connection we must disconnect it - rlog.Warnf("Could not read length prefix err %s %s", err, globals.CTXString(connection.logger)) - - connection.Exit() - - return - } - - // time to ban - frame_length := binary.LittleEndian.Uint32(frame_length_buf[:]) - if frame_length == 0 || uint64(frame_length) > (15*config.STARGATE_HE_MAX_BLOCK_SIZE/10) || uint64(frame_length) > (2*uint64(max_block_size)) { - // most probably memory DDOS attack, kill the connection - rlog.Warnf("Frame length is too big Expected %d Actual %d %s", (2 * uint64(max_block_size)), frame_length, globals.CTXString(connection.logger)) - connection.Exit() - return - - } - - data_buf = make([]byte, frame_length) - connection.set_timeout() - data_size, err := io.ReadFull(connection.Conn, data_buf) - if err != nil || data_size <= 0 || uint32(data_size) != frame_length { - // error while reading from connection we must kiil it - rlog.Warnf("Could not read data size read %d, frame length %d err %s", data_size, frame_length, err, globals.CTXString(connection.logger)) - connection.Exit() - return - - } - data_buf = data_buf[:frame_length] - - // measure parameters - connection.SpeedIn.Incr(int64(frame_length) + 4) - connection.BytesIn += (uint64(frame_length) + 4) - if atomic.LoadUint32(&connection.State) != HANDSHAKE_PENDING { - atomic.StoreUint32(&connection.State, ACTIVE) - } - - return data_buf - -} - -// handles both server and client connections -func Handle_Connection(conn net.Conn, remote_addr *net.TCPAddr, incoming bool, sync_node bool) { - - var err error - - defer func() { - if r := recover(); r != nil { // under rare condition below defer can also raise an exception, catch it now - // connection.logger.Warnf("Recovered while handling connection RARE", r) - rlog.Warnf("Recovered while handling connection RARE") - //conn.Exit() - } - }() - - var connection Connection - connection.Incoming = incoming - connection.Conn = conn - connection.SyncNode = sync_node - connection.Addr = remote_addr // since we may be connecting via socks, get target IP - //connection.Command_queue = list.New() // init command queue - connection.Objects = make(chan Queued_Command, 2048) - connection.CDIFF.Store(new(big.Int).SetUint64(1)) - connection.State = HANDSHAKE_PENDING - - connection.request_time.Store(time.Now()) - //connection.Exit = make(chan bool) - connection.SpeedIn = ratecounter.NewRateCounter(60 * time.Second) - connection.SpeedOut = ratecounter.NewRateCounter(60 * time.Second) - - if incoming { - connection.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "INC"}) - } else { - connection.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "OUT"}) - } - // this is to kill most of the races, related to logger - connection.logid = globals.CTXString(connection.logger) - - defer func() { - if r := recover(); r != nil { - connection.logger.Warnf("Recovered while handling connection, Stack trace below %+v", r) - connection.logger.Warnf("Stack trace \n%s", debug.Stack()) - connection.Exit() - } - }() - - // goroutine to exit the connection if signalled - go func() { - - defer func() { - if r := recover(); r != nil { // under rare condition below defer can also raise an exception, catch it now - // connection.logger.Warnf("Recovered while handling connection RARE", r) - rlog.Warnf("Recovered while handling Timed Sync") - connection.Exit() - } - }() - - ticker := time.NewTicker(1 * time.Second) // 1 second ticker - idle := 0 - rehandshake := 0 - for { - - if connection.IsExitInProgress() { - - //connection.logger.Warnf("Removing connection") - ticker.Stop() // release resources of timer - conn.Close() - Connection_Delete(&connection) - return // close the connection and close the routine - } - - select { - case <-ticker.C: - idle++ - rehandshake++ - if idle > 2 { // if idle more than 2 secs, we should send a timed sync - if atomic.LoadUint32(&connection.State) != HANDSHAKE_PENDING { - atomic.StoreUint32(&connection.State, IDLE) - connection.Send_TimedSync(true) - idle = 0 - } - - // if no timed sync response in 2 minute kill the connection - if time.Now().Sub(connection.request_time.Load().(time.Time)) > 120*time.Second { - connection.Exit() - } - - //if !connection.Incoming { // there is not point in sending timed sync both sides simultaneously - - if rehandshake > 1800 { // rehandshake to exchange peers every half hour - connection.Send_Handshake(true) - rehandshake = 0 - - // run cleanup process every 1800 seconds - connection.TXpool_cache_lock.Lock() - if connection.TXpool_cache != nil { - current_time := uint32(time.Now().Unix()) - for k, v := range connection.TXpool_cache { - if (v + 1800) < current_time { - delete(connection.TXpool_cache, k) - } - } - } - connection.TXpool_cache_lock.Unlock() - } - //} - } - case <-Exit_Event: - ticker.Stop() // release resources of timer - conn.Close() - Connection_Delete(&connection) - return // close the connection and close the routine - - } - } - }() - - if !incoming { - Connection_Add(&connection) // add outgoing connection to pool, incoming are added when handshake are done - connection.Send_Handshake(true) // send handshake request - } - - for { - - var command Sync_Struct // decode as sync minimum - - //connection.logger.Info("Waiting for frame") - if connection.IsExitInProgress() { - return - } - - //connection.logger.Infof("Waiting for data frame") - // no one should be able to request more than necessary amount of buffer - data_read := connection.Read_Data_Frame(0, uint32(config.STARGATE_HE_MAX_BLOCK_SIZE*2)) - - if connection.IsExitInProgress() { - return - } - //connection.logger.Info("frame received") - - // connection.logger.Infof(" data frame arrived %d", len(data_read)) - - // lets decode command and make sure we understand it - err = msgpack.Unmarshal(data_read, &command) - if err != nil { - rlog.Warnf("Error while decoding incoming frame err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return - } - - // check version sanctity - //connection.logger.Infof(" data frame parsed %+v", command) - - // till the time handshake is done, we donot process any commands - if atomic.LoadUint32(&connection.State) == HANDSHAKE_PENDING && !(command.Command == V2_COMMAND_HANDSHAKE || command.Command == V2_COMMAND_SYNC) { - // client sent something else when we were waiting for handshake, ban the peer - rlog.Warnf("Terminating connection, we were waiting for handshake but received %d %s", command.Command, globals.CTXString(connection.logger)) - - connection.Exit() - return - } - - //connection.logger.Debugf("v2 command incoming %d", command.Command) - - switch command.Command { - case V2_COMMAND_HANDSHAKE: - connection.Update(&command.Common) - connection.Handle_Handshake(data_read) - case V2_COMMAND_SYNC: - connection.Update(&command.Common) - connection.Handle_TimedSync(data_read) - case V2_COMMAND_CHAIN_REQUEST: - connection.Update(&command.Common) - connection.Handle_ChainRequest(data_read) - case V2_COMMAND_CHAIN_RESPONSE: - connection.Update(&command.Common) - connection.Handle_ChainResponse(data_read) - case V2_COMMAND_OBJECTS_REQUEST: - connection.Update(&command.Common) - connection.Handle_ObjectRequest(data_read) - case V2_COMMAND_OBJECTS_RESPONSE: - connection.Update(&command.Common) - connection.Handle_ObjectResponse(data_read) - case V2_NOTIFY_NEW_BLOCK: // for notification, instead of syncing, we will process notificaton first - connection.Handle_Notification_Block(data_read) - connection.Update(&command.Common) // we do it a bit later so we donot staart syncing - case V2_NOTIFY_NEW_TX: - connection.Update(&command.Common) - connection.Handle_Notification_Transaction(data_read) - case V2_NOTIFY_INVENTORY: - connection.Update(&command.Common) - connection.Handle_Incoming_Inventory(data_read) - - default: - connection.logger.Debugf("Unhandled v2 command %d", command.Command) - - } - - } - -} diff --git a/p2p/connection_pool.go b/p2p/connection_pool.go index 39e3a4c..9510401 100644 --- a/p2p/connection_pool.go +++ b/p2p/connection_pool.go @@ -36,14 +36,13 @@ import "encoding/binary" //import "container/list" import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" import "github.com/dustin/go-humanize" import log "github.com/sirupsen/logrus" import "github.com/paulbellamy/ratecounter" import "github.com/prometheus/client_golang/prometheus" import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/crypto" +import "github.com/deroproject/derohe/cryptography/crypto" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/transaction" @@ -60,6 +59,7 @@ type Queued_Command struct { Command uint64 // we are waiting for this response BLID []crypto.Hash TXID []crypto.Hash + Topos []int64 } // This structure is used to do book keeping for the connection and keeps other DATA related to peer @@ -81,7 +81,6 @@ type Connection struct { Addr *net.TCPAddr // endpoint on the other end Port uint32 // port advertised by other end as its server,if it's 0 server cannot accept connections Peer_ID uint64 // Remote peer id - Lowcpuram bool // whether the peer has low cpu ram SyncNode bool // whether the peer has been added to command line as sync node Top_Version uint64 // current hard fork version supported by peer TXpool_cache map[uint64]uint32 // used for ultra blocks in miner mode,cache where we keep TX which have been broadcasted to this peer @@ -96,10 +95,11 @@ type Connection struct { Cumulative_Difficulty string // cumulative difficulty of top block of peer, this is NOT required CDIFF atomic.Value //*big.Int // NOTE: this field is used internally and is the parsed from Cumulative_Difficulty - logger *log.Entry // connection specific logger - logid string // formatted version of connection - Requested_Objects [][32]byte // currently unused as we sync up with a single peer at a time - Conn net.Conn // actual object to talk + logger *log.Entry // connection specific logger + logid string // formatted version of connection + Requested_Objects [][32]byte // currently unused as we sync up with a single peer at a time + Conn net.Conn // actual object to talk + RConn *RPC_Connection // object for communication // Command_queue *list.List // New protocol is partly syncronous Objects chan Queued_Command // contains all objects that are requested SpeedIn *ratecounter.RateCounter // average speed in last 60 seconds @@ -107,7 +107,11 @@ type Connection struct { request_time atomic.Value //time.Time // used to track latency writelock sync.Mutex // used to Serialize writes - sync.Mutex // used only by connection go routine + Mutex sync.Mutex // used only by connection go routine +} + +func (c *Connection) exit() { + c.RConn.Session.Close() } @@ -226,7 +230,7 @@ func Connection_Add(c *Connection) { if ip_count >= 8 || peer_id_count >= 4 { rlog.Warnf("IP address %s (%d) Peer ID %d(%d) already has too many connections, exiting this connection", incoming_ip, ip_count, incoming_peer_id, peer_id_count) - c.Exit() + c.exit() return } @@ -287,7 +291,7 @@ func Connection_Print() { // skip pending handshakes and skip ourselves if atomic.LoadUint32(&clist[i].State) == HANDSHAKE_PENDING || GetPeerID() == clist[i].Peer_ID { - continue + // continue } dir := "OUT" @@ -322,11 +326,11 @@ func Connection_Print() { if globals.Arguments["--debug"].(bool) == true { hstring := fmt.Sprintf("%d/%d/%d", clist[i].StableHeight, clist[i].Height, clist[i].TopoHeight) - fmt.Printf("%-20s %16x %5d %7s %7s %23s %s %5d %7s %7s %8s %9s %16s %s %x\n", clist[i].Addr.IP, clist[i].Peer_ID, clist[i].Port, state, time.Duration(atomic.LoadInt64(&clist[i].Latency)).Round(time.Millisecond).String(), hstring, dir, clist[i].IsConnectionSyncing(), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesIn)), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesOut)), humanize.Bytes(uint64(clist[i].SpeedIn.Rate()/60)), humanize.Bytes(uint64(clist[i].SpeedOut.Rate()/60)), version, tag, clist[i].StateHash[:]) + fmt.Printf("%-20s %16x %5d %7s %7s %23s %s %5d %7s %7s %8s %9s %16s %s %x\n", clist[i].Addr.IP, clist[i].Peer_ID, clist[i].Port, state, time.Duration(atomic.LoadInt64(&clist[i].Latency)).Round(time.Millisecond).String(), hstring, dir, clist[i].isConnectionSyncing(), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesIn)), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesOut)), humanize.Bytes(uint64(clist[i].SpeedIn.Rate()/60)), humanize.Bytes(uint64(clist[i].SpeedOut.Rate()/60)), version, tag, clist[i].StateHash[:]) } else { hstring := fmt.Sprintf("%d/%d", clist[i].Height, clist[i].TopoHeight) - fmt.Printf("%-20s %16x %5d %7s %7s %17s %s %5d %7s %7s %8s %9s %16s %s %x\n", clist[i].Addr.IP, clist[i].Peer_ID, clist[i].Port, state, time.Duration(atomic.LoadInt64(&clist[i].Latency)).Round(time.Millisecond).String(), hstring, dir, clist[i].IsConnectionSyncing(), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesIn)), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesOut)), humanize.Bytes(uint64(clist[i].SpeedIn.Rate()/60)), humanize.Bytes(uint64(clist[i].SpeedOut.Rate()/60)), version, tag, clist[i].StateHash[:8]) + fmt.Printf("%-20s %16x %5d %7s %7s %17s %s %5d %7s %7s %8s %9s %16s %s %x\n", clist[i].Addr.IP, clist[i].Peer_ID, clist[i].Port, state, time.Duration(atomic.LoadInt64(&clist[i].Latency)).Round(time.Millisecond).String(), hstring, dir, clist[i].isConnectionSyncing(), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesIn)), humanize.Bytes(atomic.LoadUint64(&clist[i].BytesOut)), humanize.Bytes(uint64(clist[i].SpeedIn.Rate()/60)), humanize.Bytes(uint64(clist[i].SpeedOut.Rate()/60)), version, tag, clist[i].StateHash[:8]) } @@ -375,7 +379,6 @@ func Disconnect_All() (Count uint64) { // this function return peer count which have successful handshake func Peer_Count() (Count uint64) { - connection_map.Range(func(k, value interface{}) bool { v := value.(*Connection) if atomic.LoadUint32(&v.State) != HANDSHAKE_PENDING && GetPeerID() != v.Peer_ID { @@ -383,10 +386,37 @@ func Peer_Count() (Count uint64) { } return true }) - return } +// this function has infinite loop to keep ping every few sec +func ping_loop() { + + for { + time.Sleep(5 * time.Second) + connection_map.Range(func(k, value interface{}) bool { + c := value.(*Connection) + if atomic.LoadUint32(&c.State) != HANDSHAKE_PENDING && GetPeerID() != c.Peer_ID { + go func() { + defer globals.Recover() + var dummy Dummy + fill_common(&dummy.Common) // fill common info + + ctime := time.Now() + if err := c.RConn.Client.Call("Peer.Ping", dummy, &dummy); err != nil { + return + } + took := time.Now().Sub(ctime) + c.update(&dummy.Common) // update common information + atomic.StoreInt64(&c.Latency, int64(took/2)) // divide by 2 is for round-trip + c.update(&dummy.Common) // update common information + }() + } + return true + }) + } +} + // this function returnw random connection which have successful handshake func Random_Connection(height int64) (c *Connection) { @@ -430,30 +460,18 @@ func Peer_Direction_Count() (Incoming uint64, Outgoing uint64) { // this function is trigger from 2 points, one when we receive a unknown block which can be successfully added to chain // second from the blockchain which has to relay locally mined blocks as soon as possible func Broadcast_Block(cbl *block.Complete_Block, PeerID uint64) { // if peerid is provided it is skipped - var request Notify_New_Objects_Struct + var cblock_serialized Complete_Block - defer func() { - if r := recover(); r != nil { - logger.Warnf("Recovered while broadcasting Block, Stack trace below %+v", r) - logger.Warnf("Stack trace \n%s", debug.Stack()) - } - }() + defer globals.Recover() /*if IsSyncing() { // if we are syncing, do NOT broadcast the block return }*/ - fill_common(&request.Common) // fill common info - request.Command = V2_NOTIFY_NEW_BLOCK - request.CBlock.Block = cbl.Bl.Serialize() + cblock_serialized.Block = cbl.Bl.Serialize() for i := range cbl.Txs { - request.CBlock.Txs = append(request.CBlock.Txs, cbl.Txs[i].Serialize()) - } - - serialized, err := msgpack.Marshal(&request) - if err != nil { - panic(err) + cblock_serialized.Txs = append(cblock_serialized.Txs, cbl.Txs[i].Serialize()) } our_height := chain.Get_Height() @@ -480,17 +498,13 @@ func Broadcast_Block(cbl *block.Complete_Block, PeerID uint64) { // if peerid is count++ go func(connection *Connection) { - defer func() { - if r := recover(); r != nil { - rlog.Warnf("Recovered while handling connection, Stack trace below", r) - rlog.Warnf("Stack trace \n%s", debug.Stack()) - } - }() - if globals.Arguments["--lowcpuram"].(bool) == false && connection.TXpool_cache != nil { // everyone needs ultrac compact block if possible - var miner_specific_request Notify_New_Objects_Struct - miner_specific_request.Common = request.Common - miner_specific_request.Command = V2_NOTIFY_NEW_BLOCK - miner_specific_request.CBlock.Block = request.CBlock.Block + defer globals.Recover() + + { // everyone needs ultra compact block if possible + var peer_specific_block Objects + + var cblock Complete_Block + cblock.Block = cblock_serialized.Block sent := 0 skipped := 0 @@ -500,7 +514,7 @@ func Broadcast_Block(cbl *block.Complete_Block, PeerID uint64) { // if peerid is // send only tx not found in cache if _, ok := connection.TXpool_cache[binary.LittleEndian.Uint64(cbl.Bl.Tx_hashes[i][:])]; !ok { - miner_specific_request.CBlock.Txs = append(miner_specific_request.CBlock.Txs, request.CBlock.Txs[i]) + cblock.Txs = append(cblock.Txs, cblock_serialized.Txs[i]) sent++ } else { @@ -511,31 +525,28 @@ func Broadcast_Block(cbl *block.Complete_Block, PeerID uint64) { // if peerid is connection.TXpool_cache_lock.RUnlock() connection.logger.Debugf("Sending ultra block to peer total %d tx skipped %d sent %d", len(cbl.Bl.Tx_hashes), skipped, sent) - - serialized_miner_specific, err := msgpack.Marshal(&miner_specific_request) - if err != nil { - panic(err) + peer_specific_block.CBlocks = append(peer_specific_block.CBlocks, cblock) + var dummy Dummy + fill_common(&peer_specific_block.Common) // fill common info + if err := connection.RConn.Client.Call("Peer.NotifyBlock", peer_specific_block, &dummy); err != nil { + return } - connection.Send_Message(serialized_miner_specific) // miners need ultra compact blocks + connection.update(&dummy.Common) // update common information - } else { - connection.logger.Debugf("Sending full block to peer") - - connection.Send_Message(serialized) // non miners need full blocks } }(v) } } - rlog.Infof("Broadcasted block %s to %d peers", cbl.Bl.GetHash(), count) + //rlog.Infof("Broadcasted block %s to %d peers", cbl.Bl.GetHash(), count) } // broadcast a new transaction, return to how many peers the transaction has been broadcasted // this function is trigger from 2 points, one when we receive a unknown tx // second from the mempool which may want to relay local ot soon going to expire transactions -func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int) { +func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int32) { defer func() { if r := recover(); r != nil { @@ -544,19 +555,12 @@ func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int } }() - var request Object_Request_Struct + var request ObjectList fill_common_skip_topoheight(&request.Common) // fill common info, but skip topo height - request.Command = V2_NOTIFY_INVENTORY - txhash := tx.GetHash() request.Tx_list = append(request.Tx_list, txhash) - serialized, err := msgpack.Marshal(&request) - if err != nil { - panic(err) - } - our_height := chain.Get_Height() unique_map := UniqueConnections() @@ -579,7 +583,6 @@ func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int continue } - relayed_count++ go func(connection *Connection) { defer func() { if r := recover(); r != nil { @@ -591,26 +594,34 @@ func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int resend := true // disable cache if not possible due to options // assuming the peer is good, he would like to obtain the tx ASAP - if globals.Arguments["--lowcpuram"].(bool) == false && connection.TXpool_cache != nil { - connection.TXpool_cache_lock.Lock() - if _, ok := connection.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])]; !ok { - connection.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])] = uint32(time.Now().Unix()) - resend = true - } else { - resend = false - } - connection.TXpool_cache_lock.Unlock() + + connection.TXpool_cache_lock.Lock() + if _, ok := connection.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])]; !ok { + connection.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])] = uint32(time.Now().Unix()) + resend = true + } else { + resend = false } + connection.TXpool_cache_lock.Unlock() if resend { - connection.Send_Message(serialized) // send the bytes + var dummy Dummy + fill_common(&dummy.Common) // fill common info + if err := connection.RConn.Client.Call("Peer.NotifyINV", request, &dummy); err != nil { + return + } + connection.update(&dummy.Common) // update common information + + atomic.AddInt32(&relayed_count, 1) } }(v) } } - //rlog.Infof("Broadcasted tx %s to %d peers", txhash, relayed_count) + if relayed_count > 0 { + rlog.Debugf("Broadcasted tx %s to %d peers", txhash, relayed_count) + } return } @@ -620,7 +631,7 @@ func Broadcast_Tx(tx *transaction.Transaction, PeerID uint64) (relayed_count int // if objects response are queued, we are syncing // if even one of the connection is syncing, then we are syncronising // returns a number how many blocks are queued -func (connection *Connection) IsConnectionSyncing() (count int) { +func (connection *Connection) isConnectionSyncing() (count int) { //connection.Lock() //defer connection.Unlock() @@ -632,7 +643,7 @@ func (connection *Connection) IsConnectionSyncing() (count int) { // so we can try some other connection if len(connection.Objects) > 0 { if time.Now().Unix() >= (13 + atomic.LoadInt64(&connection.LastObjectRequestTime)) { - connection.Exit() + connection.exit() return 0 } } @@ -684,7 +695,16 @@ func trigger_sync() { connection.logger.Debugf("We need to resync with the peer height %d", connection.Height) //connection.Unlock() // set mode to syncronising - connection.Send_ChainRequest() + + if chain.Sync { + //fmt.Printf("chain send chain request disabled\n") + connection.sync_chain() + connection.logger.Debugf("sync done ") + + } else { // we need a state only sync, bootstrap without history but verified chain + connection.bootstrap_chain() + chain.Sync = true + } break } } @@ -700,7 +720,7 @@ func IsSyncing() (result bool) { syncing := false connection_map.Range(func(k, value interface{}) bool { v := value.(*Connection) - if v.IsConnectionSyncing() != 0 { + if v.isConnectionSyncing() != 0 { syncing = true return false } @@ -724,5 +744,4 @@ func syncroniser() { } } - } diff --git a/p2p/controller.go b/p2p/controller.go index 702a226..092fe4c 100644 --- a/p2p/controller.go +++ b/p2p/controller.go @@ -19,6 +19,7 @@ package p2p //import "os" import "fmt" import "net" +import "net/rpc" import "time" import "sort" import "strings" @@ -60,6 +61,8 @@ var nonbanlist []string // any ips in this list will never be banned func P2P_Init(params map[string]interface{}) error { logger = globals.Logger.WithFields(log.Fields{"com": "P2P"}) // all components must use this logger + // register_handlers() + GetPeerID() // Initialize peer id once // parse node tag if availble @@ -110,6 +113,7 @@ func P2P_Init(params map[string]interface{}) error { go P2P_engine() // start outgoing engine go syncroniser() // start sync engine go clean_up_propagation() // clean up propagation map + go ping_loop() // keep pinging logger.Infof("P2P started") atomic.AddUint32(&globals.Subsystem_Active, 1) // increment subsystem return nil @@ -201,7 +205,7 @@ func connect_with_endpoint(endpoint string, sync_node bool) { // check whether are already connected to this address if yes, return if IsAddressConnected(remote_ip.String()) { - return + return //nil, fmt.Errorf("Already connected") } // since we may be connecting through socks, grab the remote ip for our purpose rightnow @@ -212,7 +216,7 @@ func connect_with_endpoint(endpoint string, sync_node bool) { if err != nil { rlog.Warnf("Dial failed err %s", err.Error()) Peer_SetFail(remote_ip.String()) // update peer list as we see - return + return //nil, fmt.Errorf("Dial failed err %s", err.Error()) } tcpc := conn.(*net.TCPConn) @@ -230,9 +234,9 @@ func connect_with_endpoint(endpoint string, sync_node bool) { // TODO we need to choose fastest cipher here ( so both clients/servers are not loaded) conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) - // success is setup after handshake is done - rlog.Debugf("Connection established to %s", remote_ip) - Handle_Connection(conn, remote_ip, false, sync_node) // handle connection + process_connection(conn, remote_ip, false, sync_node) + + //Handle_Connection(conn, remote_ip, false, sync_node) // handle connection } // maintains a persistant connection to endpoint @@ -379,11 +383,47 @@ func P2P_Server_v2() { tcpc.SetLinger(0) // discard any pending data tlsconn := tls.Server(conn, tlsconfig) - go Handle_Connection(tlsconn, raddr, true, false) // handle connection in a different go routine - - //go Handle_Connection(conn, raddr, true, false) // handle connection in a different go routine + go process_connection(tlsconn, raddr, true, false) // handle connection in a different go routine } } + +} + +func process_connection(conn net.Conn, remote_addr *net.TCPAddr, incoming, sync_node bool) { + defer globals.Recover() + + var rconn *RPC_Connection + var err error + if incoming { + rconn, err = wait_stream_creation_server_side(conn) // do server side processing + } else { + rconn, err = stream_creation_client_side(conn) // do client side processing + } + if err == nil { + + var RPCSERVER = rpc.NewServer() + c := &Connection{RConn: rconn, Addr: remote_addr, State: HANDSHAKE_PENDING, Incoming: incoming, SyncNode: sync_node} + RPCSERVER.RegisterName("Peer", c) // register the handlers + + if incoming { + c.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "INC"}) + } else { + c.logger = logger.WithFields(log.Fields{"RIP": remote_addr.String(), "DIR": "OUT"}) + } + go func() { + defer globals.Recover() + //RPCSERVER.ServeConn(rconn.ServerConn) // start single threaded rpc server with GOB encoding + RPCSERVER.ServeCodec(NewCBORServerCodec(rconn.ServerConn)) // use CBOR encoding on rpc + }() + + c.dispatch_test_handshake() + + <-rconn.Session.CloseChan() + Connection_Delete(c) + //fmt.Printf("closing connection status err: %s\n",err) + } + conn.Close() + } // shutdown the p2p component @@ -456,3 +496,29 @@ func generate_random_tls_cert() tls.Certificate { } return tlsCert } + +/* +// register all the handlers +func register_handlers(){ + arpc.DefaultHandler.Handle("/handshake",Handshake_Handler) + arpc.DefaultHandler.Handle("/active",func (ctx *arpc.Context) { // set the connection active + if c,ok := ctx.Client.Get("connection");ok { + connection := c.(*Connection) + atomic.StoreUint32(&connection.State, ACTIVE) + }} ) + + arpc.DefaultHandler.HandleConnected(OnConnected_Handler) // all incoming connections will first processed here +arpc.DefaultHandler.HandleDisconnected(OnDisconnected_Handler) // all disconnected +} + + + +// triggers when new clients connect and +func OnConnected_Handler(c *arpc.Client){ + dispatch_test_handshake(c, c.Conn.RemoteAddr().(*net.TCPAddr) ,true,false) // client connected we must handshake +} + +func OnDisconnected_Handler(c *arpc.Client){ + c.Stop() +} +*/ diff --git a/p2p/handshake.go b/p2p/handshake.go deleted file mode 100644 index 7a3c8a4..0000000 --- a/p2p/handshake.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -import "fmt" -import "bytes" - -//import "net" -import "sync/atomic" -import "time" - -//import "container/list" - -//import log "github.com/sirupsen/logrus" -//import "github.com/allegro/bigcache" -import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/globals" - -//import "github.com/deroproject/derosuite/blockchain" - -// reads our data, length prefix blocks -func (connection *Connection) Send_Handshake(request bool) { - - var handshake Handshake_Struct - - fill_common(&handshake.Common) // fill common info - handshake.Command = V2_COMMAND_HANDSHAKE - handshake.Request = request - - // TODO these version strings should be setup during build - // original protocol in c daemon should be called version 1 - // the new version is version 2 - handshake.ProtocolVersion = "1.0.0" - handshake.DaemonVersion = config.Version.String() - handshake.Tag = node_tag - handshake.UTC_Time = int64(time.Now().UTC().Unix()) // send our UTC time - handshake.Local_Port = uint32(P2P_Port) // export requested or default port - handshake.Peer_ID = GetPeerID() // give our randomly generated peer id - handshake.Pruned = chain.LocatePruneTopo() - if globals.Arguments["--lowcpuram"].(bool) == false { - handshake.Flags = append(handshake.Flags, FLAG_LOWCPURAM) // add low cpu ram flag - } - - //scan our peer list and send peers which have been recently communicated - handshake.PeerList = get_peer_list() - copy(handshake.Network_ID[:], globals.Config.Network_ID[:]) - - // serialize and send - serialized, err := msgpack.Marshal(&handshake) - if err != nil { - panic(err) - } - - rlog.Tracef(2, "handshake sent %s", globals.CTXString(connection.logger)) - connection.Send_Message(serialized) -} - -// verify incoming handshake for number of checks such as mainnet/testnet etc etc -func (connection *Connection) Verify_Handshake(handshake *Handshake_Struct) bool { - return bytes.Equal(handshake.Network_ID[:], globals.Config.Network_ID[:]) -} - -// handles both server and client connections -func (connection *Connection) Handle_Handshake(buf []byte) { - - var handshake Handshake_Struct - - err := msgpack.Unmarshal(buf, &handshake) - if err != nil { - rlog.Warnf("Error while decoding incoming handshake request err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return - } - - if !connection.Verify_Handshake(&handshake) { // if not same network boot off - connection.logger.Debugf("kill connection network id mismatch peer network id %x", handshake.Network_ID) - connection.Exit() - return - } - - rlog.Tracef(2, "handshake response received %+v %s", handshake, globals.CTXString(connection.logger)) - - // check if self connection exit - if connection.Incoming && handshake.Peer_ID == GetPeerID() { - rlog.Tracef(1, "Same peer ID, probably self connection, disconnecting from this client") - connection.Exit() - return - } - - if handshake.Request { - connection.Send_Handshake(false) // send it as response - } - if !connection.Incoming { // setup success - Peer_SetSuccess(connection.Addr.String()) - } - - connection.Update(&handshake.Common) // update common information - - if atomic.LoadUint32(&connection.State) == HANDSHAKE_PENDING { // some of the fields are processed only while initial handshake - connection.Lock() - if len(handshake.ProtocolVersion) < 128 { - connection.ProtocolVersion = handshake.ProtocolVersion - } - - if len(handshake.DaemonVersion) < 128 { - connection.DaemonVersion = handshake.DaemonVersion - } - connection.Port = handshake.Local_Port - connection.Peer_ID = handshake.Peer_ID - if len(handshake.Tag) < 128 { - connection.Tag = handshake.Tag - } - if handshake.Pruned >= 0 { - connection.Pruned = handshake.Pruned - } - - // TODO we must also add the peer to our list - // which can be distributed to other peers - if connection.Port != 0 && connection.Port <= 65535 { // peer is saying it has an open port, handshake is success so add peer - - var p Peer - if connection.Addr.IP.To4() != nil { // if ipv4 - p.Address = fmt.Sprintf("%s:%d", connection.Addr.IP.String(), connection.Port) - } else { // if ipv6 - p.Address = fmt.Sprintf("[%s]:%d", connection.Addr.IP.String(), connection.Port) - } - p.ID = connection.Peer_ID - - p.LastConnected = uint64(time.Now().UTC().Unix()) - - /* TODO we should add any flags here if necessary, but they are not - required, since a peer can only be used if connected and if connected - we already have a truly synced view - for _, k := range handshake.Flags { - switch k { - case FLAG_MINER: - p.Miner = true - } - }*/ - - Peer_Add(&p) - } - - for _, k := range handshake.Flags { - switch k { - case FLAG_LOWCPURAM: - connection.Lowcpuram = true - - //connection.logger.Debugf("Miner flag \"%s\" from peer", k) - default: - connection.logger.Debugf("Unknown flag \"%s\" from peer, ignoring", k) - - } - - } - - // do NOT build TX cache, if we are runnin in lowcpu mode - if globals.Arguments["--lowcpuram"].(bool) == true { // if connection is not running in low cpu mode and we are also same, activate transaction cache - connection.TXpool_cache = nil - } else { // we do not have any limitation, activate per peer cache - connection.TXpool_cache = map[uint64]uint32{} - - } - connection.Unlock() - } - - // parse delivered peer list as grey list - rlog.Debugf("Peer provides %d peers", len(handshake.PeerList)) - for i := range handshake.PeerList { - if i < 13 { - Peer_Add(&Peer{Address: handshake.PeerList[i].Addr, LastConnected: uint64(time.Now().UTC().Unix())}) - } - } - - atomic.StoreUint32(&connection.State, ACTIVE) - if connection.Incoming { - Connection_Add(connection) - } -} diff --git a/p2p/inventory_handler.go b/p2p/inventory_handler.go deleted file mode 100644 index f17b2c0..0000000 --- a/p2p/inventory_handler.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -//import "fmt" -//import "net" -import "sync/atomic" -import "time" - -//import "container/list" - -import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -//import "github.com/deroproject/derosuite/crypto" -//import "github.com/deroproject/derosuite/globals" - -import "github.com/deroproject/derohe/crypto" - -//import "github.com/deroproject/derosuite/globals" - -//import "github.com/deroproject/derosuite/blockchain" - -// we are sending object request -// right now we only send block ids -func (connection *Connection) Send_Inventory(blids []crypto.Hash, txids []crypto.Hash) { - - var request Object_Request_Struct - fill_common(&request.Common) // fill common info - request.Command = V2_NOTIFY_INVENTORY - - for i := range blids { - request.Block_list = append(request.Block_list, blids[i]) - } - - for i := range txids { - request.Tx_list = append(request.Tx_list, txids[i]) - } - - if len(blids) > 0 || len(txids) > 0 { - serialized, err := msgpack.Marshal(&request) // serialize and send - if err != nil { - panic(err) - } - - // use first object - - command := Queued_Command{Command: V2_COMMAND_OBJECTS_RESPONSE, BLID: blids, TXID: txids} - - connection.Objects <- command - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - - // we should add to queue that we are waiting for object response - //command := Queued_Command{Command: V2_COMMAND_OBJECTS_RESPONSE, BLID: blids, TXID: txids, Started: time.Now()} - - connection.Lock() - //connection.Command_queue.PushBack(command) // queue command - connection.Send_Message_prelocked(serialized) - connection.Unlock() - rlog.Tracef(3, "object request sent contains %d blids %d txids %s ", len(blids), connection.logid) - } -} - -// peer has given his list of inventory -// if certain object is not in our list we request with the inventory -// if everything is already in our inventory, do nothing ignore -func (connection *Connection) Handle_Incoming_Inventory(buf []byte) { - var request Object_Request_Struct - var response Object_Request_Struct - - var blids, txids []crypto.Hash - - var dirty = false - - err := msgpack.Unmarshal(buf, &request) - if err != nil { - rlog.Warnf("Error while decoding incoming object request err %s %s", err, connection.logid) - connection.Exit() - } - - if len(request.Block_list) >= 1 { // handle incoming blocks list - for i := range request.Block_list { // - if !chain.Is_Block_Topological_order(request.Block_list[i]) { // block is not in our chain - if !chain.Block_Exists(request.Block_list[i]) { // check whether the block can be loaded from disk - response.Block_list = append(response.Block_list, request.Block_list[i]) - blids = append(blids, request.Block_list[i]) - dirty = true - } - } - } - } - - if len(request.Tx_list) >= 1 { // handle incoming tx list and see whether it exists in mempoolor regpool - for i := range request.Tx_list { // - if !(chain.Mempool.Mempool_TX_Exist(request.Tx_list[i]) || chain.Regpool.Regpool_TX_Exist(request.Tx_list[i])) { // check if is already in mempool skip it - if _, err = chain.Store.Block_tx_store.ReadTX(request.Tx_list[i]); err != nil { // check whether the tx can be loaded from disk - response.Tx_list = append(response.Tx_list, request.Tx_list[i]) - txids = append(txids, request.Tx_list[i]) - dirty = true - } - } - } - } - - if dirty { // request inventory only if we want it - fill_common(&response.Common) // fill common info - response.Command = V2_COMMAND_OBJECTS_REQUEST - serialized, err := msgpack.Marshal(&response) // serialize and send - if err != nil { - panic(err) - } - - command := Queued_Command{Command: V2_COMMAND_OBJECTS_RESPONSE, BLID: blids, TXID: txids} - connection.Objects <- command - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - - rlog.Tracef(3, "OBJECT REQUEST SENT sent size %d %s", len(serialized), connection.logid) - connection.Send_Message(serialized) - } - -} diff --git a/p2p/object_pool.go b/p2p/object_pool.go deleted file mode 100644 index c53d758..0000000 --- a/p2p/object_pool.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -//import "fmt" -//import "net" -import "sync" -import "time" - -//import "container/list" - -//import log "github.com/sirupsen/logrus" -//import "github.com/vmihailenco/msgpack" - -import "github.com/deroproject/derohe/crypto" - -//import "github.com/deroproject/derosuite/globals" - -import "github.com/deroproject/derohe/block" - -// if block request pool is empty, we are syncronised otherwise we are syncronising -var block_request_pool = map[crypto.Hash]uint64{} // these are received, we must attach a connection to blacklist peers - -var block_received = map[crypto.Hash]*block.Complete_Block{} // once blocks are received, they are placed here -var block_request_pool_mutex sync.Mutex - -func queue_block(blid crypto.Hash) { - block_request_pool_mutex.Lock() - defer block_request_pool_mutex.Unlock() - - // if object has already been received, it no longer should be request - if _, ok := block_received[blid]; ok { // object is already in pool - return - } - - // if object has already been requested, skip it , else add it to queue - if _, ok := block_request_pool[blid]; !ok { - block_request_pool[blid] = 0 - } -} - -// a block hash been received, make it ready for consumption -func queue_block_received(blid crypto.Hash, cbl *block.Complete_Block) { - block_request_pool_mutex.Lock() - defer block_request_pool_mutex.Unlock() - - if _, ok := block_request_pool[blid]; !ok { - // unknown object received discard it - return - } - delete(block_request_pool, blid) - - block_received[blid] = cbl -} - -// continusly retrieve_objects -func retrieve_objects() { - - for { - select { - case <-Exit_Event: - return - case <-time.After(5 * time.Second): - } - - block_request_pool_mutex.Lock() - - for k, _ := range block_request_pool { - - connection := Random_Connection(0) - if connection != nil { - connection.Send_ObjectRequest([]crypto.Hash{k}, []crypto.Hash{}) - } - } - block_request_pool_mutex.Unlock() - - } - -} - -// this goroutine will keep searching the queue for any blocks and see if they are can be attached somewhere, if yes they will be attached and cleaned up -func sync_retrieved_blocks() { - // success := make(chan bool) - for { - - select { - case <-Exit_Event: - return - //case <- success: - case <-time.After(1 * time.Second): - } - - block_request_pool_mutex.Lock() - - for blid, cbl := range block_received { - if chain.Is_Block_Topological_order(blid) { /// block is already in chain, discard it - delete(block_received, blid) - continue - } - _ = cbl - /*if cbl == nil { - delete(block_received,blid) - block_request_pool[blid]=0 // need to request object again - continue - }*/ - - /* - // attach it to parent, else skip for now - if chain.Block_Exists(cbl.Bl.Prev_Hash) { - if chain.Add_Complete_Block(cbl) { - // if block successfully added , delete it now - delete(block_received, blid) - - continue - } else { // checksum of something failed, request it randomly from another peer - block_request_pool[blid] = 0 - } - }*/ - } - block_request_pool_mutex.Unlock() - } -} diff --git a/p2p/object_response.go b/p2p/object_response.go deleted file mode 100644 index afce8fd..0000000 --- a/p2p/object_response.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -//import "fmt" -//import "net" -import "sync/atomic" -import "time" - -//import "container/list" - -//import log "github.com/sirupsen/logrus" -import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -//import "github.com/deroproject/derosuite/crypto" -//import "github.com/deroproject/derosuite/globals" - -import "github.com/deroproject/derohe/block" -import "github.com/deroproject/derohe/errormsg" -import "github.com/deroproject/derohe/transaction" - -// peer has responded with some objects, we must respond -func (connection *Connection) Handle_ObjectResponse(buf []byte) { - var response Object_Response_struct - - err := msgpack.Unmarshal(buf, &response) - if err != nil { - rlog.Warnf("Error while decoding incoming object response err %s %s", err, connection.logid) - connection.Exit() - } - - var expected Queued_Command - - select { - case expected = <-connection.Objects: - - default: // if nothing is on queue the peer sent us bogus request, - rlog.Warnf("Peer sent us a chain response, when we didnot request chain, Exiting, may be block the peer %s", connection.logid) - connection.Exit() - } - - if expected.Command != V2_COMMAND_OBJECTS_RESPONSE { - rlog.Warnf("We were waiting for a different object, but peer sent something else, Exiting, may be block the peer %s", connection.logid) - connection.Exit() - } - - // we need to verify common and update common - - if len(response.CBlocks) != len(expected.BLID) { // we requested x block , peer sent us y blocks, time to ban peer - rlog.Warnf("we got %d response for %d requests %s %s", len(response.CBlocks), len(expected.BLID), connection.logid) - } - - if len(response.Txs) != len(expected.TXID) { // we requested x block , peer sent us y blocks, time to ban peer - rlog.Warnf("we got %d response for %d requests %s %s", len(response.CBlocks), len(expected.BLID), connection.logid) - } - - // make sure connection does not timeout and be killed while processing huge blocks - processing_complete := make(chan bool) - go func() { - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - for { - select { - case <- processing_complete: return // complete the loop - case <-ticker.C:// give the chain some more time to respond - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - } - } - }() - - defer func() { - processing_complete <- true - }() - - - for i := 0; i < len(response.CBlocks); i++ { // process incoming full blocks - var cbl block.Complete_Block // parse incoming block and deserialize it - var bl block.Block - // lets deserialize block first and see whether it is the requested object - cbl.Bl = &bl - err := bl.Deserialize(response.CBlocks[i].Block) - if err != nil { // we have a block which could not be deserialized ban peer - rlog.Warnf("Error Incoming block could not be deserilised err %s %s", err, connection.logid) - connection.Exit() - return - } - - // check if deserialized hash is same as what we requested - if bl.GetHash() != expected.BLID[i] { // user is trying to spoof block, ban hime - connection.logger.Warnf("requested and response block mismatch") - rlog.Warnf("Error block hash mismatch Actual %s Expected %s err %s %s", bl.GetHash(), expected.BLID[i], connection.logid) - connection.Exit() - } - - // give the chain some more time to respond - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - - // check whether the object was requested one - - // complete the txs - for j := range response.CBlocks[i].Txs { - var tx transaction.Transaction - err = tx.DeserializeHeader(response.CBlocks[i].Txs[j]) - if err != nil { // we have a tx which could not be deserialized ban peer - rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, connection.logid) - connection.Exit() - - return - } - cbl.Txs = append(cbl.Txs, &tx) - } - - // check if we can add ourselves to chain - err, ok := chain.Add_Complete_Block(&cbl) - if !ok && err == errormsg.ErrInvalidPoW { - connection.logger.Warnf("This peer should be banned") - connection.Exit() - return - } - - if !ok && err == errormsg.ErrPastMissing { - rlog.Warnf("Error Incoming Block coould not be added due to missing past, so skipping future block err %s %s", err, connection.logid) - return - } - - // add the object to object pool from where it will be consume - // queue_block_received(bl.GetHash(),&cbl) - - } - - for i := range response.Txs { // process incoming txs for mempool - if !chain.Mempool.Mempool_TX_Exist(expected.TXID[i]) { // we still donot have it, so try to process it - var tx transaction.Transaction - err = tx.DeserializeHeader(response.Txs[i]) - if err != nil { // we have a tx which could not be deserialized ban peer - rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, connection.logid) - connection.Exit() - - return - } - chain.Add_TX_To_Pool(&tx) // currently we are ignoring error - } - } - -} diff --git a/p2p/rpc.go b/p2p/rpc.go new file mode 100644 index 0000000..b8d0686 --- /dev/null +++ b/p2p/rpc.go @@ -0,0 +1,87 @@ +package p2p + +// this function implements bidirectional rpc using yamux multiplexor +//import "fmt" +import "net" +import "net/rpc" +import "time" +import "io/ioutil" +import "github.com/hashicorp/yamux" + +type RPC_Connection struct { + ClientConn net.Conn // its used to trigger requests + Client *rpc.Client // used to dispatch RPC requests + + ServerConn net.Conn // its used to serve requests + + Session *yamux.Session + Conn net.Conn // connection backing everything +} + +func yamux_config() *yamux.Config { + return &yamux.Config{ + AcceptBacklog: 2, + EnableKeepAlive: false, + KeepAliveInterval: 5 * time.Second, + ConnectionWriteTimeout: 17 * time.Second, + MaxStreamWindowSize: uint32(256 * 1024), + LogOutput: ioutil.Discard, + } +} + +// do server side processing +func wait_stream_creation_server_side(conn net.Conn) (*RPC_Connection, error) { + + session, err := yamux.Server(conn, yamux_config()) // Setup server side of yamux + if err != nil { + conn.Close() + panic(err) + } + + // Accept a stream + client, err := session.Accept() + if err != nil { + panic(err) + } + + server, err := session.Accept() + if err != nil { + panic(err) + } + + rconn := &RPC_Connection{ClientConn: client, ServerConn: server, Session: session, Conn: conn} + rconn.common_processing() + return rconn, nil +} + +// do client side processing +func stream_creation_client_side(conn net.Conn) (*RPC_Connection, error) { + session, err := yamux.Client(conn, yamux_config()) // Setup client side of yamux + if err != nil { + conn.Close() + panic(err) + } + + // create a stream + client, err := session.Open() + if err != nil { + panic(err) + } + //create a stream + server, err := session.Open() + if err != nil { + panic(err) + } + + rconn := &RPC_Connection{ClientConn: server, ServerConn: client, Session: session, Conn: conn} // this line is flipped between client/server + rconn.common_processing() + return rconn, nil +} + +func (r *RPC_Connection) common_processing() { + //r.Client = rpc.NewClient(r.ClientConn) // will use GOB encoding, but doesn't have certain protections + r.Client = rpc.NewClientWithCodec(NewCBORClientCodec(r.ClientConn)) // will use CBOR encoding with protections + + // fmt.Printf("client connection %+v\n", r.Client) + +} diff --git a/p2p/rpc_cbor_codec.go b/p2p/rpc_cbor_codec.go new file mode 100644 index 0000000..c318eb3 --- /dev/null +++ b/p2p/rpc_cbor_codec.go @@ -0,0 +1,188 @@ +package p2p + +// this file implements CBOR codec to prevent from certain attacks +import "fmt" +import "io" +import "net" +import "net/rpc" +import "bufio" +import "encoding/binary" +import "github.com/fxamacker/cbor" + +import "github.com/deroproject/derohe/config" // only used get constants such as max data per frame + +// used to represent net/rpc structs +type Request struct { + ServiceMethod string `cbor:"M"` // format: "Service.Method" + Seq uint64 `cbor:"S"` // sequence number chosen by client +} + +type Response struct { + ServiceMethod string `cbor:"M"` // echoes that of the Request + Seq uint64 `cbor:"S"` // echoes that of the request + Error string `cbor:"E"` // error, if any. +} + +// reads our data, length prefix blocks +func Read_Data_Frame(r io.Reader, obj interface{}) error { + var frame_length_buf [4]byte + + //connection.set_timeout() + nbyte, err := io.ReadFull(r, frame_length_buf[:]) + if err != nil { + return err + } + if nbyte != 4 { + return fmt.Errorf("needed 4 bytes, but got %d bytes", nbyte) + } + + // time to ban + frame_length := binary.LittleEndian.Uint32(frame_length_buf[:]) + if frame_length == 0 { + return nil + } + // most probably memory DDOS attack, kill the connection + if uint64(frame_length) > (5 * config.STARGATE_HE_MAX_BLOCK_SIZE) { + return fmt.Errorf("Frame length is too big Expected %d Actual %d %s", 5*config.STARGATE_HE_MAX_BLOCK_SIZE, frame_length) + } + data_buf := make([]byte, frame_length) + data_size, err := io.ReadFull(r, data_buf) + if err != nil || data_size <= 0 || uint32(data_size) != frame_length { + return fmt.Errorf("Could not read data size read %d, frame length %d err %s", data_size, frame_length, err) + } + data_buf = data_buf[:frame_length] + err = cbor.Unmarshal(data_buf, obj) + + //fmt.Printf("Read object %+v raw %s\n",obj, data_buf) + return err +} + +// reads our data, length prefix blocks +func Write_Data_Frame(w io.Writer, obj interface{}) error { + var frame_length_buf [4]byte + data_bytes, err := cbor.Marshal(obj) + if err != nil { + return err + } + binary.LittleEndian.PutUint32(frame_length_buf[:], uint32(len(data_bytes))) + + if _, err = w.Write(frame_length_buf[:]); err != nil { + return err + } + _, err = w.Write(data_bytes[:]) + //fmt.Printf("Wrote object %+v raw %s\n",obj, data_bytes) + return err +} + +// ClientCodec implements the rpc.ClientCodec interface for generic golang objects. +type ClientCodec struct { + r *bufio.Reader + w io.WriteCloser +} + +// ServerCodec implements the rpc.ServerCodec interface for generic protobufs. +type ServerCodec ClientCodec + +// NewClientCodec returns a ClientCodec for communicating with the ServerCodec +// on the other end of the conn. +func NewCBORClientCodec(conn net.Conn) *ClientCodec { + return &ClientCodec{bufio.NewReader(conn), conn} +} + +// NewServerCodec returns a ServerCodec that communicates with the ClientCodec +// on the other end of the given conn. +func NewCBORServerCodec(conn net.Conn) *ServerCodec { + return &ServerCodec{bufio.NewReader(conn), conn} +} + +// WriteRequest writes the 4 byte length from the connection and encodes that many +// subsequent bytes into the given object. +func (c *ClientCodec) WriteRequest(req *rpc.Request, obj interface{}) error { + // Write the header + header := Request{ServiceMethod: req.ServiceMethod, Seq: req.Seq} + if err := Write_Data_Frame(c.w, header); err != nil { + return err + } + return Write_Data_Frame(c.w, obj) +} + +// ReadResponseHeader reads a 4 byte length from the connection and decodes that many +// subsequent bytes into the given object, decodes it, and stores the fields +// in the given request. +func (c *ClientCodec) ReadResponseHeader(resp *rpc.Response) error { + var header Response + if err := Read_Data_Frame(c.r, &header); err != nil { + return err + } + if header.ServiceMethod == "" { + return fmt.Errorf("header missing method: %s", header) + } + resp.ServiceMethod = header.ServiceMethod + resp.Seq = header.Seq + resp.Error = header.Error + + return nil +} + +// ReadResponseBody reads a 4 byte length from the connection and decodes that many +// subsequent bytes into the given object (which should be a pointer to a +// struct). +func (c *ClientCodec) ReadResponseBody(obj interface{}) error { + if obj == nil { + return nil + } + return Read_Data_Frame(c.r, obj) +} + +// Close closes the underlying connection. +func (c *ClientCodec) Close() error { + return c.w.Close() +} + +// Close closes the underlying connection. +func (c *ServerCodec) Close() error { + return c.w.Close() +} + +// ReadRequestHeader reads the header (which is prefixed by a 4 byte lil endian length +// indicating its size) from the connection, decodes it, and stores the fields +// in the given request. +func (s *ServerCodec) ReadRequestHeader(req *rpc.Request) error { + var header Request + if err := Read_Data_Frame(s.r, &header); err != nil { + return err + } + if header.ServiceMethod == "" { + return fmt.Errorf("header missing method: %s", header) + } + req.ServiceMethod = header.ServiceMethod + req.Seq = header.Seq + return nil +} + +// ReadRequestBody reads a 4 byte length from the connection and decodes that many +// subsequent bytes into the object +func (s *ServerCodec) ReadRequestBody(obj interface{}) error { + if obj == nil { + return nil + } + return Read_Data_Frame(s.r, obj) +} + +// WriteResponse writes the appropriate header. If +// the response was invalid, the size of the body of the resp is reported as +// having size zero and is not sent. +func (s *ServerCodec) WriteResponse(resp *rpc.Response, obj interface{}) error { + // Write the header + header := Response{ServiceMethod: resp.ServiceMethod, Seq: resp.Seq, Error: resp.Error} + + if err := Write_Data_Frame(s.w, header); err != nil { + return err + } + + if resp.Error == "" { // only write response object if error is nil + return Write_Data_Frame(s.w, obj) + } + + return nil +} diff --git a/p2p/chain_request.go b/p2p/rpc_chain_request.go similarity index 56% rename from p2p/chain_request.go rename to p2p/rpc_chain_request.go index 22be789..b5b101f 100644 --- a/p2p/chain_request.go +++ b/p2p/rpc_chain_request.go @@ -18,108 +18,46 @@ package p2p //import "fmt" //import "net" -import "sync/atomic" -import "time" //import "container/list" //import log "github.com/sirupsen/logrus" import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" //import "github.com/deroproject/derosuite/crypto" import "github.com/deroproject/derohe/globals" //import "github.com/deroproject/derosuite/blockchain" -// we are sending a chain request, build a packet with our chain data -// so as other side can respond with chain response -func (connection *Connection) Send_ChainRequest() { - var request Chain_Request_Struct - - fill_common(&request.Common) // fill common info - request.Command = V2_COMMAND_CHAIN_REQUEST - - // send our blocks, first 10 blocks directly, then decreasing in powers of 2 - start_point := chain.Load_TOPO_HEIGHT() - for i := int64(0); i < start_point; { - - blid, _ := chain.Load_Block_Topological_order_at_index(start_point - i) - request.Block_list = append(request.Block_list, blid) - request.TopoHeights = append(request.TopoHeights, start_point-i) - rlog.Tracef(3, "Adding block to chain request h %d %s", i, blid) - switch { - case len(request.Block_list) < 10: - i++ - default: - i = i * 2 - } - } - - // add genesis block at the end - request.Block_list = append(request.Block_list, globals.Config.Genesis_Block_Hash) - request.TopoHeights = append(request.TopoHeights, 0) - - // serialize and send - serialized, err := msgpack.Marshal(&request) - if err != nil { - panic(err) - } - - // queue command - command := Queued_Command{Command: V2_COMMAND_CHAIN_RESPONSE} - connection.Objects <- command - - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - //connection.Lock() - //connection.Command_queue.PushBack(command) // queue command - connection.Send_Message_prelocked(serialized) - //connection.Unlock() - - rlog.Tracef(2, "chain request sent successfully %s", globals.CTXString(connection.logger)) -} - // peer has requested chain -func (connection *Connection) Handle_ChainRequest(buf []byte) { - var request Chain_Request_Struct - var response Chain_Response_Struct - err := msgpack.Unmarshal(buf, &request) - if err != nil { - rlog.Warnf("Error while decoding incoming chain request err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return - } +func (c *Connection) Chain(request Chain_Request_Struct, response *Chain_Response_Struct) error { - // if len(request.Block_list) < 1 { // malformed request ban peer - rlog.Warnf("malformed chain request received, banning peer %+v %s", request, globals.CTXString(connection.logger)) - connection.Exit() - - return + rlog.Warnf("malformed chain request received, banning peer %+v %s", request, globals.CTXString(c.logger)) + c.exit() + return nil } if len(request.Block_list) != len(request.TopoHeights) || len(request.Block_list) > 1024 { rlog.Warnf("Peer chain request has %d block %d topos, therefore invalid", len(request.Block_list), len(request.TopoHeights)) - connection.Exit() - return + c.exit() + return nil } if request.Block_list[len(request.Block_list)-1] != globals.Config.Genesis_Block_Hash { rlog.Warnf("Peer's genesis block is different from our, so disconnect Actual %s Expected %s", request.Block_list[len(request.Block_list)-1], globals.Config.Genesis_Block_Hash) - connection.Exit() - return + c.exit() + return nil } - rlog.Tracef(2, "chain request received %s", globals.CTXString(connection.logger)) - // we must give user our version of the chain start_height := int64(0) start_topoheight := int64(0) for i := 0; i < len(request.Block_list); i++ { // find the common point in our chain ( the block is NOT orphan) - //connection.logger.Infof("Checking block for chain detection %d %s", i, request.Block_list[i]) + //c.logger.Infof("Checking block for chain detection %d %s", i, request.Block_list[i]) if chain.Block_Exists(request.Block_list[i]) && chain.Is_Block_Topological_order(request.Block_list[i]) && request.TopoHeights[i] == chain.Load_Block_Topological_order(request.Block_list[i]) { @@ -133,9 +71,6 @@ func (connection *Connection) Handle_ChainRequest(buf []byte) { // we can serve maximum of 512 BLID = 16K KB const MAX_BLOCKS = 512 - // if everything is OK, we must respond with chain response - //connection.Send_TimedSync(false) // send it as response - for i := start_topoheight; i <= chain.Load_TOPO_HEIGHT() && len(response.Block_list) <= MAX_BLOCKS; i++ { hash, _ := chain.Load_Block_Topological_order_at_index(i) response.Block_list = append(response.Block_list, [32]byte(hash)) @@ -155,15 +90,7 @@ func (connection *Connection) Handle_ChainRequest(buf []byte) { response.Start_height = start_height response.Start_topoheight = start_topoheight fill_common(&response.Common) // fill common info - response.Command = V2_COMMAND_CHAIN_RESPONSE + c.update(&request.Common) // update common information - // serialize and send - serialized, err := msgpack.Marshal(&response) - if err != nil { - panic(err) - } - - // we should add to queue that we are waiting for chain response - rlog.Tracef(2, "chain response sent due to incoming chain request sent len response = %d %s", len(serialized), globals.CTXString(connection.logger)) - connection.Send_Message(serialized) + return nil } diff --git a/p2p/rpc_changeset.go b/p2p/rpc_changeset.go new file mode 100644 index 0000000..ee61eb1 --- /dev/null +++ b/p2p/rpc_changeset.go @@ -0,0 +1,136 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package p2p + +import "github.com/romana/rlog" +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/graviton" + +// notifies inventory +func (c *Connection) ChangeSet(request ChangeList, response *Changes) (err error) { + + if len(request.TopoHeights) < 1 || len(request.TopoHeights) > 50 { // we are expecting 1 block or 1 tx + rlog.Warnf("malformed object request received, banning peer %+v %s", request, c.logid) + c.exit() + return nil + } + + c.update(&request.Common) // update common information + + for _, topo := range request.TopoHeights { + var cbl Complete_Block + + blid, err := chain.Load_Block_Topological_order_at_index(topo) + if err != nil { + return err + } + + bl, _ := chain.Load_BL_FROM_ID(blid) + cbl.Block = bl.Serialize() + for j := range bl.Tx_hashes { + var tx_bytes []byte + if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(bl.Tx_hashes[j]); err != nil { + return err + } + cbl.Txs = append(cbl.Txs, tx_bytes) // append all the txs + + } + cbl.Difficulty = chain.Load_Block_Difficulty(blid).String() + cbl.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(blid).String() + + // now we must load all the changes the block has done to the state tree + previous_sr, err := chain.Store.Topo_store.Read(topo - 1) + if err != nil { + return err + } + current_sr, err := chain.Store.Topo_store.Read(topo) + if err != nil { + return err + } + + { // do the heavy lifting, merge all changes before this topoheight + var previous_ss, current_ss *graviton.Snapshot + + if previous_ss, err = chain.Store.Balance_store.LoadSnapshot(previous_sr.State_Version); err == nil { + if current_ss, err = chain.Store.Balance_store.LoadSnapshot(current_sr.State_Version); err == nil { + if response.KeyCount == 0 { + var current_balance_tree *graviton.Tree + if current_balance_tree, err = current_ss.GetTree(config.BALANCE_TREE); err == nil { + response.KeyCount = current_balance_tree.KeyCountEstimate() + } + } + var changes Tree_Changes + if changes, err = record_changes(previous_ss, current_ss, config.BALANCE_TREE); err == nil { + cbl.Changes = append(cbl.Changes, changes) + } + + if response.SCKeyCount == 0 { + var current_sc_tree *graviton.Tree + if current_sc_tree, err = current_ss.GetTree(config.SC_META); err == nil { + response.SCKeyCount = current_sc_tree.KeyCountEstimate() + } + } + if changes, err = record_changes(previous_ss, current_ss, config.SC_META); err == nil { + cbl.Changes = append(cbl.Changes, changes) + // now lets build all the SC changes + for k := range cbl.Changes[1].Keys { + var sc_data Tree_Changes + //fmt.Printf("bundling SC changes %x\n", k) + if sc_data, err = record_changes(previous_ss, current_ss, string(k)); err == nil { + cbl.Changes = append(cbl.Changes, sc_data) + } + + } + } + } + + } + + if err != nil { + return err + } else { + + } + response.CBlocks = append(response.CBlocks, cbl) + } + } + + // if everything is OK, we must respond with object response + fill_common(&response.Common) // fill common info + + return nil + +} + +// this will record all the changes +func record_changes(previous_ss, current_ss *graviton.Snapshot, treename string) (changes Tree_Changes, err error) { + var previous_tree, current_tree *graviton.Tree + if previous_tree, err = previous_ss.GetTree(treename); err == nil { + if current_tree, err = current_ss.GetTree(treename); err == nil { + + change_handler := func(k, v []byte) { + changes.Keys = append(changes.Keys, k) + changes.Values = append(changes.Values, v) + } + err = graviton.Diff(previous_tree, current_tree, nil, change_handler, change_handler) + } + } + + changes.TreeName = []byte(treename) + + return +} diff --git a/p2p/rpc_handshake.go b/p2p/rpc_handshake.go new file mode 100644 index 0000000..6667fd3 --- /dev/null +++ b/p2p/rpc_handshake.go @@ -0,0 +1,190 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package p2p + +import "fmt" +import "bytes" +import "math/big" + +import "sync/atomic" +import "time" + +import "github.com/romana/rlog" +import "github.com/paulbellamy/ratecounter" + +import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/globals" + +// verify incoming handshake for number of checks such as mainnet/testnet etc etc +func Verify_Handshake(handshake *Handshake_Struct) bool { + return bytes.Equal(handshake.Network_ID[:], globals.Config.Network_ID[:]) +} + +func (handshake *Handshake_Struct) Fill() { + fill_common(&handshake.Common) // fill common info + + handshake.ProtocolVersion = "1.0.0" + handshake.DaemonVersion = config.Version.String() + handshake.Tag = node_tag + handshake.UTC_Time = int64(time.Now().UTC().Unix()) // send our UTC time + handshake.Local_Port = uint32(P2P_Port) // export requested or default port + handshake.Peer_ID = GetPeerID() // give our randomly generated peer id + handshake.Pruned = chain.LocatePruneTopo() + + // handshake.Flags = // add any flags necessary + + //scan our peer list and send peers which have been recently communicated + handshake.PeerList = get_peer_list() + copy(handshake.Network_ID[:], globals.Config.Network_ID[:]) +} + +// this is used only once +// all clients start with handshake, then other party sends avtive to mark that connection is active +func (connection *Connection) dispatch_test_handshake() { + var request, response Handshake_Struct + request.Fill() + + if err := connection.RConn.Client.Call("Peer.Handshake", request, &response); err != nil { + connection.exit() + return + } + + if !Verify_Handshake(&response) { // if not same network boot off + //fmt.Printf("kill connection network id mismatch peer network id1 %x", response.Network_ID) + connection.exit() + return + } + + connection.CDIFF.Store(new(big.Int).SetUint64(1)) + + connection.request_time.Store(time.Now()) + connection.SpeedIn = ratecounter.NewRateCounter(60 * time.Second) + connection.SpeedOut = ratecounter.NewRateCounter(60 * time.Second) + + connection.update(&response.Common) // update common information + + if !connection.Incoming { // setup success + Peer_SetSuccess(connection.Addr.String()) + } + + if len(response.ProtocolVersion) < 128 { + connection.ProtocolVersion = response.ProtocolVersion + } + + if len(response.DaemonVersion) < 128 { + connection.DaemonVersion = response.DaemonVersion + } + connection.Port = response.Local_Port + connection.Peer_ID = response.Peer_ID + if len(response.Tag) < 128 { + connection.Tag = response.Tag + } + if response.Pruned >= 1 { + connection.Pruned = response.Pruned + } + + // TODO we must also add the peer to our list + // which can be distributed to other peers + if connection.Port != 0 && connection.Port <= 65535 { // peer is saying it has an open port, handshake is success so add peer + + var p Peer + if connection.Addr.IP.To4() != nil { // if ipv4 + p.Address = fmt.Sprintf("%s:%d", connection.Addr.IP.String(), connection.Port) + } else { // if ipv6 + p.Address = fmt.Sprintf("[%s]:%d", connection.Addr.IP.String(), connection.Port) + } + p.ID = connection.Peer_ID + + p.LastConnected = uint64(time.Now().UTC().Unix()) + + // TODO we should add any flags here if necessary, but they are not + // required, since a peer can only be used if connected and if connected + // we already have a truly synced view + for _, k := range response.Flags { + switch k { + //case FLAG_MINER:p.Miner = true + default: + } + } + Peer_Add(&p) + } + + connection.TXpool_cache = map[uint64]uint32{} + + // parse delivered peer list as grey list + rlog.Debugf("Peer provides %d peers", len(response.PeerList)) + for i := range response.PeerList { + if i < 13 { + Peer_Add(&Peer{Address: response.PeerList[i].Addr, LastConnected: uint64(time.Now().UTC().Unix())}) + } + } + + Connection_Add(connection) // add connection to pool + + // mark active + var r Dummy + fill_common(&r.Common) // fill common info + if err := connection.RConn.Client.Call("Peer.Active", r, &r); err != nil { + connection.exit() + return + } + +} + +// mark connection active +func (c *Connection) Active(req Dummy, dummy *Dummy) error { + c.update(&req.Common) // update common information + atomic.StoreUint32(&c.State, ACTIVE) + fill_common(&dummy.Common) // fill common info + return nil +} + +// used to ping pong +func (c *Connection) Ping(req Dummy, resp *Dummy) error { + c.update(&req.Common) // update common information + fill_common(&resp.Common) // fill common info + return nil +} + +// serves handhake requests +func (c *Connection) Handshake(request Handshake_Struct, response *Handshake_Struct) error { + + if request.Peer_ID == GetPeerID() { // check if self connection exit + rlog.Tracef(1, "Same peer ID, probably self connection, disconnecting from this client") + c.exit() + return fmt.Errorf("Same peer ID") + } + + if !Verify_Handshake(&request) { // if not same network boot off + rlog.Tracef(1, "kill connection network id mismatch peer network id0 %x", request.Network_ID) + c.exit() + return fmt.Errorf("NID mismatch") + } + + response.Fill() + + c.update(&request.Common) // update common information + if c.State == ACTIVE { + for i := range request.PeerList { + if i < 13 { + Peer_Add(&Peer{Address: request.PeerList[i].Addr, LastConnected: uint64(time.Now().UTC().Unix())}) + } + } + } + + return nil +} diff --git a/p2p/notification.go b/p2p/rpc_notifications.go similarity index 51% rename from p2p/notification.go rename to p2p/rpc_notifications.go index 73174bd..6baff35 100644 --- a/p2p/notification.go +++ b/p2p/rpc_notifications.go @@ -16,40 +16,90 @@ package p2p -//import "fmt" -//import "net" +import "fmt" import "sync/atomic" -import "time" - import "encoding/binary" - -//import "container/list" - +import "time" import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" -import "github.com/deroproject/derohe/crypto" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/transaction" -// Peer has notified us of a new transaction -func (connection *Connection) Handle_Notification_Transaction(buf []byte) { - var request Notify_New_Objects_Struct +// notifies inventory +func (c *Connection) NotifyINV(request ObjectList, response *Dummy) (err error) { + var need ObjectList + var dirty = false - err := msgpack.Unmarshal(buf, &request) - if err != nil { - rlog.Warnf("Error while decoding incoming TX notifcation err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() + c.update(&request.Common) // update common information + + if len(request.Block_list) >= 1 { // handle incoming blocks list + for i := range request.Block_list { // + if !chain.Is_Block_Topological_order(request.Block_list[i]) { // block is not in our chain + if !chain.Block_Exists(request.Block_list[i]) { // check whether the block can be loaded from disk + need.Block_list = append(need.Block_list, request.Block_list[i]) + dirty = true + } + } + } } + if len(request.Tx_list) >= 1 { // handle incoming tx list and see whether it exists in mempoolor regpool + for i := range request.Tx_list { // + if !(chain.Mempool.Mempool_TX_Exist(request.Tx_list[i]) || chain.Regpool.Regpool_TX_Exist(request.Tx_list[i])) { // check if is already in mempool skip it + if _, err = chain.Store.Block_tx_store.ReadTX(request.Tx_list[i]); err != nil { // check whether the tx can be loaded from disk + need.Tx_list = append(need.Tx_list, request.Tx_list[i]) + dirty = true + } + } + } + } + + if dirty { // request inventory only if we want it + var oresponse Objects + fill_common(&need.Common) // fill common info + if err = c.RConn.Client.Call("Peer.GetObject", need, &oresponse); err != nil { + fmt.Printf("Call faileda: %v\n", err) + c.exit() + return + } else { // process the response + if err = c.process_object_response(oresponse); err != nil { + return + } + } + } + + fill_common(&response.Common) // fill common info + + return nil + +} + +// Peer has notified us of a new transaction +func (c *Connection) NotifyTx(request Objects, response *Dummy) error { + var err error var tx transaction.Transaction - err = tx.DeserializeHeader(request.Tx) + + c.update(&request.Common) // update common information + + if len(request.CBlocks) != 0 { + rlog.Warnf("Error while decoding incoming tx notifcation request err %s", globals.CTXString(c.logger)) + c.exit() + return fmt.Errorf("Notify TX cannot notify blocks") + } + + if len(request.Txs) != 1 { + rlog.Warnf("Error while decoding incoming tx notification request err %s ", globals.CTXString(c.logger)) + c.exit() + return fmt.Errorf("Notify TX can only notify 1 tx") + } + + err = tx.DeserializeHeader(request.Txs[0]) if err != nil { // we have a tx which could not be deserialized ban peer - rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(c.logger)) + c.exit() + return err } // track transaction propagation @@ -62,47 +112,45 @@ func (connection *Connection) Handle_Notification_Transaction(buf []byte) { } // try adding tx to pool - success_pool := chain.Add_TX_To_Pool(&tx) - - // add tx to cache of the peer who sent us this tx - connection.TXpool_cache_lock.Lock() - if success_pool && globals.Arguments["--lowcpuram"].(bool) == false && connection.TXpool_cache != nil { - + if err = chain.Add_TX_To_Pool(&tx); err == nil { + // add tx to cache of the peer who sent us this tx txhash := tx.GetHash() - connection.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])] = uint32(time.Now().Unix()) - - //logger.Debugf("Adding %s to cache", tx.GetHash()) + c.TXpool_cache_lock.Lock() + c.TXpool_cache[binary.LittleEndian.Uint64(txhash[:])] = uint32(time.Now().Unix()) + c.TXpool_cache_lock.Unlock() } - connection.TXpool_cache_lock.Unlock() + fill_common(&response.Common) // fill common info // broadcasting of tx is controlled by mempool + // broadcast how ??? + + return nil } -// Peer has notified us of a new block -func (connection *Connection) Handle_Notification_Block(buf []byte) { - var request Notify_New_Objects_Struct - - err := msgpack.Unmarshal(buf, &request) - if err != nil { - rlog.Warnf("Error while decoding incoming Block notifcation request err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() +func (c *Connection) NotifyBlock(request Objects, response *Dummy) error { + var err error + if len(request.CBlocks) != 1 { + rlog.Warnf("Error while decoding incoming Block notifcation request err %s %s", err, globals.CTXString(c.logger)) + c.exit() + return fmt.Errorf("Notify Block cannot only notify single block") } + c.update(&request.Common) // update common information var cbl block.Complete_Block // parse incoming block and deserialize it var bl block.Block // lets deserialize block first and see whether it is the requested object cbl.Bl = &bl - err = bl.Deserialize(request.CBlock.Block) + err = bl.Deserialize(request.CBlocks[0].Block) if err != nil { // we have a block which could not be deserialized ban peer - rlog.Warnf("Error Incoming block could not be deserilised err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return + rlog.Warnf("Error Incoming block could not be deserilised err %s %s", err, globals.CTXString(c.logger)) + c.exit() + return err } blid := bl.GetHash() - rlog.Infof("Incoming block Notification hash %s %s ", blid, globals.CTXString(connection.logger)) + rlog.Infof("Incoming block Notification hash %s %s ", blid, globals.CTXString(c.logger)) // track block propagation if first_time, ok := block_propagation_map.Load(blid); ok { @@ -115,32 +163,32 @@ func (connection *Connection) Handle_Notification_Block(buf []byte) { // object is already is in our chain, we need not relay it if chain.Is_Block_Topological_order(blid) { - return + return nil } // the block is not in our db, parse entire block, complete the txs and try to add it - if len(bl.Tx_hashes) == len(request.CBlock.Txs) { - connection.logger.Debugf("Received a complete block %s with %d transactions", blid, len(bl.Tx_hashes)) - for j := range request.CBlock.Txs { + if len(bl.Tx_hashes) == len(request.CBlocks[0].Txs) { + c.logger.Debugf("Received a complete block %s with %d transactions", blid, len(bl.Tx_hashes)) + for j := range request.CBlocks[0].Txs { var tx transaction.Transaction - err = tx.DeserializeHeader(request.CBlock.Txs[j]) + err = tx.DeserializeHeader(request.CBlocks[0].Txs[j]) if err != nil { // we have a tx which could not be deserialized ban peer - rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(c.logger)) + c.exit() + return err } cbl.Txs = append(cbl.Txs, &tx) } } else { // the block is NOT complete, we consider it as an ultra compact block - connection.logger.Debugf("Received an ultra compact block %s, total %d contains %d skipped %d transactions", blid, len(bl.Tx_hashes), len(request.CBlock.Txs), len(bl.Tx_hashes)-len(request.CBlock.Txs)) - for j := range request.CBlock.Txs { + c.logger.Debugf("Received an ultra compact block %s, total %d contains %d skipped %d transactions", blid, len(bl.Tx_hashes), len(request.CBlocks[0].Txs), len(bl.Tx_hashes)-len(request.CBlocks[0].Txs)) + for j := range request.CBlocks[0].Txs { var tx transaction.Transaction - err = tx.DeserializeHeader(request.CBlock.Txs[j]) + err = tx.DeserializeHeader(request.CBlocks[0].Txs[j]) if err != nil { // we have a tx which could not be deserialized ban peer - rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(connection.logger)) - connection.Exit() - return + rlog.Warnf("Error Incoming TX could not be deserialized err %s %s", err, globals.CTXString(c.logger)) + c.exit() + return err } chain.Add_TX_To_Pool(&tx) // add tx to pool } @@ -163,50 +211,54 @@ func (connection *Connection) Handle_Notification_Block(buf []byte) { var tx_bytes []byte if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(bl.Tx_hashes[i]); err != nil { // the tx mentioned in ultra compact block could not be found, request a full block - connection.Send_ObjectRequest([]crypto.Hash{blid}, []crypto.Hash{}) + //connection.Send_ObjectRequest([]crypto.Hash{blid}, []crypto.Hash{}) logger.Debugf("Ultra compact block %s missing TX %s, requesting full block", blid, bl.Tx_hashes[i]) - return + return err } tx = &transaction.Transaction{} if err = tx.DeserializeHeader(tx_bytes); err != nil { // the tx mentioned in ultra compact block could not be found, request a full block - connection.Send_ObjectRequest([]crypto.Hash{blid}, []crypto.Hash{}) + //connection.Send_ObjectRequest([]crypto.Hash{blid}, []crypto.Hash{}) logger.Debugf("Ultra compact block %s missing TX %s, requesting full block", blid, bl.Tx_hashes[i]) - return + return err } cbl.Txs = append(cbl.Txs, tx) // tx is from disk } } -// make sure connection does not timeout and be killed while processing huge blocks - processing_complete := make(chan bool) - go func() { - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - for { - select { - case <- processing_complete: return // complete the loop - case <-ticker.C:// give the chain some more time to respond - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - } - } - }() + // make sure connection does not timeout and be killed while processing huge blocks + processing_complete := make(chan bool) + go func() { + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-processing_complete: + return // complete the loop + case <-ticker.C: // give the chain some more time to respond + atomic.StoreInt64(&c.LastObjectRequestTime, time.Now().Unix()) + } + } + }() - defer func() { - processing_complete <- true - }() + defer func() { + processing_complete <- true + }() // check if we can add ourselves to chain if err, ok := chain.Add_Complete_Block(&cbl); ok { // if block addition was successfil // notify all peers - Broadcast_Block(&cbl, connection.Peer_ID) // do not send back to the original peer - + Broadcast_Block(&cbl, c.Peer_ID) // do not send back to the original peer } else { // ban the peer for sometime if err == errormsg.ErrInvalidPoW { - connection.logger.Warnf("This peer should be banned and terminated") - connection.Exit() + c.logger.Warnf("This peer should be banned and terminated") + c.exit() + return err } } + fill_common(&response.Common) // fill common info + + return nil } diff --git a/p2p/object_request.go b/p2p/rpc_object_request.go similarity index 50% rename from p2p/object_request.go rename to p2p/rpc_object_request.go index a288d47..00b5414 100644 --- a/p2p/object_request.go +++ b/p2p/rpc_object_request.go @@ -16,90 +16,29 @@ package p2p -//import "fmt" -//import "net" -import "sync/atomic" -import "time" - -//import "container/list" - import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -//import "github.com/deroproject/derosuite/crypto" -//import "github.com/deroproject/derosuite/globals" - -import "github.com/deroproject/derohe/crypto" - -//import "github.com/deroproject/derosuite/globals" - -//import "github.com/deroproject/derosuite/blockchain" - -// we are sending object request -// right now we only send block ids -func (connection *Connection) Send_ObjectRequest(blids []crypto.Hash, txids []crypto.Hash) { - - var request Object_Request_Struct - fill_common(&request.Common) // fill common info - request.Command = V2_COMMAND_OBJECTS_REQUEST - - for i := range blids { - request.Block_list = append(request.Block_list, blids[i]) - } - - for i := range txids { - request.Tx_list = append(request.Tx_list, txids[i]) - } - - if len(blids) > 0 || len(txids) > 0 { - serialized, err := msgpack.Marshal(&request) // serialize and send - if err != nil { - panic(err) - } - - // use first object - - command := Queued_Command{Command: V2_COMMAND_OBJECTS_RESPONSE, BLID: blids, TXID: txids} - - connection.Objects <- command - atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) - - // we should add to queue that we are waiting for object response - //command := Queued_Command{Command: V2_COMMAND_OBJECTS_RESPONSE, BLID: blids, TXID: txids, Started: time.Now()} - - connection.Lock() - //connection.Command_queue.PushBack(command) // queue command - connection.Send_Message_prelocked(serialized) - connection.Unlock() - rlog.Tracef(3, "object request sent contains %d blids %d txids %s ", len(blids), connection.logid) - } -} // peer has requested some objects, we must respond // if certain object is not in our list we respond with empty buffer for that slot -func (connection *Connection) Handle_ObjectRequest(buf []byte) { - var request Object_Request_Struct - var response Object_Response_struct - - err := msgpack.Unmarshal(buf, &request) - if err != nil { - rlog.Warnf("Error while decoding incoming object request err %s %s", err, connection.logid) - connection.Exit() - } +// an object is either a block or a tx +func (connection *Connection) GetObject(request ObjectList, response *Objects) error { + var err error if len(request.Block_list) < 1 && len(request.Tx_list) < 1 { // we are expecting 1 block or 1 tx - rlog.Warnf("malformed object request received, banning peer %+v %s", request, connection.logid) - connection.Exit() + rlog.Warnf("malformed object request received, banning peer %+v %s", request) + connection.exit() + return nil } + connection.update(&request.Common) // update common information - for i := range request.Block_list { // find the common point in our chain + for i := range request.Block_list { // find the block var cbl Complete_Block bl, _ := chain.Load_BL_FROM_ID(request.Block_list[i]) cbl.Block = bl.Serialize() for j := range bl.Tx_hashes { var tx_bytes []byte if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(bl.Tx_hashes[j]); err != nil { - return + return err } cbl.Txs = append(cbl.Txs, tx_bytes) // append all the txs @@ -114,20 +53,14 @@ func (connection *Connection) Handle_ObjectRequest(buf []byte) { } else if tx := chain.Regpool.Regpool_Get_TX(request.Tx_list[i]); tx != nil { // if tx can be satisfied from regpool, so be it tx_bytes = tx.Serialize() } else if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(request.Tx_list[i]); err != nil { - return + return err } response.Txs = append(response.Txs, tx_bytes) // append all the txs } // if everything is OK, we must respond with object response fill_common(&response.Common) // fill common info - response.Command = V2_COMMAND_OBJECTS_RESPONSE - serialized, err := msgpack.Marshal(&response) // serialize and send - if err != nil { - panic(err) - } - - rlog.Tracef(3, "OBJECT RESPONSE SENT sent size %d %s", len(serialized), connection.logid) - connection.Send_Message(serialized) + //rlog.Tracef(3, "OBJECT RESPONSE SENT sent size %d %s", len(serialized), connection.logid) + return nil } diff --git a/p2p/rpc_treesection.go b/p2p/rpc_treesection.go new file mode 100644 index 0000000..e1e7f29 --- /dev/null +++ b/p2p/rpc_treesection.go @@ -0,0 +1,68 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package p2p + +import "github.com/romana/rlog" +import "github.com/deroproject/graviton" + +// get parts of the specified balance tree chunk by chunk +func (c *Connection) TreeSection(request Request_Tree_Section_Struct, response *Response_Tree_Section_Struct) (err error) { + + if request.Topo < 2 || request.SectionLength > 256 || len(request.Section) < int(request.SectionLength/8) { // we are expecting 1 block or 1 tx + rlog.Warnf("malformed object request received, banning peer %+v %s", request, c.logid) + c.exit() + } + + c.update(&request.Common) // update common information + + topo_sr, err := chain.Store.Topo_store.Read(request.Topo) + if err != nil { + return + } + + { // do the heavy lifting, merge all changes before this topoheight + var topo_ss *graviton.Snapshot + var topo_balance_tree *graviton.Tree + if topo_ss, err = chain.Store.Balance_store.LoadSnapshot(topo_sr.State_Version); err == nil { + if topo_balance_tree, err = topo_ss.GetTree(string(request.TreeName)); err == nil { + cursor := topo_balance_tree.Cursor() + for k, v, err := cursor.SpecialFirst(request.Section, uint(request.SectionLength)); err == nil; k, v, err = cursor.Next() { + response.Keys = append(response.Keys, k) + response.Values = append(response.Values, v) + + if len(response.Keys) > 10000 { + break + } + + } + err = nil + + } + + } + + if err != nil { + return + } + + } + + // if everything is OK, we must respond with object response + fill_common(&response.Common) // fill common info + return nil + +} diff --git a/p2p/timedsync.go b/p2p/timedsync.go deleted file mode 100644 index 98132db..0000000 --- a/p2p/timedsync.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2017-2021 DERO Project. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 -// -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package p2p - -//import "fmt" -//import "net" -import "sync/atomic" -import "time" - -//import "container/list" -import "github.com/romana/rlog" -import "github.com/vmihailenco/msgpack" - -//import "github.com/deroproject/derosuite/crypto" -//import "github.com/deroproject/derosuite/globals" - -// reads our data, length prefix blocks -func (connection *Connection) Send_TimedSync(request bool) { - - var sync Sync_Struct - - fill_common(&sync.Common) // fill common info - sync.Command = V2_COMMAND_SYNC - sync.Request = request - - serialized, err := msgpack.Marshal(&sync) // serialize and send - if err != nil { - panic(err) - } - if request { // queue command that we are expecting a response - //connection.Lock() - connection.request_time.Store(time.Now()) - //connection.Unlock() - } - //rlog.Tracef(2, "Timed sync sent successfully %s", connection.logid) - connection.Send_Message(serialized) - -} - -// handles incoming timed syncs -func (connection *Connection) Handle_TimedSync(buf []byte) { - var sync Sync_Struct - err := msgpack.Unmarshal(buf, &sync) - if err != nil { - rlog.Warnf("Error while decoding incoming chain request err %s %s", err, connection.logid) - connection.Exit() - return - } - //rlog.Tracef(2, "Timed sync received %s", connection.logid) - if sync.Request { - connection.Send_TimedSync(false) // send it as response - } else { // this is a response for our request track latency - //connection.Lock() - atomic.StoreInt64(&connection.Latency, int64(time.Now().Sub(connection.request_time.Load().(time.Time))/2)) // divide by 2 is for round-trip - //connection.Unlock() - - } -} diff --git a/p2p/wire_structs.go b/p2p/wire_structs.go index d9a45cf..0258a49 100644 --- a/p2p/wire_structs.go +++ b/p2p/wire_structs.go @@ -16,122 +16,126 @@ package p2p -// This file defines the structure for the protocol which is a msgp encoded ( which is standard) -// msgp would cause an easy rewrite of p2p layer even in c, ruby or rust etc as future may demand -// the protocol is length prefixed msgp payload -// though we can use http2 stream features, they may become compilcated as the project evolves -// the prefix length is 4 bytes, little endian encoded ( so a frame can be 4GB in size) -// this is Work-In-Progress -// the reason for writing it from scratch is the mess of boost serialisation +// This file defines the structure for the protocol which is CBOR ( which is standard) stream multiplexed using yamux +// stream multiplexing allows us have bidirection RPC using net/rpc // the p2p package is currently the most complex within the entire project -// the protocol is partly syncronous, partly asyncronous , except for first handshake, so the node remain undetectable to external network scans, the detection cost is atleast send a handshake packet*/ - -// these are the commands required to make it completely operational - -const V2_COMMAND_NULL = 0 // default value is zero and is a null command -//const V2_COMMAND_NULL = 0 -//first 40 are reserved for future use -const V2_COMMAND_HANDSHAKE = 41 // commands are syncronous and must be responded within 10 secs -const V2_COMMAND_SYNC = 42 -const V2_COMMAND_CHAIN_REQUEST = 43 -const V2_COMMAND_CHAIN_RESPONSE = 44 -const V2_COMMAND_OBJECTS_REQUEST = 45 -const V2_COMMAND_OBJECTS_RESPONSE = 46 - -const V2_NOTIFY_NEW_BLOCK = 0xff // Notifications are asyncronous all notifications come here, such as new block, new txs -const V2_NOTIFY_NEW_TX = 0xfe // notify tx using this -const V2_NOTIFY_INVENTORY = 0xfd // notify inventory that we have a new tx, or block -// this has the same structure V2_COMMAND_OBJECTS_REQUEST +// the protocol is partly syncronous, partly asyncronous // used to parse incoming packet for for command , so as a repective command command could be triggered type Common_Struct struct { - Height int64 `msgpack:"HEIGHT"` - TopoHeight int64 `msgpack:"THEIGHT"` - StableHeight int64 `msgpack:"SHEIGHT"` - Cumulative_Difficulty string `msgpack:"CDIFF"` - StateHash [32]byte `msgpack:"STATE"` - // Top_ID [32]byte `msgpack:"TOP"` // 32 bytes of Top block - Top_Version uint64 `msgpack:"HF"` // this basically represents the hard fork version + Height int64 `cbor:"HEIGHT"` + TopoHeight int64 `cbor:"THEIGHT"` + StableHeight int64 `cbor:"SHEIGHT"` + Cumulative_Difficulty string `cbor:"CDIFF"` + StateHash [32]byte `cbor:"STATE"` + // Top_ID [32]byte `cbor:"TOP"` // 32 bytes of Top block + Top_Version uint64 `cbor:"HF"` // this basically represents the hard fork version } -const FLAG_LOWCPURAM string = "LOWCPURAM" +type Dummy struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common +} // at start, client sends handshake and server will respond to handshake type Handshake_Struct struct { - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - ProtocolVersion string `msgpack:"PVERSION"` // version is a sematic version string semver - Tag string `msgpack:"TAG"` // user specific tag - DaemonVersion string `msgpack:"DVERSION"` - UTC_Time int64 `msgpack:"UTC"` - Local_Port uint32 `msgpack:"LP"` - Peer_ID uint64 `msgpack:"PID"` - Pruned int64 `msgpack:"PRUNED"` - Network_ID [16]byte `msgpack:"NID"` // 16 bytes - Flags []string `msgpack:"FLAGS"` - PeerList []Peer_Info `msgpack:"PLIST"` - Extension_List []string `msgpack:"EXT"` - Request bool `msgpack:"REQUEST"` //whether this is a request + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + ProtocolVersion string `cbor:"PVERSION"` // version is a sematic version string semver + Tag string `cbor:"TAG"` // user specific tag + DaemonVersion string `cbor:"DVERSION"` + UTC_Time int64 `cbor:"UTC"` + Local_Port uint32 `cbor:"LP"` + Peer_ID uint64 `cbor:"PID"` + Pruned int64 `cbor:"PRUNED"` + Network_ID [16]byte `cbor:"NID"` // 16 bytes + Flags []string `cbor:"FLAGS"` + PeerList []Peer_Info `cbor:"PLIST"` + Extension_List []string `cbor:"EXT"` + Request bool `cbor:"REQUEST"` //whether this is a request } type Peer_Info struct { - Addr string `msgpack:"ADDR"` // ip:port pair - Miner bool `msgpack:"MINER"` - //ID uint64 `msgpack:"I"` - //LastSeen uint64 `msgpack:"LS"` + Addr string `cbor:"ADDR"` // ip:port pair + Miner bool `cbor:"MINER"` + //ID uint64 `cbor:"I"` + //LastSeen uint64 `cbor:"LS"` } type Sync_Struct struct { // sync packets are sent every 2 seconds - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - PeerList []Peer_Info `msgpack:"PLIST"` // update peer list - Request bool `msgpack:"REQUEST"` //whether this is a request + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + PeerList []Peer_Info `cbor:"PLIST"` // update peer list + Request bool `cbor:"REQUEST"` //whether this is a request } type Chain_Request_Struct struct { // our version of chain - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - Block_list [][32]byte `msgpack:"BLIST"` // block list - TopoHeights []int64 `msgpack:"TOPO"` // topo heights of added blocks + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + Block_list [][32]byte `cbor:"BLIST"` // block list + TopoHeights []int64 `cbor:"TOPO"` // topo heights of added blocks } type Chain_Response_Struct struct { // peers gives us point where to get the chain - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - Start_height int64 `msgpack:"SH"` - Start_topoheight int64 `msgpack:"STH"` - Block_list [][32]byte `msgpack:"BLIST"` - TopBlocks [][32]byte `msgpack:"TOPBLOCKS"` // top blocks used for faster syncronisation of alt-tips + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + Start_height int64 `cbor:"SH"` + Start_topoheight int64 `cbor:"STH"` + Block_list [][32]byte `cbor:"BLIST"` + TopBlocks [][32]byte `cbor:"TOPBLOCKS"` // top blocks used for faster syncronisation of alt-tips // this contains all blocks hashes for the last 10 heights, heightwise ordered - } -// also used by V2_NOTIFY_INVENTORY -type Object_Request_Struct struct { - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - Block_list [][32]byte `msgpack:"BLIST"` - Tx_list [][32]byte `msgpack:"TXLIST"` +type ObjectList struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + Block_list [][32]byte `cbor:"BLIST"` + Tx_list [][32]byte `cbor:"TXLIST"` } -type Object_Response_struct struct { - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - CBlocks []Complete_Block `msgpack:"CBLOCKS"` - Txs [][]byte `msgpack:"TXS"` +type Objects struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + CBlocks []Complete_Block `cbor:"CBLOCKS"` + Txs [][]byte `cbor:"TXS"` +} + +// used to request what all changes are done by the block to the chain +type ChangeList struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + TopoHeights []int64 `cbor:"TOPO"` +} + +type Changes struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + CBlocks []Complete_Block `cbor:"CBLOCKS"` + KeyCount int64 `cbor:"KEYCOUNT"` + SCKeyCount int64 `cbor:"SCKEYCOUNT"` +} + +type Request_Tree_Section_Struct struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + Topo int64 `cbor:"TOPO"` // request section from the balance tree of this topo + TreeName []byte `cbor:"TREENAME,omitempty"` // changes to state tree + Section []byte `cbor:"SECTION"` // section path from which data must be received + SectionLength uint64 `cbor:"SECTIONL"` // section length in bits +} + +type Response_Tree_Section_Struct struct { + Common Common_Struct `cbor:"COMMON"` // add all fields of Common + Topo int64 `cbor:"TOPO"` // request section from the balance tree of this topo + TreeName []byte `cbor:"TREENAME,omitempty"` // changes to state tree + Section []byte `cbor:"SECTION"` + SectionLength uint64 `cbor:"SECTIONL"` // section length in bits + StateHash [32]byte `cbor:"STATE"` + Keys [][]byte `cbor:"KEYS,omitempty"` // changes to state tree + Values [][]byte `cbor:"VALUES,omitempty"` // changes to state tree +} + +type Tree_Changes struct { + TreeName []byte `cbor:"TREENAME,omitempty"` // changes to state tree + Keys [][]byte `cbor:"KEYS,omitempty"` // changes to state tree + Values [][]byte `cbor:"VALUES,omitempty"` // changes to state tree } type Complete_Block struct { - Block []byte `msgpack:"BLOCK"` - Txs [][]byte `msgpack:"TXS"` + Block []byte `cbor:"BLOCK"` + Txs [][]byte `cbor:"TXS"` + Difficulty string `cbor:"DIFF,omitempty"` // Diff + Cumulative_Difficulty string `cbor:"CDIFF,omitempty"` // CDiff + Changes []Tree_Changes `cbor:"CHANGES,omitempty"` // changes to state tree } - -type Notify_New_Objects_Struct struct { - Command uint64 `msgpack:"COMMAND"` - Common Common_Struct `msgpack:"COMMON"` // add all fields of Common - CBlock Complete_Block `msgpack:"CBLOCK"` - Tx []byte `msgpack:"TX"` -} - -// each packet has to be parsed twice once for extracting command and then a full parsing based on Command diff --git a/proof/proof.go b/proof/proof.go index 4e9ed1c..f8a141c 100644 --- a/proof/proof.go +++ b/proof/proof.go @@ -19,20 +19,19 @@ package proof import "fmt" import "math/big" import "encoding/hex" -import "encoding/binary" -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/crypto/bn256" -import "github.com/deroproject/derohe/address" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/rpc" +import "github.com/deroproject/derohe/cryptography/bn256" import "github.com/deroproject/derohe/transaction" //import "github.com/deroproject/derosuite/walletapi" // to decode encrypted payment ID // this function will prove detect and decode output amount for the tx -func Prove(proof string, input_tx string, mainnet bool) (receivers []string, amounts []uint64, payids [][]byte, err error) { +func Prove(proof string, input_tx string, ring [][][]byte, mainnet bool) (receivers []string, amounts []uint64, payload_raw [][]byte, payload_decoded []string, err error) { var tx transaction.Transaction - addr, err := address.NewAddress(proof) + addr, err := rpc.NewAddress(proof) if err != nil { return } @@ -42,8 +41,14 @@ func Prove(proof string, input_tx string, mainnet bool) (receivers []string, amo return } - if len(addr.PaymentID) != 8 { - err = fmt.Errorf("Invalid proof paymentid") + args := addr.Arguments + + amount := uint64(0) + + if args.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // this service is expecting value to be specfic + amount = args.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { + err = fmt.Errorf("Invalid proof.") return } @@ -57,28 +62,52 @@ func Prove(proof string, input_tx string, mainnet bool) (receivers []string, amo return } + // now lets decode the ring + + for t := range ring { + for i := range ring[t] { + point_compressed := ring[t][i][:] + + var p bn256.G1 + if err = p.DecodeCompressed(point_compressed[:]); err != nil { + err = fmt.Errorf("Invalid Ring member.") + return + } + + tx.Payloads[t].Statement.Publickeylist = append(tx.Payloads[t].Statement.Publickeylist, &p) + } + } + // okay all inputs have been parsed - - amount := binary.LittleEndian.Uint64(addr.PaymentID) - var x bn256.G1 x.ScalarMult(crypto.G, new(big.Int).SetInt64(int64(amount))) x.Add(new(bn256.G1).Set(&x), addr.PublicKey.G1()) - for k := range tx.Statement.C { - if x.String() == tx.Statement.C[k].String() { + for t := range tx.Payloads { + for k := range tx.Payloads[t].Statement.C { - astring := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[k])) - astring.Mainnet = mainnet + if x.String() == tx.Payloads[t].Statement.C[k].String() { - receivers = append(receivers, astring.String()) - amounts = append(amounts, amount) + astring := rpc.NewAddressFromKeys((*crypto.Point)(tx.Payloads[t].Statement.Publickeylist[k])) + astring.Mainnet = mainnet - //decode payment id - output := crypto.EncryptDecryptPaymentID(addr.PublicKey.G1(), tx.PaymentID[:]) - payids = append(payids, output) - return + receivers = append(receivers, astring.String()) + amounts = append(amounts, amount) + crypto.EncryptDecryptUserData(addr.PublicKey.G1(), tx.Payloads[t].RPCPayload) + // skip first byte as it is not guaranteed, even rest of the bytes are not + + payload_raw = append(payload_raw, tx.Payloads[t].RPCPayload[1:]) + var args rpc.Arguments + if err := args.UnmarshalBinary(tx.Payloads[t].RPCPayload[1:]); err == nil { + payload_decoded = append(payload_decoded, fmt.Sprintf("%s", args)) + } else { + payload_decoded = append(payload_decoded, err.Error()) + } + + return + + } } } diff --git a/rpc/LICENSE b/rpc/LICENSE new file mode 100644 index 0000000..26fca3f --- /dev/null +++ b/rpc/LICENSE @@ -0,0 +1,90 @@ +RESEARCH LICENSE + + +Version 1.1.2 + +I. DEFINITIONS. + +"Licensee " means You and any other party that has entered into and has in effect a version of this License. + +“Licensor” means DERO PROJECT(GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8) and its successors and assignees. + +"Modifications" means any (a) change or addition to the Technology or (b) new source or object code implementing any portion of the Technology. + +"Research Use" means research, evaluation, or development for the purpose of advancing knowledge, teaching, learning, or customizing the Technology for personal use. Research Use expressly excludes use or distribution for direct or indirect commercial (including strategic) gain or advantage. + +"Technology" means the source code, object code and specifications of the technology made available by Licensor pursuant to this License. + +"Technology Site" means the website designated by Licensor for accessing the Technology. + +"You" means the individual executing this License or the legal entity or entities represented by the individual executing this License. + +II. PURPOSE. + +Licensor is licensing the Technology under this Research License (the "License") to promote research, education, innovation, and development using the Technology. + +COMMERCIAL USE AND DISTRIBUTION OF TECHNOLOGY AND MODIFICATIONS IS PERMITTED ONLY UNDER AN APPROPRIATE COMMERCIAL USE LICENSE AVAILABLE FROM LICENSOR AT . + +III. RESEARCH USE RIGHTS. + +A. Subject to the conditions contained herein, Licensor grants to You a non-exclusive, non-transferable, worldwide, and royalty-free license to do the following for Your Research Use only: + +1. reproduce, create Modifications of, and use the Technology alone, or with Modifications; +2. share source code of the Technology alone, or with Modifications, with other Licensees; + +3. distribute object code of the Technology, alone, or with Modifications, to any third parties for Research Use only, under a license of Your choice that is consistent with this License; and + +4. publish papers and books discussing the Technology which may include relevant excerpts that do not in the aggregate constitute a significant portion of the Technology. + +B. Residual Rights. You may use any information in intangible form that you remember after accessing the Technology, except when such use violates Licensor's copyrights or patent rights. + +C. No Implied Licenses. Other than the rights granted herein, Licensor retains all rights, title, and interest in Technology , and You retain all rights, title, and interest in Your Modifications and associated specifications, subject to the terms of this License. + +D. Open Source Licenses. Portions of the Technology may be provided with notices and open source licenses from open source communities and third parties that govern the use of those portions, and any licenses granted hereunder do not alter any rights and obligations you may have under such open source licenses, however, the disclaimer of warranty and limitation of liability provisions in this License will apply to all Technology in this distribution. + +IV. INTELLECTUAL PROPERTY REQUIREMENTS + +As a condition to Your License, You agree to comply with the following restrictions and responsibilities: + +A. License and Copyright Notices. You must include a copy of this License in a Readme file for any Technology or Modifications you distribute. You must also include the following statement, "Use and distribution of this technology is subject to the Java Research License included herein", (a) once prominently in the source code tree and/or specifications for Your source code distributions, and (b) once in the same file as Your copyright or proprietary notices for Your binary code distributions. You must cause any files containing Your Modification to carry prominent notice stating that You changed the files. You must not remove or alter any copyright or other proprietary notices in the Technology. + +B. Licensee Exchanges. Any Technology and Modifications You receive from any Licensee are governed by this License. + +V. GENERAL TERMS. + +A. Disclaimer Of Warranties. + +TECHNOLOGY IS PROVIDED "AS IS", WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT ANY SUCH TECHNOLOGY IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING OF THIRD PARTY RIGHTS. YOU AGREE THAT YOU BEAR THE ENTIRE RISK IN CONNECTION WITH YOUR USE AND DISTRIBUTION OF ANY AND ALL TECHNOLOGY UNDER THIS LICENSE. + +B. Infringement; Limitation Of Liability. + +1. If any portion of, or functionality implemented by, the Technology becomes the subject of a claim or threatened claim of infringement ("Affected Materials"), Licensor may, in its unrestricted discretion, suspend Your rights to use and distribute the Affected Materials under this License. Such suspension of rights will be effective immediately upon Licensor's posting of notice of suspension on the Technology Site. + +2. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING OUT OF THIS LICENSE (INCLUDING, WITHOUT LIMITATION, LOSS OF PROFITS, USE, DATA, OR ECONOMIC ADVANTAGE OF ANY SORT), HOWEVER IT ARISES AND ON ANY THEORY OF LIABILITY (including negligence), WHETHER OR NOT LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. LIABILITY UNDER THIS SECTION V.B.2 SHALL BE SO LIMITED AND EXCLUDED, NOTWITHSTANDING FAILURE OF THE ESSENTIAL PURPOSE OF ANY REMEDY. + +C. Termination. + +1. You may terminate this License at any time by notifying Licensor in writing. + +2. All Your rights will terminate under this License if You fail to comply with any of its material terms or conditions and do not cure such failure within thirty (30) days after becoming aware of such noncompliance. + +3. Upon termination, You must discontinue all uses and distribution of the Technology , and all provisions of this Section V shall survive termination. + +D. Miscellaneous. + +1. Trademark. You agree to comply with Licensor's Trademark & Logo Usage Requirements, if any and as modified from time to time, available at the Technology Site. Except as expressly provided in this License, You are granted no rights in or to any Licensor's trademarks now or hereafter used or licensed by Licensor. + +2. Integration. This License represents the complete agreement of the parties concerning the subject matter hereof. + +3. Severability. If any provision of this License is held unenforceable, such provision shall be reformed to the extent necessary to make it enforceable unless to do so would defeat the intent of the parties, in which case, this License shall terminate. + +4. Governing Law. This License is governed by the laws of the United States and the State of California, as applied to contracts entered into and performed in California between California residents. In no event shall this License be construed against the drafter. + +5. Export Control. You agree to comply with the U.S. export controlsand trade laws of other countries that apply to Technology and Modifications. + +READ ALL THE TERMS OF THIS LICENSE CAREFULLY BEFORE ACCEPTING. + +BY CLICKING ON THE YES BUTTON BELOW OR USING THE TECHNOLOGY, YOU ARE ACCEPTING AND AGREEING TO ABIDE BY THE TERMS AND CONDITIONS OF THIS LICENSE. YOU MUST BE AT LEAST 18 YEARS OF AGE AND OTHERWISE COMPETENT TO ENTER INTO CONTRACTS. + +IF YOU DO NOT MEET THESE CRITERIA, OR YOU DO NOT AGREE TO ANY OF THE TERMS OF THIS LICENSE, DO NOT USE THIS SOFTWARE IN ANY FORM. + diff --git a/rpc/address.go b/rpc/address.go new file mode 100644 index 0000000..57ed1d9 --- /dev/null +++ b/rpc/address.go @@ -0,0 +1,202 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package rpc + +import "fmt" + +//import "github.com/deroproject/derohe/config" +import "github.com/deroproject/derohe/cryptography/crypto" + +// older dero address https://cryptonote.org/cns/cns007.txt to understand address more +// current dero versions use https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + +// there are 3 hrps for mainnet DERO, DEROI,DEROPROOF +// these are 3 hrps for testnet DETO, DETOI,DEROPROOF +// so a total of 5 hrps +// 1 byte version is support capable to represent 255 versions, currently we will be using only version 1 +// point is 33 bytes +// payment id if present is 8 bytes + +type Address struct { + Network uint64 + Mainnet bool + Proof bool + PublicKey *crypto.Point //33 byte compressed point + Arguments Arguments // all data related to integrated address +} + +// Encode encodes hrp(human-readable part) , version(int) and data(bytes array), returns Address / or error +func (a Address) MarshalText() ([]byte, error) { + hrp := "dero" + if !a.Mainnet { + hrp = "deto" + } + + if len(a.Arguments) >= 1 { + hrp += "i" + } + + if a.Proof { + hrp = "deroproof" + } + + // first we are appending version byte + data_bytes := append([]byte{1}, a.PublicKey.EncodeCompressed()...) + + if len(a.Arguments) >= 1 { + if b, err := a.Arguments.MarshalBinary(); err != nil { + return []byte{}, err + } else { + data_bytes = append(data_bytes, b...) + } + } + var data_ints []int + for i := range data_bytes { + data_ints = append(data_ints, int(data_bytes[i])) + } + + data, err := convertbits(data_ints, 8, 5, true) + if err != nil { + return []byte{}, err + } + ret, err := Encode(hrp, data) + if err != nil { + return []byte{}, err + } + return []byte(ret), nil +} + +// stringifier +func (a Address) String() string { + result, _ := a.MarshalText() + return string(result) +} + +// base address if its integrated address +func (a Address) BaseAddress() Address { + z := a.Clone() + z.Arguments = Arguments{} + return z +} + +func (a Address) Clone() (z Address) { + z = Address{Mainnet: a.Mainnet, Proof: a.Proof, Network: a.Network, PublicKey: new(crypto.Point).Set(a.PublicKey)} + z.Arguments = append(z.Arguments, a.Arguments...) + return z +} + +func (a Address) Compressed() []byte { + return a.PublicKey.EncodeCompressed() +} + +// tells whether address is mainnet address +func (a *Address) IsMainnet() bool { + return a.Mainnet +} + +// tells whether address is mainnet address +func (a *Address) IsIntegratedAddress() bool { + return len(a.Arguments) >= 1 +} + +// tells whether address belongs to DERO Network +func (a *Address) IsDERONetwork() bool { + return true +} + +func (result *Address) UnmarshalText(text []byte) error { + dechrp, data, err := Decode(string(text)) + if err != nil { + return err + } + result.Network = 0 + result.Mainnet = false + result.Proof = false + result.PublicKey = new(crypto.Point) + result.Arguments = result.Arguments[:0] + + switch dechrp { + case "dero", "deroi", "deto", "detoi", "deroproof": + default: + return fmt.Errorf("invalid human-readable part : %s != %s", "", dechrp) + } + if len(data) < 1 { + return fmt.Errorf("invalid decode version: %d", len(data)) + } + + res, err := convertbits(data, 5, 8, false) + if err != nil { + return err + } + + if res[0] != 1 { + return fmt.Errorf("invalid address version : %d", res[0]) + } + res = res[1:] + + var resbytes []byte + for _, b := range res { + resbytes = append(resbytes, byte(b)) + } + + if len(resbytes) < 33 { + return fmt.Errorf("invalid address length as per spec : %d", len(res)) + } + + if err = result.PublicKey.DecodeCompressed(resbytes[0:33]); err != nil { + return err + } + + result.Mainnet = true + if dechrp == "deto" || dechrp == "detoi" { + result.Mainnet = false + } + if dechrp == "deroproof" { + result.Proof = true + } + + switch { + case len(res) == 33 && (dechrp == "dero" || dechrp == "deto"): + case (dechrp == "deroi" || dechrp == "detoi" || dechrp == "deroproof"): // address contains service request + if err = result.Arguments.UnmarshalBinary(resbytes[33:]); err != nil { + return err + } + default: + return fmt.Errorf("invalid address length as per spec : %d", len(res)) + } + + return nil + +} + +// NewAddress decodes hrp(human-readable part) Address(string), returns address or error +func NewAddress(addr string) (result *Address, err error) { + var r Address + if err = r.UnmarshalText([]byte(addr)); err != nil { + return + } + return &r, nil +} + +// create a new address from key +func NewAddressFromKeys(key *crypto.Point) (result *Address) { + result = &Address{ + Mainnet: true, + PublicKey: new(crypto.Point).Set(key), + } + return +} diff --git a/rpc/address_test.go1 b/rpc/address_test.go1 new file mode 100644 index 0000000..3377146 --- /dev/null +++ b/rpc/address_test.go1 @@ -0,0 +1,250 @@ +// Copyright 2017-2018 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package address + +import "fmt" +import "bytes" +import "testing" +import "encoding/hex" + +import "github.com/deroproject/derohe/config" + +func TestAddressError(t *testing.T) { + _, err := NewAddress("") + want := fmt.Errorf("Address is not complete") + if err.Error() != want.Error() { + t.Fatalf("want: %s, got: %s", want, err) + } + + _, err = NewAddress("dERoNzsi5WW1ABhQ1UGLwoLqBU6sbzvyuS4cCi4PGzW7QRM5TH4MUf3QvZUBNJCYSDPw6K495eroGe24cf75uDdD2QwWy9pchN") + want = fmt.Errorf("Checksum failed") + if err.Error() != want.Error() { + t.Fatalf("want: %s, got: %s", want, err) + } + +} + +func TestAddress(t *testing.T) { + + const Monero_MainNetwork = 18 + const Monero_TestNetwork = 53 + + tests := []struct { + name string + Network uint64 + SpendingKeyHex string + ViewingKeyHex string + Address string + }{ + { + name: "generic", + Network: Monero_MainNetwork, + SpendingKeyHex: "8c1a9d5ff5aaf1c3cdeb2a1be62f07a34ae6b15fe47a254c8bc240f348271679", + ViewingKeyHex: "0a29b163e392eb9416a52907fd7d3b84530f8d02ff70b1f63e72fdcb54cf7fe1", + Address: "46w3n5EGhBeZkYmKvQRsd8UK9GhvcbYWQDobJape3NLMMFEjFZnJ3CnRmeKspubQGiP8iMTwFEX2QiBsjUkjKT4SSPd3fKp", + }, + { + name: "generic 2", + Network: Monero_MainNetwork, + SpendingKeyHex: "5007b84275af9a173c2080683afce90b2157ab640c18ddd5ce3e060a18a9ce99", + ViewingKeyHex: "27024b45150037b677418fcf11ba9675494ffdf994f329b9f7a8f8402b7934a0", + Address: "44f1Y84r9Lu4tQdLWRxV122rygfhUeVBrcmBaqcYCwUHScmf1ht8DFLXX9YN4T7nPPLcpqYLUdrFiY77nQYeH9RuK9gg4p6", + }, + { + name: "require 1 padding in middle", + Network: Monero_MainNetwork, + SpendingKeyHex: "6add197bd82866e8bfbf1dc2fdf49873ec5f679059652da549cd806f2b166756", + ViewingKeyHex: "f5cf2897088fda0f7ac1c42491ed7d558a46ee41d0c81d038fd53ff4360afda0", + Address: "45fzHekTd5FfvxWBPYX2TqLPbtWjaofxYUeWCi6BRQXYFYd85sY2qw73bAuKhqY7deFJr6pN3STY81bZ9x2Zf4nGKASksqe", + }, + { + name: "require 1 padding in last chunk", + Network: Monero_MainNetwork, + SpendingKeyHex: "50defe92d88b19aaf6bf66f061dd4380b79866a4122b25a03bceb571767dbe7b", + ViewingKeyHex: "f8f6f28283921bf5a17f0bcf4306233fc25ce9b6276154ad0de22aebc5c67702", + Address: "44grjkXtDHJVbZgtU1UKnrNXidcHfZ3HWToU5WjR3KgHMjgwrYLjXC6i5vm3HCp4vnBfYaNEyNiuZVwqtHD2SenS1JBRyco", + }, + { + name: "testnet", + Network: Monero_TestNetwork, + SpendingKeyHex: "8de9cce254e60cd940abf6c77ef344c3a21fad74320e45734fbfcd5870e5c875", + ViewingKeyHex: "27024b45150037b677418fcf11ba9675494ffdf994f329b9f7a8f8402b7934a0", + Address: "9xYZvCDf6aFdLd7Qawg5XHZitWLKoeFvcLHfe5GxsGCFLbXSWeQNKciXX9YN4T7nPPLcpqYLUdrFiY77nQYeH9RuK9bogZJ", + }, + + { + name: "DERO testnet", + Network: config.Testnet.Public_Address_Prefix, + SpendingKeyHex: "ffb4baf32792d38d36c5f1792201d1cff142a10bad6aa088090156a35858739d", + ViewingKeyHex: "0ea428a9608fc9dc06acceea608ac97cc9119647b943941a381306548ee43455", + Address: "dETosYceeTxRZQBk5hQzN51JepzZn5H24JqR96q7mY7ZFo6JhJKPNSKR3vs9ES1ibyQDQgeRheDP6CJbb7AKJY2H9eacz2RtPy", + }, + { + name: "DERO mainnet requires padding in second block", + Network: config.Mainnet.Public_Address_Prefix, + SpendingKeyHex: "10a80329a700f25c9892a696de768f5bdc73cafe6095d647e5707c04f48c0481", + ViewingKeyHex: "b0fa8ca43a8f07681274ddd8fa891aea4222aa8027dd516bc144317a042547c4", + Address: "dERoNzsi5WW1ABhQ1UGLwoLqBU6sbzvyuS4cCi4PGzW7QRM5TH4MUf3QvZUBNJCYSDPw6K495eroGe24cf75uDdD2QwWy9pchM", + }, + } + var base58 string + var spendingKey, viewingKey []byte + for _, test := range tests { + spendingKey, _ = hex.DecodeString(test.SpendingKeyHex) + viewingKey, _ = hex.DecodeString(test.ViewingKeyHex) + + address, err := NewAddress(test.Address) + if err != nil { + t.Fatalf("%s: Failed while parsing address %s", test.name, err) + continue + } + + if address.Network != test.Network { + t.Fatalf("%s: want: %d, got: %d", test.name, test.Network, address.Network) + continue + } + + if bytes.Compare(address.SpendKey[:], spendingKey) != 0 { + t.Fatalf("%s: want: %x, got: %s", test.name, spendingKey, address.SpendKey) + continue + } + if bytes.Compare(address.ViewKey[:], viewingKey) != 0 { + t.Fatalf("%s: want: %x, got: %s", test.name, viewingKey, address.ViewKey) + continue + } + + base58 = address.Base58() + if base58 != test.Address { + t.Fatalf("%s: want: %s, got: %s", test.name, test.Address, base58) + continue + } + + } +} + +// more explaination here https://monero.stackexchange.com/questions/1910/how-do-payment-ids-work +// test case created from here https://xmr.llcoins.net/addresstests.html +func TestIntegratedAddress(t *testing.T) { + + const Monero_MainNetwork = 18 + const Monero_MainNetwork_Integrated = 19 + const Monero_TestNetwork = 53 + + tests := []struct { + name string + Network uint64 + NetworkI uint64 + SpendingKeyHex string + ViewingKeyHex string + PaymentID string + Address string + AddressI string + }{ + { + name: "generic", + Network: Monero_MainNetwork, + NetworkI: Monero_MainNetwork_Integrated, + SpendingKeyHex: "80d3eca27896f549abc41dd941d08a4c82cff165a7f8bc4c3c0841cffd11c095", + ViewingKeyHex: "7849297236cd7c0d6c69a3c8c179c038d3c1c434735741bb3c8995c3c9d6f2ac", + PaymentID: "90470a40196034b5", + Address: "46WGHoGHRT2DKhdr4BxzhXDoFe5NBjNm1Dka5144aXZHS13cAoUQWRq3FE2gcT3LJjAWJ6fGWq8t8YKRqwwit8vmLT6tcxK", + + AddressI: "4GCwJc5n2iYDKhdr4BxzhXDoFe5NBjNm1Dka5144aXZHS13cAoUQWRq3FE2gcT3LJjAWJ6fGWq8t8YKRqwwit8vmVs5oxyLeWQsMWmcgkC", + }, + + { + name: "generic", + Network: config.Mainnet.Public_Address_Prefix, + NetworkI: config.Mainnet.Public_Address_Prefix_Integrated, + SpendingKeyHex: "bd7393b76af23611e6e0eb1e4974bcb5688fceea6ad8a1b08435a4e68fcb7b8c", + ViewingKeyHex: "c828aa405d78c3a0b0a7263d2cb82811d4c6ee3374ada5cc753d8196a271b3d2", + PaymentID: "0cbd6e050cf3b73c", + Address: "dERoiVavtPjhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmK5Jur1PhN6P", + + AddressI: "dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh", + }, + } + + var base58 string + var spendingKey, viewingKey []byte + for _, test := range tests { + spendingKey, _ = hex.DecodeString(test.SpendingKeyHex) + viewingKey, _ = hex.DecodeString(test.ViewingKeyHex) + + address, err := NewAddress(test.Address) + if err != nil { + t.Fatalf("%s: Failed while parsing address %s", test.name, err) + continue + } + + if address.Network != test.Network { + t.Errorf("%s: want: %d, got: %d", test.name, test.Network, address.Network) + continue + } + + if bytes.Compare(address.SpendKey[:], spendingKey) != 0 { + t.Fatalf("%s: want: %x, got: %s", test.name, spendingKey, address.SpendKey) + continue + } + if bytes.Compare(address.ViewKey[:], viewingKey) != 0 { + t.Fatalf("%s: want: %x, got: %s", test.name, viewingKey, address.ViewKey) + continue + } + + base58 = address.Base58() + if base58 != test.Address { + t.Fatalf("%s: want: %s, got: %s", test.name, test.Address, base58) + continue + } + + address, err = NewAddress(test.AddressI) + if err != nil { + t.Fatalf("%s: Failed while parsing address %s", test.name, err) + continue + } + + base58 = address.Base58() + if base58 != test.AddressI { + t.Fatalf("%s: want: %s, got: %s", test.name, test.AddressI, base58) + continue + } + + if fmt.Sprintf("%x", address.PaymentID) != test.PaymentID { + t.Fatalf("%s: PaymentID want: %s, got: %s", test.name, test.PaymentID, address.PaymentID) + } + + } + +} + +func Test_Bruteforce_IntegratedAddress(t *testing.T) { + var AddressI string = "dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh" + + var PaymentID string = "0cbd6e050cf3b73c" + + + for i := 0; i < 100000;i++ { + address, err := NewAddress(AddressI) + if err != nil { + t.Fatalf("%s: Failed while parsing address %s", AddressI, err) + continue + } + if fmt.Sprintf("%x",address.PaymentID) != PaymentID{ + t.Fatalf("Payment ID failed at loop %d", i) + } + } +} diff --git a/rpc/bech32.go b/rpc/bech32.go new file mode 100644 index 0000000..4e98eea --- /dev/null +++ b/rpc/bech32.go @@ -0,0 +1,217 @@ +// Package bech32 reference implementation for Bech32 and segwit addresses. +// Copyright (c) 2017 Takatoshi Nakagawa +// +// 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: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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. +package rpc + +import ( + "bytes" + "fmt" + "strings" +) + +var charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +var generator = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + +func polymod(values []int) int { + chk := 1 + for _, v := range values { + top := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ v + for i := 0; i < 5; i++ { + if (top>>uint(i))&1 == 1 { + chk ^= generator[i] + } + } + } + return chk +} + +func hrpExpand(hrp string) []int { + ret := []int{} + for _, c := range hrp { + ret = append(ret, int(c>>5)) + } + ret = append(ret, 0) + for _, c := range hrp { + ret = append(ret, int(c&31)) + } + return ret +} + +func verifyChecksum(hrp string, data []int) bool { + return polymod(append(hrpExpand(hrp), data...)) == 1 +} + +func createChecksum(hrp string, data []int) []int { + values := append(append(hrpExpand(hrp), data...), []int{0, 0, 0, 0, 0, 0}...) + mod := polymod(values) ^ 1 + ret := make([]int, 6) + for p := 0; p < len(ret); p++ { + ret[p] = (mod >> uint(5*(5-p))) & 31 + } + return ret +} + +// Encode encodes hrp(human-readable part) and data(32bit data array), returns Bech32 / or error +// if hrp is uppercase, return uppercase Bech32 +func Encode(hrp string, data []int) (string, error) { + if (len(hrp) + len(data) + 7) > 90 { + //return "", fmt.Errorf("too long : hrp length=%d, data length=%d", len(hrp), len(data)) + } + if len(hrp) < 1 { + return "", fmt.Errorf("invalid hrp : hrp=%v", hrp) + } + for p, c := range hrp { + if c < 33 || c > 126 { + return "", fmt.Errorf("invalid character human-readable part : hrp[%d]=%d", p, c) + } + } + if strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp { + return "", fmt.Errorf("mix case : hrp=%v", hrp) + } + lower := strings.ToLower(hrp) == hrp + hrp = strings.ToLower(hrp) + combined := append(data, createChecksum(hrp, data)...) + var ret bytes.Buffer + ret.WriteString(hrp) + ret.WriteString("1") + for idx, p := range combined { + if p < 0 || p >= len(charset) { + return "", fmt.Errorf("invalid data : data[%d]=%d", idx, p) + } + ret.WriteByte(charset[p]) + } + if lower { + return ret.String(), nil + } + return strings.ToUpper(ret.String()), nil +} + +// Decode decodes bechString(Bech32) returns hrp(human-readable part) and data(32bit data array) / or error +func Decode(bechString string) (string, []int, error) { + if len(bechString) > 90 { + //return "", nil, fmt.Errorf("too long : len=%d", len(bechString)) + } + if strings.ToLower(bechString) != bechString && strings.ToUpper(bechString) != bechString { + return "", nil, fmt.Errorf("mixed case") + } + bechString = strings.ToLower(bechString) + pos := strings.LastIndex(bechString, "1") + if pos < 1 || pos+7 > len(bechString) { + return "", nil, fmt.Errorf("separator '1' at invalid position : pos=%d , len=%d", pos, len(bechString)) + } + hrp := bechString[0:pos] + for p, c := range hrp { + if c < 33 || c > 126 { + return "", nil, fmt.Errorf("invalid character human-readable part : bechString[%d]=%d", p, c) + } + } + data := []int{} + for p := pos + 1; p < len(bechString); p++ { + d := strings.Index(charset, fmt.Sprintf("%c", bechString[p])) + if d == -1 { + return "", nil, fmt.Errorf("invalid character data part : bechString[%d]=%d", p, bechString[p]) + } + data = append(data, d) + } + if !verifyChecksum(hrp, data) { + return "", nil, fmt.Errorf("invalid checksum") + } + return hrp, data[:len(data)-6], nil +} + +func convertbits(data []int, frombits, tobits uint, pad bool) ([]int, error) { + acc := 0 + bits := uint(0) + ret := []int{} + maxv := (1 << tobits) - 1 + for idx, value := range data { + if value < 0 || (value>>frombits) != 0 { + return nil, fmt.Errorf("invalid data range : data[%d]=%d (frombits=%d)", idx, value, frombits) + } + acc = (acc << frombits) | value + bits += frombits + for bits >= tobits { + bits -= tobits + ret = append(ret, (acc>>bits)&maxv) + } + } + if pad { + if bits > 0 { + ret = append(ret, (acc<<(tobits-bits))&maxv) + } + } else if bits >= frombits { + return nil, fmt.Errorf("illegal zero padding") + } else if ((acc << (tobits - bits)) & maxv) != 0 { + return nil, fmt.Errorf("non-zero padding") + } + return ret, nil +} + +// SegwitAddrDecode decodes hrp(human-readable part) Segwit Address(string), returns version(int) and data(bytes array) / or error +func SegwitAddrDecode(hrp, addr string) (int, []int, error) { + dechrp, data, err := Decode(addr) + if err != nil { + return -1, nil, err + } + if dechrp != hrp { + return -1, nil, fmt.Errorf("invalid human-readable part : %s != %s", hrp, dechrp) + } + if len(data) < 1 { + return -1, nil, fmt.Errorf("invalid decode data length : %d", len(data)) + } + if data[0] > 16 { + return -1, nil, fmt.Errorf("invalid witness version : %d", data[0]) + } + res, err := convertbits(data[1:], 5, 8, false) + if err != nil { + return -1, nil, err + } + if len(res) < 2 || len(res) > 40 { + return -1, nil, fmt.Errorf("invalid convertbits length : %d", len(res)) + } + if data[0] == 0 && len(res) != 20 && len(res) != 32 { + return -1, nil, fmt.Errorf("invalid program length for witness version 0 (per BIP141) : %d", len(res)) + } + return data[0], res, nil +} + +// SegwitAddrEncode encodes hrp(human-readable part) , version(int) and data(bytes array), returns Segwit Address / or error +func SegwitAddrEncode(hrp string, version int, program []int) (string, error) { + if version < 0 || version > 16 { + return "", fmt.Errorf("invalid witness version : %d", version) + } + if len(program) < 2 || len(program) > 40 { + return "", fmt.Errorf("invalid program length : %d", len(program)) + } + if version == 0 && len(program) != 20 && len(program) != 32 { + return "", fmt.Errorf("invalid program length for witness version 0 (per BIP141) : %d", len(program)) + } + data, err := convertbits(program, 8, 5, true) + if err != nil { + return "", err + } + ret, err := Encode(hrp, append([]int{version}, data...)) + if err != nil { + return "", err + } + return ret, nil +} diff --git a/rpc/bech32_test.go b/rpc/bech32_test.go new file mode 100644 index 0000000..f88804f --- /dev/null +++ b/rpc/bech32_test.go @@ -0,0 +1,402 @@ +// Copyright (c) 2017 Takatoshi Nakagawa +// +// 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: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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. +package rpc + +import ( + "reflect" + "strings" + "testing" +) + +func segwitScriptpubkey(version int, program []int) []int { + if version != 0 { + version += 0x50 + } + return append(append([]int{version}, len(program)), program...) +} + +var validChecksum = []string{ + "A12UEL5L", + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", +} + +var invalidChecksum = []string{ + " 1nwldj5", + "\x7F" + "1axkwrx", + "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", + "pzry9x0s0muk", + "1pzry9x0s0muk", + "x1b4n0q5v", + "li1dgmt3", + "de1lg7wt\xFF", +} + +type item struct { + address string + scriptpubkey []int +} + +var validAddress = []item{ + item{"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + []int{ + 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + }, + }, + item{"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + []int{ + 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, + 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, + 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, + 0x62, + }, + }, + item{"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", + []int{ + 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, + 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + }, + }, + item{"BC1SW50QA3JX3S", + []int{ + 0x60, 0x02, 0x75, 0x1e, + }, + }, + item{"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + []int{ + 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, + 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, + }, + }, + item{"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + []int{ + 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, + 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, + 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, + 0x33, + }, + }, +} + +var invalidAddress = []string{ + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + "bc1rw5uspcuh", + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + "bc1gmk9yu", +} + +func TestValidChecksum(t *testing.T) { + for _, test := range validChecksum { + hrp, data, err := Decode(test) + if err != nil { + t.Errorf("Valid checksum for %s : FAIL / error %+v\n", test, err) + } else { + t.Logf("Valid checksum for %s : ok / hrp : %+v , data : %+v\n", test, hrp, data) + } + } +} + +func TestInvalidChecksum(t *testing.T) { + for _, test := range invalidChecksum { + hrp, data, err := Decode(test) + if err != nil { + t.Logf("Invalid checksum for %s : ok / hrp : %+v , data : %+v\n", test, hrp, data) + } else { + t.Errorf("Invalid checksum for %s : FAIL\n", test) + } + } +} + +func TestValidAddress(t *testing.T) { + for _, test := range validAddress { + hrp := "bc" + version, program, err := SegwitAddrDecode(hrp, test.address) + if err != nil { + hrp = "tb" + version, program, err = SegwitAddrDecode(hrp, test.address) + } + ok := err == nil + if ok { + output := segwitScriptpubkey(version, program) + ok = reflect.DeepEqual(output, test.scriptpubkey) + } + if ok { + recreate, err := SegwitAddrEncode(hrp, version, program) + if err == nil { + ok = recreate == strings.ToLower(test.address) + } + } + if ok { + t.Logf("Valid address %v : ok\n", test.address) + } else { + t.Errorf("Valid address %v : FAIL\n", test.address) + } + } +} + +func TestInvalidAddress(t *testing.T) { + for _, test := range invalidAddress { + _, _, bcErr := SegwitAddrDecode("bc", test) + t.Logf("bc error:%v\n", bcErr) + _, _, tbErr := SegwitAddrDecode("tb", test) + t.Logf("tb error:%v\n", tbErr) + if bcErr != nil && tbErr != nil { + t.Logf("Invalid address %v : ok\n", test) + } else { + t.Errorf("Invalid address %v : FAIL\n", test) + } + } +} + +// add coverage tests + +func TestCoverage(t *testing.T) { + var err error + var bech32String string + var hrp string + var data []int + + // SegwitAddrEncode + bech32String, err = SegwitAddrEncode("bc", 1, []int{0, 1}) + if err != nil { + t.Errorf("Coverage SegwitAddrEncode normal case : FAIL / error : %+v\n", err) + } else { + t.Log("Coverage SegwitAddrEncode normal case : ok / bech32String :", bech32String) + } + data = make([]int, 40) + bech32String, err = SegwitAddrEncode("bc", 16, data) + if err != nil { + t.Errorf("Coverage SegwitAddrEncode normal case : FAIL / error : %+v\n", err) + } else { + t.Log("Coverage SegwitAddrEncode normal case : ok / bech32String :", bech32String) + } + data = make([]int, 20) + bech32String, err = SegwitAddrEncode("bc", 0, data) + if err != nil { + t.Errorf("Coverage SegwitAddrEncode normal case : FAIL / error : %+v\n", err) + } else { + t.Log("Coverage SegwitAddrEncode normal case : ok / bech32String :", bech32String) + } + data = make([]int, 32) + bech32String, err = SegwitAddrEncode("bc", 0, data) + if err != nil { + t.Errorf("Coverage SegwitAddrEncode normal case : FAIL / error : %+v\n", err) + } else { + t.Log("Coverage SegwitAddrEncode normal case : ok / bech32String :", bech32String) + } + data = make([]int, 1) + _, err = SegwitAddrEncode("bc", 1, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid program length error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid program length error case : ok / error :", err) + } + data = make([]int, 41) + _, err = SegwitAddrEncode("bc", 1, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid program length error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid program length error case : ok / error :", err) + } + data = make([]int, 26) + _, err = SegwitAddrEncode("bc", 0, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid program length for witness version 0 (per BIP141) error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid program length for witness version 0 (per BIP141) error case : ok / error :", err) + } + data = make([]int, 20) + _, err = SegwitAddrEncode("Bc", 0, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode Encode error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode Encode error case : ok / error :", err) + } + _, err = SegwitAddrEncode("bc", 1, []int{-1, 0}) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid data range error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid data range error case : ok / error :", err) + } + _, err = SegwitAddrEncode("bc", -1, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid witness version error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid witness version error case : ok / error :", err) + } + _, err = SegwitAddrEncode("bc", 17, data) + if err == nil { + t.Errorf("Coverage SegwitAddrEncode invalid witness version error case : FAIL") + } else { + t.Log("Coverage SegwitAddrEncode invalid witness version error case : ok / error :", err) + } + + // SegwitAddrDecode + _, _, err = SegwitAddrDecode("a", "A12UEL5L") + if err == nil { + t.Errorf("Coverage SegwitAddrDecode invalid decode data length error case : FAIL") + } else { + t.Log("Coverage SegwitAddrDecode invalid decode data length error case : ok / error :", err) + } + + // Decode + _, _, err = Decode("!~1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc356v3") + if err != nil { + t.Errorf("Coverage Decode normal case : FAIL / error :%v", err) + } else { + t.Log("Coverage Decode normal case : ok") + } + _, _, err = Decode("a1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq") + if err == nil { + t.Errorf("Coverage Decode too long error case : FAIL") + } else { + t.Log("Coverage Decode too long error case : ok / error :", err) + } + _, _, err = Decode("1") + if err == nil { + t.Errorf("Coverage Decode separator '1' at invalid position error case : FAIL") + } else { + t.Log("Coverage Decode separator '1' at invalid position error case : ok / error :", err) + } + _, _, err = Decode("a1qqqqq") + if err == nil { + t.Errorf("Coverage Decode separator '1' at invalid position error case : FAIL") + } else { + t.Log("Coverage Decode separator '1' at invalid position error case : ok / error :", err) + } + _, _, err = Decode("a" + string(rune(32)) + "1qqqqqq") + if err == nil { + t.Errorf("Coverage Decode invalid character human-readable part error case : FAIL") + } else { + t.Log("Coverage Decode invalid character human-readable part error case : ok / error :", err) + } + _, _, err = Decode("a" + string(rune(127)) + "1qqqqqq") + if err == nil { + t.Errorf("Coverage Decode invalid character human-readable part error case : FAIL") + } else { + t.Log("Coverage Decode invalid character human-readable part error case : ok / error :", err) + } + _, _, err = Decode("a1qqqqqb") + if err == nil { + t.Errorf("Coverage Decode invalid character data part error case : FAIL") + } else { + t.Log("Coverage Decode invalid character data part erroer case : ok / error :", err) + } + + // Encode + hrp = "bc" + data = []int{} + bech32String, err = Encode(hrp, data) + if err != nil || bech32String != strings.ToLower(bech32String) { + t.Errorf("Coverage Encode lower case : FAIL / bech32String : %v , error : %v", bech32String, err) + } else { + t.Log("Coverage Encode lower case : ok / bech32String : ", bech32String) + } + hrp = "BC" + bech32String, err = Encode(hrp, data) + if err != nil || bech32String != strings.ToUpper(bech32String) { + t.Errorf("Coverage Encode upper case : FAIL / bech32String : %v , error : %v", bech32String, err) + } else { + t.Log("Coverage Encode upper case : ok / bech32String : ", bech32String) + } + hrp = "bc" + data = make([]int, 90-7-len(hrp)+1) + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode too long error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode too long error case : ok / error : ", err) + } + hrp = "" + data = make([]int, 90-7-len(hrp)) + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode invalid hrp error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode invalid hrp error case : ok / error : ", err) + } + hrp = "Bc" + data = make([]int, 90-7-len(hrp)) + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode mix case error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode mix case error case : ok / error : ", err) + } + hrp = string(rune(33)) + string(rune(126)) + data = make([]int, 90-7-len(hrp)) + bech32String, err = Encode(hrp, data) + if err != nil { + t.Errorf("Coverage Encode normal case : FAIL / error : %v", err) + } else { + t.Log("Coverage Encode normal case : ok / bech32String : ", bech32String) + } + hrp = string(rune(32)) + "c" + data = make([]int, 90-7-len(hrp)) + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode invalid character human-readable part error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode invalid character human-readable part error case : ok / error : ", err) + } + hrp = "b" + string(rune(127)) + data = make([]int, 90-7-len(hrp)) + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode invalid character human-readable part error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode invalid character human-readable part error case : ok / error : ", err) + } + hrp = "bc" + data = []int{0, 31} + bech32String, err = Encode(hrp, data) + if err != nil { + t.Errorf("Coverage Encode normal case : FAIL / error : %v", err) + } else { + t.Log("Coverage Encode normal case : ok / bech32String : ", bech32String) + } + hrp = "bc" + data = []int{-1} + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode invalid data error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode invalid data error case : ok / error : ", err) + } + hrp = "bc" + data = []int{32} + bech32String, err = Encode(hrp, data) + if err == nil { + t.Errorf("Coverage Encode invalid data error case : FAIL / bech32String : %v", bech32String) + } else { + t.Log("Coverage Encode invalid data error case : ok / error : ", err) + } +} diff --git a/rpc/daemon_rpc.go b/rpc/daemon_rpc.go new file mode 100644 index 0000000..4a01b9d --- /dev/null +++ b/rpc/daemon_rpc.go @@ -0,0 +1,304 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// this package contains struct definitions and related processing code + +package rpc + +import "github.com/deroproject/derohe/cryptography/crypto" + +// this is used to print blockheader for the rpc and the daemon +type BlockHeader_Print struct { + Depth int64 `json:"depth"` + Difficulty string `json:"difficulty"` + Hash string `json:"hash"` + Height int64 `json:"height"` + TopoHeight int64 `json:"topoheight"` + Major_Version uint64 `json:"major_version"` + Minor_Version uint64 `json:"minor_version"` + Nonce uint64 `json:"nonce"` + Orphan_Status bool `json:"orphan_status"` + SyncBlock bool `json:"syncblock"` + SideBlock bool `json:"sideblock"` + TXCount int64 `json:"txcount"` + + Reward uint64 `json:"reward"` + Tips []string `json:"tips"` + Timestamp uint64 `json:"timestamp"` +} + +type ( + GetBlockHeaderByTopoHeight_Params struct { + TopoHeight uint64 `json:"topoheight"` + } + GetBlockHeaderByHeight_Result struct { + Block_Header BlockHeader_Print `json:"block_header"` + Status string `json:"status"` + } +) + +// GetBlockHeaderByHash +type ( + GetBlockHeaderByHash_Params struct { + Hash string `json:"hash"` + } // no params + GetBlockHeaderByHash_Result struct { + Block_Header BlockHeader_Print `json:"block_header"` + Status string `json:"status"` + } +) + +// get block count +type ( + GetBlockCount_Params struct { + // NO params + } + GetBlockCount_Result struct { + Count uint64 `json:"count"` + Status string `json:"status"` + } +) + +// getblock +type ( + GetBlock_Params struct { + Hash string `json:"hash,omitempty"` // Monero Daemon breaks if both are provided + Height uint64 `json:"height,omitempty"` // Monero Daemon breaks if both are provided + } // no params + GetBlock_Result struct { + Blob string `json:"blob"` + Json string `json:"json"` + Block_Header BlockHeader_Print `json:"block_header"` + Status string `json:"status"` + } +) + +// get block template request response +type ( + GetBlockTemplate_Params struct { + Wallet_Address string `json:"wallet_address"` + Reserve_size uint64 `json:"reserve_size"` + } + GetBlockTemplate_Result struct { + Blocktemplate_blob string `json:"blocktemplate_blob"` + Blockhashing_blob string `json:"blockhashing_blob"` + Expected_reward uint64 `json:"expected_reward"` + Difficulty uint64 `json:"difficulty"` + Height uint64 `json:"height"` + Prev_Hash string `json:"prev_hash"` + Reserved_Offset uint64 `json:"reserved_offset"` + Epoch uint64 `json:"epoch"` // used to expire pool jobs + Status string `json:"status"` + } +) + +type ( // array without name containing block template in hex + SubmitBlock_Params struct { + X []string + } + SubmitBlock_Result struct { + BLID string `json:"blid"` + Status string `json:"status"` + } +) + +type ( + GetLastBlockHeader_Params struct{} // no params + GetLastBlockHeader_Result struct { + Block_Header BlockHeader_Print `json:"block_header"` + Status string `json:"status"` + } +) + +//get encrypted balance call +type ( + GetEncryptedBalance_Params struct { + Address string `json:"address"` + SCID crypto.Hash `json:"scid"` + Merkle_Balance_TreeHash string `json:"treehash,omitempty"` + TopoHeight int64 `json:"topoheight,omitempty"` + } // no params + GetEncryptedBalance_Result struct { + Data string `json:"data"` // balance is in hex form, 66 * 2 byte = 132 bytes + Registration int64 `json:"registration"` // at what topoheight the account was registered + Bits int `json:"bits"` // no. of bits required to access the public key from the chain + Height int64 `json:"height"` // at what height is this balance + Topoheight int64 `json:"topoheight"` // at what topoheight is this balance + BlockHash string `json:"blockhash"` // blockhash at this topoheight + Merkle_Balance_TreeHash string `json:"treehash"` + DHeight int64 `json:"dheight"` // daemon height + DTopoheight int64 `json:"dtopoheight"` // daemon topoheight + DMerkle_Balance_TreeHash string `json:"dtreehash"` // daemon dmerkle tree hash + Status string `json:"status"` + } +) + +type ( + GetTxPool_Params struct{} // no params + GetTxPool_Result struct { + Tx_list []string `json:"txs,omitempty"` + Status string `json:"status"` + } +) + +// get height http response as json +type ( + Daemon_GetHeight_Result struct { + Height uint64 `json:"height"` + StableHeight int64 `json:"stableheight"` + TopoHeight int64 `json:"topoheight"` + + Status string `json:"status"` + } +) + +type ( + On_GetBlockHash_Params struct { + X [1]uint64 + } + On_GetBlockHash_Result struct{} +) + +type ( + GetTransaction_Params struct { + Tx_Hashes []string `json:"txs_hashes"` + Decode uint64 `json:"decode_as_json,omitempty"` // Monero Daemon breaks if this sent + } // no params + GetTransaction_Result struct { + Txs_as_hex []string `json:"txs_as_hex"` + Txs_as_json []string `json:"txs_as_json"` + Txs []Tx_Related_Info `json:"txs"` + Status string `json:"status"` + } + + Tx_Related_Info struct { + As_Hex string `json:"as_hex"` + As_Json string `json:"as_json"` + Block_Height int64 `json:"block_height"` + Reward uint64 `json:"reward"` // miner tx rewards are decided by the protocol during execution + Ignored bool `json:"ignored"` // tell whether this tx is okau as per client protocol or bein ignored + In_pool bool `json:"in_pool"` + Output_Indices []uint64 `json:"output_indices"` + Tx_hash string `json:"tx_hash"` + ValidBlock string `json:"valid_block"` // TX is valid in this block + InvalidBlock []string `json:"invalid_block"` // TX is invalid in this block, 0 or more + Ring [][][]byte `json:"ring"` // ring members completed, since tx contains compressed + Balance uint64 `json:"balance"` // if tx is SC, give SC balance at start + Code string `json:"code"` // smart contract code at start + BalanceNow uint64 `json:"balancenow"` // if tx is SC, give SC balance at current topo height + CodeNow string `json:"codenow"` // smart contract code at current topo + + } +) + +type ( + GetSC_Params struct { + SCID string `json:"scid"` + Code bool `json:"code,omitempty"` // if true code will be returned + TopoHeight int64 `json:"topoheight,omitempty"` // all queries are related to this topoheight + KeysUint64 []uint64 `json:"keysuint64,omitempty"` + KeysString []string `json:"keysstring,omitempty"` + KeysBytes [][]byte `json:"keysbytes,omitempty"` // all keys can also be represented as bytes + } + GetSC_Result struct { + ValuesUint64 []string `json:"valuesuint64,omitempty"` + ValuesString []string `json:"valuesstring,omitempty"` + ValuesBytes []string `json:"valuesbytes,omitempty"` + Balance uint64 `json:"balance"` + Code string `json:"code"` + Status string `json:"status"` + } +) + +type ( + GetRandomAddress_Params struct { + SCID crypto.Hash `json:"scid"` + } + + GetRandomAddress_Result struct { + Address []string `json:"address"` // daemon will return around 20 address in 1 go + Status string `json:"status"` + } +) + +type ( + SendRawTransaction_Params struct { + Tx_as_hex string `json:"tx_as_hex"` + } + SendRawTransaction_Result struct { + Status string `json:"status"` + DoubleSpend bool `json:"double_spend"` + FeeTooLow bool `json:"fee_too_low"` + InvalidInput bool `json:"invalid_input"` + InvalidOutput bool `json:"invalid_output"` + Low_Mixin bool `json:"low_mixin"` + Non_rct bool `json:"not_rct"` + NotRelayed bool `json:"not_relayed"` + Overspend bool `json:"overspend"` + TooBig bool `json:"too_big"` + Reason string `json:"string"` + } +) + +/* +{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "alt_blocks_count": 5, + "difficulty": 972165250, + "grey_peerlist_size": 2280, + "height": 993145, + "incoming_connections_count": 0, + "outgoing_connections_count": 8, + "status": "OK", + "target": 60, + "target_height": 993137, + "testnet": false, + "top_block_hash": "", + "tx_count": 564287, + "tx_pool_size": 45, + "white_peerlist_size": 529 + } +}*/ +type ( + GetInfo_Params struct{} // no params + GetInfo_Result struct { + Alt_Blocks_Count uint64 `json:"alt_blocks_count"` + Difficulty uint64 `json:"difficulty"` + Grey_PeerList_Size uint64 `json:"grey_peerlist_size"` + Height int64 `json:"height"` + StableHeight int64 `json:"stableheight"` + TopoHeight int64 `json:"topoheight"` + Merkle_Balance_TreeHash string `json:"treehash"` + AverageBlockTime50 float32 `json:"averageblocktime50"` + Incoming_connections_count uint64 `json:"incoming_connections_count"` + Outgoing_connections_count uint64 `json:"outgoing_connections_count"` + Target uint64 `json:"target"` + Target_Height uint64 `json:"target_height"` + Testnet bool `json:"testnet"` + Top_block_hash string `json:"top_block_hash"` + Tx_count uint64 `json:"tx_count"` + Tx_pool_size uint64 `json:"tx_pool_size"` + Dynamic_fee_per_kb uint64 `json:"dynamic_fee_per_kb"` // our addition + Total_Supply uint64 `json:"total_supply"` // our addition + Median_Block_Size uint64 `json:"median_block_size"` // our addition + White_peerlist_size uint64 `json:"white_peerlist_size"` + Version string `json:"version"` + + Status string `json:"status"` + } +) diff --git a/rpc/rpc.go b/rpc/rpc.go new file mode 100644 index 0000000..4d4f9ab --- /dev/null +++ b/rpc/rpc.go @@ -0,0 +1,423 @@ +package rpc + +import "fmt" +import "time" +import "sort" +import "encoding/json" + +import "github.com/fxamacker/cbor" +import "github.com/deroproject/derohe/cryptography/crypto" + +// this package defines interfaces and necessary glue code Digital Network, it exposes and provides encrypted RPC calls over DERO chain + +var enc_options = cbor.EncOptions{ + Sort: cbor.SortCTAP2, + TimeTag: cbor.EncTagRequired, +} + +var dec_options = cbor.DecOptions{ + TimeTag: cbor.DecTagRequired, +} + +var dec cbor.DecMode +var enc cbor.EncMode + +func init() { + var err error + if dec, err = dec_options.DecMode(); err != nil { + panic(err) + } + if enc, err = enc_options.EncMode(); err != nil { + panic(err) + } + +} + +// currently we support only the following Data Type +// the following data types are present +// int64 represented by inputbox +// uint64 represented by inputbox +// string represented by input box +// what about listbox, checkbox , checkbox can be represented by bool but currently not suported +type DataType string + +const ( + DataString DataType = "S" + DataInt64 = "I" + DataUint64 = "U" + DataFloat64 = "F" + DataHash = "H" // a 256 bit hash (basically sha256 of 32 bytes long) + DataAddress = "A" // dero address represented in 33 bytes + DataTime = "T" +) + +func (d DataType) String() string { + switch d { + case DataString: + return "string" + case DataInt64: + return "int64" + case DataUint64: + return "uint64" + case DataFloat64: + return "float64" + case DataHash: + return "hash" + case DataAddress: + return "address" + case DataTime: + return "time" + + default: + return "unknown data type" + } + +} + +//type DataType byte +type Argument struct { + Name string `json:"name"` // string name must be atleast 1 byte + DataType DataType `json:"datatype"` // Type must one of the known data types + Value interface{} `json:"value"` // value should be as per type +} + +type Arguments []Argument + +func (arg Argument) String() string { + switch arg.DataType { + case DataString: + return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value) + case DataInt64: + return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value) + case DataUint64: + return fmt.Sprintf("Name:%s Type:%s Value:'%d'", arg.Name, arg.DataType, arg.Value) + case DataFloat64: + return fmt.Sprintf("Name:%s Type:%s Value:'%f'", arg.Name, arg.DataType, arg.Value) + case DataHash: + return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value) + case DataAddress: + return fmt.Sprintf("Name:%s Type:%s Value:'%x'", arg.Name, arg.DataType, arg.Value) + case DataTime: + return fmt.Sprintf("Name:%s Type:%s Value:'%s'", arg.Name, arg.DataType, arg.Value) + + default: + return "unknown data type" + } +} + +// tells whether the arguments have an argument of this type +func (args Arguments) Has(name string, dtype DataType) bool { + for _, arg := range args { + if arg.Name == name && arg.DataType == dtype { + return true + } + } + return false +} + +// tells whether the arguments have an argument of this type and value it not nil +func (args Arguments) HasValue(name string, dtype DataType) bool { + for _, arg := range args { + if arg.Name == name && arg.DataType == dtype && arg.Value != nil { + return true + } + } + return false +} + +// tells the index of the specific argument +func (args Arguments) Index(name string, dtype DataType) int { + for i, arg := range args { + if arg.Name == name && arg.DataType == dtype { + return i + } + } + return -1 +} + +// return value wrapped in an interface +func (args Arguments) Value(name string, dtype DataType) interface{} { + for _, arg := range args { + if arg.Name == name && arg.DataType == dtype { + return arg.Value + } + } + return nil +} + +// this function will pack the args into buffer of specific limit, if it fails, it panics +func (args Arguments) MustPack(limit int) []byte { + if packed, err := args.CheckPack(limit); err != nil { + panic(err) + } else { + return packed + } +} + +// this function will pack the args into buffer of specific limit, if it fails, it gives eroor +func (args Arguments) CheckPack(limit int) ([]byte, error) { + packed, err := args.MarshalBinary() + if err != nil { + return nil, err + } + if len(packed) > limit { + return nil, fmt.Errorf("Packed size %d bytes, but limit is %d", len(packed), limit) + } + if len(packed) == limit { + return packed, nil + } else { // we need to fill with 0 values, upto limit size + + fill_count := limit - len(packed) + for i := 0; i < fill_count; i++ { + packed = append(packed, 0) + } + + } + + return packed, nil +} + +// pack more deeply +func (args Arguments) MarshalBinary() (data []byte, err error) { + if err = args.Validate_Arguments(); err != nil { + return + } + + localmap := map[string]interface{}{} // this also filters any duplicates + for _, arg := range args { + switch v := arg.Value.(type) { + case int64: + localmap[arg.Name+string(arg.DataType)] = v + case uint64: + localmap[arg.Name+string(arg.DataType)] = v + case float64: + localmap[arg.Name+string(arg.DataType)] = v + case crypto.Hash: + localmap[arg.Name+string(arg.DataType)] = v + case Address: + localmap[arg.Name+string(arg.DataType)] = v + case string: + localmap[arg.Name+string(arg.DataType)] = v + case time.Time: + localmap[arg.Name+string(arg.DataType)] = v + default: + err = fmt.Errorf("I don't know about type %T!\n", v) + return + } + } + return enc.Marshal(localmap) +} + +func (args *Arguments) UnmarshalBinary(data []byte) (err error) { + localmap := map[string]interface{}{} + + if err = dec.Unmarshal(data, &localmap); err != nil { + return err + } + + *args = (*args)[:0] + + for k, v := range localmap { + if len(k) < 2 { + return fmt.Errorf("Invalid encoding for key '%s'", k) + } + + arg := Argument{Name: string(k[:len(k)-1]), DataType: DataType(k[len(k)-1:])} + + switch arg.DataType { + case DataInt64: + if value, ok := v.(int64); ok { + arg.Value = value + } else if value, ok := v.(uint64); ok { + arg.Value = int64(value) + } else { + return fmt.Errorf("%+v has invalid data typei %T\n", arg, v) + } + case DataUint64: + if value, ok := v.(uint64); ok { + arg.Value = value + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + case DataFloat64: + if value, ok := v.(float64); ok { + arg.Value = value + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + case DataHash: + if value, ok := v.([]uint8); ok { + var hash crypto.Hash + copy(hash[:], value) + arg.Value = hash + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + case DataAddress: + if value, ok := v.([]uint8); ok { + a := make([]byte, 33, 33) + copy(a[:], value) + + p := new(crypto.Point) + if err = p.DecodeCompressed(a[0:33]); err != nil { + return err + } + addr := NewAddressFromKeys(p) + arg.Value = *addr + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + case DataString: + if value, ok := v.(string); ok { + arg.Value = value + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + case DataTime: + if value, ok := v.(time.Time); ok { + arg.Value = value + } else { + return fmt.Errorf("%+v has invalid data type %T\n", arg, v) + } + default: + err = fmt.Errorf("I don't know about typeaa %T %s!\n", v, k) + return + } + *args = append(*args, arg) + + } + + if err = args.Validate_Arguments(); err != nil { + return + } + args.Sort() // sort everything + + return +} + +// used to validata arguments whether the type is proper +func (args Arguments) Validate_Arguments() error { + for _, arg := range args { + if len(arg.Name) < 1 { + return fmt.Errorf("Name must be atleast 1 char long") + } + + switch arg.DataType { + case DataString: + if _, ok := arg.Value.(string); !ok { + return fmt.Errorf("'%s' value should be of type string", arg.Name) + } + case DataInt64: + if _, ok := arg.Value.(int64); !ok { + return fmt.Errorf("'%s' value should be of type int64", arg.Name) + } + case DataUint64: + if _, ok := arg.Value.(uint64); !ok { + return fmt.Errorf("'%s' value should be of type uint64", arg.Name) + } + case DataFloat64: + if _, ok := arg.Value.(float64); !ok { + return fmt.Errorf("'%s' value should be of type float64", arg.Name) + } + case DataHash: + if _, ok := arg.Value.(crypto.Hash); !ok { + return fmt.Errorf("'%s' value should be of type Hash", arg.Name) + } + case DataAddress: + if _, ok := arg.Value.(Address); !ok { + return fmt.Errorf("'%s' value should be of type address", arg.Name) + } + case DataTime: + if _, ok := arg.Value.(time.Time); !ok { + return fmt.Errorf("'%s' value should be of type time", arg.Name) + } + + default: + return fmt.Errorf("unknown data type. Pls implement") + } + } + return nil +} + +// sort the arguments by their name +func (args *Arguments) Sort() { + s := *args + if len(*args) <= 1 { + return + } + sort.Slice(s, func(i, j int) bool { + return s[i].Name <= s[j].Name + }) + +} + +// some fields are already defined +const RPC_DESTINATION_PORT = "D" // mandatory,uint64, used for ID of type uint64 +const RPC_SOURCE_PORT = "S" // mandatory,uint64, used for ID +const RPC_VALUE_TRANSFER = "V" //uint64, this is representation and is only readable, value is never transferred +const RPC_COMMENT = "C" //optional,string, used for display MSG to user +const RPC_EXPIRY = "E" //optional,time used for Expiry for this service call + +type argument_raw struct { + Name string `json:"name"` // string name must be atleast 1 byte + DataType DataType `json:"datatype"` // Type must one of the known data types + Value json.RawMessage `json:"value"` // delay parsing until we know the value should be as per type +} + +func (a *Argument) UnmarshalJSON(b []byte) (err error) { + var raw argument_raw + if err = json.Unmarshal(b, &raw); err != nil { + return err + } + a.Name = raw.Name + a.DataType = raw.DataType + switch raw.DataType { + case DataString: + var x string + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataInt64: + var x int64 + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataUint64: + var x uint64 + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataFloat64: + var x float64 + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataHash: + var x crypto.Hash + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataAddress: + var x Address + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + case DataTime: + var x time.Time + if err = json.Unmarshal(raw.Value, &x); err == nil { + a.Value = x + return + } + default: + return fmt.Errorf("unknown data type %s", raw.DataType) + + } + + return +} diff --git a/rpc/rpc_sc.go b/rpc/rpc_sc.go new file mode 100644 index 0000000..42ae051 --- /dev/null +++ b/rpc/rpc_sc.go @@ -0,0 +1,21 @@ +package rpc + +// definitions related to SC + +type SC_ACTION uint64 // sc actions are coded as follow +const ( + SC_CALL SC_ACTION = iota + SC_INSTALL +) + +// SC_CALL must have an arg of type SC_ENTRYPOINT of type String, SCID of type Hash +// SC_INSTALL must have an arg SC of String + +// some fields are already defined +const SCACTION = "SC_ACTION" //all SCS must have an ACTION +const SCCODE = "SC_CODE" // SCCODE must be sent in this ARGUMENT +const SCSIGNER = "SC_SIGNER" // the signer address +const SCSIGNC = "SC_SIGNC" // the sign C component +const SCSIGNS = "SC_SIGNS" // the sign S component + +const SCID = "SC_ID" // SCID diff --git a/rpc/wallet_rpc.go b/rpc/wallet_rpc.go new file mode 100644 index 0000000..4e76127 --- /dev/null +++ b/rpc/wallet_rpc.go @@ -0,0 +1,276 @@ +// Copyright 2017-2021 DERO Project. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 +// +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// this package contains only struct definitions required to implement wallet rpc (compatible with C daemon) +// in order to avoid the dependency on block chain by any package requiring access to rpc +// and other structures +// having the structures was causing the build times of explorer/wallet to be more than couple of secs +// so separated the logic from the structures + +package rpc + +import "fmt" +import "time" +import "strings" +import "math/big" + +import "github.com/deroproject/derohe/cryptography/crypto" + +// these structures are completely decoupled from blockchain and live only within the wallet +// all inputs and outputs which modify balance are presented by this structure +type Entry struct { + Height uint64 `json:"height"` + TopoHeight int64 `json:"topoheight"` + BlockHash string `json:"blockhash"` + MinerReward uint64 `json:"minerreward"` + TransactionPos int `json:"tpos"` // pos within block is negative -1 for coinbase + Pos int `json:"pos"` // pos within transaction + Coinbase bool `json:"coinbase"` + Incoming bool `json:"incoming"` + TXID string `json:"txid"` + Destination string `json:"destination"` + Burn uint64 `json:"burn,omitempty"` + Amount uint64 `json:"amount"` + Fees uint64 `json:"fees"` + Proof string `json:"proof"` // can be used to prove if available + Status byte `json:"status"` + Time time.Time `json:"time"` + EWData string `json:"ewdata"` // encrypted wallet balance at that point in time + + Data []byte `json:"data"` // data is entire decrypted dump + + PayloadType byte `json:"payloadtype"` + Payload []byte `json:"payload"` + PayloadError string `json:"payloaderror,omitempty"` + Payload_RPC Arguments `json:"payload_rpc,omitempty"` + + // these fields are only valid based on payload type and if payload could be successfully parsed and will by default be equal to zero values + Sender string `json:"sender"` + DestinationPort uint64 `json:"dstport"` + SourcePort uint64 `json:"srcport"` +} + +// converts entry to string +func (e Entry) String() string { + var b strings.Builder + + if e.Coinbase { + fmt.Fprintf(&b, "Type: Coinbase\n") + fmt.Fprintf(&b, "Amount: %s\n", FormatMoney(e.Amount)) + } else if e.Incoming { + fmt.Fprintf(&b, "Type: Received\n") + fmt.Fprintf(&b, "Amount: %s\n", FormatMoney(e.Amount)) + fmt.Fprintf(&b, "TXID: %s\n", e.TXID) + } else { + fmt.Fprintf(&b, "Type: Sent Outgoing\n") + fmt.Fprintf(&b, "Amount: %s\n", FormatMoney(e.Amount)) + fmt.Fprintf(&b, "TXID: %s\n", e.TXID) + fmt.Fprintf(&b, "Destination: %s\n", e.Destination) + fmt.Fprintf(&b, "Proof: %s\n", e.Proof) + } + if !e.Coinbase { + fmt.Fprintf(&b, "PayloadType : %d\n", e.PayloadType) + if e.PayloadType == 0 { + fmt.Fprintf(&b, "Sender: %s\n", e.Sender) + if e.PayloadError == "" { + args, _ := e.ProcessPayload() + fmt.Fprintf(&b, "DestPort: %016x\n", e.DestinationPort) + fmt.Fprintf(&b, "SrcPort: %016x\n", e.SourcePort) + fmt.Fprintf(&b, "Arguments: %+v\n", args) + } else { + fmt.Fprintf(&b, "Raw Payload: %x\n", e.Payload[:]) + fmt.Fprintf(&b, "Payload error: %s\n", e.PayloadError) + } + } + } + fmt.Fprintf(&b, "Block: %s\n", e.BlockHash) + fmt.Fprintf(&b, "Block Height: %d\n", e.Height) + fmt.Fprintf(&b, "Block TopoHeight: %d\n", e.TopoHeight) + fmt.Fprintf(&b, "Pos within tx: %d\n", e.Pos) + fmt.Fprintf(&b, "Time: %s\n", e.Time) + + return b.String() +} + +// process and updates necessary fields in the entry +func (e *Entry) ProcessPayload() (args Arguments, err error) { + + if len(e.Payload) == 0 { + err = fmt.Errorf("zero length payload") + return + } + if err = args.UnmarshalBinary(e.Payload); err == nil { + // lets decode dest port,source port for easier services programming + if args.Has(RPC_DESTINATION_PORT, DataUint64) { // but only it is present + e.DestinationPort = args.Value(RPC_DESTINATION_PORT, DataUint64).(uint64) + } + if args.Has(RPC_SOURCE_PORT, DataUint64) { // but only it is present + e.SourcePort = args.Value(RPC_SOURCE_PORT, DataUint64).(uint64) + } + e.Payload_RPC = append([]Argument{}, args...) + } else { // err is not nil so store it + e.PayloadError = err.Error() + } + return args, err + +} + +// never do any division operation on money due to floating point issues +// newbies, see type the next in python interpretor "3.33-3.13" +// +func FormatMoney(amount uint64) string { + return FormatMoneyPrecision(amount, 5) // default is 5 precision after floating point +} + +// 0 +func FormatMoney0(amount uint64) string { + return FormatMoneyPrecision(amount, 0) +} + +//5 precision +func FormatMoney5(amount uint64) string { + return FormatMoneyPrecision(amount, 5) +} + +//8 precision +func FormatMoney8(amount uint64) string { + return FormatMoneyPrecision(amount, 8) +} + +// 12 precision +func FormatMoney12(amount uint64) string { + return FormatMoneyPrecision(amount, 12) // default is 8 precision after floating point +} + +// format money with specific precision +func FormatMoneyPrecision(amount uint64, precision int) string { + hard_coded_decimals := new(big.Float).SetInt64(100000) + float_amount, _, _ := big.ParseFloat(fmt.Sprintf("%d", amount), 10, 0, big.ToZero) + result := new(big.Float) + result.Quo(float_amount, hard_coded_decimals) + return result.Text('f', precision) // 5 is display precision after floating point +} + +type ( + GetBalance_Params struct{} // no params + GetBalance_Result struct { + Balance uint64 `json:"balance"` + Unlocked_Balance uint64 `json:"unlocked_balance"` + } +) + +type ( + GetAddress_Params struct{} // no params + GetAddress_Result struct { + Address string `json:"address"` + } +) + +type ( + GetHeight_Params struct{} // no params + GetHeight_Result struct { + Height uint64 `json:"height"` + } +) + +// return type is string +type ( + Transfer struct { + SCID crypto.Hash `json:"scid"` + Destination string `json:"destination"` + Amount uint64 `json:"amount"` + Burn uint64 `json:"burn"` + Payload_RPC Arguments `json:"payload_rpc"` + } + + Transfer_Params struct { + Transfers []Transfer `json:"transfers"` + SC_Code string `json:"sc"` + SC_Value uint64 `json:"sc_value"` + SC_ID string `json:"scid"` + SC_RPC Arguments `json:"sc_rpc"` + } +) + +type ( + Get_Transfers_Params struct { + Coinbase bool `json:"coinbase"` + In bool `json:"in"` + Out bool `json:"out"` + Min_Height uint64 `json:"min_height"` + Max_Height uint64 `json:"max_height"` + Sender string `json:"sender"` + Receiver string `json:"receiver"` + DestinationPort uint64 `json:"dstport"` + SourcePort uint64 `json:"srcport"` + } + Get_Transfers_Result struct { + Entries []Entry `json:"entries,omitempty"` + } +) + +// Get_Bulk_Payments +type ( + Get_Bulk_Payments_Params struct { + Payment_IDs []string `json:"payment_ids"` + Min_block_height uint64 `json:"min_block_height"` + } + Get_Bulk_Payments_Result struct { + } +) + +// query_key +type ( + Query_Key_Params struct { + Key_type string `json:"key_type"` + } + Query_Key_Result struct { + Key string `json:"key"` + } +) + +// make_integrated_address_handler +type ( + Make_Integrated_Address_Params struct { + Address string `json:"address"` // if its empty we assume wallets address + Payload_RPC Arguments `json:"payload_rpc"` + } + Make_Integrated_Address_Result struct { + Integrated_Address string `json:"integrated_address"` + Payload_RPC Arguments `json:"payload_rpc"` + } +) + +// split_integrated_address_handler +type ( + Split_Integrated_Address_Params struct { + Integrated_Address string `json:"integrated_address"` + } + Split_Integrated_Address_Result struct { + Address string `json:"address"` + Payload_RPC Arguments `json:"payload_rpc"` + } +) + +// Get_Transfer_By_TXID +type ( + Get_Transfer_By_TXID_Params struct { + TXID string `json:"txid"` + } + Get_Transfer_By_TXID_Result struct { + Entry Entry `json:"entry,omitempty"` + } +) diff --git a/transaction/transaction.go b/transaction/transaction.go index d4bbea5..a685771 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -21,10 +21,11 @@ import "bytes" import "math/big" import "encoding/binary" -//import "github.com/romana/rlog" +import "github.com/romana/rlog" -import "github.com/deroproject/derohe/crypto" -import "github.com/deroproject/derohe/crypto/bn256" +import "github.com/deroproject/derohe/cryptography/crypto" +import "github.com/deroproject/derohe/cryptography/bn256" +import "github.com/deroproject/derohe/rpc" type TransactionType uint64 @@ -34,7 +35,6 @@ const ( COINBASE // normal coinbase tx ( if miner address is already registered) NORMAL // one to one TX with ring members BURN_TX // if user burns an amount to control inflation - MULTIUSER_TX // multi-user transaction SC_TX // smart contract transaction ) @@ -51,33 +51,126 @@ func (t TransactionType) String() string { return "NORMAL" case BURN_TX: return "BURN" - case MULTIUSER_TX: - return "MULTIUSER_TX" case SC_TX: - return "SMARTCONTRACT_TX" + return "SC" default: return "unknown transaction type" } } +const PAYLOAD_LIMIT = 1 + 144 // entire payload header is mandatorily encrypted +// sender position in ring representation in a byte, uptp 256 ring +// 144 byte payload ( to implement specific functionality such as delivery of keys etc), user dependent encryption +const PAYLOAD0_LIMIT = 144 // 1 byte has been reserved for sender position in ring representation in a byte, uptp 256 ring + +const ENCRYPTED_DEFAULT_PAYLOAD_CBOR = byte(0) + // the core transaction +// in our design, tx cam be sent by 1 wallet, but SC part/gas can be signed by any other user, but this is not implemented type Transaction_Prefix struct { Version uint64 `json:"version"` TransactionType TransactionType `json:"version"` - Value uint64 `json:"value"` // represnets premine, value for SC, BURN amount - Amounts []uint64 // open amounts for multi user tx + Value uint64 `json:"value"` // represents value for premine, SC, BURN transactions MinerAddress [33]byte `json:"miner_address"` // miner address // 33 bytes also used for registration C [32]byte `json:"c"` // used for registration S [32]byte `json:"s"` // used for registration Height uint64 `json:"height"` // height at the state, used to cross-check state - PaymentID [8]byte `json:"paymentid"` // hardcoded 8 bytes + SCDATA rpc.Arguments `json:"scdata"` // all SC related data is provided here, an SC tx uses all the fields +} + +type AssetPayload struct { + SCID crypto.Hash // which asset, it's zero for main asset + BurnValue uint64 `json:"value"` // represents value for premine, SC, BURN transactions + + RPCType byte // its unencrypted and is by default 0 for almost all txs + RPCPayload []byte // rpc payload encryption depends on RPCType + + // sender position in ring representation in a byte, uptp 256 ring + // 144 byte payload ( to implement specific functionality such as delivery of keys etc), user dependent encryption + Statement crypto.Statement // note statement containts fees + Proof *crypto.Proof +} + +// marshal asset +/* +func (a AssetPayload) MarshalHeader() ([]byte, error) { + + return writer.Bytes(), nil +} +*/ + +func (a AssetPayload) MarshalHeaderStatement() ([]byte, error) { + var writer bytes.Buffer + var buffer_backing [binary.MaxVarintLen64]byte + buf := buffer_backing[:] + _ = buf + + n := binary.PutUvarint(buf, a.BurnValue) + writer.Write(buf[:n]) + + writer.Write(a.SCID[:]) + + writer.WriteByte(a.RPCType) // payload type byte + writer.Write(a.RPCPayload) // src Id is always payload limit bytes + + if len(a.RPCPayload) != PAYLOAD_LIMIT { + return nil, fmt.Errorf("RPCPayload should be %d bytes, but have %d bytes", PAYLOAD_LIMIT, len(a.RPCPayload)) + } + + a.Statement.Serialize(&writer) + //if err != nil { + // return nil,err + //} + + return writer.Bytes(), nil +} + +func (a *AssetPayload) UnmarshalHeaderStatement(r *bytes.Reader) (err error) { + if a.BurnValue, err = binary.ReadUvarint(r); err != nil { + return err + } + if _, err = r.Read(a.SCID[:]); err != nil { + return err + } + if a.RPCType, err = r.ReadByte(); err != nil { + return err + } + + a.RPCPayload = make([]byte, PAYLOAD_LIMIT, PAYLOAD_LIMIT) + if _, err = r.Read(a.RPCPayload[:]); err != nil { + return err + } + + if err = a.Statement.Deserialize(r); err != nil { + return err + } + + return nil +} + +func (a AssetPayload) MarshalProofs() ([]byte, error) { + var writer bytes.Buffer + a.Proof.Serialize(&writer) + + return writer.Bytes(), nil +} + +func (a *AssetPayload) UnmarshalProofs(r *bytes.Reader) (err error) { + a.Proof = &crypto.Proof{} + + if err = a.Proof.Deserialize(r, crypto.GetPowerof2(len(a.Statement.Publickeylist_pointers)/int(a.Statement.Bytes_per_publickey))); err != nil { + return err + } + + return nil + } type Transaction struct { Transaction_Prefix // same as Transaction_Prefix - Statement crypto.Statement - Proof *crypto.Proof + + Payloads []AssetPayload // each transaction can have a number os payloads } // this excludes the proof part, so it can pruned @@ -89,7 +182,6 @@ func (tx *Transaction) GetHash() (result crypto.Hash) { panic("Transaction version unknown") } - return } @@ -106,6 +198,16 @@ func (tx *Transaction) IsPremine() (result bool) { return tx.TransactionType == PREMINE } +func (tx *Transaction) Fees() (fees uint64) { + var zero_scid [32]byte + for i := range tx.Payloads { + if zero_scid == tx.Payloads[i].SCID { + fees += tx.Payloads[i].Statement.Fees + } + } + return fees +} + func (tx *Transaction) IsRegistrationValid() (result bool) { var u bn256.G1 @@ -152,24 +254,27 @@ func (tx *Transaction) DeserializeHeader(buf []byte) (err error) { tx.TransactionType = TransactionType(tmp_uint64) switch tx.TransactionType { - case PREMINE: + case PREMINE, REGISTRATION, COINBASE, BURN_TX, NORMAL, SC_TX: + default: + panic("unknown transaction type") + } + + if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX { tx.Value, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Premine value in Transaction\n") } buf = buf[done:] + } + + if tx.TransactionType == PREMINE || tx.TransactionType == COINBASE || tx.TransactionType == REGISTRATION || tx.TransactionType == SC_TX { if 33 != copy(tx.MinerAddress[:], buf[:]) { return fmt.Errorf("Invalid Miner Address in Transaction\n") } buf = buf[33:] - goto done - - case REGISTRATION: - if 33 != copy(tx.MinerAddress[:], buf[:33]) { - return fmt.Errorf("Invalid Miner Address in Transaction\n") - } - buf = buf[33:] + } + if tx.TransactionType == REGISTRATION || tx.TransactionType == SC_TX { if 32 != copy(tx.C[:], buf[:32]) { return fmt.Errorf("Invalid C in Transaction\n") } @@ -179,68 +284,67 @@ func (tx *Transaction) DeserializeHeader(buf []byte) (err error) { return fmt.Errorf("Invalid S in Transaction\n") } buf = buf[32:] + } - goto done - - case COINBASE: - if 33 != copy(tx.MinerAddress[:], buf[:]) { - return fmt.Errorf("Invalid Miner Address in Transaction\n") - } - buf = buf[33:] - goto done - - case NORMAL: // parse height and root hash + if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX { + // parse height and root hash tx.Height, done = binary.Uvarint(buf) if done <= 0 { return fmt.Errorf("Invalid Height value in Transaction\n") } buf = buf[done:] - if len(buf) < 8 { - return fmt.Errorf("Invalid payment id value in Transaction\n") + var asset_count uint64 + asset_count, done = binary.Uvarint(buf) + if done <= 0 || asset_count < 1 { + return fmt.Errorf("Invalid asset_count in Transaction\n") } - copy(tx.PaymentID[:], buf[:8]) - buf = buf[8:] + buf = buf[done:] - tx.Proof = &crypto.Proof{} + for i := uint64(0); i < asset_count; i++ { + var a AssetPayload - r = bytes.NewReader(buf[:]) - - tx.Statement.Deserialize(r) - - statement_size := len(buf) - r.Len() - // fmt.Printf("tx Statement size deserialing %d\n", statement_size) - - // fmt.Printf("tx Proof size %d\n", len(buf) - statement_size) - - buf = buf[statement_size:] - r = bytes.NewReader(buf[:]) - - if err := tx.Proof.Deserialize(r, crypto.GetPowerof2(len(tx.Statement.Publickeylist_compressed))); err != nil { - fmt.Printf("error deserialing proof err %s", err) - return err + r = bytes.NewReader(buf[:]) + if err = a.UnmarshalHeaderStatement(r); err != nil { + panic(err) + } + tx.Payloads = append(tx.Payloads, a) + buf = buf[len(buf)-r.Len():] } - // fmt.Printf("tx Proof size deserialed %d bytes remaining %d \n", len(buf) - r.Len(), r.Len()) - - if r.Len() != 0 { - return fmt.Errorf("Extra unknown data in Transaction, extrabytes %d\n", r.Len()) - } - - case BURN_TX: - panic("TODO") - case MULTIUSER_TX: - panic("TODO") - case SC_TX: - panic("TODO") - - default: - panic("unknown transaction type") } -done: - if len(buf) != 0 { - //return fmt.Errorf("Extra unknown data in Transaction\n") + if tx.TransactionType == SC_TX { + var sc_len uint64 + sc_len, done = binary.Uvarint(buf) + if done <= 0 { + return fmt.Errorf("Invalid sc length in Transaction\n") + } + buf = buf[done:] + if sc_len > uint64(len(buf)) { // we are are crossing tx_boundary + return fmt.Errorf("SC len out of possible range") + } + if err := tx.SCDATA.UnmarshalBinary(buf[:sc_len]); err != nil { + return err + } + buf = buf[sc_len:] + } + + if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX { + + for i := range tx.Payloads { + tx.Payloads[i].Proof = &crypto.Proof{} + + r = bytes.NewReader(buf[:]) + if err = tx.Payloads[i].UnmarshalProofs(r); err != nil { + panic(err) + } + buf = buf[len(buf)-r.Len():] + } + } + + if len(buf) != 0 && (tx.TransactionType == PREMINE || tx.TransactionType == REGISTRATION || tx.TransactionType == COINBASE) { // these tx are complete + //return fmt.Errorf("Extra unknown data in Transaction, extrabytes %d\n", r.Len()) } //rlog.Tracef(8, "TX deserialized %+v\n", tx) @@ -265,41 +369,53 @@ func (tx *Transaction) SerializeHeader() []byte { n := binary.PutUvarint(buf, tx.Version) serialised_header.Write(buf[:n]) + switch tx.TransactionType { + case PREMINE, REGISTRATION, COINBASE, BURN_TX, NORMAL, SC_TX: + default: + panic("unknown transaction type") + } + n = binary.PutUvarint(buf, uint64(tx.TransactionType)) serialised_header.Write(buf[:n]) - switch tx.TransactionType { - case PREMINE: + if tx.TransactionType == PREMINE || tx.TransactionType == BURN_TX || tx.TransactionType == SC_TX { n := binary.PutUvarint(buf, tx.Value) serialised_header.Write(buf[:n]) + } + if tx.TransactionType == PREMINE || tx.TransactionType == COINBASE || tx.TransactionType == REGISTRATION || tx.TransactionType == SC_TX { serialised_header.Write(tx.MinerAddress[:]) - return serialised_header.Bytes() - - case REGISTRATION: - serialised_header.Write(tx.MinerAddress[:]) + } + if tx.TransactionType == REGISTRATION || tx.TransactionType == SC_TX { serialised_header.Write(tx.C[:]) serialised_header.Write(tx.S[:]) - return serialised_header.Bytes() + } - case COINBASE: - serialised_header.Write(tx.MinerAddress[:]) - return serialised_header.Bytes() - - case NORMAL: + if tx.TransactionType == BURN_TX || tx.TransactionType == NORMAL || tx.TransactionType == SC_TX { n = binary.PutUvarint(buf, uint64(tx.Height)) serialised_header.Write(buf[:n]) - serialised_header.Write(tx.PaymentID[:8]) // payment Id is always 8 bytes - return serialised_header.Bytes() - case BURN_TX: - panic("TODO") - case MULTIUSER_TX: - panic("TODO") - case SC_TX: - panic("TODO") + n = binary.PutUvarint(buf, uint64(len(tx.Payloads))) + serialised_header.Write(buf[:n]) - default: - panic("unknown transaction type") + for _, p := range tx.Payloads { + if pheader_bytes, err := p.MarshalHeaderStatement(); err == nil { + serialised_header.Write(pheader_bytes) + } else { + panic(err) + } + } + + } + + if tx.TransactionType == SC_TX { + if data, err := tx.SCDATA.MarshalBinary(); err != nil { + rlog.Warnf("err marshalling SC data %s\n", err) + panic(err) + } else { + n = binary.PutUvarint(buf, uint64(len(data))) + serialised_header.Write(buf[:n]) + serialised_header.Write(data) + } } return serialised_header.Bytes() @@ -307,27 +423,16 @@ func (tx *Transaction) SerializeHeader() []byte { // serialize entire transaction include signature func (tx *Transaction) Serialize() []byte { - var serialised bytes.Buffer - header_bytes := tx.SerializeHeader() - //base_bytes := tx.RctSignature.SerializeBase() - //prunable := tx.RctSignature.SerializePrunable() serialised.Write(header_bytes) - if tx.Proof != nil { - - // done_bytes := serialised.Len() - - tx.Statement.Serialize(&serialised) - // statement_size := serialised.Len() - done_bytes - // fmt.Printf("tx statement_size serializing %d\n", statement_size) - - //done_bytes =serialised.Len() - tx.Proof.Serialize(&serialised) - - // fmt.Printf("tx Proof serialised size %d\n", serialised.Len() - done_bytes) + for _, p := range tx.Payloads { + if pheader_bytes, err := p.MarshalProofs(); err == nil { + serialised.Write(pheader_bytes) + } else { + panic(err) + } } - return serialised.Bytes() //buf } @@ -339,10 +444,9 @@ func (tx *Transaction) SerializeCoreStatement() []byte { serialised.Write(header_bytes) switch tx.TransactionType { - case PREMINE, REGISTRATION, COINBASE: - case NORMAL, BURN_TX, MULTIUSER_TX, SC_TX: - tx.Statement.Serialize(&serialised) - + case PREMINE, COINBASE: + case REGISTRATION: + case NORMAL, BURN_TX, SC_TX: default: panic("unknown transaction type") } diff --git a/vendor/etcd.io/bbolt/.gitignore b/vendor/etcd.io/bbolt/.gitignore new file mode 100644 index 0000000..3bcd8cb --- /dev/null +++ b/vendor/etcd.io/bbolt/.gitignore @@ -0,0 +1,5 @@ +*.prof +*.test +*.swp +/bin/ +cover.out diff --git a/vendor/etcd.io/bbolt/.travis.yml b/vendor/etcd.io/bbolt/.travis.yml new file mode 100644 index 0000000..257dfdf --- /dev/null +++ b/vendor/etcd.io/bbolt/.travis.yml @@ -0,0 +1,17 @@ +language: go +go_import_path: go.etcd.io/bbolt + +sudo: false + +go: +- 1.12 + +before_install: +- go get -v honnef.co/go/tools/... +- go get -v github.com/kisielk/errcheck + +script: +- make fmt +- make test +- make race +# - make errcheck diff --git a/vendor/github.com/alecthomas/jsonschema/COPYING b/vendor/etcd.io/bbolt/LICENSE similarity index 52% rename from vendor/github.com/alecthomas/jsonschema/COPYING rename to vendor/etcd.io/bbolt/LICENSE index 2993ec0..004e77f 100644 --- a/vendor/github.com/alecthomas/jsonschema/COPYING +++ b/vendor/etcd.io/bbolt/LICENSE @@ -1,19 +1,20 @@ -Copyright (C) 2014 Alec Thomas +The MIT License (MIT) + +Copyright (c) 2013 Ben Johnson 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: +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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. +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. diff --git a/vendor/etcd.io/bbolt/Makefile b/vendor/etcd.io/bbolt/Makefile new file mode 100644 index 0000000..2968aaa --- /dev/null +++ b/vendor/etcd.io/bbolt/Makefile @@ -0,0 +1,38 @@ +BRANCH=`git rev-parse --abbrev-ref HEAD` +COMMIT=`git rev-parse --short HEAD` +GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" + +default: build + +race: + @TEST_FREELIST_TYPE=hashmap go test -v -race -test.run="TestSimulate_(100op|1000op)" + @echo "array freelist test" + @TEST_FREELIST_TYPE=array go test -v -race -test.run="TestSimulate_(100op|1000op)" + +fmt: + !(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]') + +# go get honnef.co/go/tools/simple +gosimple: + gosimple ./... + +# go get honnef.co/go/tools/unused +unused: + unused ./... + +# go get github.com/kisielk/errcheck +errcheck: + @errcheck -ignorepkg=bytes -ignore=os:Remove go.etcd.io/bbolt + +test: + TEST_FREELIST_TYPE=hashmap go test -timeout 20m -v -coverprofile cover.out -covermode atomic + # Note: gets "program not an importable package" in out of path builds + TEST_FREELIST_TYPE=hashmap go test -v ./cmd/bbolt + + @echo "array freelist test" + + @TEST_FREELIST_TYPE=array go test -timeout 20m -v -coverprofile cover.out -covermode atomic + # Note: gets "program not an importable package" in out of path builds + @TEST_FREELIST_TYPE=array go test -v ./cmd/bbolt + +.PHONY: race fmt errcheck test gosimple unused diff --git a/vendor/etcd.io/bbolt/README.md b/vendor/etcd.io/bbolt/README.md new file mode 100644 index 0000000..6b5ed3c --- /dev/null +++ b/vendor/etcd.io/bbolt/README.md @@ -0,0 +1,958 @@ +bbolt +===== + +[![Go Report Card](https://goreportcard.com/badge/github.com/etcd-io/bbolt?style=flat-square)](https://goreportcard.com/report/github.com/etcd-io/bbolt) +[![Coverage](https://codecov.io/gh/etcd-io/bbolt/branch/master/graph/badge.svg)](https://codecov.io/gh/etcd-io/bbolt) +[![Build Status Travis](https://img.shields.io/travis/etcd-io/bboltlabs.svg?style=flat-square&&branch=master)](https://travis-ci.com/etcd-io/bbolt) +[![Godoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/etcd-io/bbolt) +[![Releases](https://img.shields.io/github/release/etcd-io/bbolt/all.svg?style=flat-square)](https://github.com/etcd-io/bbolt/releases) +[![LICENSE](https://img.shields.io/github/license/etcd-io/bbolt.svg?style=flat-square)](https://github.com/etcd-io/bbolt/blob/master/LICENSE) + +bbolt is a fork of [Ben Johnson's][gh_ben] [Bolt][bolt] key/value +store. The purpose of this fork is to provide the Go community with an active +maintenance and development target for Bolt; the goal is improved reliability +and stability. bbolt includes bug fixes, performance enhancements, and features +not found in Bolt while preserving backwards compatibility with the Bolt API. + +Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] +[LMDB project][lmdb]. The goal of the project is to provide a simple, +fast, and reliable database for projects that don't require a full database +server such as Postgres or MySQL. + +Since Bolt is meant to be used as such a low-level piece of functionality, +simplicity is key. The API will be small and only focus on getting values +and setting values. That's it. + +[gh_ben]: https://github.com/benbjohnson +[bolt]: https://github.com/boltdb/bolt +[hyc_symas]: https://twitter.com/hyc_symas +[lmdb]: http://symas.com/mdb/ + +## Project Status + +Bolt is stable, the API is fixed, and the file format is fixed. Full unit +test coverage and randomized black box testing are used to ensure database +consistency and thread safety. Bolt is currently used in high-load production +environments serving databases as large as 1TB. Many companies such as +Shopify and Heroku use Bolt-backed services every day. + +## Project versioning + +bbolt uses [semantic versioning](http://semver.org). +API should not change between patch and minor releases. +New minor versions may add additional features to the API. + +## Table of Contents + + - [Getting Started](#getting-started) + - [Installing](#installing) + - [Opening a database](#opening-a-database) + - [Transactions](#transactions) + - [Read-write transactions](#read-write-transactions) + - [Read-only transactions](#read-only-transactions) + - [Batch read-write transactions](#batch-read-write-transactions) + - [Managing transactions manually](#managing-transactions-manually) + - [Using buckets](#using-buckets) + - [Using key/value pairs](#using-keyvalue-pairs) + - [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket) + - [Iterating over keys](#iterating-over-keys) + - [Prefix scans](#prefix-scans) + - [Range scans](#range-scans) + - [ForEach()](#foreach) + - [Nested buckets](#nested-buckets) + - [Database backups](#database-backups) + - [Statistics](#statistics) + - [Read-Only Mode](#read-only-mode) + - [Mobile Use (iOS/Android)](#mobile-use-iosandroid) + - [Resources](#resources) + - [Comparison with other databases](#comparison-with-other-databases) + - [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases) + - [LevelDB, RocksDB](#leveldb-rocksdb) + - [LMDB](#lmdb) + - [Caveats & Limitations](#caveats--limitations) + - [Reading the Source](#reading-the-source) + - [Other Projects Using Bolt](#other-projects-using-bolt) + +## Getting Started + +### Installing + +To start using Bolt, install Go and run `go get`: + +```sh +$ go get go.etcd.io/bbolt/... +``` + +This will retrieve the library and install the `bolt` command line utility into +your `$GOBIN` path. + + +### Importing bbolt + +To use bbolt as an embedded key-value store, import as: + +```go +import bolt "go.etcd.io/bbolt" + +db, err := bolt.Open(path, 0666, nil) +if err != nil { + return err +} +defer db.Close() +``` + + +### Opening a database + +The top-level object in Bolt is a `DB`. It is represented as a single file on +your disk and represents a consistent snapshot of your data. + +To open your database, simply use the `bolt.Open()` function: + +```go +package main + +import ( + "log" + + bolt "go.etcd.io/bbolt" +) + +func main() { + // Open the my.db data file in your current directory. + // It will be created if it doesn't exist. + db, err := bolt.Open("my.db", 0600, nil) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + ... +} +``` + +Please note that Bolt obtains a file lock on the data file so multiple processes +cannot open the same database at the same time. Opening an already open Bolt +database will cause it to hang until the other process closes it. To prevent +an indefinite wait you can pass a timeout option to the `Open()` function: + +```go +db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second}) +``` + + +### Transactions + +Bolt allows only one read-write transaction at a time but allows as many +read-only transactions as you want at a time. Each transaction has a consistent +view of the data as it existed when the transaction started. + +Individual transactions and all objects created from them (e.g. buckets, keys) +are not thread safe. To work with data in multiple goroutines you must start +a transaction for each one or use locking to ensure only one goroutine accesses +a transaction at a time. Creating transaction from the `DB` is thread safe. + +Transactions should not depend on one another and generally shouldn't be opened +simultaneously in the same goroutine. This can cause a deadlock as the read-write +transaction needs to periodically re-map the data file but it cannot do so while +any read-only transaction is open. Even a nested read-only transaction can cause +a deadlock, as the child transaction can block the parent transaction from releasing +its resources. + +#### Read-write transactions + +To start a read-write transaction, you can use the `DB.Update()` function: + +```go +err := db.Update(func(tx *bolt.Tx) error { + ... + return nil +}) +``` + +Inside the closure, you have a consistent view of the database. You commit the +transaction by returning `nil` at the end. You can also rollback the transaction +at any point by returning an error. All database operations are allowed inside +a read-write transaction. + +Always check the return error as it will report any disk failures that can cause +your transaction to not complete. If you return an error within your closure +it will be passed through. + + +#### Read-only transactions + +To start a read-only transaction, you can use the `DB.View()` function: + +```go +err := db.View(func(tx *bolt.Tx) error { + ... + return nil +}) +``` + +You also get a consistent view of the database within this closure, however, +no mutating operations are allowed within a read-only transaction. You can only +retrieve buckets, retrieve values, and copy the database within a read-only +transaction. + + +#### Batch read-write transactions + +Each `DB.Update()` waits for disk to commit the writes. This overhead +can be minimized by combining multiple updates with the `DB.Batch()` +function: + +```go +err := db.Batch(func(tx *bolt.Tx) error { + ... + return nil +}) +``` + +Concurrent Batch calls are opportunistically combined into larger +transactions. Batch is only useful when there are multiple goroutines +calling it. + +The trade-off is that `Batch` can call the given +function multiple times, if parts of the transaction fail. The +function must be idempotent and side effects must take effect only +after a successful return from `DB.Batch()`. + +For example: don't display messages from inside the function, instead +set variables in the enclosing scope: + +```go +var id uint64 +err := db.Batch(func(tx *bolt.Tx) error { + // Find last key in bucket, decode as bigendian uint64, increment + // by one, encode back to []byte, and add new key. + ... + id = newValue + return nil +}) +if err != nil { + return ... +} +fmt.Println("Allocated ID %d", id) +``` + + +#### Managing transactions manually + +The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()` +function. These helper functions will start the transaction, execute a function, +and then safely close your transaction if an error is returned. This is the +recommended way to use Bolt transactions. + +However, sometimes you may want to manually start and end your transactions. +You can use the `DB.Begin()` function directly but **please** be sure to close +the transaction. + +```go +// Start a writable transaction. +tx, err := db.Begin(true) +if err != nil { + return err +} +defer tx.Rollback() + +// Use the transaction... +_, err := tx.CreateBucket([]byte("MyBucket")) +if err != nil { + return err +} + +// Commit the transaction and check for error. +if err := tx.Commit(); err != nil { + return err +} +``` + +The first argument to `DB.Begin()` is a boolean stating if the transaction +should be writable. + + +### Using buckets + +Buckets are collections of key/value pairs within the database. All keys in a +bucket must be unique. You can create a bucket using the `Tx.CreateBucket()` +function: + +```go +db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("MyBucket")) + if err != nil { + return fmt.Errorf("create bucket: %s", err) + } + return nil +}) +``` + +You can also create a bucket only if it doesn't exist by using the +`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this +function for all your top-level buckets after you open your database so you can +guarantee that they exist for future transactions. + +To delete a bucket, simply call the `Tx.DeleteBucket()` function. + + +### Using key/value pairs + +To save a key/value pair to a bucket, use the `Bucket.Put()` function: + +```go +db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("MyBucket")) + err := b.Put([]byte("answer"), []byte("42")) + return err +}) +``` + +This will set the value of the `"answer"` key to `"42"` in the `MyBucket` +bucket. To retrieve this value, we can use the `Bucket.Get()` function: + +```go +db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("MyBucket")) + v := b.Get([]byte("answer")) + fmt.Printf("The answer is: %s\n", v) + return nil +}) +``` + +The `Get()` function does not return an error because its operation is +guaranteed to work (unless there is some kind of system failure). If the key +exists then it will return its byte slice value. If it doesn't exist then it +will return `nil`. It's important to note that you can have a zero-length value +set to a key which is different than the key not existing. + +Use the `Bucket.Delete()` function to delete a key from the bucket. + +Please note that values returned from `Get()` are only valid while the +transaction is open. If you need to use a value outside of the transaction +then you must use `copy()` to copy it to another byte slice. + + +### Autoincrementing integer for the bucket +By using the `NextSequence()` function, you can let Bolt determine a sequence +which can be used as the unique identifier for your key/value pairs. See the +example below. + +```go +// CreateUser saves u to the store. The new user ID is set on u once the data is persisted. +func (s *Store) CreateUser(u *User) error { + return s.db.Update(func(tx *bolt.Tx) error { + // Retrieve the users bucket. + // This should be created when the DB is first opened. + b := tx.Bucket([]byte("users")) + + // Generate ID for the user. + // This returns an error only if the Tx is closed or not writeable. + // That can't happen in an Update() call so I ignore the error check. + id, _ := b.NextSequence() + u.ID = int(id) + + // Marshal user data into bytes. + buf, err := json.Marshal(u) + if err != nil { + return err + } + + // Persist bytes to users bucket. + return b.Put(itob(u.ID), buf) + }) +} + +// itob returns an 8-byte big endian representation of v. +func itob(v int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} + +type User struct { + ID int + ... +} +``` + +### Iterating over keys + +Bolt stores its keys in byte-sorted order within a bucket. This makes sequential +iteration over these keys extremely fast. To iterate over keys we'll use a +`Cursor`: + +```go +db.View(func(tx *bolt.Tx) error { + // Assume bucket exists and has keys + b := tx.Bucket([]byte("MyBucket")) + + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + fmt.Printf("key=%s, value=%s\n", k, v) + } + + return nil +}) +``` + +The cursor allows you to move to a specific point in the list of keys and move +forward or backward through the keys one at a time. + +The following functions are available on the cursor: + +``` +First() Move to the first key. +Last() Move to the last key. +Seek() Move to a specific key. +Next() Move to the next key. +Prev() Move to the previous key. +``` + +Each of those functions has a return signature of `(key []byte, value []byte)`. +When you have iterated to the end of the cursor then `Next()` will return a +`nil` key. You must seek to a position using `First()`, `Last()`, or `Seek()` +before calling `Next()` or `Prev()`. If you do not seek to a position then +these functions will return a `nil` key. + +During iteration, if the key is non-`nil` but the value is `nil`, that means +the key refers to a bucket rather than a value. Use `Bucket.Bucket()` to +access the sub-bucket. + + +#### Prefix scans + +To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`: + +```go +db.View(func(tx *bolt.Tx) error { + // Assume bucket exists and has keys + c := tx.Bucket([]byte("MyBucket")).Cursor() + + prefix := []byte("1234") + for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { + fmt.Printf("key=%s, value=%s\n", k, v) + } + + return nil +}) +``` + +#### Range scans + +Another common use case is scanning over a range such as a time range. If you +use a sortable time encoding such as RFC3339 then you can query a specific +date range like this: + +```go +db.View(func(tx *bolt.Tx) error { + // Assume our events bucket exists and has RFC3339 encoded time keys. + c := tx.Bucket([]byte("Events")).Cursor() + + // Our time range spans the 90's decade. + min := []byte("1990-01-01T00:00:00Z") + max := []byte("2000-01-01T00:00:00Z") + + // Iterate over the 90's. + for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { + fmt.Printf("%s: %s\n", k, v) + } + + return nil +}) +``` + +Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable. + + +#### ForEach() + +You can also use the function `ForEach()` if you know you'll be iterating over +all the keys in a bucket: + +```go +db.View(func(tx *bolt.Tx) error { + // Assume bucket exists and has keys + b := tx.Bucket([]byte("MyBucket")) + + b.ForEach(func(k, v []byte) error { + fmt.Printf("key=%s, value=%s\n", k, v) + return nil + }) + return nil +}) +``` + +Please note that keys and values in `ForEach()` are only valid while +the transaction is open. If you need to use a key or value outside of +the transaction, you must use `copy()` to copy it to another byte +slice. + +### Nested buckets + +You can also store a bucket in a key to create nested buckets. The API is the +same as the bucket management API on the `DB` object: + +```go +func (*Bucket) CreateBucket(key []byte) (*Bucket, error) +func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) +func (*Bucket) DeleteBucket(key []byte) error +``` + +Say you had a multi-tenant application where the root level bucket was the account bucket. Inside of this bucket was a sequence of accounts which themselves are buckets. And inside the sequence bucket you could have many buckets pertaining to the Account itself (Users, Notes, etc) isolating the information into logical groupings. + +```go + +// createUser creates a new user in the given account. +func createUser(accountID int, u *User) error { + // Start the transaction. + tx, err := db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + // Retrieve the root bucket for the account. + // Assume this has already been created when the account was set up. + root := tx.Bucket([]byte(strconv.FormatUint(accountID, 10))) + + // Setup the users bucket. + bkt, err := root.CreateBucketIfNotExists([]byte("USERS")) + if err != nil { + return err + } + + // Generate an ID for the new user. + userID, err := bkt.NextSequence() + if err != nil { + return err + } + u.ID = userID + + // Marshal and save the encoded user. + if buf, err := json.Marshal(u); err != nil { + return err + } else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil { + return err + } + + // Commit the transaction. + if err := tx.Commit(); err != nil { + return err + } + + return nil +} + +``` + + + + +### Database backups + +Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()` +function to write a consistent view of the database to a writer. If you call +this from a read-only transaction, it will perform a hot backup and not block +your other database reads and writes. + +By default, it will use a regular file handle which will utilize the operating +system's page cache. See the [`Tx`](https://godoc.org/go.etcd.io/bbolt#Tx) +documentation for information about optimizing for larger-than-RAM datasets. + +One common use case is to backup over HTTP so you can use tools like `cURL` to +do database backups: + +```go +func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { + err := db.View(func(tx *bolt.Tx) error { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) + w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) + _, err := tx.WriteTo(w) + return err + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} +``` + +Then you can backup using this command: + +```sh +$ curl http://localhost/backup > my.db +``` + +Or you can open your browser to `http://localhost/backup` and it will download +automatically. + +If you want to backup to another file you can use the `Tx.CopyFile()` helper +function. + + +### Statistics + +The database keeps a running count of many of the internal operations it +performs so you can better understand what's going on. By grabbing a snapshot +of these stats at two points in time we can see what operations were performed +in that time range. + +For example, we could start a goroutine to log stats every 10 seconds: + +```go +go func() { + // Grab the initial stats. + prev := db.Stats() + + for { + // Wait for 10s. + time.Sleep(10 * time.Second) + + // Grab the current stats and diff them. + stats := db.Stats() + diff := stats.Sub(&prev) + + // Encode stats to JSON and print to STDERR. + json.NewEncoder(os.Stderr).Encode(diff) + + // Save stats for the next loop. + prev = stats + } +}() +``` + +It's also useful to pipe these stats to a service such as statsd for monitoring +or to provide an HTTP endpoint that will perform a fixed-length sample. + + +### Read-Only Mode + +Sometimes it is useful to create a shared, read-only Bolt database. To this, +set the `Options.ReadOnly` flag when opening your database. Read-only mode +uses a shared lock to allow multiple processes to read from the database but +it will block any processes from opening the database in read-write mode. + +```go +db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true}) +if err != nil { + log.Fatal(err) +} +``` + +### Mobile Use (iOS/Android) + +Bolt is able to run on mobile devices by leveraging the binding feature of the +[gomobile](https://github.com/golang/mobile) tool. Create a struct that will +contain your database logic and a reference to a `*bolt.DB` with a initializing +constructor that takes in a filepath where the database file will be stored. +Neither Android nor iOS require extra permissions or cleanup from using this method. + +```go +func NewBoltDB(filepath string) *BoltDB { + db, err := bolt.Open(filepath+"/demo.db", 0600, nil) + if err != nil { + log.Fatal(err) + } + + return &BoltDB{db} +} + +type BoltDB struct { + db *bolt.DB + ... +} + +func (b *BoltDB) Path() string { + return b.db.Path() +} + +func (b *BoltDB) Close() { + b.db.Close() +} +``` + +Database logic should be defined as methods on this wrapper struct. + +To initialize this struct from the native language (both platforms now sync +their local storage to the cloud. These snippets disable that functionality for the +database file): + +#### Android + +```java +String path; +if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){ + path = getNoBackupFilesDir().getAbsolutePath(); +} else{ + path = getFilesDir().getAbsolutePath(); +} +Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path) +``` + +#### iOS + +```objc +- (void)demo { + NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, + NSUserDomainMask, + YES) objectAtIndex:0]; + GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path); + [self addSkipBackupAttributeToItemAtPath:demo.path]; + //Some DB Logic would go here + [demo close]; +} + +- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString +{ + NSURL* URL= [NSURL fileURLWithPath: filePathString]; + assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); + + NSError *error = nil; + BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] + forKey: NSURLIsExcludedFromBackupKey error: &error]; + if(!success){ + NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); + } + return success; +} + +``` + +## Resources + +For more information on getting started with Bolt, check out the following articles: + +* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch). +* [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville + + +## Comparison with other databases + +### Postgres, MySQL, & other relational databases + +Relational databases structure data into rows and are only accessible through +the use of SQL. This approach provides flexibility in how you store and query +your data but also incurs overhead in parsing and planning SQL statements. Bolt +accesses all data by a byte slice key. This makes Bolt fast to read and write +data by key but provides no built-in support for joining values together. + +Most relational databases (with the exception of SQLite) are standalone servers +that run separately from your application. This gives your systems +flexibility to connect multiple application servers to a single database +server but also adds overhead in serializing and transporting data over the +network. Bolt runs as a library included in your application so all data access +has to go through your application's process. This brings data closer to your +application but limits multi-process access to the data. + + +### LevelDB, RocksDB + +LevelDB and its derivatives (RocksDB, HyperLevelDB) are similar to Bolt in that +they are libraries bundled into the application, however, their underlying +structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes +random writes by using a write ahead log and multi-tiered, sorted files called +SSTables. Bolt uses a B+tree internally and only a single file. Both approaches +have trade-offs. + +If you require a high random write throughput (>10,000 w/sec) or you need to use +spinning disks then LevelDB could be a good choice. If your application is +read-heavy or does a lot of range scans then Bolt could be a good choice. + +One other important consideration is that LevelDB does not have transactions. +It supports batch writing of key/values pairs and it supports read snapshots +but it will not give you the ability to do a compare-and-swap operation safely. +Bolt supports fully serializable ACID transactions. + + +### LMDB + +Bolt was originally a port of LMDB so it is architecturally similar. Both use +a B+tree, have ACID semantics with fully serializable transactions, and support +lock-free MVCC using a single writer and multiple readers. + +The two projects have somewhat diverged. LMDB heavily focuses on raw performance +while Bolt has focused on simplicity and ease of use. For example, LMDB allows +several unsafe actions such as direct writes for the sake of performance. Bolt +opts to disallow actions which can leave the database in a corrupted state. The +only exception to this in Bolt is `DB.NoSync`. + +There are also a few differences in API. LMDB requires a maximum mmap size when +opening an `mdb_env` whereas Bolt will handle incremental mmap resizing +automatically. LMDB overloads the getter and setter functions with multiple +flags whereas Bolt splits these specialized cases into their own functions. + + +## Caveats & Limitations + +It's important to pick the right tool for the job and Bolt is no exception. +Here are a few things to note when evaluating and using Bolt: + +* Bolt is good for read intensive workloads. Sequential write performance is + also fast but random writes can be slow. You can use `DB.Batch()` or add a + write-ahead log to help mitigate this issue. + +* Bolt uses a B+tree internally so there can be a lot of random page access. + SSDs provide a significant performance boost over spinning disks. + +* Try to avoid long running read transactions. Bolt uses copy-on-write so + old pages cannot be reclaimed while an old transaction is using them. + +* Byte slices returned from Bolt are only valid during a transaction. Once the + transaction has been committed or rolled back then the memory they point to + can be reused by a new page or can be unmapped from virtual memory and you'll + see an `unexpected fault address` panic when accessing it. + +* Bolt uses an exclusive write lock on the database file so it cannot be + shared by multiple processes. + +* Be careful when using `Bucket.FillPercent`. Setting a high fill percent for + buckets that have random inserts will cause your database to have very poor + page utilization. + +* Use larger buckets in general. Smaller buckets causes poor page utilization + once they become larger than the page size (typically 4KB). + +* Bulk loading a lot of random writes into a new bucket can be slow as the + page will not split until the transaction is committed. Randomly inserting + more than 100,000 key/value pairs into a single new bucket in a single + transaction is not advised. + +* Bolt uses a memory-mapped file so the underlying operating system handles the + caching of the data. Typically, the OS will cache as much of the file as it + can in memory and will release memory as needed to other processes. This means + that Bolt can show very high memory usage when working with large databases. + However, this is expected and the OS will release memory as needed. Bolt can + handle databases much larger than the available physical RAM, provided its + memory-map fits in the process virtual address space. It may be problematic + on 32-bits systems. + +* The data structures in the Bolt database are memory mapped so the data file + will be endian specific. This means that you cannot copy a Bolt file from a + little endian machine to a big endian machine and have it work. For most + users this is not a concern since most modern CPUs are little endian. + +* Because of the way pages are laid out on disk, Bolt cannot truncate data files + and return free pages back to the disk. Instead, Bolt maintains a free list + of unused pages within its data file. These free pages can be reused by later + transactions. This works well for many use cases as databases generally tend + to grow. However, it's important to note that deleting large chunks of data + will not allow you to reclaim that space on disk. + + For more information on page allocation, [see this comment][page-allocation]. + +[page-allocation]: https://github.com/boltdb/bolt/issues/308#issuecomment-74811638 + + +## Reading the Source + +Bolt is a relatively small code base (<5KLOC) for an embedded, serializable, +transactional key/value database so it can be a good starting point for people +interested in how databases work. + +The best places to start are the main entry points into Bolt: + +- `Open()` - Initializes the reference to the database. It's responsible for + creating the database if it doesn't exist, obtaining an exclusive lock on the + file, reading the meta pages, & memory-mapping the file. + +- `DB.Begin()` - Starts a read-only or read-write transaction depending on the + value of the `writable` argument. This requires briefly obtaining the "meta" + lock to keep track of open transactions. Only one read-write transaction can + exist at a time so the "rwlock" is acquired during the life of a read-write + transaction. + +- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the + arguments, a cursor is used to traverse the B+tree to the page and position + where they key & value will be written. Once the position is found, the bucket + materializes the underlying page and the page's parent pages into memory as + "nodes". These nodes are where mutations occur during read-write transactions. + These changes get flushed to disk during commit. + +- `Bucket.Get()` - Retrieves a key/value pair from a bucket. This uses a cursor + to move to the page & position of a key/value pair. During a read-only + transaction, the key and value data is returned as a direct reference to the + underlying mmap file so there's no allocation overhead. For read-write + transactions, this data may reference the mmap file or one of the in-memory + node values. + +- `Cursor` - This object is simply for traversing the B+tree of on-disk pages + or in-memory nodes. It can seek to a specific key, move to the first or last + value, or it can move forward or backward. The cursor handles the movement up + and down the B+tree transparently to the end user. + +- `Tx.Commit()` - Converts the in-memory dirty nodes and the list of free pages + into pages to be written to disk. Writing to disk then occurs in two phases. + First, the dirty pages are written to disk and an `fsync()` occurs. Second, a + new meta page with an incremented transaction ID is written and another + `fsync()` occurs. This two phase write ensures that partially written data + pages are ignored in the event of a crash since the meta page pointing to them + is never written. Partially written meta pages are invalidated because they + are written with a checksum. + +If you have additional notes that could be helpful for others, please submit +them via pull request. + + +## Other Projects Using Bolt + +Below is a list of public, open source projects that use Bolt: + +* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend. +* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside. +* [bolter](https://github.com/hasit/bolter) - Command-line app for viewing BoltDB file in your terminal. +* [boltcli](https://github.com/spacewander/boltcli) - the redis-cli for boltdb with Lua script support. +* [BoltHold](https://github.com/timshannon/bolthold) - An embeddable NoSQL store for Go types built on BoltDB +* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt. +* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. +* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files. +* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. +* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet. +* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining + simple tx and key scans. +* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. +* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations. +* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware. +* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. +* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency. +* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems. +* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka. +* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data. +* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. +* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB. +* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter. +* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains +* [gokv](https://github.com/philippgille/gokv) - Simple key-value store abstraction and implementations for Go (Redis, Consul, etcd, bbolt, BadgerDB, LevelDB, Memcached, DynamoDB, S3, PostgreSQL, MongoDB, CockroachDB and many more) +* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin". +* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics. +* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters. +* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed. +* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies +* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs. +* [Key Value Access Langusge (KVAL)](https://github.com/kval-access-language) - A proposed grammar for key-value datastores offering a bbolt binding. +* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage. +* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. +* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets. +* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite. +* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files. +* [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage. +* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard. +* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site. +* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system. +* [Rain](https://github.com/cenkalti/rain) - BitTorrent client and library. +* [reef-pi](https://github.com/reef-pi/reef-pi) - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi. +* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service +* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read. +* [stow](https://github.com/djherbis/stow) - a persistence manager for objects + backed by boltdb. +* [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB. +* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings. +* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics. +* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects. +* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. +* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development. +* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday. + +If you are using Bolt in a project please send a pull request to add it to the list. diff --git a/vendor/etcd.io/bbolt/allocate_test.go b/vendor/etcd.io/bbolt/allocate_test.go new file mode 100644 index 0000000..98b06b4 --- /dev/null +++ b/vendor/etcd.io/bbolt/allocate_test.go @@ -0,0 +1,31 @@ +package bbolt + +import ( + "testing" +) + +func TestTx_allocatePageStats(t *testing.T) { + f := newTestFreelist() + ids := []pgid{2, 3} + f.readIDs(ids) + + tx := &Tx{ + db: &DB{ + freelist: f, + pageSize: defaultPageSize, + }, + meta: &meta{}, + pages: make(map[pgid]*page), + } + + prePageCnt := tx.Stats().PageCount + allocateCnt := f.free_count() + + if _, err := tx.allocate(allocateCnt); err != nil { + t.Fatal(err) + } + + if tx.Stats().PageCount != prePageCnt+allocateCnt { + t.Errorf("Allocated %d but got %d page in stats", allocateCnt, tx.Stats().PageCount) + } +} diff --git a/vendor/etcd.io/bbolt/bolt_386.go b/vendor/etcd.io/bbolt/bolt_386.go new file mode 100644 index 0000000..aee2596 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_386.go @@ -0,0 +1,7 @@ +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0x7FFFFFFF // 2GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_amd64.go b/vendor/etcd.io/bbolt/bolt_amd64.go new file mode 100644 index 0000000..5dd8f3f --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_amd64.go @@ -0,0 +1,7 @@ +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_arm.go b/vendor/etcd.io/bbolt/bolt_arm.go new file mode 100644 index 0000000..aee2596 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_arm.go @@ -0,0 +1,7 @@ +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0x7FFFFFFF // 2GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_arm64.go b/vendor/etcd.io/bbolt/bolt_arm64.go new file mode 100644 index 0000000..810dfd5 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_arm64.go @@ -0,0 +1,9 @@ +// +build arm64 + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_linux.go b/vendor/etcd.io/bbolt/bolt_linux.go new file mode 100644 index 0000000..7707bca --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_linux.go @@ -0,0 +1,10 @@ +package bbolt + +import ( + "syscall" +) + +// fdatasync flushes written data to a file descriptor. +func fdatasync(db *DB) error { + return syscall.Fdatasync(int(db.file.Fd())) +} diff --git a/vendor/etcd.io/bbolt/bolt_mips64x.go b/vendor/etcd.io/bbolt/bolt_mips64x.go new file mode 100644 index 0000000..dd8ffe1 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_mips64x.go @@ -0,0 +1,9 @@ +// +build mips64 mips64le + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0x8000000000 // 512GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_mipsx.go b/vendor/etcd.io/bbolt/bolt_mipsx.go new file mode 100644 index 0000000..a669703 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_mipsx.go @@ -0,0 +1,9 @@ +// +build mips mipsle + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0x40000000 // 1GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_openbsd.go b/vendor/etcd.io/bbolt/bolt_openbsd.go new file mode 100644 index 0000000..d7f5035 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_openbsd.go @@ -0,0 +1,27 @@ +package bbolt + +import ( + "syscall" + "unsafe" +) + +const ( + msAsync = 1 << iota // perform asynchronous writes + msSync // perform synchronous writes + msInvalidate // invalidate cached data +) + +func msync(db *DB) error { + _, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate) + if errno != 0 { + return errno + } + return nil +} + +func fdatasync(db *DB) error { + if db.data != nil { + return msync(db) + } + return db.file.Sync() +} diff --git a/vendor/etcd.io/bbolt/bolt_ppc.go b/vendor/etcd.io/bbolt/bolt_ppc.go new file mode 100644 index 0000000..84e545e --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_ppc.go @@ -0,0 +1,9 @@ +// +build ppc + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0x7FFFFFFF // 2GB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0xFFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_ppc64.go b/vendor/etcd.io/bbolt/bolt_ppc64.go new file mode 100644 index 0000000..a761209 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_ppc64.go @@ -0,0 +1,9 @@ +// +build ppc64 + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_ppc64le.go b/vendor/etcd.io/bbolt/bolt_ppc64le.go new file mode 100644 index 0000000..c830f2f --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_ppc64le.go @@ -0,0 +1,9 @@ +// +build ppc64le + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_riscv64.go b/vendor/etcd.io/bbolt/bolt_riscv64.go new file mode 100644 index 0000000..c967613 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_riscv64.go @@ -0,0 +1,9 @@ +// +build riscv64 + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_s390x.go b/vendor/etcd.io/bbolt/bolt_s390x.go new file mode 100644 index 0000000..ff2a560 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_s390x.go @@ -0,0 +1,9 @@ +// +build s390x + +package bbolt + +// maxMapSize represents the largest mmap size supported by Bolt. +const maxMapSize = 0xFFFFFFFFFFFF // 256TB + +// maxAllocSize is the size used when creating array pointers. +const maxAllocSize = 0x7FFFFFFF diff --git a/vendor/etcd.io/bbolt/bolt_unix.go b/vendor/etcd.io/bbolt/bolt_unix.go new file mode 100644 index 0000000..2938fed --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_unix.go @@ -0,0 +1,93 @@ +// +build !windows,!plan9,!solaris,!aix + +package bbolt + +import ( + "fmt" + "syscall" + "time" + "unsafe" +) + +// flock acquires an advisory lock on a file descriptor. +func flock(db *DB, exclusive bool, timeout time.Duration) error { + var t time.Time + if timeout != 0 { + t = time.Now() + } + fd := db.file.Fd() + flag := syscall.LOCK_NB + if exclusive { + flag |= syscall.LOCK_EX + } else { + flag |= syscall.LOCK_SH + } + for { + // Attempt to obtain an exclusive lock. + err := syscall.Flock(int(fd), flag) + if err == nil { + return nil + } else if err != syscall.EWOULDBLOCK { + return err + } + + // If we timed out then return an error. + if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { + return ErrTimeout + } + + // Wait for a bit and try again. + time.Sleep(flockRetryTimeout) + } +} + +// funlock releases an advisory lock on a file descriptor. +func funlock(db *DB) error { + return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN) +} + +// mmap memory maps a DB's data file. +func mmap(db *DB, sz int) error { + // Map the data file to memory. + b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags) + if err != nil { + return err + } + + // Advise the kernel that the mmap is accessed randomly. + err = madvise(b, syscall.MADV_RANDOM) + if err != nil && err != syscall.ENOSYS { + // Ignore not implemented error in kernel because it still works. + return fmt.Errorf("madvise: %s", err) + } + + // Save the original byte slice and convert to a byte array pointer. + db.dataref = b + db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) + db.datasz = sz + return nil +} + +// munmap unmaps a DB's data file from memory. +func munmap(db *DB) error { + // Ignore the unmap if we have no mapped data. + if db.dataref == nil { + return nil + } + + // Unmap using the original byte slice. + err := syscall.Munmap(db.dataref) + db.dataref = nil + db.data = nil + db.datasz = 0 + return err +} + +// NOTE: This function is copied from stdlib because it is not available on darwin. +func madvise(b []byte, advice int) (err error) { + _, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice)) + if e1 != 0 { + err = e1 + } + return +} diff --git a/vendor/etcd.io/bbolt/bolt_unix_aix.go b/vendor/etcd.io/bbolt/bolt_unix_aix.go new file mode 100644 index 0000000..a64c16f --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_unix_aix.go @@ -0,0 +1,90 @@ +// +build aix + +package bbolt + +import ( + "fmt" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +// flock acquires an advisory lock on a file descriptor. +func flock(db *DB, exclusive bool, timeout time.Duration) error { + var t time.Time + if timeout != 0 { + t = time.Now() + } + fd := db.file.Fd() + var lockType int16 + if exclusive { + lockType = syscall.F_WRLCK + } else { + lockType = syscall.F_RDLCK + } + for { + // Attempt to obtain an exclusive lock. + lock := syscall.Flock_t{Type: lockType} + err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock) + if err == nil { + return nil + } else if err != syscall.EAGAIN { + return err + } + + // If we timed out then return an error. + if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { + return ErrTimeout + } + + // Wait for a bit and try again. + time.Sleep(flockRetryTimeout) + } +} + +// funlock releases an advisory lock on a file descriptor. +func funlock(db *DB) error { + var lock syscall.Flock_t + lock.Start = 0 + lock.Len = 0 + lock.Type = syscall.F_UNLCK + lock.Whence = 0 + return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock) +} + +// mmap memory maps a DB's data file. +func mmap(db *DB, sz int) error { + // Map the data file to memory. + b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags) + if err != nil { + return err + } + + // Advise the kernel that the mmap is accessed randomly. + if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil { + return fmt.Errorf("madvise: %s", err) + } + + // Save the original byte slice and convert to a byte array pointer. + db.dataref = b + db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) + db.datasz = sz + return nil +} + +// munmap unmaps a DB's data file from memory. +func munmap(db *DB) error { + // Ignore the unmap if we have no mapped data. + if db.dataref == nil { + return nil + } + + // Unmap using the original byte slice. + err := unix.Munmap(db.dataref) + db.dataref = nil + db.data = nil + db.datasz = 0 + return err +} diff --git a/vendor/etcd.io/bbolt/bolt_unix_solaris.go b/vendor/etcd.io/bbolt/bolt_unix_solaris.go new file mode 100644 index 0000000..babad65 --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_unix_solaris.go @@ -0,0 +1,88 @@ +package bbolt + +import ( + "fmt" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +// flock acquires an advisory lock on a file descriptor. +func flock(db *DB, exclusive bool, timeout time.Duration) error { + var t time.Time + if timeout != 0 { + t = time.Now() + } + fd := db.file.Fd() + var lockType int16 + if exclusive { + lockType = syscall.F_WRLCK + } else { + lockType = syscall.F_RDLCK + } + for { + // Attempt to obtain an exclusive lock. + lock := syscall.Flock_t{Type: lockType} + err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock) + if err == nil { + return nil + } else if err != syscall.EAGAIN { + return err + } + + // If we timed out then return an error. + if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { + return ErrTimeout + } + + // Wait for a bit and try again. + time.Sleep(flockRetryTimeout) + } +} + +// funlock releases an advisory lock on a file descriptor. +func funlock(db *DB) error { + var lock syscall.Flock_t + lock.Start = 0 + lock.Len = 0 + lock.Type = syscall.F_UNLCK + lock.Whence = 0 + return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock) +} + +// mmap memory maps a DB's data file. +func mmap(db *DB, sz int) error { + // Map the data file to memory. + b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags) + if err != nil { + return err + } + + // Advise the kernel that the mmap is accessed randomly. + if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil { + return fmt.Errorf("madvise: %s", err) + } + + // Save the original byte slice and convert to a byte array pointer. + db.dataref = b + db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0])) + db.datasz = sz + return nil +} + +// munmap unmaps a DB's data file from memory. +func munmap(db *DB) error { + // Ignore the unmap if we have no mapped data. + if db.dataref == nil { + return nil + } + + // Unmap using the original byte slice. + err := unix.Munmap(db.dataref) + db.dataref = nil + db.data = nil + db.datasz = 0 + return err +} diff --git a/vendor/etcd.io/bbolt/bolt_windows.go b/vendor/etcd.io/bbolt/bolt_windows.go new file mode 100644 index 0000000..fca178b --- /dev/null +++ b/vendor/etcd.io/bbolt/bolt_windows.go @@ -0,0 +1,141 @@ +package bbolt + +import ( + "fmt" + "os" + "syscall" + "time" + "unsafe" +) + +// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1 +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + procLockFileEx = modkernel32.NewProc("LockFileEx") + procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") +) + +const ( + // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + flagLockExclusive = 2 + flagLockFailImmediately = 1 + + // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + errLockViolation syscall.Errno = 0x21 +) + +func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { + r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol))) + if r == 0 { + return err + } + return nil +} + +func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { + r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0) + if r == 0 { + return err + } + return nil +} + +// fdatasync flushes written data to a file descriptor. +func fdatasync(db *DB) error { + return db.file.Sync() +} + +// flock acquires an advisory lock on a file descriptor. +func flock(db *DB, exclusive bool, timeout time.Duration) error { + var t time.Time + if timeout != 0 { + t = time.Now() + } + var flag uint32 = flagLockFailImmediately + if exclusive { + flag |= flagLockExclusive + } + for { + // Fix for https://github.com/etcd-io/bbolt/issues/121. Use byte-range + // -1..0 as the lock on the database file. + var m1 uint32 = (1 << 32) - 1 // -1 in a uint32 + err := lockFileEx(syscall.Handle(db.file.Fd()), flag, 0, 1, 0, &syscall.Overlapped{ + Offset: m1, + OffsetHigh: m1, + }) + + if err == nil { + return nil + } else if err != errLockViolation { + return err + } + + // If we timed oumercit then return an error. + if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout { + return ErrTimeout + } + + // Wait for a bit and try again. + time.Sleep(flockRetryTimeout) + } +} + +// funlock releases an advisory lock on a file descriptor. +func funlock(db *DB) error { + var m1 uint32 = (1 << 32) - 1 // -1 in a uint32 + err := unlockFileEx(syscall.Handle(db.file.Fd()), 0, 1, 0, &syscall.Overlapped{ + Offset: m1, + OffsetHigh: m1, + }) + return err +} + +// mmap memory maps a DB's data file. +// Based on: https://github.com/edsrzf/mmap-go +func mmap(db *DB, sz int) error { + if !db.readOnly { + // Truncate the database to the size of the mmap. + if err := db.file.Truncate(int64(sz)); err != nil { + return fmt.Errorf("truncate: %s", err) + } + } + + // Open a file mapping handle. + sizelo := uint32(sz >> 32) + sizehi := uint32(sz) & 0xffffffff + h, errno := syscall.CreateFileMapping(syscall.Handle(db.file.Fd()), nil, syscall.PAGE_READONLY, sizelo, sizehi, nil) + if h == 0 { + return os.NewSyscallError("CreateFileMapping", errno) + } + + // Create the memory map. + addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(sz)) + if addr == 0 { + return os.NewSyscallError("MapViewOfFile", errno) + } + + // Close mapping handle. + if err := syscall.CloseHandle(syscall.Handle(h)); err != nil { + return os.NewSyscallError("CloseHandle", err) + } + + // Convert to a byte array. + db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr))) + db.datasz = sz + + return nil +} + +// munmap unmaps a pointer from a file. +// Based on: https://github.com/edsrzf/mmap-go +func munmap(db *DB) error { + if db.data == nil { + return nil + } + + addr := (uintptr)(unsafe.Pointer(&db.data[0])) + if err := syscall.UnmapViewOfFile(addr); err != nil { + return os.NewSyscallError("UnmapViewOfFile", err) + } + return nil +} diff --git a/vendor/etcd.io/bbolt/boltsync_unix.go b/vendor/etcd.io/bbolt/boltsync_unix.go new file mode 100644 index 0000000..9587afe --- /dev/null +++ b/vendor/etcd.io/bbolt/boltsync_unix.go @@ -0,0 +1,8 @@ +// +build !windows,!plan9,!linux,!openbsd + +package bbolt + +// fdatasync flushes written data to a file descriptor. +func fdatasync(db *DB) error { + return db.file.Sync() +} diff --git a/vendor/etcd.io/bbolt/bucket.go b/vendor/etcd.io/bbolt/bucket.go new file mode 100644 index 0000000..d8750b1 --- /dev/null +++ b/vendor/etcd.io/bbolt/bucket.go @@ -0,0 +1,777 @@ +package bbolt + +import ( + "bytes" + "fmt" + "unsafe" +) + +const ( + // MaxKeySize is the maximum length of a key, in bytes. + MaxKeySize = 32768 + + // MaxValueSize is the maximum length of a value, in bytes. + MaxValueSize = (1 << 31) - 2 +) + +const bucketHeaderSize = int(unsafe.Sizeof(bucket{})) + +const ( + minFillPercent = 0.1 + maxFillPercent = 1.0 +) + +// DefaultFillPercent is the percentage that split pages are filled. +// This value can be changed by setting Bucket.FillPercent. +const DefaultFillPercent = 0.5 + +// Bucket represents a collection of key/value pairs inside the database. +type Bucket struct { + *bucket + tx *Tx // the associated transaction + buckets map[string]*Bucket // subbucket cache + page *page // inline page reference + rootNode *node // materialized node for the root page. + nodes map[pgid]*node // node cache + + // Sets the threshold for filling nodes when they split. By default, + // the bucket will fill to 50% but it can be useful to increase this + // amount if you know that your write workloads are mostly append-only. + // + // This is non-persisted across transactions so it must be set in every Tx. + FillPercent float64 +} + +// bucket represents the on-file representation of a bucket. +// This is stored as the "value" of a bucket key. If the bucket is small enough, +// then its root page can be stored inline in the "value", after the bucket +// header. In the case of inline buckets, the "root" will be 0. +type bucket struct { + root pgid // page id of the bucket's root-level page + sequence uint64 // monotonically incrementing, used by NextSequence() +} + +// newBucket returns a new bucket associated with a transaction. +func newBucket(tx *Tx) Bucket { + var b = Bucket{tx: tx, FillPercent: DefaultFillPercent} + if tx.writable { + b.buckets = make(map[string]*Bucket) + b.nodes = make(map[pgid]*node) + } + return b +} + +// Tx returns the tx of the bucket. +func (b *Bucket) Tx() *Tx { + return b.tx +} + +// Root returns the root of the bucket. +func (b *Bucket) Root() pgid { + return b.root +} + +// Writable returns whether the bucket is writable. +func (b *Bucket) Writable() bool { + return b.tx.writable +} + +// Cursor creates a cursor associated with the bucket. +// The cursor is only valid as long as the transaction is open. +// Do not use a cursor after the transaction is closed. +func (b *Bucket) Cursor() *Cursor { + // Update transaction statistics. + b.tx.stats.CursorCount++ + + // Allocate and return a cursor. + return &Cursor{ + bucket: b, + stack: make([]elemRef, 0), + } +} + +// Bucket retrieves a nested bucket by name. +// Returns nil if the bucket does not exist. +// The bucket instance is only valid for the lifetime of the transaction. +func (b *Bucket) Bucket(name []byte) *Bucket { + if b.buckets != nil { + if child := b.buckets[string(name)]; child != nil { + return child + } + } + + // Move cursor to key. + c := b.Cursor() + k, v, flags := c.seek(name) + + // Return nil if the key doesn't exist or it is not a bucket. + if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 { + return nil + } + + // Otherwise create a bucket and cache it. + var child = b.openBucket(v) + if b.buckets != nil { + b.buckets[string(name)] = child + } + + return child +} + +// Helper method that re-interprets a sub-bucket value +// from a parent into a Bucket +func (b *Bucket) openBucket(value []byte) *Bucket { + var child = newBucket(b.tx) + + // Unaligned access requires a copy to be made. + const unalignedMask = unsafe.Alignof(struct { + bucket + page + }{}) - 1 + unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0 + if unaligned { + value = cloneBytes(value) + } + + // If this is a writable transaction then we need to copy the bucket entry. + // Read-only transactions can point directly at the mmap entry. + if b.tx.writable && !unaligned { + child.bucket = &bucket{} + *child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) + } else { + child.bucket = (*bucket)(unsafe.Pointer(&value[0])) + } + + // Save a reference to the inline page if the bucket is inline. + if child.root == 0 { + child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) + } + + return &child +} + +// CreateBucket creates a new bucket at the given key and returns the new bucket. +// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long. +// The bucket instance is only valid for the lifetime of the transaction. +func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) { + if b.tx.db == nil { + return nil, ErrTxClosed + } else if !b.tx.writable { + return nil, ErrTxNotWritable + } else if len(key) == 0 { + return nil, ErrBucketNameRequired + } + + // Move cursor to correct position. + c := b.Cursor() + k, _, flags := c.seek(key) + + // Return an error if there is an existing key. + if bytes.Equal(key, k) { + if (flags & bucketLeafFlag) != 0 { + return nil, ErrBucketExists + } + return nil, ErrIncompatibleValue + } + + // Create empty, inline bucket. + var bucket = Bucket{ + bucket: &bucket{}, + rootNode: &node{isLeaf: true}, + FillPercent: DefaultFillPercent, + } + var value = bucket.write() + + // Insert into node. + key = cloneBytes(key) + c.node().put(key, key, value, 0, bucketLeafFlag) + + // Since subbuckets are not allowed on inline buckets, we need to + // dereference the inline page, if it exists. This will cause the bucket + // to be treated as a regular, non-inline bucket for the rest of the tx. + b.page = nil + + return b.Bucket(key), nil +} + +// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it. +// Returns an error if the bucket name is blank, or if the bucket name is too long. +// The bucket instance is only valid for the lifetime of the transaction. +func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) { + child, err := b.CreateBucket(key) + if err == ErrBucketExists { + return b.Bucket(key), nil + } else if err != nil { + return nil, err + } + return child, nil +} + +// DeleteBucket deletes a bucket at the given key. +// Returns an error if the bucket does not exist, or if the key represents a non-bucket value. +func (b *Bucket) DeleteBucket(key []byte) error { + if b.tx.db == nil { + return ErrTxClosed + } else if !b.Writable() { + return ErrTxNotWritable + } + + // Move cursor to correct position. + c := b.Cursor() + k, _, flags := c.seek(key) + + // Return an error if bucket doesn't exist or is not a bucket. + if !bytes.Equal(key, k) { + return ErrBucketNotFound + } else if (flags & bucketLeafFlag) == 0 { + return ErrIncompatibleValue + } + + // Recursively delete all child buckets. + child := b.Bucket(key) + err := child.ForEach(func(k, v []byte) error { + if _, _, childFlags := child.Cursor().seek(k); (childFlags & bucketLeafFlag) != 0 { + if err := child.DeleteBucket(k); err != nil { + return fmt.Errorf("delete bucket: %s", err) + } + } + return nil + }) + if err != nil { + return err + } + + // Remove cached copy. + delete(b.buckets, string(key)) + + // Release all bucket pages to freelist. + child.nodes = nil + child.rootNode = nil + child.free() + + // Delete the node if we have a matching key. + c.node().del(key) + + return nil +} + +// Get retrieves the value for a key in the bucket. +// Returns a nil value if the key does not exist or if the key is a nested bucket. +// The returned value is only valid for the life of the transaction. +func (b *Bucket) Get(key []byte) []byte { + k, v, flags := b.Cursor().seek(key) + + // Return nil if this is a bucket. + if (flags & bucketLeafFlag) != 0 { + return nil + } + + // If our target node isn't the same key as what's passed in then return nil. + if !bytes.Equal(key, k) { + return nil + } + return v +} + +// Put sets the value for a key in the bucket. +// If the key exist then its previous value will be overwritten. +// Supplied value must remain valid for the life of the transaction. +// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large. +func (b *Bucket) Put(key []byte, value []byte) error { + if b.tx.db == nil { + return ErrTxClosed + } else if !b.Writable() { + return ErrTxNotWritable + } else if len(key) == 0 { + return ErrKeyRequired + } else if len(key) > MaxKeySize { + return ErrKeyTooLarge + } else if int64(len(value)) > MaxValueSize { + return ErrValueTooLarge + } + + // Move cursor to correct position. + c := b.Cursor() + k, _, flags := c.seek(key) + + // Return an error if there is an existing key with a bucket value. + if bytes.Equal(key, k) && (flags&bucketLeafFlag) != 0 { + return ErrIncompatibleValue + } + + // Insert into node. + key = cloneBytes(key) + c.node().put(key, key, value, 0, 0) + + return nil +} + +// Delete removes a key from the bucket. +// If the key does not exist then nothing is done and a nil error is returned. +// Returns an error if the bucket was created from a read-only transaction. +func (b *Bucket) Delete(key []byte) error { + if b.tx.db == nil { + return ErrTxClosed + } else if !b.Writable() { + return ErrTxNotWritable + } + + // Move cursor to correct position. + c := b.Cursor() + k, _, flags := c.seek(key) + + // Return nil if the key doesn't exist. + if !bytes.Equal(key, k) { + return nil + } + + // Return an error if there is already existing bucket value. + if (flags & bucketLeafFlag) != 0 { + return ErrIncompatibleValue + } + + // Delete the node if we have a matching key. + c.node().del(key) + + return nil +} + +// Sequence returns the current integer for the bucket without incrementing it. +func (b *Bucket) Sequence() uint64 { return b.bucket.sequence } + +// SetSequence updates the sequence number for the bucket. +func (b *Bucket) SetSequence(v uint64) error { + if b.tx.db == nil { + return ErrTxClosed + } else if !b.Writable() { + return ErrTxNotWritable + } + + // Materialize the root node if it hasn't been already so that the + // bucket will be saved during commit. + if b.rootNode == nil { + _ = b.node(b.root, nil) + } + + // Increment and return the sequence. + b.bucket.sequence = v + return nil +} + +// NextSequence returns an autoincrementing integer for the bucket. +func (b *Bucket) NextSequence() (uint64, error) { + if b.tx.db == nil { + return 0, ErrTxClosed + } else if !b.Writable() { + return 0, ErrTxNotWritable + } + + // Materialize the root node if it hasn't been already so that the + // bucket will be saved during commit. + if b.rootNode == nil { + _ = b.node(b.root, nil) + } + + // Increment and return the sequence. + b.bucket.sequence++ + return b.bucket.sequence, nil +} + +// ForEach executes a function for each key/value pair in a bucket. +// If the provided function returns an error then the iteration is stopped and +// the error is returned to the caller. The provided function must not modify +// the bucket; this will result in undefined behavior. +func (b *Bucket) ForEach(fn func(k, v []byte) error) error { + if b.tx.db == nil { + return ErrTxClosed + } + c := b.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + if err := fn(k, v); err != nil { + return err + } + } + return nil +} + +// Stat returns stats on a bucket. +func (b *Bucket) Stats() BucketStats { + var s, subStats BucketStats + pageSize := b.tx.db.pageSize + s.BucketN += 1 + if b.root == 0 { + s.InlineBucketN += 1 + } + b.forEachPage(func(p *page, depth int) { + if (p.flags & leafPageFlag) != 0 { + s.KeyN += int(p.count) + + // used totals the used bytes for the page + used := pageHeaderSize + + if p.count != 0 { + // If page has any elements, add all element headers. + used += leafPageElementSize * uintptr(p.count-1) + + // Add all element key, value sizes. + // The computation takes advantage of the fact that the position + // of the last element's key/value equals to the total of the sizes + // of all previous elements' keys and values. + // It also includes the last element's header. + lastElement := p.leafPageElement(p.count - 1) + used += uintptr(lastElement.pos + lastElement.ksize + lastElement.vsize) + } + + if b.root == 0 { + // For inlined bucket just update the inline stats + s.InlineBucketInuse += int(used) + } else { + // For non-inlined bucket update all the leaf stats + s.LeafPageN++ + s.LeafInuse += int(used) + s.LeafOverflowN += int(p.overflow) + + // Collect stats from sub-buckets. + // Do that by iterating over all element headers + // looking for the ones with the bucketLeafFlag. + for i := uint16(0); i < p.count; i++ { + e := p.leafPageElement(i) + if (e.flags & bucketLeafFlag) != 0 { + // For any bucket element, open the element value + // and recursively call Stats on the contained bucket. + subStats.Add(b.openBucket(e.value()).Stats()) + } + } + } + } else if (p.flags & branchPageFlag) != 0 { + s.BranchPageN++ + lastElement := p.branchPageElement(p.count - 1) + + // used totals the used bytes for the page + // Add header and all element headers. + used := pageHeaderSize + (branchPageElementSize * uintptr(p.count-1)) + + // Add size of all keys and values. + // Again, use the fact that last element's position equals to + // the total of key, value sizes of all previous elements. + used += uintptr(lastElement.pos + lastElement.ksize) + s.BranchInuse += int(used) + s.BranchOverflowN += int(p.overflow) + } + + // Keep track of maximum page depth. + if depth+1 > s.Depth { + s.Depth = (depth + 1) + } + }) + + // Alloc stats can be computed from page counts and pageSize. + s.BranchAlloc = (s.BranchPageN + s.BranchOverflowN) * pageSize + s.LeafAlloc = (s.LeafPageN + s.LeafOverflowN) * pageSize + + // Add the max depth of sub-buckets to get total nested depth. + s.Depth += subStats.Depth + // Add the stats for all sub-buckets + s.Add(subStats) + return s +} + +// forEachPage iterates over every page in a bucket, including inline pages. +func (b *Bucket) forEachPage(fn func(*page, int)) { + // If we have an inline page then just use that. + if b.page != nil { + fn(b.page, 0) + return + } + + // Otherwise traverse the page hierarchy. + b.tx.forEachPage(b.root, 0, fn) +} + +// forEachPageNode iterates over every page (or node) in a bucket. +// This also includes inline pages. +func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) { + // If we have an inline page or root node then just use that. + if b.page != nil { + fn(b.page, nil, 0) + return + } + b._forEachPageNode(b.root, 0, fn) +} + +func (b *Bucket) _forEachPageNode(pgid pgid, depth int, fn func(*page, *node, int)) { + var p, n = b.pageNode(pgid) + + // Execute function. + fn(p, n, depth) + + // Recursively loop over children. + if p != nil { + if (p.flags & branchPageFlag) != 0 { + for i := 0; i < int(p.count); i++ { + elem := p.branchPageElement(uint16(i)) + b._forEachPageNode(elem.pgid, depth+1, fn) + } + } + } else { + if !n.isLeaf { + for _, inode := range n.inodes { + b._forEachPageNode(inode.pgid, depth+1, fn) + } + } + } +} + +// spill writes all the nodes for this bucket to dirty pages. +func (b *Bucket) spill() error { + // Spill all child buckets first. + for name, child := range b.buckets { + // If the child bucket is small enough and it has no child buckets then + // write it inline into the parent bucket's page. Otherwise spill it + // like a normal bucket and make the parent value a pointer to the page. + var value []byte + if child.inlineable() { + child.free() + value = child.write() + } else { + if err := child.spill(); err != nil { + return err + } + + // Update the child bucket header in this bucket. + value = make([]byte, unsafe.Sizeof(bucket{})) + var bucket = (*bucket)(unsafe.Pointer(&value[0])) + *bucket = *child.bucket + } + + // Skip writing the bucket if there are no materialized nodes. + if child.rootNode == nil { + continue + } + + // Update parent node. + var c = b.Cursor() + k, _, flags := c.seek([]byte(name)) + if !bytes.Equal([]byte(name), k) { + panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k)) + } + if flags&bucketLeafFlag == 0 { + panic(fmt.Sprintf("unexpected bucket header flag: %x", flags)) + } + c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag) + } + + // Ignore if there's not a materialized root node. + if b.rootNode == nil { + return nil + } + + // Spill nodes. + if err := b.rootNode.spill(); err != nil { + return err + } + b.rootNode = b.rootNode.root() + + // Update the root node for this bucket. + if b.rootNode.pgid >= b.tx.meta.pgid { + panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid)) + } + b.root = b.rootNode.pgid + + return nil +} + +// inlineable returns true if a bucket is small enough to be written inline +// and if it contains no subbuckets. Otherwise returns false. +func (b *Bucket) inlineable() bool { + var n = b.rootNode + + // Bucket must only contain a single leaf node. + if n == nil || !n.isLeaf { + return false + } + + // Bucket is not inlineable if it contains subbuckets or if it goes beyond + // our threshold for inline bucket size. + var size = pageHeaderSize + for _, inode := range n.inodes { + size += leafPageElementSize + uintptr(len(inode.key)) + uintptr(len(inode.value)) + + if inode.flags&bucketLeafFlag != 0 { + return false + } else if size > b.maxInlineBucketSize() { + return false + } + } + + return true +} + +// Returns the maximum total size of a bucket to make it a candidate for inlining. +func (b *Bucket) maxInlineBucketSize() uintptr { + return uintptr(b.tx.db.pageSize / 4) +} + +// write allocates and writes a bucket to a byte slice. +func (b *Bucket) write() []byte { + // Allocate the appropriate size. + var n = b.rootNode + var value = make([]byte, bucketHeaderSize+n.size()) + + // Write a bucket header. + var bucket = (*bucket)(unsafe.Pointer(&value[0])) + *bucket = *b.bucket + + // Convert byte slice to a fake page and write the root node. + var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize])) + n.write(p) + + return value +} + +// rebalance attempts to balance all nodes. +func (b *Bucket) rebalance() { + for _, n := range b.nodes { + n.rebalance() + } + for _, child := range b.buckets { + child.rebalance() + } +} + +// node creates a node from a page and associates it with a given parent. +func (b *Bucket) node(pgid pgid, parent *node) *node { + _assert(b.nodes != nil, "nodes map expected") + + // Retrieve node if it's already been created. + if n := b.nodes[pgid]; n != nil { + return n + } + + // Otherwise create a node and cache it. + n := &node{bucket: b, parent: parent} + if parent == nil { + b.rootNode = n + } else { + parent.children = append(parent.children, n) + } + + // Use the inline page if this is an inline bucket. + var p = b.page + if p == nil { + p = b.tx.page(pgid) + } + + // Read the page into the node and cache it. + n.read(p) + b.nodes[pgid] = n + + // Update statistics. + b.tx.stats.NodeCount++ + + return n +} + +// free recursively frees all pages in the bucket. +func (b *Bucket) free() { + if b.root == 0 { + return + } + + var tx = b.tx + b.forEachPageNode(func(p *page, n *node, _ int) { + if p != nil { + tx.db.freelist.free(tx.meta.txid, p) + } else { + n.free() + } + }) + b.root = 0 +} + +// dereference removes all references to the old mmap. +func (b *Bucket) dereference() { + if b.rootNode != nil { + b.rootNode.root().dereference() + } + + for _, child := range b.buckets { + child.dereference() + } +} + +// pageNode returns the in-memory node, if it exists. +// Otherwise returns the underlying page. +func (b *Bucket) pageNode(id pgid) (*page, *node) { + // Inline buckets have a fake page embedded in their value so treat them + // differently. We'll return the rootNode (if available) or the fake page. + if b.root == 0 { + if id != 0 { + panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id)) + } + if b.rootNode != nil { + return nil, b.rootNode + } + return b.page, nil + } + + // Check the node cache for non-inline buckets. + if b.nodes != nil { + if n := b.nodes[id]; n != nil { + return nil, n + } + } + + // Finally lookup the page from the transaction if no node is materialized. + return b.tx.page(id), nil +} + +// BucketStats records statistics about resources used by a bucket. +type BucketStats struct { + // Page count statistics. + BranchPageN int // number of logical branch pages + BranchOverflowN int // number of physical branch overflow pages + LeafPageN int // number of logical leaf pages + LeafOverflowN int // number of physical leaf overflow pages + + // Tree statistics. + KeyN int // number of keys/value pairs + Depth int // number of levels in B+tree + + // Page size utilization. + BranchAlloc int // bytes allocated for physical branch pages + BranchInuse int // bytes actually used for branch data + LeafAlloc int // bytes allocated for physical leaf pages + LeafInuse int // bytes actually used for leaf data + + // Bucket statistics + BucketN int // total number of buckets including the top bucket + InlineBucketN int // total number on inlined buckets + InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse) +} + +func (s *BucketStats) Add(other BucketStats) { + s.BranchPageN += other.BranchPageN + s.BranchOverflowN += other.BranchOverflowN + s.LeafPageN += other.LeafPageN + s.LeafOverflowN += other.LeafOverflowN + s.KeyN += other.KeyN + if s.Depth < other.Depth { + s.Depth = other.Depth + } + s.BranchAlloc += other.BranchAlloc + s.BranchInuse += other.BranchInuse + s.LeafAlloc += other.LeafAlloc + s.LeafInuse += other.LeafInuse + + s.BucketN += other.BucketN + s.InlineBucketN += other.InlineBucketN + s.InlineBucketInuse += other.InlineBucketInuse +} + +// cloneBytes returns a copy of a given slice. +func cloneBytes(v []byte) []byte { + var clone = make([]byte, len(v)) + copy(clone, v) + return clone +} diff --git a/vendor/etcd.io/bbolt/bucket_test.go b/vendor/etcd.io/bbolt/bucket_test.go new file mode 100644 index 0000000..e48204b --- /dev/null +++ b/vendor/etcd.io/bbolt/bucket_test.go @@ -0,0 +1,1959 @@ +package bbolt_test + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "log" + "math/rand" + "os" + "strconv" + "strings" + "testing" + "testing/quick" + + bolt "go.etcd.io/bbolt" +) + +// Ensure that a bucket that gets a non-existent key returns nil. +func TestBucket_Get_NonExistent(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if v := b.Get([]byte("foo")); v != nil { + t.Fatal("expected nil value") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can read a value that is not flushed yet. +func TestBucket_Get_FromNode(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if v := b.Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket retrieved via Get() returns a nil. +func TestBucket_Get_IncompatibleValue(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + + if tx.Bucket([]byte("widgets")).Get([]byte("foo")) != nil { + t.Fatal("expected nil value") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a slice returned from a bucket has a capacity equal to its length. +// This also allows slices to be appended to since it will require a realloc by Go. +// +// https://github.com/boltdb/bolt/issues/544 +func TestBucket_Get_Capacity(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Write key to a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("bucket")) + if err != nil { + return err + } + return b.Put([]byte("key"), []byte("val")) + }); err != nil { + t.Fatal(err) + } + + // Retrieve value and attempt to append to it. + if err := db.Update(func(tx *bolt.Tx) error { + k, v := tx.Bucket([]byte("bucket")).Cursor().First() + + // Verify capacity. + if len(k) != cap(k) { + t.Fatalf("unexpected key slice capacity: %d", cap(k)) + } else if len(v) != cap(v) { + t.Fatalf("unexpected value slice capacity: %d", cap(v)) + } + + // Ensure slice can be appended to without a segfault. + k = append(k, []byte("123")...) + v = append(v, []byte("123")...) + _, _ = k, v // to pass ineffassign + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can write a key/value. +func TestBucket_Put(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + + v := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + if !bytes.Equal([]byte("bar"), v) { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can rewrite a key in the same transaction. +func TestBucket_Put_Repeat(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("baz")); err != nil { + t.Fatal(err) + } + + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + if !bytes.Equal([]byte("baz"), value) { + t.Fatalf("unexpected value: %v", value) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can write a bunch of large values. +func TestBucket_Put_Large(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + count, factor := 100, 200 + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for i := 1; i < count; i++ { + if err := b.Put([]byte(strings.Repeat("0", i*factor)), []byte(strings.Repeat("X", (count-i)*factor))); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 1; i < count; i++ { + value := b.Get([]byte(strings.Repeat("0", i*factor))) + if !bytes.Equal(value, []byte(strings.Repeat("X", (count-i)*factor))) { + t.Fatalf("unexpected value: %v", value) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a database can perform multiple large appends safely. +func TestDB_Put_VeryLarge(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + n, batchN := 400000, 200000 + ksize, vsize := 8, 500 + + db := MustOpenDB() + defer db.MustClose() + + for i := 0; i < n; i += batchN { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for j := 0; j < batchN; j++ { + k, v := make([]byte, ksize), make([]byte, vsize) + binary.BigEndian.PutUint32(k, uint32(i+j)) + if err := b.Put(k, v); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + } +} + +// Ensure that a setting a value on a key with a bucket value returns an error. +func TestBucket_Put_IncompatibleValue(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b0, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if _, err := tx.Bucket([]byte("widgets")).CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + if err := b0.Put([]byte("foo"), []byte("bar")); err != bolt.ErrIncompatibleValue { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a setting a value while the transaction is closed returns an error. +func TestBucket_Put_Closed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + + if err := b.Put([]byte("foo"), []byte("bar")); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that setting a value on a read-only bucket returns an error. +func TestBucket_Put_ReadOnly(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + if err := b.Put([]byte("foo"), []byte("bar")); err != bolt.ErrTxNotWritable { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can delete an existing key. +func TestBucket_Delete(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Delete([]byte("foo")); err != nil { + t.Fatal(err) + } + if v := b.Get([]byte("foo")); v != nil { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a large set of keys will work correctly. +func TestBucket_Delete_Large(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 100; i++ { + if err := b.Put([]byte(strconv.Itoa(i)), []byte(strings.Repeat("*", 1024))); err != nil { + t.Fatal(err) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 0; i < 100; i++ { + if err := b.Delete([]byte(strconv.Itoa(i))); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 0; i < 100; i++ { + if v := b.Get([]byte(strconv.Itoa(i))); v != nil { + t.Fatalf("unexpected value: %v, i=%d", v, i) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Deleting a very large list of keys will cause the freelist to use overflow. +func TestBucket_Delete_FreelistOverflow(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + db := MustOpenDB() + defer db.MustClose() + + k := make([]byte, 16) + for i := uint64(0); i < 10000; i++ { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("0")) + if err != nil { + t.Fatalf("bucket error: %s", err) + } + + for j := uint64(0); j < 1000; j++ { + binary.BigEndian.PutUint64(k[:8], i) + binary.BigEndian.PutUint64(k[8:], j) + if err := b.Put(k, nil); err != nil { + t.Fatalf("put error: %s", err) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + } + + // Delete all of them in one large transaction + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("0")) + c := b.Cursor() + for k, _ := c.First(); k != nil; k, _ = c.Next() { + if err := c.Delete(); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Check more than an overflow's worth of pages are freed. + stats := db.Stats() + freePages := stats.FreePageN + stats.PendingPageN + if freePages <= 0xFFFF { + t.Fatalf("expected more than 0xFFFF free pages, got %v", freePages) + } + + // Free page count should be preserved on reopen. + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + db.MustReopen() + if reopenFreePages := db.Stats().FreePageN; freePages != reopenFreePages { + t.Fatalf("expected %d free pages, got %+v", freePages, db.Stats()) + } +} + +// Ensure that deleting of non-existing key is a no-op. +func TestBucket_Delete_NonExisting(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if _, err = b.CreateBucket([]byte("nested")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + if err := b.Delete([]byte("foo")); err != nil { + t.Fatal(err) + } + if b.Bucket([]byte("nested")) == nil { + t.Fatal("nested bucket has been deleted") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that accessing and updating nested buckets is ok across transactions. +func TestBucket_Nested(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + // Create a widgets bucket. + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + // Create a widgets/foo bucket. + _, err = b.CreateBucket([]byte("foo")) + if err != nil { + t.Fatal(err) + } + + // Create a widgets/bar key. + if err := b.Put([]byte("bar"), []byte("0000")); err != nil { + t.Fatal(err) + } + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // Update widgets/bar. + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + if err := b.Put([]byte("bar"), []byte("xxxx")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // Cause a split. + if err := db.Update(func(tx *bolt.Tx) error { + var b = tx.Bucket([]byte("widgets")) + for i := 0; i < 10000; i++ { + if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // Insert into widgets/foo/baz. + if err := db.Update(func(tx *bolt.Tx) error { + var b = tx.Bucket([]byte("widgets")) + if err := b.Bucket([]byte("foo")).Put([]byte("baz"), []byte("yyyy")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // Verify. + if err := db.View(func(tx *bolt.Tx) error { + var b = tx.Bucket([]byte("widgets")) + if v := b.Bucket([]byte("foo")).Get([]byte("baz")); !bytes.Equal(v, []byte("yyyy")) { + t.Fatalf("unexpected value: %v", v) + } + if v := b.Get([]byte("bar")); !bytes.Equal(v, []byte("xxxx")) { + t.Fatalf("unexpected value: %v", v) + } + for i := 0; i < 10000; i++ { + if v := b.Get([]byte(strconv.Itoa(i))); !bytes.Equal(v, []byte(strconv.Itoa(i))) { + t.Fatalf("unexpected value: %v", v) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a bucket using Delete() returns an error. +func TestBucket_Delete_Bucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + if err := b.Delete([]byte("foo")); err != bolt.ErrIncompatibleValue { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a key on a read-only bucket returns an error. +func TestBucket_Delete_ReadOnly(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("widgets")).Delete([]byte("foo")); err != bolt.ErrTxNotWritable { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a deleting value while the transaction is closed returns an error. +func TestBucket_Delete_Closed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + if err := b.Delete([]byte("foo")); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that deleting a bucket causes nested buckets to be deleted. +func TestBucket_DeleteBucket_Nested(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + foo, err := widgets.CreateBucket([]byte("foo")) + if err != nil { + t.Fatal(err) + } + + bar, err := foo.CreateBucket([]byte("bar")) + if err != nil { + t.Fatal(err) + } + if err := bar.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + if err := tx.Bucket([]byte("widgets")).DeleteBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a bucket causes nested buckets to be deleted after they have been committed. +func TestBucket_DeleteBucket_Nested2(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + foo, err := widgets.CreateBucket([]byte("foo")) + if err != nil { + t.Fatal(err) + } + + bar, err := foo.CreateBucket([]byte("bar")) + if err != nil { + t.Fatal(err) + } + + if err := bar.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + widgets := tx.Bucket([]byte("widgets")) + if widgets == nil { + t.Fatal("expected widgets bucket") + } + + foo := widgets.Bucket([]byte("foo")) + if foo == nil { + t.Fatal("expected foo bucket") + } + + bar := foo.Bucket([]byte("bar")) + if bar == nil { + t.Fatal("expected bar bucket") + } + + if v := bar.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { + t.Fatalf("unexpected value: %v", v) + } + if err := tx.DeleteBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) != nil { + t.Fatal("expected bucket to be deleted") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a child bucket with multiple pages causes all pages to get collected. +// NOTE: Consistency check in bolt_test.DB.Close() will panic if pages not freed properly. +func TestBucket_DeleteBucket_Large(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + foo, err := widgets.CreateBucket([]byte("foo")) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 1000; i++ { + if err := foo.Put([]byte(fmt.Sprintf("%d", i)), []byte(fmt.Sprintf("%0100d", i))); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a simple value retrieved via Bucket() returns a nil. +func TestBucket_Bucket_IncompatibleValue(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := widgets.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if b := tx.Bucket([]byte("widgets")).Bucket([]byte("foo")); b != nil { + t.Fatal("expected nil bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that creating a bucket on an existing non-bucket key returns an error. +func TestBucket_CreateBucket_IncompatibleValue(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := widgets.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if _, err := widgets.CreateBucket([]byte("foo")); err != bolt.ErrIncompatibleValue { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a bucket on an existing non-bucket key returns an error. +func TestBucket_DeleteBucket_IncompatibleValue(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := widgets.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := tx.Bucket([]byte("widgets")).DeleteBucket([]byte("foo")); err != bolt.ErrIncompatibleValue { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure bucket can set and update its sequence number. +func TestBucket_Sequence(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + bkt, err := tx.CreateBucket([]byte("0")) + if err != nil { + t.Fatal(err) + } + + // Retrieve sequence. + if v := bkt.Sequence(); v != 0 { + t.Fatalf("unexpected sequence: %d", v) + } + + // Update sequence. + if err := bkt.SetSequence(1000); err != nil { + t.Fatal(err) + } + + // Read sequence again. + if v := bkt.Sequence(); v != 1000 { + t.Fatalf("unexpected sequence: %d", v) + } + + return nil + }); err != nil { + t.Fatal(err) + } + + // Verify sequence in separate transaction. + if err := db.View(func(tx *bolt.Tx) error { + if v := tx.Bucket([]byte("0")).Sequence(); v != 1000 { + t.Fatalf("unexpected sequence: %d", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can return an autoincrementing sequence. +func TestBucket_NextSequence(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + widgets, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + woojits, err := tx.CreateBucket([]byte("woojits")) + if err != nil { + t.Fatal(err) + } + + // Make sure sequence increments. + if seq, err := widgets.NextSequence(); err != nil { + t.Fatal(err) + } else if seq != 1 { + t.Fatalf("unexpecte sequence: %d", seq) + } + + if seq, err := widgets.NextSequence(); err != nil { + t.Fatal(err) + } else if seq != 2 { + t.Fatalf("unexpected sequence: %d", seq) + } + + // Buckets should be separate. + if seq, err := woojits.NextSequence(); err != nil { + t.Fatal(err) + } else if seq != 1 { + t.Fatalf("unexpected sequence: %d", 1) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket will persist an autoincrementing sequence even if its +// the only thing updated on the bucket. +// https://github.com/boltdb/bolt/issues/296 +func TestBucket_NextSequence_Persist(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.Bucket([]byte("widgets")).NextSequence(); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + seq, err := tx.Bucket([]byte("widgets")).NextSequence() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } else if seq != 2 { + t.Fatalf("unexpected sequence: %d", seq) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that retrieving the next sequence on a read-only bucket returns an error. +func TestBucket_NextSequence_ReadOnly(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + _, err := tx.Bucket([]byte("widgets")).NextSequence() + if err != bolt.ErrTxNotWritable { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that retrieving the next sequence for a bucket on a closed database return an error. +func TestBucket_NextSequence_Closed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + if _, err := b.NextSequence(); err != bolt.ErrTxClosed { + t.Fatal(err) + } +} + +// Ensure a user can loop over all key/value pairs in a bucket. +func TestBucket_ForEach(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("0000")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("0001")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte("0002")); err != nil { + t.Fatal(err) + } + + var index int + if err := b.ForEach(func(k, v []byte) error { + switch index { + case 0: + if !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0002")) { + t.Fatalf("unexpected value: %v", v) + } + case 1: + if !bytes.Equal(k, []byte("baz")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0001")) { + t.Fatalf("unexpected value: %v", v) + } + case 2: + if !bytes.Equal(k, []byte("foo")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0000")) { + t.Fatalf("unexpected value: %v", v) + } + } + index++ + return nil + }); err != nil { + t.Fatal(err) + } + + if index != 3 { + t.Fatalf("unexpected index: %d", index) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a database can stop iteration early. +func TestBucket_ForEach_ShortCircuit(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte("0000")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("0000")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("0000")); err != nil { + t.Fatal(err) + } + + var index int + if err := tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { + index++ + if bytes.Equal(k, []byte("baz")) { + return errors.New("marker") + } + return nil + }); err == nil || err.Error() != "marker" { + t.Fatalf("unexpected error: %s", err) + } + if index != 2 { + t.Fatalf("unexpected index: %d", index) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that looping over a bucket on a closed database returns an error. +func TestBucket_ForEach_Closed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + + if err := b.ForEach(func(k, v []byte) error { return nil }); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that an error is returned when inserting with an empty key. +func TestBucket_Put_EmptyKey(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte(""), []byte("bar")); err != bolt.ErrKeyRequired { + t.Fatalf("unexpected error: %s", err) + } + if err := b.Put(nil, []byte("bar")); err != bolt.ErrKeyRequired { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that an error is returned when inserting with a key that's too large. +func TestBucket_Put_KeyTooLarge(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put(make([]byte, 32769), []byte("bar")); err != bolt.ErrKeyTooLarge { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that an error is returned when inserting a value that's too large. +func TestBucket_Put_ValueTooLarge(t *testing.T) { + // Skip this test on DroneCI because the machine is resource constrained. + if os.Getenv("DRONE") == "true" { + t.Skip("not enough RAM for test") + } + + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), make([]byte, bolt.MaxValueSize+1)); err != bolt.ErrValueTooLarge { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a bucket can calculate stats. +func TestBucket_Stats(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Add bucket with fewer keys but one big value. + bigKey := []byte("really-big-value") + for i := 0; i < 500; i++ { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("woojits")) + if err != nil { + t.Fatal(err) + } + + if err := b.Put([]byte(fmt.Sprintf("%03d", i)), []byte(strconv.Itoa(i))); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + } + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("woojits")).Put(bigKey, []byte(strings.Repeat("*", 10000))); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + stats := tx.Bucket([]byte("woojits")).Stats() + if stats.BranchPageN != 1 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.LeafPageN != 7 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 2 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.KeyN != 501 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } else if stats.Depth != 2 { + t.Fatalf("unexpected Depth: %d", stats.Depth) + } + + branchInuse := 16 // branch page header + branchInuse += 7 * 16 // branch elements + branchInuse += 7 * 3 // branch keys (6 3-byte keys) + if stats.BranchInuse != branchInuse { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } + + leafInuse := 7 * 16 // leaf page header + leafInuse += 501 * 16 // leaf elements + leafInuse += 500*3 + len(bigKey) // leaf keys + leafInuse += 1*10 + 2*90 + 3*400 + 10000 // leaf values + if stats.LeafInuse != leafInuse { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } + + // Only check allocations for 4KB pages. + if db.Info().PageSize == 4096 { + if stats.BranchAlloc != 4096 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } else if stats.LeafAlloc != 36864 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + } + + if stats.BucketN != 1 { + t.Fatalf("unexpected BucketN: %d", stats.BucketN) + } else if stats.InlineBucketN != 0 { + t.Fatalf("unexpected InlineBucketN: %d", stats.InlineBucketN) + } else if stats.InlineBucketInuse != 0 { + t.Fatalf("unexpected InlineBucketInuse: %d", stats.InlineBucketInuse) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a bucket with random insertion utilizes fill percentage correctly. +func TestBucket_Stats_RandomFill(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } else if os.Getpagesize() != 4096 { + t.Skip("invalid page size for test") + } + + db := MustOpenDB() + defer db.MustClose() + + // Add a set of values in random order. It will be the same random + // order so we can maintain consistency between test runs. + var count int + rand := rand.New(rand.NewSource(42)) + for _, i := range rand.Perm(1000) { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("woojits")) + if err != nil { + t.Fatal(err) + } + b.FillPercent = 0.9 + for _, j := range rand.Perm(100) { + index := (j * 10000) + i + if err := b.Put([]byte(fmt.Sprintf("%d000000000000000", index)), []byte("0000000000")); err != nil { + t.Fatal(err) + } + count++ + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + stats := tx.Bucket([]byte("woojits")).Stats() + if stats.KeyN != 100000 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } + + if stats.BranchPageN != 98 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.BranchInuse != 130984 { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } else if stats.BranchAlloc != 401408 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } + + if stats.LeafPageN != 3412 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 0 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.LeafInuse != 4742482 { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } else if stats.LeafAlloc != 13975552 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a bucket can calculate stats. +func TestBucket_Stats_Small(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + // Add a bucket that fits on a single root leaf. + b, err := tx.CreateBucket([]byte("whozawhats")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + + return nil + }); err != nil { + t.Fatal(err) + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("whozawhats")) + stats := b.Stats() + if stats.BranchPageN != 0 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.LeafPageN != 0 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 0 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.KeyN != 1 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } else if stats.Depth != 1 { + t.Fatalf("unexpected Depth: %d", stats.Depth) + } else if stats.BranchInuse != 0 { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } else if stats.LeafInuse != 0 { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } + + if db.Info().PageSize == 4096 { + if stats.BranchAlloc != 0 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } else if stats.LeafAlloc != 0 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + } + + if stats.BucketN != 1 { + t.Fatalf("unexpected BucketN: %d", stats.BucketN) + } else if stats.InlineBucketN != 1 { + t.Fatalf("unexpected InlineBucketN: %d", stats.InlineBucketN) + } else if stats.InlineBucketInuse != 16+16+6 { + t.Fatalf("unexpected InlineBucketInuse: %d", stats.InlineBucketInuse) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestBucket_Stats_EmptyBucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + // Add a bucket that fits on a single root leaf. + if _, err := tx.CreateBucket([]byte("whozawhats")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("whozawhats")) + stats := b.Stats() + if stats.BranchPageN != 0 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.LeafPageN != 0 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 0 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.KeyN != 0 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } else if stats.Depth != 1 { + t.Fatalf("unexpected Depth: %d", stats.Depth) + } else if stats.BranchInuse != 0 { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } else if stats.LeafInuse != 0 { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } + + if db.Info().PageSize == 4096 { + if stats.BranchAlloc != 0 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } else if stats.LeafAlloc != 0 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + } + + if stats.BucketN != 1 { + t.Fatalf("unexpected BucketN: %d", stats.BucketN) + } else if stats.InlineBucketN != 1 { + t.Fatalf("unexpected InlineBucketN: %d", stats.InlineBucketN) + } else if stats.InlineBucketInuse != 16 { + t.Fatalf("unexpected InlineBucketInuse: %d", stats.InlineBucketInuse) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a bucket can calculate stats. +func TestBucket_Stats_Nested(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("foo")) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 100; i++ { + if err := b.Put([]byte(fmt.Sprintf("%02d", i)), []byte(fmt.Sprintf("%02d", i))); err != nil { + t.Fatal(err) + } + } + + bar, err := b.CreateBucket([]byte("bar")) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 10; i++ { + if err := bar.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil { + t.Fatal(err) + } + } + + baz, err := bar.CreateBucket([]byte("baz")) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 10; i++ { + if err := baz.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil { + t.Fatal(err) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("foo")) + stats := b.Stats() + if stats.BranchPageN != 0 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.LeafPageN != 2 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 0 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.KeyN != 122 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } else if stats.Depth != 3 { + t.Fatalf("unexpected Depth: %d", stats.Depth) + } else if stats.BranchInuse != 0 { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } + + foo := 16 // foo (pghdr) + foo += 101 * 16 // foo leaf elements + foo += 100*2 + 100*2 // foo leaf key/values + foo += 3 + 16 // foo -> bar key/value + + bar := 16 // bar (pghdr) + bar += 11 * 16 // bar leaf elements + bar += 10 + 10 // bar leaf key/values + bar += 3 + 16 // bar -> baz key/value + + baz := 16 // baz (inline) (pghdr) + baz += 10 * 16 // baz leaf elements + baz += 10 + 10 // baz leaf key/values + + if stats.LeafInuse != foo+bar+baz { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } + + if db.Info().PageSize == 4096 { + if stats.BranchAlloc != 0 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } else if stats.LeafAlloc != 8192 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + } + + if stats.BucketN != 3 { + t.Fatalf("unexpected BucketN: %d", stats.BucketN) + } else if stats.InlineBucketN != 1 { + t.Fatalf("unexpected InlineBucketN: %d", stats.InlineBucketN) + } else if stats.InlineBucketInuse != baz { + t.Fatalf("unexpected InlineBucketInuse: %d", stats.InlineBucketInuse) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a large bucket can calculate stats. +func TestBucket_Stats_Large(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + db := MustOpenDB() + defer db.MustClose() + + var index int + for i := 0; i < 100; i++ { + // Add bucket with lots of keys. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 1000; i++ { + if err := b.Put([]byte(strconv.Itoa(index)), []byte(strconv.Itoa(index))); err != nil { + t.Fatal(err) + } + index++ + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + db.MustCheck() + + if err := db.View(func(tx *bolt.Tx) error { + stats := tx.Bucket([]byte("widgets")).Stats() + if stats.BranchPageN != 13 { + t.Fatalf("unexpected BranchPageN: %d", stats.BranchPageN) + } else if stats.BranchOverflowN != 0 { + t.Fatalf("unexpected BranchOverflowN: %d", stats.BranchOverflowN) + } else if stats.LeafPageN != 1196 { + t.Fatalf("unexpected LeafPageN: %d", stats.LeafPageN) + } else if stats.LeafOverflowN != 0 { + t.Fatalf("unexpected LeafOverflowN: %d", stats.LeafOverflowN) + } else if stats.KeyN != 100000 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } else if stats.Depth != 3 { + t.Fatalf("unexpected Depth: %d", stats.Depth) + } else if stats.BranchInuse != 25257 { + t.Fatalf("unexpected BranchInuse: %d", stats.BranchInuse) + } else if stats.LeafInuse != 2596916 { + t.Fatalf("unexpected LeafInuse: %d", stats.LeafInuse) + } + + if db.Info().PageSize == 4096 { + if stats.BranchAlloc != 53248 { + t.Fatalf("unexpected BranchAlloc: %d", stats.BranchAlloc) + } else if stats.LeafAlloc != 4898816 { + t.Fatalf("unexpected LeafAlloc: %d", stats.LeafAlloc) + } + } + + if stats.BucketN != 1 { + t.Fatalf("unexpected BucketN: %d", stats.BucketN) + } else if stats.InlineBucketN != 0 { + t.Fatalf("unexpected InlineBucketN: %d", stats.InlineBucketN) + } else if stats.InlineBucketInuse != 0 { + t.Fatalf("unexpected InlineBucketInuse: %d", stats.InlineBucketInuse) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can write random keys and values across multiple transactions. +func TestBucket_Put_Single(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + index := 0 + if err := quick.Check(func(items testdata) bool { + db := MustOpenDB() + defer db.MustClose() + + m := make(map[string][]byte) + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + for _, item := range items { + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("widgets")).Put(item.Key, item.Value); err != nil { + panic("put error: " + err.Error()) + } + m[string(item.Key)] = item.Value + return nil + }); err != nil { + t.Fatal(err) + } + + // Verify all key/values so far. + if err := db.View(func(tx *bolt.Tx) error { + i := 0 + for k, v := range m { + value := tx.Bucket([]byte("widgets")).Get([]byte(k)) + if !bytes.Equal(value, v) { + t.Logf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) + db.CopyTempFile() + t.FailNow() + } + i++ + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + index++ + return true + }, qconfig()); err != nil { + t.Error(err) + } +} + +// Ensure that a transaction can insert multiple key/value pairs at once. +func TestBucket_Put_Multiple(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + if err := quick.Check(func(items testdata) bool { + db := MustOpenDB() + defer db.MustClose() + + // Bulk insert all values. + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for _, item := range items { + if err := b.Put(item.Key, item.Value); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Verify all items exist. + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for _, item := range items { + value := b.Get(item.Key) + if !bytes.Equal(item.Value, value) { + db.CopyTempFile() + t.Fatalf("exp=%x; got=%x", item.Value, value) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + return true + }, qconfig()); err != nil { + t.Error(err) + } +} + +// Ensure that a transaction can delete all key/value pairs and return to a single leaf page. +func TestBucket_Delete_Quick(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + if err := quick.Check(func(items testdata) bool { + db := MustOpenDB() + defer db.MustClose() + + // Bulk insert all values. + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for _, item := range items { + if err := b.Put(item.Key, item.Value); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Remove items one at a time and check consistency. + for _, item := range items { + if err := db.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Delete(item.Key) + }); err != nil { + t.Fatal(err) + } + } + + // Anything before our deletion index should be nil. + if err := db.View(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { + t.Fatalf("bucket should be empty; found: %06x", trunc(k, 3)) + return nil + }); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + return true + }, qconfig()); err != nil { + t.Error(err) + } +} + +func ExampleBucket_Put() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Start a write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + // Create a bucket. + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + return err + } + + // Set the value "bar" for the key "foo". + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + return err + } + return nil + }); err != nil { + log.Fatal(err) + } + + // Read value back in a different read-only transaction. + if err := db.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + fmt.Printf("The value of 'foo' is: %s\n", value) + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // The value of 'foo' is: bar +} + +func ExampleBucket_Delete() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Start a write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + // Create a bucket. + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + return err + } + + // Set the value "bar" for the key "foo". + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + return err + } + + // Retrieve the key back from the database and verify it. + value := b.Get([]byte("foo")) + fmt.Printf("The value of 'foo' was: %s\n", value) + + return nil + }); err != nil { + log.Fatal(err) + } + + // Delete the key in a different write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Delete([]byte("foo")) + }); err != nil { + log.Fatal(err) + } + + // Retrieve the key again. + if err := db.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + if value == nil { + fmt.Printf("The value of 'foo' is now: nil\n") + } + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // The value of 'foo' was: bar + // The value of 'foo' is now: nil +} + +func ExampleBucket_ForEach() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Insert data into a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("animals")) + if err != nil { + return err + } + + if err := b.Put([]byte("dog"), []byte("fun")); err != nil { + return err + } + if err := b.Put([]byte("cat"), []byte("lame")); err != nil { + return err + } + if err := b.Put([]byte("liger"), []byte("awesome")); err != nil { + return err + } + + // Iterate over items in sorted key order. + if err := b.ForEach(func(k, v []byte) error { + fmt.Printf("A %s is %s.\n", k, v) + return nil + }); err != nil { + return err + } + + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // A cat is lame. + // A dog is fun. + // A liger is awesome. +} diff --git a/vendor/etcd.io/bbolt/cmd/bbolt/main.go b/vendor/etcd.io/bbolt/cmd/bbolt/main.go new file mode 100644 index 0000000..91a13bc --- /dev/null +++ b/vendor/etcd.io/bbolt/cmd/bbolt/main.go @@ -0,0 +1,2136 @@ +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "runtime" + "runtime/pprof" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" + "unsafe" + + bolt "go.etcd.io/bbolt" +) + +var ( + // ErrUsage is returned when a usage message was printed and the process + // should simply exit with an error. + ErrUsage = errors.New("usage") + + // ErrUnknownCommand is returned when a CLI command is not specified. + ErrUnknownCommand = errors.New("unknown command") + + // ErrPathRequired is returned when the path to a Bolt database is not specified. + ErrPathRequired = errors.New("path required") + + // ErrFileNotFound is returned when a Bolt database does not exist. + ErrFileNotFound = errors.New("file not found") + + // ErrInvalidValue is returned when a benchmark reads an unexpected value. + ErrInvalidValue = errors.New("invalid value") + + // ErrCorrupt is returned when a checking a data file finds errors. + ErrCorrupt = errors.New("invalid value") + + // ErrNonDivisibleBatchSize is returned when the batch size can't be evenly + // divided by the iteration count. + ErrNonDivisibleBatchSize = errors.New("number of iterations must be divisible by the batch size") + + // ErrPageIDRequired is returned when a required page id is not specified. + ErrPageIDRequired = errors.New("page id required") + + // ErrBucketRequired is returned when a bucket is not specified. + ErrBucketRequired = errors.New("bucket required") + + // ErrBucketNotFound is returned when a bucket is not found. + ErrBucketNotFound = errors.New("bucket not found") + + // ErrKeyRequired is returned when a key is not specified. + ErrKeyRequired = errors.New("key required") + + // ErrKeyNotFound is returned when a key is not found. + ErrKeyNotFound = errors.New("key not found") +) + +// PageHeaderSize represents the size of the bolt.page header. +const PageHeaderSize = 16 + +func main() { + m := NewMain() + if err := m.Run(os.Args[1:]...); err == ErrUsage { + os.Exit(2) + } else if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +// Main represents the main program execution. +type Main struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewMain returns a new instance of Main connect to the standard input/output. +func NewMain() *Main { + return &Main{ + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + } +} + +// Run executes the program. +func (m *Main) Run(args ...string) error { + // Require a command at the beginning. + if len(args) == 0 || strings.HasPrefix(args[0], "-") { + fmt.Fprintln(m.Stderr, m.Usage()) + return ErrUsage + } + + // Execute command. + switch args[0] { + case "help": + fmt.Fprintln(m.Stderr, m.Usage()) + return ErrUsage + case "bench": + return newBenchCommand(m).Run(args[1:]...) + case "buckets": + return newBucketsCommand(m).Run(args[1:]...) + case "check": + return newCheckCommand(m).Run(args[1:]...) + case "compact": + return newCompactCommand(m).Run(args[1:]...) + case "dump": + return newDumpCommand(m).Run(args[1:]...) + case "page-item": + return newPageItemCommand(m).Run(args[1:]...) + case "get": + return newGetCommand(m).Run(args[1:]...) + case "info": + return newInfoCommand(m).Run(args[1:]...) + case "keys": + return newKeysCommand(m).Run(args[1:]...) + case "page": + return newPageCommand(m).Run(args[1:]...) + case "pages": + return newPagesCommand(m).Run(args[1:]...) + case "stats": + return newStatsCommand(m).Run(args[1:]...) + default: + return ErrUnknownCommand + } +} + +// Usage returns the help message. +func (m *Main) Usage() string { + return strings.TrimLeft(` +Bolt is a tool for inspecting bolt databases. + +Usage: + + bolt command [arguments] + +The commands are: + + bench run synthetic benchmark against bolt + buckets print a list of buckets + check verifies integrity of bolt database + compact copies a bolt database, compacting it in the process + dump print a hexadecimal dump of a single page + get print the value of a key in a bucket + info print basic info + keys print a list of keys in a bucket + help print this screen + page print one or more pages in human readable format + pages print list of pages with their types + page-item print the key and value of a page item. + stats iterate over all pages and generate usage stats + +Use "bolt [command] -h" for more information about a command. +`, "\n") +} + +// CheckCommand represents the "check" command execution. +type CheckCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewCheckCommand returns a CheckCommand. +func newCheckCommand(m *Main) *CheckCommand { + return &CheckCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *CheckCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + // Perform consistency check. + return db.View(func(tx *bolt.Tx) error { + var count int + for err := range tx.Check() { + fmt.Fprintln(cmd.Stdout, err) + count++ + } + + // Print summary of errors. + if count > 0 { + fmt.Fprintf(cmd.Stdout, "%d errors found\n", count) + return ErrCorrupt + } + + // Notify user that database is valid. + fmt.Fprintln(cmd.Stdout, "OK") + return nil + }) +} + +// Usage returns the help message. +func (cmd *CheckCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt check PATH + +Check opens a database at PATH and runs an exhaustive check to verify that +all pages are accessible or are marked as freed. It also verifies that no +pages are double referenced. + +Verification errors will stream out as they are found and the process will +return after all pages have been checked. +`, "\n") +} + +// InfoCommand represents the "info" command execution. +type InfoCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewInfoCommand returns a InfoCommand. +func newInfoCommand(m *Main) *InfoCommand { + return &InfoCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *InfoCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Open the database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + // Print basic database info. + info := db.Info() + fmt.Fprintf(cmd.Stdout, "Page Size: %d\n", info.PageSize) + + return nil +} + +// Usage returns the help message. +func (cmd *InfoCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt info PATH + +Info prints basic information about the Bolt database at PATH. +`, "\n") +} + +// DumpCommand represents the "dump" command execution. +type DumpCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// newDumpCommand returns a DumpCommand. +func newDumpCommand(m *Main) *DumpCommand { + return &DumpCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *DumpCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path and page id. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Read page ids. + pageIDs, err := atois(fs.Args()[1:]) + if err != nil { + return err + } else if len(pageIDs) == 0 { + return ErrPageIDRequired + } + + // Open database to retrieve page size. + pageSize, err := ReadPageSize(path) + if err != nil { + return err + } + + // Open database file handler. + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + // Print each page listed. + for i, pageID := range pageIDs { + // Print a separator. + if i > 0 { + fmt.Fprintln(cmd.Stdout, "===============================================") + } + + // Print page to stdout. + if err := cmd.PrintPage(cmd.Stdout, f, pageID, pageSize); err != nil { + return err + } + } + + return nil +} + +// PrintPage prints a given page as hexadecimal. +func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error { + const bytesPerLineN = 16 + + // Read page into buffer. + buf := make([]byte, pageSize) + addr := pageID * pageSize + if n, err := r.ReadAt(buf, int64(addr)); err != nil { + return err + } else if n != pageSize { + return io.ErrUnexpectedEOF + } + + // Write out to writer in 16-byte lines. + var prev []byte + var skipped bool + for offset := 0; offset < pageSize; offset += bytesPerLineN { + // Retrieve current 16-byte line. + line := buf[offset : offset+bytesPerLineN] + isLastLine := (offset == (pageSize - bytesPerLineN)) + + // If it's the same as the previous line then print a skip. + if bytes.Equal(line, prev) && !isLastLine { + if !skipped { + fmt.Fprintf(w, "%07x *\n", addr+offset) + skipped = true + } + } else { + // Print line as hexadecimal in 2-byte groups. + fmt.Fprintf(w, "%07x %04x %04x %04x %04x %04x %04x %04x %04x\n", addr+offset, + line[0:2], line[2:4], line[4:6], line[6:8], + line[8:10], line[10:12], line[12:14], line[14:16], + ) + + skipped = false + } + + // Save the previous line. + prev = line + } + fmt.Fprint(w, "\n") + + return nil +} + +// Usage returns the help message. +func (cmd *DumpCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt dump PATH pageid [pageid...] + +Dump prints a hexadecimal dump of one or more pages. +`, "\n") +} + +// PageItemCommand represents the "page-item" command execution. +type PageItemCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// newPageItemCommand returns a PageItemCommand. +func newPageItemCommand(m *Main) *PageItemCommand { + return &PageItemCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +type pageItemOptions struct { + help bool + keyOnly bool + valueOnly bool + format string +} + +// Run executes the command. +func (cmd *PageItemCommand) Run(args ...string) error { + // Parse flags. + options := &pageItemOptions{} + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.BoolVar(&options.keyOnly, "key-only", false, "Print only the key") + fs.BoolVar(&options.valueOnly, "value-only", false, "Print only the value") + fs.StringVar(&options.format, "format", "ascii-encoded", "Output format. One of: ascii-encoded|hex|bytes") + fs.BoolVar(&options.help, "h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if options.help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + if options.keyOnly && options.valueOnly { + return fmt.Errorf("The --key-only or --value-only flag may be set, but not both.") + } + + // Require database path and page id. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Read page id. + pageID, err := strconv.Atoi(fs.Arg(1)) + if err != nil { + return err + } + + // Read item id. + itemID, err := strconv.Atoi(fs.Arg(2)) + if err != nil { + return err + } + + // Open database file handler. + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + // Retrieve page info and page size. + _, buf, err := ReadPage(path, pageID) + if err != nil { + return err + } + + if !options.valueOnly { + err := cmd.PrintLeafItemKey(cmd.Stdout, buf, uint16(itemID), options.format) + if err != nil { + return err + } + } + if !options.keyOnly { + err := cmd.PrintLeafItemValue(cmd.Stdout, buf, uint16(itemID), options.format) + if err != nil { + return err + } + } + return nil +} + +// leafPageElement retrieves a leaf page element. +func (cmd *PageItemCommand) leafPageElement(pageBytes []byte, index uint16) (*leafPageElement, error) { + p := (*page)(unsafe.Pointer(&pageBytes[0])) + if index >= p.count { + return nil, fmt.Errorf("leafPageElement: expected item index less than %d, but got %d.", p.count, index) + } + if p.Type() != "leaf" { + return nil, fmt.Errorf("leafPageElement: expected page type of 'leaf', but got '%s'", p.Type()) + } + return p.leafPageElement(index), nil +} + +// writeBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. +func (cmd *PageItemCommand) writeBytes(w io.Writer, b []byte, format string) error { + switch format { + case "ascii-encoded": + _, err := fmt.Fprintf(w, "%q", b) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "\n") + return err + case "hex": + _, err := fmt.Fprintf(w, "%x", b) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "\n") + return err + case "bytes": + _, err := w.Write(b) + return err + default: + return fmt.Errorf("writeBytes: unsupported format: %s", format) + } +} + +// PrintLeafItemKey writes the bytes of a leaf element's key. +func (cmd *PageItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, index uint16, format string) error { + e, err := cmd.leafPageElement(pageBytes, index) + if err != nil { + return err + } + return cmd.writeBytes(w, e.key(), format) +} + +// PrintLeafItemKey writes the bytes of a leaf element's value. +func (cmd *PageItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, index uint16, format string) error { + e, err := cmd.leafPageElement(pageBytes, index) + if err != nil { + return err + } + return cmd.writeBytes(w, e.value(), format) +} + +// Usage returns the help message. +func (cmd *PageItemCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt page-item [options] PATH pageid itemid + +Additional options include: + + --key-only + Print only the key + --value-only + Print only the value + --format + Output format. One of: ascii-encoded|hex|bytes (default=ascii-encoded) + +page-item prints a page item key and value. +`, "\n") +} + +// PageCommand represents the "page" command execution. +type PageCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// newPageCommand returns a PageCommand. +func newPageCommand(m *Main) *PageCommand { + return &PageCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *PageCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path and page id. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Read page ids. + pageIDs, err := atois(fs.Args()[1:]) + if err != nil { + return err + } else if len(pageIDs) == 0 { + return ErrPageIDRequired + } + + // Open database file handler. + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + // Print each page listed. + for i, pageID := range pageIDs { + // Print a separator. + if i > 0 { + fmt.Fprintln(cmd.Stdout, "===============================================") + } + + // Retrieve page info and page size. + p, buf, err := ReadPage(path, pageID) + if err != nil { + return err + } + + // Print basic page info. + fmt.Fprintf(cmd.Stdout, "Page ID: %d\n", p.id) + fmt.Fprintf(cmd.Stdout, "Page Type: %s\n", p.Type()) + fmt.Fprintf(cmd.Stdout, "Total Size: %d bytes\n", len(buf)) + + // Print type-specific data. + switch p.Type() { + case "meta": + err = cmd.PrintMeta(cmd.Stdout, buf) + case "leaf": + err = cmd.PrintLeaf(cmd.Stdout, buf) + case "branch": + err = cmd.PrintBranch(cmd.Stdout, buf) + case "freelist": + err = cmd.PrintFreelist(cmd.Stdout, buf) + } + if err != nil { + return err + } + } + + return nil +} + +// PrintMeta prints the data from the meta page. +func (cmd *PageCommand) PrintMeta(w io.Writer, buf []byte) error { + m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize])) + fmt.Fprintf(w, "Version: %d\n", m.version) + fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize) + fmt.Fprintf(w, "Flags: %08x\n", m.flags) + fmt.Fprintf(w, "Root: \n", m.root.root) + fmt.Fprintf(w, "Freelist: \n", m.freelist) + fmt.Fprintf(w, "HWM: \n", m.pgid) + fmt.Fprintf(w, "Txn ID: %d\n", m.txid) + fmt.Fprintf(w, "Checksum: %016x\n", m.checksum) + fmt.Fprintf(w, "\n") + return nil +} + +// PrintLeaf prints the data for a leaf page. +func (cmd *PageCommand) PrintLeaf(w io.Writer, buf []byte) error { + p := (*page)(unsafe.Pointer(&buf[0])) + + // Print number of items. + fmt.Fprintf(w, "Item Count: %d\n", p.count) + fmt.Fprintf(w, "\n") + + // Print each key/value. + for i := uint16(0); i < p.count; i++ { + e := p.leafPageElement(i) + + // Format key as string. + var k string + if isPrintable(string(e.key())) { + k = fmt.Sprintf("%q", string(e.key())) + } else { + k = fmt.Sprintf("%x", string(e.key())) + } + + // Format value as string. + var v string + if (e.flags & uint32(bucketLeafFlag)) != 0 { + b := (*bucket)(unsafe.Pointer(&e.value()[0])) + v = fmt.Sprintf("", b.root, b.sequence) + } else if isPrintable(string(e.value())) { + v = fmt.Sprintf("%q", string(e.value())) + } else { + v = fmt.Sprintf("%x", string(e.value())) + } + + fmt.Fprintf(w, "%s: %s\n", k, v) + } + fmt.Fprintf(w, "\n") + return nil +} + +// PrintBranch prints the data for a leaf page. +func (cmd *PageCommand) PrintBranch(w io.Writer, buf []byte) error { + p := (*page)(unsafe.Pointer(&buf[0])) + + // Print number of items. + fmt.Fprintf(w, "Item Count: %d\n", p.count) + fmt.Fprintf(w, "\n") + + // Print each key/value. + for i := uint16(0); i < p.count; i++ { + e := p.branchPageElement(i) + + // Format key as string. + var k string + if isPrintable(string(e.key())) { + k = fmt.Sprintf("%q", string(e.key())) + } else { + k = fmt.Sprintf("%x", string(e.key())) + } + + fmt.Fprintf(w, "%s: \n", k, e.pgid) + } + fmt.Fprintf(w, "\n") + return nil +} + +// PrintFreelist prints the data for a freelist page. +func (cmd *PageCommand) PrintFreelist(w io.Writer, buf []byte) error { + p := (*page)(unsafe.Pointer(&buf[0])) + + // Check for overflow and, if present, adjust starting index and actual element count. + idx, count := 0, int(p.count) + if p.count == 0xFFFF { + idx = 1 + count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0]) + } + + // Print number of items. + fmt.Fprintf(w, "Item Count: %d\n", count) + fmt.Fprintf(w, "Overflow: %d\n", p.overflow) + + fmt.Fprintf(w, "\n") + + // Print each page in the freelist. + ids := (*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)) + for i := idx; i < count; i++ { + fmt.Fprintf(w, "%d\n", ids[i]) + } + fmt.Fprintf(w, "\n") + return nil +} + +// PrintPage prints a given page as hexadecimal. +func (cmd *PageCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error { + const bytesPerLineN = 16 + + // Read page into buffer. + buf := make([]byte, pageSize) + addr := pageID * pageSize + if n, err := r.ReadAt(buf, int64(addr)); err != nil { + return err + } else if n != pageSize { + return io.ErrUnexpectedEOF + } + + // Write out to writer in 16-byte lines. + var prev []byte + var skipped bool + for offset := 0; offset < pageSize; offset += bytesPerLineN { + // Retrieve current 16-byte line. + line := buf[offset : offset+bytesPerLineN] + isLastLine := (offset == (pageSize - bytesPerLineN)) + + // If it's the same as the previous line then print a skip. + if bytes.Equal(line, prev) && !isLastLine { + if !skipped { + fmt.Fprintf(w, "%07x *\n", addr+offset) + skipped = true + } + } else { + // Print line as hexadecimal in 2-byte groups. + fmt.Fprintf(w, "%07x %04x %04x %04x %04x %04x %04x %04x %04x\n", addr+offset, + line[0:2], line[2:4], line[4:6], line[6:8], + line[8:10], line[10:12], line[12:14], line[14:16], + ) + + skipped = false + } + + // Save the previous line. + prev = line + } + fmt.Fprint(w, "\n") + + return nil +} + +// Usage returns the help message. +func (cmd *PageCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt page PATH pageid [pageid...] + +Page prints one or more pages in human readable format. +`, "\n") +} + +// PagesCommand represents the "pages" command execution. +type PagesCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewPagesCommand returns a PagesCommand. +func newPagesCommand(m *Main) *PagesCommand { + return &PagesCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *PagesCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer func() { _ = db.Close() }() + + // Write header. + fmt.Fprintln(cmd.Stdout, "ID TYPE ITEMS OVRFLW") + fmt.Fprintln(cmd.Stdout, "======== ========== ====== ======") + + return db.Update(func(tx *bolt.Tx) error { + var id int + for { + p, err := tx.Page(id) + if err != nil { + return &PageError{ID: id, Err: err} + } else if p == nil { + break + } + + // Only display count and overflow if this is a non-free page. + var count, overflow string + if p.Type != "free" { + count = strconv.Itoa(p.Count) + if p.OverflowCount > 0 { + overflow = strconv.Itoa(p.OverflowCount) + } + } + + // Print table row. + fmt.Fprintf(cmd.Stdout, "%-8d %-10s %-6s %-6s\n", p.ID, p.Type, count, overflow) + + // Move to the next non-overflow page. + id += 1 + if p.Type != "free" { + id += p.OverflowCount + } + } + return nil + }) +} + +// Usage returns the help message. +func (cmd *PagesCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt pages PATH + +Pages prints a table of pages with their type (meta, leaf, branch, freelist). +Leaf and branch pages will show a key count in the "items" column while the +freelist will show the number of free pages in the "items" column. + +The "overflow" column shows the number of blocks that the page spills over +into. Normally there is no overflow but large keys and values can cause +a single page to take up multiple blocks. +`, "\n") +} + +// StatsCommand represents the "stats" command execution. +type StatsCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewStatsCommand returns a StatsCommand. +func newStatsCommand(m *Main) *StatsCommand { + return &StatsCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *StatsCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path. + path, prefix := fs.Arg(0), fs.Arg(1) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + return db.View(func(tx *bolt.Tx) error { + var s bolt.BucketStats + var count int + if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { + if bytes.HasPrefix(name, []byte(prefix)) { + s.Add(b.Stats()) + count += 1 + } + return nil + }); err != nil { + return err + } + + fmt.Fprintf(cmd.Stdout, "Aggregate statistics for %d buckets\n\n", count) + + fmt.Fprintln(cmd.Stdout, "Page count statistics") + fmt.Fprintf(cmd.Stdout, "\tNumber of logical branch pages: %d\n", s.BranchPageN) + fmt.Fprintf(cmd.Stdout, "\tNumber of physical branch overflow pages: %d\n", s.BranchOverflowN) + fmt.Fprintf(cmd.Stdout, "\tNumber of logical leaf pages: %d\n", s.LeafPageN) + fmt.Fprintf(cmd.Stdout, "\tNumber of physical leaf overflow pages: %d\n", s.LeafOverflowN) + + fmt.Fprintln(cmd.Stdout, "Tree statistics") + fmt.Fprintf(cmd.Stdout, "\tNumber of keys/value pairs: %d\n", s.KeyN) + fmt.Fprintf(cmd.Stdout, "\tNumber of levels in B+tree: %d\n", s.Depth) + + fmt.Fprintln(cmd.Stdout, "Page size utilization") + fmt.Fprintf(cmd.Stdout, "\tBytes allocated for physical branch pages: %d\n", s.BranchAlloc) + var percentage int + if s.BranchAlloc != 0 { + percentage = int(float32(s.BranchInuse) * 100.0 / float32(s.BranchAlloc)) + } + fmt.Fprintf(cmd.Stdout, "\tBytes actually used for branch data: %d (%d%%)\n", s.BranchInuse, percentage) + fmt.Fprintf(cmd.Stdout, "\tBytes allocated for physical leaf pages: %d\n", s.LeafAlloc) + percentage = 0 + if s.LeafAlloc != 0 { + percentage = int(float32(s.LeafInuse) * 100.0 / float32(s.LeafAlloc)) + } + fmt.Fprintf(cmd.Stdout, "\tBytes actually used for leaf data: %d (%d%%)\n", s.LeafInuse, percentage) + + fmt.Fprintln(cmd.Stdout, "Bucket statistics") + fmt.Fprintf(cmd.Stdout, "\tTotal number of buckets: %d\n", s.BucketN) + percentage = 0 + if s.BucketN != 0 { + percentage = int(float32(s.InlineBucketN) * 100.0 / float32(s.BucketN)) + } + fmt.Fprintf(cmd.Stdout, "\tTotal number on inlined buckets: %d (%d%%)\n", s.InlineBucketN, percentage) + percentage = 0 + if s.LeafInuse != 0 { + percentage = int(float32(s.InlineBucketInuse) * 100.0 / float32(s.LeafInuse)) + } + fmt.Fprintf(cmd.Stdout, "\tBytes used for inlined buckets: %d (%d%%)\n", s.InlineBucketInuse, percentage) + + return nil + }) +} + +// Usage returns the help message. +func (cmd *StatsCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt stats PATH + +Stats performs an extensive search of the database to track every page +reference. It starts at the current meta page and recursively iterates +through every accessible bucket. + +The following errors can be reported: + + already freed + The page is referenced more than once in the freelist. + + unreachable unfreed + The page is not referenced by a bucket or in the freelist. + + reachable freed + The page is referenced by a bucket but is also in the freelist. + + out of bounds + A page is referenced that is above the high water mark. + + multiple references + A page is referenced by more than one other page. + + invalid type + The page type is not "meta", "leaf", "branch", or "freelist". + +No errors should occur in your database. However, if for some reason you +experience corruption, please submit a ticket to the Bolt project page: + + https://github.com/boltdb/bolt/issues +`, "\n") +} + +// BucketsCommand represents the "buckets" command execution. +type BucketsCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewBucketsCommand returns a BucketsCommand. +func newBucketsCommand(m *Main) *BucketsCommand { + return &BucketsCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *BucketsCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path. + path := fs.Arg(0) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + // Print buckets. + return db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(name []byte, _ *bolt.Bucket) error { + fmt.Fprintln(cmd.Stdout, string(name)) + return nil + }) + }) +} + +// Usage returns the help message. +func (cmd *BucketsCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt buckets PATH + +Print a list of buckets. +`, "\n") +} + +// KeysCommand represents the "keys" command execution. +type KeysCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewKeysCommand returns a KeysCommand. +func newKeysCommand(m *Main) *KeysCommand { + return &KeysCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *KeysCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path and bucket. + path, bucket := fs.Arg(0), fs.Arg(1) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } else if bucket == "" { + return ErrBucketRequired + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + // Print keys. + return db.View(func(tx *bolt.Tx) error { + // Find bucket. + b := tx.Bucket([]byte(bucket)) + if b == nil { + return ErrBucketNotFound + } + + // Iterate over each key. + return b.ForEach(func(key, _ []byte) error { + fmt.Fprintln(cmd.Stdout, string(key)) + return nil + }) + }) +} + +// Usage returns the help message. +func (cmd *KeysCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt keys PATH BUCKET + +Print a list of keys in the given bucket. +`, "\n") +} + +// GetCommand represents the "get" command execution. +type GetCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewGetCommand returns a GetCommand. +func newGetCommand(m *Main) *GetCommand { + return &GetCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *GetCommand) Run(args ...string) error { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + help := fs.Bool("h", false, "") + if err := fs.Parse(args); err != nil { + return err + } else if *help { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } + + // Require database path, bucket and key. + path, bucket, key := fs.Arg(0), fs.Arg(1), fs.Arg(2) + if path == "" { + return ErrPathRequired + } else if _, err := os.Stat(path); os.IsNotExist(err) { + return ErrFileNotFound + } else if bucket == "" { + return ErrBucketRequired + } else if key == "" { + return ErrKeyRequired + } + + // Open database. + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return err + } + defer db.Close() + + // Print value. + return db.View(func(tx *bolt.Tx) error { + // Find bucket. + b := tx.Bucket([]byte(bucket)) + if b == nil { + return ErrBucketNotFound + } + + // Find value for given key. + val := b.Get([]byte(key)) + if val == nil { + return ErrKeyNotFound + } + + fmt.Fprintln(cmd.Stdout, string(val)) + return nil + }) +} + +// Usage returns the help message. +func (cmd *GetCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt get PATH BUCKET KEY + +Print the value of the given key in the given bucket. +`, "\n") +} + +var benchBucketName = []byte("bench") + +// BenchCommand represents the "bench" command execution. +type BenchCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +// NewBenchCommand returns a BenchCommand using the +func newBenchCommand(m *Main) *BenchCommand { + return &BenchCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the "bench" command. +func (cmd *BenchCommand) Run(args ...string) error { + // Parse CLI arguments. + options, err := cmd.ParseFlags(args) + if err != nil { + return err + } + + // Remove path if "-work" is not set. Otherwise keep path. + if options.Work { + fmt.Fprintf(cmd.Stdout, "work: %s\n", options.Path) + } else { + defer os.Remove(options.Path) + } + + // Create database. + db, err := bolt.Open(options.Path, 0666, nil) + if err != nil { + return err + } + db.NoSync = options.NoSync + defer db.Close() + + // Write to the database. + var results BenchResults + if err := cmd.runWrites(db, options, &results); err != nil { + return fmt.Errorf("write: %v", err) + } + + // Read from the database. + if err := cmd.runReads(db, options, &results); err != nil { + return fmt.Errorf("bench: read: %s", err) + } + + // Print results. + fmt.Fprintf(os.Stderr, "# Write\t%v\t(%v/op)\t(%v op/sec)\n", results.WriteDuration, results.WriteOpDuration(), results.WriteOpsPerSecond()) + fmt.Fprintf(os.Stderr, "# Read\t%v\t(%v/op)\t(%v op/sec)\n", results.ReadDuration, results.ReadOpDuration(), results.ReadOpsPerSecond()) + fmt.Fprintln(os.Stderr, "") + return nil +} + +// ParseFlags parses the command line flags. +func (cmd *BenchCommand) ParseFlags(args []string) (*BenchOptions, error) { + var options BenchOptions + + // Parse flagset. + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.StringVar(&options.ProfileMode, "profile-mode", "rw", "") + fs.StringVar(&options.WriteMode, "write-mode", "seq", "") + fs.StringVar(&options.ReadMode, "read-mode", "seq", "") + fs.IntVar(&options.Iterations, "count", 1000, "") + fs.IntVar(&options.BatchSize, "batch-size", 0, "") + fs.IntVar(&options.KeySize, "key-size", 8, "") + fs.IntVar(&options.ValueSize, "value-size", 32, "") + fs.StringVar(&options.CPUProfile, "cpuprofile", "", "") + fs.StringVar(&options.MemProfile, "memprofile", "", "") + fs.StringVar(&options.BlockProfile, "blockprofile", "", "") + fs.Float64Var(&options.FillPercent, "fill-percent", bolt.DefaultFillPercent, "") + fs.BoolVar(&options.NoSync, "no-sync", false, "") + fs.BoolVar(&options.Work, "work", false, "") + fs.StringVar(&options.Path, "path", "", "") + fs.SetOutput(cmd.Stderr) + if err := fs.Parse(args); err != nil { + return nil, err + } + + // Set batch size to iteration size if not set. + // Require that batch size can be evenly divided by the iteration count. + if options.BatchSize == 0 { + options.BatchSize = options.Iterations + } else if options.Iterations%options.BatchSize != 0 { + return nil, ErrNonDivisibleBatchSize + } + + // Generate temp path if one is not passed in. + if options.Path == "" { + f, err := ioutil.TempFile("", "bolt-bench-") + if err != nil { + return nil, fmt.Errorf("temp file: %s", err) + } + f.Close() + os.Remove(f.Name()) + options.Path = f.Name() + } + + return &options, nil +} + +// Writes to the database. +func (cmd *BenchCommand) runWrites(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + // Start profiling for writes. + if options.ProfileMode == "rw" || options.ProfileMode == "w" { + cmd.startProfiling(options) + } + + t := time.Now() + + var err error + switch options.WriteMode { + case "seq": + err = cmd.runWritesSequential(db, options, results) + case "rnd": + err = cmd.runWritesRandom(db, options, results) + case "seq-nest": + err = cmd.runWritesSequentialNested(db, options, results) + case "rnd-nest": + err = cmd.runWritesRandomNested(db, options, results) + default: + return fmt.Errorf("invalid write mode: %s", options.WriteMode) + } + + // Save time to write. + results.WriteDuration = time.Since(t) + + // Stop profiling for writes only. + if options.ProfileMode == "w" { + cmd.stopProfiling() + } + + return err +} + +func (cmd *BenchCommand) runWritesSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + var i = uint32(0) + return cmd.runWritesWithSource(db, options, results, func() uint32 { i++; return i }) +} + +func (cmd *BenchCommand) runWritesRandom(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return cmd.runWritesWithSource(db, options, results, func() uint32 { return r.Uint32() }) +} + +func (cmd *BenchCommand) runWritesSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + var i = uint32(0) + return cmd.runWritesNestedWithSource(db, options, results, func() uint32 { i++; return i }) +} + +func (cmd *BenchCommand) runWritesRandomNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return cmd.runWritesNestedWithSource(db, options, results, func() uint32 { return r.Uint32() }) +} + +func (cmd *BenchCommand) runWritesWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { + results.WriteOps = options.Iterations + + for i := 0; i < options.Iterations; i += options.BatchSize { + if err := db.Update(func(tx *bolt.Tx) error { + b, _ := tx.CreateBucketIfNotExists(benchBucketName) + b.FillPercent = options.FillPercent + + for j := 0; j < options.BatchSize; j++ { + key := make([]byte, options.KeySize) + value := make([]byte, options.ValueSize) + + // Write key as uint32. + binary.BigEndian.PutUint32(key, keySource()) + + // Insert key/value. + if err := b.Put(key, value); err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + } + return nil +} + +func (cmd *BenchCommand) runWritesNestedWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { + results.WriteOps = options.Iterations + + for i := 0; i < options.Iterations; i += options.BatchSize { + if err := db.Update(func(tx *bolt.Tx) error { + top, err := tx.CreateBucketIfNotExists(benchBucketName) + if err != nil { + return err + } + top.FillPercent = options.FillPercent + + // Create bucket key. + name := make([]byte, options.KeySize) + binary.BigEndian.PutUint32(name, keySource()) + + // Create bucket. + b, err := top.CreateBucketIfNotExists(name) + if err != nil { + return err + } + b.FillPercent = options.FillPercent + + for j := 0; j < options.BatchSize; j++ { + var key = make([]byte, options.KeySize) + var value = make([]byte, options.ValueSize) + + // Generate key as uint32. + binary.BigEndian.PutUint32(key, keySource()) + + // Insert value into subbucket. + if err := b.Put(key, value); err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + } + return nil +} + +// Reads from the database. +func (cmd *BenchCommand) runReads(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + // Start profiling for reads. + if options.ProfileMode == "r" { + cmd.startProfiling(options) + } + + t := time.Now() + + var err error + switch options.ReadMode { + case "seq": + switch options.WriteMode { + case "seq-nest", "rnd-nest": + err = cmd.runReadsSequentialNested(db, options, results) + default: + err = cmd.runReadsSequential(db, options, results) + } + default: + return fmt.Errorf("invalid read mode: %s", options.ReadMode) + } + + // Save read time. + results.ReadDuration = time.Since(t) + + // Stop profiling for reads. + if options.ProfileMode == "rw" || options.ProfileMode == "r" { + cmd.stopProfiling() + } + + return err +} + +func (cmd *BenchCommand) runReadsSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + return db.View(func(tx *bolt.Tx) error { + t := time.Now() + + for { + var count int + + c := tx.Bucket(benchBucketName).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + if v == nil { + return errors.New("invalid value") + } + count++ + } + + if options.WriteMode == "seq" && count != options.Iterations { + return fmt.Errorf("read seq: iter mismatch: expected %d, got %d", options.Iterations, count) + } + + results.ReadOps += count + + // Make sure we do this for at least a second. + if time.Since(t) >= time.Second { + break + } + } + + return nil + }) +} + +func (cmd *BenchCommand) runReadsSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + return db.View(func(tx *bolt.Tx) error { + t := time.Now() + + for { + var count int + var top = tx.Bucket(benchBucketName) + if err := top.ForEach(func(name, _ []byte) error { + if b := top.Bucket(name); b != nil { + c := b.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + if v == nil { + return ErrInvalidValue + } + count++ + } + } + return nil + }); err != nil { + return err + } + + if options.WriteMode == "seq-nest" && count != options.Iterations { + return fmt.Errorf("read seq-nest: iter mismatch: expected %d, got %d", options.Iterations, count) + } + + results.ReadOps += count + + // Make sure we do this for at least a second. + if time.Since(t) >= time.Second { + break + } + } + + return nil + }) +} + +// File handlers for the various profiles. +var cpuprofile, memprofile, blockprofile *os.File + +// Starts all profiles set on the options. +func (cmd *BenchCommand) startProfiling(options *BenchOptions) { + var err error + + // Start CPU profiling. + if options.CPUProfile != "" { + cpuprofile, err = os.Create(options.CPUProfile) + if err != nil { + fmt.Fprintf(cmd.Stderr, "bench: could not create cpu profile %q: %v\n", options.CPUProfile, err) + os.Exit(1) + } + pprof.StartCPUProfile(cpuprofile) + } + + // Start memory profiling. + if options.MemProfile != "" { + memprofile, err = os.Create(options.MemProfile) + if err != nil { + fmt.Fprintf(cmd.Stderr, "bench: could not create memory profile %q: %v\n", options.MemProfile, err) + os.Exit(1) + } + runtime.MemProfileRate = 4096 + } + + // Start fatal profiling. + if options.BlockProfile != "" { + blockprofile, err = os.Create(options.BlockProfile) + if err != nil { + fmt.Fprintf(cmd.Stderr, "bench: could not create block profile %q: %v\n", options.BlockProfile, err) + os.Exit(1) + } + runtime.SetBlockProfileRate(1) + } +} + +// Stops all profiles. +func (cmd *BenchCommand) stopProfiling() { + if cpuprofile != nil { + pprof.StopCPUProfile() + cpuprofile.Close() + cpuprofile = nil + } + + if memprofile != nil { + pprof.Lookup("heap").WriteTo(memprofile, 0) + memprofile.Close() + memprofile = nil + } + + if blockprofile != nil { + pprof.Lookup("block").WriteTo(blockprofile, 0) + blockprofile.Close() + blockprofile = nil + runtime.SetBlockProfileRate(0) + } +} + +// BenchOptions represents the set of options that can be passed to "bolt bench". +type BenchOptions struct { + ProfileMode string + WriteMode string + ReadMode string + Iterations int + BatchSize int + KeySize int + ValueSize int + CPUProfile string + MemProfile string + BlockProfile string + StatsInterval time.Duration + FillPercent float64 + NoSync bool + Work bool + Path string +} + +// BenchResults represents the performance results of the benchmark. +type BenchResults struct { + WriteOps int + WriteDuration time.Duration + ReadOps int + ReadDuration time.Duration +} + +// Returns the duration for a single write operation. +func (r *BenchResults) WriteOpDuration() time.Duration { + if r.WriteOps == 0 { + return 0 + } + return r.WriteDuration / time.Duration(r.WriteOps) +} + +// Returns average number of write operations that can be performed per second. +func (r *BenchResults) WriteOpsPerSecond() int { + var op = r.WriteOpDuration() + if op == 0 { + return 0 + } + return int(time.Second) / int(op) +} + +// Returns the duration for a single read operation. +func (r *BenchResults) ReadOpDuration() time.Duration { + if r.ReadOps == 0 { + return 0 + } + return r.ReadDuration / time.Duration(r.ReadOps) +} + +// Returns average number of read operations that can be performed per second. +func (r *BenchResults) ReadOpsPerSecond() int { + var op = r.ReadOpDuration() + if op == 0 { + return 0 + } + return int(time.Second) / int(op) +} + +type PageError struct { + ID int + Err error +} + +func (e *PageError) Error() string { + return fmt.Sprintf("page error: id=%d, err=%s", e.ID, e.Err) +} + +// isPrintable returns true if the string is valid unicode and contains only printable runes. +func isPrintable(s string) bool { + if !utf8.ValidString(s) { + return false + } + for _, ch := range s { + if !unicode.IsPrint(ch) { + return false + } + } + return true +} + +// ReadPage reads page info & full page data from a path. +// This is not transactionally safe. +func ReadPage(path string, pageID int) (*page, []byte, error) { + // Find page size. + pageSize, err := ReadPageSize(path) + if err != nil { + return nil, nil, fmt.Errorf("read page size: %s", err) + } + + // Open database file. + f, err := os.Open(path) + if err != nil { + return nil, nil, err + } + defer f.Close() + + // Read one block into buffer. + buf := make([]byte, pageSize) + if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil { + return nil, nil, err + } else if n != len(buf) { + return nil, nil, io.ErrUnexpectedEOF + } + + // Determine total number of blocks. + p := (*page)(unsafe.Pointer(&buf[0])) + overflowN := p.overflow + + // Re-read entire page (with overflow) into buffer. + buf = make([]byte, (int(overflowN)+1)*pageSize) + if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil { + return nil, nil, err + } else if n != len(buf) { + return nil, nil, io.ErrUnexpectedEOF + } + p = (*page)(unsafe.Pointer(&buf[0])) + + return p, buf, nil +} + +// ReadPageSize reads page size a path. +// This is not transactionally safe. +func ReadPageSize(path string) (int, error) { + // Open database file. + f, err := os.Open(path) + if err != nil { + return 0, err + } + defer f.Close() + + // Read 4KB chunk. + buf := make([]byte, 4096) + if _, err := io.ReadFull(f, buf); err != nil { + return 0, err + } + + // Read page size from metadata. + m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize])) + return int(m.pageSize), nil +} + +// atois parses a slice of strings into integers. +func atois(strs []string) ([]int, error) { + var a []int + for _, str := range strs { + i, err := strconv.Atoi(str) + if err != nil { + return nil, err + } + a = append(a, i) + } + return a, nil +} + +// DO NOT EDIT. Copied from the "bolt" package. +const maxAllocSize = 0xFFFFFFF + +// DO NOT EDIT. Copied from the "bolt" package. +const ( + branchPageFlag = 0x01 + leafPageFlag = 0x02 + metaPageFlag = 0x04 + freelistPageFlag = 0x10 +) + +// DO NOT EDIT. Copied from the "bolt" package. +const bucketLeafFlag = 0x01 + +// DO NOT EDIT. Copied from the "bolt" package. +type pgid uint64 + +// DO NOT EDIT. Copied from the "bolt" package. +type txid uint64 + +// DO NOT EDIT. Copied from the "bolt" package. +type meta struct { + magic uint32 + version uint32 + pageSize uint32 + flags uint32 + root bucket + freelist pgid + pgid pgid + txid txid + checksum uint64 +} + +// DO NOT EDIT. Copied from the "bolt" package. +type bucket struct { + root pgid + sequence uint64 +} + +// DO NOT EDIT. Copied from the "bolt" package. +type page struct { + id pgid + flags uint16 + count uint16 + overflow uint32 + ptr uintptr +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (p *page) Type() string { + if (p.flags & branchPageFlag) != 0 { + return "branch" + } else if (p.flags & leafPageFlag) != 0 { + return "leaf" + } else if (p.flags & metaPageFlag) != 0 { + return "meta" + } else if (p.flags & freelistPageFlag) != 0 { + return "freelist" + } + return fmt.Sprintf("unknown<%02x>", p.flags) +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (p *page) leafPageElement(index uint16) *leafPageElement { + n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index] + return n +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (p *page) branchPageElement(index uint16) *branchPageElement { + return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index] +} + +// DO NOT EDIT. Copied from the "bolt" package. +type branchPageElement struct { + pos uint32 + ksize uint32 + pgid pgid +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (n *branchPageElement) key() []byte { + buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) + return buf[n.pos : n.pos+n.ksize] +} + +// DO NOT EDIT. Copied from the "bolt" package. +type leafPageElement struct { + flags uint32 + pos uint32 + ksize uint32 + vsize uint32 +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (n *leafPageElement) key() []byte { + buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) + return buf[n.pos : n.pos+n.ksize] +} + +// DO NOT EDIT. Copied from the "bolt" package. +func (n *leafPageElement) value() []byte { + buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) + return buf[n.pos+n.ksize : n.pos+n.ksize+n.vsize] +} + +// CompactCommand represents the "compact" command execution. +type CompactCommand struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + + SrcPath string + DstPath string + TxMaxSize int64 +} + +// newCompactCommand returns a CompactCommand. +func newCompactCommand(m *Main) *CompactCommand { + return &CompactCommand{ + Stdin: m.Stdin, + Stdout: m.Stdout, + Stderr: m.Stderr, + } +} + +// Run executes the command. +func (cmd *CompactCommand) Run(args ...string) (err error) { + // Parse flags. + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.SetOutput(ioutil.Discard) + fs.StringVar(&cmd.DstPath, "o", "", "") + fs.Int64Var(&cmd.TxMaxSize, "tx-max-size", 65536, "") + if err := fs.Parse(args); err == flag.ErrHelp { + fmt.Fprintln(cmd.Stderr, cmd.Usage()) + return ErrUsage + } else if err != nil { + return err + } else if cmd.DstPath == "" { + return fmt.Errorf("output file required") + } + + // Require database paths. + cmd.SrcPath = fs.Arg(0) + if cmd.SrcPath == "" { + return ErrPathRequired + } + + // Ensure source file exists. + fi, err := os.Stat(cmd.SrcPath) + if os.IsNotExist(err) { + return ErrFileNotFound + } else if err != nil { + return err + } + initialSize := fi.Size() + + // Open source database. + src, err := bolt.Open(cmd.SrcPath, 0444, nil) + if err != nil { + return err + } + defer src.Close() + + // Open destination database. + dst, err := bolt.Open(cmd.DstPath, fi.Mode(), nil) + if err != nil { + return err + } + defer dst.Close() + + // Run compaction. + if err := cmd.compact(dst, src); err != nil { + return err + } + + // Report stats on new size. + fi, err = os.Stat(cmd.DstPath) + if err != nil { + return err + } else if fi.Size() == 0 { + return fmt.Errorf("zero db size") + } + fmt.Fprintf(cmd.Stdout, "%d -> %d bytes (gain=%.2fx)\n", initialSize, fi.Size(), float64(initialSize)/float64(fi.Size())) + + return nil +} + +func (cmd *CompactCommand) compact(dst, src *bolt.DB) error { + // commit regularly, or we'll run out of memory for large datasets if using one transaction. + var size int64 + tx, err := dst.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + if err := cmd.walk(src, func(keys [][]byte, k, v []byte, seq uint64) error { + // On each key/value, check if we have exceeded tx size. + sz := int64(len(k) + len(v)) + if size+sz > cmd.TxMaxSize && cmd.TxMaxSize != 0 { + // Commit previous transaction. + if err := tx.Commit(); err != nil { + return err + } + + // Start new transaction. + tx, err = dst.Begin(true) + if err != nil { + return err + } + size = 0 + } + size += sz + + // Create bucket on the root transaction if this is the first level. + nk := len(keys) + if nk == 0 { + bkt, err := tx.CreateBucket(k) + if err != nil { + return err + } + if err := bkt.SetSequence(seq); err != nil { + return err + } + return nil + } + + // Create buckets on subsequent levels, if necessary. + b := tx.Bucket(keys[0]) + if nk > 1 { + for _, k := range keys[1:] { + b = b.Bucket(k) + } + } + + // Fill the entire page for best compaction. + b.FillPercent = 1.0 + + // If there is no value then this is a bucket call. + if v == nil { + bkt, err := b.CreateBucket(k) + if err != nil { + return err + } + if err := bkt.SetSequence(seq); err != nil { + return err + } + return nil + } + + // Otherwise treat it as a key/value pair. + return b.Put(k, v) + }); err != nil { + return err + } + + return tx.Commit() +} + +// walkFunc is the type of the function called for keys (buckets and "normal" +// values) discovered by Walk. keys is the list of keys to descend to the bucket +// owning the discovered key/value pair k/v. +type walkFunc func(keys [][]byte, k, v []byte, seq uint64) error + +// walk walks recursively the bolt database db, calling walkFn for each key it finds. +func (cmd *CompactCommand) walk(db *bolt.DB, walkFn walkFunc) error { + return db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(name []byte, b *bolt.Bucket) error { + return cmd.walkBucket(b, nil, name, nil, b.Sequence(), walkFn) + }) + }) +} + +func (cmd *CompactCommand) walkBucket(b *bolt.Bucket, keypath [][]byte, k, v []byte, seq uint64, fn walkFunc) error { + // Execute callback. + if err := fn(keypath, k, v, seq); err != nil { + return err + } + + // If this is not a bucket then stop. + if v != nil { + return nil + } + + // Iterate over each child key/value. + keypath = append(keypath, k) + return b.ForEach(func(k, v []byte) error { + if v == nil { + bkt := b.Bucket(k) + return cmd.walkBucket(bkt, keypath, k, nil, bkt.Sequence(), fn) + } + return cmd.walkBucket(b, keypath, k, v, b.Sequence(), fn) + }) +} + +// Usage returns the help message. +func (cmd *CompactCommand) Usage() string { + return strings.TrimLeft(` +usage: bolt compact [options] -o DST SRC + +Compact opens a database at SRC path and walks it recursively, copying keys +as they are found from all buckets, to a newly created database at DST path. + +The original database is left untouched. + +Additional options include: + + -tx-max-size NUM + Specifies the maximum size of individual transactions. + Defaults to 64KB. +`, "\n") +} diff --git a/vendor/etcd.io/bbolt/cmd/bbolt/main_test b/vendor/etcd.io/bbolt/cmd/bbolt/main_test new file mode 100644 index 0000000..b4871ff --- /dev/null +++ b/vendor/etcd.io/bbolt/cmd/bbolt/main_test @@ -0,0 +1,455 @@ +package main_test + +import ( + "bytes" + crypto "crypto/rand" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "strconv" + "testing" + + bolt "go.etcd.io/bbolt" +) + +// Ensure the "info" command can print information about a database. +func TestInfoCommand_Run(t *testing.T) { + db := MustOpen(0666, nil) + db.DB.Close() + defer db.Close() + + // Run the info command. + m := NewMain() + if err := m.Run("info", db.Path); err != nil { + t.Fatal(err) + } +} + +// Ensure the "stats" command executes correctly with an empty database. +func TestStatsCommand_Run_EmptyDatabase(t *testing.T) { + // Ignore + if os.Getpagesize() != 4096 { + t.Skip("system does not use 4KB page size") + } + + db := MustOpen(0666, nil) + defer db.Close() + db.DB.Close() + + // Generate expected result. + exp := "Aggregate statistics for 0 buckets\n\n" + + "Page count statistics\n" + + "\tNumber of logical branch pages: 0\n" + + "\tNumber of physical branch overflow pages: 0\n" + + "\tNumber of logical leaf pages: 0\n" + + "\tNumber of physical leaf overflow pages: 0\n" + + "Tree statistics\n" + + "\tNumber of keys/value pairs: 0\n" + + "\tNumber of levels in B+tree: 0\n" + + "Page size utilization\n" + + "\tBytes allocated for physical branch pages: 0\n" + + "\tBytes actually used for branch data: 0 (0%)\n" + + "\tBytes allocated for physical leaf pages: 0\n" + + "\tBytes actually used for leaf data: 0 (0%)\n" + + "Bucket statistics\n" + + "\tTotal number of buckets: 0\n" + + "\tTotal number on inlined buckets: 0 (0%)\n" + + "\tBytes used for inlined buckets: 0 (0%)\n" + + // Run the command. + m := NewMain() + if err := m.Run("stats", db.Path); err != nil { + t.Fatal(err) + } else if m.Stdout.String() != exp { + t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String()) + } +} + +// Ensure the "stats" command can execute correctly. +func TestStatsCommand_Run(t *testing.T) { + // Ignore + if os.Getpagesize() != 4096 { + t.Skip("system does not use 4KB page size") + } + + db := MustOpen(0666, nil) + defer db.Close() + + if err := db.Update(func(tx *bolt.Tx) error { + // Create "foo" bucket. + b, err := tx.CreateBucket([]byte("foo")) + if err != nil { + return err + } + for i := 0; i < 10; i++ { + if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil { + return err + } + } + + // Create "bar" bucket. + b, err = tx.CreateBucket([]byte("bar")) + if err != nil { + return err + } + for i := 0; i < 100; i++ { + if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil { + return err + } + } + + // Create "baz" bucket. + b, err = tx.CreateBucket([]byte("baz")) + if err != nil { + return err + } + if err := b.Put([]byte("key"), []byte("value")); err != nil { + return err + } + + return nil + }); err != nil { + t.Fatal(err) + } + db.DB.Close() + + // Generate expected result. + exp := "Aggregate statistics for 3 buckets\n\n" + + "Page count statistics\n" + + "\tNumber of logical branch pages: 0\n" + + "\tNumber of physical branch overflow pages: 0\n" + + "\tNumber of logical leaf pages: 1\n" + + "\tNumber of physical leaf overflow pages: 0\n" + + "Tree statistics\n" + + "\tNumber of keys/value pairs: 111\n" + + "\tNumber of levels in B+tree: 1\n" + + "Page size utilization\n" + + "\tBytes allocated for physical branch pages: 0\n" + + "\tBytes actually used for branch data: 0 (0%)\n" + + "\tBytes allocated for physical leaf pages: 4096\n" + + "\tBytes actually used for leaf data: 1996 (48%)\n" + + "Bucket statistics\n" + + "\tTotal number of buckets: 3\n" + + "\tTotal number on inlined buckets: 2 (66%)\n" + + "\tBytes used for inlined buckets: 236 (11%)\n" + + // Run the command. + m := NewMain() + if err := m.Run("stats", db.Path); err != nil { + t.Fatal(err) + } else if m.Stdout.String() != exp { + t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String()) + } +} + +// Ensure the "buckets" command can print a list of buckets. +func TestBucketsCommand_Run(t *testing.T) { + db := MustOpen(0666, nil) + defer db.Close() + + if err := db.Update(func(tx *bolt.Tx) error { + for _, name := range []string{"foo", "bar", "baz"} { + _, err := tx.CreateBucket([]byte(name)) + if err != nil { + return err + } + } + return nil + }); err != nil { + t.Fatal(err) + } + db.DB.Close() + + expected := "bar\nbaz\nfoo\n" + + // Run the command. + m := NewMain() + if err := m.Run("buckets", db.Path); err != nil { + t.Fatal(err) + } else if actual := m.Stdout.String(); actual != expected { + t.Fatalf("unexpected stdout:\n\n%s", actual) + } +} + +// Ensure the "keys" command can print a list of keys for a bucket. +func TestKeysCommand_Run(t *testing.T) { + db := MustOpen(0666, nil) + defer db.Close() + + if err := db.Update(func(tx *bolt.Tx) error { + for _, name := range []string{"foo", "bar"} { + b, err := tx.CreateBucket([]byte(name)) + if err != nil { + return err + } + for i := 0; i < 3; i++ { + key := fmt.Sprintf("%s-%d", name, i) + if err := b.Put([]byte(key), []byte{0}); err != nil { + return err + } + } + } + return nil + }); err != nil { + t.Fatal(err) + } + db.DB.Close() + + expected := "foo-0\nfoo-1\nfoo-2\n" + + // Run the command. + m := NewMain() + if err := m.Run("keys", db.Path, "foo"); err != nil { + t.Fatal(err) + } else if actual := m.Stdout.String(); actual != expected { + t.Fatalf("unexpected stdout:\n\n%s", actual) + } +} + +// Ensure the "get" command can print the value of a key in a bucket. +func TestGetCommand_Run(t *testing.T) { + db := MustOpen(0666, nil) + defer db.Close() + + if err := db.Update(func(tx *bolt.Tx) error { + for _, name := range []string{"foo", "bar"} { + b, err := tx.CreateBucket([]byte(name)) + if err != nil { + return err + } + for i := 0; i < 3; i++ { + key := fmt.Sprintf("%s-%d", name, i) + val := fmt.Sprintf("val-%s-%d", name, i) + if err := b.Put([]byte(key), []byte(val)); err != nil { + return err + } + } + } + return nil + }); err != nil { + t.Fatal(err) + } + db.DB.Close() + + expected := "val-foo-1\n" + + // Run the command. + m := NewMain() + if err := m.Run("get", db.Path, "foo", "foo-1"); err != nil { + t.Fatal(err) + } else if actual := m.Stdout.String(); actual != expected { + t.Fatalf("unexpected stdout:\n\n%s", actual) + } +} + +// Main represents a test wrapper for main.Main that records output. +type Main struct { + *main.Main + Stdin bytes.Buffer + Stdout bytes.Buffer + Stderr bytes.Buffer +} + +// NewMain returns a new instance of Main. +func NewMain() *Main { + m := &Main{Main: main.NewMain()} + m.Main.Stdin = &m.Stdin + m.Main.Stdout = &m.Stdout + m.Main.Stderr = &m.Stderr + return m +} + +// MustOpen creates a Bolt database in a temporary location. +func MustOpen(mode os.FileMode, options *bolt.Options) *DB { + // Create temporary path. + f, _ := ioutil.TempFile("", "bolt-") + f.Close() + os.Remove(f.Name()) + + db, err := bolt.Open(f.Name(), mode, options) + if err != nil { + panic(err.Error()) + } + return &DB{DB: db, Path: f.Name()} +} + +// DB is a test wrapper for bolt.DB. +type DB struct { + *bolt.DB + Path string +} + +// Close closes and removes the database. +func (db *DB) Close() error { + defer os.Remove(db.Path) + return db.DB.Close() +} + +func TestCompactCommand_Run(t *testing.T) { + var s int64 + if err := binary.Read(crypto.Reader, binary.BigEndian, &s); err != nil { + t.Fatal(err) + } + rand.Seed(s) + + dstdb := MustOpen(0666, nil) + dstdb.Close() + + // fill the db + db := MustOpen(0666, nil) + if err := db.Update(func(tx *bolt.Tx) error { + n := 2 + rand.Intn(5) + for i := 0; i < n; i++ { + k := []byte(fmt.Sprintf("b%d", i)) + b, err := tx.CreateBucketIfNotExists(k) + if err != nil { + return err + } + if err := b.SetSequence(uint64(i)); err != nil { + return err + } + if err := fillBucket(b, append(k, '.')); err != nil { + return err + } + } + return nil + }); err != nil { + db.Close() + t.Fatal(err) + } + + // make the db grow by adding large values, and delete them. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("large_vals")) + if err != nil { + return err + } + n := 5 + rand.Intn(5) + for i := 0; i < n; i++ { + v := make([]byte, 1000*1000*(1+rand.Intn(5))) + _, err := crypto.Read(v) + if err != nil { + return err + } + if err := b.Put([]byte(fmt.Sprintf("l%d", i)), v); err != nil { + return err + } + } + return nil + }); err != nil { + db.Close() + t.Fatal(err) + } + if err := db.Update(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("large_vals")).Cursor() + for k, _ := c.First(); k != nil; k, _ = c.Next() { + if err := c.Delete(); err != nil { + return err + } + } + return tx.DeleteBucket([]byte("large_vals")) + }); err != nil { + db.Close() + t.Fatal(err) + } + db.DB.Close() + defer db.Close() + defer dstdb.Close() + + dbChk, err := chkdb(db.Path) + if err != nil { + t.Fatal(err) + } + + m := NewMain() + if err := m.Run("compact", "-o", dstdb.Path, db.Path); err != nil { + t.Fatal(err) + } + + dbChkAfterCompact, err := chkdb(db.Path) + if err != nil { + t.Fatal(err) + } + + dstdbChk, err := chkdb(dstdb.Path) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(dbChk, dbChkAfterCompact) { + t.Error("the original db has been touched") + } + if !bytes.Equal(dbChk, dstdbChk) { + t.Error("the compacted db data isn't the same than the original db") + } +} + +func fillBucket(b *bolt.Bucket, prefix []byte) error { + n := 10 + rand.Intn(50) + for i := 0; i < n; i++ { + v := make([]byte, 10*(1+rand.Intn(4))) + _, err := crypto.Read(v) + if err != nil { + return err + } + k := append(prefix, []byte(fmt.Sprintf("k%d", i))...) + if err := b.Put(k, v); err != nil { + return err + } + } + // limit depth of subbuckets + s := 2 + rand.Intn(4) + if len(prefix) > (2*s + 1) { + return nil + } + n = 1 + rand.Intn(3) + for i := 0; i < n; i++ { + k := append(prefix, []byte(fmt.Sprintf("b%d", i))...) + sb, err := b.CreateBucket(k) + if err != nil { + return err + } + if err := fillBucket(sb, append(k, '.')); err != nil { + return err + } + } + return nil +} + +func chkdb(path string) ([]byte, error) { + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return nil, err + } + defer db.Close() + var buf bytes.Buffer + err = db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(name []byte, b *bolt.Bucket) error { + return walkBucket(b, name, nil, &buf) + }) + }) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func walkBucket(parent *bolt.Bucket, k []byte, v []byte, w io.Writer) error { + if _, err := fmt.Fprintf(w, "%d:%x=%x\n", parent.Sequence(), k, v); err != nil { + return err + } + + // not a bucket, exit. + if v != nil { + return nil + } + return parent.ForEach(func(k, v []byte) error { + if v == nil { + return walkBucket(parent.Bucket(k), k, nil, w) + } + return walkBucket(parent, k, v, w) + }) +} diff --git a/vendor/etcd.io/bbolt/cursor.go b/vendor/etcd.io/bbolt/cursor.go new file mode 100644 index 0000000..98aeb44 --- /dev/null +++ b/vendor/etcd.io/bbolt/cursor.go @@ -0,0 +1,396 @@ +package bbolt + +import ( + "bytes" + "fmt" + "sort" +) + +// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order. +// Cursors see nested buckets with value == nil. +// Cursors can be obtained from a transaction and are valid as long as the transaction is open. +// +// Keys and values returned from the cursor are only valid for the life of the transaction. +// +// Changing data while traversing with a cursor may cause it to be invalidated +// and return unexpected keys and/or values. You must reposition your cursor +// after mutating data. +type Cursor struct { + bucket *Bucket + stack []elemRef +} + +// Bucket returns the bucket that this cursor was created from. +func (c *Cursor) Bucket() *Bucket { + return c.bucket +} + +// First moves the cursor to the first item in the bucket and returns its key and value. +// If the bucket is empty then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. +func (c *Cursor) First() (key []byte, value []byte) { + _assert(c.bucket.tx.db != nil, "tx closed") + c.stack = c.stack[:0] + p, n := c.bucket.pageNode(c.bucket.root) + c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) + c.first() + + // If we land on an empty page then move to the next value. + // https://github.com/boltdb/bolt/issues/450 + if c.stack[len(c.stack)-1].count() == 0 { + c.next() + } + + k, v, flags := c.keyValue() + if (flags & uint32(bucketLeafFlag)) != 0 { + return k, nil + } + return k, v + +} + +// Last moves the cursor to the last item in the bucket and returns its key and value. +// If the bucket is empty then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. +func (c *Cursor) Last() (key []byte, value []byte) { + _assert(c.bucket.tx.db != nil, "tx closed") + c.stack = c.stack[:0] + p, n := c.bucket.pageNode(c.bucket.root) + ref := elemRef{page: p, node: n} + ref.index = ref.count() - 1 + c.stack = append(c.stack, ref) + c.last() + k, v, flags := c.keyValue() + if (flags & uint32(bucketLeafFlag)) != 0 { + return k, nil + } + return k, v +} + +// Next moves the cursor to the next item in the bucket and returns its key and value. +// If the cursor is at the end of the bucket then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. +func (c *Cursor) Next() (key []byte, value []byte) { + _assert(c.bucket.tx.db != nil, "tx closed") + k, v, flags := c.next() + if (flags & uint32(bucketLeafFlag)) != 0 { + return k, nil + } + return k, v +} + +// Prev moves the cursor to the previous item in the bucket and returns its key and value. +// If the cursor is at the beginning of the bucket then a nil key and value are returned. +// The returned key and value are only valid for the life of the transaction. +func (c *Cursor) Prev() (key []byte, value []byte) { + _assert(c.bucket.tx.db != nil, "tx closed") + + // Attempt to move back one element until we're successful. + // Move up the stack as we hit the beginning of each page in our stack. + for i := len(c.stack) - 1; i >= 0; i-- { + elem := &c.stack[i] + if elem.index > 0 { + elem.index-- + break + } + c.stack = c.stack[:i] + } + + // If we've hit the end then return nil. + if len(c.stack) == 0 { + return nil, nil + } + + // Move down the stack to find the last element of the last leaf under this branch. + c.last() + k, v, flags := c.keyValue() + if (flags & uint32(bucketLeafFlag)) != 0 { + return k, nil + } + return k, v +} + +// Seek moves the cursor to a given key and returns it. +// If the key does not exist then the next key is used. If no keys +// follow, a nil key is returned. +// The returned key and value are only valid for the life of the transaction. +func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) { + k, v, flags := c.seek(seek) + + // If we ended up after the last element of a page then move to the next one. + if ref := &c.stack[len(c.stack)-1]; ref.index >= ref.count() { + k, v, flags = c.next() + } + + if k == nil { + return nil, nil + } else if (flags & uint32(bucketLeafFlag)) != 0 { + return k, nil + } + return k, v +} + +// Delete removes the current key/value under the cursor from the bucket. +// Delete fails if current key/value is a bucket or if the transaction is not writable. +func (c *Cursor) Delete() error { + if c.bucket.tx.db == nil { + return ErrTxClosed + } else if !c.bucket.Writable() { + return ErrTxNotWritable + } + + key, _, flags := c.keyValue() + // Return an error if current value is a bucket. + if (flags & bucketLeafFlag) != 0 { + return ErrIncompatibleValue + } + c.node().del(key) + + return nil +} + +// seek moves the cursor to a given key and returns it. +// If the key does not exist then the next key is used. +func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) { + _assert(c.bucket.tx.db != nil, "tx closed") + + // Start from root page/node and traverse to correct page. + c.stack = c.stack[:0] + c.search(seek, c.bucket.root) + + // If this is a bucket then return a nil value. + return c.keyValue() +} + +// first moves the cursor to the first leaf element under the last page in the stack. +func (c *Cursor) first() { + for { + // Exit when we hit a leaf page. + var ref = &c.stack[len(c.stack)-1] + if ref.isLeaf() { + break + } + + // Keep adding pages pointing to the first element to the stack. + var pgid pgid + if ref.node != nil { + pgid = ref.node.inodes[ref.index].pgid + } else { + pgid = ref.page.branchPageElement(uint16(ref.index)).pgid + } + p, n := c.bucket.pageNode(pgid) + c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) + } +} + +// last moves the cursor to the last leaf element under the last page in the stack. +func (c *Cursor) last() { + for { + // Exit when we hit a leaf page. + ref := &c.stack[len(c.stack)-1] + if ref.isLeaf() { + break + } + + // Keep adding pages pointing to the last element in the stack. + var pgid pgid + if ref.node != nil { + pgid = ref.node.inodes[ref.index].pgid + } else { + pgid = ref.page.branchPageElement(uint16(ref.index)).pgid + } + p, n := c.bucket.pageNode(pgid) + + var nextRef = elemRef{page: p, node: n} + nextRef.index = nextRef.count() - 1 + c.stack = append(c.stack, nextRef) + } +} + +// next moves to the next leaf element and returns the key and value. +// If the cursor is at the last leaf element then it stays there and returns nil. +func (c *Cursor) next() (key []byte, value []byte, flags uint32) { + for { + // Attempt to move over one element until we're successful. + // Move up the stack as we hit the end of each page in our stack. + var i int + for i = len(c.stack) - 1; i >= 0; i-- { + elem := &c.stack[i] + if elem.index < elem.count()-1 { + elem.index++ + break + } + } + + // If we've hit the root page then stop and return. This will leave the + // cursor on the last element of the last page. + if i == -1 { + return nil, nil, 0 + } + + // Otherwise start from where we left off in the stack and find the + // first element of the first leaf page. + c.stack = c.stack[:i+1] + c.first() + + // If this is an empty page then restart and move back up the stack. + // https://github.com/boltdb/bolt/issues/450 + if c.stack[len(c.stack)-1].count() == 0 { + continue + } + + return c.keyValue() + } +} + +// search recursively performs a binary search against a given page/node until it finds a given key. +func (c *Cursor) search(key []byte, pgid pgid) { + p, n := c.bucket.pageNode(pgid) + if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 { + panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags)) + } + e := elemRef{page: p, node: n} + c.stack = append(c.stack, e) + + // If we're on a leaf page/node then find the specific node. + if e.isLeaf() { + c.nsearch(key) + return + } + + if n != nil { + c.searchNode(key, n) + return + } + c.searchPage(key, p) +} + +func (c *Cursor) searchNode(key []byte, n *node) { + var exact bool + index := sort.Search(len(n.inodes), func(i int) bool { + // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. + // sort.Search() finds the lowest index where f() != -1 but we need the highest index. + ret := bytes.Compare(n.inodes[i].key, key) + if ret == 0 { + exact = true + } + return ret != -1 + }) + if !exact && index > 0 { + index-- + } + c.stack[len(c.stack)-1].index = index + + // Recursively search to the next page. + c.search(key, n.inodes[index].pgid) +} + +func (c *Cursor) searchPage(key []byte, p *page) { + // Binary search for the correct range. + inodes := p.branchPageElements() + + var exact bool + index := sort.Search(int(p.count), func(i int) bool { + // TODO(benbjohnson): Optimize this range search. It's a bit hacky right now. + // sort.Search() finds the lowest index where f() != -1 but we need the highest index. + ret := bytes.Compare(inodes[i].key(), key) + if ret == 0 { + exact = true + } + return ret != -1 + }) + if !exact && index > 0 { + index-- + } + c.stack[len(c.stack)-1].index = index + + // Recursively search to the next page. + c.search(key, inodes[index].pgid) +} + +// nsearch searches the leaf node on the top of the stack for a key. +func (c *Cursor) nsearch(key []byte) { + e := &c.stack[len(c.stack)-1] + p, n := e.page, e.node + + // If we have a node then search its inodes. + if n != nil { + index := sort.Search(len(n.inodes), func(i int) bool { + return bytes.Compare(n.inodes[i].key, key) != -1 + }) + e.index = index + return + } + + // If we have a page then search its leaf elements. + inodes := p.leafPageElements() + index := sort.Search(int(p.count), func(i int) bool { + return bytes.Compare(inodes[i].key(), key) != -1 + }) + e.index = index +} + +// keyValue returns the key and value of the current leaf element. +func (c *Cursor) keyValue() ([]byte, []byte, uint32) { + ref := &c.stack[len(c.stack)-1] + + // If the cursor is pointing to the end of page/node then return nil. + if ref.count() == 0 || ref.index >= ref.count() { + return nil, nil, 0 + } + + // Retrieve value from node. + if ref.node != nil { + inode := &ref.node.inodes[ref.index] + return inode.key, inode.value, inode.flags + } + + // Or retrieve value from page. + elem := ref.page.leafPageElement(uint16(ref.index)) + return elem.key(), elem.value(), elem.flags +} + +// node returns the node that the cursor is currently positioned on. +func (c *Cursor) node() *node { + _assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") + + // If the top of the stack is a leaf node then just return it. + if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() { + return ref.node + } + + // Start from root and traverse down the hierarchy. + var n = c.stack[0].node + if n == nil { + n = c.bucket.node(c.stack[0].page.id, nil) + } + for _, ref := range c.stack[:len(c.stack)-1] { + _assert(!n.isLeaf, "expected branch node") + n = n.childAt(ref.index) + } + _assert(n.isLeaf, "expected leaf node") + return n +} + +// elemRef represents a reference to an element on a given page/node. +type elemRef struct { + page *page + node *node + index int +} + +// isLeaf returns whether the ref is pointing at a leaf page/node. +func (r *elemRef) isLeaf() bool { + if r.node != nil { + return r.node.isLeaf + } + return (r.page.flags & leafPageFlag) != 0 +} + +// count returns the number of inodes or page elements. +func (r *elemRef) count() int { + if r.node != nil { + return len(r.node.inodes) + } + return int(r.page.count) +} diff --git a/vendor/etcd.io/bbolt/cursor_test.go b/vendor/etcd.io/bbolt/cursor_test.go new file mode 100644 index 0000000..d2a8bc7 --- /dev/null +++ b/vendor/etcd.io/bbolt/cursor_test.go @@ -0,0 +1,817 @@ +package bbolt_test + +import ( + "bytes" + "encoding/binary" + "fmt" + "log" + "os" + "reflect" + "sort" + "testing" + "testing/quick" + + bolt "go.etcd.io/bbolt" +) + +// Ensure that a cursor can return a reference to the bucket that created it. +func TestCursor_Bucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) { + t.Fatal("cursor bucket mismatch") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can seek to the appropriate keys. +func TestCursor_Seek(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("0001")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte("0002")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("0003")); err != nil { + t.Fatal(err) + } + + if _, err := b.CreateBucket([]byte("bkt")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("widgets")).Cursor() + + // Exact match should go to the key. + if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0002")) { + t.Fatalf("unexpected value: %v", v) + } + + // Inexact match should go to the next key. + if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0003")) { + t.Fatalf("unexpected value: %v", v) + } + + // Low key should go to the first key. + if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte("0002")) { + t.Fatalf("unexpected value: %v", v) + } + + // High key should return no key. + if k, v := c.Seek([]byte("zzz")); k != nil { + t.Fatalf("expected nil key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + // Buckets should return their key but no value. + if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestCursor_Delete(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + const count = 1000 + + // Insert every other key between 0 and $count. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for i := 0; i < count; i += 1 { + k := make([]byte, 8) + binary.BigEndian.PutUint64(k, uint64(i)) + if err := b.Put(k, make([]byte, 100)); err != nil { + t.Fatal(err) + } + } + if _, err := b.CreateBucket([]byte("sub")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("widgets")).Cursor() + bound := make([]byte, 8) + binary.BigEndian.PutUint64(bound, uint64(count/2)) + for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() { + if err := c.Delete(); err != nil { + t.Fatal(err) + } + } + + c.Seek([]byte("sub")) + if err := c.Delete(); err != bolt.ErrIncompatibleValue { + t.Fatalf("unexpected error: %s", err) + } + + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + stats := tx.Bucket([]byte("widgets")).Stats() + if stats.KeyN != count/2+1 { + t.Fatalf("unexpected KeyN: %d", stats.KeyN) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can seek to the appropriate keys when there are a +// large number of keys. This test also checks that seek will always move +// forward to the next key. +// +// Related: https://github.com/boltdb/bolt/pull/187 +func TestCursor_Seek_Large(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var count = 10000 + + // Insert every other key between 0 and $count. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < count; i += 100 { + for j := i; j < i+100; j += 2 { + k := make([]byte, 8) + binary.BigEndian.PutUint64(k, uint64(j)) + if err := b.Put(k, make([]byte, 100)); err != nil { + t.Fatal(err) + } + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("widgets")).Cursor() + for i := 0; i < count; i++ { + seek := make([]byte, 8) + binary.BigEndian.PutUint64(seek, uint64(i)) + + k, _ := c.Seek(seek) + + // The last seek is beyond the end of the the range so + // it should return nil. + if i == count-1 { + if k != nil { + t.Fatal("expected nil key") + } + continue + } + + // Otherwise we should seek to the exact key or the next key. + num := binary.BigEndian.Uint64(k) + if i%2 == 0 { + if num != uint64(i) { + t.Fatalf("unexpected num: %d", num) + } + } else { + if num != uint64(i+1) { + t.Fatalf("unexpected num: %d", num) + } + } + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a cursor can iterate over an empty bucket without error. +func TestCursor_EmptyBucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("widgets")).Cursor() + k, v := c.First() + if k != nil { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can reverse iterate over an empty bucket without error. +func TestCursor_EmptyBucketReverse(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + if err := db.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte("widgets")).Cursor() + k, v := c.Last() + if k != nil { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can iterate over a single root with a couple elements. +func TestCursor_Iterate_Leaf(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte{}); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte{0}); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte{1}); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + tx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + defer func() { _ = tx.Rollback() }() + + c := tx.Bucket([]byte("widgets")).Cursor() + + k, v := c.First() + if !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{1}) { + t.Fatalf("unexpected value: %v", v) + } + + k, v = c.Next() + if !bytes.Equal(k, []byte("baz")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{}) { + t.Fatalf("unexpected value: %v", v) + } + + k, v = c.Next() + if !bytes.Equal(k, []byte("foo")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{0}) { + t.Fatalf("unexpected value: %v", v) + } + + k, v = c.Next() + if k != nil { + t.Fatalf("expected nil key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + k, v = c.Next() + if k != nil { + t.Fatalf("expected nil key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements. +func TestCursor_LeafRootReverse(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte{}); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte{0}); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte{1}); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + tx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + c := tx.Bucket([]byte("widgets")).Cursor() + + if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{0}) { + t.Fatalf("unexpected value: %v", v) + } + + if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{}) { + t.Fatalf("unexpected value: %v", v) + } + + if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, []byte{1}) { + t.Fatalf("unexpected value: %v", v) + } + + if k, v := c.Prev(); k != nil { + t.Fatalf("expected nil key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + if k, v := c.Prev(); k != nil { + t.Fatalf("expected nil key: %v", k) + } else if v != nil { + t.Fatalf("expected nil value: %v", v) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can restart from the beginning. +func TestCursor_Restart(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("bar"), []byte{}); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte{}); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + tx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + c := tx.Bucket([]byte("widgets")).Cursor() + + if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } + if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) { + t.Fatalf("unexpected key: %v", k) + } + + if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) { + t.Fatalf("unexpected key: %v", k) + } + if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) { + t.Fatalf("unexpected key: %v", k) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } +} + +// Ensure that a cursor can skip over empty pages that have been deleted. +func TestCursor_First_EmptyPages(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Create 1000 keys in the "widgets" bucket. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 1000; i++ { + if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil { + t.Fatal(err) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + + // Delete half the keys and then try to iterate. + if err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 0; i < 600; i++ { + if err := b.Delete(u64tob(uint64(i))); err != nil { + t.Fatal(err) + } + } + + c := b.Cursor() + var n int + for k, _ := c.First(); k != nil; k, _ = c.Next() { + n++ + } + if n != 400 { + t.Fatalf("unexpected key count: %d", n) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx can iterate over all elements in a bucket. +func TestCursor_QuickCheck(t *testing.T) { + f := func(items testdata) bool { + db := MustOpenDB() + defer db.MustClose() + + // Bulk insert all values. + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for _, item := range items { + if err := b.Put(item.Key, item.Value); err != nil { + t.Fatal(err) + } + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + + // Sort test data. + sort.Sort(items) + + // Iterate over all items and check consistency. + var index = 0 + tx, err = db.Begin(false) + if err != nil { + t.Fatal(err) + } + + c := tx.Bucket([]byte("widgets")).Cursor() + for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { + if !bytes.Equal(k, items[index].Key) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, items[index].Value) { + t.Fatalf("unexpected value: %v", v) + } + index++ + } + if len(items) != index { + t.Fatalf("unexpected item count: %v, expected %v", len(items), index) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + + return true + } + if err := quick.Check(f, qconfig()); err != nil { + t.Error(err) + } +} + +// Ensure that a transaction can iterate over all elements in a bucket in reverse. +func TestCursor_QuickCheck_Reverse(t *testing.T) { + f := func(items testdata) bool { + db := MustOpenDB() + defer db.MustClose() + + // Bulk insert all values. + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + for _, item := range items { + if err := b.Put(item.Key, item.Value); err != nil { + t.Fatal(err) + } + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + + // Sort test data. + sort.Sort(revtestdata(items)) + + // Iterate over all items and check consistency. + var index = 0 + tx, err = db.Begin(false) + if err != nil { + t.Fatal(err) + } + c := tx.Bucket([]byte("widgets")).Cursor() + for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { + if !bytes.Equal(k, items[index].Key) { + t.Fatalf("unexpected key: %v", k) + } else if !bytes.Equal(v, items[index].Value) { + t.Fatalf("unexpected value: %v", v) + } + index++ + } + if len(items) != index { + t.Fatalf("unexpected item count: %v, expected %v", len(items), index) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + + return true + } + if err := quick.Check(f, qconfig()); err != nil { + t.Error(err) + } +} + +// Ensure that a Tx cursor can iterate over subbuckets. +func TestCursor_QuickCheck_BucketsOnly(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("bar")); err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("baz")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + var names []string + c := tx.Bucket([]byte("widgets")).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + names = append(names, string(k)) + if v != nil { + t.Fatalf("unexpected value: %v", v) + } + } + if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) { + t.Fatalf("unexpected names: %+v", names) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx cursor can reverse iterate over subbuckets. +func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("bar")); err != nil { + t.Fatal(err) + } + if _, err := b.CreateBucket([]byte("baz")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + var names []string + c := tx.Bucket([]byte("widgets")).Cursor() + for k, v := c.Last(); k != nil; k, v = c.Prev() { + names = append(names, string(k)) + if v != nil { + t.Fatalf("unexpected value: %v", v) + } + } + if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) { + t.Fatalf("unexpected names: %+v", names) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func ExampleCursor() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Start a read-write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + // Create a new bucket. + b, err := tx.CreateBucket([]byte("animals")) + if err != nil { + return err + } + + // Insert data into a bucket. + if err := b.Put([]byte("dog"), []byte("fun")); err != nil { + log.Fatal(err) + } + if err := b.Put([]byte("cat"), []byte("lame")); err != nil { + log.Fatal(err) + } + if err := b.Put([]byte("liger"), []byte("awesome")); err != nil { + log.Fatal(err) + } + + // Create a cursor for iteration. + c := b.Cursor() + + // Iterate over items in sorted key order. This starts from the + // first key/value pair and updates the k/v variables to the + // next key/value on each iteration. + // + // The loop finishes at the end of the cursor when a nil key is returned. + for k, v := c.First(); k != nil; k, v = c.Next() { + fmt.Printf("A %s is %s.\n", k, v) + } + + return nil + }); err != nil { + log.Fatal(err) + } + + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // A cat is lame. + // A dog is fun. + // A liger is awesome. +} + +func ExampleCursor_reverse() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Start a read-write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + // Create a new bucket. + b, err := tx.CreateBucket([]byte("animals")) + if err != nil { + return err + } + + // Insert data into a bucket. + if err := b.Put([]byte("dog"), []byte("fun")); err != nil { + log.Fatal(err) + } + if err := b.Put([]byte("cat"), []byte("lame")); err != nil { + log.Fatal(err) + } + if err := b.Put([]byte("liger"), []byte("awesome")); err != nil { + log.Fatal(err) + } + + // Create a cursor for iteration. + c := b.Cursor() + + // Iterate over items in reverse sorted key order. This starts + // from the last key/value pair and updates the k/v variables to + // the previous key/value on each iteration. + // + // The loop finishes at the beginning of the cursor when a nil key + // is returned. + for k, v := c.Last(); k != nil; k, v = c.Prev() { + fmt.Printf("A %s is %s.\n", k, v) + } + + return nil + }); err != nil { + log.Fatal(err) + } + + // Close the database to release the file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // A liger is awesome. + // A dog is fun. + // A cat is lame. +} diff --git a/vendor/etcd.io/bbolt/db.go b/vendor/etcd.io/bbolt/db.go new file mode 100644 index 0000000..80b0095 --- /dev/null +++ b/vendor/etcd.io/bbolt/db.go @@ -0,0 +1,1174 @@ +package bbolt + +import ( + "errors" + "fmt" + "hash/fnv" + "log" + "os" + "runtime" + "sort" + "sync" + "time" + "unsafe" +) + +// The largest step that can be taken when remapping the mmap. +const maxMmapStep = 1 << 30 // 1GB + +// The data file format version. +const version = 2 + +// Represents a marker value to indicate that a file is a Bolt DB. +const magic uint32 = 0xED0CDAED + +const pgidNoFreelist pgid = 0xffffffffffffffff + +// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when +// syncing changes to a file. This is required as some operating systems, +// such as OpenBSD, do not have a unified buffer cache (UBC) and writes +// must be synchronized using the msync(2) syscall. +const IgnoreNoSync = runtime.GOOS == "openbsd" + +// Default values if not set in a DB instance. +const ( + DefaultMaxBatchSize int = 1000 + DefaultMaxBatchDelay = 10 * time.Millisecond + DefaultAllocSize = 16 * 1024 * 1024 +) + +// default page size for db is set to the OS page size. +var defaultPageSize = os.Getpagesize() + +// The time elapsed between consecutive file locking attempts. +const flockRetryTimeout = 50 * time.Millisecond + +// FreelistType is the type of the freelist backend +type FreelistType string + +const ( + // FreelistArrayType indicates backend freelist type is array + FreelistArrayType = FreelistType("array") + // FreelistMapType indicates backend freelist type is hashmap + FreelistMapType = FreelistType("hashmap") +) + +// DB represents a collection of buckets persisted to a file on disk. +// All data access is performed through transactions which can be obtained through the DB. +// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. +type DB struct { + // When enabled, the database will perform a Check() after every commit. + // A panic is issued if the database is in an inconsistent state. This + // flag has a large performance impact so it should only be used for + // debugging purposes. + StrictMode bool + + // Setting the NoSync flag will cause the database to skip fsync() + // calls after each commit. This can be useful when bulk loading data + // into a database and you can restart the bulk load in the event of + // a system failure or database corruption. Do not set this flag for + // normal use. + // + // If the package global IgnoreNoSync constant is true, this value is + // ignored. See the comment on that constant for more details. + // + // THIS IS UNSAFE. PLEASE USE WITH CAUTION. + NoSync bool + + // When true, skips syncing freelist to disk. This improves the database + // write performance under normal operation, but requires a full database + // re-sync during recovery. + NoFreelistSync bool + + // FreelistType sets the backend freelist type. There are two options. Array which is simple but endures + // dramatic performance degradation if database is large and framentation in freelist is common. + // The alternative one is using hashmap, it is faster in almost all circumstances + // but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe. + // The default type is array + FreelistType FreelistType + + // When true, skips the truncate call when growing the database. + // Setting this to true is only safe on non-ext3/ext4 systems. + // Skipping truncation avoids preallocation of hard drive space and + // bypasses a truncate() and fsync() syscall on remapping. + // + // https://github.com/boltdb/bolt/issues/284 + NoGrowSync bool + + // If you want to read the entire database fast, you can set MmapFlag to + // syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead. + MmapFlags int + + // MaxBatchSize is the maximum size of a batch. Default value is + // copied from DefaultMaxBatchSize in Open. + // + // If <=0, disables batching. + // + // Do not change concurrently with calls to Batch. + MaxBatchSize int + + // MaxBatchDelay is the maximum delay before a batch starts. + // Default value is copied from DefaultMaxBatchDelay in Open. + // + // If <=0, effectively disables batching. + // + // Do not change concurrently with calls to Batch. + MaxBatchDelay time.Duration + + // AllocSize is the amount of space allocated when the database + // needs to create new pages. This is done to amortize the cost + // of truncate() and fsync() when growing the data file. + AllocSize int + + path string + openFile func(string, int, os.FileMode) (*os.File, error) + file *os.File + dataref []byte // mmap'ed readonly, write throws SEGV + data *[maxMapSize]byte + datasz int + filesz int // current on disk file size + meta0 *meta + meta1 *meta + pageSize int + opened bool + rwtx *Tx + txs []*Tx + stats Stats + + freelist *freelist + freelistLoad sync.Once + + pagePool sync.Pool + + batchMu sync.Mutex + batch *batch + + rwlock sync.Mutex // Allows only one writer at a time. + metalock sync.Mutex // Protects meta page access. + mmaplock sync.RWMutex // Protects mmap access during remapping. + statlock sync.RWMutex // Protects stats access. + + ops struct { + writeAt func(b []byte, off int64) (n int, err error) + } + + // Read only mode. + // When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately. + readOnly bool +} + +// Path returns the path to currently open database file. +func (db *DB) Path() string { + return db.path +} + +// GoString returns the Go string representation of the database. +func (db *DB) GoString() string { + return fmt.Sprintf("bolt.DB{path:%q}", db.path) +} + +// String returns the string representation of the database. +func (db *DB) String() string { + return fmt.Sprintf("DB<%q>", db.path) +} + +// Open creates and opens a database at the given path. +// If the file does not exist then it will be created automatically. +// Passing in nil options will cause Bolt to open the database with the default options. +func Open(path string, mode os.FileMode, options *Options) (*DB, error) { + db := &DB{ + opened: true, + } + // Set default options if no options are provided. + if options == nil { + options = DefaultOptions + } + db.NoSync = options.NoSync + db.NoGrowSync = options.NoGrowSync + db.MmapFlags = options.MmapFlags + db.NoFreelistSync = options.NoFreelistSync + db.FreelistType = options.FreelistType + + // Set default values for later DB operations. + db.MaxBatchSize = DefaultMaxBatchSize + db.MaxBatchDelay = DefaultMaxBatchDelay + db.AllocSize = DefaultAllocSize + + flag := os.O_RDWR + if options.ReadOnly { + flag = os.O_RDONLY + db.readOnly = true + } + + db.openFile = options.OpenFile + if db.openFile == nil { + db.openFile = os.OpenFile + } + + // Open data file and separate sync handler for metadata writes. + var err error + if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil { + _ = db.close() + return nil, err + } + db.path = db.file.Name() + + // Lock file so that other processes using Bolt in read-write mode cannot + // use the database at the same time. This would cause corruption since + // the two processes would write meta pages and free pages separately. + // The database file is locked exclusively (only one process can grab the lock) + // if !options.ReadOnly. + // The database file is locked using the shared lock (more than one process may + // hold a lock at the same time) otherwise (options.ReadOnly is set). + if err := flock(db, !db.readOnly, options.Timeout); err != nil { + _ = db.close() + return nil, err + } + + // Default values for test hooks + db.ops.writeAt = db.file.WriteAt + + if db.pageSize = options.PageSize; db.pageSize == 0 { + // Set the default page size to the OS page size. + db.pageSize = defaultPageSize + } + + // Initialize the database if it doesn't exist. + if info, err := db.file.Stat(); err != nil { + _ = db.close() + return nil, err + } else if info.Size() == 0 { + // Initialize new files with meta pages. + if err := db.init(); err != nil { + // clean up file descriptor on initialization fail + _ = db.close() + return nil, err + } + } else { + // Read the first meta page to determine the page size. + var buf [0x1000]byte + // If we can't read the page size, but can read a page, assume + // it's the same as the OS or one given -- since that's how the + // page size was chosen in the first place. + // + // If the first page is invalid and this OS uses a different + // page size than what the database was created with then we + // are out of luck and cannot access the database. + // + // TODO: scan for next page + if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) { + if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil { + db.pageSize = int(m.pageSize) + } + } else { + _ = db.close() + return nil, ErrInvalid + } + } + + // Initialize page pool. + db.pagePool = sync.Pool{ + New: func() interface{} { + return make([]byte, db.pageSize) + }, + } + + // Memory map the data file. + if err := db.mmap(options.InitialMmapSize); err != nil { + _ = db.close() + return nil, err + } + + if db.readOnly { + return db, nil + } + + db.loadFreelist() + + // Flush freelist when transitioning from no sync to sync so + // NoFreelistSync unaware boltdb can open the db later. + if !db.NoFreelistSync && !db.hasSyncedFreelist() { + tx, err := db.Begin(true) + if tx != nil { + err = tx.Commit() + } + if err != nil { + _ = db.close() + return nil, err + } + } + + // Mark the database as opened and return. + return db, nil +} + +// loadFreelist reads the freelist if it is synced, or reconstructs it +// by scanning the DB if it is not synced. It assumes there are no +// concurrent accesses being made to the freelist. +func (db *DB) loadFreelist() { + db.freelistLoad.Do(func() { + db.freelist = newFreelist(db.FreelistType) + if !db.hasSyncedFreelist() { + // Reconstruct free list by scanning the DB. + db.freelist.readIDs(db.freepages()) + } else { + // Read free list from freelist page. + db.freelist.read(db.page(db.meta().freelist)) + } + db.stats.FreePageN = db.freelist.free_count() + }) +} + +func (db *DB) hasSyncedFreelist() bool { + return db.meta().freelist != pgidNoFreelist +} + +// mmap opens the underlying memory-mapped file and initializes the meta references. +// minsz is the minimum size that the new mmap can be. +func (db *DB) mmap(minsz int) error { + db.mmaplock.Lock() + defer db.mmaplock.Unlock() + + info, err := db.file.Stat() + if err != nil { + return fmt.Errorf("mmap stat error: %s", err) + } else if int(info.Size()) < db.pageSize*2 { + return fmt.Errorf("file size too small") + } + + // Ensure the size is at least the minimum size. + var size = int(info.Size()) + if size < minsz { + size = minsz + } + size, err = db.mmapSize(size) + if err != nil { + return err + } + + // Dereference all mmap references before unmapping. + if db.rwtx != nil { + db.rwtx.root.dereference() + } + + // Unmap existing data before continuing. + if err := db.munmap(); err != nil { + return err + } + + // Memory-map the data file as a byte slice. + if err := mmap(db, size); err != nil { + return err + } + + // Save references to the meta pages. + db.meta0 = db.page(0).meta() + db.meta1 = db.page(1).meta() + + // Validate the meta pages. We only return an error if both meta pages fail + // validation, since meta0 failing validation means that it wasn't saved + // properly -- but we can recover using meta1. And vice-versa. + err0 := db.meta0.validate() + err1 := db.meta1.validate() + if err0 != nil && err1 != nil { + return err0 + } + + return nil +} + +// munmap unmaps the data file from memory. +func (db *DB) munmap() error { + if err := munmap(db); err != nil { + return fmt.Errorf("unmap error: " + err.Error()) + } + return nil +} + +// mmapSize determines the appropriate size for the mmap given the current size +// of the database. The minimum size is 32KB and doubles until it reaches 1GB. +// Returns an error if the new mmap size is greater than the max allowed. +func (db *DB) mmapSize(size int) (int, error) { + // Double the size from 32KB until 1GB. + for i := uint(15); i <= 30; i++ { + if size <= 1< maxMapSize { + return 0, fmt.Errorf("mmap too large") + } + + // If larger than 1GB then grow by 1GB at a time. + sz := int64(size) + if remainder := sz % int64(maxMmapStep); remainder > 0 { + sz += int64(maxMmapStep) - remainder + } + + // Ensure that the mmap size is a multiple of the page size. + // This should always be true since we're incrementing in MBs. + pageSize := int64(db.pageSize) + if (sz % pageSize) != 0 { + sz = ((sz / pageSize) + 1) * pageSize + } + + // If we've exceeded the max size then only grow up to the max size. + if sz > maxMapSize { + sz = maxMapSize + } + + return int(sz), nil +} + +// init creates a new database file and initializes its meta pages. +func (db *DB) init() error { + // Create two meta pages on a buffer. + buf := make([]byte, db.pageSize*4) + for i := 0; i < 2; i++ { + p := db.pageInBuffer(buf[:], pgid(i)) + p.id = pgid(i) + p.flags = metaPageFlag + + // Initialize the meta page. + m := p.meta() + m.magic = magic + m.version = version + m.pageSize = uint32(db.pageSize) + m.freelist = 2 + m.root = bucket{root: 3} + m.pgid = 4 + m.txid = txid(i) + m.checksum = m.sum64() + } + + // Write an empty freelist at page 3. + p := db.pageInBuffer(buf[:], pgid(2)) + p.id = pgid(2) + p.flags = freelistPageFlag + p.count = 0 + + // Write an empty leaf page at page 4. + p = db.pageInBuffer(buf[:], pgid(3)) + p.id = pgid(3) + p.flags = leafPageFlag + p.count = 0 + + // Write the buffer to our data file. + if _, err := db.ops.writeAt(buf, 0); err != nil { + return err + } + if err := fdatasync(db); err != nil { + return err + } + + return nil +} + +// Close releases all database resources. +// It will block waiting for any open transactions to finish +// before closing the database and returning. +func (db *DB) Close() error { + db.rwlock.Lock() + defer db.rwlock.Unlock() + + db.metalock.Lock() + defer db.metalock.Unlock() + + db.mmaplock.Lock() + defer db.mmaplock.Unlock() + + return db.close() +} + +func (db *DB) close() error { + if !db.opened { + return nil + } + + db.opened = false + + db.freelist = nil + + // Clear ops. + db.ops.writeAt = nil + + // Close the mmap. + if err := db.munmap(); err != nil { + return err + } + + // Close file handles. + if db.file != nil { + // No need to unlock read-only file. + if !db.readOnly { + // Unlock the file. + if err := funlock(db); err != nil { + log.Printf("bolt.Close(): funlock error: %s", err) + } + } + + // Close the file descriptor. + if err := db.file.Close(); err != nil { + return fmt.Errorf("db file close: %s", err) + } + db.file = nil + } + + db.path = "" + return nil +} + +// Begin starts a new transaction. +// Multiple read-only transactions can be used concurrently but only one +// write transaction can be used at a time. Starting multiple write transactions +// will cause the calls to block and be serialized until the current write +// transaction finishes. +// +// Transactions should not be dependent on one another. Opening a read +// transaction and a write transaction in the same goroutine can cause the +// writer to deadlock because the database periodically needs to re-mmap itself +// as it grows and it cannot do that while a read transaction is open. +// +// If a long running read transaction (for example, a snapshot transaction) is +// needed, you might want to set DB.InitialMmapSize to a large enough value +// to avoid potential blocking of write transaction. +// +// IMPORTANT: You must close read-only transactions after you are finished or +// else the database will not reclaim old pages. +func (db *DB) Begin(writable bool) (*Tx, error) { + if writable { + return db.beginRWTx() + } + return db.beginTx() +} + +func (db *DB) beginTx() (*Tx, error) { + // Lock the meta pages while we initialize the transaction. We obtain + // the meta lock before the mmap lock because that's the order that the + // write transaction will obtain them. + db.metalock.Lock() + + // Obtain a read-only lock on the mmap. When the mmap is remapped it will + // obtain a write lock so all transactions must finish before it can be + // remapped. + db.mmaplock.RLock() + + // Exit if the database is not open yet. + if !db.opened { + db.mmaplock.RUnlock() + db.metalock.Unlock() + return nil, ErrDatabaseNotOpen + } + + // Create a transaction associated with the database. + t := &Tx{} + t.init(db) + + // Keep track of transaction until it closes. + db.txs = append(db.txs, t) + n := len(db.txs) + + // Unlock the meta pages. + db.metalock.Unlock() + + // Update the transaction stats. + db.statlock.Lock() + db.stats.TxN++ + db.stats.OpenTxN = n + db.statlock.Unlock() + + return t, nil +} + +func (db *DB) beginRWTx() (*Tx, error) { + // If the database was opened with Options.ReadOnly, return an error. + if db.readOnly { + return nil, ErrDatabaseReadOnly + } + + // Obtain writer lock. This is released by the transaction when it closes. + // This enforces only one writer transaction at a time. + db.rwlock.Lock() + + // Once we have the writer lock then we can lock the meta pages so that + // we can set up the transaction. + db.metalock.Lock() + defer db.metalock.Unlock() + + // Exit if the database is not open yet. + if !db.opened { + db.rwlock.Unlock() + return nil, ErrDatabaseNotOpen + } + + // Create a transaction associated with the database. + t := &Tx{writable: true} + t.init(db) + db.rwtx = t + db.freePages() + return t, nil +} + +// freePages releases any pages associated with closed read-only transactions. +func (db *DB) freePages() { + // Free all pending pages prior to earliest open transaction. + sort.Sort(txsById(db.txs)) + minid := txid(0xFFFFFFFFFFFFFFFF) + if len(db.txs) > 0 { + minid = db.txs[0].meta.txid + } + if minid > 0 { + db.freelist.release(minid - 1) + } + // Release unused txid extents. + for _, t := range db.txs { + db.freelist.releaseRange(minid, t.meta.txid-1) + minid = t.meta.txid + 1 + } + db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF)) + // Any page both allocated and freed in an extent is safe to release. +} + +type txsById []*Tx + +func (t txsById) Len() int { return len(t) } +func (t txsById) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t txsById) Less(i, j int) bool { return t[i].meta.txid < t[j].meta.txid } + +// removeTx removes a transaction from the database. +func (db *DB) removeTx(tx *Tx) { + // Release the read lock on the mmap. + db.mmaplock.RUnlock() + + // Use the meta lock to restrict access to the DB object. + db.metalock.Lock() + + // Remove the transaction. + for i, t := range db.txs { + if t == tx { + last := len(db.txs) - 1 + db.txs[i] = db.txs[last] + db.txs[last] = nil + db.txs = db.txs[:last] + break + } + } + n := len(db.txs) + + // Unlock the meta pages. + db.metalock.Unlock() + + // Merge statistics. + db.statlock.Lock() + db.stats.OpenTxN = n + db.stats.TxStats.add(&tx.stats) + db.statlock.Unlock() +} + +// Update executes a function within the context of a read-write managed transaction. +// If no error is returned from the function then the transaction is committed. +// If an error is returned then the entire transaction is rolled back. +// Any error that is returned from the function or returned from the commit is +// returned from the Update() method. +// +// Attempting to manually commit or rollback within the function will cause a panic. +func (db *DB) Update(fn func(*Tx) error) error { + t, err := db.Begin(true) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if t.db != nil { + t.rollback() + } + }() + + // Mark as a managed tx so that the inner function cannot manually commit. + t.managed = true + + // If an error is returned from the function then rollback and return error. + err = fn(t) + t.managed = false + if err != nil { + _ = t.Rollback() + return err + } + + return t.Commit() +} + +// View executes a function within the context of a managed read-only transaction. +// Any error that is returned from the function is returned from the View() method. +// +// Attempting to manually rollback within the function will cause a panic. +func (db *DB) View(fn func(*Tx) error) error { + t, err := db.Begin(false) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if t.db != nil { + t.rollback() + } + }() + + // Mark as a managed tx so that the inner function cannot manually rollback. + t.managed = true + + // If an error is returned from the function then pass it through. + err = fn(t) + t.managed = false + if err != nil { + _ = t.Rollback() + return err + } + + return t.Rollback() +} + +// Batch calls fn as part of a batch. It behaves similar to Update, +// except: +// +// 1. concurrent Batch calls can be combined into a single Bolt +// transaction. +// +// 2. the function passed to Batch may be called multiple times, +// regardless of whether it returns error or not. +// +// This means that Batch function side effects must be idempotent and +// take permanent effect only after a successful return is seen in +// caller. +// +// The maximum batch size and delay can be adjusted with DB.MaxBatchSize +// and DB.MaxBatchDelay, respectively. +// +// Batch is only useful when there are multiple goroutines calling it. +func (db *DB) Batch(fn func(*Tx) error) error { + errCh := make(chan error, 1) + + db.batchMu.Lock() + if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) { + // There is no existing batch, or the existing batch is full; start a new one. + db.batch = &batch{ + db: db, + } + db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger) + } + db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh}) + if len(db.batch.calls) >= db.MaxBatchSize { + // wake up batch, it's ready to run + go db.batch.trigger() + } + db.batchMu.Unlock() + + err := <-errCh + if err == trySolo { + err = db.Update(fn) + } + return err +} + +type call struct { + fn func(*Tx) error + err chan<- error +} + +type batch struct { + db *DB + timer *time.Timer + start sync.Once + calls []call +} + +// trigger runs the batch if it hasn't already been run. +func (b *batch) trigger() { + b.start.Do(b.run) +} + +// run performs the transactions in the batch and communicates results +// back to DB.Batch. +func (b *batch) run() { + b.db.batchMu.Lock() + b.timer.Stop() + // Make sure no new work is added to this batch, but don't break + // other batches. + if b.db.batch == b { + b.db.batch = nil + } + b.db.batchMu.Unlock() + +retry: + for len(b.calls) > 0 { + var failIdx = -1 + err := b.db.Update(func(tx *Tx) error { + for i, c := range b.calls { + if err := safelyCall(c.fn, tx); err != nil { + failIdx = i + return err + } + } + return nil + }) + + if failIdx >= 0 { + // take the failing transaction out of the batch. it's + // safe to shorten b.calls here because db.batch no longer + // points to us, and we hold the mutex anyway. + c := b.calls[failIdx] + b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1] + // tell the submitter re-run it solo, continue with the rest of the batch + c.err <- trySolo + continue retry + } + + // pass success, or bolt internal errors, to all callers + for _, c := range b.calls { + c.err <- err + } + break retry + } +} + +// trySolo is a special sentinel error value used for signaling that a +// transaction function should be re-run. It should never be seen by +// callers. +var trySolo = errors.New("batch function returned an error and should be re-run solo") + +type panicked struct { + reason interface{} +} + +func (p panicked) Error() string { + if err, ok := p.reason.(error); ok { + return err.Error() + } + return fmt.Sprintf("panic: %v", p.reason) +} + +func safelyCall(fn func(*Tx) error, tx *Tx) (err error) { + defer func() { + if p := recover(); p != nil { + err = panicked{p} + } + }() + return fn(tx) +} + +// Sync executes fdatasync() against the database file handle. +// +// This is not necessary under normal operation, however, if you use NoSync +// then it allows you to force the database file to sync against the disk. +func (db *DB) Sync() error { return fdatasync(db) } + +// Stats retrieves ongoing performance stats for the database. +// This is only updated when a transaction closes. +func (db *DB) Stats() Stats { + db.statlock.RLock() + defer db.statlock.RUnlock() + return db.stats +} + +// This is for internal access to the raw data bytes from the C cursor, use +// carefully, or not at all. +func (db *DB) Info() *Info { + return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize} +} + +// page retrieves a page reference from the mmap based on the current page size. +func (db *DB) page(id pgid) *page { + pos := id * pgid(db.pageSize) + return (*page)(unsafe.Pointer(&db.data[pos])) +} + +// pageInBuffer retrieves a page reference from a given byte array based on the current page size. +func (db *DB) pageInBuffer(b []byte, id pgid) *page { + return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)])) +} + +// meta retrieves the current meta page reference. +func (db *DB) meta() *meta { + // We have to return the meta with the highest txid which doesn't fail + // validation. Otherwise, we can cause errors when in fact the database is + // in a consistent state. metaA is the one with the higher txid. + metaA := db.meta0 + metaB := db.meta1 + if db.meta1.txid > db.meta0.txid { + metaA = db.meta1 + metaB = db.meta0 + } + + // Use higher meta page if valid. Otherwise fallback to previous, if valid. + if err := metaA.validate(); err == nil { + return metaA + } else if err := metaB.validate(); err == nil { + return metaB + } + + // This should never be reached, because both meta1 and meta0 were validated + // on mmap() and we do fsync() on every write. + panic("bolt.DB.meta(): invalid meta pages") +} + +// allocate returns a contiguous block of memory starting at a given page. +func (db *DB) allocate(txid txid, count int) (*page, error) { + // Allocate a temporary buffer for the page. + var buf []byte + if count == 1 { + buf = db.pagePool.Get().([]byte) + } else { + buf = make([]byte, count*db.pageSize) + } + p := (*page)(unsafe.Pointer(&buf[0])) + p.overflow = uint32(count - 1) + + // Use pages from the freelist if they are available. + if p.id = db.freelist.allocate(txid, count); p.id != 0 { + return p, nil + } + + // Resize mmap() if we're at the end. + p.id = db.rwtx.meta.pgid + var minsz = int((p.id+pgid(count))+1) * db.pageSize + if minsz >= db.datasz { + if err := db.mmap(minsz); err != nil { + return nil, fmt.Errorf("mmap allocate error: %s", err) + } + } + + // Move the page id high water mark. + db.rwtx.meta.pgid += pgid(count) + + return p, nil +} + +// grow grows the size of the database to the given sz. +func (db *DB) grow(sz int) error { + // Ignore if the new size is less than available file size. + if sz <= db.filesz { + return nil + } + + // If the data is smaller than the alloc size then only allocate what's needed. + // Once it goes over the allocation size then allocate in chunks. + if db.datasz < db.AllocSize { + sz = db.datasz + } else { + sz += db.AllocSize + } + + // Truncate and fsync to ensure file size metadata is flushed. + // https://github.com/boltdb/bolt/issues/284 + if !db.NoGrowSync && !db.readOnly { + if runtime.GOOS != "windows" { + if err := db.file.Truncate(int64(sz)); err != nil { + return fmt.Errorf("file resize error: %s", err) + } + } + if err := db.file.Sync(); err != nil { + return fmt.Errorf("file sync error: %s", err) + } + } + + db.filesz = sz + return nil +} + +func (db *DB) IsReadOnly() bool { + return db.readOnly +} + +func (db *DB) freepages() []pgid { + tx, err := db.beginTx() + defer func() { + err = tx.Rollback() + if err != nil { + panic("freepages: failed to rollback tx") + } + }() + if err != nil { + panic("freepages: failed to open read only tx") + } + + reachable := make(map[pgid]*page) + nofreed := make(map[pgid]bool) + ech := make(chan error) + go func() { + for e := range ech { + panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e)) + } + }() + tx.checkBucket(&tx.root, reachable, nofreed, ech) + close(ech) + + var fids []pgid + for i := pgid(2); i < db.meta().pgid; i++ { + if _, ok := reachable[i]; !ok { + fids = append(fids, i) + } + } + return fids +} + +// Options represents the options that can be set when opening a database. +type Options struct { + // Timeout is the amount of time to wait to obtain a file lock. + // When set to zero it will wait indefinitely. This option is only + // available on Darwin and Linux. + Timeout time.Duration + + // Sets the DB.NoGrowSync flag before memory mapping the file. + NoGrowSync bool + + // Do not sync freelist to disk. This improves the database write performance + // under normal operation, but requires a full database re-sync during recovery. + NoFreelistSync bool + + // FreelistType sets the backend freelist type. There are two options. Array which is simple but endures + // dramatic performance degradation if database is large and framentation in freelist is common. + // The alternative one is using hashmap, it is faster in almost all circumstances + // but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe. + // The default type is array + FreelistType FreelistType + + // Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to + // grab a shared lock (UNIX). + ReadOnly bool + + // Sets the DB.MmapFlags flag before memory mapping the file. + MmapFlags int + + // InitialMmapSize is the initial mmap size of the database + // in bytes. Read transactions won't block write transaction + // if the InitialMmapSize is large enough to hold database mmap + // size. (See DB.Begin for more information) + // + // If <=0, the initial map size is 0. + // If initialMmapSize is smaller than the previous database size, + // it takes no effect. + InitialMmapSize int + + // PageSize overrides the default OS page size. + PageSize int + + // NoSync sets the initial value of DB.NoSync. Normally this can just be + // set directly on the DB itself when returned from Open(), but this option + // is useful in APIs which expose Options but not the underlying DB. + NoSync bool + + // OpenFile is used to open files. It defaults to os.OpenFile. This option + // is useful for writing hermetic tests. + OpenFile func(string, int, os.FileMode) (*os.File, error) +} + +// DefaultOptions represent the options used if nil options are passed into Open(). +// No timeout is used which will cause Bolt to wait indefinitely for a lock. +var DefaultOptions = &Options{ + Timeout: 0, + NoGrowSync: false, + FreelistType: FreelistArrayType, +} + +// Stats represents statistics about the database. +type Stats struct { + // Freelist stats + FreePageN int // total number of free pages on the freelist + PendingPageN int // total number of pending pages on the freelist + FreeAlloc int // total bytes allocated in free pages + FreelistInuse int // total bytes used by the freelist + + // Transaction stats + TxN int // total number of started read transactions + OpenTxN int // number of currently open read transactions + + TxStats TxStats // global, ongoing stats. +} + +// Sub calculates and returns the difference between two sets of database stats. +// This is useful when obtaining stats at two different points and time and +// you need the performance counters that occurred within that time span. +func (s *Stats) Sub(other *Stats) Stats { + if other == nil { + return *s + } + var diff Stats + diff.FreePageN = s.FreePageN + diff.PendingPageN = s.PendingPageN + diff.FreeAlloc = s.FreeAlloc + diff.FreelistInuse = s.FreelistInuse + diff.TxN = s.TxN - other.TxN + diff.TxStats = s.TxStats.Sub(&other.TxStats) + return diff +} + +type Info struct { + Data uintptr + PageSize int +} + +type meta struct { + magic uint32 + version uint32 + pageSize uint32 + flags uint32 + root bucket + freelist pgid + pgid pgid + txid txid + checksum uint64 +} + +// validate checks the marker bytes and version of the meta page to ensure it matches this binary. +func (m *meta) validate() error { + if m.magic != magic { + return ErrInvalid + } else if m.version != version { + return ErrVersionMismatch + } else if m.checksum != 0 && m.checksum != m.sum64() { + return ErrChecksum + } + return nil +} + +// copy copies one meta object to another. +func (m *meta) copy(dest *meta) { + *dest = *m +} + +// write writes the meta onto a page. +func (m *meta) write(p *page) { + if m.root.root >= m.pgid { + panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid)) + } else if m.freelist >= m.pgid && m.freelist != pgidNoFreelist { + // TODO: reject pgidNoFreeList if !NoFreelistSync + panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid)) + } + + // Page id is either going to be 0 or 1 which we can determine by the transaction ID. + p.id = pgid(m.txid % 2) + p.flags |= metaPageFlag + + // Calculate the checksum. + m.checksum = m.sum64() + + m.copy(p.meta()) +} + +// generates the checksum for the meta. +func (m *meta) sum64() uint64 { + var h = fnv.New64a() + _, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:]) + return h.Sum64() +} + +// _assert will panic with a given formatted message if the given condition is false. +func _assert(condition bool, msg string, v ...interface{}) { + if !condition { + panic(fmt.Sprintf("assertion failed: "+msg, v...)) + } +} diff --git a/vendor/etcd.io/bbolt/db_test.go b/vendor/etcd.io/bbolt/db_test.go new file mode 100644 index 0000000..9b03f2f --- /dev/null +++ b/vendor/etcd.io/bbolt/db_test.go @@ -0,0 +1,1783 @@ +package bbolt_test + +import ( + "bytes" + "encoding/binary" + "errors" + "flag" + "fmt" + "hash/fnv" + "io/ioutil" + "log" + "math/rand" + "os" + "path/filepath" + "regexp" + "sync" + "testing" + "time" + "unsafe" + + bolt "go.etcd.io/bbolt" +) + +var statsFlag = flag.Bool("stats", false, "show performance stats") + +// pageSize is the size of one page in the data file. +const pageSize = 4096 + +// pageHeaderSize is the size of a page header. +const pageHeaderSize = 16 + +// meta represents a simplified version of a database meta page for testing. +type meta struct { + magic uint32 + version uint32 + _ uint32 + _ uint32 + _ [16]byte + _ uint64 + pgid uint64 + _ uint64 + checksum uint64 +} + +// Ensure that a database can be opened without error. +func TestOpen(t *testing.T) { + path := tempfile() + defer os.RemoveAll(path) + + db, err := bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } else if db == nil { + t.Fatal("expected db") + } + + if s := db.Path(); s != path { + t.Fatalf("unexpected path: %s", s) + } + + if err := db.Close(); err != nil { + t.Fatal(err) + } +} + +// Regression validation for https://github.com/etcd-io/bbolt/pull/122. +// Tests multiple goroutines simultaneously opening a database. +func TestOpen_MultipleGoroutines(t *testing.T) { + const ( + instances = 30 + iterations = 30 + ) + path := tempfile() + defer os.RemoveAll(path) + var wg sync.WaitGroup + errCh := make(chan error, iterations*instances) + for iteration := 0; iteration < iterations; iteration++ { + for instance := 0; instance < instances; instance++ { + wg.Add(1) + go func() { + defer wg.Done() + db, err := bolt.Open(path, 0600, nil) + if err != nil { + errCh <- err + return + } + if err := db.Close(); err != nil { + errCh <- err + return + } + }() + } + wg.Wait() + } + close(errCh) + for err := range errCh { + if err != nil { + t.Fatalf("error from inside goroutine: %v", err) + } + } +} + +// Ensure that opening a database with a blank path returns an error. +func TestOpen_ErrPathRequired(t *testing.T) { + _, err := bolt.Open("", 0666, nil) + if err == nil { + t.Fatalf("expected error") + } +} + +// Ensure that opening a database with a bad path returns an error. +func TestOpen_ErrNotExists(t *testing.T) { + _, err := bolt.Open(filepath.Join(tempfile(), "bad-path"), 0666, nil) + if err == nil { + t.Fatal("expected error") + } +} + +// Ensure that opening a file that is not a Bolt database returns ErrInvalid. +func TestOpen_ErrInvalid(t *testing.T) { + path := tempfile() + defer os.RemoveAll(path) + + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + if _, err := fmt.Fprintln(f, "this is not a bolt database"); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrInvalid { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that opening a file with two invalid versions returns ErrVersionMismatch. +func TestOpen_ErrVersionMismatch(t *testing.T) { + if pageSize != os.Getpagesize() { + t.Skip("page size mismatch") + } + + // Create empty database. + db := MustOpenDB() + path := db.Path() + defer db.MustClose() + + // Close database. + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + // Read data file. + buf, err := ioutil.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + // Rewrite meta pages. + meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) + meta0.version++ + meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) + meta1.version++ + if err := ioutil.WriteFile(path, buf, 0666); err != nil { + t.Fatal(err) + } + + // Reopen data file. + if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrVersionMismatch { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that opening a file with two invalid checksums returns ErrChecksum. +func TestOpen_ErrChecksum(t *testing.T) { + if pageSize != os.Getpagesize() { + t.Skip("page size mismatch") + } + + // Create empty database. + db := MustOpenDB() + path := db.Path() + defer db.MustClose() + + // Close database. + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + // Read data file. + buf, err := ioutil.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + // Rewrite meta pages. + meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) + meta0.pgid++ + meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) + meta1.pgid++ + if err := ioutil.WriteFile(path, buf, 0666); err != nil { + t.Fatal(err) + } + + // Reopen data file. + if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that opening a database does not increase its size. +// https://github.com/boltdb/bolt/issues/291 +func TestOpen_Size(t *testing.T) { + // Open a data file. + db := MustOpenDB() + path := db.Path() + defer db.MustClose() + + pagesize := db.Info().PageSize + + // Insert until we get above the minimum 4MB size. + if err := db.Update(func(tx *bolt.Tx) error { + b, _ := tx.CreateBucketIfNotExists([]byte("data")) + for i := 0; i < 10000; i++ { + if err := b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)); err != nil { + t.Fatal(err) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Close database and grab the size. + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + sz := fileSize(path) + if sz == 0 { + t.Fatalf("unexpected new file size: %d", sz) + } + + // Reopen database, update, and check size again. + db0, err := bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } + if err := db0.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + if err := db0.Close(); err != nil { + t.Fatal(err) + } + newSz := fileSize(path) + if newSz == 0 { + t.Fatalf("unexpected new file size: %d", newSz) + } + + // Compare the original size with the new size. + // db size might increase by a few page sizes due to the new small update. + if sz < newSz-5*int64(pagesize) { + t.Fatalf("unexpected file growth: %d => %d", sz, newSz) + } +} + +// Ensure that opening a database beyond the max step size does not increase its size. +// https://github.com/boltdb/bolt/issues/303 +func TestOpen_Size_Large(t *testing.T) { + if testing.Short() { + t.Skip("short mode") + } + + // Open a data file. + db := MustOpenDB() + path := db.Path() + defer db.MustClose() + + pagesize := db.Info().PageSize + + // Insert until we get above the minimum 4MB size. + var index uint64 + for i := 0; i < 10000; i++ { + if err := db.Update(func(tx *bolt.Tx) error { + b, _ := tx.CreateBucketIfNotExists([]byte("data")) + for j := 0; j < 1000; j++ { + if err := b.Put(u64tob(index), make([]byte, 50)); err != nil { + t.Fatal(err) + } + index++ + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + // Close database and grab the size. + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + sz := fileSize(path) + if sz == 0 { + t.Fatalf("unexpected new file size: %d", sz) + } else if sz < (1 << 30) { + t.Fatalf("expected larger initial size: %d", sz) + } + + // Reopen database, update, and check size again. + db0, err := bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } + if err := db0.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) + }); err != nil { + t.Fatal(err) + } + if err := db0.Close(); err != nil { + t.Fatal(err) + } + + newSz := fileSize(path) + if newSz == 0 { + t.Fatalf("unexpected new file size: %d", newSz) + } + + // Compare the original size with the new size. + // db size might increase by a few page sizes due to the new small update. + if sz < newSz-5*int64(pagesize) { + t.Fatalf("unexpected file growth: %d => %d", sz, newSz) + } +} + +// Ensure that a re-opened database is consistent. +func TestOpen_Check(t *testing.T) { + path := tempfile() + defer os.RemoveAll(path) + + db, err := bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } + if err = db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil { + t.Fatal(err) + } + if err = db.Close(); err != nil { + t.Fatal(err) + } + + db, err = bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } + if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil { + t.Fatal(err) + } + if err := db.Close(); err != nil { + t.Fatal(err) + } +} + +// Ensure that write errors to the meta file handler during initialization are returned. +func TestOpen_MetaInitWriteError(t *testing.T) { + t.Skip("pending") +} + +// Ensure that a database that is too small returns an error. +func TestOpen_FileTooSmall(t *testing.T) { + path := tempfile() + defer os.RemoveAll(path) + + db, err := bolt.Open(path, 0666, nil) + if err != nil { + t.Fatal(err) + } + pageSize := int64(db.Info().PageSize) + if err = db.Close(); err != nil { + t.Fatal(err) + } + + // corrupt the database + if err = os.Truncate(path, pageSize); err != nil { + t.Fatal(err) + } + + db, err = bolt.Open(path, 0666, nil) + if err == nil || err.Error() != "file size too small" { + t.Fatalf("unexpected error: %s", err) + } +} + +// TestDB_Open_InitialMmapSize tests if having InitialMmapSize large enough +// to hold data from concurrent write transaction resolves the issue that +// read transaction blocks the write transaction and causes deadlock. +// This is a very hacky test since the mmap size is not exposed. +func TestDB_Open_InitialMmapSize(t *testing.T) { + path := tempfile() + defer os.Remove(path) + + initMmapSize := 1 << 30 // 1GB + testWriteSize := 1 << 27 // 134MB + + db, err := bolt.Open(path, 0666, &bolt.Options{InitialMmapSize: initMmapSize}) + if err != nil { + t.Fatal(err) + } + + // create a long-running read transaction + // that never gets closed while writing + rtx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + + // create a write transaction + wtx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + b, err := wtx.CreateBucket([]byte("test")) + if err != nil { + t.Fatal(err) + } + + // and commit a large write + err = b.Put([]byte("foo"), make([]byte, testWriteSize)) + if err != nil { + t.Fatal(err) + } + + done := make(chan error, 1) + + go func() { + err := wtx.Commit() + done <- err + }() + + select { + case <-time.After(5 * time.Second): + t.Errorf("unexpected that the reader blocks writer") + case err := <-done: + if err != nil { + t.Fatal(err) + } + } + + if err := rtx.Rollback(); err != nil { + t.Fatal(err) + } +} + +// TestDB_Open_ReadOnly checks a database in read only mode can read but not write. +func TestDB_Open_ReadOnly(t *testing.T) { + // Create a writable db, write k-v and close it. + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + f := db.f + o := &bolt.Options{ReadOnly: true} + readOnlyDB, err := bolt.Open(f, 0666, o) + if err != nil { + panic(err) + } + + if !readOnlyDB.IsReadOnly() { + t.Fatal("expect db in read only mode") + } + + // Read from a read-only transaction. + if err := readOnlyDB.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + if !bytes.Equal(value, []byte("bar")) { + t.Fatal("expect value 'bar', got", value) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Can't launch read-write transaction. + if _, err := readOnlyDB.Begin(true); err != bolt.ErrDatabaseReadOnly { + t.Fatalf("unexpected error: %s", err) + } + + if err := readOnlyDB.Close(); err != nil { + t.Fatal(err) + } +} + +// TestOpen_BigPage checks the database uses bigger pages when +// changing PageSize. +func TestOpen_BigPage(t *testing.T) { + pageSize := os.Getpagesize() + + db1 := MustOpenWithOption(&bolt.Options{PageSize: pageSize * 2}) + defer db1.MustClose() + + db2 := MustOpenWithOption(&bolt.Options{PageSize: pageSize * 4}) + defer db2.MustClose() + + if db1sz, db2sz := fileSize(db1.f), fileSize(db2.f); db1sz >= db2sz { + t.Errorf("expected %d < %d", db1sz, db2sz) + } +} + +// TestOpen_RecoverFreeList tests opening the DB with free-list +// write-out after no free list sync will recover the free list +// and write it out. +func TestOpen_RecoverFreeList(t *testing.T) { + db := MustOpenWithOption(&bolt.Options{NoFreelistSync: true}) + defer db.MustClose() + + // Write some pages. + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + wbuf := make([]byte, 8192) + for i := 0; i < 100; i++ { + s := fmt.Sprintf("%d", i) + b, err := tx.CreateBucket([]byte(s)) + if err != nil { + t.Fatal(err) + } + if err = b.Put([]byte(s), wbuf); err != nil { + t.Fatal(err) + } + } + if err = tx.Commit(); err != nil { + t.Fatal(err) + } + + // Generate free pages. + if tx, err = db.Begin(true); err != nil { + t.Fatal(err) + } + for i := 0; i < 50; i++ { + s := fmt.Sprintf("%d", i) + b := tx.Bucket([]byte(s)) + if b == nil { + t.Fatal(err) + } + if err := b.Delete([]byte(s)); err != nil { + t.Fatal(err) + } + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + // Record freelist count from opening with NoFreelistSync. + db.MustReopen() + freepages := db.Stats().FreePageN + if freepages == 0 { + t.Fatalf("no free pages on NoFreelistSync reopen") + } + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + // Check free page count is reconstructed when opened with freelist sync. + db.o = &bolt.Options{} + db.MustReopen() + // One less free page for syncing the free list on open. + freepages-- + if fp := db.Stats().FreePageN; fp < freepages { + t.Fatalf("closed with %d free pages, opened with %d", freepages, fp) + } +} + +// Ensure that a database cannot open a transaction when it's not open. +func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) { + var db bolt.DB + if _, err := db.Begin(false); err != bolt.ErrDatabaseNotOpen { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that a read-write transaction can be retrieved. +func TestDB_BeginRW(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } else if tx == nil { + t.Fatal("expected tx") + } + + if tx.DB() != db.DB { + t.Fatal("unexpected tx database") + } else if !tx.Writable() { + t.Fatal("expected writable tx") + } + + if err := tx.Commit(); err != nil { + t.Fatal(err) + } +} + +// TestDB_Concurrent_WriteTo checks that issuing WriteTo operations concurrently +// with commits does not produce corrupted db files. +func TestDB_Concurrent_WriteTo(t *testing.T) { + o := &bolt.Options{NoFreelistSync: false} + db := MustOpenWithOption(o) + defer db.MustClose() + + var wg sync.WaitGroup + wtxs, rtxs := 5, 5 + wg.Add(wtxs * rtxs) + f := func(tx *bolt.Tx) { + defer wg.Done() + f, err := ioutil.TempFile("", "bolt-") + if err != nil { + panic(err) + } + time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond) + tx.WriteTo(f) + tx.Rollback() + f.Close() + snap := &DB{nil, f.Name(), o} + snap.MustReopen() + defer snap.MustClose() + snap.MustCheck() + } + + tx1, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + if _, err := tx1.CreateBucket([]byte("abc")); err != nil { + t.Fatal(err) + } + if err := tx1.Commit(); err != nil { + t.Fatal(err) + } + + for i := 0; i < wtxs; i++ { + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil { + t.Fatal(err) + } + for j := 0; j < rtxs; j++ { + rtx, rerr := db.Begin(false) + if rerr != nil { + t.Fatal(rerr) + } + go f(rtx) + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + } + wg.Wait() +} + +// Ensure that opening a transaction while the DB is closed returns an error. +func TestDB_BeginRW_Closed(t *testing.T) { + var db bolt.DB + if _, err := db.Begin(true); err != bolt.ErrDatabaseNotOpen { + t.Fatalf("unexpected error: %s", err) + } +} + +func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) } +func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) } + +// Ensure that a database cannot close while transactions are open. +func testDB_Close_PendingTx(t *testing.T, writable bool) { + db := MustOpenDB() + + // Start transaction. + tx, err := db.Begin(writable) + if err != nil { + t.Fatal(err) + } + + // Open update in separate goroutine. + done := make(chan error, 1) + go func() { + err := db.Close() + done <- err + }() + + // Ensure database hasn't closed. + time.Sleep(100 * time.Millisecond) + select { + case err := <-done: + if err != nil { + t.Errorf("error from inside goroutine: %v", err) + } + t.Fatal("database closed too early") + default: + } + + // Commit/close transaction. + if writable { + err = tx.Commit() + } else { + err = tx.Rollback() + } + if err != nil { + t.Fatal(err) + } + + // Ensure database closed now. + time.Sleep(100 * time.Millisecond) + select { + case err := <-done: + if err != nil { + t.Fatalf("error from inside goroutine: %v", err) + } + default: + t.Fatal("database did not close") + } +} + +// Ensure a database can provide a transactional block. +func TestDB_Update(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + if err := b.Delete([]byte("foo")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + if v := b.Get([]byte("foo")); v != nil { + t.Fatalf("expected nil value, got: %v", v) + } + if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a closed database returns an error while running a transaction block +func TestDB_Update_Closed(t *testing.T) { + var db bolt.DB + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != bolt.ErrDatabaseNotOpen { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure a panic occurs while trying to commit a managed transaction. +func TestDB_Update_ManualCommit(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var panicked bool + if err := db.Update(func(tx *bolt.Tx) error { + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + }() + return nil + }); err != nil { + t.Fatal(err) + } else if !panicked { + t.Fatal("expected panic") + } +} + +// Ensure a panic occurs while trying to rollback a managed transaction. +func TestDB_Update_ManualRollback(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var panicked bool + if err := db.Update(func(tx *bolt.Tx) error { + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + }() + return nil + }); err != nil { + t.Fatal(err) + } else if !panicked { + t.Fatal("expected panic") + } +} + +// Ensure a panic occurs while trying to commit a managed transaction. +func TestDB_View_ManualCommit(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var panicked bool + if err := db.View(func(tx *bolt.Tx) error { + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + }() + return nil + }); err != nil { + t.Fatal(err) + } else if !panicked { + t.Fatal("expected panic") + } +} + +// Ensure a panic occurs while trying to rollback a managed transaction. +func TestDB_View_ManualRollback(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var panicked bool + if err := db.View(func(tx *bolt.Tx) error { + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + }() + return nil + }); err != nil { + t.Fatal(err) + } else if !panicked { + t.Fatal("expected panic") + } +} + +// Ensure a write transaction that panics does not hold open locks. +func TestDB_Update_Panic(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Panic during update but recover. + func() { + defer func() { + if r := recover(); r != nil { + t.Log("recover: update", r) + } + }() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + panic("omg") + }); err != nil { + t.Fatal(err) + } + }() + + // Verify we can update again. + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Verify that our change persisted. + if err := db.Update(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure a database can return an error through a read-only transactional block. +func TestDB_View_Error(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.View(func(tx *bolt.Tx) error { + return errors.New("xxx") + }); err == nil || err.Error() != "xxx" { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure a read transaction that panics does not hold open locks. +func TestDB_View_Panic(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Panic during view transaction but recover. + func() { + defer func() { + if r := recover(); r != nil { + t.Log("recover: view", r) + } + }() + + if err := db.View(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + panic("omg") + }); err != nil { + t.Fatal(err) + } + }() + + // Verify that we can still use read transactions. + if err := db.View(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that DB stats can be returned. +func TestDB_Stats(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + + stats := db.Stats() + if stats.TxStats.PageCount != 2 { + t.Fatalf("unexpected TxStats.PageCount: %d", stats.TxStats.PageCount) + } else if stats.FreePageN != 0 { + t.Fatalf("unexpected FreePageN != 0: %d", stats.FreePageN) + } else if stats.PendingPageN != 2 { + t.Fatalf("unexpected PendingPageN != 2: %d", stats.PendingPageN) + } +} + +// Ensure that database pages are in expected order and type. +func TestDB_Consistency(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + + for i := 0; i < 10; i++ { + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + if err := db.Update(func(tx *bolt.Tx) error { + if p, _ := tx.Page(0); p == nil { + t.Fatal("expected page") + } else if p.Type != "meta" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(1); p == nil { + t.Fatal("expected page") + } else if p.Type != "meta" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(2); p == nil { + t.Fatal("expected page") + } else if p.Type != "free" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(3); p == nil { + t.Fatal("expected page") + } else if p.Type != "free" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(4); p == nil { + t.Fatal("expected page") + } else if p.Type != "leaf" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(5); p == nil { + t.Fatal("expected page") + } else if p.Type != "freelist" { + t.Fatalf("unexpected page type: %s", p.Type) + } + + if p, _ := tx.Page(6); p != nil { + t.Fatal("unexpected page") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that DB stats can be subtracted from one another. +func TestDBStats_Sub(t *testing.T) { + var a, b bolt.Stats + a.TxStats.PageCount = 3 + a.FreePageN = 4 + b.TxStats.PageCount = 10 + b.FreePageN = 14 + diff := b.Sub(&a) + if diff.TxStats.PageCount != 7 { + t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.PageCount) + } + + // free page stats are copied from the receiver and not subtracted + if diff.FreePageN != 14 { + t.Fatalf("unexpected FreePageN: %d", diff.FreePageN) + } +} + +// Ensure two functions can perform updates in a single batch. +func TestDB_Batch(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Iterate over multiple updates in separate goroutines. + n := 2 + ch := make(chan error) + for i := 0; i < n; i++ { + go func(i int) { + ch <- db.Batch(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) + }) + }(i) + } + + // Check all responses to make sure there's no error. + for i := 0; i < n; i++ { + if err := <-ch; err != nil { + t.Fatal(err) + } + } + + // Ensure data is correct. + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 0; i < n; i++ { + if v := b.Get(u64tob(uint64(i))); v == nil { + t.Errorf("key not found: %d", i) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestDB_Batch_Panic(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var sentinel int + var bork = &sentinel + var problem interface{} + var err error + + // Execute a function inside a batch that panics. + func() { + defer func() { + if p := recover(); p != nil { + problem = p + } + }() + err = db.Batch(func(tx *bolt.Tx) error { + panic(bork) + }) + }() + + // Verify there is no error. + if g, e := err, error(nil); g != e { + t.Fatalf("wrong error: %v != %v", g, e) + } + // Verify the panic was captured. + if g, e := problem, bork; g != e { + t.Fatalf("wrong error: %v != %v", g, e) + } +} + +func TestDB_BatchFull(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + + const size = 3 + // buffered so we never leak goroutines + ch := make(chan error, size) + put := func(i int) { + ch <- db.Batch(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) + }) + } + + db.MaxBatchSize = size + // high enough to never trigger here + db.MaxBatchDelay = 1 * time.Hour + + go put(1) + go put(2) + + // Give the batch a chance to exhibit bugs. + time.Sleep(10 * time.Millisecond) + + // not triggered yet + select { + case <-ch: + t.Fatalf("batch triggered too early") + default: + } + + go put(3) + + // Check all responses to make sure there's no error. + for i := 0; i < size; i++ { + if err := <-ch; err != nil { + t.Fatal(err) + } + } + + // Ensure data is correct. + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 1; i <= size; i++ { + if v := b.Get(u64tob(uint64(i))); v == nil { + t.Errorf("key not found: %d", i) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func TestDB_BatchTime(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + t.Fatal(err) + } + + const size = 1 + // buffered so we never leak goroutines + ch := make(chan error, size) + put := func(i int) { + ch <- db.Batch(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) + }) + } + + db.MaxBatchSize = 1000 + db.MaxBatchDelay = 0 + + go put(1) + + // Batch must trigger by time alone. + + // Check all responses to make sure there's no error. + for i := 0; i < size; i++ { + if err := <-ch; err != nil { + t.Fatal(err) + } + } + + // Ensure data is correct. + if err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("widgets")) + for i := 1; i <= size; i++ { + if v := b.Get(u64tob(uint64(i))); v == nil { + t.Errorf("key not found: %d", i) + } + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +func ExampleDB_Update() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Execute several commands within a read-write transaction. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + return err + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + return err + } + return nil + }); err != nil { + log.Fatal(err) + } + + // Read the value back from a separate read-only transaction. + if err := db.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + fmt.Printf("The value of 'foo' is: %s\n", value) + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release the file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // The value of 'foo' is: bar +} + +func ExampleDB_View() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Insert data into a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("people")) + if err != nil { + return err + } + if err := b.Put([]byte("john"), []byte("doe")); err != nil { + return err + } + if err := b.Put([]byte("susy"), []byte("que")); err != nil { + return err + } + return nil + }); err != nil { + log.Fatal(err) + } + + // Access data from within a read-only transactional block. + if err := db.View(func(tx *bolt.Tx) error { + v := tx.Bucket([]byte("people")).Get([]byte("john")) + fmt.Printf("John's last name is %s.\n", v) + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release the file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // John's last name is doe. +} + +func ExampleDB_Begin() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Create a bucket using a read-write transaction. + if err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + log.Fatal(err) + } + + // Create several keys in a transaction. + tx, err := db.Begin(true) + if err != nil { + log.Fatal(err) + } + b := tx.Bucket([]byte("widgets")) + if err = b.Put([]byte("john"), []byte("blue")); err != nil { + log.Fatal(err) + } + if err = b.Put([]byte("abby"), []byte("red")); err != nil { + log.Fatal(err) + } + if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil { + log.Fatal(err) + } + if err = tx.Commit(); err != nil { + log.Fatal(err) + } + + // Iterate over the values in sorted key order. + tx, err = db.Begin(false) + if err != nil { + log.Fatal(err) + } + c := tx.Bucket([]byte("widgets")).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + fmt.Printf("%s likes %s\n", k, v) + } + + if err = tx.Rollback(); err != nil { + log.Fatal(err) + } + + if err = db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // abby likes red + // john likes blue + // zephyr likes purple +} + +func BenchmarkDBBatchAutomatic(b *testing.B) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("bench")) + return err + }); err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + start := make(chan struct{}) + var wg sync.WaitGroup + + for round := 0; round < 1000; round++ { + wg.Add(1) + + go func(id uint32) { + defer wg.Done() + <-start + + h := fnv.New32a() + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, id) + _, _ = h.Write(buf[:]) + k := h.Sum(nil) + insert := func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("bench")) + return b.Put(k, []byte("filler")) + } + if err := db.Batch(insert); err != nil { + b.Error(err) + return + } + }(uint32(round)) + } + close(start) + wg.Wait() + } + + b.StopTimer() + validateBatchBench(b, db) +} + +func BenchmarkDBBatchSingle(b *testing.B) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("bench")) + return err + }); err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + start := make(chan struct{}) + var wg sync.WaitGroup + + for round := 0; round < 1000; round++ { + wg.Add(1) + go func(id uint32) { + defer wg.Done() + <-start + + h := fnv.New32a() + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, id) + _, _ = h.Write(buf[:]) + k := h.Sum(nil) + insert := func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("bench")) + return b.Put(k, []byte("filler")) + } + if err := db.Update(insert); err != nil { + b.Error(err) + return + } + }(uint32(round)) + } + close(start) + wg.Wait() + } + + b.StopTimer() + validateBatchBench(b, db) +} + +func BenchmarkDBBatchManual10x100(b *testing.B) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("bench")) + return err + }); err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + start := make(chan struct{}) + var wg sync.WaitGroup + errCh := make(chan error, 10) + + for major := 0; major < 10; major++ { + wg.Add(1) + go func(id uint32) { + defer wg.Done() + <-start + + insert100 := func(tx *bolt.Tx) error { + h := fnv.New32a() + buf := make([]byte, 4) + for minor := uint32(0); minor < 100; minor++ { + binary.LittleEndian.PutUint32(buf, uint32(id*100+minor)) + h.Reset() + _, _ = h.Write(buf[:]) + k := h.Sum(nil) + b := tx.Bucket([]byte("bench")) + if err := b.Put(k, []byte("filler")); err != nil { + return err + } + } + return nil + } + err := db.Update(insert100) + errCh <- err + }(uint32(major)) + } + close(start) + wg.Wait() + close(errCh) + for err := range errCh { + if err != nil { + b.Fatal(err) + } + } + } + + b.StopTimer() + validateBatchBench(b, db) +} + +func validateBatchBench(b *testing.B, db *DB) { + var rollback = errors.New("sentinel error to cause rollback") + validate := func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte("bench")) + h := fnv.New32a() + buf := make([]byte, 4) + for id := uint32(0); id < 1000; id++ { + binary.LittleEndian.PutUint32(buf, id) + h.Reset() + _, _ = h.Write(buf[:]) + k := h.Sum(nil) + v := bucket.Get(k) + if v == nil { + b.Errorf("not found id=%d key=%x", id, k) + continue + } + if g, e := v, []byte("filler"); !bytes.Equal(g, e) { + b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e) + } + if err := bucket.Delete(k); err != nil { + return err + } + } + // should be empty now + c := bucket.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + b.Errorf("unexpected key: %x = %q", k, v) + } + return rollback + } + if err := db.Update(validate); err != nil && err != rollback { + b.Error(err) + } +} + +// DB is a test wrapper for bolt.DB. +type DB struct { + *bolt.DB + f string + o *bolt.Options +} + +// MustOpenDB returns a new, open DB at a temporary location. +func MustOpenDB() *DB { + return MustOpenWithOption(nil) +} + +// MustOpenDBWithOption returns a new, open DB at a temporary location with given options. +func MustOpenWithOption(o *bolt.Options) *DB { + f := tempfile() + if o == nil { + o = bolt.DefaultOptions + } + + freelistType := bolt.FreelistArrayType + if env := os.Getenv(bolt.TestFreelistType); env == string(bolt.FreelistMapType) { + freelistType = bolt.FreelistMapType + } + o.FreelistType = freelistType + + db, err := bolt.Open(f, 0666, o) + if err != nil { + panic(err) + } + return &DB{ + DB: db, + f: f, + o: o, + } +} + +// Close closes the database and deletes the underlying file. +func (db *DB) Close() error { + // Log statistics. + if *statsFlag { + db.PrintStats() + } + + // Check database consistency after every test. + db.MustCheck() + + // Close database and remove file. + defer os.Remove(db.Path()) + return db.DB.Close() +} + +// MustClose closes the database and deletes the underlying file. Panic on error. +func (db *DB) MustClose() { + if err := db.Close(); err != nil { + panic(err) + } +} + +// MustReopen reopen the database. Panic on error. +func (db *DB) MustReopen() { + indb, err := bolt.Open(db.f, 0666, db.o) + if err != nil { + panic(err) + } + db.DB = indb +} + +// PrintStats prints the database stats +func (db *DB) PrintStats() { + var stats = db.Stats() + fmt.Printf("[db] %-20s %-20s %-20s\n", + fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc), + fmt.Sprintf("cur(%d)", stats.TxStats.CursorCount), + fmt.Sprintf("node(%d/%d)", stats.TxStats.NodeCount, stats.TxStats.NodeDeref), + ) + fmt.Printf(" %-20s %-20s %-20s\n", + fmt.Sprintf("rebal(%d/%v)", stats.TxStats.Rebalance, truncDuration(stats.TxStats.RebalanceTime)), + fmt.Sprintf("spill(%d/%v)", stats.TxStats.Spill, truncDuration(stats.TxStats.SpillTime)), + fmt.Sprintf("w(%d/%v)", stats.TxStats.Write, truncDuration(stats.TxStats.WriteTime)), + ) +} + +// MustCheck runs a consistency check on the database and panics if any errors are found. +func (db *DB) MustCheck() { + if err := db.Update(func(tx *bolt.Tx) error { + // Collect all the errors. + var errors []error + for err := range tx.Check() { + errors = append(errors, err) + if len(errors) > 10 { + break + } + } + + // If errors occurred, copy the DB and print the errors. + if len(errors) > 0 { + var path = tempfile() + if err := tx.CopyFile(path, 0600); err != nil { + panic(err) + } + + // Print errors. + fmt.Print("\n\n") + fmt.Printf("consistency check failed (%d errors)\n", len(errors)) + for _, err := range errors { + fmt.Println(err) + } + fmt.Println("") + fmt.Println("db saved to:") + fmt.Println(path) + fmt.Print("\n\n") + os.Exit(-1) + } + + return nil + }); err != nil && err != bolt.ErrDatabaseNotOpen { + panic(err) + } +} + +// CopyTempFile copies a database to a temporary file. +func (db *DB) CopyTempFile() { + path := tempfile() + if err := db.View(func(tx *bolt.Tx) error { + return tx.CopyFile(path, 0600) + }); err != nil { + panic(err) + } + fmt.Println("db copied to: ", path) +} + +// tempfile returns a temporary file path. +func tempfile() string { + f, err := ioutil.TempFile("", "bolt-") + if err != nil { + panic(err) + } + if err := f.Close(); err != nil { + panic(err) + } + if err := os.Remove(f.Name()); err != nil { + panic(err) + } + return f.Name() +} + +func trunc(b []byte, length int) []byte { + if length < len(b) { + return b[:length] + } + return b +} + +func truncDuration(d time.Duration) string { + return regexp.MustCompile(`^(\d+)(\.\d+)`).ReplaceAllString(d.String(), "$1") +} + +func fileSize(path string) int64 { + fi, err := os.Stat(path) + if err != nil { + return 0 + } + return fi.Size() +} + +// u64tob converts a uint64 into an 8-byte slice. +func u64tob(v uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, v) + return b +} diff --git a/vendor/etcd.io/bbolt/doc.go b/vendor/etcd.io/bbolt/doc.go new file mode 100644 index 0000000..95f25f0 --- /dev/null +++ b/vendor/etcd.io/bbolt/doc.go @@ -0,0 +1,44 @@ +/* +package bbolt implements a low-level key/value store in pure Go. It supports +fully serializable transactions, ACID semantics, and lock-free MVCC with +multiple readers and a single writer. Bolt can be used for projects that +want a simple data store without the need to add large dependencies such as +Postgres or MySQL. + +Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is +optimized for fast read access and does not require recovery in the event of a +system crash. Transactions which have not finished committing will simply be +rolled back in the event of a crash. + +The design of Bolt is based on Howard Chu's LMDB database project. + +Bolt currently works on Windows, Mac OS X, and Linux. + + +Basics + +There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is +a collection of buckets and is represented by a single file on disk. A bucket is +a collection of unique keys that are associated with values. + +Transactions provide either read-only or read-write access to the database. +Read-only transactions can retrieve key/value pairs and can use Cursors to +iterate over the dataset sequentially. Read-write transactions can create and +delete buckets and can insert and remove keys. Only one read-write transaction +is allowed at a time. + + +Caveats + +The database uses a read-only, memory-mapped data file to ensure that +applications cannot corrupt the database, however, this means that keys and +values returned from Bolt cannot be changed. Writing to a read-only byte slice +will cause Go to panic. + +Keys and values retrieved from the database are only valid for the life of +the transaction. When used outside the transaction, these byte slices can +point to different data or can point to invalid memory which will cause a panic. + + +*/ +package bbolt diff --git a/vendor/etcd.io/bbolt/errors.go b/vendor/etcd.io/bbolt/errors.go new file mode 100644 index 0000000..48758ca --- /dev/null +++ b/vendor/etcd.io/bbolt/errors.go @@ -0,0 +1,71 @@ +package bbolt + +import "errors" + +// These errors can be returned when opening or calling methods on a DB. +var ( + // ErrDatabaseNotOpen is returned when a DB instance is accessed before it + // is opened or after it is closed. + ErrDatabaseNotOpen = errors.New("database not open") + + // ErrDatabaseOpen is returned when opening a database that is + // already open. + ErrDatabaseOpen = errors.New("database already open") + + // ErrInvalid is returned when both meta pages on a database are invalid. + // This typically occurs when a file is not a bolt database. + ErrInvalid = errors.New("invalid database") + + // ErrVersionMismatch is returned when the data file was created with a + // different version of Bolt. + ErrVersionMismatch = errors.New("version mismatch") + + // ErrChecksum is returned when either meta page checksum does not match. + ErrChecksum = errors.New("checksum error") + + // ErrTimeout is returned when a database cannot obtain an exclusive lock + // on the data file after the timeout passed to Open(). + ErrTimeout = errors.New("timeout") +) + +// These errors can occur when beginning or committing a Tx. +var ( + // ErrTxNotWritable is returned when performing a write operation on a + // read-only transaction. + ErrTxNotWritable = errors.New("tx not writable") + + // ErrTxClosed is returned when committing or rolling back a transaction + // that has already been committed or rolled back. + ErrTxClosed = errors.New("tx closed") + + // ErrDatabaseReadOnly is returned when a mutating transaction is started on a + // read-only database. + ErrDatabaseReadOnly = errors.New("database is in read-only mode") +) + +// These errors can occur when putting or deleting a value or a bucket. +var ( + // ErrBucketNotFound is returned when trying to access a bucket that has + // not been created yet. + ErrBucketNotFound = errors.New("bucket not found") + + // ErrBucketExists is returned when creating a bucket that already exists. + ErrBucketExists = errors.New("bucket already exists") + + // ErrBucketNameRequired is returned when creating a bucket with a blank name. + ErrBucketNameRequired = errors.New("bucket name required") + + // ErrKeyRequired is returned when inserting a zero-length key. + ErrKeyRequired = errors.New("key required") + + // ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize. + ErrKeyTooLarge = errors.New("key too large") + + // ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize. + ErrValueTooLarge = errors.New("value too large") + + // ErrIncompatibleValue is returned when trying create or delete a bucket + // on an existing non-bucket key or when trying to create or delete a + // non-bucket key on an existing bucket key. + ErrIncompatibleValue = errors.New("incompatible value") +) diff --git a/vendor/etcd.io/bbolt/freelist.go b/vendor/etcd.io/bbolt/freelist.go new file mode 100644 index 0000000..697a469 --- /dev/null +++ b/vendor/etcd.io/bbolt/freelist.go @@ -0,0 +1,404 @@ +package bbolt + +import ( + "fmt" + "sort" + "unsafe" +) + +// txPending holds a list of pgids and corresponding allocation txns +// that are pending to be freed. +type txPending struct { + ids []pgid + alloctx []txid // txids allocating the ids + lastReleaseBegin txid // beginning txid of last matching releaseRange +} + +// pidSet holds the set of starting pgids which have the same span size +type pidSet map[pgid]struct{} + +// freelist represents a list of all pages that are available for allocation. +// It also tracks pages that have been freed but are still in use by open transactions. +type freelist struct { + freelistType FreelistType // freelist type + ids []pgid // all free and available free page ids. + allocs map[pgid]txid // mapping of txid that allocated a pgid. + pending map[txid]*txPending // mapping of soon-to-be free page ids by tx. + cache map[pgid]bool // fast lookup of all free and pending page ids. + freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size + forwardMap map[pgid]uint64 // key is start pgid, value is its span size + backwardMap map[pgid]uint64 // key is end pgid, value is its span size + allocate func(txid txid, n int) pgid // the freelist allocate func + free_count func() int // the function which gives you free page number + mergeSpans func(ids pgids) // the mergeSpan func + getFreePageIDs func() []pgid // get free pgids func + readIDs func(pgids []pgid) // readIDs func reads list of pages and init the freelist +} + +// newFreelist returns an empty, initialized freelist. +func newFreelist(freelistType FreelistType) *freelist { + f := &freelist{ + freelistType: freelistType, + allocs: make(map[pgid]txid), + pending: make(map[txid]*txPending), + cache: make(map[pgid]bool), + freemaps: make(map[uint64]pidSet), + forwardMap: make(map[pgid]uint64), + backwardMap: make(map[pgid]uint64), + } + + if freelistType == FreelistMapType { + f.allocate = f.hashmapAllocate + f.free_count = f.hashmapFreeCount + f.mergeSpans = f.hashmapMergeSpans + f.getFreePageIDs = f.hashmapGetFreePageIDs + f.readIDs = f.hashmapReadIDs + } else { + f.allocate = f.arrayAllocate + f.free_count = f.arrayFreeCount + f.mergeSpans = f.arrayMergeSpans + f.getFreePageIDs = f.arrayGetFreePageIDs + f.readIDs = f.arrayReadIDs + } + + return f +} + +// size returns the size of the page after serialization. +func (f *freelist) size() int { + n := f.count() + if n >= 0xFFFF { + // The first element will be used to store the count. See freelist.write. + n++ + } + return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n) +} + +// count returns count of pages on the freelist +func (f *freelist) count() int { + return f.free_count() + f.pending_count() +} + +// arrayFreeCount returns count of free pages(array version) +func (f *freelist) arrayFreeCount() int { + return len(f.ids) +} + +// pending_count returns count of pending pages +func (f *freelist) pending_count() int { + var count int + for _, txp := range f.pending { + count += len(txp.ids) + } + return count +} + +// copyall copies a list of all free ids and all pending ids in one sorted list. +// f.count returns the minimum length required for dst. +func (f *freelist) copyall(dst []pgid) { + m := make(pgids, 0, f.pending_count()) + for _, txp := range f.pending { + m = append(m, txp.ids...) + } + sort.Sort(m) + mergepgids(dst, f.getFreePageIDs(), m) +} + +// arrayAllocate returns the starting page id of a contiguous list of pages of a given size. +// If a contiguous block cannot be found then 0 is returned. +func (f *freelist) arrayAllocate(txid txid, n int) pgid { + if len(f.ids) == 0 { + return 0 + } + + var initial, previd pgid + for i, id := range f.ids { + if id <= 1 { + panic(fmt.Sprintf("invalid page allocation: %d", id)) + } + + // Reset initial page if this is not contiguous. + if previd == 0 || id-previd != 1 { + initial = id + } + + // If we found a contiguous block then remove it and return it. + if (id-initial)+1 == pgid(n) { + // If we're allocating off the beginning then take the fast path + // and just adjust the existing slice. This will use extra memory + // temporarily but the append() in free() will realloc the slice + // as is necessary. + if (i + 1) == n { + f.ids = f.ids[i+1:] + } else { + copy(f.ids[i-n+1:], f.ids[i+1:]) + f.ids = f.ids[:len(f.ids)-n] + } + + // Remove from the free cache. + for i := pgid(0); i < pgid(n); i++ { + delete(f.cache, initial+i) + } + f.allocs[initial] = txid + return initial + } + + previd = id + } + return 0 +} + +// free releases a page and its overflow for a given transaction id. +// If the page is already free then a panic will occur. +func (f *freelist) free(txid txid, p *page) { + if p.id <= 1 { + panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id)) + } + + // Free page and all its overflow pages. + txp := f.pending[txid] + if txp == nil { + txp = &txPending{} + f.pending[txid] = txp + } + allocTxid, ok := f.allocs[p.id] + if ok { + delete(f.allocs, p.id) + } else if (p.flags & freelistPageFlag) != 0 { + // Freelist is always allocated by prior tx. + allocTxid = txid - 1 + } + + for id := p.id; id <= p.id+pgid(p.overflow); id++ { + // Verify that page is not already free. + if f.cache[id] { + panic(fmt.Sprintf("page %d already freed", id)) + } + // Add to the freelist and cache. + txp.ids = append(txp.ids, id) + txp.alloctx = append(txp.alloctx, allocTxid) + f.cache[id] = true + } +} + +// release moves all page ids for a transaction id (or older) to the freelist. +func (f *freelist) release(txid txid) { + m := make(pgids, 0) + for tid, txp := range f.pending { + if tid <= txid { + // Move transaction's pending pages to the available freelist. + // Don't remove from the cache since the page is still free. + m = append(m, txp.ids...) + delete(f.pending, tid) + } + } + f.mergeSpans(m) +} + +// releaseRange moves pending pages allocated within an extent [begin,end] to the free list. +func (f *freelist) releaseRange(begin, end txid) { + if begin > end { + return + } + var m pgids + for tid, txp := range f.pending { + if tid < begin || tid > end { + continue + } + // Don't recompute freed pages if ranges haven't updated. + if txp.lastReleaseBegin == begin { + continue + } + for i := 0; i < len(txp.ids); i++ { + if atx := txp.alloctx[i]; atx < begin || atx > end { + continue + } + m = append(m, txp.ids[i]) + txp.ids[i] = txp.ids[len(txp.ids)-1] + txp.ids = txp.ids[:len(txp.ids)-1] + txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1] + txp.alloctx = txp.alloctx[:len(txp.alloctx)-1] + i-- + } + txp.lastReleaseBegin = begin + if len(txp.ids) == 0 { + delete(f.pending, tid) + } + } + f.mergeSpans(m) +} + +// rollback removes the pages from a given pending tx. +func (f *freelist) rollback(txid txid) { + // Remove page ids from cache. + txp := f.pending[txid] + if txp == nil { + return + } + var m pgids + for i, pgid := range txp.ids { + delete(f.cache, pgid) + tx := txp.alloctx[i] + if tx == 0 { + continue + } + if tx != txid { + // Pending free aborted; restore page back to alloc list. + f.allocs[pgid] = tx + } else { + // Freed page was allocated by this txn; OK to throw away. + m = append(m, pgid) + } + } + // Remove pages from pending list and mark as free if allocated by txid. + delete(f.pending, txid) + f.mergeSpans(m) +} + +// freed returns whether a given page is in the free list. +func (f *freelist) freed(pgid pgid) bool { + return f.cache[pgid] +} + +// read initializes the freelist from a freelist page. +func (f *freelist) read(p *page) { + if (p.flags & freelistPageFlag) == 0 { + panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ())) + } + // If the page.count is at the max uint16 value (64k) then it's considered + // an overflow and the size of the freelist is stored as the first element. + var idx, count = 0, int(p.count) + if count == 0xFFFF { + idx = 1 + c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))) + count = int(c) + if count < 0 { + panic(fmt.Sprintf("leading element count %d overflows int", c)) + } + } + + // Copy the list of page ids from the freelist. + if count == 0 { + f.ids = nil + } else { + var ids []pgid + data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(ids[0]), idx) + unsafeSlice(unsafe.Pointer(&ids), data, count) + + // copy the ids, so we don't modify on the freelist page directly + idsCopy := make([]pgid, count) + copy(idsCopy, ids) + // Make sure they're sorted. + sort.Sort(pgids(idsCopy)) + + f.readIDs(idsCopy) + } +} + +// arrayReadIDs initializes the freelist from a given list of ids. +func (f *freelist) arrayReadIDs(ids []pgid) { + f.ids = ids + f.reindex() +} + +func (f *freelist) arrayGetFreePageIDs() []pgid { + return f.ids +} + +// write writes the page ids onto a freelist page. All free and pending ids are +// saved to disk since in the event of a program crash, all pending ids will +// become free. +func (f *freelist) write(p *page) error { + // Combine the old free pgids and pgids waiting on an open transaction. + + // Update the header flag. + p.flags |= freelistPageFlag + + // The page.count can only hold up to 64k elements so if we overflow that + // number then we handle it by putting the size in the first element. + l := f.count() + if l == 0 { + p.count = uint16(l) + } else if l < 0xFFFF { + p.count = uint16(l) + var ids []pgid + data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)) + unsafeSlice(unsafe.Pointer(&ids), data, l) + f.copyall(ids) + } else { + p.count = 0xFFFF + var ids []pgid + data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)) + unsafeSlice(unsafe.Pointer(&ids), data, l+1) + ids[0] = pgid(l) + f.copyall(ids[1:]) + } + + return nil +} + +// reload reads the freelist from a page and filters out pending items. +func (f *freelist) reload(p *page) { + f.read(p) + + // Build a cache of only pending pages. + pcache := make(map[pgid]bool) + for _, txp := range f.pending { + for _, pendingID := range txp.ids { + pcache[pendingID] = true + } + } + + // Check each page in the freelist and build a new available freelist + // with any pages not in the pending lists. + var a []pgid + for _, id := range f.getFreePageIDs() { + if !pcache[id] { + a = append(a, id) + } + } + + f.readIDs(a) +} + +// noSyncReload reads the freelist from pgids and filters out pending items. +func (f *freelist) noSyncReload(pgids []pgid) { + // Build a cache of only pending pages. + pcache := make(map[pgid]bool) + for _, txp := range f.pending { + for _, pendingID := range txp.ids { + pcache[pendingID] = true + } + } + + // Check each page in the freelist and build a new available freelist + // with any pages not in the pending lists. + var a []pgid + for _, id := range pgids { + if !pcache[id] { + a = append(a, id) + } + } + + f.readIDs(a) +} + +// reindex rebuilds the free cache based on available and pending free lists. +func (f *freelist) reindex() { + ids := f.getFreePageIDs() + f.cache = make(map[pgid]bool, len(ids)) + for _, id := range ids { + f.cache[id] = true + } + for _, txp := range f.pending { + for _, pendingID := range txp.ids { + f.cache[pendingID] = true + } + } +} + +// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array +func (f *freelist) arrayMergeSpans(ids pgids) { + sort.Sort(ids) + f.ids = pgids(f.ids).merge(ids) +} diff --git a/vendor/etcd.io/bbolt/freelist_hmap.go b/vendor/etcd.io/bbolt/freelist_hmap.go new file mode 100644 index 0000000..02ef2be --- /dev/null +++ b/vendor/etcd.io/bbolt/freelist_hmap.go @@ -0,0 +1,178 @@ +package bbolt + +import "sort" + +// hashmapFreeCount returns count of free pages(hashmap version) +func (f *freelist) hashmapFreeCount() int { + // use the forwardmap to get the total count + count := 0 + for _, size := range f.forwardMap { + count += int(size) + } + return count +} + +// hashmapAllocate serves the same purpose as arrayAllocate, but use hashmap as backend +func (f *freelist) hashmapAllocate(txid txid, n int) pgid { + if n == 0 { + return 0 + } + + // if we have a exact size match just return short path + if bm, ok := f.freemaps[uint64(n)]; ok { + for pid := range bm { + // remove the span + f.delSpan(pid, uint64(n)) + + f.allocs[pid] = txid + + for i := pgid(0); i < pgid(n); i++ { + delete(f.cache, pid+i) + } + return pid + } + } + + // lookup the map to find larger span + for size, bm := range f.freemaps { + if size < uint64(n) { + continue + } + + for pid := range bm { + // remove the initial + f.delSpan(pid, uint64(size)) + + f.allocs[pid] = txid + + remain := size - uint64(n) + + // add remain span + f.addSpan(pid+pgid(n), remain) + + for i := pgid(0); i < pgid(n); i++ { + delete(f.cache, pid+pgid(i)) + } + return pid + } + } + + return 0 +} + +// hashmapReadIDs reads pgids as input an initial the freelist(hashmap version) +func (f *freelist) hashmapReadIDs(pgids []pgid) { + f.init(pgids) + + // Rebuild the page cache. + f.reindex() +} + +// hashmapGetFreePageIDs returns the sorted free page ids +func (f *freelist) hashmapGetFreePageIDs() []pgid { + count := f.free_count() + if count == 0 { + return nil + } + + m := make([]pgid, 0, count) + for start, size := range f.forwardMap { + for i := 0; i < int(size); i++ { + m = append(m, start+pgid(i)) + } + } + sort.Sort(pgids(m)) + + return m +} + +// hashmapMergeSpans try to merge list of pages(represented by pgids) with existing spans +func (f *freelist) hashmapMergeSpans(ids pgids) { + for _, id := range ids { + // try to see if we can merge and update + f.mergeWithExistingSpan(id) + } +} + +// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward +func (f *freelist) mergeWithExistingSpan(pid pgid) { + prev := pid - 1 + next := pid + 1 + + preSize, mergeWithPrev := f.backwardMap[prev] + nextSize, mergeWithNext := f.forwardMap[next] + newStart := pid + newSize := uint64(1) + + if mergeWithPrev { + //merge with previous span + start := prev + 1 - pgid(preSize) + f.delSpan(start, preSize) + + newStart -= pgid(preSize) + newSize += preSize + } + + if mergeWithNext { + // merge with next span + f.delSpan(next, nextSize) + newSize += nextSize + } + + f.addSpan(newStart, newSize) +} + +func (f *freelist) addSpan(start pgid, size uint64) { + f.backwardMap[start-1+pgid(size)] = size + f.forwardMap[start] = size + if _, ok := f.freemaps[size]; !ok { + f.freemaps[size] = make(map[pgid]struct{}) + } + + f.freemaps[size][start] = struct{}{} +} + +func (f *freelist) delSpan(start pgid, size uint64) { + delete(f.forwardMap, start) + delete(f.backwardMap, start+pgid(size-1)) + delete(f.freemaps[size], start) + if len(f.freemaps[size]) == 0 { + delete(f.freemaps, size) + } +} + +// initial from pgids using when use hashmap version +// pgids must be sorted +func (f *freelist) init(pgids []pgid) { + if len(pgids) == 0 { + return + } + + size := uint64(1) + start := pgids[0] + + if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) { + panic("pgids not sorted") + } + + f.freemaps = make(map[uint64]pidSet) + f.forwardMap = make(map[pgid]uint64) + f.backwardMap = make(map[pgid]uint64) + + for i := 1; i < len(pgids); i++ { + // continuous page + if pgids[i] == pgids[i-1]+1 { + size++ + } else { + f.addSpan(start, size) + + size = 1 + start = pgids[i] + } + } + + // init the tail + if size != 0 && start != 0 { + f.addSpan(start, size) + } +} diff --git a/vendor/etcd.io/bbolt/freelist_test.go b/vendor/etcd.io/bbolt/freelist_test.go new file mode 100644 index 0000000..97656f4 --- /dev/null +++ b/vendor/etcd.io/bbolt/freelist_test.go @@ -0,0 +1,434 @@ +package bbolt + +import ( + "math/rand" + "os" + "reflect" + "sort" + "testing" + "unsafe" +) + +// TestFreelistType is used as a env variable for test to indicate the backend type +const TestFreelistType = "TEST_FREELIST_TYPE" + +// Ensure that a page is added to a transaction's freelist. +func TestFreelist_free(t *testing.T) { + f := newTestFreelist() + f.free(100, &page{id: 12}) + if !reflect.DeepEqual([]pgid{12}, f.pending[100].ids) { + t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100].ids) + } +} + +// Ensure that a page and its overflow is added to a transaction's freelist. +func TestFreelist_free_overflow(t *testing.T) { + f := newTestFreelist() + f.free(100, &page{id: 12, overflow: 3}) + if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100].ids) { + t.Fatalf("exp=%v; got=%v", exp, f.pending[100].ids) + } +} + +// Ensure that a transaction's free pages can be released. +func TestFreelist_release(t *testing.T) { + f := newTestFreelist() + f.free(100, &page{id: 12, overflow: 1}) + f.free(100, &page{id: 9}) + f.free(102, &page{id: 39}) + f.release(100) + f.release(101) + if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs()) + } + + f.release(102) + if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs()) + } +} + +// Ensure that releaseRange handles boundary conditions correctly +func TestFreelist_releaseRange(t *testing.T) { + type testRange struct { + begin, end txid + } + + type testPage struct { + id pgid + n int + allocTxn txid + freeTxn txid + } + + var releaseRangeTests = []struct { + title string + pagesIn []testPage + releaseRanges []testRange + wantFree []pgid + }{ + { + title: "Single pending in range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}}, + releaseRanges: []testRange{{1, 300}}, + wantFree: []pgid{3}, + }, + { + title: "Single pending with minimum end range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}}, + releaseRanges: []testRange{{1, 200}}, + wantFree: []pgid{3}, + }, + { + title: "Single pending outsize minimum end range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}}, + releaseRanges: []testRange{{1, 199}}, + wantFree: nil, + }, + { + title: "Single pending with minimum begin range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}}, + releaseRanges: []testRange{{100, 300}}, + wantFree: []pgid{3}, + }, + { + title: "Single pending outside minimum begin range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}}, + releaseRanges: []testRange{{101, 300}}, + wantFree: nil, + }, + { + title: "Single pending in minimum range", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}}, + releaseRanges: []testRange{{199, 200}}, + wantFree: []pgid{3}, + }, + { + title: "Single pending and read transaction at 199", + pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}}, + releaseRanges: []testRange{{100, 198}, {200, 300}}, + wantFree: nil, + }, + { + title: "Adjacent pending and read transactions at 199, 200", + pagesIn: []testPage{ + {id: 3, n: 1, allocTxn: 199, freeTxn: 200}, + {id: 4, n: 1, allocTxn: 200, freeTxn: 201}, + }, + releaseRanges: []testRange{ + {100, 198}, + {200, 199}, // Simulate the ranges db.freePages might produce. + {201, 300}, + }, + wantFree: nil, + }, + { + title: "Out of order ranges", + pagesIn: []testPage{ + {id: 3, n: 1, allocTxn: 199, freeTxn: 200}, + {id: 4, n: 1, allocTxn: 200, freeTxn: 201}, + }, + releaseRanges: []testRange{ + {201, 199}, + {201, 200}, + {200, 200}, + }, + wantFree: nil, + }, + { + title: "Multiple pending, read transaction at 150", + pagesIn: []testPage{ + {id: 3, n: 1, allocTxn: 100, freeTxn: 200}, + {id: 4, n: 1, allocTxn: 100, freeTxn: 125}, + {id: 5, n: 1, allocTxn: 125, freeTxn: 150}, + {id: 6, n: 1, allocTxn: 125, freeTxn: 175}, + {id: 7, n: 2, allocTxn: 150, freeTxn: 175}, + {id: 9, n: 2, allocTxn: 175, freeTxn: 200}, + }, + releaseRanges: []testRange{{50, 149}, {151, 300}}, + wantFree: []pgid{4, 9, 10}, + }, + } + + for _, c := range releaseRangeTests { + f := newTestFreelist() + var ids []pgid + for _, p := range c.pagesIn { + for i := uint64(0); i < uint64(p.n); i++ { + ids = append(ids, pgid(uint64(p.id)+i)) + } + } + f.readIDs(ids) + for _, p := range c.pagesIn { + f.allocate(p.allocTxn, p.n) + } + + for _, p := range c.pagesIn { + f.free(p.freeTxn, &page{id: p.id, overflow: uint32(p.n - 1)}) + } + + for _, r := range c.releaseRanges { + f.releaseRange(r.begin, r.end) + } + + if exp := c.wantFree; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Errorf("exp=%v; got=%v for %s", exp, f.getFreePageIDs(), c.title) + } + } +} + +func TestFreelistHashmap_allocate(t *testing.T) { + f := newTestFreelist() + if f.freelistType != FreelistMapType { + t.Skip() + } + + ids := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18} + f.readIDs(ids) + + f.allocate(1, 3) + if x := f.free_count(); x != 6 { + t.Fatalf("exp=6; got=%v", x) + } + + f.allocate(1, 2) + if x := f.free_count(); x != 4 { + t.Fatalf("exp=4; got=%v", x) + } + f.allocate(1, 1) + if x := f.free_count(); x != 3 { + t.Fatalf("exp=3; got=%v", x) + } + + f.allocate(1, 0) + if x := f.free_count(); x != 3 { + t.Fatalf("exp=3; got=%v", x) + } +} + +// Ensure that a freelist can find contiguous blocks of pages. +func TestFreelistArray_allocate(t *testing.T) { + f := newTestFreelist() + if f.freelistType != FreelistArrayType { + t.Skip() + } + ids := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18} + f.readIDs(ids) + if id := int(f.allocate(1, 3)); id != 3 { + t.Fatalf("exp=3; got=%v", id) + } + if id := int(f.allocate(1, 1)); id != 6 { + t.Fatalf("exp=6; got=%v", id) + } + if id := int(f.allocate(1, 3)); id != 0 { + t.Fatalf("exp=0; got=%v", id) + } + if id := int(f.allocate(1, 2)); id != 12 { + t.Fatalf("exp=12; got=%v", id) + } + if id := int(f.allocate(1, 1)); id != 7 { + t.Fatalf("exp=7; got=%v", id) + } + if id := int(f.allocate(1, 0)); id != 0 { + t.Fatalf("exp=0; got=%v", id) + } + if id := int(f.allocate(1, 0)); id != 0 { + t.Fatalf("exp=0; got=%v", id) + } + if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs()) + } + + if id := int(f.allocate(1, 1)); id != 9 { + t.Fatalf("exp=9; got=%v", id) + } + if id := int(f.allocate(1, 1)); id != 18 { + t.Fatalf("exp=18; got=%v", id) + } + if id := int(f.allocate(1, 1)); id != 0 { + t.Fatalf("exp=0; got=%v", id) + } + if exp := []pgid{}; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs()) + } +} + +// Ensure that a freelist can deserialize from a freelist page. +func TestFreelist_read(t *testing.T) { + // Create a page. + var buf [4096]byte + page := (*page)(unsafe.Pointer(&buf[0])) + page.flags = freelistPageFlag + page.count = 2 + + // Insert 2 page ids. + ids := (*[3]pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(page)) + unsafe.Sizeof(*page))) + ids[0] = 23 + ids[1] = 50 + + // Deserialize page into a freelist. + f := newTestFreelist() + f.read(page) + + // Ensure that there are two page ids in the freelist. + if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f.getFreePageIDs()) + } +} + +// Ensure that a freelist can serialize into a freelist page. +func TestFreelist_write(t *testing.T) { + // Create a freelist and write it to a page. + var buf [4096]byte + f := newTestFreelist() + + f.readIDs([]pgid{12, 39}) + f.pending[100] = &txPending{ids: []pgid{28, 11}} + f.pending[101] = &txPending{ids: []pgid{3}} + p := (*page)(unsafe.Pointer(&buf[0])) + if err := f.write(p); err != nil { + t.Fatal(err) + } + + // Read the page back out. + f2 := newTestFreelist() + f2.read(p) + + // Ensure that the freelist is correct. + // All pages should be present and in reverse order. + if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.getFreePageIDs()) { + t.Fatalf("exp=%v; got=%v", exp, f2.getFreePageIDs()) + } +} + +func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) } +func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) } +func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) } +func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) } + +func benchmark_FreelistRelease(b *testing.B, size int) { + ids := randomPgids(size) + pending := randomPgids(len(ids) / 400) + b.ResetTimer() + for i := 0; i < b.N; i++ { + txp := &txPending{ids: pending} + f := newTestFreelist() + f.pending = map[txid]*txPending{1: txp} + f.readIDs(ids) + f.release(1) + } +} + +func randomPgids(n int) []pgid { + rand.Seed(42) + pgids := make(pgids, n) + for i := range pgids { + pgids[i] = pgid(rand.Int63()) + } + sort.Sort(pgids) + return pgids +} + +func Test_freelist_ReadIDs_and_getFreePageIDs(t *testing.T) { + f := newTestFreelist() + exp := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18} + + f.readIDs(exp) + + if got := f.getFreePageIDs(); !reflect.DeepEqual(exp, got) { + t.Fatalf("exp=%v; got=%v", exp, got) + } + + f2 := newTestFreelist() + var exp2 []pgid + f2.readIDs(exp2) + + if got2 := f2.getFreePageIDs(); !reflect.DeepEqual(got2, exp2) { + t.Fatalf("exp2=%#v; got2=%#v", exp2, got2) + } + +} + +func Test_freelist_mergeWithExist(t *testing.T) { + bm1 := pidSet{1: struct{}{}} + + bm2 := pidSet{5: struct{}{}} + tests := []struct { + name string + ids []pgid + pgid pgid + want []pgid + wantForwardmap map[pgid]uint64 + wantBackwardmap map[pgid]uint64 + wantfreemap map[uint64]pidSet + }{ + { + name: "test1", + ids: []pgid{1, 2, 4, 5, 6}, + pgid: 3, + want: []pgid{1, 2, 3, 4, 5, 6}, + wantForwardmap: map[pgid]uint64{1: 6}, + wantBackwardmap: map[pgid]uint64{6: 6}, + wantfreemap: map[uint64]pidSet{6: bm1}, + }, + { + name: "test2", + ids: []pgid{1, 2, 5, 6}, + pgid: 3, + want: []pgid{1, 2, 3, 5, 6}, + wantForwardmap: map[pgid]uint64{1: 3, 5: 2}, + wantBackwardmap: map[pgid]uint64{6: 2, 3: 3}, + wantfreemap: map[uint64]pidSet{3: bm1, 2: bm2}, + }, + { + name: "test3", + ids: []pgid{1, 2}, + pgid: 3, + want: []pgid{1, 2, 3}, + wantForwardmap: map[pgid]uint64{1: 3}, + wantBackwardmap: map[pgid]uint64{3: 3}, + wantfreemap: map[uint64]pidSet{3: bm1}, + }, + { + name: "test4", + ids: []pgid{2, 3}, + pgid: 1, + want: []pgid{1, 2, 3}, + wantForwardmap: map[pgid]uint64{1: 3}, + wantBackwardmap: map[pgid]uint64{3: 3}, + wantfreemap: map[uint64]pidSet{3: bm1}, + }, + } + for _, tt := range tests { + f := newTestFreelist() + if f.freelistType == FreelistArrayType { + t.Skip() + } + f.readIDs(tt.ids) + + f.mergeWithExistingSpan(tt.pgid) + + if got := f.getFreePageIDs(); !reflect.DeepEqual(tt.want, got) { + t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.want, got) + } + if got := f.forwardMap; !reflect.DeepEqual(tt.wantForwardmap, got) { + t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantForwardmap, got) + } + if got := f.backwardMap; !reflect.DeepEqual(tt.wantBackwardmap, got) { + t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantBackwardmap, got) + } + if got := f.freemaps; !reflect.DeepEqual(tt.wantfreemap, got) { + t.Fatalf("name %s; exp=%v; got=%v", tt.name, tt.wantfreemap, got) + } + } +} + +// newTestFreelist get the freelist type from env and initial the freelist +func newTestFreelist() *freelist { + freelistType := FreelistArrayType + if env := os.Getenv(TestFreelistType); env == string(FreelistMapType) { + freelistType = FreelistMapType + } + + return newFreelist(freelistType) +} diff --git a/vendor/etcd.io/bbolt/go.mod b/vendor/etcd.io/bbolt/go.mod new file mode 100644 index 0000000..c2366da --- /dev/null +++ b/vendor/etcd.io/bbolt/go.mod @@ -0,0 +1,5 @@ +module go.etcd.io/bbolt + +go 1.12 + +require golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 diff --git a/vendor/etcd.io/bbolt/go.sum b/vendor/etcd.io/bbolt/go.sum new file mode 100644 index 0000000..4ad15a4 --- /dev/null +++ b/vendor/etcd.io/bbolt/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/etcd.io/bbolt/manydbs_test.go b/vendor/etcd.io/bbolt/manydbs_test.go new file mode 100644 index 0000000..fbbe8fe --- /dev/null +++ b/vendor/etcd.io/bbolt/manydbs_test.go @@ -0,0 +1,67 @@ +package bbolt + +import ( + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "testing" +) + +func createDb(t *testing.T) (*DB, func()) { + // First, create a temporary directory to be used for the duration of + // this test. + tempDirName, err := ioutil.TempDir("", "bboltmemtest") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + path := filepath.Join(tempDirName, "testdb.db") + + bdb, err := Open(path, 0600, nil) + if err != nil { + t.Fatalf("error creating bbolt db: %v", err) + } + + cleanup := func() { + bdb.Close() + os.RemoveAll(tempDirName) + } + + return bdb, cleanup +} + +func createAndPutKeys(t *testing.T) { + t.Parallel() + + db, cleanup := createDb(t) + defer cleanup() + + bucketName := []byte("bucket") + + for i := 0; i < 100; i++ { + err := db.Update(func(tx *Tx) error { + nodes, err := tx.CreateBucketIfNotExists(bucketName) + if err != nil { + return err + } + + var key [16]byte + rand.Read(key[:]) + if err := nodes.Put(key[:], nil); err != nil { + return err + } + + return nil + }) + if err != nil { + t.Fatal(err) + } + } +} + +func TestManyDBs(t *testing.T) { + for i := 0; i < 100; i++ { + t.Run(fmt.Sprintf("%d", i), createAndPutKeys) + } +} diff --git a/vendor/etcd.io/bbolt/node.go b/vendor/etcd.io/bbolt/node.go new file mode 100644 index 0000000..73988b5 --- /dev/null +++ b/vendor/etcd.io/bbolt/node.go @@ -0,0 +1,602 @@ +package bbolt + +import ( + "bytes" + "fmt" + "sort" + "unsafe" +) + +// node represents an in-memory, deserialized page. +type node struct { + bucket *Bucket + isLeaf bool + unbalanced bool + spilled bool + key []byte + pgid pgid + parent *node + children nodes + inodes inodes +} + +// root returns the top-level node this node is attached to. +func (n *node) root() *node { + if n.parent == nil { + return n + } + return n.parent.root() +} + +// minKeys returns the minimum number of inodes this node should have. +func (n *node) minKeys() int { + if n.isLeaf { + return 1 + } + return 2 +} + +// size returns the size of the node after serialization. +func (n *node) size() int { + sz, elsz := pageHeaderSize, n.pageElementSize() + for i := 0; i < len(n.inodes); i++ { + item := &n.inodes[i] + sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value)) + } + return int(sz) +} + +// sizeLessThan returns true if the node is less than a given size. +// This is an optimization to avoid calculating a large node when we only need +// to know if it fits inside a certain page size. +func (n *node) sizeLessThan(v uintptr) bool { + sz, elsz := pageHeaderSize, n.pageElementSize() + for i := 0; i < len(n.inodes); i++ { + item := &n.inodes[i] + sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value)) + if sz >= v { + return false + } + } + return true +} + +// pageElementSize returns the size of each page element based on the type of node. +func (n *node) pageElementSize() uintptr { + if n.isLeaf { + return leafPageElementSize + } + return branchPageElementSize +} + +// childAt returns the child node at a given index. +func (n *node) childAt(index int) *node { + if n.isLeaf { + panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index)) + } + return n.bucket.node(n.inodes[index].pgid, n) +} + +// childIndex returns the index of a given child node. +func (n *node) childIndex(child *node) int { + index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 }) + return index +} + +// numChildren returns the number of children. +func (n *node) numChildren() int { + return len(n.inodes) +} + +// nextSibling returns the next node with the same parent. +func (n *node) nextSibling() *node { + if n.parent == nil { + return nil + } + index := n.parent.childIndex(n) + if index >= n.parent.numChildren()-1 { + return nil + } + return n.parent.childAt(index + 1) +} + +// prevSibling returns the previous node with the same parent. +func (n *node) prevSibling() *node { + if n.parent == nil { + return nil + } + index := n.parent.childIndex(n) + if index == 0 { + return nil + } + return n.parent.childAt(index - 1) +} + +// put inserts a key/value. +func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) { + if pgid >= n.bucket.tx.meta.pgid { + panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid)) + } else if len(oldKey) <= 0 { + panic("put: zero-length old key") + } else if len(newKey) <= 0 { + panic("put: zero-length new key") + } + + // Find insertion index. + index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 }) + + // Add capacity and shift nodes if we don't have an exact match and need to insert. + exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey)) + if !exact { + n.inodes = append(n.inodes, inode{}) + copy(n.inodes[index+1:], n.inodes[index:]) + } + + inode := &n.inodes[index] + inode.flags = flags + inode.key = newKey + inode.value = value + inode.pgid = pgid + _assert(len(inode.key) > 0, "put: zero-length inode key") +} + +// del removes a key from the node. +func (n *node) del(key []byte) { + // Find index of key. + index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 }) + + // Exit if the key isn't found. + if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) { + return + } + + // Delete inode from the node. + n.inodes = append(n.inodes[:index], n.inodes[index+1:]...) + + // Mark the node as needing rebalancing. + n.unbalanced = true +} + +// read initializes the node from a page. +func (n *node) read(p *page) { + n.pgid = p.id + n.isLeaf = ((p.flags & leafPageFlag) != 0) + n.inodes = make(inodes, int(p.count)) + + for i := 0; i < int(p.count); i++ { + inode := &n.inodes[i] + if n.isLeaf { + elem := p.leafPageElement(uint16(i)) + inode.flags = elem.flags + inode.key = elem.key() + inode.value = elem.value() + } else { + elem := p.branchPageElement(uint16(i)) + inode.pgid = elem.pgid + inode.key = elem.key() + } + _assert(len(inode.key) > 0, "read: zero-length inode key") + } + + // Save first key so we can find the node in the parent when we spill. + if len(n.inodes) > 0 { + n.key = n.inodes[0].key + _assert(len(n.key) > 0, "read: zero-length node key") + } else { + n.key = nil + } +} + +// write writes the items onto one or more pages. +func (n *node) write(p *page) { + // Initialize page. + if n.isLeaf { + p.flags |= leafPageFlag + } else { + p.flags |= branchPageFlag + } + + if len(n.inodes) >= 0xFFFF { + panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id)) + } + p.count = uint16(len(n.inodes)) + + // Stop here if there are no items to write. + if p.count == 0 { + return + } + + // Loop over each item and write it to the page. + // off tracks the offset into p of the start of the next data. + off := unsafe.Sizeof(*p) + n.pageElementSize()*uintptr(len(n.inodes)) + for i, item := range n.inodes { + _assert(len(item.key) > 0, "write: zero-length inode key") + + // Create a slice to write into of needed size and advance + // byte pointer for next iteration. + sz := len(item.key) + len(item.value) + b := unsafeByteSlice(unsafe.Pointer(p), off, 0, sz) + off += uintptr(sz) + + // Write the page element. + if n.isLeaf { + elem := p.leafPageElement(uint16(i)) + elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) + elem.flags = item.flags + elem.ksize = uint32(len(item.key)) + elem.vsize = uint32(len(item.value)) + } else { + elem := p.branchPageElement(uint16(i)) + elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))) + elem.ksize = uint32(len(item.key)) + elem.pgid = item.pgid + _assert(elem.pgid != p.id, "write: circular dependency occurred") + } + + // Write data for the element to the end of the page. + l := copy(b, item.key) + copy(b[l:], item.value) + } + + // DEBUG ONLY: n.dump() +} + +// split breaks up a node into multiple smaller nodes, if appropriate. +// This should only be called from the spill() function. +func (n *node) split(pageSize uintptr) []*node { + var nodes []*node + + node := n + for { + // Split node into two. + a, b := node.splitTwo(pageSize) + nodes = append(nodes, a) + + // If we can't split then exit the loop. + if b == nil { + break + } + + // Set node to b so it gets split on the next iteration. + node = b + } + + return nodes +} + +// splitTwo breaks up a node into two smaller nodes, if appropriate. +// This should only be called from the split() function. +func (n *node) splitTwo(pageSize uintptr) (*node, *node) { + // Ignore the split if the page doesn't have at least enough nodes for + // two pages or if the nodes can fit in a single page. + if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) { + return n, nil + } + + // Determine the threshold before starting a new node. + var fillPercent = n.bucket.FillPercent + if fillPercent < minFillPercent { + fillPercent = minFillPercent + } else if fillPercent > maxFillPercent { + fillPercent = maxFillPercent + } + threshold := int(float64(pageSize) * fillPercent) + + // Determine split position and sizes of the two pages. + splitIndex, _ := n.splitIndex(threshold) + + // Split node into two separate nodes. + // If there's no parent then we'll need to create one. + if n.parent == nil { + n.parent = &node{bucket: n.bucket, children: []*node{n}} + } + + // Create a new node and add it to the parent. + next := &node{bucket: n.bucket, isLeaf: n.isLeaf, parent: n.parent} + n.parent.children = append(n.parent.children, next) + + // Split inodes across two nodes. + next.inodes = n.inodes[splitIndex:] + n.inodes = n.inodes[:splitIndex] + + // Update the statistics. + n.bucket.tx.stats.Split++ + + return n, next +} + +// splitIndex finds the position where a page will fill a given threshold. +// It returns the index as well as the size of the first page. +// This is only be called from split(). +func (n *node) splitIndex(threshold int) (index, sz uintptr) { + sz = pageHeaderSize + + // Loop until we only have the minimum number of keys required for the second page. + for i := 0; i < len(n.inodes)-minKeysPerPage; i++ { + index = uintptr(i) + inode := n.inodes[i] + elsize := n.pageElementSize() + uintptr(len(inode.key)) + uintptr(len(inode.value)) + + // If we have at least the minimum number of keys and adding another + // node would put us over the threshold then exit and return. + if index >= minKeysPerPage && sz+elsize > uintptr(threshold) { + break + } + + // Add the element size to the total size. + sz += elsize + } + + return +} + +// spill writes the nodes to dirty pages and splits nodes as it goes. +// Returns an error if dirty pages cannot be allocated. +func (n *node) spill() error { + var tx = n.bucket.tx + if n.spilled { + return nil + } + + // Spill child nodes first. Child nodes can materialize sibling nodes in + // the case of split-merge so we cannot use a range loop. We have to check + // the children size on every loop iteration. + sort.Sort(n.children) + for i := 0; i < len(n.children); i++ { + if err := n.children[i].spill(); err != nil { + return err + } + } + + // We no longer need the child list because it's only used for spill tracking. + n.children = nil + + // Split nodes into appropriate sizes. The first node will always be n. + var nodes = n.split(uintptr(tx.db.pageSize)) + for _, node := range nodes { + // Add node's page to the freelist if it's not new. + if node.pgid > 0 { + tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid)) + node.pgid = 0 + } + + // Allocate contiguous space for the node. + p, err := tx.allocate((node.size() + tx.db.pageSize - 1) / tx.db.pageSize) + if err != nil { + return err + } + + // Write the node. + if p.id >= tx.meta.pgid { + panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid)) + } + node.pgid = p.id + node.write(p) + node.spilled = true + + // Insert into parent inodes. + if node.parent != nil { + var key = node.key + if key == nil { + key = node.inodes[0].key + } + + node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0) + node.key = node.inodes[0].key + _assert(len(node.key) > 0, "spill: zero-length node key") + } + + // Update the statistics. + tx.stats.Spill++ + } + + // If the root node split and created a new root then we need to spill that + // as well. We'll clear out the children to make sure it doesn't try to respill. + if n.parent != nil && n.parent.pgid == 0 { + n.children = nil + return n.parent.spill() + } + + return nil +} + +// rebalance attempts to combine the node with sibling nodes if the node fill +// size is below a threshold or if there are not enough keys. +func (n *node) rebalance() { + if !n.unbalanced { + return + } + n.unbalanced = false + + // Update statistics. + n.bucket.tx.stats.Rebalance++ + + // Ignore if node is above threshold (25%) and has enough keys. + var threshold = n.bucket.tx.db.pageSize / 4 + if n.size() > threshold && len(n.inodes) > n.minKeys() { + return + } + + // Root node has special handling. + if n.parent == nil { + // If root node is a branch and only has one node then collapse it. + if !n.isLeaf && len(n.inodes) == 1 { + // Move root's child up. + child := n.bucket.node(n.inodes[0].pgid, n) + n.isLeaf = child.isLeaf + n.inodes = child.inodes[:] + n.children = child.children + + // Reparent all child nodes being moved. + for _, inode := range n.inodes { + if child, ok := n.bucket.nodes[inode.pgid]; ok { + child.parent = n + } + } + + // Remove old child. + child.parent = nil + delete(n.bucket.nodes, child.pgid) + child.free() + } + + return + } + + // If node has no keys then just remove it. + if n.numChildren() == 0 { + n.parent.del(n.key) + n.parent.removeChild(n) + delete(n.bucket.nodes, n.pgid) + n.free() + n.parent.rebalance() + return + } + + _assert(n.parent.numChildren() > 1, "parent must have at least 2 children") + + // Destination node is right sibling if idx == 0, otherwise left sibling. + var target *node + var useNextSibling = (n.parent.childIndex(n) == 0) + if useNextSibling { + target = n.nextSibling() + } else { + target = n.prevSibling() + } + + // If both this node and the target node are too small then merge them. + if useNextSibling { + // Reparent all child nodes being moved. + for _, inode := range target.inodes { + if child, ok := n.bucket.nodes[inode.pgid]; ok { + child.parent.removeChild(child) + child.parent = n + child.parent.children = append(child.parent.children, child) + } + } + + // Copy over inodes from target and remove target. + n.inodes = append(n.inodes, target.inodes...) + n.parent.del(target.key) + n.parent.removeChild(target) + delete(n.bucket.nodes, target.pgid) + target.free() + } else { + // Reparent all child nodes being moved. + for _, inode := range n.inodes { + if child, ok := n.bucket.nodes[inode.pgid]; ok { + child.parent.removeChild(child) + child.parent = target + child.parent.children = append(child.parent.children, child) + } + } + + // Copy over inodes to target and remove node. + target.inodes = append(target.inodes, n.inodes...) + n.parent.del(n.key) + n.parent.removeChild(n) + delete(n.bucket.nodes, n.pgid) + n.free() + } + + // Either this node or the target node was deleted from the parent so rebalance it. + n.parent.rebalance() +} + +// removes a node from the list of in-memory children. +// This does not affect the inodes. +func (n *node) removeChild(target *node) { + for i, child := range n.children { + if child == target { + n.children = append(n.children[:i], n.children[i+1:]...) + return + } + } +} + +// dereference causes the node to copy all its inode key/value references to heap memory. +// This is required when the mmap is reallocated so inodes are not pointing to stale data. +func (n *node) dereference() { + if n.key != nil { + key := make([]byte, len(n.key)) + copy(key, n.key) + n.key = key + _assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node") + } + + for i := range n.inodes { + inode := &n.inodes[i] + + key := make([]byte, len(inode.key)) + copy(key, inode.key) + inode.key = key + _assert(len(inode.key) > 0, "dereference: zero-length inode key") + + value := make([]byte, len(inode.value)) + copy(value, inode.value) + inode.value = value + } + + // Recursively dereference children. + for _, child := range n.children { + child.dereference() + } + + // Update statistics. + n.bucket.tx.stats.NodeDeref++ +} + +// free adds the node's underlying page to the freelist. +func (n *node) free() { + if n.pgid != 0 { + n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid)) + n.pgid = 0 + } +} + +// dump writes the contents of the node to STDERR for debugging purposes. +/* +func (n *node) dump() { + // Write node header. + var typ = "branch" + if n.isLeaf { + typ = "leaf" + } + warnf("[NODE %d {type=%s count=%d}]", n.pgid, typ, len(n.inodes)) + + // Write out abbreviated version of each item. + for _, item := range n.inodes { + if n.isLeaf { + if item.flags&bucketLeafFlag != 0 { + bucket := (*bucket)(unsafe.Pointer(&item.value[0])) + warnf("+L %08x -> (bucket root=%d)", trunc(item.key, 4), bucket.root) + } else { + warnf("+L %08x -> %08x", trunc(item.key, 4), trunc(item.value, 4)) + } + } else { + warnf("+B %08x -> pgid=%d", trunc(item.key, 4), item.pgid) + } + } + warn("") +} +*/ + +type nodes []*node + +func (s nodes) Len() int { return len(s) } +func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s nodes) Less(i, j int) bool { + return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 +} + +// inode represents an internal node inside of a node. +// It can be used to point to elements in a page or point +// to an element which hasn't been added to a page yet. +type inode struct { + flags uint32 + pgid pgid + key []byte + value []byte +} + +type inodes []inode diff --git a/vendor/etcd.io/bbolt/node_test.go b/vendor/etcd.io/bbolt/node_test.go new file mode 100644 index 0000000..eea4d25 --- /dev/null +++ b/vendor/etcd.io/bbolt/node_test.go @@ -0,0 +1,156 @@ +package bbolt + +import ( + "testing" + "unsafe" +) + +// Ensure that a node can insert a key/value. +func TestNode_put(t *testing.T) { + n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{meta: &meta{pgid: 1}}}} + n.put([]byte("baz"), []byte("baz"), []byte("2"), 0, 0) + n.put([]byte("foo"), []byte("foo"), []byte("0"), 0, 0) + n.put([]byte("bar"), []byte("bar"), []byte("1"), 0, 0) + n.put([]byte("foo"), []byte("foo"), []byte("3"), 0, leafPageFlag) + + if len(n.inodes) != 3 { + t.Fatalf("exp=3; got=%d", len(n.inodes)) + } + if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "1" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "baz" || string(v) != "2" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if k, v := n.inodes[2].key, n.inodes[2].value; string(k) != "foo" || string(v) != "3" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if n.inodes[2].flags != uint32(leafPageFlag) { + t.Fatalf("not a leaf: %d", n.inodes[2].flags) + } +} + +// Ensure that a node can deserialize from a leaf page. +func TestNode_read_LeafPage(t *testing.T) { + // Create a page. + var buf [4096]byte + page := (*page)(unsafe.Pointer(&buf[0])) + page.flags = leafPageFlag + page.count = 2 + + // Insert 2 elements at the beginning. sizeof(leafPageElement) == 16 + nodes := (*[3]leafPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(page)) + unsafe.Sizeof(*page))) + nodes[0] = leafPageElement{flags: 0, pos: 32, ksize: 3, vsize: 4} // pos = sizeof(leafPageElement) * 2 + nodes[1] = leafPageElement{flags: 0, pos: 23, ksize: 10, vsize: 3} // pos = sizeof(leafPageElement) + 3 + 4 + + // Write data for the nodes at the end. + const s = "barfoozhelloworldbye" + data := unsafeByteSlice(unsafe.Pointer(&nodes[2]), 0, 0, len(s)) + copy(data, s) + + // Deserialize page into a leaf. + n := &node{} + n.read(page) + + // Check that there are two inodes with correct data. + if !n.isLeaf { + t.Fatal("expected leaf") + } + if len(n.inodes) != 2 { + t.Fatalf("exp=2; got=%d", len(n.inodes)) + } + if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "fooz" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "helloworld" || string(v) != "bye" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } +} + +// Ensure that a node can serialize into a leaf page. +func TestNode_write_LeafPage(t *testing.T) { + // Create a node. + n := &node{isLeaf: true, inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} + n.put([]byte("susy"), []byte("susy"), []byte("que"), 0, 0) + n.put([]byte("ricki"), []byte("ricki"), []byte("lake"), 0, 0) + n.put([]byte("john"), []byte("john"), []byte("johnson"), 0, 0) + + // Write it to a page. + var buf [4096]byte + p := (*page)(unsafe.Pointer(&buf[0])) + n.write(p) + + // Read the page back in. + n2 := &node{} + n2.read(p) + + // Check that the two pages are the same. + if len(n2.inodes) != 3 { + t.Fatalf("exp=3; got=%d", len(n2.inodes)) + } + if k, v := n2.inodes[0].key, n2.inodes[0].value; string(k) != "john" || string(v) != "johnson" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if k, v := n2.inodes[1].key, n2.inodes[1].value; string(k) != "ricki" || string(v) != "lake" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } + if k, v := n2.inodes[2].key, n2.inodes[2].value; string(k) != "susy" || string(v) != "que" { + t.Fatalf("exp=; got=<%s,%s>", k, v) + } +} + +// Ensure that a node can split into appropriate subgroups. +func TestNode_split(t *testing.T) { + // Create a node. + n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} + n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0) + + // Split between 2 & 3. + n.split(100) + + var parent = n.parent + if len(parent.children) != 2 { + t.Fatalf("exp=2; got=%d", len(parent.children)) + } + if len(parent.children[0].inodes) != 2 { + t.Fatalf("exp=2; got=%d", len(parent.children[0].inodes)) + } + if len(parent.children[1].inodes) != 3 { + t.Fatalf("exp=3; got=%d", len(parent.children[1].inodes)) + } +} + +// Ensure that a page with the minimum number of inodes just returns a single node. +func TestNode_split_MinKeys(t *testing.T) { + // Create a node. + n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} + n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) + + // Split. + n.split(20) + if n.parent != nil { + t.Fatalf("expected nil parent") + } +} + +// Ensure that a node that has keys that all fit on a page just returns one leaf. +func TestNode_split_SinglePage(t *testing.T) { + // Create a node. + n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}} + n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0) + n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0) + + // Split. + n.split(4096) + if n.parent != nil { + t.Fatalf("expected nil parent") + } +} diff --git a/vendor/etcd.io/bbolt/page.go b/vendor/etcd.io/bbolt/page.go new file mode 100644 index 0000000..c9a158f --- /dev/null +++ b/vendor/etcd.io/bbolt/page.go @@ -0,0 +1,204 @@ +package bbolt + +import ( + "fmt" + "os" + "sort" + "unsafe" +) + +const pageHeaderSize = unsafe.Sizeof(page{}) + +const minKeysPerPage = 2 + +const branchPageElementSize = unsafe.Sizeof(branchPageElement{}) +const leafPageElementSize = unsafe.Sizeof(leafPageElement{}) + +const ( + branchPageFlag = 0x01 + leafPageFlag = 0x02 + metaPageFlag = 0x04 + freelistPageFlag = 0x10 +) + +const ( + bucketLeafFlag = 0x01 +) + +type pgid uint64 + +type page struct { + id pgid + flags uint16 + count uint16 + overflow uint32 +} + +// typ returns a human readable page type string used for debugging. +func (p *page) typ() string { + if (p.flags & branchPageFlag) != 0 { + return "branch" + } else if (p.flags & leafPageFlag) != 0 { + return "leaf" + } else if (p.flags & metaPageFlag) != 0 { + return "meta" + } else if (p.flags & freelistPageFlag) != 0 { + return "freelist" + } + return fmt.Sprintf("unknown<%02x>", p.flags) +} + +// meta returns a pointer to the metadata section of the page. +func (p *page) meta() *meta { + return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))) +} + +// leafPageElement retrieves the leaf node by index +func (p *page) leafPageElement(index uint16) *leafPageElement { + return (*leafPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), + leafPageElementSize, int(index))) +} + +// leafPageElements retrieves a list of leaf nodes. +func (p *page) leafPageElements() []leafPageElement { + if p.count == 0 { + return nil + } + var elems []leafPageElement + data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)) + unsafeSlice(unsafe.Pointer(&elems), data, int(p.count)) + return elems +} + +// branchPageElement retrieves the branch node by index +func (p *page) branchPageElement(index uint16) *branchPageElement { + return (*branchPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), + unsafe.Sizeof(branchPageElement{}), int(index))) +} + +// branchPageElements retrieves a list of branch nodes. +func (p *page) branchPageElements() []branchPageElement { + if p.count == 0 { + return nil + } + var elems []branchPageElement + data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)) + unsafeSlice(unsafe.Pointer(&elems), data, int(p.count)) + return elems +} + +// dump writes n bytes of the page to STDERR as hex output. +func (p *page) hexdump(n int) { + buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, n) + fmt.Fprintf(os.Stderr, "%x\n", buf) +} + +type pages []*page + +func (s pages) Len() int { return len(s) } +func (s pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s pages) Less(i, j int) bool { return s[i].id < s[j].id } + +// branchPageElement represents a node on a branch page. +type branchPageElement struct { + pos uint32 + ksize uint32 + pgid pgid +} + +// key returns a byte slice of the node key. +func (n *branchPageElement) key() []byte { + return unsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize)) +} + +// leafPageElement represents a node on a leaf page. +type leafPageElement struct { + flags uint32 + pos uint32 + ksize uint32 + vsize uint32 +} + +// key returns a byte slice of the node key. +func (n *leafPageElement) key() []byte { + i := int(n.pos) + j := i + int(n.ksize) + return unsafeByteSlice(unsafe.Pointer(n), 0, i, j) +} + +// value returns a byte slice of the node value. +func (n *leafPageElement) value() []byte { + i := int(n.pos) + int(n.ksize) + j := i + int(n.vsize) + return unsafeByteSlice(unsafe.Pointer(n), 0, i, j) +} + +// PageInfo represents human readable information about a page. +type PageInfo struct { + ID int + Type string + Count int + OverflowCount int +} + +type pgids []pgid + +func (s pgids) Len() int { return len(s) } +func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s pgids) Less(i, j int) bool { return s[i] < s[j] } + +// merge returns the sorted union of a and b. +func (a pgids) merge(b pgids) pgids { + // Return the opposite slice if one is nil. + if len(a) == 0 { + return b + } + if len(b) == 0 { + return a + } + merged := make(pgids, len(a)+len(b)) + mergepgids(merged, a, b) + return merged +} + +// mergepgids copies the sorted union of a and b into dst. +// If dst is too small, it panics. +func mergepgids(dst, a, b pgids) { + if len(dst) < len(a)+len(b) { + panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b))) + } + // Copy in the opposite slice if one is nil. + if len(a) == 0 { + copy(dst, b) + return + } + if len(b) == 0 { + copy(dst, a) + return + } + + // Merged will hold all elements from both lists. + merged := dst[:0] + + // Assign lead to the slice with a lower starting value, follow to the higher value. + lead, follow := a, b + if b[0] < a[0] { + lead, follow = b, a + } + + // Continue while there are elements in the lead. + for len(lead) > 0 { + // Merge largest prefix of lead that is ahead of follow[0]. + n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] }) + merged = append(merged, lead[:n]...) + if n >= len(lead) { + break + } + + // Swap lead and follow. + lead, follow = follow, lead[n:] + } + + // Append what's left in follow. + _ = append(merged, follow...) +} diff --git a/vendor/etcd.io/bbolt/page_test.go b/vendor/etcd.io/bbolt/page_test.go new file mode 100644 index 0000000..9f5b7c0 --- /dev/null +++ b/vendor/etcd.io/bbolt/page_test.go @@ -0,0 +1,72 @@ +package bbolt + +import ( + "reflect" + "sort" + "testing" + "testing/quick" +) + +// Ensure that the page type can be returned in human readable format. +func TestPage_typ(t *testing.T) { + if typ := (&page{flags: branchPageFlag}).typ(); typ != "branch" { + t.Fatalf("exp=branch; got=%v", typ) + } + if typ := (&page{flags: leafPageFlag}).typ(); typ != "leaf" { + t.Fatalf("exp=leaf; got=%v", typ) + } + if typ := (&page{flags: metaPageFlag}).typ(); typ != "meta" { + t.Fatalf("exp=meta; got=%v", typ) + } + if typ := (&page{flags: freelistPageFlag}).typ(); typ != "freelist" { + t.Fatalf("exp=freelist; got=%v", typ) + } + if typ := (&page{flags: 20000}).typ(); typ != "unknown<4e20>" { + t.Fatalf("exp=unknown<4e20>; got=%v", typ) + } +} + +// Ensure that the hexdump debugging function doesn't blow up. +func TestPage_dump(t *testing.T) { + (&page{id: 256}).hexdump(16) +} + +func TestPgids_merge(t *testing.T) { + a := pgids{4, 5, 6, 10, 11, 12, 13, 27} + b := pgids{1, 3, 8, 9, 25, 30} + c := a.merge(b) + if !reflect.DeepEqual(c, pgids{1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30}) { + t.Errorf("mismatch: %v", c) + } + + a = pgids{4, 5, 6, 10, 11, 12, 13, 27, 35, 36} + b = pgids{8, 9, 25, 30} + c = a.merge(b) + if !reflect.DeepEqual(c, pgids{4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30, 35, 36}) { + t.Errorf("mismatch: %v", c) + } +} + +func TestPgids_merge_quick(t *testing.T) { + if err := quick.Check(func(a, b pgids) bool { + // Sort incoming lists. + sort.Sort(a) + sort.Sort(b) + + // Merge the two lists together. + got := a.merge(b) + + // The expected value should be the two lists combined and sorted. + exp := append(a, b...) + sort.Sort(exp) + + if !reflect.DeepEqual(exp, got) { + t.Errorf("\nexp=%+v\ngot=%+v\n", exp, got) + return false + } + + return true + }, nil); err != nil { + t.Fatal(err) + } +} diff --git a/vendor/etcd.io/bbolt/quick_test.go b/vendor/etcd.io/bbolt/quick_test.go new file mode 100644 index 0000000..da8b2e3 --- /dev/null +++ b/vendor/etcd.io/bbolt/quick_test.go @@ -0,0 +1,90 @@ +package bbolt_test + +import ( + "bytes" + "flag" + "fmt" + "math/rand" + "os" + "reflect" + "testing" + "testing/quick" + "time" +) + +// testing/quick defaults to 5 iterations and a random seed. +// You can override these settings from the command line: +// +// -quick.count The number of iterations to perform. +// -quick.seed The seed to use for randomizing. +// -quick.maxitems The maximum number of items to insert into a DB. +// -quick.maxksize The maximum size of a key. +// -quick.maxvsize The maximum size of a value. +// + +var qcount, qseed, qmaxitems, qmaxksize, qmaxvsize int + +func TestMain(m *testing.M) { + flag.IntVar(&qcount, "quick.count", 5, "") + flag.IntVar(&qseed, "quick.seed", int(time.Now().UnixNano())%100000, "") + flag.IntVar(&qmaxitems, "quick.maxitems", 1000, "") + flag.IntVar(&qmaxksize, "quick.maxksize", 1024, "") + flag.IntVar(&qmaxvsize, "quick.maxvsize", 1024, "") + flag.Parse() + fmt.Fprintln(os.Stderr, "seed:", qseed) + fmt.Fprintf(os.Stderr, "quick settings: count=%v, items=%v, ksize=%v, vsize=%v\n", qcount, qmaxitems, qmaxksize, qmaxvsize) + + m.Run() +} + +func qconfig() *quick.Config { + return &quick.Config{ + MaxCount: qcount, + Rand: rand.New(rand.NewSource(int64(qseed))), + } +} + +type testdata []testdataitem + +func (t testdata) Len() int { return len(t) } +func (t testdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t testdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == -1 } + +func (t testdata) Generate(rand *rand.Rand, size int) reflect.Value { + n := rand.Intn(qmaxitems-1) + 1 + items := make(testdata, n) + used := make(map[string]bool) + for i := 0; i < n; i++ { + item := &items[i] + // Ensure that keys are unique by looping until we find one that we have not already used. + for { + item.Key = randByteSlice(rand, 1, qmaxksize) + if !used[string(item.Key)] { + used[string(item.Key)] = true + break + } + } + item.Value = randByteSlice(rand, 0, qmaxvsize) + } + return reflect.ValueOf(items) +} + +type revtestdata []testdataitem + +func (t revtestdata) Len() int { return len(t) } +func (t revtestdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t revtestdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == 1 } + +type testdataitem struct { + Key []byte + Value []byte +} + +func randByteSlice(rand *rand.Rand, minSize, maxSize int) []byte { + n := rand.Intn(maxSize-minSize) + minSize + b := make([]byte, n) + for i := 0; i < n; i++ { + b[i] = byte(rand.Intn(255)) + } + return b +} diff --git a/vendor/etcd.io/bbolt/simulation_no_freelist_sync_test.go b/vendor/etcd.io/bbolt/simulation_no_freelist_sync_test.go new file mode 100644 index 0000000..25c3dfb --- /dev/null +++ b/vendor/etcd.io/bbolt/simulation_no_freelist_sync_test.go @@ -0,0 +1,47 @@ +package bbolt_test + +import ( + "testing" + + bolt "go.etcd.io/bbolt" +) + +func TestSimulateNoFreeListSync_1op_1p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 1, 1) +} +func TestSimulateNoFreeListSync_10op_1p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10, 1) +} +func TestSimulateNoFreeListSync_100op_1p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 100, 1) +} +func TestSimulateNoFreeListSync_1000op_1p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 1000, 1) +} +func TestSimulateNoFreeListSync_10000op_1p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10000, 1) +} +func TestSimulateNoFreeListSync_10op_10p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10, 10) +} +func TestSimulateNoFreeListSync_100op_10p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 100, 10) +} +func TestSimulateNoFreeListSync_1000op_10p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 1000, 10) +} +func TestSimulateNoFreeListSync_10000op_10p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10000, 10) +} +func TestSimulateNoFreeListSync_100op_100p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 100, 100) +} +func TestSimulateNoFreeListSync_1000op_100p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 1000, 100) +} +func TestSimulateNoFreeListSync_10000op_100p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10000, 100) +} +func TestSimulateNoFreeListSync_10000op_1000p(t *testing.T) { + testSimulate(t, &bolt.Options{NoFreelistSync: true}, 8, 10000, 1000) +} diff --git a/vendor/etcd.io/bbolt/simulation_test.go b/vendor/etcd.io/bbolt/simulation_test.go new file mode 100644 index 0000000..a96a241 --- /dev/null +++ b/vendor/etcd.io/bbolt/simulation_test.go @@ -0,0 +1,361 @@ +package bbolt_test + +import ( + "bytes" + "fmt" + "math/rand" + "sync" + "sync/atomic" + "testing" + + bolt "go.etcd.io/bbolt" +) + +func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, nil, 1, 1, 1) } +func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, nil, 1, 10, 1) } +func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, nil, 1, 100, 1) } +func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, nil, 1, 1000, 1) } +func TestSimulate_10000op_1p(t *testing.T) { testSimulate(t, nil, 1, 10000, 1) } + +func TestSimulate_10op_10p(t *testing.T) { testSimulate(t, nil, 1, 10, 10) } +func TestSimulate_100op_10p(t *testing.T) { testSimulate(t, nil, 1, 100, 10) } +func TestSimulate_1000op_10p(t *testing.T) { testSimulate(t, nil, 1, 1000, 10) } +func TestSimulate_10000op_10p(t *testing.T) { testSimulate(t, nil, 1, 10000, 10) } + +func TestSimulate_100op_100p(t *testing.T) { testSimulate(t, nil, 1, 100, 100) } +func TestSimulate_1000op_100p(t *testing.T) { testSimulate(t, nil, 1, 1000, 100) } +func TestSimulate_10000op_100p(t *testing.T) { testSimulate(t, nil, 1, 10000, 100) } + +func TestSimulate_10000op_1000p(t *testing.T) { testSimulate(t, nil, 1, 10000, 1000) } + +// Randomly generate operations on a given database with multiple clients to ensure consistency and thread safety. +func testSimulate(t *testing.T, openOption *bolt.Options, round, threadCount, parallelism int) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + rand.Seed(int64(qseed)) + + // A list of operations that readers and writers can perform. + var readerHandlers = []simulateHandler{simulateGetHandler} + var writerHandlers = []simulateHandler{simulateGetHandler, simulatePutHandler} + + var versions = make(map[int]*QuickDB) + versions[1] = NewQuickDB() + + db := MustOpenWithOption(openOption) + defer db.MustClose() + + var mutex sync.Mutex + + for n := 0; n < round; n++ { + // Run n threads in parallel, each with their own operation. + var threads = make(chan bool, parallelism) + var wg sync.WaitGroup + + // counter for how many goroutines were fired + var opCount int64 + + // counter for ignored operations + var igCount int64 + + var errCh = make(chan error, threadCount) + + var i int + for { + // this buffered channel will keep accepting booleans + // until it hits the limit defined by the parallelism + // argument to testSimulate() + threads <- true + + // this wait group can only be marked "done" from inside + // the subsequent goroutine + wg.Add(1) + writable := ((rand.Int() % 100) < 20) // 20% writers + + // Choose an operation to execute. + var handler simulateHandler + if writable { + handler = writerHandlers[rand.Intn(len(writerHandlers))] + } else { + handler = readerHandlers[rand.Intn(len(readerHandlers))] + } + + // Execute a thread for the given operation. + go func(writable bool, handler simulateHandler) { + defer wg.Done() + atomic.AddInt64(&opCount, 1) + // Start transaction. + tx, err := db.Begin(writable) + if err != nil { + errCh <- fmt.Errorf("error tx begin: %v", err) + return + } + + // Obtain current state of the dataset. + mutex.Lock() + var qdb = versions[tx.ID()] + if writable { + qdb = versions[tx.ID()-1].Copy() + } + mutex.Unlock() + + // Make sure we commit/rollback the tx at the end and update the state. + if writable { + defer func() { + mutex.Lock() + versions[tx.ID()] = qdb + mutex.Unlock() + + if err := tx.Commit(); err != nil { + errCh <- err + return + } + }() + } else { + defer func() { _ = tx.Rollback() }() + } + + // Ignore operation if we don't have data yet. + if qdb == nil { + atomic.AddInt64(&igCount, 1) + return + } + + // Execute handler. + handler(tx, qdb) + + // Release a thread back to the scheduling loop. + <-threads + }(writable, handler) + + i++ + if i >= threadCount { + break + } + } + + // Wait until all threads are done. + wg.Wait() + t.Logf("transactions:%d ignored:%d", opCount, igCount) + close(errCh) + for err := range errCh { + if err != nil { + t.Fatalf("error from inside goroutine: %v", err) + } + } + + db.MustClose() + db.MustReopen() + } + +} + +type simulateHandler func(tx *bolt.Tx, qdb *QuickDB) + +// Retrieves a key from the database and verifies that it is what is expected. +func simulateGetHandler(tx *bolt.Tx, qdb *QuickDB) { + // Randomly retrieve an existing exist. + keys := qdb.Rand() + if len(keys) == 0 { + return + } + + // Retrieve root bucket. + b := tx.Bucket(keys[0]) + if b == nil { + panic(fmt.Sprintf("bucket[0] expected: %08x\n", trunc(keys[0], 4))) + } + + // Drill into nested buckets. + for _, key := range keys[1 : len(keys)-1] { + b = b.Bucket(key) + if b == nil { + panic(fmt.Sprintf("bucket[n] expected: %v -> %v\n", keys, key)) + } + } + + // Verify key/value on the final bucket. + expected := qdb.Get(keys) + actual := b.Get(keys[len(keys)-1]) + if !bytes.Equal(actual, expected) { + fmt.Println("=== EXPECTED ===") + fmt.Println(expected) + fmt.Println("=== ACTUAL ===") + fmt.Println(actual) + fmt.Println("=== END ===") + panic("value mismatch") + } +} + +// Inserts a key into the database. +func simulatePutHandler(tx *bolt.Tx, qdb *QuickDB) { + var err error + keys, value := randKeys(), randValue() + + // Retrieve root bucket. + b := tx.Bucket(keys[0]) + if b == nil { + b, err = tx.CreateBucket(keys[0]) + if err != nil { + panic("create bucket: " + err.Error()) + } + } + + // Create nested buckets, if necessary. + for _, key := range keys[1 : len(keys)-1] { + child := b.Bucket(key) + if child != nil { + b = child + } else { + b, err = b.CreateBucket(key) + if err != nil { + panic("create bucket: " + err.Error()) + } + } + } + + // Insert into database. + if err := b.Put(keys[len(keys)-1], value); err != nil { + panic("put: " + err.Error()) + } + + // Insert into in-memory database. + qdb.Put(keys, value) +} + +// QuickDB is an in-memory database that replicates the functionality of the +// Bolt DB type except that it is entirely in-memory. It is meant for testing +// that the Bolt database is consistent. +type QuickDB struct { + sync.RWMutex + m map[string]interface{} +} + +// NewQuickDB returns an instance of QuickDB. +func NewQuickDB() *QuickDB { + return &QuickDB{m: make(map[string]interface{})} +} + +// Get retrieves the value at a key path. +func (db *QuickDB) Get(keys [][]byte) []byte { + db.RLock() + defer db.RUnlock() + + m := db.m + for _, key := range keys[:len(keys)-1] { + value := m[string(key)] + if value == nil { + return nil + } + switch value := value.(type) { + case map[string]interface{}: + m = value + case []byte: + return nil + } + } + + // Only return if it's a simple value. + if value, ok := m[string(keys[len(keys)-1])].([]byte); ok { + return value + } + return nil +} + +// Put inserts a value into a key path. +func (db *QuickDB) Put(keys [][]byte, value []byte) { + db.Lock() + defer db.Unlock() + + // Build buckets all the way down the key path. + m := db.m + for _, key := range keys[:len(keys)-1] { + if _, ok := m[string(key)].([]byte); ok { + return // Keypath intersects with a simple value. Do nothing. + } + + if m[string(key)] == nil { + m[string(key)] = make(map[string]interface{}) + } + m = m[string(key)].(map[string]interface{}) + } + + // Insert value into the last key. + m[string(keys[len(keys)-1])] = value +} + +// Rand returns a random key path that points to a simple value. +func (db *QuickDB) Rand() [][]byte { + db.RLock() + defer db.RUnlock() + if len(db.m) == 0 { + return nil + } + var keys [][]byte + db.rand(db.m, &keys) + return keys +} + +func (db *QuickDB) rand(m map[string]interface{}, keys *[][]byte) { + i, index := 0, rand.Intn(len(m)) + for k, v := range m { + if i == index { + *keys = append(*keys, []byte(k)) + if v, ok := v.(map[string]interface{}); ok { + db.rand(v, keys) + } + return + } + i++ + } + panic("quickdb rand: out-of-range") +} + +// Copy copies the entire database. +func (db *QuickDB) Copy() *QuickDB { + db.RLock() + defer db.RUnlock() + return &QuickDB{m: db.copy(db.m)} +} + +func (db *QuickDB) copy(m map[string]interface{}) map[string]interface{} { + clone := make(map[string]interface{}, len(m)) + for k, v := range m { + switch v := v.(type) { + case map[string]interface{}: + clone[k] = db.copy(v) + default: + clone[k] = v + } + } + return clone +} + +func randKey() []byte { + var min, max = 1, 1024 + n := rand.Intn(max-min) + min + b := make([]byte, n) + for i := 0; i < n; i++ { + b[i] = byte(rand.Intn(255)) + } + return b +} + +func randKeys() [][]byte { + var keys [][]byte + var count = rand.Intn(2) + 2 + for i := 0; i < count; i++ { + keys = append(keys, randKey()) + } + return keys +} + +func randValue() []byte { + n := rand.Intn(8192) + b := make([]byte, n) + for i := 0; i < n; i++ { + b[i] = byte(rand.Intn(255)) + } + return b +} diff --git a/vendor/etcd.io/bbolt/tx.go b/vendor/etcd.io/bbolt/tx.go new file mode 100644 index 0000000..4b1a64a --- /dev/null +++ b/vendor/etcd.io/bbolt/tx.go @@ -0,0 +1,724 @@ +package bbolt + +import ( + "fmt" + "io" + "os" + "sort" + "strings" + "time" + "unsafe" +) + +// txid represents the internal transaction identifier. +type txid uint64 + +// Tx represents a read-only or read/write transaction on the database. +// Read-only transactions can be used for retrieving values for keys and creating cursors. +// Read/write transactions can create and remove buckets and create and remove keys. +// +// IMPORTANT: You must commit or rollback transactions when you are done with +// them. Pages can not be reclaimed by the writer until no more transactions +// are using them. A long running read transaction can cause the database to +// quickly grow. +type Tx struct { + writable bool + managed bool + db *DB + meta *meta + root Bucket + pages map[pgid]*page + stats TxStats + commitHandlers []func() + + // WriteFlag specifies the flag for write-related methods like WriteTo(). + // Tx opens the database file with the specified flag to copy the data. + // + // By default, the flag is unset, which works well for mostly in-memory + // workloads. For databases that are much larger than available RAM, + // set the flag to syscall.O_DIRECT to avoid trashing the page cache. + WriteFlag int +} + +// init initializes the transaction. +func (tx *Tx) init(db *DB) { + tx.db = db + tx.pages = nil + + // Copy the meta page since it can be changed by the writer. + tx.meta = &meta{} + db.meta().copy(tx.meta) + + // Copy over the root bucket. + tx.root = newBucket(tx) + tx.root.bucket = &bucket{} + *tx.root.bucket = tx.meta.root + + // Increment the transaction id and add a page cache for writable transactions. + if tx.writable { + tx.pages = make(map[pgid]*page) + tx.meta.txid += txid(1) + } +} + +// ID returns the transaction id. +func (tx *Tx) ID() int { + return int(tx.meta.txid) +} + +// DB returns a reference to the database that created the transaction. +func (tx *Tx) DB() *DB { + return tx.db +} + +// Size returns current database size in bytes as seen by this transaction. +func (tx *Tx) Size() int64 { + return int64(tx.meta.pgid) * int64(tx.db.pageSize) +} + +// Writable returns whether the transaction can perform write operations. +func (tx *Tx) Writable() bool { + return tx.writable +} + +// Cursor creates a cursor associated with the root bucket. +// All items in the cursor will return a nil value because all root bucket keys point to buckets. +// The cursor is only valid as long as the transaction is open. +// Do not use a cursor after the transaction is closed. +func (tx *Tx) Cursor() *Cursor { + return tx.root.Cursor() +} + +// Stats retrieves a copy of the current transaction statistics. +func (tx *Tx) Stats() TxStats { + return tx.stats +} + +// Bucket retrieves a bucket by name. +// Returns nil if the bucket does not exist. +// The bucket instance is only valid for the lifetime of the transaction. +func (tx *Tx) Bucket(name []byte) *Bucket { + return tx.root.Bucket(name) +} + +// CreateBucket creates a new bucket. +// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long. +// The bucket instance is only valid for the lifetime of the transaction. +func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) { + return tx.root.CreateBucket(name) +} + +// CreateBucketIfNotExists creates a new bucket if it doesn't already exist. +// Returns an error if the bucket name is blank, or if the bucket name is too long. +// The bucket instance is only valid for the lifetime of the transaction. +func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) { + return tx.root.CreateBucketIfNotExists(name) +} + +// DeleteBucket deletes a bucket. +// Returns an error if the bucket cannot be found or if the key represents a non-bucket value. +func (tx *Tx) DeleteBucket(name []byte) error { + return tx.root.DeleteBucket(name) +} + +// ForEach executes a function for each bucket in the root. +// If the provided function returns an error then the iteration is stopped and +// the error is returned to the caller. +func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error { + return tx.root.ForEach(func(k, v []byte) error { + return fn(k, tx.root.Bucket(k)) + }) +} + +// OnCommit adds a handler function to be executed after the transaction successfully commits. +func (tx *Tx) OnCommit(fn func()) { + tx.commitHandlers = append(tx.commitHandlers, fn) +} + +// Commit writes all changes to disk and updates the meta page. +// Returns an error if a disk write error occurs, or if Commit is +// called on a read-only transaction. +func (tx *Tx) Commit() error { + _assert(!tx.managed, "managed tx commit not allowed") + if tx.db == nil { + return ErrTxClosed + } else if !tx.writable { + return ErrTxNotWritable + } + + // TODO(benbjohnson): Use vectorized I/O to write out dirty pages. + + // Rebalance nodes which have had deletions. + var startTime = time.Now() + tx.root.rebalance() + if tx.stats.Rebalance > 0 { + tx.stats.RebalanceTime += time.Since(startTime) + } + + // spill data onto dirty pages. + startTime = time.Now() + if err := tx.root.spill(); err != nil { + tx.rollback() + return err + } + tx.stats.SpillTime += time.Since(startTime) + + // Free the old root bucket. + tx.meta.root.root = tx.root.root + + // Free the old freelist because commit writes out a fresh freelist. + if tx.meta.freelist != pgidNoFreelist { + tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist)) + } + + if !tx.db.NoFreelistSync { + err := tx.commitFreelist() + if err != nil { + return err + } + } else { + tx.meta.freelist = pgidNoFreelist + } + + // Write dirty pages to disk. + startTime = time.Now() + if err := tx.write(); err != nil { + tx.rollback() + return err + } + + // If strict mode is enabled then perform a consistency check. + // Only the first consistency error is reported in the panic. + if tx.db.StrictMode { + ch := tx.Check() + var errs []string + for { + err, ok := <-ch + if !ok { + break + } + errs = append(errs, err.Error()) + } + if len(errs) > 0 { + panic("check fail: " + strings.Join(errs, "\n")) + } + } + + // Write meta to disk. + if err := tx.writeMeta(); err != nil { + tx.rollback() + return err + } + tx.stats.WriteTime += time.Since(startTime) + + // Finalize the transaction. + tx.close() + + // Execute commit handlers now that the locks have been removed. + for _, fn := range tx.commitHandlers { + fn() + } + + return nil +} + +func (tx *Tx) commitFreelist() error { + // Allocate new pages for the new free list. This will overestimate + // the size of the freelist but not underestimate the size (which would be bad). + opgid := tx.meta.pgid + p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1) + if err != nil { + tx.rollback() + return err + } + if err := tx.db.freelist.write(p); err != nil { + tx.rollback() + return err + } + tx.meta.freelist = p.id + // If the high water mark has moved up then attempt to grow the database. + if tx.meta.pgid > opgid { + if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil { + tx.rollback() + return err + } + } + + return nil +} + +// Rollback closes the transaction and ignores all previous updates. Read-only +// transactions must be rolled back and not committed. +func (tx *Tx) Rollback() error { + _assert(!tx.managed, "managed tx rollback not allowed") + if tx.db == nil { + return ErrTxClosed + } + tx.nonPhysicalRollback() + return nil +} + +// nonPhysicalRollback is called when user calls Rollback directly, in this case we do not need to reload the free pages from disk. +func (tx *Tx) nonPhysicalRollback() { + if tx.db == nil { + return + } + if tx.writable { + tx.db.freelist.rollback(tx.meta.txid) + } + tx.close() +} + +// rollback needs to reload the free pages from disk in case some system error happens like fsync error. +func (tx *Tx) rollback() { + if tx.db == nil { + return + } + if tx.writable { + tx.db.freelist.rollback(tx.meta.txid) + if !tx.db.hasSyncedFreelist() { + // Reconstruct free page list by scanning the DB to get the whole free page list. + // Note: scaning the whole db is heavy if your db size is large in NoSyncFreeList mode. + tx.db.freelist.noSyncReload(tx.db.freepages()) + } else { + // Read free page list from freelist page. + tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist)) + } + } + tx.close() +} + +func (tx *Tx) close() { + if tx.db == nil { + return + } + if tx.writable { + // Grab freelist stats. + var freelistFreeN = tx.db.freelist.free_count() + var freelistPendingN = tx.db.freelist.pending_count() + var freelistAlloc = tx.db.freelist.size() + + // Remove transaction ref & writer lock. + tx.db.rwtx = nil + tx.db.rwlock.Unlock() + + // Merge statistics. + tx.db.statlock.Lock() + tx.db.stats.FreePageN = freelistFreeN + tx.db.stats.PendingPageN = freelistPendingN + tx.db.stats.FreeAlloc = (freelistFreeN + freelistPendingN) * tx.db.pageSize + tx.db.stats.FreelistInuse = freelistAlloc + tx.db.stats.TxStats.add(&tx.stats) + tx.db.statlock.Unlock() + } else { + tx.db.removeTx(tx) + } + + // Clear all references. + tx.db = nil + tx.meta = nil + tx.root = Bucket{tx: tx} + tx.pages = nil +} + +// Copy writes the entire database to a writer. +// This function exists for backwards compatibility. +// +// Deprecated; Use WriteTo() instead. +func (tx *Tx) Copy(w io.Writer) error { + _, err := tx.WriteTo(w) + return err +} + +// WriteTo writes the entire database to a writer. +// If err == nil then exactly tx.Size() bytes will be written into the writer. +func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) { + // Attempt to open reader with WriteFlag + f, err := tx.db.openFile(tx.db.path, os.O_RDONLY|tx.WriteFlag, 0) + if err != nil { + return 0, err + } + defer func() { + if cerr := f.Close(); err == nil { + err = cerr + } + }() + + // Generate a meta page. We use the same page data for both meta pages. + buf := make([]byte, tx.db.pageSize) + page := (*page)(unsafe.Pointer(&buf[0])) + page.flags = metaPageFlag + *page.meta() = *tx.meta + + // Write meta 0. + page.id = 0 + page.meta().checksum = page.meta().sum64() + nn, err := w.Write(buf) + n += int64(nn) + if err != nil { + return n, fmt.Errorf("meta 0 copy: %s", err) + } + + // Write meta 1 with a lower transaction id. + page.id = 1 + page.meta().txid -= 1 + page.meta().checksum = page.meta().sum64() + nn, err = w.Write(buf) + n += int64(nn) + if err != nil { + return n, fmt.Errorf("meta 1 copy: %s", err) + } + + // Move past the meta pages in the file. + if _, err := f.Seek(int64(tx.db.pageSize*2), io.SeekStart); err != nil { + return n, fmt.Errorf("seek: %s", err) + } + + // Copy data pages. + wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)) + n += wn + if err != nil { + return n, err + } + + return n, nil +} + +// CopyFile copies the entire database to file at the given path. +// A reader transaction is maintained during the copy so it is safe to continue +// using the database while a copy is in progress. +func (tx *Tx) CopyFile(path string, mode os.FileMode) error { + f, err := tx.db.openFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + + err = tx.Copy(f) + if err != nil { + _ = f.Close() + return err + } + return f.Close() +} + +// Check performs several consistency checks on the database for this transaction. +// An error is returned if any inconsistency is found. +// +// It can be safely run concurrently on a writable transaction. However, this +// incurs a high cost for large databases and databases with a lot of subbuckets +// because of caching. This overhead can be removed if running on a read-only +// transaction, however, it is not safe to execute other writer transactions at +// the same time. +func (tx *Tx) Check() <-chan error { + ch := make(chan error) + go tx.check(ch) + return ch +} + +func (tx *Tx) check(ch chan error) { + // Force loading free list if opened in ReadOnly mode. + tx.db.loadFreelist() + + // Check if any pages are double freed. + freed := make(map[pgid]bool) + all := make([]pgid, tx.db.freelist.count()) + tx.db.freelist.copyall(all) + for _, id := range all { + if freed[id] { + ch <- fmt.Errorf("page %d: already freed", id) + } + freed[id] = true + } + + // Track every reachable page. + reachable := make(map[pgid]*page) + reachable[0] = tx.page(0) // meta0 + reachable[1] = tx.page(1) // meta1 + if tx.meta.freelist != pgidNoFreelist { + for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ { + reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist) + } + } + + // Recursively check buckets. + tx.checkBucket(&tx.root, reachable, freed, ch) + + // Ensure all pages below high water mark are either reachable or freed. + for i := pgid(0); i < tx.meta.pgid; i++ { + _, isReachable := reachable[i] + if !isReachable && !freed[i] { + ch <- fmt.Errorf("page %d: unreachable unfreed", int(i)) + } + } + + // Close the channel to signal completion. + close(ch) +} + +func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) { + // Ignore inline buckets. + if b.root == 0 { + return + } + + // Check every page used by this bucket. + b.tx.forEachPage(b.root, 0, func(p *page, _ int) { + if p.id > tx.meta.pgid { + ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid)) + } + + // Ensure each page is only referenced once. + for i := pgid(0); i <= pgid(p.overflow); i++ { + var id = p.id + i + if _, ok := reachable[id]; ok { + ch <- fmt.Errorf("page %d: multiple references", int(id)) + } + reachable[id] = p + } + + // We should only encounter un-freed leaf and branch pages. + if freed[p.id] { + ch <- fmt.Errorf("page %d: reachable freed", int(p.id)) + } else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 { + ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ()) + } + }) + + // Check each bucket within this bucket. + _ = b.ForEach(func(k, v []byte) error { + if child := b.Bucket(k); child != nil { + tx.checkBucket(child, reachable, freed, ch) + } + return nil + }) +} + +// allocate returns a contiguous block of memory starting at a given page. +func (tx *Tx) allocate(count int) (*page, error) { + p, err := tx.db.allocate(tx.meta.txid, count) + if err != nil { + return nil, err + } + + // Save to our page cache. + tx.pages[p.id] = p + + // Update statistics. + tx.stats.PageCount += count + tx.stats.PageAlloc += count * tx.db.pageSize + + return p, nil +} + +// write writes any dirty pages to disk. +func (tx *Tx) write() error { + // Sort pages by id. + pages := make(pages, 0, len(tx.pages)) + for _, p := range tx.pages { + pages = append(pages, p) + } + // Clear out page cache early. + tx.pages = make(map[pgid]*page) + sort.Sort(pages) + + // Write pages to disk in order. + for _, p := range pages { + rem := (uint64(p.overflow) + 1) * uint64(tx.db.pageSize) + offset := int64(p.id) * int64(tx.db.pageSize) + var written uintptr + + // Write out page in "max allocation" sized chunks. + for { + sz := rem + if sz > maxAllocSize-1 { + sz = maxAllocSize - 1 + } + buf := unsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz)) + + if _, err := tx.db.ops.writeAt(buf, offset); err != nil { + return err + } + + // Update statistics. + tx.stats.Write++ + + // Exit inner for loop if we've written all the chunks. + rem -= sz + if rem == 0 { + break + } + + // Otherwise move offset forward and move pointer to next chunk. + offset += int64(sz) + written += uintptr(sz) + } + } + + // Ignore file sync if flag is set on DB. + if !tx.db.NoSync || IgnoreNoSync { + if err := fdatasync(tx.db); err != nil { + return err + } + } + + // Put small pages back to page pool. + for _, p := range pages { + // Ignore page sizes over 1 page. + // These are allocated using make() instead of the page pool. + if int(p.overflow) != 0 { + continue + } + + buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize) + + // See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1 + for i := range buf { + buf[i] = 0 + } + tx.db.pagePool.Put(buf) + } + + return nil +} + +// writeMeta writes the meta to the disk. +func (tx *Tx) writeMeta() error { + // Create a temporary buffer for the meta page. + buf := make([]byte, tx.db.pageSize) + p := tx.db.pageInBuffer(buf, 0) + tx.meta.write(p) + + // Write the meta page to file. + if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil { + return err + } + if !tx.db.NoSync || IgnoreNoSync { + if err := fdatasync(tx.db); err != nil { + return err + } + } + + // Update statistics. + tx.stats.Write++ + + return nil +} + +// page returns a reference to the page with a given id. +// If page has been written to then a temporary buffered page is returned. +func (tx *Tx) page(id pgid) *page { + // Check the dirty pages first. + if tx.pages != nil { + if p, ok := tx.pages[id]; ok { + return p + } + } + + // Otherwise return directly from the mmap. + return tx.db.page(id) +} + +// forEachPage iterates over every page within a given page and executes a function. +func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) { + p := tx.page(pgid) + + // Execute function. + fn(p, depth) + + // Recursively loop over children. + if (p.flags & branchPageFlag) != 0 { + for i := 0; i < int(p.count); i++ { + elem := p.branchPageElement(uint16(i)) + tx.forEachPage(elem.pgid, depth+1, fn) + } + } +} + +// Page returns page information for a given page number. +// This is only safe for concurrent use when used by a writable transaction. +func (tx *Tx) Page(id int) (*PageInfo, error) { + if tx.db == nil { + return nil, ErrTxClosed + } else if pgid(id) >= tx.meta.pgid { + return nil, nil + } + + // Build the page info. + p := tx.db.page(pgid(id)) + info := &PageInfo{ + ID: id, + Count: int(p.count), + OverflowCount: int(p.overflow), + } + + // Determine the type (or if it's free). + if tx.db.freelist.freed(pgid(id)) { + info.Type = "free" + } else { + info.Type = p.typ() + } + + return info, nil +} + +// TxStats represents statistics about the actions performed by the transaction. +type TxStats struct { + // Page statistics. + PageCount int // number of page allocations + PageAlloc int // total bytes allocated + + // Cursor statistics. + CursorCount int // number of cursors created + + // Node statistics + NodeCount int // number of node allocations + NodeDeref int // number of node dereferences + + // Rebalance statistics. + Rebalance int // number of node rebalances + RebalanceTime time.Duration // total time spent rebalancing + + // Split/Spill statistics. + Split int // number of nodes split + Spill int // number of nodes spilled + SpillTime time.Duration // total time spent spilling + + // Write statistics. + Write int // number of writes performed + WriteTime time.Duration // total time spent writing to disk +} + +func (s *TxStats) add(other *TxStats) { + s.PageCount += other.PageCount + s.PageAlloc += other.PageAlloc + s.CursorCount += other.CursorCount + s.NodeCount += other.NodeCount + s.NodeDeref += other.NodeDeref + s.Rebalance += other.Rebalance + s.RebalanceTime += other.RebalanceTime + s.Split += other.Split + s.Spill += other.Spill + s.SpillTime += other.SpillTime + s.Write += other.Write + s.WriteTime += other.WriteTime +} + +// Sub calculates and returns the difference between two sets of transaction stats. +// This is useful when obtaining stats at two different points and time and +// you need the performance counters that occurred within that time span. +func (s *TxStats) Sub(other *TxStats) TxStats { + var diff TxStats + diff.PageCount = s.PageCount - other.PageCount + diff.PageAlloc = s.PageAlloc - other.PageAlloc + diff.CursorCount = s.CursorCount - other.CursorCount + diff.NodeCount = s.NodeCount - other.NodeCount + diff.NodeDeref = s.NodeDeref - other.NodeDeref + diff.Rebalance = s.Rebalance - other.Rebalance + diff.RebalanceTime = s.RebalanceTime - other.RebalanceTime + diff.Split = s.Split - other.Split + diff.Spill = s.Spill - other.Spill + diff.SpillTime = s.SpillTime - other.SpillTime + diff.Write = s.Write - other.Write + diff.WriteTime = s.WriteTime - other.WriteTime + return diff +} diff --git a/vendor/etcd.io/bbolt/tx_test.go b/vendor/etcd.io/bbolt/tx_test.go new file mode 100644 index 0000000..38a25c6 --- /dev/null +++ b/vendor/etcd.io/bbolt/tx_test.go @@ -0,0 +1,924 @@ +package bbolt_test + +import ( + "bytes" + "errors" + "fmt" + "log" + "os" + "testing" + + bolt "go.etcd.io/bbolt" +) + +// TestTx_Check_ReadOnly tests consistency checking on a ReadOnly database. +func TestTx_Check_ReadOnly(t *testing.T) { + db := MustOpenDB() + defer db.Close() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + if err := db.DB.Close(); err != nil { + t.Fatal(err) + } + + readOnlyDB, err := bolt.Open(db.f, 0666, &bolt.Options{ReadOnly: true}) + if err != nil { + t.Fatal(err) + } + defer readOnlyDB.Close() + + tx, err := readOnlyDB.Begin(false) + if err != nil { + t.Fatal(err) + } + // ReadOnly DB will load freelist on Check call. + numChecks := 2 + errc := make(chan error, numChecks) + check := func() { + err, _ := <-tx.Check() + errc <- err + } + // Ensure the freelist is not reloaded and does not race. + for i := 0; i < numChecks; i++ { + go check() + } + for i := 0; i < numChecks; i++ { + if err := <-errc; err != nil { + t.Fatal(err) + } + } + // Close the view transaction + tx.Rollback() +} + +// Ensure that committing a closed transaction returns an error. +func TestTx_Commit_ErrTxClosed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + if _, err := tx.CreateBucket([]byte("foo")); err != nil { + t.Fatal(err) + } + + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + + if err := tx.Commit(); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that rolling back a closed transaction returns an error. +func TestTx_Rollback_ErrTxClosed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + + if err := tx.Rollback(); err != nil { + t.Fatal(err) + } + if err := tx.Rollback(); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that committing a read-only transaction returns an error. +func TestTx_Commit_ErrTxNotWritable(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + if err := tx.Commit(); err != bolt.ErrTxNotWritable { + t.Fatal(err) + } + // Close the view transaction + tx.Rollback() +} + +// Ensure that a transaction can retrieve a cursor on the root bucket. +func TestTx_Cursor(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + + if _, err := tx.CreateBucket([]byte("woojits")); err != nil { + t.Fatal(err) + } + + c := tx.Cursor() + if k, v := c.First(); !bytes.Equal(k, []byte("widgets")) { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("unexpected value: %v", v) + } + + if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("unexpected value: %v", v) + } + + if k, v := c.Next(); k != nil { + t.Fatalf("unexpected key: %v", k) + } else if v != nil { + t.Fatalf("unexpected value: %v", k) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that creating a bucket with a read-only transaction returns an error. +func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.View(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("foo")) + if err != bolt.ErrTxNotWritable { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that creating a bucket on a closed transaction returns an error. +func TestTx_CreateBucket_ErrTxClosed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + + if _, err := tx.CreateBucket([]byte("foo")); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that a Tx can retrieve a bucket. +func TestTx_Bucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a Tx retrieving a non-existent key returns nil. +func TestTx_Get_NotFound(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if b.Get([]byte("no_such_key")) != nil { + t.Fatal("expected nil value") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can be created and retrieved. +func TestTx_CreateBucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Create a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } else if b == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Read the bucket through a separate transaction. + if err := db.View(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can be created if it doesn't already exist. +func TestTx_CreateBucketIfNotExists(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + // Create bucket. + if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { + t.Fatal(err) + } else if b == nil { + t.Fatal("expected bucket") + } + + // Create bucket again. + if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { + t.Fatal(err) + } else if b == nil { + t.Fatal("expected bucket") + } + + return nil + }); err != nil { + t.Fatal(err) + } + + // Read the bucket through a separate transaction. + if err := db.View(func(tx *bolt.Tx) error { + if tx.Bucket([]byte("widgets")) == nil { + t.Fatal("expected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure transaction returns an error if creating an unnamed bucket. +func TestTx_CreateBucketIfNotExists_ErrBucketNameRequired(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte{}); err != bolt.ErrBucketNameRequired { + t.Fatalf("unexpected error: %s", err) + } + + if _, err := tx.CreateBucketIfNotExists(nil); err != bolt.ErrBucketNameRequired { + t.Fatalf("unexpected error: %s", err) + } + + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket cannot be created twice. +func TestTx_CreateBucket_ErrBucketExists(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Create a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Create the same bucket again. + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket([]byte("widgets")); err != bolt.ErrBucketExists { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket is created with a non-blank name. +func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucket(nil); err != bolt.ErrBucketNameRequired { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that a bucket can be deleted. +func TestTx_DeleteBucket(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + // Create a bucket and add a value. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + // Delete the bucket and make sure we can't get the value. + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + if tx.Bucket([]byte("widgets")) != nil { + t.Fatal("unexpected bucket") + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + // Create the bucket again and make sure there's not a phantom value. + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if v := b.Get([]byte("foo")); v != nil { + t.Fatalf("unexpected phantom value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that deleting a bucket on a closed transaction returns an error. +func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + tx, err := db.Begin(true) + if err != nil { + t.Fatal(err) + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxClosed { + t.Fatalf("unexpected error: %s", err) + } +} + +// Ensure that deleting a bucket with a read-only transaction returns an error. +func TestTx_DeleteBucket_ReadOnly(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.View(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxNotWritable { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that nothing happens when deleting a bucket that doesn't exist. +func TestTx_DeleteBucket_NotFound(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte("widgets")); err != bolt.ErrBucketNotFound { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that no error is returned when a tx.ForEach function does not return +// an error. +func TestTx_ForEach_NoError(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + + if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { + return nil + }); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that an error is returned when a tx.ForEach function returns an error. +func TestTx_ForEach_WithError(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + + marker := errors.New("marker") + if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { + return marker + }); err != marker { + t.Fatalf("unexpected error: %s", err) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + +// Ensure that Tx commit handlers are called after a transaction successfully commits. +func TestTx_OnCommit(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var x int + if err := db.Update(func(tx *bolt.Tx) error { + tx.OnCommit(func() { x += 1 }) + tx.OnCommit(func() { x += 2 }) + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } else if x != 3 { + t.Fatalf("unexpected x: %d", x) + } +} + +// Ensure that Tx commit handlers are NOT called after a transaction rolls back. +func TestTx_OnCommit_Rollback(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + var x int + if err := db.Update(func(tx *bolt.Tx) error { + tx.OnCommit(func() { x += 1 }) + tx.OnCommit(func() { x += 2 }) + if _, err := tx.CreateBucket([]byte("widgets")); err != nil { + t.Fatal(err) + } + return errors.New("rollback this commit") + }); err == nil || err.Error() != "rollback this commit" { + t.Fatalf("unexpected error: %s", err) + } else if x != 0 { + t.Fatalf("unexpected x: %d", x) + } +} + +// Ensure that the database can be copied to a file path. +func TestTx_CopyFile(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + + path := tempfile() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + return tx.CopyFile(path, 0600) + }); err != nil { + t.Fatal(err) + } + + db2, err := bolt.Open(path, 0600, nil) + if err != nil { + t.Fatal(err) + } + + if err := db2.View(func(tx *bolt.Tx) error { + if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) { + t.Fatalf("unexpected value: %v", v) + } + if v := tx.Bucket([]byte("widgets")).Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { + t.Fatalf("unexpected value: %v", v) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db2.Close(); err != nil { + t.Fatal(err) + } +} + +type failWriterError struct{} + +func (failWriterError) Error() string { + return "error injected for tests" +} + +type failWriter struct { + // fail after this many bytes + After int +} + +func (f *failWriter) Write(p []byte) (n int, err error) { + n = len(p) + if n > f.After { + n = f.After + err = failWriterError{} + } + f.After -= n + return n, err +} + +// Ensure that Copy handles write errors right. +func TestTx_CopyFile_Error_Meta(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + return tx.Copy(&failWriter{}) + }); err == nil || err.Error() != "meta 0 copy: error injected for tests" { + t.Fatalf("unexpected error: %v", err) + } +} + +// Ensure that Copy handles write errors right. +func TestTx_CopyFile_Error_Normal(t *testing.T) { + db := MustOpenDB() + defer db.MustClose() + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + t.Fatal(err) + } + if err := b.Put([]byte("baz"), []byte("bat")); err != nil { + t.Fatal(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + if err := db.View(func(tx *bolt.Tx) error { + return tx.Copy(&failWriter{3 * db.Info().PageSize}) + }); err == nil || err.Error() != "error injected for tests" { + t.Fatalf("unexpected error: %v", err) + } +} + +// TestTx_Rollback ensures there is no error when tx rollback whether we sync freelist or not. +func TestTx_Rollback(t *testing.T) { + for _, isSyncFreelist := range []bool{false, true} { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + db.NoFreelistSync = isSyncFreelist + + tx, err := db.Begin(true) + if err != nil { + t.Fatalf("Error starting tx: %v", err) + } + bucket := []byte("mybucket") + if _, err := tx.CreateBucket(bucket); err != nil { + t.Fatalf("Error creating bucket: %v", err) + } + if err := tx.Commit(); err != nil { + t.Fatalf("Error on commit: %v", err) + } + + tx, err = db.Begin(true) + if err != nil { + t.Fatalf("Error starting tx: %v", err) + } + b := tx.Bucket(bucket) + if err := b.Put([]byte("k"), []byte("v")); err != nil { + t.Fatalf("Error on put: %v", err) + } + // Imagine there is an error and tx needs to be rolled-back + if err := tx.Rollback(); err != nil { + t.Fatalf("Error on rollback: %v", err) + } + + tx, err = db.Begin(false) + if err != nil { + t.Fatalf("Error starting tx: %v", err) + } + b = tx.Bucket(bucket) + if v := b.Get([]byte("k")); v != nil { + t.Fatalf("Value for k should not have been stored") + } + if err := tx.Rollback(); err != nil { + t.Fatalf("Error on rollback: %v", err) + } + + } +} + +// TestTx_releaseRange ensures db.freePages handles page releases +// correctly when there are transaction that are no longer reachable +// via any read/write transactions and are "between" ongoing read +// transactions, which requires they must be freed by +// freelist.releaseRange. +func TestTx_releaseRange(t *testing.T) { + // Set initial mmap size well beyond the limit we will hit in this + // test, since we are testing with long running read transactions + // and will deadlock if db.grow is triggered. + db := MustOpenWithOption(&bolt.Options{InitialMmapSize: os.Getpagesize() * 100}) + defer db.MustClose() + + bucket := "bucket" + + put := func(key, value string) { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte(bucket)) + if err != nil { + t.Fatal(err) + } + return b.Put([]byte(key), []byte(value)) + }); err != nil { + t.Fatal(err) + } + } + + del := func(key string) { + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte(bucket)) + if err != nil { + t.Fatal(err) + } + return b.Delete([]byte(key)) + }); err != nil { + t.Fatal(err) + } + } + + getWithTxn := func(txn *bolt.Tx, key string) []byte { + return txn.Bucket([]byte(bucket)).Get([]byte(key)) + } + + openReadTxn := func() *bolt.Tx { + readTx, err := db.Begin(false) + if err != nil { + t.Fatal(err) + } + return readTx + } + + checkWithReadTxn := func(txn *bolt.Tx, key string, wantValue []byte) { + value := getWithTxn(txn, key) + if !bytes.Equal(value, wantValue) { + t.Errorf("Wanted value to be %s for key %s, but got %s", wantValue, key, string(value)) + } + } + + rollback := func(txn *bolt.Tx) { + if err := txn.Rollback(); err != nil { + t.Fatal(err) + } + } + + put("k1", "v1") + rtx1 := openReadTxn() + put("k2", "v2") + hold1 := openReadTxn() + put("k3", "v3") + hold2 := openReadTxn() + del("k3") + rtx2 := openReadTxn() + del("k1") + hold3 := openReadTxn() + del("k2") + hold4 := openReadTxn() + put("k4", "v4") + hold5 := openReadTxn() + + // Close the read transactions we established to hold a portion of the pages in pending state. + rollback(hold1) + rollback(hold2) + rollback(hold3) + rollback(hold4) + rollback(hold5) + + // Execute a write transaction to trigger a releaseRange operation in the db + // that will free multiple ranges between the remaining open read transactions, now that the + // holds have been rolled back. + put("k4", "v4") + + // Check that all long running reads still read correct values. + checkWithReadTxn(rtx1, "k1", []byte("v1")) + checkWithReadTxn(rtx2, "k2", []byte("v2")) + rollback(rtx1) + rollback(rtx2) + + // Check that the final state is correct. + rtx7 := openReadTxn() + checkWithReadTxn(rtx7, "k1", nil) + checkWithReadTxn(rtx7, "k2", nil) + checkWithReadTxn(rtx7, "k3", nil) + checkWithReadTxn(rtx7, "k4", []byte("v4")) + rollback(rtx7) +} + +func ExampleTx_Rollback() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Create a bucket. + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("widgets")) + return err + }); err != nil { + log.Fatal(err) + } + + // Set a value for a key. + if err := db.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) + }); err != nil { + log.Fatal(err) + } + + // Update the key but rollback the transaction so it never saves. + tx, err := db.Begin(true) + if err != nil { + log.Fatal(err) + } + b := tx.Bucket([]byte("widgets")) + if err := b.Put([]byte("foo"), []byte("baz")); err != nil { + log.Fatal(err) + } + if err := tx.Rollback(); err != nil { + log.Fatal(err) + } + + // Ensure that our original value is still set. + if err := db.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + fmt.Printf("The value for 'foo' is still: %s\n", value) + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // The value for 'foo' is still: bar +} + +func ExampleTx_CopyFile() { + // Open the database. + db, err := bolt.Open(tempfile(), 0666, nil) + if err != nil { + log.Fatal(err) + } + defer os.Remove(db.Path()) + + // Create a bucket and a key. + if err := db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucket([]byte("widgets")) + if err != nil { + return err + } + if err := b.Put([]byte("foo"), []byte("bar")); err != nil { + return err + } + return nil + }); err != nil { + log.Fatal(err) + } + + // Copy the database to another file. + toFile := tempfile() + if err := db.View(func(tx *bolt.Tx) error { + return tx.CopyFile(toFile, 0666) + }); err != nil { + log.Fatal(err) + } + defer os.Remove(toFile) + + // Open the cloned database. + db2, err := bolt.Open(toFile, 0666, nil) + if err != nil { + log.Fatal(err) + } + + // Ensure that the key exists in the copy. + if err := db2.View(func(tx *bolt.Tx) error { + value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) + fmt.Printf("The value for 'foo' in the clone is: %s\n", value) + return nil + }); err != nil { + log.Fatal(err) + } + + // Close database to release file lock. + if err := db.Close(); err != nil { + log.Fatal(err) + } + + if err := db2.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // The value for 'foo' in the clone is: bar +} diff --git a/vendor/etcd.io/bbolt/unsafe.go b/vendor/etcd.io/bbolt/unsafe.go new file mode 100644 index 0000000..c0e5037 --- /dev/null +++ b/vendor/etcd.io/bbolt/unsafe.go @@ -0,0 +1,39 @@ +package bbolt + +import ( + "reflect" + "unsafe" +) + +func unsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(base) + offset) +} + +func unsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer { + return unsafe.Pointer(uintptr(base) + offset + uintptr(n)*elemsz) +} + +func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte { + // See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // + // This memory is not allocated from C, but it is unmanaged by Go's + // garbage collector and should behave similarly, and the compiler + // should produce similar code. Note that this conversion allows a + // subslice to begin after the base address, with an optional offset, + // while the URL above does not cover this case and only slices from + // index 0. However, the wiki never says that the address must be to + // the beginning of a C allocation (or even that malloc was used at + // all), so this is believed to be correct. + return (*[maxAllocSize]byte)(unsafeAdd(base, offset))[i:j:j] +} + +// unsafeSlice modifies the data, len, and cap of a slice variable pointed to by +// the slice parameter. This helper should be used over other direct +// manipulation of reflect.SliceHeader to prevent misuse, namely, converting +// from reflect.SliceHeader to a Go slice type. +func unsafeSlice(slice, data unsafe.Pointer, len int) { + s := (*reflect.SliceHeader)(slice) + s.Data = uintptr(data) + s.Cap = len + s.Len = len +} diff --git a/vendor/github.com/alecthomas/.directory b/vendor/github.com/alecthomas/.directory deleted file mode 100644 index 36a5291..0000000 --- a/vendor/github.com/alecthomas/.directory +++ /dev/null @@ -1,6 +0,0 @@ -[Dolphin] -Timestamp=2018,1,23,17,47,46 -Version=3 - -[Settings] -HiddenFilesShown=true diff --git a/vendor/github.com/alecthomas/jsonschema/.travis.yml b/vendor/github.com/alecthomas/jsonschema/.travis.yml deleted file mode 100644 index d1c0f8f..0000000 --- a/vendor/github.com/alecthomas/jsonschema/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -sudo: false -language: go -install: go get -t -v ./... -go: - - 1.7 - - 1.8 diff --git a/vendor/github.com/alecthomas/jsonschema/README.md b/vendor/github.com/alecthomas/jsonschema/README.md deleted file mode 100644 index 1254f01..0000000 --- a/vendor/github.com/alecthomas/jsonschema/README.md +++ /dev/null @@ -1,140 +0,0 @@ -# Go JSON Schema Reflection - -[![Build Status](https://travis-ci.org/alecthomas/jsonschema.png)](https://travis-ci.org/alecthomas/jsonschema) -[![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby) -[![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/jsonschema)](https://goreportcard.com/report/github.com/alecthomas/jsonschema) -[![GoDoc](https://godoc.org/github.com/alecthomas/jsonschema?status.svg)](https://godoc.org/github.com/alecthomas/jsonschema) - -This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection. - -It supports arbitrarily complex types, including `interface{}`, maps, slices, etc. -And it also supports json-schema features such as minLength, maxLength, pattern, format and etc. -## Example - -The following Go type: - -```go -type TestUser struct { - ID int `json:"id"` - Name string `json:"name"` - Friends []int `json:"friends,omitempty"` - Tags map[string]interface{} `json:"tags,omitempty"` - BirthDate time.Time `json:"birth_date,omitempty"` -} -``` - -Results in following JSON Schema: - -```go -jsonschema.Reflect(&TestUser{}) -``` - -```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/TestUser", - "definitions": { - "TestUser": { - "type": "object", - "properties": { - "birth_date": { - "type": "string", - "format": "date-time" - }, - "friends": { - "type": "array", - "items": { - "type": "integer" - } - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "tags": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "additionalProperties": false, - "required": ["id", "name"] - } - } -} -``` -## Configurable behaviour - -The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector` -instance is created. - -### ExpandedStruct - -If set to ```true```, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type. - -eg. - -```go -type GrandfatherType struct { - FamilyName string `json:"family_name" jsonschema:"required"` -} - -type SomeBaseType struct { - SomeBaseProperty int `json:"some_base_property"` - // The jsonschema required tag is nonsensical for private and ignored properties. - // Their presence here tests that the fields *will not* be required in the output - // schema, even if they are tagged required. - somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` - SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` - SomeSchemaIgnoredProperty string `jsonschema:"-,required"` - SomeUntaggedBaseProperty bool `jsonschema:"required"` - someUnexportedUntaggedBaseProperty bool - Grandfather GrandfatherType `json:"grand"` -} -``` - -will output: - -```json -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "required": [ - "some_base_property", - "grand", - "SomeUntaggedBaseProperty" - ], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "grand": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/GrandfatherType" - }, - "some_base_property": { - "type": "integer" - } - }, - "type": "object", - "definitions": { - "GrandfatherType": { - "required": [ - "family_name" - ], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} -``` diff --git a/vendor/github.com/alecthomas/jsonschema/fixtures/allow_additional_props.json b/vendor/github.com/alecthomas/jsonschema/fixtures/allow_additional_props.json deleted file mode 100644 index 4d90c77..0000000 --- a/vendor/github.com/alecthomas/jsonschema/fixtures/allow_additional_props.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/TestUser", - "definitions": { - "GrandfatherType": { - "required": [ - "family_name" - ], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": true, - "type": "object" - }, - "TestUser": { - "required": [ - "some_base_property", - "grand", - "SomeUntaggedBaseProperty", - "id", - "name", - "TestFlag", - "age", - "email" - ], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "TestFlag": { - "type": "boolean" - }, - "age":{ - "maximum": 120, - "minimum": 18, - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "type": "integer" - }, - "email": { - "type": "string", - "format": "email" - }, - "birth_date": { - "type": "string", - "format": "date-time" - }, - "feeling": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "friends": { - "items": { - "type": "integer" - }, - "type": "array" - }, - "grand": { - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/GrandfatherType" - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - }, - "network_address": { - "type": "string", - "format": "ipv4" - }, - "photo": { - "type": "string", - "media": { - "binaryEncoding": "base64" - } - }, - "some_base_property": { - "type": "integer" - }, - "tags": { - "patternProperties": { - ".*": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object" - }, - "website": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": true, - "type": "object" - } - } -} \ No newline at end of file diff --git a/vendor/github.com/alecthomas/jsonschema/fixtures/defaults.json b/vendor/github.com/alecthomas/jsonschema/fixtures/defaults.json deleted file mode 100644 index 801b57b..0000000 --- a/vendor/github.com/alecthomas/jsonschema/fixtures/defaults.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/TestUser", - "definitions": { - "GrandfatherType": { - "required": [ - "family_name" - ], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "TestUser": { - "required": [ - "some_base_property", - "grand", - "SomeUntaggedBaseProperty", - "id", - "name", - "TestFlag", - "age", - "email" - ], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "TestFlag": { - "type": "boolean" - }, - "age":{ - "maximum": 120, - "minimum": 18, - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "type": "integer" - }, - "email": { - "type": "string", - "format": "email" - }, - "birth_date": { - "type": "string", - "format": "date-time" - }, - "feeling": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "friends": { - "items": { - "type": "integer" - }, - "type": "array" - }, - "grand": { - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/GrandfatherType" - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - }, - "network_address": { - "type": "string", - "format": "ipv4" - }, - "photo": { - "type": "string", - "media": { - "binaryEncoding": "base64" - } - }, - "some_base_property": { - "type": "integer" - }, - "tags": { - "patternProperties": { - ".*": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object" - }, - "website": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} \ No newline at end of file diff --git a/vendor/github.com/alecthomas/jsonschema/fixtures/defaults_expanded_toplevel.json b/vendor/github.com/alecthomas/jsonschema/fixtures/defaults_expanded_toplevel.json deleted file mode 100644 index 275689d..0000000 --- a/vendor/github.com/alecthomas/jsonschema/fixtures/defaults_expanded_toplevel.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "required": [ - "some_base_property", - "grand", - "SomeUntaggedBaseProperty", - "id", - "name", - "TestFlag", - "age", - "email" - ], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "TestFlag": { - "type": "boolean" - }, - "age":{ - "maximum": 120, - "minimum": 18, - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "type": "integer" - }, - "email": { - "type": "string", - "format": "email" - }, - "birth_date": { - "type": "string", - "format": "date-time" - }, - "feeling": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "friends": { - "items": { - "type": "integer" - }, - "type": "array" - }, - "grand": { - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/GrandfatherType" - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - }, - "network_address": { - "type": "string", - "format": "ipv4" - }, - "photo": { - "type": "string", - "media": { - "binaryEncoding": "base64" - } - }, - "some_base_property": { - "type": "integer" - }, - "tags": { - "patternProperties": { - ".*": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object" - }, - "website": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false, - "type": "object", - "definitions": { - "GrandfatherType": { - "required": [ - "family_name" - ], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} diff --git a/vendor/github.com/alecthomas/jsonschema/fixtures/required_from_jsontags.json b/vendor/github.com/alecthomas/jsonschema/fixtures/required_from_jsontags.json deleted file mode 100644 index 3625a2a..0000000 --- a/vendor/github.com/alecthomas/jsonschema/fixtures/required_from_jsontags.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/TestUser", - "definitions": { - "GrandfatherType": { - "required": [ - "family_name" - ], - "properties": { - "family_name": { - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "TestUser": { - "required": [ - "SomeUntaggedBaseProperty", - "id", - "name", - "photo" - ], - "properties": { - "SomeUntaggedBaseProperty": { - "type": "boolean" - }, - "TestFlag": { - "type": "boolean" - }, - "age":{ - "maximum": 120, - "minimum": 18, - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "type": "integer" - }, - "email": { - "type": "string", - "format": "email" - }, - "birth_date": { - "type": "string", - "format": "date-time" - }, - "feeling": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "friends": { - "items": { - "type": "integer" - }, - "type": "array" - }, - "grand": { - "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", - "$ref": "#\/definitions\/GrandfatherType" - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string", - "maxLength": 20, - "minLength": 1 - }, - "network_address": { - "type": "string", - "format": "ipv4" - }, - "photo": { - "type": "string", - "media": { - "binaryEncoding": "base64" - } - }, - "some_base_property": { - "type": "integer" - }, - "tags": { - "patternProperties": { - ".*": { - "additionalProperties": true, - "type": "object" - } - }, - "type": "object" - }, - "website": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false, - "type": "object" - } - } -} \ No newline at end of file diff --git a/vendor/github.com/alecthomas/jsonschema/reflect.go b/vendor/github.com/alecthomas/jsonschema/reflect.go deleted file mode 100644 index 496587c..0000000 --- a/vendor/github.com/alecthomas/jsonschema/reflect.go +++ /dev/null @@ -1,451 +0,0 @@ -// Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. -// -// If json tags are present on struct fields, they will be used to infer -// property names and if a property is required (omitempty is present). -// -// [1] http://json-schema.org/latest/json-schema-validation.html -package jsonschema - -import ( - "encoding/json" - "net" - "net/url" - "reflect" - "strings" - "time" - "strconv" -) - -// Version is the JSON Schema version. -// If extending JSON Schema with custom values use a custom URI. -// RFC draft-wright-json-schema-00, section 6 -var Version = "http://json-schema.org/draft-04/schema#" - -// Schema is the root schema. -// RFC draft-wright-json-schema-00, section 4.5 -type Schema struct { - *Type - Definitions Definitions `json:"definitions,omitempty"` -} - -// Type represents a JSON Schema object type. -type Type struct { - // RFC draft-wright-json-schema-00 - Version string `json:"$schema,omitempty"` // section 6.1 - Ref string `json:"$ref,omitempty"` // section 7 - // RFC draft-wright-json-schema-validation-00, section 5 - MultipleOf int `json:"multipleOf,omitempty"` // section 5.1 - Maximum int `json:"maximum,omitempty"` // section 5.2 - ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 5.3 - Minimum int `json:"minimum,omitempty"` // section 5.4 - ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 5.5 - MaxLength int `json:"maxLength,omitempty"` // section 5.6 - MinLength int `json:"minLength,omitempty"` // section 5.7 - Pattern string `json:"pattern,omitempty"` // section 5.8 - AdditionalItems *Type `json:"additionalItems,omitempty"` // section 5.9 - Items *Type `json:"items,omitempty"` // section 5.9 - MaxItems int `json:"maxItems,omitempty"` // section 5.10 - MinItems int `json:"minItems,omitempty"` // section 5.11 - UniqueItems bool `json:"uniqueItems,omitempty"` // section 5.12 - MaxProperties int `json:"maxProperties,omitempty"` // section 5.13 - MinProperties int `json:"minProperties,omitempty"` // section 5.14 - Required []string `json:"required,omitempty"` // section 5.15 - Properties map[string]*Type `json:"properties,omitempty"` // section 5.16 - PatternProperties map[string]*Type `json:"patternProperties,omitempty"` // section 5.17 - AdditionalProperties json.RawMessage `json:"additionalProperties,omitempty"` // section 5.18 - Dependencies map[string]*Type `json:"dependencies,omitempty"` // section 5.19 - Enum []interface{} `json:"enum,omitempty"` // section 5.20 - Type string `json:"type,omitempty"` // section 5.21 - AllOf []*Type `json:"allOf,omitempty"` // section 5.22 - AnyOf []*Type `json:"anyOf,omitempty"` // section 5.23 - OneOf []*Type `json:"oneOf,omitempty"` // section 5.24 - Not *Type `json:"not,omitempty"` // section 5.25 - Definitions Definitions `json:"definitions,omitempty"` // section 5.26 - // RFC draft-wright-json-schema-validation-00, section 6, 7 - Title string `json:"title,omitempty"` // section 6.1 - Description string `json:"description,omitempty"` // section 6.1 - Default interface{} `json:"default,omitempty"` // section 6.2 - Format string `json:"format,omitempty"` // section 7 - // RFC draft-wright-json-schema-hyperschema-00, section 4 - Media *Type `json:"media,omitempty"` // section 4.3 - BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3 -} - -// Reflect reflects to Schema from a value using the default Reflector -func Reflect(v interface{}) *Schema { - return ReflectFromType(reflect.TypeOf(v)) -} - -// ReflectFromType generates root schema using the default Reflector -func ReflectFromType(t reflect.Type) *Schema { - r := &Reflector{} - return r.ReflectFromType(t) -} - -// A Reflector reflects values into a Schema. -type Reflector struct { - // AllowAdditionalProperties will cause the Reflector to generate a schema - // with additionalProperties to 'true' for all struct types. This means - // the presence of additional keys in JSON objects will not cause validation - // to fail. Note said additional keys will simply be dropped when the - // validated JSON is unmarshaled. - AllowAdditionalProperties bool - - // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema - // that requires any key tagged with `jsonschema:required`, overriding the - // default of requiring any key *not* tagged with `json:,omitempty`. - RequiredFromJSONSchemaTags bool - - // ExpandedStruct will cause the toplevel definitions of the schema not - // be referenced itself to a definition. - ExpandedStruct bool -} - -// Reflect reflects to Schema from a value. -func (r *Reflector) Reflect(v interface{}) *Schema { - return r.ReflectFromType(reflect.TypeOf(v)) -} - -// ReflectFromType generates root schema -func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { - definitions := Definitions{} - if r.ExpandedStruct { - st := &Type{ - Version: Version, - Type: "object", - Properties: map[string]*Type{}, - AdditionalProperties: []byte("false"), - } - if r.AllowAdditionalProperties { - st.AdditionalProperties = []byte("true") - } - r.reflectStructFields(st, definitions, t) - r.reflectStruct(definitions, t) - delete(definitions, t.Name()) - return &Schema{Type: st, Definitions: definitions} - } - - s := &Schema{ - Type: r.reflectTypeToSchema(definitions, t), - Definitions: definitions, - } - return s -} - -// Definitions hold schema definitions. -// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 -// RFC draft-wright-json-schema-validation-00, section 5.26 -type Definitions map[string]*Type - -// Available Go defined types for JSON Schema Validation. -// RFC draft-wright-json-schema-validation-00, section 7.3 -var ( - timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 - ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 - uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 -) - -// Byte slices will be encoded as base64 -var byteSliceType = reflect.TypeOf([]byte(nil)) - -// Go code generated from protobuf enum types should fulfil this interface. -type protoEnum interface { - EnumDescriptor() ([]byte, []int) -} - -var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() - -func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Type { - // Already added to definitions? - if _, ok := definitions[t.Name()]; ok { - return &Type{Ref: "#/definitions/" + t.Name()} - } - - // jsonpb will marshal protobuf enum options as either strings or integers. - // It will unmarshal either. - if t.Implements(protoEnumType) { - return &Type{OneOf: []*Type{ - {Type: "string"}, - {Type: "integer"}, - }} - } - - // Defined format types for JSON Schema Validation - // RFC draft-wright-json-schema-validation-00, section 7.3 - // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 - switch t { - case ipType: - // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 - return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4 - } - - switch t.Kind() { - case reflect.Struct: - - switch t { - case timeType: // date-time RFC section 7.3.1 - return &Type{Type: "string", Format: "date-time"} - case uriType: // uri RFC section 7.3.6 - return &Type{Type: "string", Format: "uri"} - default: - return r.reflectStruct(definitions, t) - } - - case reflect.Map: - rt := &Type{ - Type: "object", - PatternProperties: map[string]*Type{ - ".*": r.reflectTypeToSchema(definitions, t.Elem()), - }, - } - delete(rt.PatternProperties, "additionalProperties") - return rt - - case reflect.Slice: - switch t { - case byteSliceType: - return &Type{ - Type: "string", - Media: &Type{BinaryEncoding: "base64"}, - } - default: - return &Type{ - Type: "array", - Items: r.reflectTypeToSchema(definitions, t.Elem()), - } - } - - case reflect.Interface: - return &Type{ - Type: "object", - AdditionalProperties: []byte("true"), - } - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return &Type{Type: "integer"} - - case reflect.Float32, reflect.Float64: - return &Type{Type: "number"} - - case reflect.Bool: - return &Type{Type: "boolean"} - - case reflect.String: - return &Type{Type: "string"} - - case reflect.Ptr: - return r.reflectTypeToSchema(definitions, t.Elem()) - } - panic("unsupported type " + t.String()) -} - -// Refects a struct to a JSON Schema type. -func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type { - st := &Type{ - Type: "object", - Properties: map[string]*Type{}, - AdditionalProperties: []byte("false"), - } - if r.AllowAdditionalProperties { - st.AdditionalProperties = []byte("true") - } - definitions[t.Name()] = st - r.reflectStructFields(st, definitions, t) - - - return &Type{ - Version: Version, - Ref: "#/definitions/" + t.Name(), - } -} - -func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t reflect.Type) { - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - // anonymous and exported type should be processed recursively - // current type should inherit properties of anonymous one - if f.Anonymous && f.PkgPath == "" { - r.reflectStructFields(st, definitions, f.Type) - continue - } - - name, required := r.reflectFieldName(f) - if name == "" { - continue - } - property := r.reflectTypeToSchema(definitions, f.Type) - property.structKeywordsFromTags(f) - st.Properties[name] = property - if required { - st.Required = append(st.Required, name) - } - } -} - -func (t *Type) structKeywordsFromTags(f reflect.StructField){ - tags := strings.Split(f.Tag.Get("jsonschema"), ",") - switch t.Type{ - case "string": - t.stringKeywords(tags) - case "number": - t.numbericKeywords(tags) - case "integer": - t.numbericKeywords(tags) - case "array": - t.arrayKeywords(tags) - } -} - -// read struct tags for string type keyworks -func (t *Type) stringKeywords(tags []string) { - for _, tag := range tags{ - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2{ - name, val := nameValue[0], nameValue[1] - switch name{ - case "minLength": - i, _ := strconv.Atoi(val) - t.MinLength = i - case "maxLength": - i, _ := strconv.Atoi(val) - t.MaxLength = i - case "format": - switch val{ - case "date-time", "email", "hostname", "ipv4", "ipv6", "uri": - t.Format = val - break - } - } - } - } -} - -// read struct tags for numberic type keyworks -func (t *Type) numbericKeywords(tags []string) { - for _, tag := range tags{ - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2{ - name, val := nameValue[0], nameValue[1] - switch name{ - case "multipleOf": - i, _ := strconv.Atoi(val) - t.MultipleOf = i - case "minimum": - i, _ := strconv.Atoi(val) - t.Minimum = i - case "maximum": - i, _ := strconv.Atoi(val) - t.Maximum = i - case "exclusiveMaximum": - b, _ := strconv.ParseBool(val) - t.ExclusiveMaximum = b - case "exclusiveMinimum": - b, _ := strconv.ParseBool(val) - t.ExclusiveMinimum = b - } - } - } -} - -// read struct tags for object type keyworks -// func (t *Type) objectKeywords(tags []string) { -// for _, tag := range tags{ -// nameValue := strings.Split(tag, "=") -// name, val := nameValue[0], nameValue[1] -// switch name{ -// case "dependencies": -// t.Dependencies = val -// break; -// case "patternProperties": -// t.PatternProperties = val -// break; -// } -// } -// } - -// read struct tags for array type keyworks -func (t *Type) arrayKeywords(tags []string) { - for _, tag := range tags{ - nameValue := strings.Split(tag, "=") - if len(nameValue) == 2{ - name, val := nameValue[0], nameValue[1] - switch name{ - case "minItems": - i, _ := strconv.Atoi(val) - t.MinItems = i - case "maxItems": - i, _ := strconv.Atoi(val) - t.MaxItems = i - case "uniqueItems": - t.UniqueItems = true - } - } - } -} - -func requiredFromJSONTags(tags []string) bool { - if ignoredByJSONTags(tags) { - return false - } - - for _, tag := range tags[1:] { - if tag == "omitempty" { - return false - } - } - return true -} - -func requiredFromJSONSchemaTags(tags []string) bool { - if ignoredByJSONSchemaTags(tags) { - return false - } - for _, tag := range tags { - if tag == "required" { - return true - } - } - return false -} - -func ignoredByJSONTags(tags []string) bool { - return tags[0] == "-" -} - -func ignoredByJSONSchemaTags(tags []string) bool { - return tags[0] == "-" -} - -func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool) { - if f.PkgPath != "" { // unexported field, ignore it - return "", false - } - - jsonTags := strings.Split(f.Tag.Get("json"), ",") - - if ignoredByJSONTags(jsonTags) { - return "", false - } - - jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",") - if ignoredByJSONSchemaTags(jsonSchemaTags) { - return "", false - } - - name := f.Name - required := requiredFromJSONTags(jsonTags) - - if r.RequiredFromJSONSchemaTags { - required = requiredFromJSONSchemaTags(jsonSchemaTags) - } - - if jsonTags[0] != "" { - name = jsonTags[0] - } - - return name, required -} diff --git a/vendor/github.com/alecthomas/jsonschema/reflect_test.go b/vendor/github.com/alecthomas/jsonschema/reflect_test.go deleted file mode 100644 index 8447f31..0000000 --- a/vendor/github.com/alecthomas/jsonschema/reflect_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package jsonschema - -import ( - "encoding/json" - "io/ioutil" - "net" - "net/url" - "path/filepath" - "reflect" - "strings" - "testing" - "time" -) - -type GrandfatherType struct { - FamilyName string `json:"family_name" jsonschema:"required"` -} - -type SomeBaseType struct { - SomeBaseProperty int `json:"some_base_property"` - // The jsonschema required tag is nonsensical for private and ignored properties. - // Their presence here tests that the fields *will not* be required in the output - // schema, even if they are tagged required. - somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` - SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` - SomeSchemaIgnoredProperty string `jsonschema:"-,required"` - Grandfather GrandfatherType `json:"grand"` - - SomeUntaggedBaseProperty bool `jsonschema:"required"` - someUnexportedUntaggedBaseProperty bool -} - -type nonExported struct { - PublicNonExported int - privateNonExported int -} - -type ProtoEnum int32 - -func (ProtoEnum) EnumDescriptor() ([]byte, []int) { return []byte(nil), []int{0} } - -const ( - Unset ProtoEnum = iota - Great -) - -type TestUser struct { - SomeBaseType - nonExported - - ID int `json:"id" jsonschema:"required"` - Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20"` - Friends []int `json:"friends,omitempty"` - Tags map[string]interface{} `json:"tags,omitempty"` - - TestFlag bool - IgnoredCounter int `json:"-"` - - // Tests for RFC draft-wright-json-schema-validation-00, section 7.3 - BirthDate time.Time `json:"birth_date,omitempty"` - Website url.URL `json:"website,omitempty"` - IPAddress net.IP `json:"network_address,omitempty"` - - // Tests for RFC draft-wright-json-schema-hyperschema-00, section 4 - Photo []byte `json:"photo,omitempty" jsonschema:"required"` - - // Tests for jsonpb enum support - Feeling ProtoEnum `json:"feeling,omitempty"` - Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=true,exclusiveMinimum=true"` - Email string `json:"email" jsonschema:"format=email"` -} - -var schemaGenerationTests = []struct { - reflector *Reflector - fixture string -}{ - {&Reflector{}, "fixtures/defaults.json"}, - {&Reflector{AllowAdditionalProperties: true}, "fixtures/allow_additional_props.json"}, - {&Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/required_from_jsontags.json"}, - {&Reflector{ExpandedStruct: true}, "fixtures/defaults_expanded_toplevel.json"}, -} - -func TestSchemaGeneration(t *testing.T) { - for _, tt := range schemaGenerationTests { - name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") - t.Run(name, func(t *testing.T) { - f, err := ioutil.ReadFile(tt.fixture) - if err != nil { - t.Errorf("ioutil.ReadAll(%s): %s", tt.fixture, err) - return - } - - actualSchema := tt.reflector.Reflect(&TestUser{}) - expectedSchema := &Schema{} - - if err := json.Unmarshal(f, expectedSchema); err != nil { - t.Errorf("json.Unmarshal(%s, %v): %s", tt.fixture, expectedSchema, err) - return - } - - if !reflect.DeepEqual(actualSchema, expectedSchema) { - actualJSON, err := json.MarshalIndent(actualSchema, "", " ") - if err != nil { - t.Errorf("json.MarshalIndent(%v, \"\", \" \"): %v", actualSchema, err) - return - } - t.Errorf("reflector %+v wanted schema %s, got %s", tt.reflector, f, actualJSON) - } - }) - } -} diff --git a/vendor/github.com/cheggaaa/pb/.travis.yml b/vendor/github.com/cheggaaa/pb/.travis.yml deleted file mode 100644 index b0fccc5..0000000 --- a/vendor/github.com/cheggaaa/pb/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -arch: - - amd64 - - ppc64le -go: - - 1.9.x - - 1.12.x -sudo: false -os: - - linux - - osx -before_install: - - go get github.com/mattn/goveralls -script: - - $GOPATH/bin/goveralls -package github.com/cheggaaa/pb/v3 -repotoken QT1y5Iujb8ete6JOiE0ytKFlBDv9vheWc diff --git a/vendor/github.com/cheggaaa/pb/LICENSE b/vendor/github.com/cheggaaa/pb/LICENSE deleted file mode 100644 index 5119703..0000000 --- a/vendor/github.com/cheggaaa/pb/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2012-2015, Sergey Cherepanov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/cheggaaa/pb/README.md b/vendor/github.com/cheggaaa/pb/README.md deleted file mode 100644 index 1b21085..0000000 --- a/vendor/github.com/cheggaaa/pb/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Terminal progress bar for Go -[![Coverage Status](https://coveralls.io/repos/github/cheggaaa/pb/badge.svg)](https://coveralls.io/github/cheggaaa/pb) - -## Installation - -``` -go get github.com/cheggaaa/pb/v3 -``` - -Documentation for v1 bar available [here](README_V1.md) - -## Quick start - -```Go -package main - -import ( - "time" - - "github.com/cheggaaa/pb/v3" -) - -func main() { - count := 100000 - // create and start new bar - bar := pb.StartNew(count) - - // start bar from 'default' template - // bar := pb.Default.Start(count) - - // start bar from 'simple' template - // bar := pb.Simple.Start(count) - - // start bar from 'full' template - // bar := pb.Full.Start(count) - - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.Finish() -} - -``` - -Result will be like this: - -``` -> go run test.go -37158 / 100000 [================>_______________________________] 37.16% 1m11s -``` - -## Settings - -```Go -// create bar -bar := pb.New(count) - -// refresh info every second (default 200ms) -bar.SetRefreshRate(time.Second) - -// force set io.Writer, by default it's os.Stderr -bar.SetWriter(os.Stdout) - -// bar will format numbers as bytes (B, KiB, MiB, etc) -bar.Set(pb.Bytes, true) - -// bar use SI bytes prefix names (B, kB) instead of IEC (B, KiB) -bar.Set(pb.SIBytesPrefix, true) - -// set custom bar template -bar.SetTemplateString(myTemplate) - -// check for error after template set -if err = bar.Err(); err != nil { - return -} - -// start bar -bar.Start() - -``` - -## Progress bar for IO Operations -```go -package main - -import ( - "crypto/rand" - "io" - "io/ioutil" - - "github.com/cheggaaa/pb/v3" -) - -func main() { - - var limit int64 = 1024 * 1024 * 500 - // we will copy 200 Mb from /dev/rand to /dev/null - reader := io.LimitReader(rand.Reader, limit) - writer := ioutil.Discard - - // start new bar - bar := pb.Full.Start64(limit) - // create proxy reader - barReader := bar.NewProxyReader(reader) - // copy from proxy reader - io.Copy(writer, barReader) - // finish bar - bar.Finish() -} - -``` - -## Custom Progress Bar templates - -Rendering based on builtin text/template package. You can use existing pb's elements or create you own. - -All available elements are described in element.go file. - -#### All in one example: -```go -tmpl := `{{ red "With funcs:" }} {{ bar . "<" "-" (cycle . "↖" "↗" "↘" "↙" ) "." ">"}} {{speed . | rndcolor }} {{percent .}} {{string . "my_green_string" | green}} {{string . "my_blue_string" | blue}}` -// start bar based on our template -bar := pb.ProgressBarTemplate(tmpl).Start64(limit) -// set values for string elements -bar.Set("my_green_string", "green"). - Set("my_blue_string", "blue") -``` diff --git a/vendor/github.com/cheggaaa/pb/README_V1.md b/vendor/github.com/cheggaaa/pb/README_V1.md deleted file mode 100644 index f0689ac..0000000 --- a/vendor/github.com/cheggaaa/pb/README_V1.md +++ /dev/null @@ -1,175 +0,0 @@ -# Terminal progress bar for Go - -Simple progress bar for console programs. - -## Installation - -``` -go get github.com/cheggaaa/pb -``` - -## Usage - -```Go -package main - -import ( - "github.com/cheggaaa/pb" - "time" -) - -func main() { - count := 100000 - bar := pb.StartNew(count) - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.FinishPrint("The End!") -} - -``` - -Result will be like this: - -``` -> go run test.go -37158 / 100000 [================>_______________________________] 37.16% 1m11s -``` - -## Customization - -```Go -// create bar -bar := pb.New(count) - -// refresh info every second (default 200ms) -bar.SetRefreshRate(time.Second) - -// show percents (by default already true) -bar.ShowPercent = true - -// show bar (by default already true) -bar.ShowBar = true - -// no counters -bar.ShowCounters = false - -// show "time left" -bar.ShowTimeLeft = true - -// show average speed -bar.ShowSpeed = true - -// sets the width of the progress bar -bar.SetWidth(80) - -// sets the width of the progress bar, but if terminal size smaller will be ignored -bar.SetMaxWidth(80) - -// convert output to readable format (like KB, MB) -bar.SetUnits(pb.U_BYTES) - -// and start -bar.Start() -``` - -## Progress bar for IO Operations - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create proxy reader -reader := bar.NewProxyReader(r) - -// and copy from pb reader -io.Copy(w, reader) - -``` - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create multi writer -writer := io.MultiWriter(w, bar) - -// and copy -io.Copy(writer, r) - -bar.Finish() -``` - -## Custom Progress Bar Look-and-feel - -```go -bar.Format("<.- >") -``` - -## Multiple Progress Bars (experimental and unstable) - -Do not print to terminal while pool is active. - -```go -package main - -import ( - "math/rand" - "sync" - "time" - - "github.com/cheggaaa/pb" -) - -func main() { - // create bars - first := pb.New(200).Prefix("First ") - second := pb.New(200).Prefix("Second ") - third := pb.New(200).Prefix("Third ") - // start pool - pool, err := pb.StartPool(first, second, third) - if err != nil { - panic(err) - } - // update bars - wg := new(sync.WaitGroup) - for _, bar := range []*pb.ProgressBar{first, second, third} { - wg.Add(1) - go func(cb *pb.ProgressBar) { - for n := 0; n < 200; n++ { - cb.Increment() - time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) - } - cb.Finish() - wg.Done() - }(bar) - } - wg.Wait() - // close pool - pool.Stop() -} -``` - -The result will be as follows: - -``` -$ go run example/multiple.go -First 34 / 200 [=========>---------------------------------------------] 17.00% 00m08s -Second 42 / 200 [===========>------------------------------------------] 21.00% 00m06s -Third 36 / 200 [=========>---------------------------------------------] 18.00% 00m08s -``` diff --git a/vendor/github.com/cheggaaa/pb/example_copy_test.go b/vendor/github.com/cheggaaa/pb/example_copy_test.go deleted file mode 100644 index 85662d0..0000000 --- a/vendor/github.com/cheggaaa/pb/example_copy_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package pb_test - -import ( - "fmt" - "io" - "net/http" - "os" - "strconv" - "strings" - "time" - - "github.com/cheggaaa/pb" -) - -func Example_copy() { - // check args - if len(os.Args) < 3 { - printUsage() - return - } - sourceName, destName := os.Args[1], os.Args[2] - - // check source - var source io.Reader - var sourceSize int64 - if strings.HasPrefix(sourceName, "http://") { - // open as url - resp, err := http.Get(sourceName) - if err != nil { - fmt.Printf("Can't get %s: %v\n", sourceName, err) - return - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - fmt.Printf("Server return non-200 status: %v\n", resp.Status) - return - } - i, _ := strconv.Atoi(resp.Header.Get("Content-Length")) - sourceSize = int64(i) - source = resp.Body - } else { - // open as file - s, err := os.Open(sourceName) - if err != nil { - fmt.Printf("Can't open %s: %v\n", sourceName, err) - return - } - defer s.Close() - // get source size - sourceStat, err := s.Stat() - if err != nil { - fmt.Printf("Can't stat %s: %v\n", sourceName, err) - return - } - sourceSize = sourceStat.Size() - source = s - } - - // create dest - dest, err := os.Create(destName) - if err != nil { - fmt.Printf("Can't create %s: %v\n", destName, err) - return - } - defer dest.Close() - - // create bar - bar := pb.New(int(sourceSize)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10) - bar.ShowSpeed = true - bar.Start() - - // create proxy reader - reader := bar.NewProxyReader(source) - - // and copy from reader - io.Copy(dest, reader) - bar.Finish() -} - -func printUsage() { - fmt.Println("copy [source file or url] [dest file]") -} diff --git a/vendor/github.com/cheggaaa/pb/example_multiple_test.go b/vendor/github.com/cheggaaa/pb/example_multiple_test.go deleted file mode 100644 index d3396f4..0000000 --- a/vendor/github.com/cheggaaa/pb/example_multiple_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package pb_test - -import ( - "math/rand" - "sync" - "time" - - "github.com/cheggaaa/pb" -) - -func Example_multiple() { - // create bars - first := pb.New(200).Prefix("First ") - second := pb.New(200).Prefix("Second ") - third := pb.New(200).Prefix("Third ") - // start pool - pool, err := pb.StartPool(first, second, third) - if err != nil { - panic(err) - } - // update bars - wg := new(sync.WaitGroup) - for _, bar := range []*pb.ProgressBar{first, second, third} { - wg.Add(1) - go func(cb *pb.ProgressBar) { - for n := 0; n < 200; n++ { - cb.Increment() - time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) - } - cb.Finish() - wg.Done() - }(bar) - } - wg.Wait() - // close pool - pool.Stop() -} diff --git a/vendor/github.com/cheggaaa/pb/example_test.go b/vendor/github.com/cheggaaa/pb/example_test.go deleted file mode 100644 index fe00f68..0000000 --- a/vendor/github.com/cheggaaa/pb/example_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package pb_test - -import ( - "time" - - "github.com/cheggaaa/pb" -) - -func Example() { - count := 5000 - bar := pb.New(count) - - // show percents (by default already true) - bar.ShowPercent = true - - // show bar (by default already true) - bar.ShowBar = true - - bar.ShowCounters = true - - bar.ShowTimeLeft = true - - // and start - bar.Start() - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.FinishPrint("The End!") -} diff --git a/vendor/github.com/cheggaaa/pb/format.go b/vendor/github.com/cheggaaa/pb/format.go deleted file mode 100644 index 8bb8a7a..0000000 --- a/vendor/github.com/cheggaaa/pb/format.go +++ /dev/null @@ -1,125 +0,0 @@ -package pb - -import ( - "fmt" - "time" -) - -type Units int - -const ( - // U_NO are default units, they represent a simple value and are not formatted at all. - U_NO Units = iota - // U_BYTES units are formatted in a human readable way (B, KiB, MiB, ...) - U_BYTES - // U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...) - U_BYTES_DEC - // U_DURATION units are formatted in a human readable way (3h14m15s) - U_DURATION -) - -const ( - KiB = 1024 - MiB = 1048576 - GiB = 1073741824 - TiB = 1099511627776 - - KB = 1e3 - MB = 1e6 - GB = 1e9 - TB = 1e12 -) - -func Format(i int64) *formatter { - return &formatter{n: i} -} - -type formatter struct { - n int64 - unit Units - width int - perSec bool -} - -func (f *formatter) To(unit Units) *formatter { - f.unit = unit - return f -} - -func (f *formatter) Width(width int) *formatter { - f.width = width - return f -} - -func (f *formatter) PerSec() *formatter { - f.perSec = true - return f -} - -func (f *formatter) String() (out string) { - switch f.unit { - case U_BYTES: - out = formatBytes(f.n) - case U_BYTES_DEC: - out = formatBytesDec(f.n) - case U_DURATION: - out = formatDuration(f.n) - default: - out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) - } - if f.perSec { - out += "/s" - } - return -} - -// Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B -func formatBytes(i int64) (result string) { - switch { - case i >= TiB: - result = fmt.Sprintf("%.02f TiB", float64(i)/TiB) - case i >= GiB: - result = fmt.Sprintf("%.02f GiB", float64(i)/GiB) - case i >= MiB: - result = fmt.Sprintf("%.02f MiB", float64(i)/MiB) - case i >= KiB: - result = fmt.Sprintf("%.02f KiB", float64(i)/KiB) - default: - result = fmt.Sprintf("%d B", i) - } - return -} - -// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B -func formatBytesDec(i int64) (result string) { - switch { - case i >= TB: - result = fmt.Sprintf("%.02f TB", float64(i)/TB) - case i >= GB: - result = fmt.Sprintf("%.02f GB", float64(i)/GB) - case i >= MB: - result = fmt.Sprintf("%.02f MB", float64(i)/MB) - case i >= KB: - result = fmt.Sprintf("%.02f KB", float64(i)/KB) - default: - result = fmt.Sprintf("%d B", i) - } - return -} - -func formatDuration(n int64) (result string) { - d := time.Duration(n) - if d > time.Hour*24 { - result = fmt.Sprintf("%dd", d/24/time.Hour) - d -= (d / time.Hour / 24) * (time.Hour * 24) - } - if d > time.Hour { - result = fmt.Sprintf("%s%dh", result, d/time.Hour) - d -= d / time.Hour * time.Hour - } - m := d / time.Minute - d -= m * time.Minute - s := d / time.Second - result = fmt.Sprintf("%s%02dm%02ds", result, m, s) - return -} diff --git a/vendor/github.com/cheggaaa/pb/format_test.go b/vendor/github.com/cheggaaa/pb/format_test.go deleted file mode 100644 index 645709e..0000000 --- a/vendor/github.com/cheggaaa/pb/format_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package pb - -import ( - "fmt" - "strconv" - "testing" - "time" -) - -func Test_DefaultsToInteger(t *testing.T) { - value := int64(1000) - expected := strconv.Itoa(int(value)) - actual := Format(value).String() - - if actual != expected { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) - } -} - -func Test_CanFormatAsInteger(t *testing.T) { - value := int64(1000) - expected := strconv.Itoa(int(value)) - actual := Format(value).To(U_NO).String() - - if actual != expected { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) - } -} - -func Test_CanFormatAsBytes(t *testing.T) { - inputs := []struct { - v int64 - e string - }{ - {v: 1000, e: "1000 B"}, - {v: 1024, e: "1.00 KiB"}, - {v: 3*MiB + 140*KiB, e: "3.14 MiB"}, - {v: 2 * GiB, e: "2.00 GiB"}, - {v: 2048 * GiB, e: "2.00 TiB"}, - } - - for _, input := range inputs { - actual := Format(input.v).To(U_BYTES).String() - if actual != input.e { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", input.e, actual)) - } - } -} - -func Test_CanFormatAsBytesDec(t *testing.T) { - inputs := []struct { - v int64 - e string - }{ - {v: 999, e: "999 B"}, - {v: 1024, e: "1.02 KB"}, - {v: 3*MB + 140*KB, e: "3.14 MB"}, - {v: 2 * GB, e: "2.00 GB"}, - {v: 2048 * GB, e: "2.05 TB"}, - } - - for _, input := range inputs { - actual := Format(input.v).To(U_BYTES_DEC).String() - if actual != input.e { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", input.e, actual)) - } - } -} - -func Test_CanFormatDuration(t *testing.T) { - value := 10 * time.Minute - expected := "10m00s" - actual := Format(int64(value)).To(U_DURATION).String() - if actual != expected { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) - } -} - -func Test_CanFormatLongDuration(t *testing.T) { - value := 62 * time.Hour + 13 * time.Second - expected := "2d14h00m13s" - actual := Format(int64(value)).To(U_DURATION).String() - if actual != expected { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) - } -} - -func Test_DefaultUnitsWidth(t *testing.T) { - value := 10 - expected := " 10" - actual := Format(int64(value)).Width(7).String() - if actual != expected { - t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) - } -} diff --git a/vendor/github.com/cheggaaa/pb/go.mod b/vendor/github.com/cheggaaa/pb/go.mod deleted file mode 100644 index 058345e..0000000 --- a/vendor/github.com/cheggaaa/pb/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/cheggaaa/pb - -require ( - github.com/fatih/color v1.9.0 - github.com/mattn/go-colorable v0.1.4 - github.com/mattn/go-runewidth v0.0.4 - golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 -) - -go 1.12 diff --git a/vendor/github.com/cheggaaa/pb/go.sum b/vendor/github.com/cheggaaa/pb/go.sum deleted file mode 100644 index 3ecb91d..0000000 --- a/vendor/github.com/cheggaaa/pb/go.sum +++ /dev/null @@ -1,19 +0,0 @@ -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/cheggaaa/pb/pb.go b/vendor/github.com/cheggaaa/pb/pb.go deleted file mode 100644 index 04dbce6..0000000 --- a/vendor/github.com/cheggaaa/pb/pb.go +++ /dev/null @@ -1,506 +0,0 @@ -// Simple console progress bars -package pb - -import ( - "fmt" - "io" - "math" - "strings" - "sync" - "sync/atomic" - "time" - "unicode/utf8" -) - -// Current version -const Version = "1.0.29" - -const ( - // Default refresh rate - 200ms - DEFAULT_REFRESH_RATE = time.Millisecond * 200 - FORMAT = "[=>-]" -) - -// DEPRECATED -// variables for backward compatibility, from now do not work -// use pb.Format and pb.SetRefreshRate -var ( - DefaultRefreshRate = DEFAULT_REFRESH_RATE - BarStart, BarEnd, Empty, Current, CurrentN string -) - -// Create new progress bar object -func New(total int) *ProgressBar { - return New64(int64(total)) -} - -// Create new progress bar object using int64 as total -func New64(total int64) *ProgressBar { - pb := &ProgressBar{ - Total: total, - RefreshRate: DEFAULT_REFRESH_RATE, - ShowPercent: true, - ShowCounters: true, - ShowBar: true, - ShowTimeLeft: true, - ShowElapsedTime: false, - ShowFinalTime: true, - Units: U_NO, - ManualUpdate: false, - finish: make(chan struct{}), - } - return pb.Format(FORMAT) -} - -// Create new object and start -func StartNew(total int) *ProgressBar { - return New(total).Start() -} - -// Callback for custom output -// For example: -// bar.Callback = func(s string) { -// mySuperPrint(s) -// } -// -type Callback func(out string) - -type ProgressBar struct { - current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) - previous int64 - - Total int64 - RefreshRate time.Duration - ShowPercent, ShowCounters bool - ShowSpeed, ShowTimeLeft, ShowBar bool - ShowFinalTime, ShowElapsedTime bool - Output io.Writer - Callback Callback - NotPrint bool - Units Units - Width int - ForceWidth bool - ManualUpdate bool - AutoStat bool - - // Default width for the time box. - UnitsWidth int - TimeBoxWidth int - - finishOnce sync.Once //Guards isFinish - finish chan struct{} - isFinish bool - - startTime time.Time - startValue int64 - - changeTime time.Time - - prefix, postfix string - - mu sync.Mutex - lastPrint string - - BarStart string - BarEnd string - Empty string - Current string - CurrentN string - - AlwaysUpdate bool -} - -// Start print -func (pb *ProgressBar) Start() *ProgressBar { - pb.startTime = time.Now() - pb.startValue = atomic.LoadInt64(&pb.current) - if atomic.LoadInt64(&pb.Total) == 0 { - pb.ShowTimeLeft = false - pb.ShowPercent = false - pb.AutoStat = false - } - if !pb.ManualUpdate { - pb.Update() // Initial printing of the bar before running the bar refresher. - go pb.refresher() - } - return pb -} - -// Increment current value -func (pb *ProgressBar) Increment() int { - return pb.Add(1) -} - -// Get current value -func (pb *ProgressBar) Get() int64 { - c := atomic.LoadInt64(&pb.current) - return c -} - -// Set current value -func (pb *ProgressBar) Set(current int) *ProgressBar { - return pb.Set64(int64(current)) -} - -// Set64 sets the current value as int64 -func (pb *ProgressBar) Set64(current int64) *ProgressBar { - atomic.StoreInt64(&pb.current, current) - return pb -} - -// Add to current value -func (pb *ProgressBar) Add(add int) int { - return int(pb.Add64(int64(add))) -} - -func (pb *ProgressBar) Add64(add int64) int64 { - return atomic.AddInt64(&pb.current, add) -} - -// Set prefix string -func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - pb.prefix = prefix - return pb -} - -// Set postfix string -func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - pb.postfix = postfix - return pb -} - -// Set custom format for bar -// Example: bar.Format("[=>_]") -// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter -func (pb *ProgressBar) Format(format string) *ProgressBar { - var formatEntries []string - if utf8.RuneCountInString(format) == 5 { - formatEntries = strings.Split(format, "") - } else { - formatEntries = strings.Split(format, "\x00") - } - if len(formatEntries) == 5 { - pb.BarStart = formatEntries[0] - pb.BarEnd = formatEntries[4] - pb.Empty = formatEntries[3] - pb.Current = formatEntries[1] - pb.CurrentN = formatEntries[2] - } - return pb -} - -// Set bar refresh rate -func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { - pb.RefreshRate = rate - return pb -} - -// Set units -// bar.SetUnits(U_NO) - by default -// bar.SetUnits(U_BYTES) - for Mb, Kb, etc -func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { - pb.Units = units - return pb -} - -// Set max width, if width is bigger than terminal width, will be ignored -func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = false - return pb -} - -// Set bar width -func (pb *ProgressBar) SetWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = true - return pb -} - -// End print -func (pb *ProgressBar) Finish() { - //Protect multiple calls - pb.finishOnce.Do(func() { - close(pb.finish) - pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current)) - pb.mu.Lock() - defer pb.mu.Unlock() - switch { - case pb.Output != nil: - fmt.Fprintln(pb.Output) - case !pb.NotPrint: - fmt.Println() - } - pb.isFinish = true - }) -} - -// IsFinished return boolean -func (pb *ProgressBar) IsFinished() bool { - pb.mu.Lock() - defer pb.mu.Unlock() - return pb.isFinish -} - -// End print and write string 'str' -func (pb *ProgressBar) FinishPrint(str string) { - pb.Finish() - if pb.Output != nil { - fmt.Fprintln(pb.Output, str) - } else { - fmt.Println(str) - } -} - -// implement io.Writer -func (pb *ProgressBar) Write(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// implement io.Reader -func (pb *ProgressBar) Read(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// Create new proxy reader over bar -// Takes io.Reader or io.ReadCloser -func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { - return &Reader{r, pb} -} - -// Create new proxy writer over bar -// Takes io.Writer or io.WriteCloser -func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer { - return &Writer{r, pb} -} - -func (pb *ProgressBar) write(total, current int64) { - pb.mu.Lock() - defer pb.mu.Unlock() - width := pb.GetWidth() - - var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string - - // percents - if pb.ShowPercent { - var percent float64 - if total > 0 { - percent = float64(current) / (float64(total) / float64(100)) - } else { - percent = float64(current) / float64(100) - } - percentBox = fmt.Sprintf(" %6.02f%%", percent) - } - - // counters - if pb.ShowCounters { - current := Format(current).To(pb.Units).Width(pb.UnitsWidth) - if total > 0 { - totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth) - countersBox = fmt.Sprintf(" %s / %s ", current, totalS) - } else { - countersBox = fmt.Sprintf(" %s / ? ", current) - } - } - - // time left - currentFromStart := current - pb.startValue - fromStart := time.Now().Sub(pb.startTime) - lastChangeTime := pb.changeTime - fromChange := lastChangeTime.Sub(pb.startTime) - - if pb.ShowElapsedTime { - timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second) - } - - select { - case <-pb.finish: - if pb.ShowFinalTime { - var left time.Duration - left = (fromStart / time.Second) * time.Second - timeLeftBox = fmt.Sprintf(" %s", left.String()) - } - default: - if pb.ShowTimeLeft && currentFromStart > 0 { - perEntry := fromChange / time.Duration(currentFromStart) - var left time.Duration - if total > 0 { - left = time.Duration(total-current) * perEntry - left -= time.Since(lastChangeTime) - left = (left / time.Second) * time.Second - } - if left > 0 { - timeLeft := Format(int64(left)).To(U_DURATION).String() - timeLeftBox = fmt.Sprintf(" %s", timeLeft) - } - } - } - - if len(timeLeftBox) < pb.TimeBoxWidth { - timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) - } - - // speed - if pb.ShowSpeed && currentFromStart > 0 { - fromStart := time.Now().Sub(pb.startTime) - speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) - speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() - } - - barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) - // bar - if pb.ShowBar { - size := width - barWidth - if size > 0 { - if total > 0 { - curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size))) - emptySize := size - curSize - barBox = pb.BarStart - if emptySize < 0 { - emptySize = 0 - } - if curSize > size { - curSize = size - } - - cursorLen := escapeAwareRuneCountInString(pb.Current) - if emptySize <= 0 { - barBox += strings.Repeat(pb.Current, curSize/cursorLen) - } else if curSize > 0 { - cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN) - cursorRepetitions := (curSize - cursorEndLen) / cursorLen - barBox += strings.Repeat(pb.Current, cursorRepetitions) - barBox += pb.CurrentN - } - - emptyLen := escapeAwareRuneCountInString(pb.Empty) - barBox += strings.Repeat(pb.Empty, emptySize/emptyLen) - barBox += pb.BarEnd - } else { - pos := size - int(current)%int(size) - barBox = pb.BarStart - if pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, pos-1) - } - barBox += pb.Current - if size-pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, size-pos-1) - } - barBox += pb.BarEnd - } - } - } - - // check len - out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix - - if cl := escapeAwareRuneCountInString(out); cl < width { - end = strings.Repeat(" ", width-cl) - } - - // and print! - pb.lastPrint = out + end - isFinish := pb.isFinish - - switch { - case isFinish: - return - case pb.Output != nil: - fmt.Fprint(pb.Output, "\r"+out+end) - case pb.Callback != nil: - pb.Callback(out + end) - case !pb.NotPrint: - fmt.Print("\r" + out + end) - } -} - -// GetTerminalWidth - returns terminal width for all platforms. -func GetTerminalWidth() (int, error) { - return terminalWidth() -} - -func (pb *ProgressBar) GetWidth() int { - if pb.ForceWidth { - return pb.Width - } - - width := pb.Width - termWidth, _ := terminalWidth() - if width == 0 || termWidth <= width { - width = termWidth - } - - return width -} - -// Write the current state of the progressbar -func (pb *ProgressBar) Update() { - c := atomic.LoadInt64(&pb.current) - p := atomic.LoadInt64(&pb.previous) - t := atomic.LoadInt64(&pb.Total) - if p != c { - pb.mu.Lock() - pb.changeTime = time.Now() - pb.mu.Unlock() - atomic.StoreInt64(&pb.previous, c) - } - pb.write(t, c) - if pb.AutoStat { - if c == 0 { - pb.startTime = time.Now() - pb.startValue = 0 - } else if c >= t && pb.isFinish != true { - pb.Finish() - } - } -} - -// String return the last bar print -func (pb *ProgressBar) String() string { - pb.mu.Lock() - defer pb.mu.Unlock() - return pb.lastPrint -} - -// SetTotal atomically sets new total count -func (pb *ProgressBar) SetTotal(total int) *ProgressBar { - return pb.SetTotal64(int64(total)) -} - -// SetTotal64 atomically sets new total count -func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar { - atomic.StoreInt64(&pb.Total, total) - return pb -} - -// Reset bar and set new total count -// Does effect only on finished bar -func (pb *ProgressBar) Reset(total int) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - if pb.isFinish { - pb.SetTotal(total).Set(0) - atomic.StoreInt64(&pb.previous, 0) - } - return pb -} - -// Internal loop for refreshing the progressbar -func (pb *ProgressBar) refresher() { - for { - select { - case <-pb.finish: - return - case <-time.After(pb.RefreshRate): - pb.Update() - } - } -} diff --git a/vendor/github.com/cheggaaa/pb/pb_appengine.go b/vendor/github.com/cheggaaa/pb/pb_appengine.go deleted file mode 100644 index 17168f3..0000000 --- a/vendor/github.com/cheggaaa/pb/pb_appengine.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build appengine js - -package pb - -import "errors" - -// terminalWidth returns width of the terminal, which is not supported -// and should always failed on appengine classic which is a sandboxed PaaS. -func terminalWidth() (int, error) { - return 0, errors.New("Not supported") -} diff --git a/vendor/github.com/cheggaaa/pb/pb_plan9.go b/vendor/github.com/cheggaaa/pb/pb_plan9.go deleted file mode 100644 index 32e3b98..0000000 --- a/vendor/github.com/cheggaaa/pb/pb_plan9.go +++ /dev/null @@ -1,70 +0,0 @@ -package pb - -import ( - "errors" - "os" - "os/signal" - "sync" - "syscall" -) - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var ( - echoLockMutex sync.Mutex - consctl *os.File -) - -// terminalWidth returns width of the terminal. -func terminalWidth() (int, error) { - return 0, errors.New("Not Supported") -} - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - if consctl != nil { - return nil, ErrPoolWasStarted - } - consctl, err = os.OpenFile("/dev/consctl", os.O_WRONLY, 0) - if err != nil { - return nil, err - } - _, err = consctl.WriteString("rawon") - if err != nil { - consctl.Close() - consctl = nil - return nil, err - } - shutdownCh = make(chan struct{}) - go catchTerminate(shutdownCh) - return -} - -func unlockEcho() error { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - if consctl == nil { - return nil - } - if err := consctl.Close(); err != nil { - return err - } - consctl = nil - return nil -} - -// listen exit signals and restore terminal state -func catchTerminate(shutdownCh chan struct{}) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGKILL) - defer signal.Stop(sig) - select { - case <-shutdownCh: - unlockEcho() - case <-sig: - unlockEcho() - } -} diff --git a/vendor/github.com/cheggaaa/pb/pb_test.go b/vendor/github.com/cheggaaa/pb/pb_test.go deleted file mode 100644 index 86d4ce6..0000000 --- a/vendor/github.com/cheggaaa/pb/pb_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package pb - -import ( - "bytes" - "strings" - "sync" - "testing" - "time" - - "github.com/fatih/color" - "github.com/mattn/go-colorable" -) - -func Test_IncrementAddsOne(t *testing.T) { - count := 5000 - bar := New(count) - expected := 1 - actual := bar.Increment() - - if actual != expected { - t.Errorf("Expected {%d} was {%d}", expected, actual) - } -} - -func Test_Width(t *testing.T) { - count := 5000 - bar := New(count) - width := 100 - bar.SetWidth(100).Callback = func(out string) { - if len(out) != width { - t.Errorf("Bar width expected {%d} was {%d}", len(out), width) - } - } - bar.Start() - bar.Increment() - bar.Finish() -} - -func Test_MultipleFinish(t *testing.T) { - bar := New(5000) - bar.Add(2000) - bar.Finish() - bar.Finish() -} - -func TestWriteRace(t *testing.T) { - outBuffer := &bytes.Buffer{} - totalCount := 20 - bar := New(totalCount) - bar.Output = outBuffer - bar.Start() - var wg sync.WaitGroup - for i := 0; i < totalCount; i++ { - wg.Add(1) - go func() { - bar.Increment() - time.Sleep(250 * time.Millisecond) - wg.Done() - }() - } - wg.Wait() - bar.Finish() -} - -func Test_Format(t *testing.T) { - bar := New(5000).Format(strings.Join([]string{ - color.GreenString("["), - color.New(color.BgGreen).SprintFunc()("o"), - color.New(color.BgHiGreen).SprintFunc()("o"), - color.New(color.BgRed).SprintFunc()("o"), - color.GreenString("]"), - }, "\x00")) - w := colorable.NewColorableStdout() - bar.Callback = func(out string) { - w.Write([]byte(out)) - } - bar.Add(2000) - bar.Finish() - bar.Finish() -} - -func Test_MultiCharacter(t *testing.T) { - bar := New(5).Format(strings.Join([]string{"[[[", "---", ">>", "....", "]]"}, "\x00")) - bar.Start() - for i := 0; i < 5; i++ { - time.Sleep(500 * time.Millisecond) - bar.Increment() - } - - time.Sleep(500 * time.Millisecond) - bar.Finish() -} - -func Test_AutoStat(t *testing.T) { - bar := New(5) - bar.AutoStat = true - bar.Start() - time.Sleep(2 * time.Second) - //real start work - for i := 0; i < 5; i++ { - time.Sleep(500 * time.Millisecond) - bar.Increment() - } - //real finish work - time.Sleep(2 * time.Second) - bar.Finish() -} - -func Test_Finish_PrintNewline(t *testing.T) { - bar := New(5) - buf := &bytes.Buffer{} - bar.Output = buf - bar.Finish() - - expected := "\n" - actual := buf.String() - //Finish should write newline to bar.Output - if !strings.HasSuffix(actual, expected) { - t.Errorf("Expected %q to have suffix %q", expected, actual) - } -} - -func Test_FinishPrint(t *testing.T) { - bar := New(5) - buf := &bytes.Buffer{} - bar.Output = buf - bar.FinishPrint("foo") - - expected := "foo\n" - actual := buf.String() - //FinishPrint should write to bar.Output - if !strings.HasSuffix(actual, expected) { - t.Errorf("Expected %q to have suffix %q", expected, actual) - } -} - -func Test_Reset(t *testing.T) { - bar := StartNew(5) - for i := 0; i < 5; i++ { - bar.Increment() - } - if actual := bar.Get(); actual != 5 { - t.Errorf("Expected: %d; actual: %d", 5, actual) - } - bar.Finish() - bar.Reset(10).Start() - defer bar.Finish() - if actual := bar.Get(); actual != 0 { - t.Errorf("Expected: %d; actual: %d", 0, actual) - } - if actual := bar.Total; actual != 10 { - t.Errorf("Expected: %d; actual: %d", 10, actual) - } -} diff --git a/vendor/github.com/cheggaaa/pb/pb_win.go b/vendor/github.com/cheggaaa/pb/pb_win.go deleted file mode 100644 index 9595e82..0000000 --- a/vendor/github.com/cheggaaa/pb/pb_win.go +++ /dev/null @@ -1,143 +0,0 @@ -// +build windows - -package pb - -import ( - "errors" - "fmt" - "os" - "sync" - "syscall" - "unsafe" -) - -var tty = os.Stdin - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - - // GetConsoleScreenBufferInfo retrieves information about the - // specified console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - - // GetConsoleMode retrieves the current input mode of a console's - // input buffer or the current output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx - getConsoleMode = kernel32.NewProc("GetConsoleMode") - - // SetConsoleMode sets the input mode of a console's input buffer - // or the output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - setConsoleMode = kernel32.NewProc("SetConsoleMode") - - // SetConsoleCursorPosition sets the cursor position in the - // specified console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx - setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") -) - -type ( - // Defines the coordinates of the upper left and lower right corners - // of a rectangle. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx - smallRect struct { - Left, Top, Right, Bottom int16 - } - - // Defines the coordinates of a character cell in a console screen - // buffer. The origin of the coordinate system (0,0) is at the top, left cell - // of the buffer. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx - coordinates struct { - X, Y int16 - } - - word int16 - - // Contains information about a console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx - consoleScreenBufferInfo struct { - dwSize coordinates - dwCursorPosition coordinates - wAttributes word - srWindow smallRect - dwMaximumWindowSize coordinates - } -) - -// terminalWidth returns width of the terminal. -func terminalWidth() (width int, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return 0, error(e) - } - return int(info.dwSize.X) - 1, nil -} - -func getCursorPos() (pos coordinates, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return info.dwCursorPosition, error(e) - } - return info.dwCursorPosition, nil -} - -func setCursorPos(pos coordinates) error { - _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0) - if e != 0 { - return error(e) - } - return nil -} - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var echoLocked bool -var echoLockMutex sync.Mutex - -var oldState word - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if echoLocked { - err = ErrPoolWasStarted - return - } - echoLocked = true - - if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - const ENABLE_ECHO_INPUT = 0x0004 - const ENABLE_LINE_INPUT = 0x0002 - newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - - shutdownCh = make(chan struct{}) - return -} - -func unlockEcho() (err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if !echoLocked { - return - } - echoLocked = false - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/pb_x.go b/vendor/github.com/cheggaaa/pb/pb_x.go deleted file mode 100644 index af42517..0000000 --- a/vendor/github.com/cheggaaa/pb/pb_x.go +++ /dev/null @@ -1,118 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly -// +build !appengine !js - -package pb - -import ( - "errors" - "fmt" - "os" - "os/signal" - "sync" - "syscall" - - "golang.org/x/sys/unix" -) - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var ( - echoLockMutex sync.Mutex - origTermStatePtr *unix.Termios - tty *os.File - istty bool -) - -func init() { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - var err error - tty, err = os.Open("/dev/tty") - istty = true - if err != nil { - tty = os.Stdin - istty = false - } -} - -// terminalWidth returns width of the terminal. -func terminalWidth() (int, error) { - if !istty { - return 0, errors.New("Not Supported") - } - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - - fd := int(tty.Fd()) - - ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) - if err != nil { - return 0, err - } - - return int(ws.Col), nil -} - -func lockEcho() (shutdownCh chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if istty { - if origTermStatePtr != nil { - return shutdownCh, ErrPoolWasStarted - } - - fd := int(tty.Fd()) - - origTermStatePtr, err = unix.IoctlGetTermios(fd, ioctlReadTermios) - if err != nil { - return nil, fmt.Errorf("Can't get terminal settings: %v", err) - } - - oldTermios := *origTermStatePtr - newTermios := oldTermios - newTermios.Lflag &^= syscall.ECHO - newTermios.Lflag |= syscall.ICANON | syscall.ISIG - newTermios.Iflag |= syscall.ICRNL - if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil { - return nil, fmt.Errorf("Can't set terminal settings: %v", err) - } - - } - shutdownCh = make(chan struct{}) - go catchTerminate(shutdownCh) - return -} - -func unlockEcho() error { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if istty { - if origTermStatePtr == nil { - return nil - } - - fd := int(tty.Fd()) - - if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil { - return fmt.Errorf("Can't set terminal settings: %v", err) - } - - } - origTermStatePtr = nil - - return nil -} - -// listen exit signals and restore terminal state -func catchTerminate(shutdownCh chan struct{}) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL) - defer signal.Stop(sig) - select { - case <-shutdownCh: - unlockEcho() - case <-sig: - unlockEcho() - } -} diff --git a/vendor/github.com/cheggaaa/pb/pool.go b/vendor/github.com/cheggaaa/pb/pool.go deleted file mode 100644 index 861d787..0000000 --- a/vendor/github.com/cheggaaa/pb/pool.go +++ /dev/null @@ -1,104 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows plan9 - -package pb - -import ( - "io" - "sync" - "time" -) - -// Create and start new pool with given bars -// You need call pool.Stop() after work -func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) { - pool = new(Pool) - if err = pool.Start(); err != nil { - return - } - pool.Add(pbs...) - return -} - -// NewPool initialises a pool with progress bars, but -// doesn't start it. You need to call Start manually -func NewPool(pbs ...*ProgressBar) (pool *Pool) { - pool = new(Pool) - pool.Add(pbs...) - return -} - -type Pool struct { - Output io.Writer - RefreshRate time.Duration - bars []*ProgressBar - lastBarsCount int - shutdownCh chan struct{} - workerCh chan struct{} - m sync.Mutex - finishOnce sync.Once -} - -// Add progress bars. -func (p *Pool) Add(pbs ...*ProgressBar) { - p.m.Lock() - defer p.m.Unlock() - for _, bar := range pbs { - bar.ManualUpdate = true - bar.NotPrint = true - bar.Start() - p.bars = append(p.bars, bar) - } -} - -func (p *Pool) Start() (err error) { - p.RefreshRate = DefaultRefreshRate - p.shutdownCh, err = lockEcho() - if err != nil { - return - } - p.workerCh = make(chan struct{}) - go p.writer() - return -} - -func (p *Pool) writer() { - var first = true - defer func() { - if first == false { - p.print(false) - } else { - p.print(true) - p.print(false) - } - close(p.workerCh) - }() - - for { - select { - case <-time.After(p.RefreshRate): - if p.print(first) { - p.print(false) - return - } - first = false - case <-p.shutdownCh: - return - } - } -} - -// Restore terminal state and close pool -func (p *Pool) Stop() error { - p.finishOnce.Do(func() { - if p.shutdownCh != nil { - close(p.shutdownCh) - } - }) - - // Wait for the worker to complete - select { - case <-p.workerCh: - } - - return unlockEcho() -} diff --git a/vendor/github.com/cheggaaa/pb/pool_win.go b/vendor/github.com/cheggaaa/pb/pool_win.go deleted file mode 100644 index 63598d3..0000000 --- a/vendor/github.com/cheggaaa/pb/pool_win.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build windows - -package pb - -import ( - "fmt" - "log" -) - -func (p *Pool) print(first bool) bool { - p.m.Lock() - defer p.m.Unlock() - var out string - if !first { - coords, err := getCursorPos() - if err != nil { - log.Panic(err) - } - coords.Y -= int16(p.lastBarsCount) - if coords.Y < 0 { - coords.Y = 0 - } - coords.X = 0 - - err = setCursorPos(coords) - if err != nil { - log.Panic(err) - } - } - isFinished := true - for _, bar := range p.bars { - if !bar.IsFinished() { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - if p.Output != nil { - fmt.Fprint(p.Output, out) - } else { - fmt.Print(out) - } - p.lastBarsCount = len(p.bars) - return isFinished -} diff --git a/vendor/github.com/cheggaaa/pb/pool_x.go b/vendor/github.com/cheggaaa/pb/pool_x.go deleted file mode 100644 index 3d4ed94..0000000 --- a/vendor/github.com/cheggaaa/pb/pool_x.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly plan9 - -package pb - -import "fmt" - -func (p *Pool) print(first bool) bool { - p.m.Lock() - defer p.m.Unlock() - var out string - if !first { - out = fmt.Sprintf("\033[%dA", p.lastBarsCount) - } - isFinished := true - for _, bar := range p.bars { - if !bar.IsFinished() { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - if p.Output != nil { - fmt.Fprint(p.Output, out) - } else { - fmt.Print(out) - } - p.lastBarsCount = len(p.bars) - return isFinished -} diff --git a/vendor/github.com/cheggaaa/pb/reader.go b/vendor/github.com/cheggaaa/pb/reader.go deleted file mode 100644 index 9562e94..0000000 --- a/vendor/github.com/cheggaaa/pb/reader.go +++ /dev/null @@ -1,26 +0,0 @@ -package pb - -import ( - "io" -) - -// It's proxy reader, implement io.Reader -type Reader struct { - io.Reader - bar *ProgressBar -} - -func (r *Reader) Read(p []byte) (n int, err error) { - n, err = r.Reader.Read(p) - r.bar.Add(n) - return -} - -// Close the reader when it implements io.Closer -func (r *Reader) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/runecount.go b/vendor/github.com/cheggaaa/pb/runecount.go deleted file mode 100644 index c617c55..0000000 --- a/vendor/github.com/cheggaaa/pb/runecount.go +++ /dev/null @@ -1,17 +0,0 @@ -package pb - -import ( - "github.com/mattn/go-runewidth" - "regexp" -) - -// Finds the control character sequences (like colors) -var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d") - -func escapeAwareRuneCountInString(s string) int { - n := runewidth.StringWidth(s) - for _, sm := range ctrlFinder.FindAllString(s, -1) { - n -= runewidth.StringWidth(sm) - } - return n -} diff --git a/vendor/github.com/cheggaaa/pb/runecount_test.go b/vendor/github.com/cheggaaa/pb/runecount_test.go deleted file mode 100644 index d23f511..0000000 --- a/vendor/github.com/cheggaaa/pb/runecount_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package pb - -import "testing" - -func Test_RuneCount(t *testing.T) { - s := string([]byte{ - 27, 91, 51, 49, 109, // {Red} - 72, 101, 108, 108, 111, // Hello - 44, 32, // , - 112, 108, 97, 121, 103, 114, 111, 117, 110, 100, // Playground - 27, 91, 48, 109, // {Reset} - }) - if e, l := 17, escapeAwareRuneCountInString(s); l != e { - t.Errorf("Invalid length %d, expected %d", l, e) - } - s = "進捗 " - if e, l := 5, escapeAwareRuneCountInString(s); l != e { - t.Errorf("Invalid length %d, expected %d", l, e) - } -} diff --git a/vendor/github.com/cheggaaa/pb/termios_bsd.go b/vendor/github.com/cheggaaa/pb/termios_bsd.go deleted file mode 100644 index 517ea8e..0000000 --- a/vendor/github.com/cheggaaa/pb/termios_bsd.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package pb - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/cheggaaa/pb/termios_sysv.go b/vendor/github.com/cheggaaa/pb/termios_sysv.go deleted file mode 100644 index b10f618..0000000 --- a/vendor/github.com/cheggaaa/pb/termios_sysv.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux solaris -// +build !appengine - -package pb - -import "golang.org/x/sys/unix" - -const ioctlReadTermios = unix.TCGETS -const ioctlWriteTermios = unix.TCSETS diff --git a/vendor/github.com/cheggaaa/pb/v3/LICENSE b/vendor/github.com/cheggaaa/pb/v3/LICENSE deleted file mode 100644 index 5119703..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2012-2015, Sergey Cherepanov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/cheggaaa/pb/v3/element.go b/vendor/github.com/cheggaaa/pb/v3/element.go deleted file mode 100644 index 965183f..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/element.go +++ /dev/null @@ -1,290 +0,0 @@ -package pb - -import ( - "bytes" - "fmt" - "math" - "sync" - "time" -) - -const ( - adElPlaceholder = "%_ad_el_%" - adElPlaceholderLen = len(adElPlaceholder) -) - -var ( - defaultBarEls = [5]string{"[", "-", ">", "_", "]"} -) - -// Element is an interface for bar elements -type Element interface { - ProgressElement(state *State, args ...string) string -} - -// ElementFunc type implements Element interface and created for simplify elements -type ElementFunc func(state *State, args ...string) string - -// ProgressElement just call self func -func (e ElementFunc) ProgressElement(state *State, args ...string) string { - return e(state, args...) -} - -var elementsM sync.Mutex - -var elements = map[string]Element{ - "percent": ElementPercent, - "counters": ElementCounters, - "bar": adaptiveWrap(ElementBar), - "speed": ElementSpeed, - "rtime": ElementRemainingTime, - "etime": ElementElapsedTime, - "string": ElementString, - "cycle": ElementCycle, -} - -// RegisterElement give you a chance to use custom elements -func RegisterElement(name string, el Element, adaptive bool) { - if adaptive { - el = adaptiveWrap(el) - } - elementsM.Lock() - elements[name] = el - elementsM.Unlock() -} - -type argsHelper []string - -func (args argsHelper) getOr(n int, value string) string { - if len(args) > n { - return args[n] - } - return value -} - -func (args argsHelper) getNotEmptyOr(n int, value string) (v string) { - if v = args.getOr(n, value); v == "" { - return value - } - return -} - -func adaptiveWrap(el Element) Element { - return ElementFunc(func(state *State, args ...string) string { - state.recalc = append(state.recalc, ElementFunc(func(s *State, _ ...string) (result string) { - s.adaptive = true - result = el.ProgressElement(s, args...) - s.adaptive = false - return - })) - return adElPlaceholder - }) -} - -// ElementPercent shows current percent of progress. -// Optionally can take one or two string arguments. -// First string will be used as value for format float64, default is "%.02f%%". -// Second string will be used when percent can't be calculated, default is "?%" -// In template use as follows: {{percent .}} or {{percent . "%.03f%%"}} or {{percent . "%.03f%%" "?"}} -var ElementPercent ElementFunc = func(state *State, args ...string) string { - argsh := argsHelper(args) - if state.Total() > 0 { - return fmt.Sprintf( - argsh.getNotEmptyOr(0, "%.02f%%"), - float64(state.Value())/(float64(state.Total())/float64(100)), - ) - } - return argsh.getOr(1, "?%") -} - -// ElementCounters shows current and total values. -// Optionally can take one or two string arguments. -// First string will be used as format value when Total is present (>0). Default is "%s / %s" -// Second string will be used when total <= 0. Default is "%[1]s" -// In template use as follows: {{counters .}} or {{counters . "%s/%s"}} or {{counters . "%s/%s" "%s/?"}} -var ElementCounters ElementFunc = func(state *State, args ...string) string { - var f string - if state.Total() > 0 { - f = argsHelper(args).getNotEmptyOr(0, "%s / %s") - } else { - f = argsHelper(args).getNotEmptyOr(1, "%[1]s") - } - return fmt.Sprintf(f, state.Format(state.Value()), state.Format(state.Total())) -} - -type elementKey int - -const ( - barObj elementKey = iota - speedObj - cycleObj -) - -type bar struct { - eb [5][]byte // elements in bytes - cc [5]int // cell counts - buf *bytes.Buffer -} - -func (p *bar) write(state *State, eln, width int) int { - repeat := width / p.cc[eln] - for i := 0; i < repeat; i++ { - p.buf.Write(p.eb[eln]) - } - StripStringToBuffer(string(p.eb[eln]), width%p.cc[eln], p.buf) - return width -} - -func getProgressObj(state *State, args ...string) (p *bar) { - var ok bool - if p, ok = state.Get(barObj).(*bar); !ok { - p = &bar{ - buf: bytes.NewBuffer(nil), - } - state.Set(barObj, p) - } - argsH := argsHelper(args) - for i := range p.eb { - arg := argsH.getNotEmptyOr(i, defaultBarEls[i]) - if string(p.eb[i]) != arg { - p.cc[i] = CellCount(arg) - p.eb[i] = []byte(arg) - if p.cc[i] == 0 { - p.cc[i] = 1 - p.eb[i] = []byte(" ") - } - } - } - return -} - -// ElementBar make progress bar view [-->__] -// Optionally can take up to 5 string arguments. Defaults is "[", "-", ">", "_", "]" -// In template use as follows: {{bar . }} or {{bar . "<" "oOo" "|" "~" ">"}} -// Color args: {{bar . (red "[") (green "-") ... -var ElementBar ElementFunc = func(state *State, args ...string) string { - // init - var p = getProgressObj(state, args...) - - total, value := state.Total(), state.Value() - if total < 0 { - total = -total - } - if value < 0 { - value = -value - } - - // check for overflow - if total != 0 && value > total { - total = value - } - - p.buf.Reset() - - var widthLeft = state.AdaptiveElWidth() - if widthLeft <= 0 || !state.IsAdaptiveWidth() { - widthLeft = 30 - } - - // write left border - if p.cc[0] < widthLeft { - widthLeft -= p.write(state, 0, p.cc[0]) - } else { - p.write(state, 0, widthLeft) - return p.buf.String() - } - - // check right border size - if p.cc[4] < widthLeft { - // write later - widthLeft -= p.cc[4] - } else { - p.write(state, 4, widthLeft) - return p.buf.String() - } - - var curCount int - - if total > 0 { - // calculate count of currenct space - curCount = int(math.Ceil((float64(value) / float64(total)) * float64(widthLeft))) - } - - // write bar - if total == value && state.IsFinished() { - widthLeft -= p.write(state, 1, curCount) - } else if toWrite := curCount - p.cc[2]; toWrite > 0 { - widthLeft -= p.write(state, 1, toWrite) - widthLeft -= p.write(state, 2, p.cc[2]) - } else if curCount > 0 { - widthLeft -= p.write(state, 2, curCount) - } - if widthLeft > 0 { - widthLeft -= p.write(state, 3, widthLeft) - } - // write right border - p.write(state, 4, p.cc[4]) - // cut result and return string - return p.buf.String() -} - -// ElementRemainingTime calculates remaining time based on speed (EWMA) -// Optionally can take one or two string arguments. -// First string will be used as value for format time duration string, default is "%s". -// Second string will be used when bar finished and value indicates elapsed time, default is "%s" -// Third string will be used when value not available, default is "?" -// In template use as follows: {{rtime .}} or {{rtime . "%s remain"}} or {{rtime . "%s remain" "%s total" "???"}} -var ElementRemainingTime ElementFunc = func(state *State, args ...string) string { - var rts string - sp := getSpeedObj(state).value(state) - if !state.IsFinished() { - if sp > 0 { - remain := float64(state.Total() - state.Value()) - remainDur := time.Duration(remain/sp) * time.Second - rts = remainDur.String() - } else { - return argsHelper(args).getOr(2, "?") - } - } else { - rts = state.Time().Truncate(time.Second).Sub(state.StartTime().Truncate(time.Second)).String() - return fmt.Sprintf(argsHelper(args).getOr(1, "%s"), rts) - } - return fmt.Sprintf(argsHelper(args).getOr(0, "%s"), rts) -} - -// ElementElapsedTime shows elapsed time -// Optionally cat take one argument - it's format for time string. -// In template use as follows: {{etime .}} or {{etime . "%s elapsed"}} -var ElementElapsedTime ElementFunc = func(state *State, args ...string) string { - etm := state.Time().Truncate(time.Second).Sub(state.StartTime().Truncate(time.Second)) - return fmt.Sprintf(argsHelper(args).getOr(0, "%s"), etm.String()) -} - -// ElementString get value from bar by given key and print them -// bar.Set("myKey", "string to print") -// In template use as follows: {{string . "myKey"}} -var ElementString ElementFunc = func(state *State, args ...string) string { - if len(args) == 0 { - return "" - } - v := state.Get(args[0]) - if v == nil { - return "" - } - return fmt.Sprint(v) -} - -// ElementCycle return next argument for every call -// In template use as follows: {{cycle . "1" "2" "3"}} -// Or mix width other elements: {{ bar . "" "" (cycle . "↖" "↗" "↘" "↙" )}} -var ElementCycle ElementFunc = func(state *State, args ...string) string { - if len(args) == 0 { - return "" - } - n, _ := state.Get(cycleObj).(int) - if n >= len(args) { - n = 0 - } - state.Set(cycleObj, n+1) - return args[n] -} diff --git a/vendor/github.com/cheggaaa/pb/v3/element_test.go b/vendor/github.com/cheggaaa/pb/v3/element_test.go deleted file mode 100644 index d5c5cfd..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/element_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package pb - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/fatih/color" -) - -func testState(total, value int64, maxWidth int, bools ...bool) (s *State) { - s = &State{ - total: total, - current: value, - adaptiveElWidth: maxWidth, - ProgressBar: new(ProgressBar), - } - if len(bools) > 0 { - s.Set(Bytes, bools[0]) - } - if len(bools) > 1 && bools[1] { - s.adaptive = true - } - return -} - -func testElementBarString(t *testing.T, state *State, el Element, want string, args ...string) { - if state.ProgressBar == nil { - state.ProgressBar = new(ProgressBar) - } - res := el.ProgressElement(state, args...) - if res != want { - t.Errorf("Unexpected result: '%s'; want: '%s'", res, want) - } - if state.IsAdaptiveWidth() && state.AdaptiveElWidth() != CellCount(res) { - t.Errorf("Unepected width: %d; want: %d", CellCount(res), state.AdaptiveElWidth()) - } -} - -func TestElementPercent(t *testing.T) { - testElementBarString(t, testState(100, 50, 0), ElementPercent, "50.00%") - testElementBarString(t, testState(100, 50, 0), ElementPercent, "50 percent", "%v percent") - testElementBarString(t, testState(0, 50, 0), ElementPercent, "?%") - testElementBarString(t, testState(0, 50, 0), ElementPercent, "unkn", "%v%%", "unkn") -} - -func TestElementCounters(t *testing.T) { - testElementBarString(t, testState(100, 50, 0), ElementCounters, "50 / 100") - testElementBarString(t, testState(100, 50, 0), ElementCounters, "50 of 100", "%s of %s") - testElementBarString(t, testState(100, 50, 0, true), ElementCounters, "50 B of 100 B", "%s of %s") - testElementBarString(t, testState(100, 50, 0, true), ElementCounters, "50 B / 100 B") - testElementBarString(t, testState(0, 50, 0, true), ElementCounters, "50 B") - testElementBarString(t, testState(0, 50, 0, true), ElementCounters, "50 B / ?", "", "%[1]s / ?") -} - -func TestElementBar(t *testing.T) { - // short - testElementBarString(t, testState(100, 50, 1, false, true), ElementBar, "[") - testElementBarString(t, testState(100, 50, 2, false, true), ElementBar, "[]") - testElementBarString(t, testState(100, 50, 3, false, true), ElementBar, "[>]") - testElementBarString(t, testState(100, 50, 4, false, true), ElementBar, "[>_]") - testElementBarString(t, testState(100, 50, 5, false, true), ElementBar, "[->_]") - // middle - testElementBarString(t, testState(100, 50, 10, false, true), ElementBar, "[--->____]") - testElementBarString(t, testState(100, 50, 10, false, true), ElementBar, "<--->____>", "<", "", "", "", ">") - // finished - st := testState(100, 100, 10, false, true) - st.finished = true - testElementBarString(t, st, ElementBar, "[--------]") - // empty color - st = testState(100, 50, 10, false, true) - st.Set(Terminal, true) - color.NoColor = false - testElementBarString(t, st, ElementBar, " --->____]", color.RedString("%s", "")) - // empty - testElementBarString(t, testState(0, 50, 10, false, true), ElementBar, "[________]") - // full - testElementBarString(t, testState(20, 20, 10, false, true), ElementBar, "[------->]") - // everflow - testElementBarString(t, testState(20, 50, 10, false, true), ElementBar, "[------->]") - // small width - testElementBarString(t, testState(20, 50, 2, false, true), ElementBar, "[]") - testElementBarString(t, testState(20, 50, 1, false, true), ElementBar, "[") - // negative counters - testElementBarString(t, testState(-50, -150, 10, false, true), ElementBar, "[------->]") - testElementBarString(t, testState(-150, -50, 10, false, true), ElementBar, "[-->_____]") - testElementBarString(t, testState(50, -150, 10, false, true), ElementBar, "[------->]") - testElementBarString(t, testState(-50, 150, 10, false, true), ElementBar, "[------->]") - // long entities / unicode - f1 := []string{"進捗|", "многобайт", "active", "пусто", "|end"} - testElementBarString(t, testState(100, 50, 1, false, true), ElementBar, " ", f1...) - testElementBarString(t, testState(100, 50, 3, false, true), ElementBar, "進 ", f1...) - testElementBarString(t, testState(100, 50, 4, false, true), ElementBar, "進捗", f1...) - testElementBarString(t, testState(100, 50, 29, false, true), ElementBar, "進捗|многactiveпустопусто|end", f1...) - testElementBarString(t, testState(100, 50, 11, false, true), ElementBar, "進捗|aп|end", f1...) - - // unicode - f2 := []string{"⚑", ".", ">", "⟞", "⚐"} - testElementBarString(t, testState(100, 50, 8, false, true), ElementBar, "⚑..>⟞⟞⟞⚐", f2...) - - // no adaptive - testElementBarString(t, testState(0, 50, 10), ElementBar, "[____________________________]") - - var formats = [][]string{ - []string{}, - f1, f2, - } - - // all widths / extreme values - // check for panic and correct width - for _, f := range formats { - for tt := int64(-2); tt < 12; tt++ { - for v := int64(-2); v < 12; v++ { - state := testState(tt, v, 0, false, true) - for w := -2; w < 20; w++ { - state.adaptiveElWidth = w - res := ElementBar(state, f...) - var we = w - if we <= 0 { - we = 30 - } - if CellCount(res) != we { - t.Errorf("Unexpected len(%d): '%s'", we, res) - } - } - } - } - } -} - -func TestElementSpeed(t *testing.T) { - var state = testState(1000, 0, 0, false) - state.time = time.Now() - for i := int64(0); i < 10; i++ { - state.id = uint64(i) + 1 - state.current += 42 - state.time = state.time.Add(time.Second) - state.finished = i == 9 - if state.finished { - state.current += 100 - } - r := ElementSpeed(state) - r2 := ElementSpeed(state) - if r != r2 { - t.Errorf("Must be the same: '%s' vs '%s'", r, r2) - } - if i < 1 { - // do not calc first result - if w := "? p/s"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } else if state.finished { - if w := "58 p/s"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - state.time = state.time.Add(-time.Hour) - r = ElementSpeed(state) - if w := "? p/s"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } else { - if w := "42 p/s"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } - } -} - -func TestElementRemainingTime(t *testing.T) { - var state = testState(100, 0, 0, false) - state.time = time.Now() - state.startTime = state.time - for i := int64(0); i < 10; i++ { - state.id = uint64(i) + 1 - state.time = state.time.Add(time.Second) - state.finished = i == 9 - r := ElementRemainingTime(state) - if i < 1 { - // do not calc first two results - if w := "?"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } else if state.finished { - // final elapsed time - if w := "10s"; r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } else { - w := fmt.Sprintf("%ds", 10-i) - if r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - } - state.current += 10 - } -} - -func TestElementElapsedTime(t *testing.T) { - var state = testState(1000, 0, 0, false) - state.startTime = time.Now() - state.time = state.startTime - for i := int64(0); i < 10; i++ { - r := ElementElapsedTime(state) - if w := fmt.Sprintf("%ds", i); r != w { - t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w) - } - state.time = state.time.Add(time.Second) - } -} - -func TestElementString(t *testing.T) { - var state = testState(0, 0, 0, false) - testElementBarString(t, state, ElementString, "", "myKey") - state.Set("myKey", "my value") - testElementBarString(t, state, ElementString, "my value", "myKey") - state.Set("myKey", "my value1") - testElementBarString(t, state, ElementString, "my value1", "myKey") - testElementBarString(t, state, ElementString, "") -} - -func TestElementCycle(t *testing.T) { - var state = testState(0, 0, 0, false) - testElementBarString(t, state, ElementCycle, "") - testElementBarString(t, state, ElementCycle, "1", "1", "2", "3") - testElementBarString(t, state, ElementCycle, "2", "1", "2", "3") - testElementBarString(t, state, ElementCycle, "3", "1", "2", "3") - testElementBarString(t, state, ElementCycle, "1", "1", "2", "3") - testElementBarString(t, state, ElementCycle, "2", "1", "2") - testElementBarString(t, state, ElementCycle, "1", "1", "2") -} - -func TestAdaptiveWrap(t *testing.T) { - var state = testState(0, 0, 0, false) - state.id = 1 - state.Set("myKey", "my value") - el := adaptiveWrap(ElementString) - testElementBarString(t, state, el, adElPlaceholder, "myKey") - if v := state.recalc[0].ProgressElement(state); v != "my value" { - t.Errorf("Unexpected result: %s", v) - } - state.id = 2 - testElementBarString(t, state, el, adElPlaceholder, "myKey1") - state.Set("myKey", "my value1") - if v := state.recalc[0].ProgressElement(state); v != "my value1" { - t.Errorf("Unexpected result: %s", v) - } -} - -func TestRegisterElement(t *testing.T) { - var testEl ElementFunc = func(state *State, args ...string) string { - return strings.Repeat("*", state.AdaptiveElWidth()) - } - RegisterElement("testEl", testEl, true) - result := ProgressBarTemplate(`{{testEl . }}`).New(0).SetWidth(5).String() - if result != "*****" { - t.Errorf("Unexpected result: '%v'", result) - } -} - -func BenchmarkBar(b *testing.B) { - var formats = map[string][]string{ - "simple": []string{".", ".", ".", ".", "."}, - "unicode": []string{"⚑", "⚒", "⚟", "⟞", "⚐"}, - "color": []string{color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", ".")}, - "long": []string{"..", "..", "..", "..", ".."}, - "longunicode": []string{"⚑⚑", "⚒⚒", "⚟⚟", "⟞⟞", "⚐⚐"}, - } - for name, args := range formats { - state := testState(100, 50, 100, false, true) - b.Run(name, func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - ElementBar(state, args...) - } - }) - } -} diff --git a/vendor/github.com/cheggaaa/pb/v3/go.mod b/vendor/github.com/cheggaaa/pb/v3/go.mod deleted file mode 100644 index 666c86b..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/cheggaaa/pb/v3 - -require ( - github.com/VividCortex/ewma v1.1.1 - github.com/fatih/color v1.7.0 - github.com/mattn/go-colorable v0.1.2 - github.com/mattn/go-isatty v0.0.12 - github.com/mattn/go-runewidth v0.0.7 -) - -go 1.12 diff --git a/vendor/github.com/cheggaaa/pb/v3/go.sum b/vendor/github.com/cheggaaa/pb/v3/go.sum deleted file mode 100644 index 71cb183..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/go.sum +++ /dev/null @@ -1,21 +0,0 @@ -github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= -github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/cheggaaa/pb/v3/io.go b/vendor/github.com/cheggaaa/pb/v3/io.go deleted file mode 100644 index 6ad5abc..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/io.go +++ /dev/null @@ -1,49 +0,0 @@ -package pb - -import ( - "io" -) - -// Reader it's a wrapper for given reader, but with progress handle -type Reader struct { - io.Reader - bar *ProgressBar -} - -// Read reads bytes from wrapped reader and add amount of bytes to progress bar -func (r *Reader) Read(p []byte) (n int, err error) { - n, err = r.Reader.Read(p) - r.bar.Add(n) - return -} - -// Close the wrapped reader when it implements io.Closer -func (r *Reader) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() - } - return -} - -// Writer it's a wrapper for given writer, but with progress handle -type Writer struct { - io.Writer - bar *ProgressBar -} - -// Write writes bytes to wrapped writer and add amount of bytes to progress bar -func (r *Writer) Write(p []byte) (n int, err error) { - n, err = r.Writer.Write(p) - r.bar.Add(n) - return -} - -// Close the wrapped reader when it implements io.Closer -func (r *Writer) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Writer.(io.Closer); ok { - return closer.Close() - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/v3/io_test.go b/vendor/github.com/cheggaaa/pb/v3/io_test.go deleted file mode 100644 index bfd8a06..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/io_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package pb - -import ( - "testing" -) - -func TestPBProxyReader(t *testing.T) { - bar := new(ProgressBar) - if bar.GetBool(Bytes) { - t.Errorf("By default bytes must be false") - } - - testReader := new(testReaderWriterCloser) - proxyReader := bar.NewProxyReader(testReader) - - if !bar.GetBool(Bytes) { - t.Errorf("Bytes must be true after call NewProxyReader") - } - - for i := 0; i < 10; i++ { - buf := make([]byte, 10) - n, e := proxyReader.Read(buf) - if e != nil { - t.Errorf("Proxy reader return err: %v", e) - } - if n != len(buf) { - t.Errorf("Proxy reader return unexpected N: %d (wand %d)", n, len(buf)) - } - for _, b := range buf { - if b != 'f' { - t.Errorf("Unexpected read value: %v (want %v)", b, 'f') - } - } - if want := int64((i + 1) * len(buf)); bar.Current() != want { - t.Errorf("Unexpected bar current value: %d (want %d)", bar.Current(), want) - } - } - proxyReader.Close() - if !testReader.closed { - t.Errorf("Reader must be closed after call ProxyReader.Close") - } - proxyReader.Reader = nil - proxyReader.Close() -} - -func TestPBProxyWriter(t *testing.T) { - bar := new(ProgressBar) - if bar.GetBool(Bytes) { - t.Errorf("By default bytes must be false") - } - - testWriter := new(testReaderWriterCloser) - proxyReader := bar.NewProxyWriter(testWriter) - - if !bar.GetBool(Bytes) { - t.Errorf("Bytes must be true after call NewProxyReader") - } - - for i := 0; i < 10; i++ { - buf := make([]byte, 10) - n, e := proxyReader.Write(buf) - if e != nil { - t.Errorf("Proxy reader return err: %v", e) - } - if n != len(buf) { - t.Errorf("Proxy reader return unexpected N: %d (wand %d)", n, len(buf)) - } - if want := int64((i + 1) * len(buf)); bar.Current() != want { - t.Errorf("Unexpected bar current value: %d (want %d)", bar.Current(), want) - } - } - proxyReader.Close() - if !testWriter.closed { - t.Errorf("Reader must be closed after call ProxyReader.Close") - } - proxyReader.Writer = nil - proxyReader.Close() -} - -type testReaderWriterCloser struct { - closed bool - data []byte -} - -func (tr *testReaderWriterCloser) Read(p []byte) (n int, err error) { - for i := range p { - p[i] = 'f' - } - return len(p), nil -} - -func (tr *testReaderWriterCloser) Write(p []byte) (n int, err error) { - tr.data = append(tr.data, p...) - return len(p), nil -} - -func (tr *testReaderWriterCloser) Close() (err error) { - tr.closed = true - return -} diff --git a/vendor/github.com/cheggaaa/pb/v3/pb.go b/vendor/github.com/cheggaaa/pb/v3/pb.go deleted file mode 100644 index 17f3750..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/pb.go +++ /dev/null @@ -1,566 +0,0 @@ -package pb - -import ( - "bytes" - "fmt" - "io" - "os" - "strconv" - "strings" - "sync" - "sync/atomic" - "text/template" - "time" - - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" - - "github.com/cheggaaa/pb/v3/termutil" -) - -// Version of ProgressBar library -const Version = "3.0.5" - -type key int - -const ( - // Bytes means we're working with byte sizes. Numbers will print as Kb, Mb, etc - // bar.Set(pb.Bytes, true) - Bytes key = 1 << iota - - // Use SI bytes prefix names (kB, MB, etc) instead of IEC prefix names (KiB, MiB, etc) - SIBytesPrefix - - // Terminal means we're will print to terminal and can use ascii sequences - // Also we're will try to use terminal width - Terminal - - // Static means progress bar will not update automaticly - Static - - // ReturnSymbol - by default in terminal mode it's '\r' - ReturnSymbol - - // Color by default is true when output is tty, but you can set to false for disabling colors - Color -) - -const ( - defaultBarWidth = 100 - defaultRefreshRate = time.Millisecond * 200 -) - -// New creates new ProgressBar object -func New(total int) *ProgressBar { - return New64(int64(total)) -} - -// New64 creates new ProgressBar object using int64 as total -func New64(total int64) *ProgressBar { - pb := new(ProgressBar) - return pb.SetTotal(total) -} - -// StartNew starts new ProgressBar with Default template -func StartNew(total int) *ProgressBar { - return New(total).Start() -} - -// Start64 starts new ProgressBar with Default template. Using int64 as total. -func Start64(total int64) *ProgressBar { - return New64(total).Start() -} - -var ( - terminalWidth = termutil.TerminalWidth - isTerminal = isatty.IsTerminal - isCygwinTerminal = isatty.IsCygwinTerminal -) - -// ProgressBar is the main object of bar -type ProgressBar struct { - current, total int64 - width int - maxWidth int - mu sync.RWMutex - rm sync.Mutex - vars map[interface{}]interface{} - elements map[string]Element - output io.Writer - coutput io.Writer - nocoutput io.Writer - startTime time.Time - refreshRate time.Duration - tmpl *template.Template - state *State - buf *bytes.Buffer - ticker *time.Ticker - finish chan struct{} - finished bool - configured bool - err error -} - -func (pb *ProgressBar) configure() { - if pb.configured { - return - } - pb.configured = true - - if pb.vars == nil { - pb.vars = make(map[interface{}]interface{}) - } - if pb.output == nil { - pb.output = os.Stderr - } - - if pb.tmpl == nil { - pb.tmpl, pb.err = getTemplate(string(Default)) - if pb.err != nil { - return - } - } - if pb.vars[Terminal] == nil { - if f, ok := pb.output.(*os.File); ok { - if isTerminal(f.Fd()) || isCygwinTerminal(f.Fd()) { - pb.vars[Terminal] = true - } - } - } - if pb.vars[ReturnSymbol] == nil { - if tm, ok := pb.vars[Terminal].(bool); ok && tm { - pb.vars[ReturnSymbol] = "\r" - } - } - if pb.vars[Color] == nil { - if tm, ok := pb.vars[Terminal].(bool); ok && tm { - pb.vars[Color] = true - } - } - if pb.refreshRate == 0 { - pb.refreshRate = defaultRefreshRate - } - if f, ok := pb.output.(*os.File); ok { - pb.coutput = colorable.NewColorable(f) - } else { - pb.coutput = pb.output - } - pb.nocoutput = colorable.NewNonColorable(pb.output) -} - -// Start starts the bar -func (pb *ProgressBar) Start() *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - if pb.finish != nil { - return pb - } - pb.configure() - pb.finished = false - pb.state = nil - pb.startTime = time.Now() - if st, ok := pb.vars[Static].(bool); ok && st { - return pb - } - pb.finish = make(chan struct{}) - pb.ticker = time.NewTicker(pb.refreshRate) - go pb.writer(pb.finish) - return pb -} - -func (pb *ProgressBar) writer(finish chan struct{}) { - for { - select { - case <-pb.ticker.C: - pb.write(false) - case <-finish: - pb.ticker.Stop() - pb.write(true) - finish <- struct{}{} - return - } - } -} - -// Write performs write to the output -func (pb *ProgressBar) Write() *ProgressBar { - pb.mu.RLock() - finished := pb.finished - pb.mu.RUnlock() - pb.write(finished) - return pb -} - -func (pb *ProgressBar) write(finish bool) { - result, width := pb.render() - if pb.Err() != nil { - return - } - if pb.GetBool(Terminal) { - if r := (width - CellCount(result)); r > 0 { - result += strings.Repeat(" ", r) - } - } - if ret, ok := pb.Get(ReturnSymbol).(string); ok { - result = ret + result - if finish && ret == "\r" { - result += "\n" - } - } - if pb.GetBool(Color) { - pb.coutput.Write([]byte(result)) - } else { - pb.nocoutput.Write([]byte(result)) - } -} - -// Total return current total bar value -func (pb *ProgressBar) Total() int64 { - return atomic.LoadInt64(&pb.total) -} - -// SetTotal sets the total bar value -func (pb *ProgressBar) SetTotal(value int64) *ProgressBar { - atomic.StoreInt64(&pb.total, value) - return pb -} - -// SetCurrent sets the current bar value -func (pb *ProgressBar) SetCurrent(value int64) *ProgressBar { - atomic.StoreInt64(&pb.current, value) - return pb -} - -// Current return current bar value -func (pb *ProgressBar) Current() int64 { - return atomic.LoadInt64(&pb.current) -} - -// Add adding given int64 value to bar value -func (pb *ProgressBar) Add64(value int64) *ProgressBar { - atomic.AddInt64(&pb.current, value) - return pb -} - -// Add adding given int value to bar value -func (pb *ProgressBar) Add(value int) *ProgressBar { - return pb.Add64(int64(value)) -} - -// Increment atomically increments the progress -func (pb *ProgressBar) Increment() *ProgressBar { - return pb.Add64(1) -} - -// Set sets any value by any key -func (pb *ProgressBar) Set(key, value interface{}) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - if pb.vars == nil { - pb.vars = make(map[interface{}]interface{}) - } - pb.vars[key] = value - return pb -} - -// Get return value by key -func (pb *ProgressBar) Get(key interface{}) interface{} { - pb.mu.RLock() - defer pb.mu.RUnlock() - if pb.vars == nil { - return nil - } - return pb.vars[key] -} - -// GetBool return value by key and try to convert there to boolean -// If value doesn't set or not boolean - return false -func (pb *ProgressBar) GetBool(key interface{}) bool { - if v, ok := pb.Get(key).(bool); ok { - return v - } - return false -} - -// SetWidth sets the bar width -// When given value <= 0 would be using the terminal width (if possible) or default value. -func (pb *ProgressBar) SetWidth(width int) *ProgressBar { - pb.mu.Lock() - pb.width = width - pb.mu.Unlock() - return pb -} - -// SetMaxWidth sets the bar maximum width -// When given value <= 0 would be using the terminal width (if possible) or default value. -func (pb *ProgressBar) SetMaxWidth(maxWidth int) *ProgressBar { - pb.mu.Lock() - pb.maxWidth = maxWidth - pb.mu.Unlock() - return pb -} - -// Width return the bar width -// It's current terminal width or settled over 'SetWidth' value. -func (pb *ProgressBar) Width() (width int) { - defer func() { - if r := recover(); r != nil { - width = defaultBarWidth - } - }() - pb.mu.RLock() - width = pb.width - maxWidth := pb.maxWidth - pb.mu.RUnlock() - if width <= 0 { - var err error - if width, err = terminalWidth(); err != nil { - return defaultBarWidth - } - } - if maxWidth > 0 && width > maxWidth { - width = maxWidth - } - return -} - -func (pb *ProgressBar) SetRefreshRate(dur time.Duration) *ProgressBar { - pb.mu.Lock() - if dur > 0 { - pb.refreshRate = dur - } - pb.mu.Unlock() - return pb -} - -// SetWriter sets the io.Writer. Bar will write in this writer -// By default this is os.Stderr -func (pb *ProgressBar) SetWriter(w io.Writer) *ProgressBar { - pb.mu.Lock() - pb.output = w - pb.configured = false - pb.configure() - pb.mu.Unlock() - return pb -} - -// StartTime return the time when bar started -func (pb *ProgressBar) StartTime() time.Time { - pb.mu.RLock() - defer pb.mu.RUnlock() - return pb.startTime -} - -// Format convert int64 to string according to the current settings -func (pb *ProgressBar) Format(v int64) string { - if pb.GetBool(Bytes) { - return formatBytes(v, pb.GetBool(SIBytesPrefix)) - } - return strconv.FormatInt(v, 10) -} - -// Finish stops the bar -func (pb *ProgressBar) Finish() *ProgressBar { - pb.mu.Lock() - if pb.finished { - pb.mu.Unlock() - return pb - } - finishChan := pb.finish - pb.finished = true - pb.mu.Unlock() - if finishChan != nil { - finishChan <- struct{}{} - <-finishChan - pb.mu.Lock() - pb.finish = nil - pb.mu.Unlock() - } - return pb -} - -// IsStarted indicates progress bar state -func (pb *ProgressBar) IsStarted() bool { - pb.mu.RLock() - defer pb.mu.RUnlock() - return pb.finish != nil -} - -// SetTemplateString sets ProgressBar tempate string and parse it -func (pb *ProgressBar) SetTemplateString(tmpl string) *ProgressBar { - pb.mu.Lock() - defer pb.mu.Unlock() - pb.tmpl, pb.err = getTemplate(tmpl) - return pb -} - -// SetTemplateString sets ProgressBarTempate and parse it -func (pb *ProgressBar) SetTemplate(tmpl ProgressBarTemplate) *ProgressBar { - return pb.SetTemplateString(string(tmpl)) -} - -// NewProxyReader creates a wrapper for given reader, but with progress handle -// Takes io.Reader or io.ReadCloser -// Also, it automatically switches progress bar to handle units as bytes -func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { - pb.Set(Bytes, true) - return &Reader{r, pb} -} - -// NewProxyWriter creates a wrapper for given writer, but with progress handle -// Takes io.Writer or io.WriteCloser -// Also, it automatically switches progress bar to handle units as bytes -func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer { - pb.Set(Bytes, true) - return &Writer{r, pb} -} - -func (pb *ProgressBar) render() (result string, width int) { - defer func() { - if r := recover(); r != nil { - pb.SetErr(fmt.Errorf("render panic: %v", r)) - } - }() - pb.rm.Lock() - defer pb.rm.Unlock() - pb.mu.Lock() - pb.configure() - if pb.state == nil { - pb.state = &State{ProgressBar: pb} - pb.buf = bytes.NewBuffer(nil) - } - if pb.startTime.IsZero() { - pb.startTime = time.Now() - } - pb.state.id++ - pb.state.finished = pb.finished - pb.state.time = time.Now() - pb.mu.Unlock() - - pb.state.width = pb.Width() - width = pb.state.width - pb.state.total = pb.Total() - pb.state.current = pb.Current() - pb.buf.Reset() - - if e := pb.tmpl.Execute(pb.buf, pb.state); e != nil { - pb.SetErr(e) - return "", 0 - } - - result = pb.buf.String() - - aec := len(pb.state.recalc) - if aec == 0 { - // no adaptive elements - return - } - - staticWidth := CellCount(result) - (aec * adElPlaceholderLen) - - if pb.state.Width()-staticWidth <= 0 { - result = strings.Replace(result, adElPlaceholder, "", -1) - result = StripString(result, pb.state.Width()) - } else { - pb.state.adaptiveElWidth = (width - staticWidth) / aec - for _, el := range pb.state.recalc { - result = strings.Replace(result, adElPlaceholder, el.ProgressElement(pb.state), 1) - } - } - pb.state.recalc = pb.state.recalc[:0] - return -} - -// SetErr sets error to the ProgressBar -// Error will be available over Err() -func (pb *ProgressBar) SetErr(err error) *ProgressBar { - pb.mu.Lock() - pb.err = err - pb.mu.Unlock() - return pb -} - -// Err return possible error -// When all ok - will be nil -// May contain template.Execute errors -func (pb *ProgressBar) Err() error { - pb.mu.RLock() - defer pb.mu.RUnlock() - return pb.err -} - -// String return currrent string representation of ProgressBar -func (pb *ProgressBar) String() string { - res, _ := pb.render() - return res -} - -// ProgressElement implements Element interface -func (pb *ProgressBar) ProgressElement(s *State, args ...string) string { - if s.IsAdaptiveWidth() { - pb.SetWidth(s.AdaptiveElWidth()) - } - return pb.String() -} - -// State represents the current state of bar -// Need for bar elements -type State struct { - *ProgressBar - - id uint64 - total, current int64 - width, adaptiveElWidth int - finished, adaptive bool - time time.Time - - recalc []Element -} - -// Id it's the current state identifier -// - incremental -// - starts with 1 -// - resets after finish/start -func (s *State) Id() uint64 { - return s.id -} - -// Total it's bar int64 total -func (s *State) Total() int64 { - return s.total -} - -// Value it's current value -func (s *State) Value() int64 { - return s.current -} - -// Width of bar -func (s *State) Width() int { - return s.width -} - -// AdaptiveElWidth - adaptive elements must return string with given cell count (when AdaptiveElWidth > 0) -func (s *State) AdaptiveElWidth() int { - return s.adaptiveElWidth -} - -// IsAdaptiveWidth returns true when element must be shown as adaptive -func (s *State) IsAdaptiveWidth() bool { - return s.adaptive -} - -// IsFinished return true when bar is finished -func (s *State) IsFinished() bool { - return s.finished -} - -// IsFirst return true only in first render -func (s *State) IsFirst() bool { - return s.id == 1 -} - -// Time when state was created -func (s *State) Time() time.Time { - return s.time -} diff --git a/vendor/github.com/cheggaaa/pb/v3/pb_test.go b/vendor/github.com/cheggaaa/pb/v3/pb_test.go deleted file mode 100644 index c8439d9..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/pb_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package pb - -import ( - "bytes" - "errors" - "fmt" - "strings" - "testing" - "time" - - "github.com/fatih/color" -) - -func TestPBBasic(t *testing.T) { - bar := new(ProgressBar) - var a, e int64 - if a, e = bar.Total(), 0; a != e { - t.Errorf("Unexpected total: actual: %v; expected: %v", a, e) - } - if a, e = bar.Current(), 0; a != e { - t.Errorf("Unexpected current: actual: %v; expected: %v", a, e) - } - bar.SetCurrent(10).SetTotal(20) - if a, e = bar.Total(), 20; a != e { - t.Errorf("Unexpected total: actual: %v; expected: %v", a, e) - } - if a, e = bar.Current(), 10; a != e { - t.Errorf("Unexpected current: actual: %v; expected: %v", a, e) - } - bar.Add(5) - if a, e = bar.Current(), 15; a != e { - t.Errorf("Unexpected current: actual: %v; expected: %v", a, e) - } - bar.Increment() - if a, e = bar.Current(), 16; a != e { - t.Errorf("Unexpected current: actual: %v; expected: %v", a, e) - } -} - -func TestPBWidth(t *testing.T) { - terminalWidth = func() (int, error) { - return 50, nil - } - // terminal width - bar := new(ProgressBar) - if a, e := bar.Width(), 50; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - // terminal width error - terminalWidth = func() (int, error) { - return 0, errors.New("test error") - } - if a, e := bar.Width(), defaultBarWidth; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - // terminal width panic - terminalWidth = func() (int, error) { - panic("test") - return 0, nil - } - if a, e := bar.Width(), defaultBarWidth; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - // set negative terminal width - bar.SetWidth(-42) - if a, e := bar.Width(), defaultBarWidth; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - // set terminal width - bar.SetWidth(42) - if a, e := bar.Width(), 42; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } -} - -func TestPBMaxWidth(t *testing.T) { - terminalWidth = func() (int, error) { - return 50, nil - } - // terminal width - bar := new(ProgressBar) - if a, e := bar.Width(), 50; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - - bar.SetMaxWidth(55) - if a, e := bar.Width(), 50; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } - - bar.SetMaxWidth(38) - if a, e := bar.Width(), 38; a != e { - t.Errorf("Unexpected width: actual: %v; expected: %v", a, e) - } -} - -func TestPBTemplate(t *testing.T) { - bar := new(ProgressBar) - result := bar.SetTotal(100).SetCurrent(50).SetWidth(40).String() - expected := "50 / 100 [------->________] 50.00% ? p/s" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } - - // check strip - result = bar.SetWidth(8).String() - expected = "50 / 100" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } - - // invalid template - for _, invalidTemplate := range []string{ - `{{invalid template`, `{{speed}}`, - } { - bar.SetTemplateString(invalidTemplate) - result = bar.String() - expected = "" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } - if err := bar.Err(); err == nil { - t.Errorf("Must be error") - } - } - - // simple template without adaptive elemnts - bar.SetTemplateString(`{{counters . }}`) - result = bar.String() - expected = "50 / 100" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } -} - -func TestPBStartFinish(t *testing.T) { - bar := ProgressBarTemplate(`{{counters . }}`).New(0) - for i := int64(0); i < 2; i++ { - if bar.IsStarted() { - t.Error("Must be false") - } - var buf = bytes.NewBuffer(nil) - bar.SetTotal(100). - SetCurrent(int64(i)). - SetWidth(7). - Set(Terminal, true). - SetWriter(buf). - SetRefreshRate(time.Millisecond * 20). - Start() - if !bar.IsStarted() { - t.Error("Must be true") - } - time.Sleep(time.Millisecond * 100) - bar.Finish() - if buf.Len() == 0 { - t.Error("no writes") - } - var resultsString = strings.TrimPrefix(buf.String(), "\r") - if !strings.HasSuffix(resultsString, "\n") { - t.Error("No end \\n symb") - } else { - resultsString = resultsString[:len(resultsString)-1] - } - var results = strings.Split(resultsString, "\r") - if len(results) < 3 { - t.Errorf("Unexpected writes count: %v", len(results)) - } - exp := fmt.Sprintf("%d / 100", i) - for i, res := range results { - if res != exp { - t.Errorf("Unexpected result[%d]: '%v'", i, res) - } - } - // test second finish call - bar.Finish() - } -} - -func TestPBFlags(t *testing.T) { - // Static - color.NoColor = false - buf := bytes.NewBuffer(nil) - bar := ProgressBarTemplate(`{{counters . | red}}`).New(100) - bar.Set(Static, true).SetCurrent(50).SetWidth(10).SetWriter(buf).Start() - if bar.IsStarted() { - t.Error("Must be false") - } - bar.Write() - result := buf.String() - expected := "50 / 100" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n'%s'\n'%s'", result, expected) - } - if !bar.state.IsFirst() { - t.Error("must be true") - } - // Color - bar.Set(Color, true) - buf.Reset() - bar.Write() - result = buf.String() - expected = color.RedString("50 / 100") - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n'%s'\n'%s'", result, expected) - } - if bar.state.IsFirst() { - t.Error("must be false") - } - // Terminal - bar.Set(Terminal, true).SetWriter(buf) - buf.Reset() - bar.Write() - result = buf.String() - expected = "\r" + color.RedString("50 / 100") + " " - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n'%s'\n'%s'", result, expected) - } -} - -func BenchmarkRender(b *testing.B) { - var formats = []string{ - string(Simple), - string(Default), - string(Full), - `{{string . "prefix" | red}}{{counters . | green}} {{bar . | yellow}} {{percent . | cyan}} {{speed . | cyan}}{{string . "suffix" | cyan}}`, - } - var names = []string{ - "Simple", "Default", "Full", "Color", - } - for i, tmpl := range formats { - bar := new(ProgressBar) - bar.SetTemplateString(tmpl).SetWidth(100) - b.Run(names[i], func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - bar.String() - } - }) - } -} diff --git a/vendor/github.com/cheggaaa/pb/v3/preset.go b/vendor/github.com/cheggaaa/pb/v3/preset.go deleted file mode 100644 index f5e2fff..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/preset.go +++ /dev/null @@ -1,15 +0,0 @@ -package pb - -var ( - // Full - preset with all default available elements - // Example: 'Prefix 20/100 [-->______] 20% 1 p/s ETA 1m Suffix' - Full ProgressBarTemplate = `{{string . "prefix"}}{{counters . }} {{bar . }} {{percent . }} {{speed . }} {{rtime . "ETA %s"}}{{string . "suffix"}}` - - // Default - preset like Full but without elapsed time - // Example: 'Prefix 20/100 [-->______] 20% 1 p/s ETA 1m Suffix' - Default ProgressBarTemplate = `{{string . "prefix"}}{{counters . }} {{bar . }} {{percent . }} {{speed . }}{{string . "suffix"}}` - - // Simple - preset without speed and any timers. Only counters, bar and percents - // Example: 'Prefix 20/100 [-->______] 20% Suffix' - Simple ProgressBarTemplate = `{{string . "prefix"}}{{counters . }} {{bar . }} {{percent . }}{{string . "suffix"}}` -) diff --git a/vendor/github.com/cheggaaa/pb/v3/speed.go b/vendor/github.com/cheggaaa/pb/v3/speed.go deleted file mode 100644 index 17a6b1b..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/speed.go +++ /dev/null @@ -1,83 +0,0 @@ -package pb - -import ( - "fmt" - "math" - "time" - - "github.com/VividCortex/ewma" -) - -var speedAddLimit = time.Second / 2 - -type speed struct { - ewma ewma.MovingAverage - lastStateId uint64 - prevValue, startValue int64 - prevTime, startTime time.Time -} - -func (s *speed) value(state *State) float64 { - if s.ewma == nil { - s.ewma = ewma.NewMovingAverage() - } - if state.IsFirst() || state.Id() < s.lastStateId { - s.reset(state) - return 0 - } - if state.Id() == s.lastStateId { - return s.ewma.Value() - } - if state.IsFinished() { - return s.absValue(state) - } - dur := state.Time().Sub(s.prevTime) - if dur < speedAddLimit { - return s.ewma.Value() - } - diff := math.Abs(float64(state.Value() - s.prevValue)) - lastSpeed := diff / dur.Seconds() - s.prevTime = state.Time() - s.prevValue = state.Value() - s.lastStateId = state.Id() - s.ewma.Add(lastSpeed) - return s.ewma.Value() -} - -func (s *speed) reset(state *State) { - s.lastStateId = state.Id() - s.startTime = state.Time() - s.prevTime = state.Time() - s.startValue = state.Value() - s.prevValue = state.Value() - s.ewma = ewma.NewMovingAverage() -} - -func (s *speed) absValue(state *State) float64 { - if dur := state.Time().Sub(s.startTime); dur > 0 { - return float64(state.Value()) / dur.Seconds() - } - return 0 -} - -func getSpeedObj(state *State) (s *speed) { - if sObj, ok := state.Get(speedObj).(*speed); ok { - return sObj - } - s = new(speed) - state.Set(speedObj, s) - return -} - -// ElementSpeed calculates current speed by EWMA -// Optionally can take one or two string arguments. -// First string will be used as value for format speed, default is "%s p/s". -// Second string will be used when speed not available, default is "? p/s" -// In template use as follows: {{speed .}} or {{speed . "%s per second"}} or {{speed . "%s ps" "..."} -var ElementSpeed ElementFunc = func(state *State, args ...string) string { - sp := getSpeedObj(state).value(state) - if sp == 0 { - return argsHelper(args).getNotEmptyOr(1, "? p/s") - } - return fmt.Sprintf(argsHelper(args).getNotEmptyOr(0, "%s p/s"), state.Format(int64(round(sp)))) -} diff --git a/vendor/github.com/cheggaaa/pb/v3/template.go b/vendor/github.com/cheggaaa/pb/v3/template.go deleted file mode 100644 index ecfc271..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/template.go +++ /dev/null @@ -1,88 +0,0 @@ -package pb - -import ( - "math/rand" - "sync" - "text/template" - - "github.com/fatih/color" -) - -// ProgressBarTemplate that template string -type ProgressBarTemplate string - -// New creates new bar from template -func (pbt ProgressBarTemplate) New(total int) *ProgressBar { - return New(total).SetTemplate(pbt) -} - -// Start64 create and start new bar with given int64 total value -func (pbt ProgressBarTemplate) Start64(total int64) *ProgressBar { - return New64(total).SetTemplate(pbt).Start() -} - -// Start create and start new bar with given int total value -func (pbt ProgressBarTemplate) Start(total int) *ProgressBar { - return pbt.Start64(int64(total)) -} - -var templateCacheMu sync.Mutex -var templateCache = make(map[string]*template.Template) - -var defaultTemplateFuncs = template.FuncMap{ - // colors - "black": color.New(color.FgBlack).SprintFunc(), - "red": color.New(color.FgRed).SprintFunc(), - "green": color.New(color.FgGreen).SprintFunc(), - "yellow": color.New(color.FgYellow).SprintFunc(), - "blue": color.New(color.FgBlue).SprintFunc(), - "magenta": color.New(color.FgMagenta).SprintFunc(), - "cyan": color.New(color.FgCyan).SprintFunc(), - "white": color.New(color.FgWhite).SprintFunc(), - "resetcolor": color.New(color.Reset).SprintFunc(), - "rndcolor": rndcolor, - "rnd": rnd, -} - -func getTemplate(tmpl string) (t *template.Template, err error) { - templateCacheMu.Lock() - defer templateCacheMu.Unlock() - t = templateCache[tmpl] - if t != nil { - // found in cache - return - } - t = template.New("") - fillTemplateFuncs(t) - _, err = t.Parse(tmpl) - if err != nil { - t = nil - return - } - templateCache[tmpl] = t - return -} - -func fillTemplateFuncs(t *template.Template) { - t.Funcs(defaultTemplateFuncs) - emf := make(template.FuncMap) - elementsM.Lock() - for k, v := range elements { - emf[k] = v - } - elementsM.Unlock() - t.Funcs(emf) - return -} - -func rndcolor(s string) string { - c := rand.Intn(int(color.FgWhite-color.FgBlack)) + int(color.FgBlack) - return color.New(color.Attribute(c)).Sprint(s) -} - -func rnd(args ...string) string { - if len(args) == 0 { - return "" - } - return args[rand.Intn(len(args))] -} diff --git a/vendor/github.com/cheggaaa/pb/v3/template_test.go b/vendor/github.com/cheggaaa/pb/v3/template_test.go deleted file mode 100644 index 84022d3..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/template_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package pb - -import ( - "bytes" - "testing" -) - -func TestProgressBarTemplate(t *testing.T) { - // test New - bar := ProgressBarTemplate(`{{counters . }}`).New(0) - result := bar.String() - expected := "0" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } - if bar.IsStarted() { - t.Error("Must be false") - } - - // test Start - bar = ProgressBarTemplate(`{{counters . }}`).Start(42).SetWriter(bytes.NewBuffer(nil)) - result = bar.String() - expected = "0 / 42" - if result != expected { - t.Errorf("Unexpected result: (actual/expected)\n%s\n%s", result, expected) - } - if !bar.IsStarted() { - t.Error("Must be true") - } -} - -func TestTemplateFuncs(t *testing.T) { - var results = make(map[string]int) - for i := 0; i < 100; i++ { - r := rndcolor("s") - results[r] = results[r] + 1 - } - if len(results) < 6 { - t.Errorf("Unexpected rndcolor results count: %v", len(results)) - } - - results = make(map[string]int) - for i := 0; i < 100; i++ { - r := rnd("1", "2", "3") - results[r] = results[r] + 1 - } - if len(results) != 3 { - t.Errorf("Unexpected rnd results count: %v", len(results)) - } - if r := rnd(); r != "" { - t.Errorf("Unexpected rnd result: '%v'", r) - } -} diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term.go deleted file mode 100644 index 02b5279..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term.go +++ /dev/null @@ -1,56 +0,0 @@ -package termutil - -import ( - "errors" - "os" - "os/signal" - "sync" -) - -var echoLocked bool -var echoLockMutex sync.Mutex -var errLocked = errors.New("terminal locked") - -// RawModeOn switches terminal to raw mode -func RawModeOn() (quit chan struct{}, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if echoLocked { - err = errLocked - return - } - if err = lockEcho(); err != nil { - return - } - echoLocked = true - quit = make(chan struct{}, 1) - go catchTerminate(quit) - return -} - -// RawModeOff restore previous terminal state -func RawModeOff() (err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if !echoLocked { - return - } - if err = unlockEcho(); err != nil { - return - } - echoLocked = false - return -} - -// listen exit signals and restore terminal state -func catchTerminate(quit chan struct{}) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, unlockSignals...) - defer signal.Stop(sig) - select { - case <-quit: - RawModeOff() - case <-sig: - RawModeOff() - } -} diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_appengine.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_appengine.go deleted file mode 100644 index 4b7b20e..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_appengine.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build appengine - -package termutil - -import "errors" - -// terminalWidth returns width of the terminal, which is not supported -// and should always failed on appengine classic which is a sandboxed PaaS. -func TerminalWidth() (int, error) { - return 0, errors.New("Not supported") -} diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_bsd.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_bsd.go deleted file mode 100644 index 272659a..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_bsd.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package termutil - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_linux.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_linux.go deleted file mode 100644 index 2f59e53..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_linux.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build linux -// +build !appengine - -package termutil - -const ioctlReadTermios = 0x5401 // syscall.TCGETS -const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_nix.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_nix.go deleted file mode 100644 index 14277e7..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_nix.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package termutil - -import "syscall" - -const sysIoctl = syscall.SYS_IOCTL diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_plan9.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_plan9.go deleted file mode 100644 index f3934c6..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_plan9.go +++ /dev/null @@ -1,50 +0,0 @@ -package termutil - -import ( - "errors" - "os" - "syscall" -) - -var ( - consctl *os.File - - // Plan 9 doesn't have syscall.SIGQUIT - unlockSignals = []os.Signal{ - os.Interrupt, syscall.SIGTERM, syscall.SIGKILL, - } -) - -// TerminalWidth returns width of the terminal. -func TerminalWidth() (int, error) { - return 0, errors.New("Not supported") -} - -func lockEcho() error { - if consctl != nil { - return errors.New("consctl already open") - } - var err error - consctl, err = os.OpenFile("/dev/consctl", os.O_WRONLY, 0) - if err != nil { - return err - } - _, err = consctl.WriteString("rawon") - if err != nil { - consctl.Close() - consctl = nil - return err - } - return nil -} - -func unlockEcho() error { - if consctl == nil { - return nil - } - if err := consctl.Close(); err != nil { - return err - } - consctl = nil - return nil -} diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_solaris.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_solaris.go deleted file mode 100644 index fc96c2b..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_solaris.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build solaris -// +build !appengine - -package termutil - -const ioctlReadTermios = 0x5401 // syscall.TCGETS -const ioctlWriteTermios = 0x5402 // syscall.TCSETS -const sysIoctl = 54 diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_win.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_win.go deleted file mode 100644 index c867d27..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_win.go +++ /dev/null @@ -1,155 +0,0 @@ -// +build windows - -package termutil - -import ( - "fmt" - "os" - "os/exec" - "strconv" - "syscall" - "unsafe" -) - -var ( - tty = os.Stdin - - unlockSignals = []os.Signal{ - os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL, - } -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - - // GetConsoleScreenBufferInfo retrieves information about the - // specified console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - - // GetConsoleMode retrieves the current input mode of a console's - // input buffer or the current output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx - getConsoleMode = kernel32.NewProc("GetConsoleMode") - - // SetConsoleMode sets the input mode of a console's input buffer - // or the output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - setConsoleMode = kernel32.NewProc("SetConsoleMode") - - // SetConsoleCursorPosition sets the cursor position in the - // specified console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx - setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - - mingw = isMingw() -) - -type ( - // Defines the coordinates of the upper left and lower right corners - // of a rectangle. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx - smallRect struct { - Left, Top, Right, Bottom int16 - } - - // Defines the coordinates of a character cell in a console screen - // buffer. The origin of the coordinate system (0,0) is at the top, left cell - // of the buffer. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx - coordinates struct { - X, Y int16 - } - - word int16 - - // Contains information about a console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx - consoleScreenBufferInfo struct { - dwSize coordinates - dwCursorPosition coordinates - wAttributes word - srWindow smallRect - dwMaximumWindowSize coordinates - } -) - -// TerminalWidth returns width of the terminal. -func TerminalWidth() (width int, err error) { - if mingw { - return termWidthTPut() - } - return termWidthCmd() -} - -func termWidthCmd() (width int, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return 0, error(e) - } - return int(info.dwSize.X) - 1, nil -} - -func isMingw() bool { - return os.Getenv("MINGW_PREFIX") != "" || os.Getenv("MSYSTEM") == "MINGW64" -} - -func termWidthTPut() (width int, err error) { - // TODO: maybe anybody knows a better way to get it on mintty... - var res []byte - cmd := exec.Command("tput", "cols") - cmd.Stdin = os.Stdin - if res, err = cmd.CombinedOutput(); err != nil { - return 0, fmt.Errorf("%s: %v", string(res), err) - } - if len(res) > 1 { - res = res[:len(res)-1] - } - return strconv.Atoi(string(res)) -} - -func getCursorPos() (pos coordinates, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return info.dwCursorPosition, error(e) - } - return info.dwCursorPosition, nil -} - -func setCursorPos(pos coordinates) error { - _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0) - if e != 0 { - return error(e) - } - return nil -} - -var oldState word - -func lockEcho() (err error) { - if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - const ENABLE_ECHO_INPUT = 0x0004 - const ENABLE_LINE_INPUT = 0x0002 - newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - return -} - -func unlockEcho() (err error) { - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/v3/termutil/term_x.go b/vendor/github.com/cheggaaa/pb/v3/termutil/term_x.go deleted file mode 100644 index 6937755..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/termutil/term_x.go +++ /dev/null @@ -1,76 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly -// +build !appengine - -package termutil - -import ( - "fmt" - "os" - "syscall" - "unsafe" -) - -var ( - tty *os.File - - unlockSignals = []os.Signal{ - os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL, - } -) - -type window struct { - Row uint16 - Col uint16 - Xpixel uint16 - Ypixel uint16 -} - -func init() { - var err error - tty, err = os.Open("/dev/tty") - if err != nil { - tty = os.Stdin - } -} - -// TerminalWidth returns width of the terminal. -func TerminalWidth() (int, error) { - w := new(window) - res, _, err := syscall.Syscall(sysIoctl, - tty.Fd(), - uintptr(syscall.TIOCGWINSZ), - uintptr(unsafe.Pointer(w)), - ) - if int(res) == -1 { - return 0, err - } - return int(w.Col), nil -} - -var oldState syscall.Termios - -func lockEcho() (err error) { - fd := tty.Fd() - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - newState.Lflag &^= syscall.ECHO - newState.Lflag |= syscall.ICANON | syscall.ISIG - newState.Iflag |= syscall.ICRNL - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - return -} - -func unlockEcho() (err error) { - fd := tty.Fd() - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/v3/util.go b/vendor/github.com/cheggaaa/pb/v3/util.go deleted file mode 100644 index 0781234..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/util.go +++ /dev/null @@ -1,115 +0,0 @@ -package pb - -import ( - "bytes" - "fmt" - "github.com/mattn/go-runewidth" - "math" - "regexp" - //"unicode/utf8" -) - -const ( - _KiB = 1024 - _MiB = 1048576 - _GiB = 1073741824 - _TiB = 1099511627776 - - _kB = 1e3 - _MB = 1e6 - _GB = 1e9 - _TB = 1e12 -) - -var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d") - -func CellCount(s string) int { - n := runewidth.StringWidth(s) - for _, sm := range ctrlFinder.FindAllString(s, -1) { - n -= runewidth.StringWidth(sm) - } - return n -} - -func StripString(s string, w int) string { - l := CellCount(s) - if l <= w { - return s - } - var buf = bytes.NewBuffer(make([]byte, 0, len(s))) - StripStringToBuffer(s, w, buf) - return buf.String() -} - -func StripStringToBuffer(s string, w int, buf *bytes.Buffer) { - var seqs = ctrlFinder.FindAllStringIndex(s, -1) -mainloop: - for i, r := range s { - for _, seq := range seqs { - if i >= seq[0] && i < seq[1] { - buf.WriteRune(r) - continue mainloop - } - } - if rw := CellCount(string(r)); rw <= w { - w -= rw - buf.WriteRune(r) - } else { - break - } - } - for w > 0 { - buf.WriteByte(' ') - w-- - } - return -} - -func round(val float64) (newVal float64) { - roundOn := 0.5 - places := 0 - var round float64 - pow := math.Pow(10, float64(places)) - digit := pow * val - _, div := math.Modf(digit) - if div >= roundOn { - round = math.Ceil(digit) - } else { - round = math.Floor(digit) - } - newVal = round / pow - return -} - -// Convert bytes to human readable string. Like a 2 MiB, 64.2 KiB, or 2 MB, 64.2 kB -// if useSIPrefix is set to true -func formatBytes(i int64, useSIPrefix bool) (result string) { - if !useSIPrefix { - switch { - case i >= _TiB: - result = fmt.Sprintf("%.02f TiB", float64(i)/_TiB) - case i >= _GiB: - result = fmt.Sprintf("%.02f GiB", float64(i)/_GiB) - case i >= _MiB: - result = fmt.Sprintf("%.02f MiB", float64(i)/_MiB) - case i >= _KiB: - result = fmt.Sprintf("%.02f KiB", float64(i)/_KiB) - default: - result = fmt.Sprintf("%d B", i) - } - } else { - switch { - case i >= _TB: - result = fmt.Sprintf("%.02f TB", float64(i)/_TB) - case i >= _GB: - result = fmt.Sprintf("%.02f GB", float64(i)/_GB) - case i >= _MB: - result = fmt.Sprintf("%.02f MB", float64(i)/_MB) - case i >= _kB: - result = fmt.Sprintf("%.02f kB", float64(i)/_kB) - default: - result = fmt.Sprintf("%d B", i) - } - } - return -} diff --git a/vendor/github.com/cheggaaa/pb/v3/util_test.go b/vendor/github.com/cheggaaa/pb/v3/util_test.go deleted file mode 100644 index d541876..0000000 --- a/vendor/github.com/cheggaaa/pb/v3/util_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package pb - -import ( - "github.com/fatih/color" - "testing" -) - -var testColorString = color.RedString("red") + - color.GreenString("hello") + - "simple" + - color.WhiteString("進捗") - -func TestUtilCellCount(t *testing.T) { - if e, l := 18, CellCount(testColorString); l != e { - t.Errorf("Invalid length %d, expected %d", l, e) - } -} - -func TestUtilStripString(t *testing.T) { - if r, e := StripString("12345", 4), "1234"; r != e { - t.Errorf("Invalid result '%s', expected '%s'", r, e) - } - - if r, e := StripString("12345", 5), "12345"; r != e { - t.Errorf("Invalid result '%s', expected '%s'", r, e) - } - if r, e := StripString("12345", 10), "12345"; r != e { - t.Errorf("Invalid result '%s', expected '%s'", r, e) - } - - s := color.RedString("1") + "23" - e := color.RedString("1") + "2" - if r := StripString(s, 2); r != e { - t.Errorf("Invalid result '%s', expected '%s'", r, e) - } - return -} - -func TestUtilRound(t *testing.T) { - if v := round(4.4); v != 4 { - t.Errorf("Unexpected result: %v", v) - } - if v := round(4.501); v != 5 { - t.Errorf("Unexpected result: %v", v) - } -} - -func TestUtilFormatBytes(t *testing.T) { - inputs := []struct { - v int64 - s bool - e string - }{ - {v: 1000, s: false, e: "1000 B"}, - {v: 1024, s: false, e: "1.00 KiB"}, - {v: 3*_MiB + 140*_KiB, s: false, e: "3.14 MiB"}, - {v: 2 * _GiB, s: false, e: "2.00 GiB"}, - {v: 2048 * _GiB, s: false, e: "2.00 TiB"}, - - {v: 999, s: true, e: "999 B"}, - {v: 1024, s: true, e: "1.02 kB"}, - {v: 3*_MB + 140*_kB, s: true, e: "3.14 MB"}, - {v: 2 * _GB, s: true, e: "2.00 GB"}, - {v: 2048 * _GB, s: true, e: "2.05 TB"}, - } - - for _, input := range inputs { - actual := formatBytes(input.v, input.s) - if actual != input.e { - t.Errorf("Expected {%s} was {%s}", input.e, actual) - } - } -} - -func BenchmarkUtilsCellCount(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - CellCount(testColorString) - } -} diff --git a/vendor/github.com/cheggaaa/pb/writer.go b/vendor/github.com/cheggaaa/pb/writer.go deleted file mode 100644 index 9451ec5..0000000 --- a/vendor/github.com/cheggaaa/pb/writer.go +++ /dev/null @@ -1,26 +0,0 @@ -package pb - -import ( - "io" -) - -// It's proxy Writer, implement io.Writer -type Writer struct { - io.Writer - bar *ProgressBar -} - -func (r *Writer) Write(p []byte) (n int, err error) { - n, err = r.Writer.Write(p) - r.bar.Add(n) - return -} - -// Close the reader when it implements io.Closer -func (r *Writer) Close() (err error) { - r.bar.Finish() - if closer, ok := r.Writer.(io.Closer); ok { - return closer.Close() - } - return -} diff --git a/vendor/github.com/dchest/.directory b/vendor/github.com/dchest/.directory deleted file mode 100644 index dcad5d9..0000000 --- a/vendor/github.com/dchest/.directory +++ /dev/null @@ -1,6 +0,0 @@ -[Dolphin] -Timestamp=2018,1,11,12,15,22 -Version=3 - -[Settings] -HiddenFilesShown=true diff --git a/vendor/github.com/dchest/blake256/README.markdown b/vendor/github.com/dchest/blake256/README.markdown deleted file mode 100644 index 98426c2..0000000 --- a/vendor/github.com/dchest/blake256/README.markdown +++ /dev/null @@ -1,56 +0,0 @@ -Package blake256 -===================== - - import "github.com/dchest/blake256" - -Package blake256 implements BLAKE-256 and BLAKE-224 hash functions (SHA-3 -candidate). - -Public domain. - - -Constants ---------- - -``` go -const BlockSize = 64 -``` -The block size of the hash algorithm in bytes. - -``` go -const Size = 32 -``` -The size of BLAKE-256 hash in bytes. - -``` go -const Size224 = 28 -``` -The size of BLAKE-224 hash in bytes. - - -Functions ---------- - -### func New - - func New() hash.Hash - -New returns a new hash.Hash computing the BLAKE-256 checksum. - -### func New224 - - func New224() hash.Hash - -New224 returns a new hash.Hash computing the BLAKE-224 checksum. - -### func New224Salt - - func New224Salt(salt []byte) hash.Hash - -New224Salt is like New224 but initializes salt with the given 16-byte slice. - -### func NewSalt - - func NewSalt(salt []byte) hash.Hash - -NewSalt is like New but initializes salt with the given 16-byte slice. diff --git a/vendor/github.com/dchest/blake256/blake256.go b/vendor/github.com/dchest/blake256/blake256.go deleted file mode 100644 index 148ec9e..0000000 --- a/vendor/github.com/dchest/blake256/blake256.go +++ /dev/null @@ -1,194 +0,0 @@ -// Written in 2011-2012 by Dmitry Chestnykh. -// -// To the extent possible under law, the author have dedicated all copyright -// and related and neighboring rights to this software to the public domain -// worldwide. This software is distributed without any warranty. -// http://creativecommons.org/publicdomain/zero/1.0/ - -// Package blake256 implements BLAKE-256 and BLAKE-224 hash functions (SHA-3 -// candidate). -package blake256 - -import "hash" - -// The block size of the hash algorithm in bytes. -const BlockSize = 64 - -// The size of BLAKE-256 hash in bytes. -const Size = 32 - -// The size of BLAKE-224 hash in bytes. -const Size224 = 28 - -type digest struct { - hashSize int // hash output size in bits (224 or 256) - h [8]uint32 // current chain value - s [4]uint32 // salt (zero by default) - t uint64 // message bits counter - nullt bool // special case for finalization: skip counter - x [BlockSize]byte // buffer for data not yet compressed - nx int // number of bytes in buffer -} - -var ( - // Initialization values. - iv256 = [8]uint32{ - 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, - 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19} - - iv224 = [8]uint32{ - 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, - 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4} - - pad = [64]byte{0x80} -) - -// Reset resets the state of digest. It leaves salt intact. -func (d *digest) Reset() { - if d.hashSize == 224 { - d.h = iv224 - } else { - d.h = iv256 - } - d.t = 0 - d.nx = 0 - d.nullt = false -} - -func (d *digest) Size() int { return d.hashSize >> 3 } - -func (d *digest) BlockSize() int { return BlockSize } - -func (d *digest) Write(p []byte) (nn int, err error) { - nn = len(p) - if d.nx > 0 { - n := len(p) - if n > BlockSize-d.nx { - n = BlockSize - d.nx - } - d.nx += copy(d.x[d.nx:], p) - if d.nx == BlockSize { - block(d, d.x[:]) - d.nx = 0 - } - p = p[n:] - } - if len(p) >= BlockSize { - n := len(p) &^ (BlockSize - 1) - block(d, p[:n]) - p = p[n:] - } - if len(p) > 0 { - d.nx = copy(d.x[:], p) - } - return -} - -// Sum returns the calculated checksum. -func (d0 *digest) Sum(in []byte) []byte { - // Make a copy of d0 so that caller can keep writing and summing. - d := *d0 - - nx := uint64(d.nx) - l := d.t + nx<<3 - len := make([]byte, 8) - len[0] = byte(l >> 56) - len[1] = byte(l >> 48) - len[2] = byte(l >> 40) - len[3] = byte(l >> 32) - len[4] = byte(l >> 24) - len[5] = byte(l >> 16) - len[6] = byte(l >> 8) - len[7] = byte(l) - - if nx == 55 { - // One padding byte. - d.t -= 8 - if d.hashSize == 224 { - d.Write([]byte{0x80}) - } else { - d.Write([]byte{0x81}) - } - } else { - if nx < 55 { - // Enough space to fill the block. - if nx == 0 { - d.nullt = true - } - d.t -= 440 - nx<<3 - d.Write(pad[0 : 55-nx]) - } else { - // Need 2 compressions. - d.t -= 512 - nx<<3 - d.Write(pad[0 : 64-nx]) - d.t -= 440 - d.Write(pad[1:56]) - d.nullt = true - } - if d.hashSize == 224 { - d.Write([]byte{0x00}) - } else { - d.Write([]byte{0x01}) - } - d.t -= 8 - } - d.t -= 64 - d.Write(len) - - out := make([]byte, d.Size()) - j := 0 - for _, s := range d.h[:d.hashSize>>5] { - out[j+0] = byte(s >> 24) - out[j+1] = byte(s >> 16) - out[j+2] = byte(s >> 8) - out[j+3] = byte(s >> 0) - j += 4 - } - return append(in, out...) -} - -func (d *digest) setSalt(s []byte) { - if len(s) != 16 { - panic("salt length must be 16 bytes") - } - d.s[0] = uint32(s[0])<<24 | uint32(s[1])<<16 | uint32(s[2])<<8 | uint32(s[3]) - d.s[1] = uint32(s[4])<<24 | uint32(s[5])<<16 | uint32(s[6])<<8 | uint32(s[7]) - d.s[2] = uint32(s[8])<<24 | uint32(s[9])<<16 | uint32(s[10])<<8 | uint32(s[11]) - d.s[3] = uint32(s[12])<<24 | uint32(s[13])<<16 | uint32(s[14])<<8 | uint32(s[15]) -} - -// New returns a new hash.Hash computing the BLAKE-256 checksum. -func New() hash.Hash { - return &digest{ - hashSize: 256, - h: iv256, - } -} - -// NewSalt is like New but initializes salt with the given 16-byte slice. -func NewSalt(salt []byte) hash.Hash { - d := &digest{ - hashSize: 256, - h: iv256, - } - d.setSalt(salt) - return d -} - -// New224 returns a new hash.Hash computing the BLAKE-224 checksum. -func New224() hash.Hash { - return &digest{ - hashSize: 224, - h: iv224, - } -} - -// New224Salt is like New224 but initializes salt with the given 16-byte slice. -func New224Salt(salt []byte) hash.Hash { - d := &digest{ - hashSize: 224, - h: iv224, - } - d.setSalt(salt) - return d -} diff --git a/vendor/github.com/dchest/blake256/blake256_test.go b/vendor/github.com/dchest/blake256/blake256_test.go deleted file mode 100644 index 1908133..0000000 --- a/vendor/github.com/dchest/blake256/blake256_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// Written in 2011-2012 by Dmitry Chestnykh. -// -// To the extent possible under law, the author have dedicated all copyright -// and related and neighboring rights to this software to the public domain -// worldwide. This software is distributed without any warranty. -// http://creativecommons.org/publicdomain/zero/1.0/ - -package blake256 - -import ( - "bytes" - "fmt" - "hash" - "testing" -) - -func Test256C(t *testing.T) { - // Test as in C program. - var hashes = [][]byte{ - { - 0x0C, 0xE8, 0xD4, 0xEF, 0x4D, 0xD7, 0xCD, 0x8D, - 0x62, 0xDF, 0xDE, 0xD9, 0xD4, 0xED, 0xB0, 0xA7, - 0x74, 0xAE, 0x6A, 0x41, 0x92, 0x9A, 0x74, 0xDA, - 0x23, 0x10, 0x9E, 0x8F, 0x11, 0x13, 0x9C, 0x87, - }, - { - 0xD4, 0x19, 0xBA, 0xD3, 0x2D, 0x50, 0x4F, 0xB7, - 0xD4, 0x4D, 0x46, 0x0C, 0x42, 0xC5, 0x59, 0x3F, - 0xE5, 0x44, 0xFA, 0x4C, 0x13, 0x5D, 0xEC, 0x31, - 0xE2, 0x1B, 0xD9, 0xAB, 0xDC, 0xC2, 0x2D, 0x41, - }, - } - data := make([]byte, 72) - - h := New() - h.Write(data[:1]) - sum := h.Sum(nil) - if !bytes.Equal(hashes[0], sum) { - t.Errorf("0: expected %X, got %X", hashes[0], sum) - } - - // Try to continue hashing. - h.Write(data[1:]) - sum = h.Sum(nil) - if !bytes.Equal(hashes[1], sum) { - t.Errorf("1(1): expected %X, got %X", hashes[1], sum) - } - - // Try with reset. - h.Reset() - h.Write(data) - sum = h.Sum(nil) - if !bytes.Equal(hashes[1], sum) { - t.Errorf("1(2): expected %X, got %X", hashes[1], sum) - } -} - -type blakeVector struct { - out, in string -} - -var vectors256 = []blakeVector{ - {"7576698ee9cad30173080678e5965916adbb11cb5245d386bf1ffda1cb26c9d7", - "The quick brown fox jumps over the lazy dog"}, - {"07663e00cf96fbc136cf7b1ee099c95346ba3920893d18cc8851f22ee2e36aa6", - "BLAKE"}, - {"716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", - ""}, - {"18a393b4e62b1887a2edf79a5c5a5464daf5bbb976f4007bea16a73e4c1e198e", - "'BLAKE wins SHA-3! Hooray!!!' (I have time machine)"}, - {"fd7282ecc105ef201bb94663fc413db1b7696414682090015f17e309b835f1c2", - "Go"}, - {"1e75db2a709081f853c2229b65fd1558540aa5e7bd17b04b9a4b31989effa711", - "HELP! I'm trapped in hash!"}, - {"4181475cb0c22d58ae847e368e91b4669ea2d84bcd55dbf01fe24bae6571dd08", - `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.`, - }, - {"af95fffc7768821b1e08866a2f9f66916762bfc9d71c4acb5fd515f31fd6785a", // test with one padding byte - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congu", - }, -} - -var vectors224 = []blakeVector{ - {"c8e92d7088ef87c1530aee2ad44dc720cc10589cc2ec58f95a15e51b", - "The quick brown fox jumps over the lazy dog"}, - {"cfb6848add73e1cb47994c4765df33b8f973702705a30a71fe4747a3", - "BLAKE"}, - {"7dc5313b1c04512a174bd6503b89607aecbee0903d40a8a569c94eed", - ""}, - {"dde9e442003c24495db607b17e07ec1f67396cc1907642a09a96594e", - "Go"}, - {"9f655b0a92d4155754fa35e055ce7c5e18eb56347081ea1e5158e751", - "Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo"}, -} - -func testVectors(t *testing.T, hashfunc func() hash.Hash, vectors []blakeVector) { - for i, v := range vectors { - h := hashfunc() - h.Write([]byte(v.in)) - res := fmt.Sprintf("%x", h.Sum(nil)) - if res != v.out { - t.Errorf("%d: expected %q, got %q", i, v.out, res) - } - } -} - -func Test256(t *testing.T) { - testVectors(t, New, vectors256) -} - -func Test224(t *testing.T) { - testVectors(t, New224, vectors224) -} - -var vectors256salt = []struct{ out, in, salt string }{ - {"561d6d0cfa3d31d5eedaf2d575f3942539b03522befc2a1196ba0e51af8992a8", - "", - "1234567890123456"}, - {"88cc11889bbbee42095337fe2153c591971f94fbf8fe540d3c7e9f1700ab2d0c", - "It's so salty out there!", - "SALTsaltSaltSALT"}, -} - -func TestSalt(t *testing.T) { - for i, v := range vectors256salt { - h := NewSalt([]byte(v.salt)) - h.Write([]byte(v.in)) - res := fmt.Sprintf("%x", h.Sum(nil)) - if res != v.out { - t.Errorf("%d: expected %q, got %q", i, v.out, res) - } - } - - // Check that passing bad salt length panics. - defer func() { - if err := recover(); err == nil { - t.Errorf("expected panic for bad salt length") - } - }() - NewSalt([]byte{1, 2, 3, 4, 5, 6, 7, 8}) -} - -func TestTwoWrites(t *testing.T) { - b := make([]byte, 65) - for i := range b { - b[i] = byte(i) - } - h1 := New() - h1.Write(b[:1]) - h1.Write(b[1:]) - sum1 := h1.Sum(nil) - - h2 := New() - h2.Write(b) - sum2 := h2.Sum(nil) - - if !bytes.Equal(sum1, sum2) { - t.Errorf("Result of two writes differs from a single write with the same bytes") - } -} - -var bench = New() -var buf = make([]byte, 8<<10) - -func BenchmarkHash1K(b *testing.B) { - b.SetBytes(1024) - for i := 0; i < b.N; i++ { - bench.Write(buf[:1024]) - } -} - -func BenchmarkHash8K(b *testing.B) { - b.SetBytes(int64(len(buf))) - for i := 0; i < b.N; i++ { - bench.Write(buf) - } -} - -func BenchmarkFull64(b *testing.B) { - b.SetBytes(64) - tmp := make([]byte, 32) - b.ResetTimer() - for i := 0; i < b.N; i++ { - bench.Reset() - bench.Write(buf[:64]) - bench.Sum(tmp[0:0]) - } -} diff --git a/vendor/github.com/dchest/blake256/blake256block.go b/vendor/github.com/dchest/blake256/blake256block.go deleted file mode 100644 index 49daf69..0000000 --- a/vendor/github.com/dchest/blake256/blake256block.go +++ /dev/null @@ -1,1681 +0,0 @@ -// Written in 2011-2012 by Dmitry Chestnykh. -// -// To the extent possible under law, the author have dedicated all copyright -// and related and neighboring rights to this software to the public domain -// worldwide. This software is distributed without any warranty. -// http://creativecommons.org/publicdomain/zero/1.0/ - -// BLAKE-256 block step. -// In its own file so that a faster assembly or C version -// can be substituted easily. - -package blake256 - -const ( - cst0 = 0x243F6A88 - cst1 = 0x85A308D3 - cst2 = 0x13198A2E - cst3 = 0x03707344 - cst4 = 0xA4093822 - cst5 = 0x299F31D0 - cst6 = 0x082EFA98 - cst7 = 0xEC4E6C89 - cst8 = 0x452821E6 - cst9 = 0x38D01377 - cst10 = 0xBE5466CF - cst11 = 0x34E90C6C - cst12 = 0xC0AC29B7 - cst13 = 0xC97C50DD - cst14 = 0x3F84D5B5 - cst15 = 0xB5470917 -) - -func block(d *digest, p []uint8) { - h0, h1, h2, h3, h4, h5, h6, h7 := d.h[0], d.h[1], d.h[2], d.h[3], d.h[4], d.h[5], d.h[6], d.h[7] - s0, s1, s2, s3 := d.s[0], d.s[1], d.s[2], d.s[3] - - for len(p) >= BlockSize { - v0, v1, v2, v3, v4, v5, v6, v7 := h0, h1, h2, h3, h4, h5, h6, h7 - v8 := cst0 ^ s0 - v9 := cst1 ^ s1 - v10 := cst2 ^ s2 - v11 := cst3 ^ s3 - v12 := uint32(cst4) - v13 := uint32(cst5) - v14 := uint32(cst6) - v15 := uint32(cst7) - d.t += 512 - if !d.nullt { - v12 ^= uint32(d.t) - v13 ^= uint32(d.t) - v14 ^= uint32(d.t >> 32) - v15 ^= uint32(d.t >> 32) - } - var m [16]uint32 - - m[0] = uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]) - m[1] = uint32(p[4])<<24 | uint32(p[5])<<16 | uint32(p[6])<<8 | uint32(p[7]) - m[2] = uint32(p[8])<<24 | uint32(p[9])<<16 | uint32(p[10])<<8 | uint32(p[11]) - m[3] = uint32(p[12])<<24 | uint32(p[13])<<16 | uint32(p[14])<<8 | uint32(p[15]) - m[4] = uint32(p[16])<<24 | uint32(p[17])<<16 | uint32(p[18])<<8 | uint32(p[19]) - m[5] = uint32(p[20])<<24 | uint32(p[21])<<16 | uint32(p[22])<<8 | uint32(p[23]) - m[6] = uint32(p[24])<<24 | uint32(p[25])<<16 | uint32(p[26])<<8 | uint32(p[27]) - m[7] = uint32(p[28])<<24 | uint32(p[29])<<16 | uint32(p[30])<<8 | uint32(p[31]) - m[8] = uint32(p[32])<<24 | uint32(p[33])<<16 | uint32(p[34])<<8 | uint32(p[35]) - m[9] = uint32(p[36])<<24 | uint32(p[37])<<16 | uint32(p[38])<<8 | uint32(p[39]) - m[10] = uint32(p[40])<<24 | uint32(p[41])<<16 | uint32(p[42])<<8 | uint32(p[43]) - m[11] = uint32(p[44])<<24 | uint32(p[45])<<16 | uint32(p[46])<<8 | uint32(p[47]) - m[12] = uint32(p[48])<<24 | uint32(p[49])<<16 | uint32(p[50])<<8 | uint32(p[51]) - m[13] = uint32(p[52])<<24 | uint32(p[53])<<16 | uint32(p[54])<<8 | uint32(p[55]) - m[14] = uint32(p[56])<<24 | uint32(p[57])<<16 | uint32(p[58])<<8 | uint32(p[59]) - m[15] = uint32(p[60])<<24 | uint32(p[61])<<16 | uint32(p[62])<<8 | uint32(p[63]) - - // Round 1. - v0 += m[0] ^ cst1 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[2] ^ cst3 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[4] ^ cst5 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[6] ^ cst7 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[5] ^ cst4 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[7] ^ cst6 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[3] ^ cst2 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[1] ^ cst0 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[8] ^ cst9 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[10] ^ cst11 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[12] ^ cst13 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[14] ^ cst15 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[13] ^ cst12 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[15] ^ cst14 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[11] ^ cst10 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[9] ^ cst8 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 2. - v0 += m[14] ^ cst10 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[4] ^ cst8 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[9] ^ cst15 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[13] ^ cst6 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[15] ^ cst9 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[6] ^ cst13 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[8] ^ cst4 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[10] ^ cst14 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[1] ^ cst12 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[0] ^ cst2 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[11] ^ cst7 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[5] ^ cst3 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[7] ^ cst11 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[3] ^ cst5 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[2] ^ cst0 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[12] ^ cst1 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 3. - v0 += m[11] ^ cst8 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[12] ^ cst0 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[5] ^ cst2 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[15] ^ cst13 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[2] ^ cst5 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[13] ^ cst15 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[0] ^ cst12 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[8] ^ cst11 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[10] ^ cst14 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[3] ^ cst6 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[7] ^ cst1 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[9] ^ cst4 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[1] ^ cst7 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[4] ^ cst9 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[6] ^ cst3 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[14] ^ cst10 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 4. - v0 += m[7] ^ cst9 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[3] ^ cst1 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[13] ^ cst12 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[11] ^ cst14 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[12] ^ cst13 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[14] ^ cst11 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[1] ^ cst3 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[9] ^ cst7 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[2] ^ cst6 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[5] ^ cst10 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[4] ^ cst0 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[15] ^ cst8 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[0] ^ cst4 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[8] ^ cst15 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[10] ^ cst5 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[6] ^ cst2 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 5. - v0 += m[9] ^ cst0 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[5] ^ cst7 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[2] ^ cst4 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[10] ^ cst15 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[4] ^ cst2 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[15] ^ cst10 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[7] ^ cst5 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[0] ^ cst9 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[14] ^ cst1 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[11] ^ cst12 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[6] ^ cst8 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[3] ^ cst13 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[8] ^ cst6 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[13] ^ cst3 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[12] ^ cst11 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[1] ^ cst14 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 6. - v0 += m[2] ^ cst12 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[6] ^ cst10 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[0] ^ cst11 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[8] ^ cst3 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[11] ^ cst0 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[3] ^ cst8 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[10] ^ cst6 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[12] ^ cst2 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[4] ^ cst13 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[7] ^ cst5 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[15] ^ cst14 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[1] ^ cst9 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[14] ^ cst15 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[9] ^ cst1 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[5] ^ cst7 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[13] ^ cst4 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 7. - v0 += m[12] ^ cst5 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[1] ^ cst15 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[14] ^ cst13 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[4] ^ cst10 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[13] ^ cst14 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[10] ^ cst4 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[15] ^ cst1 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[5] ^ cst12 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[0] ^ cst7 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[6] ^ cst3 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[9] ^ cst2 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[8] ^ cst11 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[2] ^ cst9 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[11] ^ cst8 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[3] ^ cst6 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[7] ^ cst0 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 8. - v0 += m[13] ^ cst11 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[7] ^ cst14 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[12] ^ cst1 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[3] ^ cst9 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[1] ^ cst12 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[9] ^ cst3 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[14] ^ cst7 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[11] ^ cst13 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[5] ^ cst0 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[15] ^ cst4 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[8] ^ cst6 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[2] ^ cst10 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[6] ^ cst8 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[10] ^ cst2 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[4] ^ cst15 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[0] ^ cst5 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 9. - v0 += m[6] ^ cst15 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[14] ^ cst9 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[11] ^ cst3 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[0] ^ cst8 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[3] ^ cst11 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[8] ^ cst0 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[9] ^ cst14 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[15] ^ cst6 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[12] ^ cst2 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[13] ^ cst7 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[1] ^ cst4 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[10] ^ cst5 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[4] ^ cst1 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[5] ^ cst10 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[7] ^ cst13 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[2] ^ cst12 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 10. - v0 += m[10] ^ cst2 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[8] ^ cst4 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[7] ^ cst6 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[1] ^ cst5 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[6] ^ cst7 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[5] ^ cst1 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[4] ^ cst8 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[2] ^ cst10 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[15] ^ cst11 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[9] ^ cst14 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[3] ^ cst12 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[13] ^ cst0 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[12] ^ cst3 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[0] ^ cst13 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[14] ^ cst9 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[11] ^ cst15 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 11. - v0 += m[0] ^ cst1 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[2] ^ cst3 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[4] ^ cst5 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[6] ^ cst7 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[5] ^ cst4 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[7] ^ cst6 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[3] ^ cst2 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[1] ^ cst0 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[8] ^ cst9 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[10] ^ cst11 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[12] ^ cst13 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[14] ^ cst15 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[13] ^ cst12 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[15] ^ cst14 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[11] ^ cst10 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[9] ^ cst8 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 12. - v0 += m[14] ^ cst10 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[4] ^ cst8 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[9] ^ cst15 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[13] ^ cst6 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[15] ^ cst9 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[6] ^ cst13 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[8] ^ cst4 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[10] ^ cst14 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[1] ^ cst12 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[0] ^ cst2 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[11] ^ cst7 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[5] ^ cst3 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[7] ^ cst11 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[3] ^ cst5 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[2] ^ cst0 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[12] ^ cst1 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 13. - v0 += m[11] ^ cst8 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[12] ^ cst0 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[5] ^ cst2 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[15] ^ cst13 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[2] ^ cst5 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[13] ^ cst15 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[0] ^ cst12 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[8] ^ cst11 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[10] ^ cst14 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[3] ^ cst6 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[7] ^ cst1 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[9] ^ cst4 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[1] ^ cst7 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[4] ^ cst9 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[6] ^ cst3 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[14] ^ cst10 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - // Round 14. - v0 += m[7] ^ cst9 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-16) | v12>>16 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-12) | v4>>12 - v1 += m[3] ^ cst1 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-16) | v13>>16 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-12) | v5>>12 - v2 += m[13] ^ cst12 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-16) | v14>>16 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-12) | v6>>12 - v3 += m[11] ^ cst14 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-16) | v15>>16 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-12) | v7>>12 - v2 += m[12] ^ cst13 - v2 += v6 - v14 ^= v2 - v14 = v14<<(32-8) | v14>>8 - v10 += v14 - v6 ^= v10 - v6 = v6<<(32-7) | v6>>7 - v3 += m[14] ^ cst11 - v3 += v7 - v15 ^= v3 - v15 = v15<<(32-8) | v15>>8 - v11 += v15 - v7 ^= v11 - v7 = v7<<(32-7) | v7>>7 - v1 += m[1] ^ cst3 - v1 += v5 - v13 ^= v1 - v13 = v13<<(32-8) | v13>>8 - v9 += v13 - v5 ^= v9 - v5 = v5<<(32-7) | v5>>7 - v0 += m[9] ^ cst7 - v0 += v4 - v12 ^= v0 - v12 = v12<<(32-8) | v12>>8 - v8 += v12 - v4 ^= v8 - v4 = v4<<(32-7) | v4>>7 - v0 += m[2] ^ cst6 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-16) | v15>>16 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-12) | v5>>12 - v1 += m[5] ^ cst10 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-16) | v12>>16 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-12) | v6>>12 - v2 += m[4] ^ cst0 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-16) | v13>>16 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-12) | v7>>12 - v3 += m[15] ^ cst8 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-16) | v14>>16 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-12) | v4>>12 - v2 += m[0] ^ cst4 - v2 += v7 - v13 ^= v2 - v13 = v13<<(32-8) | v13>>8 - v8 += v13 - v7 ^= v8 - v7 = v7<<(32-7) | v7>>7 - v3 += m[8] ^ cst15 - v3 += v4 - v14 ^= v3 - v14 = v14<<(32-8) | v14>>8 - v9 += v14 - v4 ^= v9 - v4 = v4<<(32-7) | v4>>7 - v1 += m[10] ^ cst5 - v1 += v6 - v12 ^= v1 - v12 = v12<<(32-8) | v12>>8 - v11 += v12 - v6 ^= v11 - v6 = v6<<(32-7) | v6>>7 - v0 += m[6] ^ cst2 - v0 += v5 - v15 ^= v0 - v15 = v15<<(32-8) | v15>>8 - v10 += v15 - v5 ^= v10 - v5 = v5<<(32-7) | v5>>7 - - h0 ^= v0 ^ v8 ^ s0 - h1 ^= v1 ^ v9 ^ s1 - h2 ^= v2 ^ v10 ^ s2 - h3 ^= v3 ^ v11 ^ s3 - h4 ^= v4 ^ v12 ^ s0 - h5 ^= v5 ^ v13 ^ s1 - h6 ^= v6 ^ v14 ^ s2 - h7 ^= v7 ^ v15 ^ s3 - - p = p[BlockSize:] - } - d.h[0], d.h[1], d.h[2], d.h[3], d.h[4], d.h[5], d.h[6], d.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 -} diff --git a/vendor/github.com/deroproject/graviton/cursor.go b/vendor/github.com/deroproject/graviton/cursor.go index f11d4c8..53b5c7f 100644 --- a/vendor/github.com/deroproject/graviton/cursor.go +++ b/vendor/github.com/deroproject/graviton/cursor.go @@ -10,7 +10,8 @@ type Cursor struct { tree *Tree node_path []*inner - left []bool + left []bool // it basically represents the path as bools + } // get Cursor which is used as an iterator that can traverse over all key/value pairs in a tree in hash sorted order. diff --git a/vendor/github.com/deroproject/graviton/extra.go b/vendor/github.com/deroproject/graviton/extra.go index 3f31828..84094d9 100644 --- a/vendor/github.com/deroproject/graviton/extra.go +++ b/vendor/github.com/deroproject/graviton/extra.go @@ -4,6 +4,10 @@ import "fmt" import "math" import "crypto/rand" + +func (t *Tree) GetName() (string) { + return t.treename +} // Random returns a random key,value from the tree, provided a tree has keys // the following are limitations // a tree containing 0 key, value pairs will return err diff --git a/vendor/github.com/deroproject/graviton/go.mod b/vendor/github.com/deroproject/graviton/go.mod new file mode 100644 index 0000000..e0c388a --- /dev/null +++ b/vendor/github.com/deroproject/graviton/go.mod @@ -0,0 +1,3 @@ +module github.com/deroproject/graviton + +go 1.14 diff --git a/vendor/github.com/deroproject/graviton/special.go b/vendor/github.com/deroproject/graviton/special.go index 3ec5430..de99784 100644 --- a/vendor/github.com/deroproject/graviton/special.go +++ b/vendor/github.com/deroproject/graviton/special.go @@ -3,11 +3,30 @@ package graviton //import "io" //import "math" +import "fmt" +import "bytes" import "golang.org/x/xerrors" +// this file contains some functions ( to extend read-only api). these apis are used in the dero blockchain. + +func Sum(key []byte) [HASHSIZE]byte { + return sum(key) +} + +// we have a key and need to get both the key,value +func (t *Tree) GetKeyValueFromKey(key []byte) (int, []byte, []byte, error) { + return t.root.GetKeyValue(t.store, sum(key), 256, 0) +} + // we only have a keyhash and need to get both the key,value -func (t *Tree) GetKeyValue(keyhash [HASHSIZE]byte) (int, []byte, []byte, error) { - return t.root.GetKeyValue(t.store, keyhash, 256, 0) +func (t *Tree) GetKeyValueFromHash(keyhashc []byte) (int, []byte, []byte, error) { + var keyhash [HASHSIZE]byte + if len(keyhashc) <= 0 || len(keyhashc) > HASHSIZE { + return 0, nil, nil, fmt.Errorf("keyhashc must be atleast 1 byte and less than 33 bytes, len=%d", len(keyhashc)) + } + copy(keyhash[:], keyhashc) + + return t.root.GetKeyValue(t.store, keyhash, len(keyhashc)*8, 0) } func (in *inner) GetKeyValue(store *Store, keyhash [HASHSIZE]byte, valid_bit_count, used_bit_count int) (int, []byte, []byte, error) { @@ -54,9 +73,69 @@ func (l *leaf) GetKeyValue(store *Store, keyhash [HASHSIZE]byte, valid_bit_count } } - if l.keyhash == keyhash { + if bytes.Compare(l.keyhash[:valid_bit_count/8], keyhash[:valid_bit_count/8]) == 0 { return used_bit_count, l.key, l.value, nil } return used_bit_count, nil, nil, xerrors.Errorf("%w: collision, keyhash %x not found", ErrNotFound, keyhash) } + +// sets a root for the cursor, so the cursor visits only a specific prefix keys +func (c *Cursor) SpecialFirst(section []byte, validbits uint) (k, v []byte, err error) { + loop_node := node(c.tree.root) // we always start at root node + + donebits := uint(0) + + if validbits >= 256 { + err = fmt.Errorf("invalid valid bits %d", validbits) + return + } + + if validbits == 0 { + return c.First() + } + + // the function is iterative and not recursive + for { + switch node := loop_node.(type) { + case *inner: + if node.loaded_partial { // if node is loaded partially, load it fully now + if err = node.loadinnerfromstore(c.tree.store); err != nil { + return + } + } + + left, right := node.left, node.right + if isBitSet(section, donebits) { // 1 is right + if right == nil { + err = ErrNoMoreKeys + return + } + loop_node = right + } else { //0 is left + if left == nil { + err = ErrNoMoreKeys + return + } + loop_node = left + } + donebits++ + + if donebits < validbits { + continue + } else if donebits == validbits { + return c.next_internal(loop_node, false) + } + + // we can only reach here if a tree has both left,right nil, ie an empty tree + err = ErrNoMoreKeys + return + + case *leaf: + err = ErrNoMoreKeys + return + default: + return k, v, fmt.Errorf("unknown node type, corruption") + } + } +} diff --git a/vendor/github.com/dgraph-io/badger/.travis.yml b/vendor/github.com/dgraph-io/badger/.travis.yml deleted file mode 100644 index c942e02..0000000 --- a/vendor/github.com/dgraph-io/badger/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: go -go: 1.9 -matrix: - include: - - os: osx -notifications: - email: false - slack: - secure: X7uBLWYbuUhf8QFE16CoS5z7WvFR8EN9j6cEectMW6mKZ3vwXGwVXRIPsgUq/606DsQdCCx34MR8MRWYGlu6TBolbSe9y0EP0i46yipPz22YtuT7umcVUbGEyx8MZKgG0v1u/zA0O4aCsOBpGAA3gxz8h3JlEHDt+hv6U8xRsSllVLzLSNb5lwxDtcfEDxVVqP47GMEgjLPM28Pyt5qwjk7o5a4YSVzkfdxBXxd3gWzFUWzJ5E3cTacli50dK4GVfiLcQY2aQYoYO7AAvDnvP+TPfjDkBlUEE4MUz5CDIN51Xb+WW33sX7g+r3Bj7V5IRcF973RiYkpEh+3eoiPnyWyxhDZBYilty3b+Hysp6d4Ov/3I3ll7Bcny5+cYjakjkMH3l9w3gs6Y82GlpSLSJshKWS8vPRsxFe0Pstj6QSJXTd9EBaFr+l1ScXjJv/Sya9j8N9FfTuOTESWuaL1auX4Y7zEEVHlA8SCNOO8K0eTfxGZnC/YcIHsR8rePEAcFxfOYQppkyLF/XvAtnb/LMUuu0g4y2qNdme6Oelvyar1tFEMRtbl4mRCdu/krXBFtkrsfUaVY6WTPdvXAGotsFJ0wuA53zGVhlcd3+xAlSlR3c1QX95HIMeivJKb5L4nTjP+xnrmQNtnVk+tG4LSH2ltuwcZSSczModtcBmRefrk= - -env: - global: - - secure: CRkV2+/jlO0gXzzS50XGxfMS117FNwiVjxNY/LeWq06RKD+dDCPxTJl3JCNe3l0cYEPAglV2uMMYukDiTqJ7e+HI4nh4N4mv6lwx39N8dAvJe1x5ITS2T4qk4kTjuQb1Q1vw/ZOxoQqmvNKj2uRmBdJ/HHmysbRJ1OzCWML3OXdUwJf0AYlJzTjpMfkOKr7sTtE4rwyyQtd4tKH1fGdurgI9ZuFd9qvYxK2qcJhsQ6CNqMXt+7FkVkN1rIPmofjjBTNryzUr4COFXuWH95aDAif19DeBW4lbNgo1+FpDsrgmqtuhl6NAuptI8q/imow2KXBYJ8JPXsxW8DVFj0IIp0RCd3GjaEnwBEbxAyiIHLfW7AudyTS/dJOvZffPqXnuJ8xj3OPIdNe4xY0hWl8Ju2HhKfLOAHq7VadHZWd3IHLil70EiL4/JLD1rNbMImUZisFaA8pyrcIvYYebjOnk4TscwKFLedClRSX1XsMjWWd0oykQtrdkHM2IxknnBpaLu7mFnfE07f6dkG0nlpyu4SCLey7hr5FdcEmljA0nIxTSYDg6035fQkBEAbe7hlESOekkVNT9IZPwG+lmt3vU4ofi6NqNbJecOuSB+h36IiZ9s4YQtxYNnLgW14zjuFGGyT5smc3IjBT7qngDjKIgyrSVoRkY/8udy9qbUgvBeW8= - -before_script: -- go get github.com/mattn/goveralls -script: -- bash contrib/cover.sh $HOME/build coverage.out || travis_terminate 1 -- goveralls -service=travis-ci -coverprofile=coverage.out || true -- goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/vendor/github.com/dgraph-io/badger/CHANGELOG.md b/vendor/github.com/dgraph-io/badger/CHANGELOG.md deleted file mode 100644 index abab551..0000000 --- a/vendor/github.com/dgraph-io/badger/CHANGELOG.md +++ /dev/null @@ -1,69 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [1.5.0] - 2018-05-08 -* Introduce `NumVersionsToKeep` option. This option is used to discard many - versions of the same key, which saves space. -* Add a new `SetWithDiscard` method, which would indicate that all the older - versions of the key are now invalid. Those versions would be discarded during - compactions. -* Value log GC moves are now bound to another keyspace to ensure latest versions - of data are always at the top in LSM tree. -* Introduce `ValueLogMaxEntries` to restrict the number of key-value pairs per - value log file. This helps bound the time it takes to garbage collect one - file. - -## [1.4.0] - 2018-05-04 -* Make mmap-ing of value log optional. -* Run GC multiple times, based on recorded discard statistics. -* Add MergeOperator. -* Force compact L0 on clsoe (#439). -* Add truncate option to warn about data loss (#452). -* Discard key versions during compaction (#464). -* Introduce new `LSMOnlyOptions`, to make Badger act like a typical LSM based DB. - -Bug fix: -* [Temporary] Check max version across all tables in Get (removed in next - release). -* Update commit and read ts while loading from backup. -* Ensure all transaction entries are part of the same value log file. -* On commit, run unlock callbacks before doing writes (#413). -* Wait for goroutines to finish before closing iterators (#421). - -## [1.3.0] - 2017-12-12 -* Add `DB.NextSequence()` method to generate monotonically increasing integer - sequences. -* Add `DB.Size()` method to return the size of LSM and value log files. -* Tweaked mmap code to make Windows 32-bit builds work. -* Tweaked build tags on some files to make iOS builds work. -* Fix `DB.PurgeOlderVersions()` to not violate some constraints. - -## [1.2.0] - 2017-11-30 -* Expose a `Txn.SetEntry()` method to allow setting the key-value pair - and all the metadata at the same time. - -## [1.1.1] - 2017-11-28 -* Fix bug where txn.Get was returing key deleted in same transaction. -* Fix race condition while decrementing reference in oracle. -* Update doneCommit in the callback for CommitAsync. -* Iterator see writes of current txn. - -## [1.1.0] - 2017-11-13 -* Create Badger directory if it does not exist when `badger.Open` is called. -* Added `Item.ValueCopy()` to avoid deadlocks in long-running iterations -* Fixed 64-bit alignment issues to make Badger run on Arm v7 - -## [1.0.1] - 2017-11-06 -* Fix an uint16 overflow when resizing key slice - -[Unreleased]: https://github.com/dgraph-io/badger/compare/v1.3.0...HEAD -[1.3.0]: https://github.com/dgraph-io/badger/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/dgraph-io/badger/compare/v1.1.1...v1.2.0 -[1.1.1]: https://github.com/dgraph-io/badger/compare/v1.1.0...v1.1.1 -[1.1.0]: https://github.com/dgraph-io/badger/compare/v1.0.1...v1.1.0 -[1.0.1]: https://github.com/dgraph-io/badger/compare/v1.0.0...v1.0.1 diff --git a/vendor/github.com/dgraph-io/badger/LICENSE b/vendor/github.com/dgraph-io/badger/LICENSE deleted file mode 100644 index d9a10c0..0000000 --- a/vendor/github.com/dgraph-io/badger/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/dgraph-io/badger/README.md b/vendor/github.com/dgraph-io/badger/README.md deleted file mode 100644 index 083cb4c..0000000 --- a/vendor/github.com/dgraph-io/badger/README.md +++ /dev/null @@ -1,619 +0,0 @@ -# BadgerDB [![GoDoc](https://godoc.org/github.com/dgraph-io/badger?status.svg)](https://godoc.org/github.com/dgraph-io/badger) [![Go Report Card](https://goreportcard.com/badge/github.com/dgraph-io/badger)](https://goreportcard.com/report/github.com/dgraph-io/badger) [![Build Status](https://teamcity.dgraph.io/guestAuth/app/rest/builds/buildType:(id:Badger_UnitTests)/statusIcon.svg)](https://teamcity.dgraph.io/viewLog.html?buildTypeId=Badger_UnitTests&buildId=lastFinished&guest=1) ![Appveyor](https://ci.appveyor.com/api/projects/status/github/dgraph-io/badger?branch=master&svg=true) [![Coverage Status](https://coveralls.io/repos/github/dgraph-io/badger/badge.svg?branch=master)](https://coveralls.io/github/dgraph-io/badger?branch=master) - -![Badger mascot](images/diggy-shadow.png) - -BadgerDB is an embeddable, persistent, simple and fast key-value (KV) database -written in pure Go. It's meant to be a performant alternative to non-Go-based -key-value stores like [RocksDB](https://github.com/facebook/rocksdb). - -## Project Status -Badger v1.0 was released in Nov 2017. Check the [Changelog] for the full details. - -[Changelog]:https://github.com/dgraph-io/badger/blob/master/CHANGELOG.md - -We introduced transactions in [v0.9.0] which involved a major API change. If you have a Badger -datastore prior to that, please use [v0.8.1], but we strongly urge you to upgrade. Upgrading from -both v0.8 and v0.9 will require you to [take backups](#database-backup) and restore using the new -version. - -[v1.0.1]: //github.com/dgraph-io/badger/tree/v1.0.1 -[v0.8.1]: //github.com/dgraph-io/badger/tree/v0.8.1 -[v0.9.0]: //github.com/dgraph-io/badger/tree/v0.9.0 - -## Table of Contents - * [Getting Started](#getting-started) - + [Installing](#installing) - + [Opening a database](#opening-a-database) - + [Transactions](#transactions) - - [Read-only transactions](#read-only-transactions) - - [Read-write transactions](#read-write-transactions) - - [Managing transactions manually](#managing-transactions-manually) - + [Using key/value pairs](#using-keyvalue-pairs) - + [Monotonically increasing integers](#monotonically-increasing-integers) - * [Merge Operations](#merge-operations) - + [Setting Time To Live(TTL) and User Metadata on Keys](#setting-time-to-livettl-and-user-metadata-on-keys) - + [Iterating over keys](#iterating-over-keys) - - [Prefix scans](#prefix-scans) - - [Key-only iteration](#key-only-iteration) - + [Garbage Collection](#garbage-collection) - + [Database backup](#database-backup) - + [Memory usage](#memory-usage) - + [Statistics](#statistics) - * [Resources](#resources) - + [Blog Posts](#blog-posts) - * [Contact](#contact) - * [Design](#design) - + [Comparisons](#comparisons) - + [Benchmarks](#benchmarks) - * [Other Projects Using Badger](#other-projects-using-badger) - * [Frequently Asked Questions](#frequently-asked-questions) - -## Getting Started - -### Installing -To start using Badger, install Go 1.8 or above and run `go get`: - -```sh -$ go get github.com/dgraph-io/badger/... -``` - -This will retrieve the library and install the `badger_info` command line -utility into your `$GOBIN` path. - - -### Opening a database -The top-level object in Badger is a `DB`. It represents multiple files on disk -in specific directories, which contain the data for a single database. - -To open your database, use the `badger.Open()` function, with the appropriate -options. The `Dir` and `ValueDir` options are mandatory and must be -specified by the client. They can be set to the same value to simplify things. - -```go -package main - -import ( - "log" - - "github.com/dgraph-io/badger" -) - -func main() { - // Open the Badger database located in the /tmp/badger directory. - // It will be created if it doesn't exist. - opts := badger.DefaultOptions - opts.Dir = "/tmp/badger" - opts.ValueDir = "/tmp/badger" - db, err := badger.Open(opts) - if err != nil { - log.Fatal(err) - } - defer db.Close() -  // Your code here… -} -``` - -Please note that Badger obtains a lock on the directories so multiple processes -cannot open the same database at the same time. - -### Transactions - -#### Read-only transactions -To start a read-only transaction, you can use the `DB.View()` method: - -```go -err := db.View(func(txn *badger.Txn) error { -  // Your code here… -  return nil -}) -``` - -You cannot perform any writes or deletes within this transaction. Badger -ensures that you get a consistent view of the database within this closure. Any -writes that happen elsewhere after the transaction has started, will not be -seen by calls made within the closure. - -#### Read-write transactions -To start a read-write transaction, you can use the `DB.Update()` method: - -```go -err := db.Update(func(txn *badger.Txn) error { -  // Your code here… -  return nil -}) -``` - -All database operations are allowed inside a read-write transaction. - -Always check the returned error value. If you return an error -within your closure it will be passed through. - -An `ErrConflict` error will be reported in case of a conflict. Depending on the state -of your application, you have the option to retry the operation if you receive -this error. - -An `ErrTxnTooBig` will be reported in case the number of pending writes/deletes in -the transaction exceed a certain limit. In that case, it is best to commit the -transaction and start a new transaction immediately. Here is an example (we are -not checking for errors in some places for simplicity): - -```go -updates := make(map[string]string) -txn := db.NewTransaction(true) -for k,v := range updates { - if err := txn.Set([]byte(k),[]byte(v)); err == ErrTxnTooBig { - _ = txn.Commit() - txn = db.NewTransaction(..) - _ = txn.Set([]byte(k),[]byte(v)) - } -} -_ = txn.Commit() -``` - -#### Managing transactions manually -The `DB.View()` and `DB.Update()` methods are wrappers around the -`DB.NewTransaction()` and `Txn.Commit()` methods (or `Txn.Discard()` in case of -read-only transactions). These helper methods will start the transaction, -execute a function, and then safely discard your transaction if an error is -returned. This is the recommended way to use Badger transactions. - -However, sometimes you may want to manually create and commit your -transactions. You can use the `DB.NewTransaction()` function directly, which -takes in a boolean argument to specify whether a read-write transaction is -required. For read-write transactions, it is necessary to call `Txn.Commit()` -to ensure the transaction is committed. For read-only transactions, calling -`Txn.Discard()` is sufficient. `Txn.Commit()` also calls `Txn.Discard()` -internally to cleanup the transaction, so just calling `Txn.Commit()` is -sufficient for read-write transaction. However, if your code doesn’t call -`Txn.Commit()` for some reason (for e.g it returns prematurely with an error), -then please make sure you call `Txn.Discard()` in a `defer` block. Refer to the -code below. - -```go -// Start a writable transaction. -txn, err := db.NewTransaction(true) -if err != nil { - return err -} -defer txn.Discard() - -// Use the transaction... -err := txn.Set([]byte("answer"), []byte("42")) -if err != nil { - return err -} - -// Commit the transaction and check for error. -if err := txn.Commit(nil); err != nil { - return err -} -``` - -The first argument to `DB.NewTransaction()` is a boolean stating if the transaction -should be writable. - -Badger allows an optional callback to the `Txn.Commit()` method. Normally, the -callback can be set to `nil`, and the method will return after all the writes -have succeeded. However, if this callback is provided, the `Txn.Commit()` -method returns as soon as it has checked for any conflicts. The actual writing -to the disk happens asynchronously, and the callback is invoked once the -writing has finished, or an error has occurred. This can improve the throughput -of the application in some cases. But it also means that a transaction is not -durable until the callback has been invoked with a `nil` error value. - -### Using key/value pairs -To save a key/value pair, use the `Txn.Set()` method: - -```go -err := db.Update(func(txn *badger.Txn) error { - err := txn.Set([]byte("answer"), []byte("42")) - return err -}) -``` - -This will set the value of the `"answer"` key to `"42"`. To retrieve this -value, we can use the `Txn.Get()` method: - -```go -err := db.View(func(txn *badger.Txn) error { - item, err := txn.Get([]byte("answer")) - if err != nil { - return err - } - val, err := item.Value() - if err != nil { - return err - } - fmt.Printf("The answer is: %s\n", val) - return nil -}) -``` - -`Txn.Get()` returns `ErrKeyNotFound` if the value is not found. - -Please note that values returned from `Get()` are only valid while the -transaction is open. If you need to use a value outside of the transaction -then you must use `copy()` to copy it to another byte slice. - -Use the `Txn.Delete()` method to delete a key. - -### Monotonically increasing integers - -To get unique monotonically increasing integers with strong durability, you can -use the `DB.GetSequence` method. This method returns a `Sequence` object, which -is thread-safe and can be used concurrently via various goroutines. - -Badger would lease a range of integers to hand out from memory, with the -bandwidth provided to `DB.GetSequence`. The frequency at which disk writes are -done is determined by this lease bandwidth and the frequency of `Next` -invocations. Setting a bandwith too low would do more disk writes, setting it -too high would result in wasted integers if Badger is closed or crashes. -To avoid wasted integers, call `Release` before closing Badger. - -```go -seq, err := db.GetSequence(key, 1000) -defer seq.Release() -for { - num, err := seq.Next() -} -``` - -### Merge Operations -Badger provides support for unordered merge operations. You can define a func -of type `MergeFunc` which takes in an existing value, and a value to be -_merged_ with it. It returns a new value which is the result of the _merge_ -operation. All values are specified in byte arrays. For e.g., here is a merge -function (`add`) which adds a `uint64` value to an existing `uint64` value. - -```Go -uint64ToBytes(i uint64) []byte { - var buf [8]byte - binary.BigEndian.PutUint64(buf[:], i) - return buf[:] -} - -func bytesToUint64(b []byte) uint64 { - return binary.BigEndian.Uint64(b) -} - -// Merge function to add two uint64 numbers -func add(existing, new []byte) []byte { - return uint64ToBytes(bytesToUint64(existing) + bytesToUint64(new)) -} -``` - -This function can then be passed to the `DB.GetMergeOperator()` method, along -with a key, and a duration value. The duration specifies how often the merge -function is run on values that have been added using the `MergeOperator.Add()` -method. - -`MergeOperator.Get()` method can be used to retrieve the cumulative value of the key -associated with the merge operation. - -```Go -key := []byte("merge") -m := db.GetMergeOperator(key, add, 200*time.Millisecond) -defer m.Stop() - -m.Add(uint64ToBytes(1)) -m.Add(uint64ToBytes(2)) -m.Add(uint64ToBytes(3)) - -res, err := m.Get() // res should have value 6 encoded -fmt.Println(bytesToUint64(res)) -``` - -### Setting Time To Live(TTL) and User Metadata on Keys -Badger allows setting an optional Time to Live (TTL) value on keys. Once the TTL has -elapsed, the key will no longer be retrievable and will be eligible for garbage -collection. A TTL can be set as a `time.Duration` value using the `Txn.SetWithTTL()` -API method. - -An optional user metadata value can be set on each key. A user metadata value -is represented by a single byte. It can be used to set certain bits along -with the key to aid in interpreting or decoding the key-value pair. User -metadata can be set using the `Txn.SetWithMeta()` API method. - -`Txn.SetEntry()` can be used to set the key, value, user metatadata and TTL, -all at once. - -### Iterating over keys -To iterate over keys, we can use an `Iterator`, which can be obtained using the -`Txn.NewIterator()` method. Iteration happens in byte-wise lexicographical sorting -order. - - -```go -err := db.View(func(txn *badger.Txn) error { - opts := badger.DefaultIteratorOptions - opts.PrefetchSize = 10 - it := txn.NewIterator(opts) - defer it.Close() - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - k := item.Key() - v, err := item.Value() - if err != nil { - return err - } - fmt.Printf("key=%s, value=%s\n", k, v) - } - return nil -}) -``` - -The iterator allows you to move to a specific point in the list of keys and move -forward or backward through the keys one at a time. - -By default, Badger prefetches the values of the next 100 items. You can adjust -that with the `IteratorOptions.PrefetchSize` field. However, setting it to -a value higher than GOMAXPROCS (which we recommend to be 128 or higher) -shouldn’t give any additional benefits. You can also turn off the fetching of -values altogether. See section below on key-only iteration. - -#### Prefix scans -To iterate over a key prefix, you can combine `Seek()` and `ValidForPrefix()`: - -```go -db.View(func(txn *badger.Txn) error { - it := txn.NewIterator(badger.DefaultIteratorOptions) - defer it.Close() - prefix := []byte("1234") - for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { - item := it.Item() - k := item.Key() - v, err := item.Value() - if err != nil { - return err - } - fmt.Printf("key=%s, value=%s\n", k, v) - } - return nil -}) -``` - -#### Key-only iteration -Badger supports a unique mode of iteration called _key-only_ iteration. It is -several order of magnitudes faster than regular iteration, because it involves -access to the LSM-tree only, which is usually resident entirely in RAM. To -enable key-only iteration, you need to set the `IteratorOptions.PrefetchValues` -field to `false`. This can also be used to do sparse reads for selected keys -during an iteration, by calling `item.Value()` only when required. - -```go -err := db.View(func(txn *badger.Txn) error { - opts := badger.DefaultIteratorOptions - opts.PrefetchValues = false - it := txn.NewIterator(opts) - defer it.Close() - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - k := item.Key() - fmt.Printf("key=%s\n", k) - } - return nil -}) -``` - -### Garbage Collection -Badger values need to be garbage collected, because of two reasons: - -* Badger keeps values separately from the LSM tree. This means that the compaction operations -that clean up the LSM tree do not touch the values at all. Values need to be cleaned up -separately. - -* Concurrent read/write transactions could leave behind multiple values for a single key, because they -are stored with different versions. These could accumulate, and take up unneeded space beyond the -time these older versions are needed. - -Badger relies on the client to perform garbage collection at a time of their choosing. It provides -the following methods, which can be invoked at an appropriate time: - -* `DB.PurgeOlderVersions()`: Is no longer needed since v1.5.0. Badger's LSM tree automatically discards older/invalid versions of keys. -* `DB.RunValueLogGC()`: This method is designed to do garbage collection while - Badger is online. Along with randomly picking a file, it uses statistics generated by the - LSM-tree compactions to pick files that are likely to lead to maximum space - reclamation. - - It is recommended that this method be called regularly. - -### Database backup -There are two public API methods `DB.Backup()` and `DB.Load()` which can be -used to do online backups and restores. Badger v0.9 provides a CLI tool -`badger`, which can do offline backup/restore. Make sure you have `$GOPATH/bin` -in your PATH to use this tool. - -The command below will create a version-agnostic backup of the database, to a -file `badger.bak` in the current working directory - -``` -badger backup --dir -``` - -To restore `badger.bak` in the current working directory to a new database: - -``` -badger restore --dir -``` - -See `badger --help` for more details. - -If you have a Badger database that was created using v0.8 (or below), you can -use the `badger_backup` tool provided in v0.8.1, and then restore it using the -command above to upgrade your database to work with the latest version. - -``` -badger_backup --dir --backup-file badger.bak -``` - -### Memory usage -Badger's memory usage can be managed by tweaking several options available in -the `Options` struct that is passed in when opening the database using -`DB.Open`. - -- `Options.ValueLogLoadingMode` can be set to `options.FileIO` (instead of the - default `options.MemoryMap`) to avoid memory-mapping log files. This can be - useful in environments with low RAM. -- Number of memtables (`Options.NumMemtables`) - - If you modify `Options.NumMemtables`, also adjust `Options.NumLevelZeroTables` and - `Options.NumLevelZeroTablesStall` accordingly. -- Number of concurrent compactions (`Options.NumCompactors`) -- Mode in which LSM tree is loaded (`Options.TableLoadingMode`) -- Size of table (`Options.MaxTableSize`) -- Size of value log file (`Options.ValueLogFileSize`) - -If you want to decrease the memory usage of Badger instance, tweak these -options (ideally one at a time) until you achieve the desired -memory usage. - -### Statistics -Badger records metrics using the [expvar] package, which is included in the Go -standard library. All the metrics are documented in [y/metrics.go][metrics] -file. - -`expvar` package adds a handler in to the default HTTP server (which has to be -started explicitly), and serves up the metrics at the `/debug/vars` endpoint. -These metrics can then be collected by a system like [Prometheus], to get -better visibility into what Badger is doing. - -[expvar]: https://golang.org/pkg/expvar/ -[metrics]: https://github.com/dgraph-io/badger/blob/master/y/metrics.go -[Prometheus]: https://prometheus.io/ - -## Resources - -### Blog Posts -1. [Introducing Badger: A fast key-value store written natively in -Go](https://open.dgraph.io/post/badger/) -2. [Make Badger crash resilient with ALICE](https://blog.dgraph.io/post/alice/) -3. [Badger vs LMDB vs BoltDB: Benchmarking key-value databases in Go](https://blog.dgraph.io/post/badger-lmdb-boltdb/) -4. [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/) - -## Design -Badger was written with these design goals in mind: - -- Write a key-value database in pure Go. -- Use latest research to build the fastest KV database for data sets spanning terabytes. -- Optimize for SSDs. - -Badger’s design is based on a paper titled _[WiscKey: Separating Keys from -Values in SSD-conscious Storage][wisckey]_. - -[wisckey]: https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf - -### Comparisons -| Feature | Badger | RocksDB | BoltDB | -| ------- | ------ | ------- | ------ | -| Design | LSM tree with value log | LSM tree only | B+ tree | -| High Read throughput | Yes | No | Yes | -| High Write throughput | Yes | Yes | No | -| Designed for SSDs | Yes (with latest research 1) | Not specifically 2 | No | -| Embeddable | Yes | Yes | Yes | -| Sorted KV access | Yes | Yes | Yes | -| Pure Go (no Cgo) | Yes | No | Yes | -| Transactions | Yes, ACID, concurrent with SSI3 | Yes (but non-ACID) | Yes, ACID | -| Snapshots | Yes | Yes | Yes | -| TTL support | Yes | Yes | No | - -1 The [WISCKEY paper][wisckey] (on which Badger is based) saw big -wins with separating values from keys, significantly reducing the write -amplification compared to a typical LSM tree. - -2 RocksDB is an SSD optimized version of LevelDB, which was designed specifically for rotating disks. -As such RocksDB's design isn't aimed at SSDs. - -3 SSI: Serializable Snapshot Isolation. For more details, see the blog post [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/) - -### Benchmarks -We have run comprehensive benchmarks against RocksDB, Bolt and LMDB. The -benchmarking code, and the detailed logs for the benchmarks can be found in the -[badger-bench] repo. More explanation, including graphs can be found the blog posts (linked -above). - -[badger-bench]: https://github.com/dgraph-io/badger-bench - -## Other Projects Using Badger -Below is a list of known projects that use Badger: - -* [0-stor](https://github.com/zero-os/0-stor) - Single device object store. -* [Dgraph](https://github.com/dgraph-io/dgraph) - Distributed graph database. -* [Sandglass](https://github.com/celrenheit/sandglass) - distributed, horizontally scalable, persistent, time sorted message queue. -* [Usenet Express](https://usenetexpress.com/) - Serving over 300TB of data with Badger. -* [go-ipfs](https://github.com/ipfs/go-ipfs) - Go client for the InterPlanetary File System (IPFS), a new hypermedia distribution protocol. -* [gorush](https://github.com/appleboy/gorush) - A push notification server written in Go. - -If you are using Badger in a project please send a pull request to add it to the list. - -## Frequently Asked Questions -- **My writes are getting stuck. Why?** - -This can happen if a long running iteration with `Prefetch` is set to false, but -a `Item::Value` call is made internally in the loop. That causes Badger to -acquire read locks over the value log files to avoid value log GC removing the -file from underneath. As a side effect, this also blocks a new value log GC -file from being created, when the value log file boundary is hit. - -Please see Github issues [#293](https://github.com/dgraph-io/badger/issues/293) -and [#315](https://github.com/dgraph-io/badger/issues/315). - -There are multiple workarounds during iteration: - -1. Use `Item::ValueCopy` instead of `Item::Value` when retrieving value. -1. Set `Prefetch` to true. Badger would then copy over the value and release the - file lock immediately. -1. When `Prefetch` is false, don't call `Item::Value` and do a pure key-only - iteration. This might be useful if you just want to delete a lot of keys. -1. Do the writes in a separate transaction after the reads. - -- **My writes are really slow. Why?** - -Are you creating a new transaction for every single key update? This will lead -to very low throughput. To get best write performance, batch up multiple writes -inside a transaction using single `DB.Update()` call. You could also have -multiple such `DB.Update()` calls being made concurrently from multiple -goroutines. - -- **I don't see any disk write. Why?** - -If you're using Badger with `SyncWrites=false`, then your writes might not be written to value log -and won't get synced to disk immediately. Writes to LSM tree are done inmemory first, before they -get compacted to disk. The compaction would only happen once `MaxTableSize` has been reached. So, if -you're doing a few writes and then checking, you might not see anything on disk. Once you `Close` -the database, you'll see these writes on disk. - -- **Reverse iteration doesn't give me the right results.** - -Just like forward iteration goes to the first key which is equal or greater than the SEEK key, reverse iteration goes to the first key which is equal or lesser than the SEEK key. Therefore, SEEK key would not be part of the results. You can typically add a tilde (~) as a suffix to the SEEK key to include it in the results. See the following issues: [#436](https://github.com/dgraph-io/badger/issues/436) and [#347](https://github.com/dgraph-io/badger/issues/347). - -- **Which instances should I use for Badger?** - -We recommend using instances which provide local SSD storage, without any limit -on the maximum IOPS. In AWS, these are storage optimized instances like i3. They -provide local SSDs which clock 100K IOPS over 4KB blocks easily. - -- **I'm getting a closed channel error. Why?** - -``` -panic: close of closed channel -panic: send on closed channel -``` - -If you're seeing panics like above, this would be because you're operating on a closed DB. This can happen, if you call `Close()` before sending a write, or multiple times. You should ensure that you only call `Close()` once, and all your read/write operations finish before closing. - -- **Are there any Go specific settings that I should use?** - -We *highly* recommend setting a high number for GOMAXPROCS, which allows Go to -observe the full IOPS throughput provided by modern SSDs. In Dgraph, we have set -it to 128. For more details, [see this -thread](https://groups.google.com/d/topic/golang-nuts/jPb_h3TvlKE/discussion). - -- **Are there any linux specific settings that I should use?** - -We recommend setting max file descriptors to a high number depending upon the expected size of you data. - -## Contact -- Please use [discuss.dgraph.io](https://discuss.dgraph.io) for questions, feature requests and discussions. -- Please use [Github issue tracker](https://github.com/dgraph-io/badger/issues) for filing bugs or feature requests. -- Join [![Slack Status](http://slack.dgraph.io/badge.svg)](http://slack.dgraph.io). -- Follow us on Twitter [@dgraphlabs](https://twitter.com/dgraphlabs). - diff --git a/vendor/github.com/dgraph-io/badger/appveyor.yml b/vendor/github.com/dgraph-io/badger/appveyor.yml deleted file mode 100644 index 79dac33..0000000 --- a/vendor/github.com/dgraph-io/badger/appveyor.yml +++ /dev/null @@ -1,48 +0,0 @@ -# version format -version: "{build}" - -# Operating system (build VM template) -os: Windows Server 2012 R2 - -# Platform. -platform: x64 - -clone_folder: c:\gopath\src\github.com\dgraph-io\badger - -# Environment variables -environment: - GOVERSION: 1.8.3 - GOPATH: c:\gopath - -# scripts that run after cloning repository -install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - go version - - go env - - python --version - -# To run your custom scripts instead of automatic MSBuild -build_script: - # We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648 - - ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)' - - cd c:\gopath\src\github.com\dgraph-io\badger - - git branch - - go get -t ./... - -# To run your custom scripts instead of automatic tests -test_script: - # Unit tests - - ps: Add-AppveyorTest "Unit Tests" -Outcome Running - - go test -v github.com/dgraph-io/badger/... - - go test -v -vlog_mmap=false github.com/dgraph-io/badger/... - - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed - -notifications: - - provider: Email - to: - - pawan@dgraph.io - on_build_failure: true - on_build_status_changed: true -# to disable deployment -deploy: off - diff --git a/vendor/github.com/dgraph-io/badger/backup.go b/vendor/github.com/dgraph-io/badger/backup.go deleted file mode 100644 index 4cd4de2..0000000 --- a/vendor/github.com/dgraph-io/badger/backup.go +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "bufio" - "encoding/binary" - "io" - "log" - "sync" - - "github.com/dgraph-io/badger/y" - - "github.com/dgraph-io/badger/protos" -) - -func writeTo(entry *protos.KVPair, w io.Writer) error { - if err := binary.Write(w, binary.LittleEndian, uint64(entry.Size())); err != nil { - return err - } - buf, err := entry.Marshal() - if err != nil { - return err - } - _, err = w.Write(buf) - return err -} - -// Backup dumps a protobuf-encoded list of all entries in the database into the -// given writer, that are newer than the specified version. It returns a -// timestamp indicating when the entries were dumped which can be passed into a -// later invocation to generate an incremental dump, of entries that have been -// added/modified since the last invocation of DB.Backup() -// -// This can be used to backup the data in a database at a given point in time. -func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { - var tsNew uint64 - err := db.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - it := txn.NewIterator(opts) - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - if item.Version() < since { - // Ignore versions less than given timestamp - continue - } - val, err := item.Value() - if err != nil { - log.Printf("Key [%x]. Error while fetching value [%v]\n", item.Key(), err) - continue - } - - entry := &protos.KVPair{ - Key: y.Copy(item.Key()), - Value: y.Copy(val), - UserMeta: []byte{item.UserMeta()}, - Version: item.Version(), - ExpiresAt: item.ExpiresAt(), - } - - // Write entries to disk - if err := writeTo(entry, w); err != nil { - return err - } - } - tsNew = txn.readTs - return nil - }) - return tsNew, err -} - -// Load reads a protobuf-encoded list of all entries from a reader and writes -// them to the database. This can be used to restore the database from a backup -// made by calling DB.Backup(). -// -// DB.Load() should be called on a database that is not running any other -// concurrent transactions while it is running. -func (db *DB) Load(r io.Reader) error { - br := bufio.NewReaderSize(r, 16<<10) - unmarshalBuf := make([]byte, 1<<10) - var entries []*Entry - var wg sync.WaitGroup - errChan := make(chan error, 1) - - // func to check for pending error before sending off a batch for writing - batchSetAsyncIfNoErr := func(entries []*Entry) error { - select { - case err := <-errChan: - return err - default: - wg.Add(1) - return db.batchSetAsync(entries, func(err error) { - defer wg.Done() - if err != nil { - select { - case errChan <- err: - default: - } - } - }) - } - } - - for { - var sz uint64 - err := binary.Read(br, binary.LittleEndian, &sz) - if err == io.EOF { - break - } else if err != nil { - return err - } - - if cap(unmarshalBuf) < int(sz) { - unmarshalBuf = make([]byte, sz) - } - - e := &protos.KVPair{} - if _, err = io.ReadFull(br, unmarshalBuf[:sz]); err != nil { - return err - } - if err = e.Unmarshal(unmarshalBuf[:sz]); err != nil { - return err - } - entries = append(entries, &Entry{ - Key: y.KeyWithTs(e.Key, e.Version), - Value: e.Value, - UserMeta: e.UserMeta[0], - ExpiresAt: e.ExpiresAt, - }) - // Update nextCommit, memtable stores this timestamp in badger head - // when flushed. - if e.Version >= db.orc.commitTs() { - db.orc.nextCommit = e.Version + 1 - } - - if len(entries) == 1000 { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - entries = make([]*Entry, 0, 1000) - } - } - - if len(entries) > 0 { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - } - - wg.Wait() - - select { - case err := <-errChan: - return err - default: - db.orc.curRead = db.orc.commitTs() - 1 - return nil - } -} diff --git a/vendor/github.com/dgraph-io/badger/backup_test.go b/vendor/github.com/dgraph-io/badger/backup_test.go deleted file mode 100644 index 350c3e6..0000000 --- a/vendor/github.com/dgraph-io/badger/backup_test.go +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDumpLoad(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - db, err := Open(getTestOptions(dir)) - require.NoError(t, err) - - // Write some stuff - entries := []struct { - key []byte - val []byte - userMeta byte - version uint64 - }{ - {key: []byte("answer1"), val: []byte("42"), version: 1}, - {key: []byte("answer2"), val: []byte("43"), userMeta: 1, version: 2}, - } - - err = db.Update(func(txn *Txn) error { - e := entries[0] - err := txn.SetWithMeta(e.key, e.val, e.userMeta) - if err != nil { - return err - } - return nil - }) - require.NoError(t, err) - - err = db.Update(func(txn *Txn) error { - e := entries[1] - err := txn.SetWithMeta(e.key, e.val, e.userMeta) - if err != nil { - return err - } - return nil - }) - require.NoError(t, err) - - // Use different directory. - dir, err = ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - bak, err := ioutil.TempFile(dir, "badgerbak") - require.NoError(t, err) - ts, err := db.Backup(bak, 0) - t.Logf("New ts: %d\n", ts) - require.NoError(t, err) - require.NoError(t, bak.Close()) - require.NoError(t, db.Close()) - - db, err = Open(getTestOptions(dir)) - require.NoError(t, err) - defer db.Close() - bak, err = os.Open(bak.Name()) - require.NoError(t, err) - defer bak.Close() - - require.NoError(t, db.Load(bak)) - - err = db.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - it := txn.NewIterator(opts) - var count int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - val, err := item.Value() - if err != nil { - return err - } - require.Equal(t, entries[count].key, item.Key()) - require.Equal(t, entries[count].val, val) - require.Equal(t, entries[count].version, item.Version()) - require.Equal(t, entries[count].userMeta, item.UserMeta()) - count++ - } - require.Equal(t, count, 2) - return nil - }) - require.NoError(t, err) -} - -func Test_BackupRestore(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "badger-test") - if err != nil { - t.Fatal(err) - } - defer func() { - os.RemoveAll(tmpdir) - }() - - s1Path := filepath.Join(tmpdir, "test1") - s2Path := filepath.Join(tmpdir, "test2") - s3Path := filepath.Join(tmpdir, "test3") - - opts := DefaultOptions - opts.Dir = s1Path - opts.ValueDir = s1Path - db1, err := Open(opts) - if err != nil { - t.Fatal(err) - } - key1 := []byte("key1") - key2 := []byte("key2") - rawValue := []byte("NotLongValue") - N := byte(251) - err = db1.Update(func(tx *Txn) error { - if err := tx.Set(key1, rawValue); err != nil { - return err - } - return tx.Set(key2, rawValue) - }) - if err != nil { - t.Fatal(err) - } - for i := byte(0); i < N; i++ { - err = db1.Update(func(tx *Txn) error { - if err := tx.Set(append(key1, i), rawValue); err != nil { - return err - } - return tx.Set(append(key2, i), rawValue) - }) - if err != nil { - t.Fatal(err) - } - } - var backup bytes.Buffer - _, err = db1.Backup(&backup, 0) - if err != nil { - t.Fatal(err) - } - fmt.Println("backup1 length:", backup.Len()) - - opts = DefaultOptions - opts.Dir = s2Path - opts.ValueDir = s2Path - db2, err := Open(opts) - if err != nil { - t.Fatal(err) - } - err = db2.Load(&backup) - if err != nil { - t.Fatal(err) - } - - for i := byte(0); i < N; i++ { - err = db2.View(func(tx *Txn) error { - k := append(key1, i) - item, err := tx.Get(k) - if err != nil { - if err == ErrKeyNotFound { - return fmt.Errorf("Key %q has been not found, but was set\n", k) - } - return err - } - v, err := item.Value() - if err != nil { - return err - } - if !reflect.DeepEqual(v, rawValue) { - return fmt.Errorf("Values not match, got %v, expected %v", v, rawValue) - } - return nil - }) - if err != nil { - t.Fatal(err) - } - } - - for i := byte(0); i < N; i++ { - err = db2.Update(func(tx *Txn) error { - if err := tx.Set(append(key1, i), rawValue); err != nil { - return err - } - return tx.Set(append(key2, i), rawValue) - }) - if err != nil { - t.Fatal(err) - } - } - - backup.Reset() - _, err = db2.Backup(&backup, 0) - if err != nil { - t.Fatal(err) - } - fmt.Println("backup2 length:", backup.Len()) - opts = DefaultOptions - opts.Dir = s3Path - opts.ValueDir = s3Path - db3, err := Open(opts) - if err != nil { - t.Fatal(err) - } - - err = db3.Load(&backup) - if err != nil { - t.Fatal(err) - } - - for i := byte(0); i < N; i++ { - err = db3.View(func(tx *Txn) error { - k := append(key1, i) - item, err := tx.Get(k) - if err != nil { - if err == ErrKeyNotFound { - return fmt.Errorf("Key %q has been not found, but was set\n", k) - } - return err - } - v, err := item.Value() - if err != nil { - return err - } - if !reflect.DeepEqual(v, rawValue) { - return fmt.Errorf("Values not match, got %v, expected %v", v, rawValue) - } - return nil - }) - if err != nil { - t.Fatal(err) - } - } - -} diff --git a/vendor/github.com/dgraph-io/badger/badger/.gitignore b/vendor/github.com/dgraph-io/badger/badger/.gitignore deleted file mode 100644 index a8e6bd9..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/badger diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go b/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go deleted file mode 100644 index 98b7392..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/backup.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "os" - - "github.com/dgraph-io/badger" - "github.com/spf13/cobra" -) - -var backupFile string -var truncate bool - -// backupCmd represents the backup command -var backupCmd = &cobra.Command{ - Use: "backup", - Short: "Backup Badger database.", - Long: `Backup Badger database to a file in a version-agnostic manner. - -Iterates over each key-value pair, encodes it along with its metadata and -version in protocol buffers and writes them to a file. This file can later be -used by the restore command to create an identical copy of the -database.`, - RunE: doBackup, -} - -func init() { - RootCmd.AddCommand(backupCmd) - backupCmd.Flags().StringVarP(&backupFile, "backup-file", "f", - "badger.bak", "File to backup to") - backupCmd.Flags().BoolVarP(&truncate, "truncate", "t", - false, "Allow value log truncation if required.") -} - -func doBackup(cmd *cobra.Command, args []string) error { - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.Truncate = truncate - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - // Create File - f, err := os.Create(backupFile) - if err != nil { - return err - } - defer f.Close() - - // Run Backup - _, err = db.Backup(f, 0) - return err -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/info.go b/vendor/github.com/dgraph-io/badger/badger/cmd/info.go deleted file mode 100644 index 0d23d3a..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/info.go +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/dgraph-io/badger" - "github.com/dgraph-io/badger/table" - "github.com/dgraph-io/badger/y" - humanize "github.com/dustin/go-humanize" - "github.com/spf13/cobra" -) - -var infoCmd = &cobra.Command{ - Use: "info", - Short: "Health info about Badger database.", - Long: ` -This command prints information about the badger key-value store. It reads MANIFEST and prints its -info. It also prints info about missing/extra files, and general information about the value log -files (which are not referenced by the manifest). Use this tool to report any issues about Badger -to the Dgraph team. -`, - Run: func(cmd *cobra.Command, args []string) { - err := printInfo(sstDir, vlogDir) - if err != nil { - fmt.Println("Error:", err.Error()) - os.Exit(1) - } - err = tableInfo(sstDir, vlogDir) - if err != nil { - fmt.Println("Error:", err.Error()) - os.Exit(1) - } - }, -} - -func init() { - RootCmd.AddCommand(infoCmd) -} - -func bytes(sz int64) string { - return humanize.Bytes(uint64(sz)) -} - -func dur(src, dst time.Time) string { - return humanize.RelTime(dst, src, "earlier", "later") -} - -func tableInfo(dir, valueDir string) error { - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - opts.ReadOnly = true - - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - tables := db.Tables() - for _, t := range tables { - lk, lv := y.ParseKey(t.Left), y.ParseTs(t.Left) - rk, rv := y.ParseKey(t.Right), y.ParseTs(t.Right) - fmt.Printf("SSTable [L%d, %03d] [%20X, v%-10d -> %20X, v%-10d]\n", - t.Level, t.ID, lk, lv, rk, rv) - } - return nil -} - -func printInfo(dir, valueDir string) error { - if dir == "" { - return fmt.Errorf("--dir not supplied") - } - if valueDir == "" { - valueDir = dir - } - fp, err := os.Open(filepath.Join(dir, badger.ManifestFilename)) - if err != nil { - return err - } - defer func() { - if fp != nil { - fp.Close() - } - }() - manifest, truncOffset, err := badger.ReplayManifestFile(fp) - if err != nil { - return err - } - fp.Close() - fp = nil - - fileinfos, err := ioutil.ReadDir(dir) - if err != nil { - return err - } - fileinfoByName := make(map[string]os.FileInfo) - fileinfoMarked := make(map[string]bool) - for _, info := range fileinfos { - fileinfoByName[info.Name()] = info - fileinfoMarked[info.Name()] = false - } - - fmt.Println() - var baseTime time.Time - // fmt.Print("\n[Manifest]\n") - manifestTruncated := false - manifestInfo, ok := fileinfoByName[badger.ManifestFilename] - if ok { - fileinfoMarked[badger.ManifestFilename] = true - truncatedString := "" - if truncOffset != manifestInfo.Size() { - truncatedString = fmt.Sprintf(" [TRUNCATED to %d]", truncOffset) - manifestTruncated = true - } - - baseTime = manifestInfo.ModTime() - fmt.Printf("[%25s] %-12s %6s MA%s\n", manifestInfo.ModTime().Format(time.RFC3339), - manifestInfo.Name(), bytes(manifestInfo.Size()), truncatedString) - } else { - fmt.Printf("%s [MISSING]\n", manifestInfo.Name()) - } - - numMissing := 0 - numEmpty := 0 - - levelSizes := make([]int64, len(manifest.Levels)) - for level, lm := range manifest.Levels { - // fmt.Printf("\n[Level %d]\n", level) - // We create a sorted list of table ID's so that output is in consistent order. - tableIDs := make([]uint64, 0, len(lm.Tables)) - for id := range lm.Tables { - tableIDs = append(tableIDs, id) - } - sort.Slice(tableIDs, func(i, j int) bool { - return tableIDs[i] < tableIDs[j] - }) - for _, tableID := range tableIDs { - tableFile := table.IDToFilename(tableID) - file, ok := fileinfoByName[tableFile] - if ok { - fileinfoMarked[tableFile] = true - emptyString := "" - fileSize := file.Size() - if fileSize == 0 { - emptyString = " [EMPTY]" - numEmpty++ - } - levelSizes[level] += fileSize - // (Put level on every line to make easier to process with sed/perl.) - fmt.Printf("[%25s] %-12s %6s L%d%s\n", dur(baseTime, file.ModTime()), - tableFile, bytes(fileSize), level, emptyString) - } else { - fmt.Printf("%s [MISSING]\n", tableFile) - numMissing++ - } - } - } - - valueDirFileinfos := fileinfos - if valueDir != dir { - valueDirFileinfos, err = ioutil.ReadDir(valueDir) - if err != nil { - return err - } - } - - // If valueDir is different from dir, holds extra files in the value dir. - valueDirExtras := []os.FileInfo{} - - valueLogSize := int64(0) - // fmt.Print("\n[Value Log]\n") - for _, file := range valueDirFileinfos { - if !strings.HasSuffix(file.Name(), ".vlog") { - if valueDir != dir { - valueDirExtras = append(valueDirExtras, file) - } - continue - } - - fileSize := file.Size() - emptyString := "" - if fileSize == 0 { - emptyString = " [EMPTY]" - numEmpty++ - } - valueLogSize += fileSize - fmt.Printf("[%25s] %-12s %6s VL%s\n", dur(baseTime, file.ModTime()), file.Name(), - bytes(fileSize), emptyString) - - fileinfoMarked[file.Name()] = true - } - - numExtra := 0 - for _, file := range fileinfos { - if fileinfoMarked[file.Name()] { - continue - } - if numExtra == 0 { - fmt.Print("\n[EXTRA]\n") - } - fmt.Printf("[%s] %-12s %6s\n", file.ModTime().Format(time.RFC3339), - file.Name(), bytes(file.Size())) - numExtra++ - } - - numValueDirExtra := 0 - for _, file := range valueDirExtras { - if numValueDirExtra == 0 { - fmt.Print("\n[ValueDir EXTRA]\n") - } - fmt.Printf("[%s] %-12s %6s\n", file.ModTime().Format(time.RFC3339), - file.Name(), bytes(file.Size())) - numValueDirExtra++ - } - - fmt.Print("\n[Summary]\n") - totalIndexSize := int64(0) - for i, sz := range levelSizes { - fmt.Printf("Level %d size: %12s\n", i, bytes(sz)) - totalIndexSize += sz - } - - fmt.Printf("Total index size: %8s\n", bytes(totalIndexSize)) - fmt.Printf("Value log size: %10s\n", bytes(valueLogSize)) - fmt.Println() - totalExtra := numExtra + numValueDirExtra - if totalExtra == 0 && numMissing == 0 && numEmpty == 0 && !manifestTruncated { - fmt.Println("Abnormalities: None.") - } else { - fmt.Println("Abnormalities:") - } - fmt.Printf("%d extra %s.\n", totalExtra, pluralFiles(totalExtra)) - fmt.Printf("%d missing %s.\n", numMissing, pluralFiles(numMissing)) - fmt.Printf("%d empty %s.\n", numEmpty, pluralFiles(numEmpty)) - fmt.Printf("%d truncated %s.\n", boolToNum(manifestTruncated), - pluralManifest(manifestTruncated)) - - return nil -} - -func boolToNum(x bool) int { - if x { - return 1 - } - return 0 -} - -func pluralManifest(manifestTruncated bool) string { - if manifestTruncated { - return "manifest" - } - return "manifests" -} - -func pluralFiles(count int) string { - if count == 1 { - return "file" - } - return "files" -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go b/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go deleted file mode 100644 index 80f7382..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/restore.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "errors" - "os" - "path" - - "github.com/dgraph-io/badger" - "github.com/spf13/cobra" -) - -var restoreFile string - -// restoreCmd represents the restore command -var restoreCmd = &cobra.Command{ - Use: "restore", - Short: "Restore Badger database.", - Long: `Restore Badger database from a file. - -It reads a file generated using the backup command (or by calling the -DB.Backup() API method) and writes each key-value pair found in the file to -the Badger database. - -Restore creates a new database, and currently does not work on an already -existing database.`, - RunE: doRestore, -} - -func init() { - RootCmd.AddCommand(restoreCmd) - restoreCmd.Flags().StringVarP(&restoreFile, "backup-file", "f", - "badger.bak", "File to restore from") -} - -func doRestore(cmd *cobra.Command, args []string) error { - // Check if the DB already exists - manifestFile := path.Join(sstDir, badger.ManifestFilename) - if _, err := os.Stat(manifestFile); err == nil { // No error. File already exists. - return errors.New("Cannot restore to an already existing database") - } else if os.IsNotExist(err) { - // pass - } else { // Return an error if anything other than the error above - return err - } - - // Open DB - opts := badger.DefaultOptions - opts.Dir = sstDir - opts.ValueDir = vlogDir - db, err := badger.Open(opts) - if err != nil { - return err - } - defer db.Close() - - // Open File - f, err := os.Open(restoreFile) - if err != nil { - return err - } - defer f.Close() - - // Run restore - return db.Load(f) -} diff --git a/vendor/github.com/dgraph-io/badger/badger/cmd/root.go b/vendor/github.com/dgraph-io/badger/badger/cmd/root.go deleted file mode 100644 index f536243..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/cmd/root.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "errors" - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" -) - -var sstDir, vlogDir string - -// RootCmd represents the base command when called without any subcommands -var RootCmd = &cobra.Command{ - Use: "badger", - Short: "Tools to manage Badger database.", - PersistentPreRunE: validateRootCmdArgs, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func init() { - RootCmd.PersistentFlags().StringVar(&sstDir, "dir", "", - "Directory where the LSM tree files are located. (required)") - - RootCmd.PersistentFlags().StringVar(&vlogDir, "vlog-dir", "", - "Directory where the value log files are located, if different from --dir") -} - -func validateRootCmdArgs(cmd *cobra.Command, args []string) error { - if strings.HasPrefix(cmd.Use, "help ") { // No need to validate if it is help - return nil - } - if sstDir == "" { - return errors.New("--sst-dir not specified") - } - if vlogDir == "" { - vlogDir = sstDir - } - return nil -} diff --git a/vendor/github.com/dgraph-io/badger/badger/main.go b/vendor/github.com/dgraph-io/badger/badger/main.go deleted file mode 100644 index 4ad9eaf..0000000 --- a/vendor/github.com/dgraph-io/badger/badger/main.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "fmt" - "net/http" - - "github.com/dgraph-io/badger/badger/cmd" -) - -func main() { - go func() { - for i := 8080; i < 9080; i++ { - fmt.Printf("Listening for /debug HTTP requests at port: %d\n", i) - if err := http.ListenAndServe(fmt.Sprintf("localhost:%d", i), nil); err != nil { - fmt.Println("Port busy. Trying another one...") - continue - - } - } - }() - cmd.Execute() -} diff --git a/vendor/github.com/dgraph-io/badger/compaction.go b/vendor/github.com/dgraph-io/badger/compaction.go deleted file mode 100644 index 00be8f6..0000000 --- a/vendor/github.com/dgraph-io/badger/compaction.go +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "bytes" - "fmt" - "log" - "math" - "sync" - - "golang.org/x/net/trace" - - "github.com/dgraph-io/badger/table" - "github.com/dgraph-io/badger/y" -) - -type keyRange struct { - left []byte - right []byte - inf bool -} - -var infRange = keyRange{inf: true} - -func (r keyRange) String() string { - return fmt.Sprintf("[left=%x, right=%x, inf=%v]", r.left, r.right, r.inf) -} - -func (r keyRange) equals(dst keyRange) bool { - return bytes.Equal(r.left, dst.left) && - bytes.Equal(r.right, dst.right) && - r.inf == dst.inf -} - -func (r keyRange) overlapsWith(dst keyRange) bool { - if r.inf || dst.inf { - return true - } - - // If my left is greater than dst right, we have no overlap. - if y.CompareKeys(r.left, dst.right) > 0 { - return false - } - // If my right is less than dst left, we have no overlap. - if y.CompareKeys(r.right, dst.left) < 0 { - return false - } - // We have overlap. - return true -} - -func getKeyRange(tables []*table.Table) keyRange { - y.AssertTrue(len(tables) > 0) - smallest := tables[0].Smallest() - biggest := tables[0].Biggest() - for i := 1; i < len(tables); i++ { - if y.CompareKeys(tables[i].Smallest(), smallest) < 0 { - smallest = tables[i].Smallest() - } - if y.CompareKeys(tables[i].Biggest(), biggest) > 0 { - biggest = tables[i].Biggest() - } - } - return keyRange{ - left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64), - right: y.KeyWithTs(y.ParseKey(biggest), 0), - } -} - -type levelCompactStatus struct { - ranges []keyRange - delSize int64 -} - -func (lcs *levelCompactStatus) debug() string { - var b bytes.Buffer - for _, r := range lcs.ranges { - b.WriteString(r.String()) - } - return b.String() -} - -func (lcs *levelCompactStatus) overlapsWith(dst keyRange) bool { - for _, r := range lcs.ranges { - if r.overlapsWith(dst) { - return true - } - } - return false -} - -func (lcs *levelCompactStatus) remove(dst keyRange) bool { - final := lcs.ranges[:0] - var found bool - for _, r := range lcs.ranges { - if !r.equals(dst) { - final = append(final, r) - } else { - found = true - } - } - lcs.ranges = final - return found -} - -type compactStatus struct { - sync.RWMutex - levels []*levelCompactStatus -} - -func (cs *compactStatus) toLog(tr trace.Trace) { - cs.RLock() - defer cs.RUnlock() - - tr.LazyPrintf("Compaction status:") - for i, l := range cs.levels { - if len(l.debug()) == 0 { - continue - } - tr.LazyPrintf("[%d] %s", i, l.debug()) - } -} - -func (cs *compactStatus) overlapsWith(level int, this keyRange) bool { - cs.RLock() - defer cs.RUnlock() - - thisLevel := cs.levels[level] - return thisLevel.overlapsWith(this) -} - -func (cs *compactStatus) delSize(l int) int64 { - cs.RLock() - defer cs.RUnlock() - return cs.levels[l].delSize -} - -type thisAndNextLevelRLocked struct{} - -// compareAndAdd will check whether we can run this compactDef. That it doesn't overlap with any -// other running compaction. If it can be run, it would store this run in the compactStatus state. -func (cs *compactStatus) compareAndAdd(_ thisAndNextLevelRLocked, cd compactDef) bool { - cs.Lock() - defer cs.Unlock() - - level := cd.thisLevel.level - - y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels)) - thisLevel := cs.levels[level] - nextLevel := cs.levels[level+1] - - if thisLevel.overlapsWith(cd.thisRange) { - return false - } - if nextLevel.overlapsWith(cd.nextRange) { - return false - } - // Check whether this level really needs compaction or not. Otherwise, we'll end up - // running parallel compactions for the same level. - // NOTE: We can directly call thisLevel.totalSize, because we already have acquire a read lock - // over this and the next level. - if cd.thisLevel.totalSize-thisLevel.delSize < cd.thisLevel.maxTotalSize { - return false - } - - thisLevel.ranges = append(thisLevel.ranges, cd.thisRange) - nextLevel.ranges = append(nextLevel.ranges, cd.nextRange) - thisLevel.delSize += cd.thisSize - - return true -} - -func (cs *compactStatus) delete(cd compactDef) { - cs.Lock() - defer cs.Unlock() - - level := cd.thisLevel.level - y.AssertTruef(level < len(cs.levels)-1, "Got level %d. Max levels: %d", level, len(cs.levels)) - - thisLevel := cs.levels[level] - nextLevel := cs.levels[level+1] - - thisLevel.delSize -= cd.thisSize - found := thisLevel.remove(cd.thisRange) - found = nextLevel.remove(cd.nextRange) && found - - if !found { - this := cd.thisRange - next := cd.nextRange - fmt.Printf("Looking for: [%q, %q, %v] in this level.\n", this.left, this.right, this.inf) - fmt.Printf("This Level:\n%s\n", thisLevel.debug()) - fmt.Println() - fmt.Printf("Looking for: [%q, %q, %v] in next level.\n", next.left, next.right, next.inf) - fmt.Printf("Next Level:\n%s\n", nextLevel.debug()) - log.Fatal("keyRange not found") - } -} diff --git a/vendor/github.com/dgraph-io/badger/contrib/cover.sh b/vendor/github.com/dgraph-io/badger/contrib/cover.sh deleted file mode 100755 index 5e2c179..0000000 --- a/vendor/github.com/dgraph-io/badger/contrib/cover.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -SRC="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/.." -TMP=$(mktemp /tmp/badger-coverage-XXXXX.txt) - -BUILD=$1 -OUT=$2 - -set -e - -pushd $SRC &> /dev/null - -# create coverage output -echo 'mode: atomic' > $OUT -for PKG in $(go list ./...|grep -v -E 'vendor'); do - go test -covermode=atomic -coverprofile=$TMP $PKG - tail -n +2 $TMP >> $OUT -done - -# Another round of tests after turning off mmap -go test -v -vlog_mmap=false github.com/dgraph-io/badger - -popd &> /dev/null diff --git a/vendor/github.com/dgraph-io/badger/db.go b/vendor/github.com/dgraph-io/badger/db.go deleted file mode 100644 index 1685bc8..0000000 --- a/vendor/github.com/dgraph-io/badger/db.go +++ /dev/null @@ -1,1230 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "encoding/binary" - "expvar" - "log" - "math" - "os" - "path/filepath" - "strconv" - "sync" - "time" - - "github.com/dgraph-io/badger/options" - - "golang.org/x/net/trace" - - "github.com/dgraph-io/badger/skl" - "github.com/dgraph-io/badger/table" - "github.com/dgraph-io/badger/y" - "github.com/pkg/errors" -) - -var ( - badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. - head = []byte("!badger!head") // For storing value offset for replay. - txnKey = []byte("!badger!txn") // For indicating end of entries in txn. - badgerMove = []byte("!badger!move") // For key-value pairs which got moved during GC. -) - -type closers struct { - updateSize *y.Closer - compactors *y.Closer - memtable *y.Closer - writes *y.Closer - valueGC *y.Closer -} - -// DB provides the various functions required to interact with Badger. -// DB is thread-safe. -type DB struct { - sync.RWMutex // Guards list of inmemory tables, not individual reads and writes. - - dirLockGuard *directoryLockGuard - // nil if Dir and ValueDir are the same - valueDirGuard *directoryLockGuard - - closers closers - elog trace.EventLog - mt *skl.Skiplist // Our latest (actively written) in-memory table - imm []*skl.Skiplist // Add here only AFTER pushing to flushChan. - opt Options - manifest *manifestFile - lc *levelsController - vlog valueLog - vptr valuePointer // less than or equal to a pointer to the last vlog value put into mt - writeCh chan *request - flushChan chan flushTask // For flushing memtables. - - orc *oracle -} - -const ( - kvWriteChCapacity = 1000 -) - -func replayFunction(out *DB) func(Entry, valuePointer) error { - type txnEntry struct { - nk []byte - v y.ValueStruct - } - - var txn []txnEntry - var lastCommit uint64 - - toLSM := func(nk []byte, vs y.ValueStruct) { - for err := out.ensureRoomForWrite(); err != nil; err = out.ensureRoomForWrite() { - out.elog.Printf("Replay: Making room for writes") - time.Sleep(10 * time.Millisecond) - } - out.mt.Put(nk, vs) - } - - first := true - return func(e Entry, vp valuePointer) error { // Function for replaying. - if first { - out.elog.Printf("First key=%s\n", e.Key) - } - first = false - - if out.orc.curRead < y.ParseTs(e.Key) { - out.orc.curRead = y.ParseTs(e.Key) - } - - nk := make([]byte, len(e.Key)) - copy(nk, e.Key) - var nv []byte - meta := e.meta - if out.shouldWriteValueToLSM(e) { - nv = make([]byte, len(e.Value)) - copy(nv, e.Value) - } else { - nv = make([]byte, vptrSize) - vp.Encode(nv) - meta = meta | bitValuePointer - } - - v := y.ValueStruct{ - Value: nv, - Meta: meta, - UserMeta: e.UserMeta, - } - - if e.meta&bitFinTxn > 0 { - txnTs, err := strconv.ParseUint(string(e.Value), 10, 64) - if err != nil { - return errors.Wrapf(err, "Unable to parse txn fin: %q", e.Value) - } - y.AssertTrue(lastCommit == txnTs) - y.AssertTrue(len(txn) > 0) - // Got the end of txn. Now we can store them. - for _, t := range txn { - toLSM(t.nk, t.v) - } - txn = txn[:0] - lastCommit = 0 - - } else if e.meta&bitTxn == 0 { - // This entry is from a rewrite. - toLSM(nk, v) - - // We shouldn't get this entry in the middle of a transaction. - y.AssertTrue(lastCommit == 0) - y.AssertTrue(len(txn) == 0) - - } else { - txnTs := y.ParseTs(nk) - if lastCommit == 0 { - lastCommit = txnTs - } - y.AssertTrue(lastCommit == txnTs) - te := txnEntry{nk: nk, v: v} - txn = append(txn, te) - } - return nil - } -} - -// Open returns a new DB object. -func Open(opt Options) (db *DB, err error) { - opt.maxBatchSize = (15 * opt.MaxTableSize) / 100 - opt.maxBatchCount = opt.maxBatchSize / int64(skl.MaxNodeSize) - - if opt.ValueThreshold > math.MaxUint16-16 { - return nil, ErrValueThreshold - } - - if opt.ReadOnly { - // Can't truncate if the DB is read only. - opt.Truncate = false - } - - for _, path := range []string{opt.Dir, opt.ValueDir} { - dirExists, err := exists(path) - if err != nil { - return nil, y.Wrapf(err, "Invalid Dir: %q", path) - } - if !dirExists { - if opt.ReadOnly { - return nil, y.Wrapf(err, "Cannot find Dir for read-only open: %q", path) - } - // Try to create the directory - err = os.Mkdir(path, 0700) - if err != nil { - return nil, y.Wrapf(err, "Error Creating Dir: %q", path) - } - } - } - absDir, err := filepath.Abs(opt.Dir) - if err != nil { - return nil, err - } - absValueDir, err := filepath.Abs(opt.ValueDir) - if err != nil { - return nil, err - } - var dirLockGuard, valueDirLockGuard *directoryLockGuard - dirLockGuard, err = acquireDirectoryLock(opt.Dir, lockFile, opt.ReadOnly) - if err != nil { - return nil, err - } - defer func() { - if dirLockGuard != nil { - _ = dirLockGuard.release() - } - }() - if absValueDir != absDir { - valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile, opt.ReadOnly) - if err != nil { - return nil, err - } - } - defer func() { - if valueDirLockGuard != nil { - _ = valueDirLockGuard.release() - } - }() - if !(opt.ValueLogFileSize <= 2<<30 && opt.ValueLogFileSize >= 1<<20) { - return nil, ErrValueLogSize - } - if !(opt.ValueLogLoadingMode == options.FileIO || - opt.ValueLogLoadingMode == options.MemoryMap) { - return nil, ErrInvalidLoadingMode - } - manifestFile, manifest, err := openOrCreateManifestFile(opt.Dir, opt.ReadOnly) - if err != nil { - return nil, err - } - defer func() { - if manifestFile != nil { - _ = manifestFile.close() - } - }() - - orc := &oracle{ - isManaged: opt.managedTxns, - nextCommit: 1, - commits: make(map[uint64]uint64), - readMark: y.WaterMark{}, - } - orc.readMark.Init() - - db = &DB{ - imm: make([]*skl.Skiplist, 0, opt.NumMemtables), - flushChan: make(chan flushTask, opt.NumMemtables), - writeCh: make(chan *request, kvWriteChCapacity), - opt: opt, - manifest: manifestFile, - elog: trace.NewEventLog("Badger", "DB"), - dirLockGuard: dirLockGuard, - valueDirGuard: valueDirLockGuard, - orc: orc, - } - - // Calculate initial size. - db.calculateSize() - db.closers.updateSize = y.NewCloser(1) - go db.updateSize(db.closers.updateSize) - db.mt = skl.NewSkiplist(arenaSize(opt)) - - // newLevelsController potentially loads files in directory. - if db.lc, err = newLevelsController(db, &manifest); err != nil { - return nil, err - } - - if !opt.ReadOnly { - db.closers.compactors = y.NewCloser(1) - db.lc.startCompact(db.closers.compactors) - - db.closers.memtable = y.NewCloser(1) - go db.flushMemtable(db.closers.memtable) // Need levels controller to be up. - } - - if err = db.vlog.Open(db, opt); err != nil { - return nil, err - } - - headKey := y.KeyWithTs(head, math.MaxUint64) - // Need to pass with timestamp, lsm get removes the last 8 bytes and compares key - vs, err := db.get(headKey) - if err != nil { - return nil, errors.Wrap(err, "Retrieving head") - } - db.orc.curRead = vs.Version - var vptr valuePointer - if len(vs.Value) > 0 { - vptr.Decode(vs.Value) - } - - // lastUsedCasCounter will either be the value stored in !badger!head, or some subsequently - // written value log entry that we replay. (Subsequent value log entries might be _less_ - // than lastUsedCasCounter, if there was value log gc so we have to max() values while - // replaying.) - // out.lastUsedCasCounter = item.casCounter - // TODO: Figure this out. This would update the read timestamp, and set nextCommitTs. - - replayCloser := y.NewCloser(1) - go db.doWrites(replayCloser) - - if err = db.vlog.Replay(vptr, replayFunction(db)); err != nil { - return db, err - } - - replayCloser.SignalAndWait() // Wait for replay to be applied first. - // Now that we have the curRead, we can update the nextCommit. - db.orc.nextCommit = db.orc.curRead + 1 - - // Mmap writable log - lf := db.vlog.filesMap[db.vlog.maxFid] - if err = lf.mmap(2 * db.vlog.opt.ValueLogFileSize); err != nil { - return db, errors.Wrapf(err, "Unable to mmap RDWR log file") - } - - db.writeCh = make(chan *request, kvWriteChCapacity) - db.closers.writes = y.NewCloser(1) - go db.doWrites(db.closers.writes) - - db.closers.valueGC = y.NewCloser(1) - go db.vlog.waitOnGC(db.closers.valueGC) - - valueDirLockGuard = nil - dirLockGuard = nil - manifestFile = nil - return db, nil -} - -// Close closes a DB. It's crucial to call it to ensure all the pending updates -// make their way to disk. Calling DB.Close() multiple times is not safe and would -// cause panic. -func (db *DB) Close() (err error) { - db.elog.Printf("Closing database") - // Stop value GC first. - db.closers.valueGC.SignalAndWait() - - // Stop writes next. - db.closers.writes.SignalAndWait() - - // Now close the value log. - if vlogErr := db.vlog.Close(); err == nil { - err = errors.Wrap(vlogErr, "DB.Close") - } - - // Make sure that block writer is done pushing stuff into memtable! - // Otherwise, you will have a race condition: we are trying to flush memtables - // and remove them completely, while the block / memtable writer is still - // trying to push stuff into the memtable. This will also resolve the value - // offset problem: as we push into memtable, we update value offsets there. - if !db.mt.Empty() { - db.elog.Printf("Flushing memtable") - for { - pushedFlushTask := func() bool { - db.Lock() - defer db.Unlock() - y.AssertTrue(db.mt != nil) - select { - case db.flushChan <- flushTask{db.mt, db.vptr}: - db.imm = append(db.imm, db.mt) // Flusher will attempt to remove this from s.imm. - db.mt = nil // Will segfault if we try writing! - db.elog.Printf("pushed to flush chan\n") - return true - default: - // If we fail to push, we need to unlock and wait for a short while. - // The flushing operation needs to update s.imm. Otherwise, we have a deadlock. - // TODO: Think about how to do this more cleanly, maybe without any locks. - } - return false - }() - if pushedFlushTask { - break - } - time.Sleep(10 * time.Millisecond) - } - } - db.flushChan <- flushTask{nil, valuePointer{}} // Tell flusher to quit. - - if db.closers.memtable != nil { - db.closers.memtable.Wait() - db.elog.Printf("Memtable flushed") - } - if db.closers.compactors != nil { - db.closers.compactors.SignalAndWait() - db.elog.Printf("Compaction finished") - } - - // Force Compact L0 - // We don't need to care about cstatus since no parallel compaction is running. - cd := compactDef{ - elog: trace.New("Badger", "Compact"), - thisLevel: db.lc.levels[0], - nextLevel: db.lc.levels[1], - } - cd.elog.SetMaxEvents(100) - defer cd.elog.Finish() - if db.lc.fillTablesL0(&cd) { - if err := db.lc.runCompactDef(0, cd); err != nil { - cd.elog.LazyPrintf("\tLOG Compact FAILED with error: %+v: %+v", err, cd) - } - } else { - cd.elog.LazyPrintf("fillTables failed for level zero. No compaction required") - } - - if lcErr := db.lc.close(); err == nil { - err = errors.Wrap(lcErr, "DB.Close") - } - db.elog.Printf("Waiting for closer") - db.closers.updateSize.SignalAndWait() - - db.elog.Finish() - - if db.dirLockGuard != nil { - if guardErr := db.dirLockGuard.release(); err == nil { - err = errors.Wrap(guardErr, "DB.Close") - } - } - if db.valueDirGuard != nil { - if guardErr := db.valueDirGuard.release(); err == nil { - err = errors.Wrap(guardErr, "DB.Close") - } - } - if manifestErr := db.manifest.close(); err == nil { - err = errors.Wrap(manifestErr, "DB.Close") - } - - // Fsync directories to ensure that lock file, and any other removed files whose directory - // we haven't specifically fsynced, are guaranteed to have their directory entry removal - // persisted to disk. - if syncErr := syncDir(db.opt.Dir); err == nil { - err = errors.Wrap(syncErr, "DB.Close") - } - if syncErr := syncDir(db.opt.ValueDir); err == nil { - err = errors.Wrap(syncErr, "DB.Close") - } - - return err -} - -const ( - lockFile = "LOCK" -) - -// When you create or delete a file, you have to ensure the directory entry for the file is synced -// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync, -// or see https://github.com/coreos/etcd/issues/6368 for an example.) -func syncDir(dir string) error { - f, err := openDir(dir) - if err != nil { - return errors.Wrapf(err, "While opening directory: %s.", dir) - } - err = f.Sync() - closeErr := f.Close() - if err != nil { - return errors.Wrapf(err, "While syncing directory: %s.", dir) - } - return errors.Wrapf(closeErr, "While closing directory: %s.", dir) -} - -// getMemtables returns the current memtables and get references. -func (db *DB) getMemTables() ([]*skl.Skiplist, func()) { - db.RLock() - defer db.RUnlock() - - tables := make([]*skl.Skiplist, len(db.imm)+1) - - // Get mutable memtable. - tables[0] = db.mt - tables[0].IncrRef() - - // Get immutable memtables. - last := len(db.imm) - 1 - for i := range db.imm { - tables[i+1] = db.imm[last-i] - tables[i+1].IncrRef() - } - return tables, func() { - for _, tbl := range tables { - tbl.DecrRef() - } - } -} - -// get returns the value in memtable or disk for given key. -// Note that value will include meta byte. -// -// IMPORTANT: We should never write an entry with an older timestamp for the same key, We need to -// maintain this invariant to search for the latest value of a key, or else we need to search in all -// tables and find the max version among them. To maintain this invariant, we also need to ensure -// that all versions of a key are always present in the same table from level 1, because compaction -// can push any table down. -func (db *DB) get(key []byte) (y.ValueStruct, error) { - tables, decr := db.getMemTables() // Lock should be released. - defer decr() - - y.NumGets.Add(1) - for i := 0; i < len(tables); i++ { - vs := tables[i].Get(key) - y.NumMemtableGets.Add(1) - if vs.Meta != 0 || vs.Value != nil { - return vs, nil - } - } - return db.lc.get(key) -} - -func (db *DB) updateOffset(ptrs []valuePointer) { - var ptr valuePointer - for i := len(ptrs) - 1; i >= 0; i-- { - p := ptrs[i] - if !p.IsZero() { - ptr = p - break - } - } - if ptr.IsZero() { - return - } - - db.Lock() - defer db.Unlock() - y.AssertTrue(!ptr.Less(db.vptr)) - db.vptr = ptr -} - -var requestPool = sync.Pool{ - New: func() interface{} { - return new(request) - }, -} - -func (db *DB) shouldWriteValueToLSM(e Entry) bool { - return len(e.Value) < db.opt.ValueThreshold -} - -func (db *DB) writeToLSM(b *request) error { - if len(b.Ptrs) != len(b.Entries) { - return errors.Errorf("Ptrs and Entries don't match: %+v", b) - } - - for i, entry := range b.Entries { - if entry.meta&bitFinTxn != 0 { - continue - } - if db.shouldWriteValueToLSM(*entry) { // Will include deletion / tombstone case. - db.mt.Put(entry.Key, - y.ValueStruct{ - Value: entry.Value, - Meta: entry.meta, - UserMeta: entry.UserMeta, - ExpiresAt: entry.ExpiresAt, - }) - } else { - var offsetBuf [vptrSize]byte - db.mt.Put(entry.Key, - y.ValueStruct{ - Value: b.Ptrs[i].Encode(offsetBuf[:]), - Meta: entry.meta | bitValuePointer, - UserMeta: entry.UserMeta, - ExpiresAt: entry.ExpiresAt, - }) - } - } - return nil -} - -// writeRequests is called serially by only one goroutine. -func (db *DB) writeRequests(reqs []*request) error { - if len(reqs) == 0 { - return nil - } - - done := func(err error) { - for _, r := range reqs { - r.Err = err - r.Wg.Done() - } - } - - db.elog.Printf("writeRequests called. Writing to value log") - - err := db.vlog.write(reqs) - if err != nil { - done(err) - return err - } - - db.elog.Printf("Writing to memtable") - var count int - for _, b := range reqs { - if len(b.Entries) == 0 { - continue - } - count += len(b.Entries) - var i uint64 - for err := db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { - i++ - if i%100 == 0 { - db.elog.Printf("Making room for writes") - } - // We need to poll a bit because both hasRoomForWrite and the flusher need access to s.imm. - // When flushChan is full and you are blocked there, and the flusher is trying to update s.imm, - // you will get a deadlock. - time.Sleep(10 * time.Millisecond) - } - if err != nil { - done(err) - return errors.Wrap(err, "writeRequests") - } - if err := db.writeToLSM(b); err != nil { - done(err) - return errors.Wrap(err, "writeRequests") - } - db.updateOffset(b.Ptrs) - } - done(nil) - db.elog.Printf("%d entries written", count) - return nil -} - -func (db *DB) sendToWriteCh(entries []*Entry) (*request, error) { - var count, size int64 - for _, e := range entries { - size += int64(e.estimateSize(db.opt.ValueThreshold)) - count++ - } - if count >= db.opt.maxBatchCount || size >= db.opt.maxBatchSize { - return nil, ErrTxnTooBig - } - - // We can only service one request because we need each txn to be stored in a contigous section. - // Txns should not interleave among other txns or rewrites. - req := requestPool.Get().(*request) - req.Entries = entries - req.Wg = sync.WaitGroup{} - req.Wg.Add(1) - db.writeCh <- req // Handled in doWrites. - y.NumPuts.Add(int64(len(entries))) - - return req, nil -} - -func (db *DB) doWrites(lc *y.Closer) { - defer lc.Done() - pendingCh := make(chan struct{}, 1) - - writeRequests := func(reqs []*request) { - if err := db.writeRequests(reqs); err != nil { - log.Printf("ERROR in Badger::writeRequests: %v", err) - } - <-pendingCh - } - - // This variable tracks the number of pending writes. - reqLen := new(expvar.Int) - y.PendingWrites.Set(db.opt.Dir, reqLen) - - reqs := make([]*request, 0, 10) - for { - var r *request - select { - case r = <-db.writeCh: - case <-lc.HasBeenClosed(): - goto closedCase - } - - for { - reqs = append(reqs, r) - reqLen.Set(int64(len(reqs))) - - if len(reqs) >= 3*kvWriteChCapacity { - pendingCh <- struct{}{} // blocking. - goto writeCase - } - - select { - // Either push to pending, or continue to pick from writeCh. - case r = <-db.writeCh: - case pendingCh <- struct{}{}: - goto writeCase - case <-lc.HasBeenClosed(): - goto closedCase - } - } - - closedCase: - close(db.writeCh) - for r := range db.writeCh { // Flush the channel. - reqs = append(reqs, r) - } - - pendingCh <- struct{}{} // Push to pending before doing a write. - writeRequests(reqs) - return - - writeCase: - go writeRequests(reqs) - reqs = make([]*request, 0, 10) - reqLen.Set(0) - } -} - -// batchSet applies a list of badger.Entry. If a request level error occurs it -// will be returned. -// Check(kv.BatchSet(entries)) -func (db *DB) batchSet(entries []*Entry) error { - req, err := db.sendToWriteCh(entries) - if err != nil { - return err - } - - return req.Wait() -} - -// batchSetAsync is the asynchronous version of batchSet. It accepts a callback -// function which is called when all the sets are complete. If a request level -// error occurs, it will be passed back via the callback. -// err := kv.BatchSetAsync(entries, func(err error)) { -// Check(err) -// } -func (db *DB) batchSetAsync(entries []*Entry, f func(error)) error { - req, err := db.sendToWriteCh(entries) - if err != nil { - return err - } - go func() { - err := req.Wait() - // Write is complete. Let's call the callback function now. - f(err) - }() - return nil -} - -var errNoRoom = errors.New("No room for write") - -// ensureRoomForWrite is always called serially. -func (db *DB) ensureRoomForWrite() error { - var err error - db.Lock() - defer db.Unlock() - if db.mt.MemSize() < db.opt.MaxTableSize { - return nil - } - - y.AssertTrue(db.mt != nil) // A nil mt indicates that DB is being closed. - select { - case db.flushChan <- flushTask{db.mt, db.vptr}: - db.elog.Printf("Flushing value log to disk if async mode.") - // Ensure value log is synced to disk so this memtable's contents wouldn't be lost. - err = db.vlog.sync() - if err != nil { - return err - } - - db.elog.Printf("Flushing memtable, mt.size=%d size of flushChan: %d\n", - db.mt.MemSize(), len(db.flushChan)) - // We manage to push this task. Let's modify imm. - db.imm = append(db.imm, db.mt) - db.mt = skl.NewSkiplist(arenaSize(db.opt)) - // New memtable is empty. We certainly have room. - return nil - default: - // We need to do this to unlock and allow the flusher to modify imm. - return errNoRoom - } -} - -func arenaSize(opt Options) int64 { - return opt.MaxTableSize + opt.maxBatchSize + opt.maxBatchCount*int64(skl.MaxNodeSize) -} - -// WriteLevel0Table flushes memtable. -func writeLevel0Table(s *skl.Skiplist, f *os.File) error { - iter := s.NewIterator() - defer iter.Close() - b := table.NewTableBuilder() - defer b.Close() - for iter.SeekToFirst(); iter.Valid(); iter.Next() { - if err := b.Add(iter.Key(), iter.Value()); err != nil { - return err - } - } - _, err := f.Write(b.Finish()) - return err -} - -type flushTask struct { - mt *skl.Skiplist - vptr valuePointer -} - -// TODO: Ensure that this function doesn't return, or is handled by another wrapper function. -// Otherwise, we would have no goroutine which can flush memtables. -func (db *DB) flushMemtable(lc *y.Closer) error { - defer lc.Done() - - for ft := range db.flushChan { - if ft.mt == nil { - return nil - } - - if !ft.mt.Empty() { - // Store badger head even if vptr is zero, need it for readTs - db.elog.Printf("Storing offset: %+v\n", ft.vptr) - offset := make([]byte, vptrSize) - ft.vptr.Encode(offset) - - // Pick the max commit ts, so in case of crash, our read ts would be higher than all the - // commits. - headTs := y.KeyWithTs(head, db.orc.commitTs()) - ft.mt.Put(headTs, y.ValueStruct{Value: offset}) - } - - fileID := db.lc.reserveFileID() - fd, err := y.CreateSyncedFile(table.NewFilename(fileID, db.opt.Dir), true) - if err != nil { - return y.Wrap(err) - } - - // Don't block just to sync the directory entry. - dirSyncCh := make(chan error) - go func() { dirSyncCh <- syncDir(db.opt.Dir) }() - - err = writeLevel0Table(ft.mt, fd) - dirSyncErr := <-dirSyncCh - - if err != nil { - db.elog.Errorf("ERROR while writing to level 0: %v", err) - return err - } - if dirSyncErr != nil { - db.elog.Errorf("ERROR while syncing level directory: %v", dirSyncErr) - return err - } - - tbl, err := table.OpenTable(fd, db.opt.TableLoadingMode) - if err != nil { - db.elog.Printf("ERROR while opening table: %v", err) - return err - } - // We own a ref on tbl. - err = db.lc.addLevel0Table(tbl) // This will incrRef (if we don't error, sure) - tbl.DecrRef() // Releases our ref. - if err != nil { - return err - } - - // Update s.imm. Need a lock. - db.Lock() - y.AssertTrue(ft.mt == db.imm[0]) //For now, single threaded. - db.imm = db.imm[1:] - ft.mt.DecrRef() // Return memory. - db.Unlock() - } - return nil -} - -func exists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return true, err -} - -// This function does a filewalk, calculates the size of vlog and sst files and stores it in -// y.LSMSize and y.VlogSize. -func (db *DB) calculateSize() { - newInt := func(val int64) *expvar.Int { - v := new(expvar.Int) - v.Add(val) - return v - } - - totalSize := func(dir string) (int64, int64) { - var lsmSize, vlogSize int64 - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - ext := filepath.Ext(path) - if ext == ".sst" { - lsmSize += info.Size() - } else if ext == ".vlog" { - vlogSize += info.Size() - } - return nil - }) - if err != nil { - db.elog.Printf("Got error while calculating total size of directory: %s", dir) - } - return lsmSize, vlogSize - } - - lsmSize, vlogSize := totalSize(db.opt.Dir) - y.LSMSize.Set(db.opt.Dir, newInt(lsmSize)) - // If valueDir is different from dir, we'd have to do another walk. - if db.opt.ValueDir != db.opt.Dir { - _, vlogSize = totalSize(db.opt.ValueDir) - } - y.VlogSize.Set(db.opt.Dir, newInt(vlogSize)) - -} - -func (db *DB) updateSize(lc *y.Closer) { - defer lc.Done() - - metricsTicker := time.NewTicker(time.Minute) - defer metricsTicker.Stop() - - for { - select { - case <-metricsTicker.C: - db.calculateSize() - case <-lc.HasBeenClosed(): - return - } - } -} - -// RunValueLogGC triggers a value log garbage collection. -// -// It picks value log files to perform GC based on statistics that are collected -// duing compactions. If no such statistics are available, then log files are -// picked in random order. The process stops as soon as the first log file is -// encountered which does not result in garbage collection. -// -// When a log file is picked, it is first sampled. If the sample shows that we -// can discard at least discardRatio space of that file, it would be rewritten. -// -// If a call to RunValueLogGC results in no rewrites, then an ErrNoRewrite is -// thrown indicating that the call resulted in no file rewrites. -// -// We recommend setting discardRatio to 0.5, thus indicating that a file be -// rewritten if half the space can be discarded. This results in a lifetime -// value log write amplification of 2 (1 from original write + 0.5 rewrite + -// 0.25 + 0.125 + ... = 2). Setting it to higher value would result in fewer -// space reclaims, while setting it to a lower value would result in more space -// reclaims at the cost of increased activity on the LSM tree. discardRatio -// must be in the range (0.0, 1.0), both endpoints excluded, otherwise an -// ErrInvalidRequest is returned. -// -// Only one GC is allowed at a time. If another value log GC is running, or DB -// has been closed, this would return an ErrRejected. -// -// Note: Every time GC is run, it would produce a spike of activity on the LSM -// tree. -func (db *DB) RunValueLogGC(discardRatio float64) error { - if discardRatio >= 1.0 || discardRatio <= 0.0 { - return ErrInvalidRequest - } - - // Find head on disk - headKey := y.KeyWithTs(head, math.MaxUint64) - // Need to pass with timestamp, lsm get removes the last 8 bytes and compares key - val, err := db.lc.get(headKey) - if err != nil { - return errors.Wrap(err, "Retrieving head from on-disk LSM") - } - - var head valuePointer - if len(val.Value) > 0 { - head.Decode(val.Value) - } - - // Pick a log file and run GC - return db.vlog.runGC(discardRatio, head) -} - -// Size returns the size of lsm and value log files in bytes. It can be used to decide how often to -// call RunValueLogGC. -func (db *DB) Size() (lsm int64, vlog int64) { - if y.LSMSize.Get(db.opt.Dir) == nil { - lsm, vlog = 0, 0 - return - } - lsm = y.LSMSize.Get(db.opt.Dir).(*expvar.Int).Value() - vlog = y.VlogSize.Get(db.opt.Dir).(*expvar.Int).Value() - return -} - -// Sequence represents a Badger sequence. -type Sequence struct { - sync.Mutex - db *DB - key []byte - next uint64 - leased uint64 - bandwidth uint64 -} - -// Next would return the next integer in the sequence, updating the lease by running a transaction -// if needed. -func (seq *Sequence) Next() (uint64, error) { - seq.Lock() - defer seq.Unlock() - if seq.next >= seq.leased { - if err := seq.updateLease(); err != nil { - return 0, err - } - } - val := seq.next - seq.next++ - return val, nil -} - -// Release the leased sequence to avoid wasted integers. This should be done right -// before closing the associated DB. However it is valid to use the sequence after -// it was released, causing a new lease with full bandwidth. -func (seq *Sequence) Release() error { - seq.Lock() - defer seq.Unlock() - err := seq.db.Update(func(txn *Txn) error { - var buf [8]byte - binary.BigEndian.PutUint64(buf[:], seq.next) - return txn.Set(seq.key, buf[:]) - }) - if err != nil { - return err - } - seq.leased = seq.next - return nil -} - -func (seq *Sequence) updateLease() error { - return seq.db.Update(func(txn *Txn) error { - item, err := txn.Get(seq.key) - if err == ErrKeyNotFound { - seq.next = 0 - } else if err != nil { - return err - } else { - val, err := item.Value() - if err != nil { - return err - } - num := binary.BigEndian.Uint64(val) - seq.next = num - } - - lease := seq.next + seq.bandwidth - var buf [8]byte - binary.BigEndian.PutUint64(buf[:], lease) - if err = txn.Set(seq.key, buf[:]); err != nil { - return err - } - seq.leased = lease - return nil - }) -} - -// GetSequence would initiate a new sequence object, generating it from the stored lease, if -// available, in the database. Sequence can be used to get a list of monotonically increasing -// integers. Multiple sequences can be created by providing different keys. Bandwidth sets the -// size of the lease, determining how many Next() requests can be served from memory. -func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) { - switch { - case len(key) == 0: - return nil, ErrEmptyKey - case bandwidth == 0: - return nil, ErrZeroBandwidth - } - seq := &Sequence{ - db: db, - key: key, - next: 0, - leased: 0, - bandwidth: bandwidth, - } - err := seq.updateLease() - return seq, err -} - -func (db *DB) Tables() []TableInfo { - return db.lc.getTableInfo() -} - -// MergeOperator represents a Badger merge operator. -type MergeOperator struct { - sync.RWMutex - f MergeFunc - db *DB - key []byte - closer *y.Closer -} - -// MergeFunc accepts two byte slices, one representing an existing value, and -// another representing a new value that needs to be ‘merged’ into it. MergeFunc -// contains the logic to perform the ‘merge’ and return an updated value. -// MergeFunc could perform operations like integer addition, list appends etc. -// Note that the ordering of the operands is unspecified, so the merge func -// should either be agnostic to ordering or do additional handling if ordering -// is required. -type MergeFunc func(existing, val []byte) []byte - -// GetMergeOperator creates a new MergeOperator for a given key and returns a -// pointer to it. It also fires off a goroutine that performs a compaction using -// the merge function that runs periodically, as specified by dur. -func (db *DB) GetMergeOperator(key []byte, - f MergeFunc, dur time.Duration) *MergeOperator { - op := &MergeOperator{ - f: f, - db: db, - key: key, - closer: y.NewCloser(1), - } - - go op.runCompactions(dur) - return op -} - -var errNoMerge = errors.New("No need for merge") - -func (op *MergeOperator) iterateAndMerge(txn *Txn) (val []byte, err error) { - opt := DefaultIteratorOptions - opt.AllVersions = true - it := txn.NewIterator(opt) - var numVersions int - for it.Rewind(); it.ValidForPrefix(op.key); it.Next() { - item := it.Item() - numVersions++ - if numVersions == 1 { - val, err = item.ValueCopy(val) - if err != nil { - return nil, err - } - } else { - newVal, err := item.Value() - if err != nil { - return nil, err - } - val = op.f(val, newVal) - } - if item.DiscardEarlierVersions() { - break - } - } - if numVersions == 0 { - return nil, ErrKeyNotFound - } else if numVersions == 1 { - return val, errNoMerge - } - return val, nil -} - -func (op *MergeOperator) compact() error { - op.Lock() - defer op.Unlock() - err := op.db.Update(func(txn *Txn) error { - var ( - val []byte - err error - ) - val, err = op.iterateAndMerge(txn) - if err != nil { - return err - } - - // Write value back to db - if err := txn.SetWithDiscard(op.key, val, 0); err != nil { - return err - } - return nil - }) - - if err == ErrKeyNotFound || err == errNoMerge { - // pass. - } else if err != nil { - return err - } - return nil -} - -func (op *MergeOperator) runCompactions(dur time.Duration) { - ticker := time.NewTicker(dur) - defer op.closer.Done() - var stop bool - for { - select { - case <-op.closer.HasBeenClosed(): - stop = true - case <-ticker.C: // wait for tick - } - if err := op.compact(); err != nil { - log.Printf("Error while running merge operation: %s", err) - } - if stop { - ticker.Stop() - break - } - } -} - -// Add records a value in Badger which will eventually be merged by a background -// routine into the values that were recorded by previous invocations to Add(). -func (op *MergeOperator) Add(val []byte) error { - return op.db.Update(func(txn *Txn) error { - return txn.Set(op.key, val) - }) -} - -// Get returns the latest value for the merge operator, which is derived by -// applying the merge function to all the values added so far. -// -// If Add has not been called even once, Get will return ErrKeyNotFound. -func (op *MergeOperator) Get() ([]byte, error) { - op.RLock() - defer op.RUnlock() - var existing []byte - err := op.db.View(func(txn *Txn) (err error) { - existing, err = op.iterateAndMerge(txn) - return err - }) - if err == errNoMerge { - return existing, nil - } - return existing, err -} - -// Stop waits for any pending merge to complete and then stops the background -// goroutine. -func (op *MergeOperator) Stop() { - op.closer.SignalAndWait() -} diff --git a/vendor/github.com/dgraph-io/badger/db_test.go b/vendor/github.com/dgraph-io/badger/db_test.go deleted file mode 100644 index 004b837..0000000 --- a/vendor/github.com/dgraph-io/badger/db_test.go +++ /dev/null @@ -1,1565 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "bytes" - "encoding/binary" - "flag" - "fmt" - "io/ioutil" - "log" - "math" - "math/rand" - "os" - "path/filepath" - "regexp" - "sort" - "sync" - "testing" - "time" - - "github.com/dgraph-io/badger/options" - - "github.com/dgraph-io/badger/y" - "github.com/stretchr/testify/require" -) - -var mmap = flag.Bool("vlog_mmap", true, "Specify if value log must be memory-mapped") - -func getTestOptions(dir string) Options { - opt := DefaultOptions - opt.MaxTableSize = 1 << 15 // Force more compaction. - opt.LevelOneSize = 4 << 15 // Force more compaction. - opt.Dir = dir - opt.ValueDir = dir - opt.SyncWrites = false - if !*mmap { - opt.ValueLogLoadingMode = options.FileIO - } - return opt -} - -func getItemValue(t *testing.T, item *Item) (val []byte) { - v, err := item.Value() - if err != nil { - t.Error(err) - } - if v == nil { - return nil - } - another, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, v, another) - return v -} - -func txnSet(t *testing.T, kv *DB, key []byte, val []byte, meta byte) { - txn := kv.NewTransaction(true) - require.NoError(t, txn.SetWithMeta(key, val, meta)) - require.NoError(t, txn.Commit(nil)) -} - -func txnDelete(t *testing.T, kv *DB, key []byte) { - txn := kv.NewTransaction(true) - require.NoError(t, txn.Delete(key)) - require.NoError(t, txn.Commit(nil)) -} - -// Opens a badger db and runs a a test on it. -func runBadgerTest(t *testing.T, opts *Options, test func(t *testing.T, db *DB)) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - if opts == nil { - opts = new(Options) - *opts = getTestOptions(dir) - } - db, err := Open(*opts) - require.NoError(t, err) - defer db.Close() - test(t, db) -} - -func TestWrite(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - for i := 0; i < 100; i++ { - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i)), 0x00) - } - }) -} - -func TestUpdateAndView(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - err := db.Update(func(txn *Txn) error { - for i := 0; i < 10; i++ { - err := txn.Set([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i))) - if err != nil { - return err - } - } - return nil - }) - require.NoError(t, err) - - err = db.View(func(txn *Txn) error { - for i := 0; i < 10; i++ { - item, err := txn.Get([]byte(fmt.Sprintf("key%d", i))) - if err != nil { - return err - } - - val, err := item.Value() - if err != nil { - return err - } - expected := []byte(fmt.Sprintf("val%d", i)) - require.Equal(t, expected, val, - "Invalid value for key %q. expected: %q, actual: %q", - item.Key(), expected, val) - } - return nil - }) - require.NoError(t, err) - }) -} - -func TestConcurrentWrite(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Not a benchmark. Just a simple test for concurrent writes. - n := 20 - m := 500 - var wg sync.WaitGroup - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := 0; j < m; j++ { - txnSet(t, db, []byte(fmt.Sprintf("k%05d_%08d", i, j)), - []byte(fmt.Sprintf("v%05d_%08d", i, j)), byte(j%127)) - } - }(i) - } - wg.Wait() - - t.Log("Starting iteration") - - opt := IteratorOptions{} - opt.Reverse = false - opt.PrefetchSize = 10 - opt.PrefetchValues = true - - txn := db.NewTransaction(true) - it := txn.NewIterator(opt) - defer it.Close() - var i, j int - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - k := item.Key() - if k == nil { - break // end of iteration. - } - - require.EqualValues(t, fmt.Sprintf("k%05d_%08d", i, j), string(k)) - v := getItemValue(t, item) - require.EqualValues(t, fmt.Sprintf("v%05d_%08d", i, j), string(v)) - require.Equal(t, item.UserMeta(), byte(j%127)) - j++ - if j == m { - i++ - j = 0 - } - } - require.EqualValues(t, n, i) - require.EqualValues(t, 0, j) - }) -} - -func TestGet(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - txnSet(t, db, []byte("key1"), []byte("val1"), 0x08) - - txn := db.NewTransaction(false) - item, err := txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val1", getItemValue(t, item)) - require.Equal(t, byte(0x08), item.UserMeta()) - txn.Discard() - - txnSet(t, db, []byte("key1"), []byte("val2"), 0x09) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val2", getItemValue(t, item)) - require.Equal(t, byte(0x09), item.UserMeta()) - txn.Discard() - - txnDelete(t, db, []byte("key1")) - - txn = db.NewTransaction(false) - _, err = txn.Get([]byte("key1")) - require.Equal(t, ErrKeyNotFound, err) - txn.Discard() - - txnSet(t, db, []byte("key1"), []byte("val3"), 0x01) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, "val3", getItemValue(t, item)) - require.Equal(t, byte(0x01), item.UserMeta()) - - longVal := make([]byte, 1000) - txnSet(t, db, []byte("key1"), longVal, 0x00) - - txn = db.NewTransaction(false) - item, err = txn.Get([]byte("key1")) - require.NoError(t, err) - require.EqualValues(t, longVal, getItemValue(t, item)) - txn.Discard() - }) -} - -func TestGetAfterDelete(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // populate with one entry - key := []byte("key") - txnSet(t, db, key, []byte("val1"), 0x00) - require.NoError(t, db.Update(func(txn *Txn) error { - err := txn.Delete(key) - require.NoError(t, err) - - _, err = txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - }) -} - -func TestTxnTooBig(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - // n := 500000 - n := 1000 - txn := db.NewTransaction(true) - for i := 0; i < n; { - if err := txn.Set(data(i), data(i)); err != nil { - require.NoError(t, txn.Commit(nil)) - txn = db.NewTransaction(true) - } else { - i++ - } - } - require.NoError(t, txn.Commit(nil)) - - txn = db.NewTransaction(true) - for i := 0; i < n; { - if err := txn.Delete(data(i)); err != nil { - require.NoError(t, txn.Commit(nil)) - txn = db.NewTransaction(true) - } else { - i++ - } - } - require.NoError(t, txn.Commit(nil)) - }) -} - -func TestForceCompactL0(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := getTestOptions(dir) - opts.ValueLogFileSize = 15 << 20 - db, err := OpenManaged(opts) - require.NoError(t, err) - - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - n := 80 - m := 45 // Increasing would cause ErrTxnTooBig - sz := 32 << 10 - v := make([]byte, sz) - for i := 0; i < n; i += 2 { - version := uint64(i) - txn := db.NewTransactionAt(version, true) - for j := 0; j < m; j++ { - require.NoError(t, txn.Set(data(j), v)) - } - require.NoError(t, txn.CommitAt(version+1, nil)) - } - db.Close() - - db, err = OpenManaged(opts) - defer db.Close() - require.Equal(t, len(db.lc.levels[0].tables), 0) -} - -// Put a lot of data to move some data to disk. -// WARNING: This test might take a while but it should pass! -func TestGetMore(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - data := func(i int) []byte { - return []byte(fmt.Sprintf("%b", i)) - } - // n := 500000 - n := 10000 - m := 45 // Increasing would cause ErrTxnTooBig - for i := 0; i < n; i += m { - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set(data(j), data(j))) - } - require.NoError(t, txn.Commit(nil)) - } - require.NoError(t, db.validate()) - - for i := 0; i < n; i++ { - txn := db.NewTransaction(false) - item, err := txn.Get(data(i)) - if err != nil { - t.Error(err) - } - require.EqualValues(t, string(data(i)), string(getItemValue(t, item))) - txn.Discard() - } - - // Overwrite - for i := 0; i < n; i += m { - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set(data(j), - // Use a long value that will certainly exceed value threshold. - []byte(fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", j)))) - } - require.NoError(t, txn.Commit(nil)) - } - require.NoError(t, db.validate()) - - for i := 0; i < n; i++ { - expectedValue := fmt.Sprintf("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz%9d", i) - k := data(i) - txn := db.NewTransaction(false) - item, err := txn.Get(k) - if err != nil { - t.Error(err) - } - got := string(getItemValue(t, item)) - if expectedValue != got { - - vs, err := db.get(y.KeyWithTs(k, math.MaxUint64)) - require.NoError(t, err) - fmt.Printf("wanted=%q Item: %s\n", k, item) - fmt.Printf("on re-run, got version: %+v\n", vs) - - txn := db.NewTransaction(false) - itr := txn.NewIterator(DefaultIteratorOptions) - for itr.Seek(k); itr.Valid(); itr.Next() { - item := itr.Item() - fmt.Printf("item=%s\n", item) - if !bytes.Equal(item.Key(), k) { - break - } - } - itr.Close() - txn.Discard() - } - require.EqualValues(t, expectedValue, string(getItemValue(t, item)), "wanted=%q Item: %s\n", k, item) - txn.Discard() - } - - // "Delete" key. - for i := 0; i < n; i += m { - if (i % 10000) == 0 { - fmt.Printf("Deleting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Delete(data(j))) - } - require.NoError(t, txn.Commit(nil)) - } - db.validate() - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - // Display some progress. Right now, it's not very fast with no caching. - fmt.Printf("Testing i=%d\n", i) - } - k := data(i) - txn := db.NewTransaction(false) - _, err := txn.Get([]byte(k)) - require.Equal(t, ErrKeyNotFound, err, "should not have found k: %q", k) - txn.Discard() - } - }) -} - -// Put a lot of data to move some data to disk. -// WARNING: This test might take a while but it should pass! -func TestExistsMore(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // n := 500000 - n := 10000 - m := 45 - for i := 0; i < n; i += m { - if (i % 1000) == 0 { - t.Logf("Putting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Set([]byte(fmt.Sprintf("%09d", j)), - []byte(fmt.Sprintf("%09d", j)))) - } - require.NoError(t, txn.Commit(nil)) - } - db.validate() - - for i := 0; i < n; i++ { - if (i % 1000) == 0 { - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte(k)) - require.NoError(t, err) - return nil - })) - } - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("non-exists")) - require.Error(t, err) - return nil - })) - - // "Delete" key. - for i := 0; i < n; i += m { - if (i % 1000) == 0 { - fmt.Printf("Deleting i=%d\n", i) - } - txn := db.NewTransaction(true) - for j := i; j < i+m && j < n; j++ { - require.NoError(t, txn.Delete([]byte(fmt.Sprintf("%09d", j)))) - } - require.NoError(t, txn.Commit(nil)) - } - db.validate() - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - // Display some progress. Right now, it's not very fast with no caching. - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get([]byte(k)) - require.Error(t, err) - return nil - })) - } - fmt.Println("Done and closing") - }) -} - -func TestIterate2Basic(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - // n := 500000 - n := 10000 - for i := 0; i < n; i++ { - if (i % 1000) == 0 { - t.Logf("Put i=%d\n", i) - } - txnSet(t, db, bkey(i), bval(i), byte(i%127)) - } - - opt := IteratorOptions{} - opt.PrefetchValues = true - opt.PrefetchSize = 10 - - txn := db.NewTransaction(false) - it := txn.NewIterator(opt) - { - var count int - rewind := true - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - key := item.Key() - if rewind && count == 5000 { - // Rewind would skip /head/ key, and it.Next() would skip 0. - count = 1 - it.Rewind() - t.Log("Rewinding from 5000 to zero.") - rewind = false - continue - } - require.EqualValues(t, bkey(count), string(key)) - val := getItemValue(t, item) - require.EqualValues(t, bval(count), string(val)) - require.Equal(t, byte(count%127), item.UserMeta()) - count++ - } - require.EqualValues(t, n, count) - } - - { - t.Log("Starting second basic iteration") - idx := 5030 - for it.Seek(bkey(idx)); it.Valid(); it.Next() { - item := it.Item() - require.EqualValues(t, bkey(idx), string(item.Key())) - require.EqualValues(t, bval(idx), string(getItemValue(t, item))) - idx++ - } - } - it.Close() - }) -} - -func TestLoad(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - fmt.Printf("Writing to dir %s\n", dir) - require.NoError(t, err) - defer os.RemoveAll(dir) - n := 10000 - { - kv, _ := Open(getTestOptions(dir)) - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - fmt.Printf("Putting i=%d\n", i) - } - k := []byte(fmt.Sprintf("%09d", i)) - txnSet(t, kv, k, k, 0x00) - } - kv.Close() - } - - kv, err := Open(getTestOptions(dir)) - require.NoError(t, err) - require.Equal(t, uint64(10001), kv.orc.readTs()) - for i := 0; i < n; i++ { - if (i % 10000) == 0 { - fmt.Printf("Testing i=%d\n", i) - } - k := fmt.Sprintf("%09d", i) - require.NoError(t, kv.View(func(txn *Txn) error { - item, err := txn.Get([]byte(k)) - require.NoError(t, err) - require.EqualValues(t, k, string(getItemValue(t, item))) - return nil - })) - - } - kv.Close() - summary := kv.lc.getSummary() - - // Check that files are garbage collected. - idMap := getIDMap(dir) - for fileID := range idMap { - // Check that name is in summary.filenames. - require.True(t, summary.fileIDs[fileID], "%d", fileID) - } - require.EqualValues(t, len(idMap), len(summary.fileIDs)) - - var fileIDs []uint64 - for k := range summary.fileIDs { // Map to array. - fileIDs = append(fileIDs, k) - } - sort.Slice(fileIDs, func(i, j int) bool { return fileIDs[i] < fileIDs[j] }) - fmt.Printf("FileIDs: %v\n", fileIDs) -} - -func TestIterateDeleted(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - txnSet(t, db, []byte("Key1"), []byte("Value1"), 0x00) - txnSet(t, db, []byte("Key2"), []byte("Value2"), 0x00) - - iterOpt := DefaultIteratorOptions - iterOpt.PrefetchValues = false - txn := db.NewTransaction(false) - idxIt := txn.NewIterator(iterOpt) - defer idxIt.Close() - - count := 0 - txn2 := db.NewTransaction(true) - prefix := []byte("Key") - for idxIt.Seek(prefix); idxIt.ValidForPrefix(prefix); idxIt.Next() { - key := idxIt.Item().Key() - count++ - newKey := make([]byte, len(key)) - copy(newKey, key) - require.NoError(t, txn2.Delete(newKey)) - } - require.Equal(t, 2, count) - require.NoError(t, txn2.Commit(nil)) - - for _, prefetch := range [...]bool{true, false} { - t.Run(fmt.Sprintf("Prefetch=%t", prefetch), func(t *testing.T) { - txn := db.NewTransaction(false) - iterOpt = DefaultIteratorOptions - iterOpt.PrefetchValues = prefetch - idxIt = txn.NewIterator(iterOpt) - - var estSize int64 - var idxKeys []string - for idxIt.Seek(prefix); idxIt.Valid(); idxIt.Next() { - item := idxIt.Item() - key := item.Key() - estSize += item.EstimatedSize() - if !bytes.HasPrefix(key, prefix) { - break - } - idxKeys = append(idxKeys, string(key)) - t.Logf("%+v\n", idxIt.Item()) - } - require.Equal(t, 0, len(idxKeys)) - require.Equal(t, int64(0), estSize) - }) - } - }) -} - -func TestDeleteWithoutSyncWrite(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - kv, err := Open(opt) - if err != nil { - t.Error(err) - t.Fail() - } - - key := []byte("k1") - // Set a value with size > value threshold so that its written to value log. - txnSet(t, kv, key, []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789FOOBARZOGZOG"), 0x00) - txnDelete(t, kv, key) - kv.Close() - - // Reopen KV - kv, err = Open(opt) - if err != nil { - t.Error(err) - t.Fail() - } - defer kv.Close() - - require.NoError(t, kv.View(func(txn *Txn) error { - _, err := txn.Get(key) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) -} - -func TestPidFile(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Reopen database - _, err := Open(getTestOptions(db.opt.Dir)) - require.Error(t, err) - require.Contains(t, err.Error(), "Another process is using this Badger database") - }) -} - -func TestBigKeyValuePairs(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - bigK := make([]byte, maxKeySize+1) - bigV := make([]byte, db.opt.ValueLogFileSize+1) - small := make([]byte, 10) - - txn := db.NewTransaction(true) - require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, small)) - require.Regexp(t, regexp.MustCompile("Value.*exceeded"), txn.Set(small, bigV)) - - require.NoError(t, txn.Set(small, small)) - require.Regexp(t, regexp.MustCompile("Key.*exceeded"), txn.Set(bigK, bigV)) - - require.NoError(t, db.View(func(txn *Txn) error { - _, err := txn.Get(small) - require.Equal(t, ErrKeyNotFound, err) - return nil - })) - }) -} - -func TestIteratorPrefetchSize(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - n := 100 - for i := 0; i < n; i++ { - // if (i % 10) == 0 { - // t.Logf("Put i=%d\n", i) - // } - txnSet(t, db, bkey(i), bval(i), byte(i%127)) - } - - getIteratorCount := func(prefetchSize int) int { - opt := IteratorOptions{} - opt.PrefetchValues = true - opt.PrefetchSize = prefetchSize - - var count int - txn := db.NewTransaction(false) - it := txn.NewIterator(opt) - { - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - require.EqualValues(t, n, count) - } - return count - } - - var sizes = []int{-10, 0, 1, 10} - for _, size := range sizes { - c := getIteratorCount(size) - require.Equal(t, 100, c) - } - }) -} - -func TestSetIfAbsentAsync(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - kv, _ := Open(getTestOptions(dir)) - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - - f := func(err error) {} - - n := 1000 - for i := 0; i < n; i++ { - // if (i % 10) == 0 { - // t.Logf("Put i=%d\n", i) - // } - txn := kv.NewTransaction(true) - _, err = txn.Get(bkey(i)) - require.Equal(t, ErrKeyNotFound, err) - require.NoError(t, txn.SetWithMeta(bkey(i), nil, byte(i%127))) - require.NoError(t, txn.Commit(f)) - } - - require.NoError(t, kv.Close()) - kv, err = Open(getTestOptions(dir)) - require.NoError(t, err) - - opt := DefaultIteratorOptions - txn := kv.NewTransaction(false) - var count int - it := txn.NewIterator(opt) - { - t.Log("Starting first basic iteration") - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - require.EqualValues(t, n, count) - } - require.Equal(t, n, count) - require.NoError(t, kv.Close()) -} - -func TestGetSetRace(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - - data := make([]byte, 4096) - _, err := rand.Read(data) - require.NoError(t, err) - - var ( - numOp = 100 - wg sync.WaitGroup - keyCh = make(chan string) - ) - - // writer - wg.Add(1) - go func() { - defer func() { - wg.Done() - close(keyCh) - }() - - for i := 0; i < numOp; i++ { - key := fmt.Sprintf("%d", i) - txnSet(t, db, []byte(key), data, 0x00) - keyCh <- key - } - }() - - // reader - wg.Add(1) - go func() { - defer wg.Done() - - for key := range keyCh { - require.NoError(t, db.View(func(txn *Txn) error { - item, err := txn.Get([]byte(key)) - require.NoError(t, err) - _, err = item.Value() - require.NoError(t, err) - return nil - })) - } - }() - - wg.Wait() - }) -} - -func TestDiscardVersionsBelow(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Write 4 versions of the same key - for i := 0; i < 4; i++ { - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte("answer"), []byte(fmt.Sprintf("%d", i))) - }) - require.NoError(t, err) - } - - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - - // Verify that there are 4 versions, and record 3rd version (2nd from top in iteration) - db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer"), item.Key()) - if item.DiscardEarlierVersions() { - break - } - } - require.Equal(t, 4, count) - return nil - }) - - // Set new version and discard older ones. - err := db.Update(func(txn *Txn) error { - return txn.SetWithDiscard([]byte("answer"), []byte("5"), 0) - }) - require.NoError(t, err) - - // Verify that there are only 2 versions left, and versions - // below ts have been deleted. - db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer"), item.Key()) - if item.DiscardEarlierVersions() { - break - } - } - require.Equal(t, 1, count) - return nil - }) - }) -} - -func TestExpiry(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // Write two keys, one with a TTL - err := db.Update(func(txn *Txn) error { - return txn.Set([]byte("answer1"), []byte("42")) - }) - require.NoError(t, err) - - err = db.Update(func(txn *Txn) error { - return txn.SetWithTTL([]byte("answer2"), []byte("43"), 1*time.Second) - }) - require.NoError(t, err) - - time.Sleep(2 * time.Second) - - // Verify that only unexpired key is found during iteration - err = db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("answer1")) - require.NoError(t, err) - - _, err = txn.Get([]byte("answer2")) - require.Equal(t, ErrKeyNotFound, err) - return nil - }) - require.NoError(t, err) - - // Verify that only one key is found during iteration - opts := DefaultIteratorOptions - opts.PrefetchValues = false - err = db.View(func(txn *Txn) error { - it := txn.NewIterator(opts) - var count int - for it.Rewind(); it.Valid(); it.Next() { - count++ - item := it.Item() - require.Equal(t, []byte("answer1"), item.Key()) - } - require.Equal(t, 1, count) - return nil - }) - require.NoError(t, err) - }) -} - -func randBytes(n int) []byte { - recv := make([]byte, n) - in, err := rand.Read(recv) - if err != nil { - log.Fatal(err) - } - return recv[:in] -} - -var benchmarkData = []struct { - key, value []byte -}{ - {randBytes(100), nil}, - {randBytes(1000), []byte("foo")}, - {[]byte("foo"), randBytes(1000)}, - {[]byte(""), randBytes(1000)}, - {nil, randBytes(1000000)}, - {randBytes(100000), nil}, - {randBytes(1000000), nil}, -} - -func TestLargeKeys(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := new(Options) - *opts = DefaultOptions - opts.ValueLogFileSize = 1024 * 1024 * 1024 - opts.Dir = dir - opts.ValueDir = dir - - db, err := Open(*opts) - if err != nil { - t.Fatal(err) - } - for i := 0; i < 1000; i++ { - tx := db.NewTransaction(true) - for _, kv := range benchmarkData { - k := make([]byte, len(kv.key)) - copy(k, kv.key) - - v := make([]byte, len(kv.value)) - copy(v, kv.value) - if err := tx.Set(k, v); err != nil { - // Skip over this record. - } - } - if err := tx.Commit(nil); err != nil { - t.Fatalf("#%d: batchSet err: %v", i, err) - } - } -} - -func TestCreateDirs(t *testing.T) { - dir, err := ioutil.TempDir("", "parent") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := DefaultOptions - dir = filepath.Join(dir, "badger") - opts.Dir = dir - opts.ValueDir = dir - db, err := Open(opts) - require.NoError(t, err) - db.Close() - _, err = os.Stat(dir) - require.NoError(t, err) -} - -func TestGetSetDeadlock(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - fmt.Println(dir) - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - opt.ValueLogFileSize = 1 << 20 - db, err := Open(opt) - require.NoError(t, err) - defer db.Close() - - val := make([]byte, 1<<19) - key := []byte("key1") - require.NoError(t, db.Update(func(txn *Txn) error { - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - return nil - })) - - timeout, done := time.After(10*time.Second), make(chan bool) - - go func() { - db.Update(func(txn *Txn) error { - item, err := txn.Get(key) - require.NoError(t, err) - _, err = item.Value() // This take a RLock on file - require.NoError(t, err) - - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - require.NoError(t, txn.Set([]byte("key2"), val)) - return nil - }) - done <- true - }() - - select { - case <-timeout: - t.Fatal("db.Update did not finish within 10s, assuming deadlock.") - case <-done: - t.Log("db.Update finished.") - } -} - -func TestWriteDeadlock(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - fmt.Println(dir) - require.NoError(t, err) - defer os.RemoveAll(dir) - - opt := DefaultOptions - opt.Dir = dir - opt.ValueDir = dir - opt.ValueLogFileSize = 10 << 20 - db, err := Open(opt) - require.NoError(t, err) - - print := func(count *int) { - *count++ - if *count%100 == 0 { - fmt.Printf("%05d\r", *count) - } - } - - var count int - val := make([]byte, 10000) - require.NoError(t, db.Update(func(txn *Txn) error { - for i := 0; i < 1500; i++ { - key := fmt.Sprintf("%d", i) - rand.Read(val) - require.NoError(t, txn.Set([]byte(key), val)) - print(&count) - } - return nil - })) - - count = 0 - fmt.Println("\nWrites done. Iteration and updates starting...") - err = db.Update(func(txn *Txn) error { - opt := DefaultIteratorOptions - opt.PrefetchValues = false - it := txn.NewIterator(opt) - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - - // Using Value() would cause deadlock. - // item.Value() - out, err := item.ValueCopy(nil) - require.NoError(t, err) - require.Equal(t, len(val), len(out)) - - key := y.Copy(item.Key()) - rand.Read(val) - require.NoError(t, txn.Set(key, val)) - print(&count) - } - return nil - }) - require.NoError(t, err) -} - -func TestSequence(t *testing.T) { - key0 := []byte("seq0") - key1 := []byte("seq1") - - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - seq0, err := db.GetSequence(key0, 10) - require.NoError(t, err) - seq1, err := db.GetSequence(key1, 100) - require.NoError(t, err) - - for i := uint64(0); i < uint64(105); i++ { - num, err := seq0.Next() - require.NoError(t, err) - require.Equal(t, i, num) - - num, err = seq1.Next() - require.NoError(t, err) - require.Equal(t, i, num) - } - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key0) - if err != nil { - return err - } - val, err := item.Value() - if err != nil { - return err - } - num0 := binary.BigEndian.Uint64(val) - require.Equal(t, uint64(110), num0) - - item, err = txn.Get(key1) - if err != nil { - return err - } - val, err = item.Value() - if err != nil { - return err - } - num1 := binary.BigEndian.Uint64(val) - require.Equal(t, uint64(200), num1) - return nil - }) - require.NoError(t, err) - }) -} - -func TestSequence_Release(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - // get sequence, use once and release - key := []byte("key") - seq, err := db.GetSequence(key, 1000) - require.NoError(t, err) - num, err := seq.Next() - require.NoError(t, err) - require.Equal(t, uint64(0), num) - require.NoError(t, seq.Release()) - - // we used up 0 and 1 should be stored now - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key) - if err != nil { - return err - } - val, err := item.Value() - if err != nil { - return err - } - require.Equal(t, num+1, binary.BigEndian.Uint64(val)) - return nil - }) - require.NoError(t, err) - - // using it again will lease 1+1000 - num, err = seq.Next() - require.NoError(t, err) - require.Equal(t, uint64(1), num) - err = db.View(func(txn *Txn) error { - item, err := txn.Get(key) - if err != nil { - return err - } - val, err := item.Value() - if err != nil { - return err - } - require.Equal(t, uint64(1001), binary.BigEndian.Uint64(val)) - return nil - }) - require.NoError(t, err) - }) -} - -func uint64ToBytes(i uint64) []byte { - var buf [8]byte - binary.BigEndian.PutUint64(buf[:], i) - return buf[:] -} - -func bytesToUint64(b []byte) uint64 { - return binary.BigEndian.Uint64(b) -} - -// Merge function to add two uint64 numbers -func add(existing, new []byte) []byte { - return uint64ToBytes( - bytesToUint64(existing) + - bytesToUint64(new)) -} - -func TestMergeOperatorGetBeforeAdd(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - _, err := m.Get() - require.Equal(t, ErrKeyNotFound, err) - }) -} - -func TestMergeOperatorBeforeAdd(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - time.Sleep(time.Second) - }) -} - -func TestMergeOperatorAddAndGet(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestMergeOperatorCompactBeforeGet(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 200*time.Millisecond) - defer m.Stop() - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - time.Sleep(250 * time.Millisecond) // wait for merge to happen - - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestMergeOperatorGetAfterStop(t *testing.T) { - key := []byte("merge") - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - m := db.GetMergeOperator(key, add, 1*time.Second) - - err := m.Add(uint64ToBytes(1)) - require.NoError(t, err) - m.Add(uint64ToBytes(2)) - require.NoError(t, err) - m.Add(uint64ToBytes(3)) - require.NoError(t, err) - - m.Stop() - res, err := m.Get() - require.NoError(t, err) - require.Equal(t, uint64(6), bytesToUint64(res)) - }) -} - -func TestReadOnly(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - opts := getTestOptions(dir) - - // Create the DB - db, err := Open(opts) - require.NoError(t, err) - for i := 0; i < 10000; i++ { - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i)), 0x00) - } - - // Attempt a read-only open while it's open read-write. - opts.ReadOnly = true - _, err = Open(opts) - require.Error(t, err) - if err == ErrWindowsNotSupported { - return - } - require.Contains(t, err.Error(), "Another process is using this Badger database") - db.Close() - - // Open one read-only - opts.ReadOnly = true - kv1, err := Open(opts) - require.NoError(t, err) - defer kv1.Close() - - // Open another read-only - kv2, err := Open(opts) - require.NoError(t, err) - defer kv2.Close() - - // Attempt a read-write open while it's open for read-only - opts.ReadOnly = false - _, err = Open(opts) - require.Error(t, err) - require.Contains(t, err.Error(), "Another process is using this Badger database") - - // Get a thing from the DB - txn1 := kv1.NewTransaction(true) - v1, err := txn1.Get([]byte("key1")) - require.NoError(t, err) - b1, err := v1.Value() - require.NoError(t, err) - require.Equal(t, b1, []byte("value1")) - err = txn1.Commit(nil) - require.NoError(t, err) - - // Get a thing from the DB via the other connection - txn2 := kv2.NewTransaction(true) - v2, err := txn2.Get([]byte("key2000")) - require.NoError(t, err) - b2, err := v2.Value() - require.NoError(t, err) - require.Equal(t, b2, []byte("value2000")) - err = txn2.Commit(nil) - require.NoError(t, err) - - // Attempt to set a value on a read-only connection - txn := kv1.NewTransaction(true) - err = txn.SetWithMeta([]byte("key"), []byte("value"), 0x00) - require.Error(t, err) - require.Contains(t, err.Error(), "No sets or deletes are allowed in a read-only transaction") - err = txn.Commit(nil) - require.NoError(t, err) -} - -func TestLSMOnly(t *testing.T) { - dir, err := ioutil.TempDir("", "badger") - require.NoError(t, err) - defer os.RemoveAll(dir) - - opts := LSMOnlyOptions - opts.Dir = dir - opts.ValueDir = dir - - dopts := DefaultOptions - require.NotEqual(t, dopts.ValueThreshold, opts.ValueThreshold) - require.NotEqual(t, dopts.ValueLogLoadingMode, opts.ValueLogLoadingMode) - require.NotEqual(t, dopts.ValueLogFileSize, opts.ValueLogFileSize) - - dopts.ValueThreshold = 1 << 16 - _, err = Open(dopts) - require.Equal(t, ErrValueThreshold, err) - - db, err := Open(opts) - require.NoError(t, err) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - for i := 0; i < 5000; i++ { - value := make([]byte, 64000) - _, err = rand.Read(value) - require.NoError(t, err) - - txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), value, 0x00) - } - require.NoError(t, db.RunValueLogGC(0.2)) -} - -func TestMinReadTs(t *testing.T) { - runBadgerTest(t, nil, func(t *testing.T, db *DB) { - for i := 0; i < 10; i++ { - require.NoError(t, db.Update(func(txn *Txn) error { - return txn.Set([]byte("x"), []byte("y")) - })) - } - time.Sleep(time.Millisecond) - require.Equal(t, uint64(10), db.orc.readTs()) - min := db.orc.readMark.MinReadTs() - require.Equal(t, uint64(9), min) - - readTxn := db.NewTransaction(false) - for i := 0; i < 10; i++ { - require.NoError(t, db.Update(func(txn *Txn) error { - return txn.Set([]byte("x"), []byte("y")) - })) - } - require.Equal(t, uint64(20), db.orc.readTs()) - time.Sleep(time.Millisecond) - require.Equal(t, min, db.orc.readMark.MinReadTs()) - readTxn.Discard() - time.Sleep(time.Millisecond) - require.Equal(t, uint64(19), db.orc.readMark.MinReadTs()) - - for i := 0; i < 10; i++ { - db.View(func(txn *Txn) error { - return nil - }) - } - time.Sleep(time.Millisecond) - require.Equal(t, uint64(20), db.orc.readMark.MinReadTs()) - }) -} - -func ExampleOpen() { - dir, err := ioutil.TempDir("", "badger") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - opts := DefaultOptions - opts.Dir = dir - opts.ValueDir = dir - db, err := Open(opts) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - err = db.View(func(txn *Txn) error { - _, err := txn.Get([]byte("key")) - // We expect ErrKeyNotFound - fmt.Println(err) - return nil - }) - - if err != nil { - log.Fatal(err) - } - - txn := db.NewTransaction(true) // Read-write txn - err = txn.Set([]byte("key"), []byte("value")) - if err != nil { - log.Fatal(err) - } - err = txn.Commit(nil) - if err != nil { - log.Fatal(err) - } - - err = db.View(func(txn *Txn) error { - item, err := txn.Get([]byte("key")) - if err != nil { - return err - } - val, err := item.Value() - if err != nil { - return err - } - fmt.Printf("%s\n", string(val)) - return nil - }) - - if err != nil { - log.Fatal(err) - } - - // Output: - // Key not found - // value -} - -func ExampleTxn_NewIterator() { - dir, err := ioutil.TempDir("", "badger") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - - opts := DefaultOptions - opts.Dir = dir - opts.ValueDir = dir - - db, err := Open(opts) - if err != nil { - log.Fatal(err) - } - defer db.Close() - - bkey := func(i int) []byte { - return []byte(fmt.Sprintf("%09d", i)) - } - bval := func(i int) []byte { - return []byte(fmt.Sprintf("%025d", i)) - } - - txn := db.NewTransaction(true) - - // Fill in 1000 items - n := 1000 - for i := 0; i < n; i++ { - err := txn.Set(bkey(i), bval(i)) - if err != nil { - log.Fatal(err) - } - } - - err = txn.Commit(nil) - if err != nil { - log.Fatal(err) - } - - opt := DefaultIteratorOptions - opt.PrefetchSize = 10 - - // Iterate over 1000 items - var count int - err = db.View(func(txn *Txn) error { - it := txn.NewIterator(opt) - for it.Rewind(); it.Valid(); it.Next() { - count++ - } - return nil - }) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Counted %d elements", count) - // Output: - // Counted 1000 elements -} diff --git a/vendor/github.com/dgraph-io/badger/dir_unix.go b/vendor/github.com/dgraph-io/badger/dir_unix.go deleted file mode 100644 index a5e0fa3..0000000 --- a/vendor/github.com/dgraph-io/badger/dir_unix.go +++ /dev/null @@ -1,100 +0,0 @@ -// +build !windows - -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - "golang.org/x/sys/unix" -) - -// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part -// of the locking mechanism, it's just advisory. -type directoryLockGuard struct { - // File handle on the directory, which we've flocked. - f *os.File - // The absolute path to our pid file. - path string - // Was this a shared lock for a read-only database? - readOnly bool -} - -// acquireDirectoryLock gets a lock on the directory (using flock). If -// this is not read-only, it will also write our pid to -// dirPath/pidFileName for convenience. -func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { - // Convert to absolute path so that Release still works even if we do an unbalanced - // chdir in the meantime. - absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) - if err != nil { - return nil, errors.Wrap(err, "cannot get absolute path for pid lock file") - } - f, err := os.Open(dirPath) - if err != nil { - return nil, errors.Wrapf(err, "cannot open directory %q", dirPath) - } - opts := unix.LOCK_EX | unix.LOCK_NB - if readOnly { - opts = unix.LOCK_SH | unix.LOCK_NB - } - - err = unix.Flock(int(f.Fd()), opts) - if err != nil { - f.Close() - return nil, errors.Wrapf(err, - "Cannot acquire directory lock on %q. Another process is using this Badger database.", - dirPath) - } - - if !readOnly { - // Yes, we happily overwrite a pre-existing pid file. We're the - // only read-write badger process using this directory. - err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) - if err != nil { - f.Close() - return nil, errors.Wrapf(err, - "Cannot write pid file %q", absPidFilePath) - } - } - return &directoryLockGuard{f, absPidFilePath, readOnly}, nil -} - -// Release deletes the pid file and releases our lock on the directory. -func (guard *directoryLockGuard) release() error { - var err error - if !guard.readOnly { - // It's important that we remove the pid file first. - err = os.Remove(guard.path) - } - - if closeErr := guard.f.Close(); err == nil { - err = closeErr - } - guard.path = "" - guard.f = nil - - return err -} - -// openDir opens a directory for syncing. -func openDir(path string) (*os.File, error) { return os.Open(path) } diff --git a/vendor/github.com/dgraph-io/badger/dir_windows.go b/vendor/github.com/dgraph-io/badger/dir_windows.go deleted file mode 100644 index 80de84e..0000000 --- a/vendor/github.com/dgraph-io/badger/dir_windows.go +++ /dev/null @@ -1,94 +0,0 @@ -// +build windows - -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -// OpenDir opens a directory in windows with write access for syncing. -import ( - "fmt" - "os" - "path/filepath" - "syscall" - - "github.com/pkg/errors" -) - -func openDir(path string) (*os.File, error) { - fd, err := openDirWin(path) - if err != nil { - return nil, err - } - return os.NewFile(uintptr(fd), path), nil -} - -func openDirWin(path string) (fd syscall.Handle, err error) { - if len(path) == 0 { - return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND - } - pathp, err := syscall.UTF16PtrFromString(path) - if err != nil { - return syscall.InvalidHandle, err - } - access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE) - sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE) - createmode := uint32(syscall.OPEN_EXISTING) - fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) - return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0) -} - -// DirectoryLockGuard holds a lock on the directory. -type directoryLockGuard struct { - path string -} - -// AcquireDirectoryLock acquires exclusive access to a directory. -func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { - if readOnly { - return nil, ErrWindowsNotSupported - } - - // Convert to absolute path so that Release still works even if we do an unbalanced - // chdir in the meantime. - absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) - if err != nil { - return nil, errors.Wrap(err, "Cannot get absolute path for pid lock file") - } - - f, err := os.OpenFile(absLockFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return nil, errors.Wrapf(err, - "Cannot create pid lock file %q. Another process is using this Badger database", - absLockFilePath) - } - _, err = fmt.Fprintf(f, "%d\n", os.Getpid()) - closeErr := f.Close() - if err != nil { - return nil, errors.Wrap(err, "Cannot write to pid lock file") - } - if closeErr != nil { - return nil, errors.Wrap(closeErr, "Cannot close pid lock file") - } - return &directoryLockGuard{path: absLockFilePath}, nil -} - -// Release removes the directory lock. -func (g *directoryLockGuard) release() error { - path := g.path - g.path = "" - return os.Remove(path) -} diff --git a/vendor/github.com/dgraph-io/badger/doc.go b/vendor/github.com/dgraph-io/badger/doc.go deleted file mode 100644 index 83dc9a2..0000000 --- a/vendor/github.com/dgraph-io/badger/doc.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Package badger implements an embeddable, simple and fast key-value database, -written in pure Go. It is designed to be highly performant for both reads and -writes simultaneously. Badger uses Multi-Version Concurrency Control (MVCC), and -supports transactions. It runs transactions concurrently, with serializable -snapshot isolation guarantees. - -Badger uses an LSM tree along with a value log to separate keys from values, -hence reducing both write amplification and the size of the LSM tree. This -allows LSM tree to be served entirely from RAM, while the values are served -from SSD. - - -Usage - -Badger has the following main types: DB, Txn, Item and Iterator. DB contains -keys that are associated with values. It must be opened with the appropriate -options before it can be accessed. - -All operations happen inside a Txn. Txn represents a transaction, which can -be read-only or read-write. Read-only transactions can read values for a -given key (which are returned inside an Item), or iterate over a set of -key-value pairs using an Iterator (which are returned as Item type values as -well). Read-write transactions can also update and delete keys from the DB. - -See the examples for more usage details. -*/ -package badger diff --git a/vendor/github.com/dgraph-io/badger/errors.go b/vendor/github.com/dgraph-io/badger/errors.go deleted file mode 100644 index c567d3c..0000000 --- a/vendor/github.com/dgraph-io/badger/errors.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2017 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package badger - -import ( - "encoding/hex" - - "github.com/pkg/errors" -) - -var ( - // ErrValueLogSize is returned when opt.ValueLogFileSize option is not within the valid - // range. - ErrValueLogSize = errors.New("Invalid ValueLogFileSize, must be between 1MB and 2GB") - - // ErrValueThreshold is returned when ValueThreshold is set to a value close to or greater than - // uint16. - ErrValueThreshold = errors.New("Invalid ValueThreshold, must be lower than uint16.") - - // ErrKeyNotFound is returned when key isn't found on a txn.Get. - ErrKeyNotFound = errors.New("Key not found") - - // ErrTxnTooBig is returned if too many writes are fit into a single transaction. - ErrTxnTooBig = errors.New("Txn is too big to fit into one request") - - // ErrConflict is returned when a transaction conflicts with another transaction. This can happen if - // the read rows had been updated concurrently by another transaction. - ErrConflict = errors.New("Transaction Conflict. Please retry") - - // ErrReadOnlyTxn is returned if an update function is called on a read-only transaction. - ErrReadOnlyTxn = errors.New("No sets or deletes are allowed in a read-only transaction") - - // ErrDiscardedTxn is returned if a previously discarded transaction is re-used. - ErrDiscardedTxn = errors.New("This transaction has been discarded. Create a new one") - - // ErrEmptyKey is returned if an empty key is passed on an update function. - ErrEmptyKey = errors.New("Key cannot be empty") - - // ErrRetry is returned when a log file containing the value is not found. - // This usually indicates that it may have been garbage collected, and the - // operation needs to be retried. - ErrRetry = errors.New("Unable to find log file. Please retry") - - // ErrThresholdZero is returned if threshold is set to zero, and value log GC is called. - // In such a case, GC can't be run. - ErrThresholdZero = errors.New( - "Value log GC can't run because threshold is set to zero") - - // ErrNoRewrite is returned if a call for value log GC doesn't result in a log file rewrite. - ErrNoRewrite = errors.New( - "Value log GC attempt didn't result in any cleanup") - - // ErrRejected is returned if a value log GC is called either while another GC is running, or - // after DB::Close has been called. - ErrRejected = errors.New("Value log GC request rejected") - - // ErrInvalidRequest is returned if the user request is invalid. - ErrInvalidRequest = errors.New("Invalid request") - - // ErrManagedTxn is returned if the user tries to use an API which isn't - // allowed due to external management of transactions, when using ManagedDB. - ErrManagedTxn = errors.New( - "Invalid API request. Not allowed to perform this action using ManagedDB") - - // ErrInvalidDump if a data dump made previously cannot be loaded into the database. - ErrInvalidDump = errors.New("Data dump cannot be read") - - // ErrZeroBandwidth is returned if the user passes in zero bandwidth for sequence. - ErrZeroBandwidth = errors.New("Bandwidth must be greater than zero") - - // ErrInvalidLoadingMode is returned when opt.ValueLogLoadingMode option is not - // within the valid range - ErrInvalidLoadingMode = errors.New("Invalid ValueLogLoadingMode, must be FileIO or MemoryMap") - - // ErrReplayNeeded is returned when opt.ReadOnly is set but the - // database requires a value log replay. - ErrReplayNeeded = errors.New("Database was not properly closed, cannot open read-only") - - // ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows - ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows") - - // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of - // corrupt data to allow Badger to run properly. - ErrTruncateNeeded = errors.New("Value log truncate required to run DB. This might result in data loss.") -) - -// Key length can't be more than uint16, as determined by table::header. -const maxKeySize = 1<<16 - 8 // 8 bytes are for storing timestamp - -func exceedsMaxKeySizeError(key []byte) error { - return errors.Errorf("Key with size %d exceeded %d limit. Key:\n%s", - len(key), maxKeySize, hex.Dump(key[:1<<10])) -} - -func exceedsMaxValueSizeError(value []byte, maxValueSize int64) error { - return errors.Errorf("Value with size %d exceeded ValueLogFileSize (%d). Key:\n%s", - len(value), maxValueSize, hex.Dump(value[:1<<10])) -} diff --git a/vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png b/vendor/github.com/dgraph-io/badger/images/benchmarks-rocksdb.png deleted file mode 100644 index 27081e8122b857c6f0dade69c5e837c74229ba10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66938 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

    O~p2fT%OZppHBrdo-hWU~q+ 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 &n9PjlYsugMZ;@_v)xEa+HpQ+)1ClhgU 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

    V$ zB~KWoNMCEuQ64QW-!oL~zdCTSD9OdcC)w^YX8}PrTLkGCrDmBz@0n;p zBTtp*#CUp4+4!fQ(ZQ+)@|$$!+DalBid%UNPW`Oo1l4-0IVvDjo>aPrkC*lW?wFWDIZ(FNXJjl zLtk(+0zyr=vZ{_9GrZXEo+ur!C0mw>p=qKJq}xCQ-#X3vvI6WiGQ zH9o$VSR3mfLNyw}VTnP{2Es2$KaTid@~Ef*%9rdPlp&!*&6_;BS=F5r9=#{v$1z-@ z;+LTh@J90T^_$kOBHpfAqwFC-D^x;yM6&E2w6Yf44{xq3wpzc@q0KOnGau#?ht(9n zYNC9P7$`7R_c~YNXLd`i>K)TO0Gy8*4VAsJv1G`-f{|FQD`uRzXB_QqZeI}a?C4tT z8Pr_wV_TUBOKc*Y-nb&4*jzGBHL+CHFI%nw886&?o>8Ajq}686HZ%U~{g|$6S9c-q z65ot`n@=92fwF?8ZM>#_?eXaqXQ@d$5;nG>k4{oOkcwq8=;5t!&W7~CA-6tz)6gzV zMybiVh4#4NhDON}#zgjeOSsF88JS#TqNLO0r!7N5EMAKw+o;w_*?pLNSNE zYsULLxm%psHVaFVgdAW+E~5%^7r$ZZbryIx!`@h;5{&7IIxIQS0;~kA_9hSL(Ovgv z?`W=r!1ildSqJ`GfdEKa&~#q$gw8VxfIbJ<0RLt4`@y`f+R@|203z?QzKr5b8j~IA z!+?3BoGQ*x0B=qWB+>b6lFh#!{hr20^Uq_8#M2s5&?b=@&dzmB_O+SnBMk*@~=-)37vG|L4x8_S72t zcEI$rBckw>YfYHM`~iwSO_KI|lyGyXxxeacXU{Do@I4z_H~`*5X+1C1T$mrER`iu^ zFG%e7&*xJf^yn^IQJ(pqPcjUp++JwU(0FQNcg3I}l#AJl7guM2Q~}PJueY~(sfS>V zY4@A%I2}z>rr}1pXy%^c0p8rbC?{Ey-f72jqMWo2^X`l`05m`1 zI2BCIHaJZ?WtUX)TU}!XN#bjI+ZToebuiL88L+q4bE~n2cX)rni4fe0OFcYkml~zp zc1s}k8uHNJ8c16YpRL9C`OBMukq`4D5ac6kE^yzfQXyL&;rQXna&j|W%MQN?f$>~f zj`Ugo&FI%WbFgc()GR#|;Ro^+knkw*7Bh=nnA*kF<+&yQ&}mNL4ZeoZl7Q5?Pd{26 zU$UV&H*Da6(~(_#Gru_sVP3H(uV<#xPp|h#^g8vBOHYlA!gs~ACI{Lurre*CT>I8* zZ(jLScc|vZuIrK|HY2`ia#4aUQa|`6_mPbkeh*$%ltZ?~ugK>gFHc3) zD!P?_urcPre=CAeJAu&*lH?M{T`merqzf9gSlwlYh^P!yYEanfUi+i=-juhhEC#H= zBZT%R?N{(trCOdb8`wrg_k`9icPU5`ML^rSP@Z+b+)P3%!5z zd-Y_S3&)HYkc2R(=1kXk5*}GTYTDz5&&Ttb5S1prnm?1$I?mdYd!a;syzhzbKY;nS z%0*o@Qk=C!yZGO6Em77!j1EnB@m=;SpdXFZ1(x?!9Bl1roBH*7q9hRw2tmKhf9p{3 z0bEL-8ub`t_^1%T7u%JO+k$Nbz0OzynKJzv_SRt@$6T!bt&64(7R2_eR=eV*{`JmiD zg+tv7?US57u#ilC=hJH0DK>+(aDgP}Q=?~h_aPNiK!;7?~Gk zllL#GEbR3z$us${bvJHZsfS5hS@Vp9(nnv?iIE3(5Xztb*0SA|-*&hnTZo1# zP5=ox=v!H5lpg4LC564)M+cJ7&$P;PIbB#DbBu_aT{pjnUj{w#h@2Ct4&5zII`hcg z-BVlXyzBM48XMuM(7w2@OId*idD&Eng`vD z)s#ScWMTk6KrZro=%c#V$;b2$r=B_p4JJcZT-zbLW=QniU`BV`<+;g<#J(665Gx}z zU*wCKNqP>I4Yo0#iKTT5oo}{nKqb%1h3(3#D@8%$_ zld2Vs_ikDG8bNu5BD7wF$D*90D!V!~xx#BD69aH1Ka`MM6WO#n$y$PyEIvdjRgN}8s+MytzCKGbz%n1EtS@cQ~Ga3C0;(&m&c!! zsFr)i6{ew{(n0mfgREn81Gs)nJvzan=q72Z(sPX|LZ1+b7W$-gpzE|c#(5%)bu!@A zLLj>pf^N`t#LCR6&+)B22kSb&4p0+2*tI~q0N>A*8{c$dH!P$2z`EoG8%!yYKgh3Lo}7@r0)zt(=<8t!hJQ^zGjV(e?$MQ}Fekv%f|`Hb%e+-?iw*bGO>Qpd z>(J=pTA5jUhE!vO7j?emUK_iSF?c+q7hCi@%$TQRPWps)BeLplKi#(5$77stQ6qP$ zK2*pMF)jj-xA_PpS!?}4yyG@73X^b9pC?M+;a^Rp8HOev?|l>|pq|zC zTuI7fiD91rcR}8mfu8q9PB_A;)(rrv-5MxOTDGCwx$Pr}%U&9!5ff5X7{Tg@uTW6; z?@pJ(4t4yZOZUhRKOa5d_soC{&NZ?S{Qr-LSwlzCz8d#-O~PMp3IW%D;oynhTfFi+ zFMl4R)mrUe1Q^&)%f9QtoUFTU;wACl@}}1&2_+9*SY|A*9IJEId~Fu37F5-?_CVW? z)_GpC^yAtTvQUg}fXw@c=ZebbTR&)DBg|(Vs6dURLv_Y6Kr3bZ-Q@y(=ejaP^3ec( zpp_;L)d_9YF0s%)jK=G8L1ez=Ci7>(v*?imjE)Lb?ah)l9XM)tu6Iyh6UH<5I>K_} zrLd9_Lif8HlKYO3hi!KpF34`3rAydtlW}sBC&zUr$U|$-wCZsz&dRVa258_4L?4VZ zWQnA`)+O|G+|=IIt$niJ`R4wI#q<`n;4nsD(@aBPS+ylc!-$hCdlWG*K^M8d zxKOERnz+sPo@L2`WhDy4rPn(EQUkJ3FmL3F-s7^={zFe?fs2qd6to;QodKje%=VPl zp5}x@)pfwZ|8E8DIVl*-^hq||tyP-y2_lmgL%EXrqsW970A|AHx2LH-RxvLVp+>I3 ze(PC34Fd?~)E=`qC8OrSD1DeFNIu)rfU*adX9Z0Es(IAy5V^?IVT}F&b}>rmz6|Fp zQYK&`FWlHD`|xV7F2GqIFqHoXYa+_;>vtF=<^>~A-?B_Wjs);idt&lH9c}B;y3(?{ z9u&e|Ns3IeUd^mLdp*yrD`!H#s|4qQ9jd3ioM{Eu7iMslcJaYnu2&es`aVgmg(4iI z4^8du{kwBu&-*!yy;=|BjtatxqARXG64{739}hxL`u+?ZdRBGyyZ_~%6dWWcXxO6F z$gjc*WX-h8Z4>q0+BAlM`E_{hzkM!Sela$b z`6>_0Bt_wj#7=M`cZ=&7^wW+bkT&kN&y3ZdiyGz2wGl_<$M=-nxrsFsQ-(^%CDksz znHv9#rZx<4ZFtlu$|4g?e*n;5yDf8lUQ5{uFA_ub^8MjRi)v#PTizqYLsUa8ZHZ9nRfY~ zTgUoRW3^%n0_sV?I+vr1ox)*uY2)5|*?Q+ts$bOQ^ z(Ifi-3%b@ZGh#Fl?%U0`*&$)K%3Ff$`Ac#}|9g^pz=Ldugw6M18lk?cK16=^AiB{x zH(=MYmLU5~3eK)qivG9f(*%mYLwZ4SLg5u0^TgIZ^{WXb{Z4LpEm=$tWJkfH(?kr! zuqE(Bt1;y^?rhy29sXeSWI$#tWaiF?QZ9h(gh+l_XYLqHYuji@16g{qELjeY`qdAr zxS>5!!DHD&Nm!sAQEE4eh;1py=Lxsofv8OK<;Yy0r4oBa4>Hn3lbDOGXF^XLVa@S&yNgLPrPGQy~OMTBvsVW@5 zQMA<;tSEJM1jSPQJ86IJQ|sqcbp>522EmvKtH=H%HIuw^JVtQg+l(>dkTx(j0-L1Q zB}MjD_Ujg=j03cPPdp=~Cn%1}lR1;F5Jry8`T2c-Um(hLSr zDIwbC!L>>1nlr5u0J~)hvr(qoHF7&N3;!53fsY>U(+1 z!KGdwtQ8K5({m0;&G_8n*HajA;9taI(`6ds4y4x8&x! zagQQOU2(Qp!xLGo^#Y0P3;*3nwZqr~lL^o24;Z-mDTBzW{?t+i0atkAQ3_3~TExVQ zE02lseDpGd#MN&#=_cMt%hlsV=LaLY@_ICrtY|W`<*0tI7vMRFh;_&^zQCDFFwzFw zK^uY~O^*V1=}4BuWh>L}-RCR3d$k37H|J1>e=sy>p28vo)r=|IFPFxW-)yv_`j%_Z zK*gsrA65`Q=&7{QAsc^}h`i|S7=w3@v4RJ{3ve2&zV5YYnaEM0JAX-^*_^CuoLD4& z)xlX|4>5G!d#w6d+j|uAoJGD->&0n+r6VU7-qI}UFofM!iG8qKN5-wCFXTAen zUEn>s$9}v ze3~BWc~+nH`NoDr=hvE(?%YsJjZEEY9Xpu_vz`JuENOtJqw8#MIn;l%j23Vk7$J>1 zR3vt{;Jr*+EPt)%{^eZoKGa&FcpL9O=EsclTlQQie=wD1mE;HG=MQ0BlEL)htJ}@W zxZO9qd&qBl-A&JhCXCd_2#p~tiZC4zxA}QVjYyJ)GdY|ptqx$s-KOm33!7)VP(>F0lz5l&XKb)F+ zGnYIM8XeFB=%$VCjp#*Z)d$~#z~{|_u38t^19QtzrH?4jv?JF~vy@B(Tm(beapK~S za*M4-;dz2(6n;kh*lF0T3V8#4CQUCFgRsKvz{Q|zDEtl@VG{39=1^tlD)2A51{1_` zh50PQ5#jVkE`*Y)?DD%8+)?Rmao7bbq#~p zQ03PW4pYVpuYahpIF^I#yXhjpB%Hu{SDRn$%;yz_{@`KEgIg&hP8|gQPnMgL>8gSo zI$0-A2)s-(@zEf7`=&{Y*tq0g)(o7gro7m%wDu1wzH_KMAY+Aty~F?y#Xo!X1~>t- zsBm^~cQp?``eS(ucIg`|G=8kkXF~?@LtN5(945=OHy{CzF)W8}+;Cbf@Cz{WRJSIv zmAZ^DzgHYUPeyB?RHTe09-u4Y?h|YB3hQo(38#A20p#E=tkG5c2c{c6s3D;nN!7XZ z3Ecw`?qzB2$V-BRAFS{a>MMP^^~5^5-TRbn=A>^cE<5hs+hU{~rJ%-`>mLU$-m|r( zR(qEPZ`)J&)$Q2@0^YHAA?SUKQ1Ruz+05d;BUARqU#z`KjFslY6=51ol)jcRrZk6k zt{!Jj{bI~ho2)o>`mOJ!d0Go}+OKAbQ%y)SwpKn?^VTBg0pdrId_WABU8{z$->k`a z4Mr~`nC&Nl7oDLTnaczSi?T33M2e4dE={IZt;hFPuS!zU8~}31RVa`Ybly)F`Wm_= zPCdM|#{a7EKCE(u1X}bXy5PxkU92$zH-)n;v9*1|^`tcmzsXRHv*E;nXW1ag0GUJW zHR^%3#M>zzAGtOh!s*vlV{Nm|s1CY+(97lxO$s!oPJ@QA6lPRFFLIpDUH31Kbh`Y@ zo>W9)9x?e07(z)uQIm0mR!Q92;^H((otLhy>syO|@d z+V;URD3TM}lG!@JBnW6pe6nbYS&(01HiFV5v`lLX6y)>uv6E(OBkPo@y2xJ4p}Rhm zB}FlqEURBD``mrk1iEKAJQE=$@Pv(7t2ETnS%jlu`B;89(r;G!5^Swl=^&8#yKt?$ zZ~uV$rm>er4(S6~M|7lJLb+6cq4s&eJ@1i0e%Vz@w~PVqJoTt-`(#{xo9!0HOrReJdew(DB>6ynI%1w? zQDb()tz%1zy2W$fxx7h0MFE$_4+%LMQH~Gyn~Uz>d(u+r9ilM20D>2Jr|=PAhq{E# zshc`f?i|AGI-XrC_hbH?C1uWK#F2uCLrwf{2!yPN$`gXsmKB#dXi=*VoaEyXfJb#yXjG@t>_`{FI0@RoCzE!S zTL9H@x+@+0#VkxVMcy5y;U!n$p5V?tS6e1l^#uegdW@XwFF`~_TBANdFP_B^{A|bl zii428r>a^%{O%j=9~uZGWZ+$lK#c{bArIaAJhks#2D`pSpG$5!L{qXjVoaW$S;`)2 zFoOt(1pVg^cB7smx^^&iX|d+nHaUP!K-QKWc;!vuQttWLFrAUv=rpV_8|G)|^^c2H z`vbP(YN&H9nv#*lHyNr~NyM0T6&2UW1iB@q;|NL~ws=zXR&Dq8G0OnHZZty1sEPV+ z;`Yd7k1_p>#;U^J-_Q0mL-9k?TIAgd<==&fpxv8BOpe>Hb-si*d%L&IUL!@69|E#d z!+7wUBbNBwU6dDVPsB=RyTlsx^3gVzESi9p8al7_B$gt)&-V!IaK?Z|zO@n*o z#dQL$VZTIUb2w?&mU81qdHtaR{nh)v-83V52qv+Im%F~hXz8PrJOvzvT1WG2f^pCO zm)q)t;KzdU2$WnnPm^*M)ZYrqL#@ND8G-^fnht$rdV;n$LGD9% zdPx0raq#gsUN_%MekI3iYKrNLj6vxqi(UO2752Ye`edtjY2$`4Jbw8lG#Z&WLjp0> z-b4M0pI(OB^&Xv+?hJDV6_Q*2cdU47_qjN#k}CxBsscuo3A%V^c?i)6#sqmm^#VTy zzo|XcuSX223SaB-ABDvdQMj`M>6=<0*HCj7BLWFt$R04Aw=jNVVA>tI9XLYZqO(m)AlVE?!*7@;-FJ zEZKH_()#Uhrb>OHwuVP$*HFgDV|9;rm-Y7%4^aum!RTWQPQdB%pf%$#29u(y4M$+W_J=a zGZ(^N+=uK{rAEV^*^3bTF>>X;pPS2NXCUC0EQBk-_J%11o%r72+gqc`v<#$zoZ^r@ zQz;iHKIzmqu~c%Idn$`f_;1DQ6Uym>sX$k$^_W;F{af}x!+1RiC?J4vE}|C1_6Fzo z9;d6ti$`M>1amM9dcNTg(=Hb#YxgNS$76Ktq9cszcKkw zhdgO>2EAh{8WFYtY83#t@+nz{>8jiL08(!Ks9!SM8i z=gQ~V>M5PTMmYD=rJy2o2*XXX+5y7 zc$YgXsu4c1F;O?!)rmCwezU!=wvWDMJ?(p<9I|AE;o;E{i%rVaRO;;Q~EuP z3fUJ7HL|^iXJq|R8Eu{t~@0SqmITFh~uUiYKp37s)D~E#2MJLTZxMiVgG0H7$DoT_ty*%p|?2ww3x4a+0 zH|la|#58D_a_T8xbK`%y5+xyv_b6;G^YRgBU^o5sB)b1AA6cKB3k9IQkue|}K3D9V zFU9!h-IyW%)5_v8mRqIO7XsK7&c#2O}Z&`2^!w@MyU(kD_`au9jgBnkNlT=zfgMf)Y#5eoApZJ_NYUq>2IH^c z+eP;gdlF@{EHFM;_-r;jrr_a~c%dxG9UjUw54@m4j2@J;7(8v=~u#}vuGMfT32VJ2UuH=pjEe}0Tqr8Hhf3c zO0r?7E%dOMP&Erdb%B~O2{jC)ypd}0U%M(vkx#bs;)_FSc;P$1S$OSShoQ#snGK;? z$IAPNf7iX%aG?nYR2+?6PJ96`kE_zWp`c^q%=?kZkT@q5^HiMuey%LYaQ?huQ6{*r zwePWrI@{!Wt?@+FbgO+<9W#XQxG4BeeGPF$aVKI(>@uVFI@@!2y2dVVd$a_ z*G%zki63q6`_spgP(8UvR+?ioexZN2(h+11G0 zGp*9Zv^i*8Dv(@&3mXOkQVTEfTf~82NQv1;7?~r}B6wCV&XvEti0ie8)gfr+s_c`s zfb~h(h7H7w>v{(1`1ve`Crnf&A@PFTz{SCq`~d(~h)SPVLL!*<^BT(V+%lMZOd*FC zzLTTQ`QMng&KZ)hi)Q?!|1K}SJ%aT9_iPl2UuM(C*DX|9IK%+g0>gQCIITk51DAb^ z#32pOJUpU=myVYVOF*?QF01kW$lFw+c2zHt)7K__1%)@nQ0Uk#QU}1r#kscF@#RV< zgG4T089N^eb}IALVi3(|Civ-bSs3Lc^vyra17w#`GcWSwG{A4T`|k6+TV{9vsl0)p z`OuCf<-yk<$-*z8CKtbB^4$x2y@HWw|Hy)^?o_L&pgY*bgW{Svj|&||9#d0it6THm zK6b9p{_1m3b65n<#beLSiafM_AmS^MTM)fqtEQF>bx9G{cy`3o-c;}*&r@JMiqf{p zirr0H1fcpSt;FwtVZ?;7H{Ip&oZh)f==unw{JRZIyhHKh9NunkyUiY{{BY+*%18*H zyCUq+FD=>bMW-I86({UU^&#lQz{kD{-8E58RIRnEZ5nX1@EdSR9mEdAZlZukxEZwK z!4;-N^<&{54fd8q5^`WX0)~8I4PdJF>fK_k_P_JEQAHU?Tdvhy08#^B2WkA(if>fD zP*GZMvcI7|1Vh(njCAa>cWmBP@Q9PWh#sPpNnc1mZ6*=!pq(c+qzGw?Y=W0PII?R> zhGk>5f&I6#Evou%_f}i(lr8{si@^ub!)Zu<5#i(WXB=&vT&!d4GA3&f2PvI>{GW!C z(8D@TYcGfLRDIxhZYo1;;B7ESe0v#kvQPrXb6NYus$_@KZtfZw!DI%6 zXv%pwn%^nRE`NEDI!_}iB%!8hS6FB{rCvhQWi`Rvdu+6>=bRl;e_dAfo`SvXe$J0& z#$RaNuX>cvGAT34*V4fH(Mn87B<1SHrTlxKG%60K;Gbh2mvHZ|A7|;EyJO;gpi^@O z4Ab|_!Gd3xt&5$ON6me9F8}3LcpN>xKj*Ta1Q0tyU21=CF$fS)=}V+rBu!I?x$vc zy|e{qatC7yXFn}?-k@<5`*-L}#%`pzV}r-tU=9!|}4%Uj7E@cb>qZ9P_WKQOFik|7~#PDx*s?meNtKRm^`I2b$OwnY;W zm!a6xB@bhIhBUr(tcC7kVAQgGAIEPHN#elO9i$B+NGblg=9j6Ql#I(Ym<~&h(-&_hmJ3c-Q1-CMz)7foTp9=MtmSYDC4~& z;J%k(?eH-bHZ_vESbc-E+vE;wk*3U50uL8z<4lJGvv+;t{;EBf(yUZhkruR;*NQ7~ zyJ1%r3m@l%5#1gu@$@#J2m>9u3EO_cfRExwh;+J_C5Oa+>hx!6ulbiti3{dGZFs3T zK?T;ZCiY!2n>JsCAHClP z{_Jo3uClD1KM(A1K02w2x;oAI0hA@VdF1DDW=NKHxywu^=Tf41vV9Rc3CcZ9qBtf* zL6+WQt(d~@^9|=xy)UHt_gjqO)%Iue6L(uBLGo*p2njQ+*vEA5ll+s~zv`qWaO&G2 zCL1uMFT%ADnOmPf-0Tgb1r!$&P87!!dR=HH%n?z9h)ZnhY0>QF2-6nez=-E>d-pOk6IX%I5z= z`4ENrI>R9OthI2>zrQF!?LiEd&ydO+4H4_S@p1Xg)@W1FyFh@Q$6N{JRw+5PB5m! zLrKWl3dJ)TIb~ebgzC0D>~}9LOaK|LwrL|>*Z)P_Sz3Wg)I9lKG{O{`gTY!%$4n}h zt$qb-PtsecP7RBEsKbC9?T83xX4J)dOPMX(ES|AMW9^YysO1Kc+)llR=zU@vI~gsx z%J=&!!yCdgun^k?F?{PQ@@}JMY%7leO9lGwX+}Ted2A(^SD=QE|L-ZI(uxM7E-)Vq zS?qll%v86|JX1bX_BDBN0cI6#2n!r)kU1iPxHw)4fA5kdF9Dz&#ymp+lQm)g0(XX& z+_cx>jZVx$pgq|`j)P=D+dx(Hh?0Po?)m9k)||L^)Nx-M3SpwliGpiIcaSMe1-|SC}we7$(ToqRpC91_WZi1 zwIlbiRgC0&2xj)R3$s+9)c*b+Pa!eCqhHnib|IQ#Qf~z|PBU!-2vscKZDv^@w-N#6DWdt^>`99dzqD ze@YNmQzVm|Fbj<%_ED`Loc5{~X=n=Z)Ujt@q$KH+|5hjio*SrCI(XWSY>0ayAEv-f zkqNVaprV)}*Mob;S*vPdj>8|}wZeeSmV_2VDY-9wu9TH|&?K0(@{cu&h3`M;K)eN% z#azviym{`whbzr~?CC1;ROvU#rPOgO@bkFgDXERu&DQpN8RtS5TmX54yEfAiItHQJ zUOPeOM3rOV@BN2rDI&25r0=TsIw@1yeP!lN=dS(uWKtw9w^d%IE7wTf{vf&+FI4U} z=y_TXTdyK-uFa9HIWuNh``dEpGVgYQ{Ym;`ec247Jo|Yh&Jai+$9n=<|0vuROh&Ca z8>QcCGYQO!9=MUG{ilmFVoLS>c@Gl0t;R~NrG80a9DaDXe2j6mF#l9dk(2*POCuDG zsXZA5snb-rWUc`nMWEv`p`dk4HH>0iBz^JT3Hs%qO5x)6yW=$n52XwM;cFNQU0nkl zp{C>2f95c3Wi(iFQ${D`BUP_6Mn;qy*k1vT!VhS{{yV=;0I4ufC;V(tdiS#KT5=sQ-N#w7LA@~cK z^ayM`eOpeK#*33*JXG`1Y(Gv<4hniRaWQO3!;^QIt{G7nm`F-w1h_T1nIHBjlO=XOa zLOJ929aFbFk>rZQtW8IEQ&{xNsCOhn(=u^*YYfdf(64`1Oo1KsjS|>N5vWSLRXJcQ zIaRoEhbexa86(e%@=~qQP)KMH%siG3rk}54wXwfR#tz!D;}_*?mp!vm+dR7nN1MMU z?Go+VIdwRW2|Swgli|HEupym?>HoQnI%{!y zmM4k3G}~)87IpA0#Xwio(JJSfP4=m3Jj*Bcrlz*LU`qEq%b_g4pairuyuCrj5Bs0hA`mx>z#8y7my~A?h4jxp2 zApd+~!h7K~Q>M>I7A!M5m$ido2Fw4Yu;Gj#EAM1In8#j}W~1X5C6urC8VyD85_K=1 zt5%{;7d)H1egGKZ^a#gdCO!0%ew)zzc^L`1S89l+VftWY#Xv}>UQLp=F;*)tANm&> zEZ#Z!g*BXObK&+e-9nU3&Al+Tv@NpHPzfqHAR5IPD&WRwlu9^V2X_!-9>=VUHszs9 z_8~5Bxs$+V%5r7U$B$rd18%Xd$93Gw{oUy9zy$J`@HV=_B% z{+-}+tBr6ksuNFja-t3_EofmlRLt6$HV`5{>wj=E>uJ=&gd49kdy#RWMfM$9heo*v zSZaG5QD?1Jj&}C^`z$0R{0{d_z{tuEi>{3{%yucl-8ofNbD{zNN`wmPW9xBB zrf_!SH$ZrEDOw$lT4ptrr(!xmEej8kR1u_YbcZG4JnxUx$Pjf4d&3H*lm#yyDsJ$9 zg^wcMEDKgm-6@DKEGSk5Y`Jq8S;A!1W7D0r(hhcxpEe-SEoH+HMTE+et)Djf6%3u^ zXFkp8eFdl3A~q3F;=|ySAdJ$Wk+`2;XjEGd7EzBkrez!!$Zg0D8!iLGT-RgJW~2Qd zE(xa>eKH8Y!5;&{0+)yoIk*i{=kVj)Mk9^r6q6j5=45h<;?H_o!o3O616At%KK%um zlGmCq#bo&nY8mcx=`4@q#;0V@VdRz#^=()O$IuRO2HZ;5!eQ$5T;gL5ttnvg= z?&h{!NsD_=ai7Y1&;v#pF4!d4$!Rq_r%{P|L3jd4LUu(XW5%(<|yr z%&kUixwKW7Q@+8$Ohz^`)=lwq_Ts9|4P6m^qDWiKMC9*$7qnU?*-veT6$83~YVP54#NdfiHzKVRn$_*g*80hv2QV?U7ufAgT<$n7`ya73b zqWw1S9fjN12sGUh|5ssxVaI(Vs7o!{%~Tqk=Sut#<*%{~p&D^YpjwbL7#Y;B%89Wu z{%=LL(4~5r(nl#<6rP^4GfVv4bC>jShK2^^gDtpKMongwm*J2hTkO79^sUUdV-x*mpEsn1aob(( zvG!XTnH|ucy#$Wpd5AsCkUn|y5ArA+)a?^mS;t2$bthRA4O&aQG;hS%(1DnAC?M1& z`q{cjDwh*4#D2D&$ zQBVQZk2 zE=pCdbz#xGfS&kg>D;?6Vh`L}#HEJ%YG-xr10v!Z=L$x^_6zv#Wbb-keJ!~UmR7s1_L9@aHPLDShO53B*LqY`}p4%#J($5TnCkBC&V-m zYZ%m+PO(j=tezccJ{P^F0&Nw`PM7ing^z8<`)_V-DGqJhvAd8kPdvFlE-c(!{F&l+p3Bg%eQHV`a5GA!f~e zG3vWeq*DIWS8vZe<3Phuu1LBJK;$p`Impcm{~|&aZFf z#vOgQhH<|P;X6cK>7lHH-P0DQkJZRBy<}%ZNU&MhwJe|9(y~d}M-h*p>eo|X8e%WI z{1!~N$w^|(yN9a{ueUt4kVmL`p1O3zU2%2!b;E-;w0gNmkvFCW9W z^pfTp3?j+R8>M@bs3CjW=ee%?`X`U7l^&&9qs0z6sCo|2vkoXPD>BM7 zU52&KL&XGgTyu9=J>hS^;)&MHbyN1Um?~a!pEUH&332jP_4%Ft2RE?XUDV{TmNV%p zYSU2{GeR_OccWGdY37IZ(WfwkPzqTSbB)D8$MYKXXukLDBKM-6!9|Cgn74s^Gr%mq zfEe?EFsF;Q%hDuG>rK8c;7V&TP{7J~>3fjR2`fCR_EtC47>}J!IAa&B1OOa7={OgL z_)C$S?jLhbFARgeDT~U2`sNk4zacayNBlb1sdW&dE4Im{gDJCv)MIh+fnDDBt^KT| zuZ*2()qZc!H{IYUdz;7q-pPI6J!W@d{xZ= z+Y@4St!X0*n>6g7P1j+f2oo%L>rnVbRU}O{FEZW}wlVCWC~=U&HlAbE(UnCqiMMyL zn(vqRygG8%KqdDcOoNo?0JQ@loUazgGdw}92vw&%htg(=P&Z(bb*)gMNur}cO1sQmb;CC`>pcuQt4)zLAGS#Zm5Y(z^OZqg<hF zQS6=jZ$YORw-BZp*E%+^M^Tc`_hJW;_-dgV$OJ)!2NpM0$b#XOuiNCXu=Wa*X- z?puqTq0ozOMj;Y^)M24&IY1m?#dsBi{IhH&vTd^i=S=}hWd?>6IK*9vDg zeT$kV3%@(UtA}hpE+wNZH+briH!?r%!8=v-2CWW$FN^gc*^m|K*xy0txExu5FmlMd z0W(#IXisYW7kT)z-@p9;oLo8-CLg&0U<$71x80C-J??3=!zTEiJCcY{SIG ztR3kC_gI$)8m4s|8Xc2gJ}FtR3?+71QQ0FfRLK#6BeYNld++{sv5ARvB`p47`6rTJ z0^qJ!s&?tWEt_a6vDRI$p(56NfSz(E`ClE|-I|*W@-!{l^Vi9T#YwXB+q`Q>law~k z!)fiSG6*#gjSYv9;dmqYnY89qaZ~QaTPK>myaq`q2UZ7&Lmd-TtNd8HTe*;L>roRq z5@1tbORl)ku8Q<+OhjE8ttx0+;hH+_L^Y4n_Xt&;whB7v??zicthOd({d!uQyy@^S zsRi8fPlw|1t4>t7ylm2161|fmC=#uR(@KXW(P!Re@$c9}2^c zfEy^_*NF_T)yGNCi}Ou0e}EmED-H1Lfl$arRTZF@+u!>Gc8m z2MTrTIhap4cmE?aH|IJma^%JeVrQIThz7Uu5emL((u(-nHmfz+7k(a%$XPg+m<1T3_OARk96pU9#c&$6A@@hn58=Gw5m z{7Myc6df0v0^{C^K8vnel7-&+32Iov$|}@WDt6W;jVRKqX*ApWreevsF?b6Y*hCpT zVQW)?WUSzQBn*EyoAXldK^d^BP?F=si7yF|-SXj!msR+u3L5BoGUL2vq`zb&2Fsi9e}k<9*?~RLKovFy3e1R-Q}1)+i2=8C434 zRp0Jw3StK&aG74!#m>JN0ZzfhNEd2%iSLv(dg^zIT@hs0H7-&v1{&NfD~f+q7~UUL z)gQfkRF{cRwh>KbRi=HcMsZWUBjQ>wv53lNG-$4J@)p zrs`v^yR3L&l3WRwlMoKPO9MdV`MG?(jt7S6(!azz{OamniH_!AHZ+l={6@_M-|`+Z z{k~LcRlh0Q4-8xl)h?JrJ;NAuiL2-k?YG}Xxoy^2NIgY4Fx23_BXKnPE(hV%ui31+0P$?*ZYxuVM`ts z0X;E{Y72ldYu<5;;a9fEvmvQlYbWY-@B@|5G$+eg2#Dp(1|6F*-=GrFTQ5ZHcdGAb zKStH;a@co?K}tW_^NjWSjr-gg!+gur!I^p6MaDMd6=;odwjrN(Mcre8xD5ZAgDUlD zd6!owP`aZSbqxanXasw0i^fcy&~a!=+A+_Z175mH=(k*x@2otK(IT~83(tG{xXcm# z?c80vP&#Az-|nRMDcVyG(}+1idfYbPi~>Z`^v-d9rne~{8(3gKVNqPu_OO;8kQc_G zuC(i=05(Z#)INFBHG;R-b5p=*OzUf(;aP#?U*oIX#`77w6E^UN5Jz{vCbO5 zZk-l(k8PvphR0vXeO-K3W$vM73(PFn7lpl5_K$BpA#(F}@lHlZe zz2(1VzF1gTM@Ol9Ww(vmc-lEm1VMS%1Z?*SWVPPz{G@HRYS)k30J`RGmFEk~?}0l+ zS_9GSZ+>}(YoQPny6&oZiyIJDRi)D_>uR97pQ(7-G7^vyM#XNOf!iJ@IZK4hq0y_| z-KE#NPz9qV&?o{+^dk!nZnk@ZN;Op`Hps+iylX-sV%<6&S?93xA*S+m{SeK!E$-fH zjue|?4s|jaLT`>Gm(Ppu95OcFX+$^8IVJ=eAiVMH*@TyWcvv5N-588sHf}(D;dx*_ zSFcX_3om2&yB+HK!+qo8I;yo+ALN-CoO!3O8U)rFX1io~xN)y9jtyG9vL;6k2DBY5 z#BkI3MSb@?kKzNLP(Q4S+GpBRl*>WY8H#7WEWR+>ebPzyacjW?7rK4a_UBNS@R%A6 zV%O(m$=et?Sgx0Ok_W)w%DmZyR!F{E(~vhlEgutfU;KU)wumkRE7*I8> z2)AVoDu0HGz#d`3`OMeNTBC|D*5?3VoPPtr8DW(4B=B zV>=nT^!Bmb9=hSMy%#uSxYe7MF+JUyu6N+KQ^2VL|xi zFU!%tLnC+=sqOo=mjvh7e!~moUx{7te6g3jstBxP(U@!Yst>x)Pk2(Sz!5N-m;=9| zVl=yTX85QVqq$pGsv!nJMFYIRtB!!JrwFu8fWwY3siE?-*WSZy9N66BPO zm?}sdn6F0($aP?J=!`eCbFj>Fl$#wHytoW@;oRI5u~V1(bfYy&>rO)B30w1MlUI1X z?c@#!iI7zh`X7I0y1Rv0Id_;73PlbXD0Pf-9xQ`+(mSm;dmIHfbFjdU6-;G7$9L3& zj9s#{3JR1Q`R+EAuPINcS&t>|eO59>teJ2$zbgoY&39!UWq%=;nX=hlp) z*HBuWb5InK$TDarq(l3VWp6iFm?MAWL`~CR5x{-arYlD_tm2no7Os?~jRtdwdeno! zotVsqiQ*tC(K@arf^TGZ>2%cb4=dJ^1tJ)RxUzm4vhb(FPh1c#`Vd12#IC5zc>Swa zMb;+;ej%n5OFvewePa+=bpZD>G)sj;bD84A9{Xafk2k~ZB%Xn>XqlyeMh}C!EboF| z=heDCX*q{{{O!JGV}g0>o5RR;|DO%)JOzj^#K&HxjC9e1#d|=Iq3E7yo5H(3 zxw~PTr|lcV${!hQJur9^j(^$7D3m5`5~hCVScPes zWl`jP)8~Vtrc`g@xq@*wPF&I@54dC$ydr3i@-BtLYQat8YE=lUB3XIN{m*;Z*tQUo<>5j? z1}|B89_+JB#WEw|X@s-xW2Z%7CJp&y-h`+b3SRsbZ1=KaHNTVYJv@tFtqF-?YGig4 z%$@gb6mXN4RT8q*y$?Ti{@L>p&U0D$!0jU$9aT;ZD-T!75;e^=^qyIPOGDqW%8lK8 z4Fj`#IbqgoL*e4&F(JycK^*wx=9)Y0dZbcZ;B9yaDs}W^F&fcG5r>CPZmu_hsJ6D>u#d#oxWtiT*}z%O8M+%NvL?4j)aA;34@Z zS88g7ZOjY(Z^3)zzu6N|HJCyL7l^J^*dm2HhMF|u364{ z4%PG=>`jQV<$ZI~j>F#yv%64hg0LfwtIwmxj?Ru*oF}-qT>mLV{+>EYMXxj@+a;oU zA*Hic$(b0gBL||H#|@J|m&Y|?=3|vm@HI3twih$X*Leg!!Dx~(mty&Ma$)NEaG(t` z24**rbPQq;#iQPK(_ua(BHJHgmD#eu+YKO$|H-%n3?&3=y=RJ4xIh-6PJM#TZvXQw%@^SzhII zz5@_KI@>S>YAch*&nt3bzHGT5G_Lkw>(thyK)i#JEs2__HRzgobmtC!qtoSdhoVaD zeoSrhu0eS$t6Mgpd*7O&bN-g4|3=@!sduOl132LJKL?W+oXSY1mWMpZ+3Y-8T&DL0 z&dRHL04i#;{#CLPVGtHxv%%WY`%~E|&#sRA0ZBY8Z=D2wG2vg%PGf|NZl*{-aX{tv zvrAQyr};ShOpSWQayejCn-~QtF)%}(N?x0p_&sk^FZw5HzEz%w`=T0DNVxC9jR%j~>vi`iN#e7Iu*Aki`B)QR!mLb+NIm$AT@ zkf0|RjWPGf$`S}E7(L({ZcsG(tFOg^do!+iFa?0NbB4@yv*KN;WxiH~;3(Zj-5r8# zRaICkllX)bqup3nTG|=x6|AaYLE9h-yS@o5^F+y6J5C<{b%zMChAcxeE=A6T8wYeG z4I?H=((C$9{*(HT2cO*eV-+GFgM4_M$`KqF6h!R5EiHQOAGzz zu!|l{wIEV~+C+@%zw7yF=4W8SA^&F^Qja9OKT8eW(+e56qV}GD{tV-GvHfP4S6>|) zyZnhZ(P5B-x~xFg1YD`2n;^WX=6;1x4?dil8+PRt-|d;=f}Q^@u=dCw zl+P)M*ekrgh&Nc*>IFY`fmO*m|A<%?3dfYPEX`?wcLQyXaVO(>6_kM zcgNRvQa!5N9JMwa(n|{#rxC^3LWmX!UOyqc5ovPhq=U#%rB1Llbn49Oxdw*f7%sgc+}(!64CRj0gi=?RS8msFN?Jx<@BvN zX({Cd8z7VqT>)5ITS(Xjxiuxd?ZxSH`^cKMrh0>zj)@%tI_Cmr)x zP&GENBKq%k`7mAnM&8N}Q^mtt%Wg%+DEAK5$tjZw;i_zgB3?YZx1nQ`nJn?|)E7*@c-ZYIKE44K9%bUEjqMU#Nn5D#QOm$i_E`I+?u9O16* zA^dn>{jq&j={Js{P{Gos4TmTOcr-9DoLyhHrk3w!{_Y9e*f3+}@Z|RlnwSN>$G>@B ze>EkX%MGsB-RbctO?-GrM}7jWfTkzGeB(o8gn6@p4~2Ldtq6?+M%mCbu?9-W6NC@S z6E&gzf1S)g!;tHV>^Q9Fewd?-=GgfYe^z)nPTRr>xlE8o|+IxjlZ=&?b@D>lC1`L6y(jj;0X<4qyct^ zr-b4Mi6{Us002aXpD}Vy871>ZfL-P$m0wT;H#HS}*>`#Pk4!hMxD6IGJtkW)EQx3J z5+%6WfMXT^EqFGy&S)&8n7i*K5o3@wAUgBxdRJ$z`Rj+sF-JxkVnqiE_0t*d1UAm} zh4`rPRldLaA67@c(PmKcLX0dPD#>js-Q43Nad+nLYkCo(`<(L!{CmK)v_!Z*^|;i) z(a#+v!Hu`caP=A}dcU+T&_{j#i=Uv|99s}(bwLHj=!qeI574nP_@ED`D~c&G3yM zMa-$=;ISL48LASyaJ`f}e8z?vai;f&5{j{K zoGsH=nkO&ZSs@h<;kNRPA|16ff~j;rPvw9a43|0Mjn4LJUZ)6cTe#nL<@?A81_G(9 zRCt8H>B!5Wzri=2@)nhh`T)5`u*Uor+7|Dq_#n#*CQ8o8@l<2@eH3u(-toG#(3y=( zqsajpdjS6h{;cp6H$ADjb*Ol!dICX*o4h5}ZWxjz#<9o7ez)`3eYe}Z-JC)%P=hM{ zS*Oeiwn%xG-v-3Z0uk$Gd|D{$veNI!c*n|N-YDOQ5c@Kwy)1Mvqcus1)$47g#9_y; z%$&{~9axi>aN-)m=DsK`m4{k|N(ZF|@#M(rYZ2V3+NEFkS(eVxmcfb+Jpo@C(GI4Php(K_ z3dWzS|LAX4J)fInWAR1>kflq`4}%DyBGFCo>u|CF_->(~{Q|%(u%F>-?);L_^7tRR z?T2<-y-FY&K~Y!$GY>nys=;{-bazZ{t2tzN00rZ5(iy#H$;@`J8I@1UZ~wPo@$CHA zyKrRlNLryIFKna53~(Rdi zz)}2$R#63ae`eG)aX9EZc5dtxSh+v9XoW~Av*kL}g9B5_0VQ%uH4=w#`iCw19ox{S z$*q$GsOiL+1=>hSfcr?P$lmOQt&_Cz0`Na+rf>y3nbXpLw2K}_-`a!SnZY(~czP5M zucvhFJ-SH%>q;PutfS=Hpl0v0rCH7VyXDttZE&7`r+L)qebD4}Og|)+h-H_{(GL2J zr?3kzWsmn;L`3LfrFhxi|D)hURq14)6&$vq=7W}F9+XIZaF$w3q~_%sGLMI(vo~Gx z!5E0t&>5>?L$yYO^JoPi&iaVsDfG;!E%iy6W4k%0~NaD89W+(j6-|jbc(Ns-c^n+tk zIZf0`u*Y`Ux=z{eqMmjb8I48-Fd2t8bnW3g<5gD;|`ENld3^Lz>7 zxJFXmp`#0g@2BT;It|C?)z?YvU@!!iMpPf?kbqai3?zv;n1$yy%PwcDX5$Z3G~o>E zzQ;z$s#I~8+lH>I#K=I$1ij(4Dk18h-@!>X_{lkILnsuZ7ax*E9-zAD*2F5Q1e-i@ z_l*c=aqqjLctS1Okj^(GIf|!c#9u_P(d1sQ+TgZV7F&y~NxV@?oQ^)CH<8p>v-eHq zHT0SnSt6syk!~$le<<@*M9IFF{+~0c(z+m)*y6#$YNSly)7Vg%5ms=(~G<5K(`A z-3LJKgV+#j2|*<0c)j{R_h%_;9EqQyd>J@f%|+ZkCR}fJ_-epRoK`FstqKd9zT691 z|8qJggN;n6f1=$61D0=5)T0LA)4@mSL{^Tk)HY#8(@r8=cG;946GUz6rpe?s! zw%wjpi3i%jUsw*YS&~2uVu3-7(&BR3z^D>CUTW1qzgpaQs_fjZH7tXuk@RL+kfJBP z>Fdo>QjTX3ZFfOUx6vKlNMnTXvYny{oh=-1ZI0-^aqGVD?v$pNcff_$!6Z#qU$kc; zv0HW_NtrxFnIpHAIYdEr5^&D=KEl$D43}(q-sbnx$qqs9lX>y; z4`zhaof`{05c@N;TPDchUAGC!dPuq);tg7q>Krt$)HiE3uCzi8zuXH4hXT^uJ;YLl ztgI$;UVNjyGRV$nrw$#h>SMvId$N*wc}+ED+rAA&xL)eP_>Z(&gD3;qaC{vE3UtK^ zjOw($;9Ey-VGX|zs0clk{ug-$X`?5?Vi;e6P(R=bv}INhbNNn@(5&g+V9=UO(@H%y zR5EZK{HejhdlP;M|2}h`8~zFC>F`I&-u{NOkTr5MC$rHWc)QsNdQ+X)M4{c^MugT| zrOwy(C=1~jgb@|PT{Vmfl{mmB#Ej|Kjc>5p5@%H*GOPj(9ozH{Zkk3PpdzQn#*8z% zQVz%3PdU)hOa)+eSRz_DJDly4-nEXmh@2Qe9-G=*oReMmJc6IL#ACOzL}Z*zY!N18pW z#)r_GUdUId>9%PMNp*5aD#YVdzVD<9ABLu664(62*N}pvWz14K_IX&1EnR}3TB1IJ zo1OLYiLxC#fTUHJEP!H=B{ZCGWeu_=ryyc-O{zb=I478>9sS!ZDQqqM`1LlEEXBZd zQx^piu&TL?g=-C-Hg8U(8IFkB2I1apXe=J%sJkO?8`m>L%Vt`2UCybvOCJ+C(0gzu z--6j?!`QaJ%QAzxD+n+Xu`~prt4)!XIP;ekVZfK3+4uQHV#n-^>TAN|&Va|>syia+ z0QcAB{}P|$#bHG0W%yPHpgu&gR4Gvk?A~rxR=o>ZNTnyER1sJQurlcY@Qejm1}*PVF&H%S{%kED>z-8xgQpgmt@@H@4Vg~b5gP&@Rp(p| z`X(~Zvx^~WkQGrdJ*q>VxVBpD_||Lt_n4$fX%4_4`+I7R74v_v*I#JGbSZb=*8MUG zcEAVa=YNSM*t7fn>6PnCnSt~>mNX~A{k(nV@C7Ku5A?|ZtH;U!%h*9k0uCnQWOWKV zmdIb_S;92iut8`N_H8OTn{7^3Bp)RAkaEUsANg&R&XiEBKyw^tkRqQ1f8xy7q4W|_ zkrle-!#*Rax}Fk=fkgzaUM@$5xbZhv8tqFIv4q%ZoTz=EA#&Mj58gIX!J_>|_EV3D z=pSIz$G?M{LxJ>qtTAT6p^db!eq<)pO7kI;c0Nm)_y;YE!&79`2r(lSk~Ko1_>q8R zoxwVjXh-!GWuX{U9}y=-s3PTBY*{SZ27w{~-afrnkZE6!x=bi*b@HC6w;~oAlej?xVN!mtH+(r~?>3Apwo$4$P9HmZNr;+1n#uEpredBSQFw z_rYCq(|-%hR{{0%Y4>g2>NnUGn5wPLAE2MfqfWKyjT1Rq5PZbkm0Fyl$|17c+Ke1?e_Kmy@e@n~w*UX5HC-V! zwYjH*7u<{y-Y9z^l*m~#z+63Q+!c2W63!6(!B`?>?A5`$sg?Z_M)LMpNN@BM@{0y$oAPksNJhYO zuk&qVz8U-quFh9s3ZP_roqO5o$k~GZ8Up@g3Syr(Y%T-Q7@e`6 zMa8(Cq&g0~p0Z&7-Nf2FKCHQ| zyKF;XWAT41-)Kid_1?7NN#_lw68YeT6&mGQsabSr4AJr1??GzC zhG{zTXc&3G`j|6h!T)UBS_#2BDJ3RKtf#5oZ98C#$nnsB)`ZT|%XBBKPp%9fGTYBY zGr{wC+nygj_X20o;v0L3(`yA3)swG;vtCZBofM1ejp< z1A&*-@XoVc! zY{HE=8p9TFR?@y^%-Eaii92w$IuKIQaYD*$vk{fAa6H)HuziZ*zKyVA!%ZrsMTV|{BwAN6@g<< zay)K(bs*@UA?L6&R159NY;%eSi&M3&Iu~SdTH{ z-d)pBxIE@Q{P87>%EI#NEe1w3w*J%0)Ym>PMpLb@4u-8RD7%{a$8w#{*!#{rtsy?k z`J)-`QA;9BeEALStM=T#exTT8t;>OlI8?8a6&bcNFOS#M7`t%mQO?|1ka4Igze0C- zO8ki?!**r_Hn71yb$QY0OERL~$bSpcAj-pT^>U%r8^4fHHy`_2mJG6VK?~0yeBXYP zdhjjbz6an~IyN*&Pj*{}J-T64aV}g9EmnEh`8xPQ@i5+<$VubHKvw0V zg%U;rdvN@4RbDwS*4_P=#td669ctGHd{APlq~qOrw~JQn*vXG>^@=jcAU5=C>{Wir zkS0z(TUMnO=vL@ab;)MKnaF(+q=N&J0@c;hzCN%&Ro6P~9w=2j*e20W4UAXbCic!8 zQR+_eq;C$|IAqpbgp!=}L^&X1i~@v<@{FlM>pr%6Qn3Dk>}Ubw7BGu}SnwoHjGpTV z^kLlYq=uUrj!Y@P#?OZ+IAENuYkiQW<|_`vXJ*fSv#lI}xkX1uO`j#tVRb%>_`V16 zOEq*I()&@AJqr$JT-Ei>JyPJ*^FDo!;~^g=i>FQN1n#v=%u>JK^8nQx%?a~R`n8EA zgqeSBhr8?_cWcw?8Olgl%zdo7Nl1RIE;5Uq87>MrW*#+2*d5K7KbuRQzUOUc(Ef6waWPRY%%8 zq`={6Y}xUbFhngourxV;g;&7Iow6Med7IduRL>F;qh7ofa+aIZHCvEu8KKyCNz7`%XrnJ!?9wjclYr!DbHJtFHO1&_BUv*jNFXbeeJ!!`^ zti(=DEqcjM^lM6$~r?!d^)|>ccPWLM*M&UtdC@ZY^)2iPNE9`)Y-(jt>ZW zSpAx_A?a{aoht1j_`d~6^sF_Xfhoowr+3w^U2-{j6+z6ngAx%Tnx!GAN|2Lh;Fr`b zu&IOmLx9<}S#{&rH~Z*#=bjH!e}8sr?lj*`h=m#<3nx1BBB}WzZ?wx#C(}ywI3jUy zryvU|^-{`}iR&f>~va&(X#L``@zlv*Y0vzuLscCt16csB%MHj)Otqtl* z;I-f2uVz(D$x-JkBLVj( zpEVmDEk63DYW24JC!*%mu|hC7m8+)54p&4~RWHld zoiH{NQ?+opx8nY~PaYC05S{_s9k?^kyBvVf8@1Xtc7UTd-*LsmHt-os<2XLtk?S+z zAVSe0`ma7=KFx(N9UpL-^2f)gPCXA^<#SMd7usZjV(!`l-*hhFKA7Cx+PN`I{WLh5 zQs!RTlCK=aj5dd-*;eS9N7@M~LBx)=XhZ41FDyZraC(=Z`1H-<70jafJ6P@bD`BrH z3Fo)(<1LbfKJln99*puQvSGDDPwO-36iRryD;jzF9q}K{pioXt7w2UU3=9TYzAsrK z^s_+=3*n)#3~oi3PUcWR#47#`!}$(9X@9$xQ>vh>j_I*&m;UaQxNLSL5!);1(MP_F zJjC}h;%@qfzxk=UaV+{~xc#$^o0fK9b%+<%`wXMgPfJ&mWPHvwJa zhqkC$Ek#S#(cdlLxn<8c;8t$5969g-qaPgXw^g)1X(quc8XYyw<11Ka5ZcH+m<5`a zW9ux&HWk{qV6;}N13ju)mM7`V-Za{{h(71P522^svrhh%_!59;70B<#)d{|tw^Qq2 zuQLH5Dg?+bdf*#Kg0DOYutq*K=DSjpy#{feJ%6w!0#RVcG|^~CTXMamdhPdvjbQ+k z_=@A2BozkWc+~pz9 z?Pk$sPp)3?8{K491bZ-}mk=W;FXJ1Qm|bwKj>Y*)x7N^}SzUzEG0I^lEiflD6!~ts zsaCu6LCYk}*B@laId@D=nSBO^UPxyGiIagGC0X1)eibwj!*aaMD8J!5M@aGCAS!Dj zn}G|)LADKURs|~BQQoFkxUtLmbVaYW%4&XBLfKf~rP^h;UZbfV0{b|6N>V9XR;Ik6 zQu5EG|Bek5Uca5|nMra8UXzrE)02vnWc=QeS30qSUE=Z8>tk5wdvXr&D6CHGd>9b0 z94v~Fzh0FHf72Y?rNv0A73PG(`v)^;MM<%wy24zmK7xzj~LwB1L zTS`hqLVNRh{HuakF?zEV8K@#2%Ig2OId0>5wLF)Fl;dgp64RE;5*-E7Xw?umH~5yX zd4aM1RtyDR94vm5Mp+r}o^Zwdds}STe#dWUTbOq$no@m_ipR#rg?FDEW;mA{mYV zZmG4j(F50BwZ#9j{loIiOn?a5O~=~|0&x){ zR)FP}ccjFeOn>kca<7yz$17IBO#x60#CgAdK z8yJgPe`s#!R5{9tG)RG5Q**te#AvFswYhkPY=@etM~(RaHo!av+SAdPBZwu_;hd4n zWUw}a;PpW8ifO8U0%BG2gjU@ip2ADgpE@*m$w@IfTdcEg;^hri^i{+qS}(m<&0$O&N|7>yq)pglKdO>0nEJ zHv0B!74`mC_6+Es8+m)R)y#yPHFtlF+&BWsC4O;4+y93`W&W@UF)lmU@92Jpg!@X` z`&}dpNy8pnRCUjOZSsmyq;3zY4{-j_0x;<~W<9>ZiZI1?UmUTnrM2G)`jQZFtJ-)w zyNC%#^F##@vCSfJQd1JY$l*u^$sKovn zW@}8Op~LfCdh;qqQiKF(6I}6VeQ`|4!)lB{9@*}UiYZ|cg=Y3$yV~hWKXC0Rx3J&z zC+b1F#sua)>j$gr%G8NH1b!ghBc$7qx`XRL)`RA;Yv7Ww@8DIUbBe%TL9CO(Dd-!-qn&Tr#&aZ0j_--1mfWCCz*JVb=}&L zaR=y!0TCmVA2|o*=i^X6_#oARBL6I&wA9$-413?|Xa`yy2)G~tV&Is{1w0(TXu`l7 zvt5=O20Trx_&KIZhvmafbzTtG7JJMN$XnQ@fadgI6GxLb@v#AMJBKs^EQ9T=FLjlH0>u$GbH{Q z0_#(vC3wUk+^2LEfBzTA=sktcy}gt6F{vAgYgiD@C=~Q*mV)D${|(=n*Zn18+T76K zR4;iBRjYp0AB>)T{?0#24G&vVXUCFFf~9jnM5)Ccccb&~C?-tpz4ZAgy$NDN0jxmo z*=py-*RhmioP98Yz|HSdVQt^{xUA=qy9==Wj`C=gO4C1snrtt(8Y>|^E7FaUMlwUJ+x z?a|U%chhgs<&Syd2T-_G0^540E4@1+*FYB1y$O5lCHyS-OJ1D}DyuvC*?b+^2vOE1 zNH$L#ya2xPpIP0q$L_KCz=J}gnqmw0s^H5M9j^TAWyOHTPZ={%*uEvn#Q9^e&ve98 zGmH_5@7ve2AU~bo@b+oTmV5Rxlc%9GiB^F1HVPR_2zS%8Y!~O$IzEw#l>>UyfNls? zXqtavPF{V>7|uf@>I-jDpo=nyj&1DOW6lT6e3wh^%k-Bi`Z%sK+7iJ5)bSiF;<7ML z2Q~F_sZBL>WNy%*Y?WbnxuIcvjS|n0qM*_k2l4?K6C4I|Bux*NtsRf z!6~xF9b?(WHw@abc>K-p-Vbj6YDosj;1YT;L(!uznOry29mYFZ@t*6{Qy{?IR@y26 zS|qGF)Zy)xBOYIKtF7vE)F-lGDq8zt`%ucvl^6?q!jo`(rvIl{>u8KJPKErt-*}dw zHh#c=d#-(&Q%i~0W~`~I3hB3y4R{J~KX~i63R6V2R-?nDf@xa2BHXx}+fgd8yRdKW zTL?vtn&P$@rZg+VP0#MhXZwV768^o~l0`bF1}^1d30nw=Y{78#E*HL!cvI2luGwRW`1LF;$}El;#nu46{`vHSV*RBU{ZyxqJ$bG; zB?Pe!xUFh%f9Tjy7*?~LY)V4Ss$O23;Y7hr)U)(f+Ajnd8Ygo#p`@y&73;Q7g7K1i?bmEudn+|F}4mR-@>O~qlF0`eG@ z_)i?1rVm(SsJ^_PDIR$>zH90;bqO(ejFuf|4HS($9A{(}Od-dALsM;;-p{|8u1ege zygaI8s%Yd@qvKkkl$Ttih=fE_m@|Mc?m-}tm;VxPXJCB&m#_1Y-ari_N}$(9)s63j zM8VPchVMatRUGaKQQXDpDI)(L>9CLQ;JJ_t^X{oP`2+H=(TS6KqRr5mT`3t_`&(7e zgE!1Kuofz8aJUip`EQ+fBW?ee%Brnqy{<(l+em zwpg@gIgaa*z=1(FFivDD9cL%KggNuulGBZh(aH;`&j114FTKj$;#2X|!`#qaPpi5G zPFffW>YzX;LlItQmqYe#(A`+4?bM2ns>yFM^5}R!lON?e5Z-JsqZAa{V804RO`nB` zgy@nzg%|DD+}FwX$*_Hyhmt-3gdqZ)p@g)Fr&6Lbk$Idp!D7yXf(hPb^J=@259`?9 zZp-wBgH-5>ipA{=G0wt(GimQ{1|bJ;A5XlkXjBKtTPfr}6VwQXk%A>m_ojdtOxnE|MbTS9yZqIkj>eWistz~cK#o^+v=tIlS*kfu5CR56P zRn*FafV5bAj$ckm=t^bepki&}0QoI^WtF#w#AU?W3VHRwMAE5ki0s*0P%l{uSTG@A z0WD?dCh}3LkF{g^yA*Aw_%yo1Z7O84z!XA+W*Lo*z5iDW|1PAu>`$vsTl1(Y4N>g8 z6u4>jGSLo0>&P*B$@r#JBb~@qWW)Sz3Kpnzl;;y6&lSsGz@f5w$P$xyBCcb80FPjJ zm0#!kKiroeg`0rId?|7GJ8sUfrti)uT6U&UyY9# zE=hy=a{o41XfUi&ezhW8u+c4QUpqHr_A_+5X>1#SfF6@o4?<|{k&4HwWbQL4|+w0)5qVG zwBs4E^1ShGWa~tA%af!iB@aL$*|_fK(ZANOA2YA@Cbu7DdcM#4i&h7D#TqXjeIQH; zDVb%6qd9{6#K%exgA2-dAC7Q#GEjZ2RVRoy)%q%gyV3wtunBxdoRU!=X&L~if-@zS zf5GDRjaErUYaXkYJga~{KqqKV7utAVHH_>Ip!#6u9S8w&*zSg*ZHL#g>II<#?hc44 z=&`^CpGZG^g|PP!F7RQX4n72ZvoLC(*|EdK26m?PK)EV!uqdj5#1Sfpl?GEPtBhIs@div7N#2L3J=q5kmG$Mmuv&{So9pLhaN$kEJ>Qu@VMK!!virjA{nn7i0xY00Ga)c;ue_CTih|NqW8-E-1~l(0g%q?|-bSRo^qQFHw_)hijPh4t_}3$aDMJ~7*ZY-f z@1fiSR$B(^B^CAWCClBBoY@Aj@hf7k4oLJdDNQP|{Hq?xfccLmP6R$ScI6(X!Jzl$ z2e_awX5!^;i?ijU(6y{Do@_5vcTU)80geL#>&=pIy8MQIzM^!%0oGc!v#O$_?UU6S zc_`@GL<@rG&tO44eB&SU8{TKgNinNuHOX^e2qPCVYe_oVRyHk|j~dWKXDcE-$wRlT zaQ(Omh@uHn>t_jqfkz30uV&0Ezih+xXx|PkpQAs@pVs%(QAl!1WCK?@p}j=(}=HmAjO8=i!SZ zWzb=1$g_+`KsFj?DNa#Zt=6ievr>IG6Vl$>MKLp(mL814{b@JT%u^WUaCje9i?Kkn)F8~XNB3Lgpj~qrtvXS$vGlQ+KWN||v1Q>a3*~$y z=wB8y?|1!-pl7193n9vBhxbnKe@@M(9{^t08c<=0FI9nIqqA);48ZDwAp zc=Ok2WjpI8VQ_AKQN#so!@RlAY&A){d4iAs0-23^LjGAeQgd=OR$8E33y$t8B%N(p z-pcFEo`5klLK1M`mt%~iC?JjOaWTz)-P3?fVai3beVm)I6jShWe){x6sCFwW37QA7 z4Djo-D>MCZQUAQok6myMoJv~cLJcHhCS$7m{}ysmOp{92O@?}=J`U0YJo4}@+C&3^ zJQTpBs(!SHPQJeU@yuFNoGC(tfh4I2iDB4rGtS>vab0f9bG&{1T+q84w(wWWvw9D# zc*lKzr*xWi$s7HZa*DLzhlB>?S>u_D3O9`Zm?dB_`J(gJ4I^ zxktG)SCxhIag={o{ERRAf{3qI;-*^sv+b$d3LJNW`}7i$RgpgI<@6Uv=p!6>GO%^v z62FA-+;*G7U(^%iI+Rw5OSq$>R`H$fRLR*Po-PeK_Mt39SHa(Uv6%n$d`>PtOvdb| z^p8bZ{sG7ZD`kFMq<_n?f zTr2&1ZI0d?a~Hk)hf~Ql)5?OQ`!64*OTU9o0Zb4w^R~cb2dV^5BuKgWS{Mcin^H6!BLE47DdAbxRb5I zo>+K#T7DOA|2UOC*Qx@{Zl35xaMts~tbrAGKU7qT^Fnvk_d01P*mBtDu#+#TjwiYq zMJ5M6T-dvv5(~|2XDb+y`3-YWr|o8@8uPq^?(io+wEh0H#U={ zgkiTx+MpHlEKcy{Vx;hsXNK|WwQy)ZcUhfv*fN`?Mhg!0%Y=Cx^`k`hqW4_C6YC~$ z-~+cLFVGtwn5evD3QI~JZ1k=s~yQ}PFZ4X#; zFq_^?n)CUK_thdjXBY7VB?;M9Uu)g?Gkh8~hz-xp3}ky0`k+$1SNyC;(y&%t0@}ES z?sIH%z}8zEt&wc$CZ7R)fOI&0w3c>mjO0{VpLP$^V-7cBMiw%yzdU1oRl)rRRGN2E zzh#j2tEqwP8bFS3og*|zy!zvcsJ@;hO`?sK)-GD6b5D`JwBbe}!~I|ZFJnWG?FP*D zuNaJ?5lkwOzlR5jn8vDVh6a`^_ z@L&%YO)Zeie(`J2y%e{hK3FA{L8&cTcRl}UN)v*LS$YFs zNA3i}2&OdI;odfu!>y#o#GnsZ$faPh6|W${)1y$t*D-NqpB}~BcK}a#1o$2epDHh! z6`6jopgoGRB1}vN*1NaPept#Xy<^5ZK%H&5XXc_OCM{B`gdhuf%{98j89V>|`>Bsv zD|7%(hl8kOp;$S#Ag_h)+k4#E&HZvD(+;;0`h6)IGt)EMI=8-N!;WRRcSfOs(u}dh zTUp!fBa@MX`w1Ti&wHl(aYCCr?WMn5oWRVJ%F!cyh5Ch9;i$$FQPkgHG0Kxnl|MfA zwjQ|JubjAC`}%yP&B}fwbW(KrqRAetn$zG{A8pyl66A z+BM|okVk~G3Lh(!Fg+D$?hY;b#7!W@ktsK;i`*K=N%}Z+C z33o+d7<}z%5@7?cCP`(;qx4U)sg=SZ&j)g>4luW!zsp%$a6HMZjt1(?Nx2jFFtE4K zeHS?#_Wiv(X`hxyK#6}&$Uyu6(CLeJ3H0}NgBxv4BH4}`8L}^I#0+D0cz@rS!L4?2 z_JjUclG+aO!61iFOOk@hQ{-YpaV*s9E6s|9wpJtCp)m`jg%%}P;?dh2X%feqzdKge zBBk-7nP?l-`t0**HYMNUx8-;dN5Rn%B{_#Dp`@>%OZD2(Cs^9CHml)?PNH**s)ryF z*bK*~KV<}2rWeZhZ+xhu7c0zE#9CbUm7W3-vyX`>qkMhOnln}#9VMZ_O8JUHOn^@E zJ!oj`(0lehkp>}hkgU8g1qTXCh%KmBSKrKd=%anVw=#%XHl|3%+bm~-r0`mF$jV@b z-A3SxkbBgt5_1TOlqUz)g2?swB-7vt9qS-mLY}RU1@d*cwTv-*@u_pC(nFHQ)j`k; z@lxjHImmGeE#h&JpVh_q9BuD77zWVHGDRJia}g6Ul44=hK5W2qiso>X4G5z6I0WDcE;?W7=Gu7n_q^N`g$(tJeFRr}oGL-pVeQ>1g>IX5+x{{A=eR5owARMb zN>O9R?uN?(qxtN4Tz%jy+~fcSJpH9Rn32lMNE%N}1wkN5&gCn5bQKyG>?Z-V!|p?(z3q7&y{b$?1S0zAevl*&l3i z3kS~ywpD)uqXvh>XV!rC?X(q+Y{J2}yJ<^s%C>@)+x4slJ2R69Ufc4L&Fg402)${T zj%}icaphUL-hYezya`{52T~Muz+7!w;TS0?J1I>5tGEO?Bp#9qM(0DNw+O;lqqRb` z@8SB5_b>gZSY+1O}k*?vOaF-k50g?MVJf&`$VlitY7{y52Nr> z_m+fpF6zPHk1_TwU4oxx{KwRxY*V^$xlT8UZqADYd^crI(n$Lr%~UuzN7Sw=BhTxR zn4lgM+XBf09P)bRdC$?4mz7NnYZ46rFsRdk*MBh|`GXM6WkX3`6LjXOmZs^f?!IhT zg-py=jsG6TxYS_r(SQFrINp;Q0Nn>d2B?$eJ>+3rH*3I@g6h3pAC62CHsler7ibf` zc#>bDmazW;^~{cA8}_H|#C`-&TGNt%9)z+5=VPIdf4Q;C&dcE-Z4}2@Dpqrl*W&$a z=CQ2I0QJlmA8=nE+9<(#-tp+S5U$$0^LSL_sElcfKO{|6-<2-6MMs3M^Nh$nFif&8 zC~+9f^|n>-laur$RE}}2VASuA&xTtvY^YS&=DdMJ>c$ef#($n$YAOEkV@u??wL0k> z{t7?Jk}w07UNsQ*=a&B^Kgb4vhi`|<9+U+xvx?;Y9=CSrU?JxNuwoT;!o zJGX3Ij!gqgu%2uK^LZ?*9^bTKC#$7haxgp@J^vtpe8h?Eck4l)&fxMqjtbp;UZ! z0OEl#^EVW4nBggs^B=s8XU<&KH?1=M20qX#N%Bh~@<2}&Ahp&j>i>l6H?2hz0%Yo@ z!1zq|`#Q0b!9{W&e?&ejQ=5%3A5eMnqK{SwgsFNwT08_uV-cehZCuzF$!sdyEb>=~ zv^8?)2q6mQmq10+BKJ<=yn0Z?9^U2H1&;-&fR1}*RzuWZj=47M($-IL&PWZa0`r4u zAgV6iI!b7j_X@v3i1WoiT6&C=B1(<-N$bFjC7OaMSVv7qq(Qc#7;}3I}w#BTU z1y$xxiX{G3=i1c+0kY;KmJAAe=O`#Sr}b4-c+CzhW_{VtUR~`I1Z`)M9_b?}OW<*( zp0P|jlW3RWjhsi;uu`D;l7&J|JvBY`E+6-qR~zMzy{~uYT#D`^E(v~VBu0pz?*04f z9_F1T)Azj_HA0O6F0WAj(_a55mtOwI-QL&rd&C$FlMVp&@WPzxJD%?iAAZFjmVG=q zr5ONjNJQs=9cwToT)aR2Bv!%^UAmDzAhT&b=h?9hKQl7>X|IU}0c=pA!l z=I_^lA0#HYW^UWc`}$-_pdtqQVe;eeUogsw& z^yl?VtSWy!*O`0C^;RB%NPj$^w4bZz- z6vg5YM&}Zl)^~`RNk^ZtiQVNNrxGX&g7*T*HIofd0>08R+x8a-s4 z+R`ju^H5gIMb#BLcUl??95;?hBiUNUMwK5jHbZ!6X^5t0ihZwrY0qPs;D5{XlO5nG zCjbQ#epZTAf3v*>1NoF^T9MAc_LF<4ba1SkPPQbS^)5%9)~zidD?jOwBltl-@kJVc zB~U410+wkzB-yne68x**>FnQfKehg0 zo;KlUpj@f@aT(Z%P&AKhGo|=Gcwf4VP%@CUwFA;hcr%n(Pjo`j^_^>0r}REr+AIs@ z%=9GrL@e@FXp z<`@7&d|ve*bgyEk*Hnb;NP_$>Aq4AMm%2O6pf#}{2H%^=bXjCBfNZqaW2(B?e3Z9aHt7n50B^0z+j z-(4tz{;4y%=(WMt(9ulZIw~hcuWxJ!dZMLyIeEvXgZ8kLe47ISILeEV+|`)HQwob| zD-B)iFgoE2EcxUk@S8$RtzwT_8K3paxz(ipw>X1y+Ima0AqWc@8dh~Mv$E!|^(aha zX&Xk|wJku-cOfK|XpyHo_?*c`3)D~CswL&Kb_EnObPn0oJO<#VlqQ^ock|4xvJGiF zwH>!3sWK)jp;?e&? zveqbwjH;AS(T3eYns%n8mqXlfp1(=Lk3;XX$QuXc>yAPy#m}LN%Htb0(hmG|jsg~j z@d5-jG;DoArJl}-H@~b1*}BTNK$5bDWTW&NOJug|sZu`&rn}2JZfH+f2VMr%REUdJ zxqo=_bkMCwyhlf9GtjYe(4i5Jy`ORe0-kKKHBBn6fAm;EWyIq9r>BodQIxb}?`q#4 zb;z>|n0LJC5)Nxy;N|A@3g&qgChvp#jEm9sZ{RikiXq{xyzL7tKC0*ZJ^_v#PGqK7 z8XglFqQP4m9KRoVt9Q%5gGcg7>EJ?2iLj#~{-yG^fJDGnlcIFfK{-SSv097}#54?J ze^pC6&p!fQ0wd_JBv|JjU6>9^>d#BA$AOLO+;B=iyMK%%ReVsFV`~B0cTLrSJvB>~ zMdY(TWgo+CwzJ2NvHDLit)hz94-E6(+uO;45MLeD0kSD7D6>d#3yFWjOrN}J4_%H#F{3;}7lpv_t{>!7~^H&61bw~V;{ z`W|CqZtA8Og^*rPZ=d~S_8UkNbabfy>;3%N3!CJ2$)0>kK&Z3CV|d?J1S9S@tlqcT z_7^#I-#@qdk7zvZTK;Hhr$Y~dZluU+RcOhcybrvJgUIMqPO*U#mvVaI>DiAXp@Xuw z)|C?4SyXOJ<_*nE;p;d3QW8%_U8L1wX zGhlrXz3WIz3Zb)g6$qN3O}ywHxa!=9@3DNeGbzBXM*>?hkhvP3T2=PZMIYC!;ePPh z!BnRyloT2uCka;L7ux}-UmZfF#=~n}u0A!(1~boXy2jQbu@x^CMWA4s!)ItT2O1p5f`c4%H8Iufqg3tIJ(Mmy#6 zvxCeM?AO~Y8OP1cmJL4|=ELW*&-n{oemgeW4j9;|VK$}m@e80+oE_eMA&*hM*D}04 z@P)&;{38j5y&F1m@=%Y$pygt%iv?Zd&wEeRJlokf;gghsgL`>@Q>+)|T>YntmY&~@ z8sm_`E&lxD@*9J&pn~AQH8_RP9QE4x@QV%<0wZtE3Z z;709EnuySHa|xAE!7lKl;*JJ6hTvm7HmdloH~fkSJ4 zeXLuqQPLMIz2qaZbCC^0PAv^nW8qwR)s*7yBWOm)_R`7`OA1}w5tu)i>Ol|xE{qkK z9wEc8!-w+wX~_yN1s9e>FWeijMNP6E^mc~~1__!r90u3jnCX*wk^dK?RZ_B>^cpUd zIeQKxx@6rD3h_EqM}ire7Xtr{w?9rW)dTU~*JLLG{S2XE`h|m;W@zkbL#?ubG~+ie zhLb&kG@zS@rVka@;;85R<1VdLJNG9Qvf;W@Vegy?+&T zR-}{2ioqinC)Ij?iV_|B(l($h|Llmbv@HXp#idq4@Mvva+4jQTzPu(VYNxdIo=Hc+ z=RaEH+G3v(G3u|e*%P_+l7LAULBoBBK0B<(NiK*nbE`gr%s1-R9jLjAPvn(F4DAyRRqaZ&cfWmsNkxsgFX`})clhnSCI@=O zWzx&n<5w3sl`8$4j!UfLAJhYcISA=Zm(X35@7Knf`qyka@y0u+K9pBTlI9|ps$wLR zqy!~Fe!|Y9?t%Xm-@h^+EI_>Z6-h~e5SPm;SFz-M;TBycYt!75^1`SxXBd74tzMT< z3KW**U=x*82m7Vv-( z8wb60y9F=*KCbL;rh6%Z@C;^Aa9W2Z&3LFmhwE92IbmO$8WSqPBO#hbF?;l25$R_& zFW!5)&c{5E`GG{1yTOx4q=6&S6OJk`#)&wRAEjl6@dK-oFgdalPYbH0p1hi8VtRF& z(lRGDW+h`_?>Y1Ab7`>p{kc|c3tek8Uh^7iFY~~PS)%}iT}aL>V(AU-rU(@F+E=+J zQ%yfk3gzE+KbY;3X3wC<;qx(SpS`H&^@prkBO`97IN_@pMZN8~}i{0lCHJ1y?Wmu%8uf;4(4?ETzfm z&GIX#1|gZKdF4s5b~86Aim99c;Hab+1K2+bXcV=H_L;Wvr>kP#hOBKQ%@Zc1*>xKd zn#uA93;FkRTx*z4>vXs&=N|vm=`T(oub!q&p%-XD;=l}{i37R2Z@ToAB?+jAoo~T> zZ_r9hL2Hlv#{~CIb6-K>bt4nha-8r*8XAK4Ad3^#0A!x(LgAJ}EC;q{$^!dtAbOZ7 z!SETNZk_buDnHV3Xel!6$fAh?V(F5~ML4UxvlY;i$6ea%(jaV97K)(Q%KAar=m@XB zDchQI#OQztC$hiS?%qy_nC;D`LNK8^8{0l(i&EDmsw4bpu+)a90ZV8Fa#GJs!~VW} zVw9@y3FGg)Q~+iRXiLf?V$r$oip295cy6G9Av&-UAiv0W9f$G2vg`Dx3if4dN6W!6 z!n{8eER9?_ojh7)OgTmIwHR_PLnL{z?ZK)9s$L+*wZZib%80)34trV_0_EYb9!W}= zBH+8TG6TB!GV8}hQ9f5}66MwYb2wtm^Lo9)A2t<%-&!0e1 z1t2hq@LcXG;H#e$(%<)ZKHY3<5QEM|mcqV@tsDYbLG4Cy%l~)~m3;9*_5>VcL|U~% z`1qN({omRrbkm;FZm}kyWfII7AlAd7YQV)0E0RG78@gjNZSGrEfZMLPeNz#5=C(f*ZhU4D8b6(hQlpQ{AGh>-yx|^hy zjE2>!nz%*~?FuHCevGWQHPi2Oa`O_{vTWtaR{69RZ$KQpDaz-#Iy-372zq#KNk<$N zGYd|0cmGIC=`NXy02wp*k~Kq{H@1w^{Q?W(EYlywrFcg}Ewk8#@f4A+xE*dQlO0o{g-nuq?yH(4-djIg|Hr;-<0PHeYQaKaw&_3RXUVCAqqgUQT%O3dr zU*x>WC;6h<^(AGspH%%5zZw~JvTnlDlk5okH?dWYL2W5c6B!Cx`*um=X;{?43zx;qsg3xLQ7YEy-=s%z$CZ0U>**1Wq1b~ zd>~#o%j#faNnypiGH)~Eb%YZ5RS0u!z&m5Q8szZq#56h7o*6*CP8z!j(_rH6IYM0o z*dh+zEeYdw9I0i`qm_qV;nI-j${(}nAJB3x*pU%4{ErO;px^dyFLaZH@{%!}*%;Fm zE*8Yz!&P{zz1tfy*?jpe%nVZyEb@B{gq+NO?qNg`BX$BueX%4G>rXFd5iDIcVw4h0 z(?B469S&@NNf1;3yNwLP|5)V@2*r#gT@dUt<>7GVe1Vr0K&gF*u_bQXWl|fM8jUiafu|?uQ{GlfPwuffM?Gqf22}l}TBNNc_vN*!b~f&#$49UNP9?8e6;Z1EXy#eoc9h!~L|;JgaV`G# z*7OA$yX)F=Au{ajH87xzwe5 zN6JhXcI@3V@TG*gCH>zdm+dhdAdmH76q2%3A+~zEqZ7x!<$LqcQ4-==znjE>+^ms> z7X4Wt%|DnlF#3)6Fr@H79zi$f!wgk{nt#e>&Nhu%1;(pmv!d2m2pWqMOuynmfwVyT zB!njAq9F;2cn2pLa+p=$n1y;GqamqYcmb zAeVj{NsUvnI=m$u7krYr6XRri$mN0KYt{c`aX^~@w6hhzZ zImpU$1=%&rbwgV(Aq!&;^ahj(JTo^XS|Wzb)mXDWn42giw#?xshvC|;~zwEWS=b>R(i7XF-$NUDN4w9hO%=T$UpT+LzTpkg| zz4G*F*Q^Vyq6Pu--XNz}HJCHO!bR1d)U?2n7{hzmC9T%_`Y=lH)>1sbSJS>=Z&9%& zuE)3|2~Ju<@Ni4Hvdf0mIlMgdFUgswq^v1nt33Qb*W~b89Jb=9zQqUIW)Cx+Q4h;F z$>`iC#~JK2eRI*aSlI`R9;9=93B-Z5#|Q`VhGTN&OeOkdiDtA zvIeAEZ_TzAphZ_6JIN&qTJH|JYgI+d4&!{CsNFqey6h0F5G9z(r}ykFf?RRw6RD;x z{sfJ<-L>g58l0MgC)Q{3bWn(RRWC+~-2K8sxJJkUkze+&Pc+XL#M+&tZ;n#E6(<=1 zAHQb#jsGp1#tTjhh*1Vjq;tiaR-gBEl85L*x45JO0d2t}Z-nkZclc#O=}`p35h{!Ri*QwWfan9$=Ou zv$%amDQoXV^Ly3JWt&EZZtywpHk3K??zHEq<^~kXn+pJ6C_g5tmt3b<&^2MOxM=OJ$S>)385$j+YnF~C$mT?s z)$dsl%2l|v5vEzkd-TW{#YRWuLkJzbR?=sm?uj?+rZs2;W+C;E^EcZ;=sJ98_S|~S z8=ng2$mTN+adKw}^9%!}f$-f$VsXS34Bz*Pk6zj#5L1u#{9kw89hsAwwFTXc#DIg> zBII}zm@{kvzwD53_u-2hJU%3_`If{V_4g0!CLaPvWCzgxD7Y;)p4xgRnTSmAf6565sBg@ z4fDC*aHI4c>wIlyvM!`eDPSF>G->+iA?k^NW6oDfH#sfqE6Y8|l#zy#74xbY>{f$C z*HW-k+XgdoK0JaR9|7b@%nx*k<9PqpCG7ibJ113-eMPEkEnwe*U@XvJRw8wkQP;n* z9DBGgUi^k2Stn^6<_5B9M%ZO@A|=0(6m2ztSZBPoqV6X;!3jVTtN-7 z7ZeT=bJ0cT>v1Btk&tz3jj|4cu2li6HP9b8Q3cVVO-q87tJwHRqsvL`J_c@+3bICn zK%zp00ujo$ey)3A0HYvO_ewYCf6F$9UW@3s>RruKaj$ZF)%KvYi`pYe>LZi^k%iM8 zl*FV&zIZ&Enz&Y5!G@tAf1smbxfVG|I-Ti$-T7OHHbNUYL#We5%+fj~LuQfK@(LZi z=&m?IUK81sj9DCqiFs9BpnyAd-Sv%WvI9duktzFnA82ekBw4?4ZN#iwW?p4mPb$Xr z+7GJqdddQA*iTeei|%_9f3QGF>|X%;3zA52ql2$C^le*RVo$WvvYT5nLjkFfQY*x4 zw8GH~4|tT)CdsnQP4p!-qhya*eTJ}V~$d$@;t}sqy2kH1CauE>6B=G|T?7iRG zzq;eB3^Nq1;|zfcIccM{&gD(tIS1c*np{2eo4Eu9oSlCA$7y#vFcPq=en*pDX#E(WJA8XVEoLL-R&%PBPLG z5>unI{8!xD^BZo}u3#f=ZOj$DW1E;Ou zcl0QH)0ss~iw=VSh5e`-oT2$U&UMaPvu{3gQ3o}%jDfo>YY6vC`7XP)hGUGxeC^) zTA3ztVn{Mqw!gpH9Hm@dM>V`i+&g5p0;@z)KaR0=^-mEYZx{rBOX!(m@?_SG2(EQj zqBT-y`COBwhs|g4J*HC2oFlB|MWM6^DPb9>&EQMoLmc^@)Y_CqXD7f~-FeK#V$x~j z<^8GlUIHA8@8*qsdAGyp0sE+ zN_y)5U?0^4Hw+gNrJEELQ>hx4WPe z+jdxM*ZC6Gr6{v0f^OK7)$}R!iQCIPv0s%sf3tjUKI1$KN6KMIkxGr|TCtmB5igaM zRLx1mLerywn9S8=J7+(-oYu>e9WOln0hfeur+k0+0X_&ZNvN3(O#PSl75C#dd;c5a z;cbNIY_;hnZE%{vaq~`zTtccD#0<{cu|M4Na)N4Lq1e; z=+;>G5&P&@BoRi6r2!SMvciLT-68qDqs~(@N%pm;T}JpM@8Y4Om)~(4_#5^q$z~M) zE4~IA)G`JHC%PLNF#geT7QXjf=A$n|Qahsvqrt#H`x-=z7V<#%TuF zIwR`Sk*lJwpq^c zs9%H8kYCUFpcUgpxf+}7s7n~yyE|Qmn1rA|v(phX#3cGw>*P;JpkBjVa1gY}b6nze z*H@dBh>4qRUg>x1&dKUQ|G0VRlFTCCV(j{V51$A+<=?@^QO?HHNoWF0&8kzj>VhmX* zDbS4atN`m7<2^G@cT97vs>u;Js@7-E2RFkc%9*TcJ~0Q@A4;u}86{p{!J8b7SfXk) zepLWPZ&8J9@5tDDSvGO<9=X;CSO)w0rT$SLPZE!z!69S`Af`TsQQ4;BhU7?h%$Fhs z#izE+`1R)&J{mYxaW)KUYQ)s$Be+;ol=Bb;&8ydiY}pzx(|9c-2#pnt%H@T-=Q&XiuWd$w>4y{xfc}!A`O`WTsA+>!&2quz zfv*?_=Gva=SL#W`>SKLRw6cC}CMV`yNz{a^3Vrub450$Ps?}@%t1PNV(I1s2DDAfI zoxJ;@^Jzr1*ml_Bhr;HjZ7>l&%OnnZk_7n?yrrLP;4{~ZQ~$4y6KXeUJ_8w!nU-5( zs6Er8YU2YX%$<7*p0JXHhImm*I}4y4!c9QHBWe2@{9bl{^xnOx{E27l1Zq<&)WtN~ zwDL47tA_E^OL&{jH&5(AG|R6cg$?jc(id3!>6^G`2)0Y0&&DVO7cF?Mit+k}zDu&V zx8jR*2z_tBR{(-j3vn-jlG&t5OFqrTECG@k^ z+$3`Pl`~fnsHgsE4pzgfHE=cVyRO6^I#IRLi|x1tEee1>^oYXRD7vhrS}GoE^+xyA zN}+|HkSU->yk0?n5WUD<73)wxcq==1RM-xYEhqgc$K%en7Qd)3BJ-gBt^pn+@OiFrE~fT?0!z@(9N6tC1mKlf%Vsw zZ#AQL2s;f%$OB$mw%BAW&vM$Tpj7h_4T42$|5EquJ9gW7h{uk7;{z1z(SVm)gaCOt zr|IhLnNl;cU84d=$+7Bt!{cnHtPM#$@b-)qpa=`2VYXiWR0r)7wO2xz8`K|{ z;be`!9|z-S?TkR@V2v)NaM0MygEi1MQAPiTZ<)uVGJA5i|Dyb}7uJ_HHoj`tWDt-MMD8B5jXK9&%d} z3_fI*KI+(2aK2a9#1LO4d&8nh3NY8S9_KV$Onv;-X8DMH529?ZTsUp+wNX7lm=GUy z@|aF#ONP;$BCTC9OOxr&T5rwvv?{g(e?7Maf5}0y6C}hAHAJrc5Bv1JIyO^v?6R=l zST5F%wsDT$d zfzM6lHSAJ-;=(FO6nFqYtjB~geir)kc*d&IAL`ACIVO5)&uoY2LJ@06xdraV8Iy2eQ=~?K;P@X%x{-uAD^_r z25&*?XK)FezXJ1H=&pAPv2C_`X;}s-EmW~-)GfHPFPY?_wR`$cd!AyHs)oHtKC8pA z6Pq7|;;0B0>zQ7>x59$rT+Puu`oLu!X@mksi;Jwe|8EY_M^WK?u&Uw=Y-J~34cB>k zH?$0v3eWlW$N>3lrQb9ALaOUueJ;M6>Ze)cQ^_ih{&THtSO&DpsX^lC()?Z@E5nvv z<1$T44D4Q6>G8%rQzCmF?qfVw70=dQ+Ws+49w5^p9$qCK7F}M%+1S6Pd(^w9+V`^p z8A3V~HgkbA`hs6UreWM_A0;8tJM$l~)^_OHM;n^MfRTx`M|An*uq7!^xLxSM!!mU7 zNIQVErOb3d&Z6SIw#oJd!t%hu!^(w`K0WapHbJ_sCXS z=iCQ(FyKWDg%zn%U4HwFa_Rjy36+!Haz;26rJVxsF^ekQJWSUr*;dkr{a@c_vVs2Q z_lbzokiD86Icz+C+^K<9iw3ivgX4z&J4ISWu+kMVBWPwOk8m3si-P(zpr%yx*|293zK$j8P)VeK!Qo@#zAzrZNji71ADl4q|J@3GYA6yr$ zx>ot0u=@ZzaX=qh9zLjQ4D9hi>XqrH0z2rM#2FUFUOe6Y^?VcS>SyOJS?=B`U&Vv` zCn$hVKYM@dwV$tC9PNaxR`8rrShGU=(`J*@Qt3`N>C|aSK~4bOu)_yPj5Rv*9H?#y z-uXF!oH5x)f>PFDARI9`4`89Er@dj zq2dqx^3xrv;v`*D=-;JT4qXCKk+oLQwq}ba<7w9oIB)aSU~ST>!$-_Fe&)oT{;i~) z8l`*c{PvIaY7rzU8;?;z4K;$4yo8w>ryXR#-q$0apv_}y6m(@jV`%gU)(w8STb?}e z(HBNe42WsN&|%Lpp)j43RrmkPwY%|qcq4lIvlk{yme~s8PDRa_)wzg~@11*#bsU(| zc^-OuGfNEBYUTq(BY{kyDB|N9ogU4RZA5_aL8{**uZ<48c@P&|YH}n+_fP8XJQ4PJvxw{}3nR|@odTV4K zZ1}Ea`sPvEOSP?5m&RE59lGEtMTHK=O7IIV7QJP-e~Hw6X~`i@?3;u|=!Xtc0oH}Q zg@S5n?NiCV;=(T0l|%&Q;2ddC3T56BT}kFr5-&hT*Pci#vK+9^(t_ zi$Fk}kmOL4E&$4&fBHNPEDZh%jmSq+4yMxv7ESpR28_Z_{V-WM#7h)euO7>o7 z$Q%UkbsnNZLGz6SPPp1@iz|gAulN3L`JlK6@$6g@ek?`d7?W|cV3pRJJ-5Hr?E{pd^3Xp)TN)_}`br_O2jCL6x0I}oKM|396H290aQD>3=o&?0U;0;}aP zoh&vwX^JL_8w6hJmt)73E zUSm=y`IP$wb624l+1o> zMeByk$H5;?DEW(3;N$K?$tzg3(J8ek`P{UBFN#!9h>Df6vmCV}bK#__BD5oM-0xRc zb>;8=GNet}?7LevP{3H)6jYFG7^lyk$1|scnPS>^(4I)v-Nq1HD&i9KdOA>EXg5;fKYJqc;~UIsZQvmU#_S_A zNjb0sT`B8{B=;K!2fn^(i>c8Ne3o^hTy(70Ryqb0g12^MyOTcH)hR=5K*543^OT`M zH~UJeuZ@@X60$271$F?&il7}>fjEE25I$mh*jjcV|FbEm!4i%2hQQ0voN7gMCYz*ybr{)&xv!`hoY1P7mwdb*o zZ_YXV$w%z6aEODV2{3=WK8;7IxkIw9GW;F1M02JYmV6%!HXMsR^&lQNbwZD zdy|xFeb6gLd3(#bYqI+}&j7We;jm zo^y?HwD6z1aX$=fvi9q%t}4$@B1BXUv-vnA>v=Zon}QrNY}U6XR5#-Lk0wcIK>^t2 z%twn9#>}pHZ%l%^KlRikP27}K5?GHhT+`@~CqT=+^J~z6-GkjlUT$A*1;Zqnmi5(j z=CKZQ70IQt)o6VmpMLf9!~ll{!wW%xsg5yYuI!zwiqmP;z@wEXjx5qx>0k{w99_8I zb1VBHSN0YGL-wAHeU6gKW=VNA7YU^LXv&&xUi}F{M75;pP(A60HTut%6~P0fbHCd- zs$hR{dxlZ;QL*U?vp=SnrA6CaBbVY_o;I!vvMdqP_&yqSD89Ot3ry;&kTRbNa?+~U zb6IDh(fG93fCuZ`cwzFpwMKDy;rFEB^cX_5BGih}^E#{I_ptXbYjdf*?gd$^uB@=3 zHgMSM|B-a<@l5akzjNu^gs{ z#oTWzkrH-J(nxX{R)kYi%tB@}c00e<&hPO!=Z|w9$I0jOd0$?y=j(a7rAj<(f7sxK z=fIZtNPI9>Fre(QN6X6GCaASXTph>i3`IMKu(tG7%MZgx&;K9*YsLQL{PK;rCj;rb znaT+W31CH!ZzKLl_7KI4+Jbz(h0`n8m}9L9Vvf*2F_IsdxSDm#>PQf zM=te|V=>xvU&wJ@_tJCl)r5oYO8nSy;po;wyCUt+TtOHZnHjUB6n?g>LF#{Z`!md$ z`?x)2q~Nm8JSjQ@4fskPlzzD92mFO#<*xb=i*O1KMU#(^r>o^H4bqxC;*F~BgVAnQ zMRPAics_JZ8CWQhhcJDRTw1B$eDsCGyGr|t?wYZBl`sv8$Qimi@uZ;N@=LH@3Vl2X zPTt@*^jIvv9O^x_<2j{TlIJl@So-1(SvpW|`7EjZ_4@_J?$@5Wi|~`{;BdSTe>bwn zX<^`XG&RM+FOjtH1_st?Yfd%MHWL3&)+ zko}^Kw6dtBAr^jy1+Jt|dZ#Q{KBcbt1!G5cTa{vKAjkkgRe7;PmCP}^<4wsU@Z%{e zJvz$TGTAe`i%c+pp@+b-1D+>WZU2|eB3)}G7$+fRC)%OfnR&K` zJYy6xQO&E;Omfp(JdPX8wHi}DfUykI?9bSNZoKJkyUF^2Aa*NBUDS7|I7%G*m!CK# zanX-!-Cj-2jqvgL2>5|2e=f<5`r!2_Dzpie5W$2>Lpf$P9WV8tid?_pSF2*W$*6YB z%lmjW3*kYxt~uW2tKZp-u(&}8JjH@jLvKD5>Te6SE(cYEr8E4(972S>v&kUG>l6Kq z)uZvicp;D?zkruI`w>&g9Bz&%PtzUB{k0rPc?Ti3>QLEib#YxH-na7O1({L@J4Pv! zKA7k(HvIl-)!uq*#)+xpg#C5U`NK(Esx}~Xxx33n!#9L^_s$Pf-TEz%hcq*`Km(sV zOaCfEW18P>s&R&WfQUR%s2&!CX+I}f?+6|YIG+6K4WmJ3CZ=MC8m4WdgQQHb2J>(F zarM#VJ)Nouk*EYtyCoTY>Z(kERgctA?-*e?5RkWs@@HPC>e4XE3v_b_orUzq`VE#jZ-&=;wCqa)|k&X3USR7=|UKs4D$9*!CFdzpU_ zO?j?{F`(dG$&bz<+47aPH2vg%myM~tEIq$X?&TTP`ZW{bt9dCX#JH&kT*I2+0^^>W z`E}sT*A46Ak{T->rga0F&P3+k^wDt`#YvVZ?{L$-1Gi!&nfO?5QgEQNet@!lS{)5r zdZKrJ3Y(jw@cBXcs(GDStOjJFY$fr@xGw4)f#HA4L1$)^U=Uq` zC;FN%tatHz!HpR@&|6>#^*&9S;3+-#3iWQ;1>xQ_N0VI{yMmWK5%k!X<>dE*nuo^I z+*fn$SKWm7z?+r>;Y~pA`E0QfqC32F9Red((#Hn{#uca0YP)n$kO&KO_7SE2Gq;BQ7-@q;R$EnzaZz&wXfQ!|PaC40tK>Dos6C)aZMgyo{`hFeuGbX{b?_MmCHJ zfr+w%A5h-k`}%d)mn8jz=nOJXK|%mCprVmH+E{u$*8g{sYV3o%Py}j4;#}ugV?}jT z94DwAUtM&jWM>deT}BwsRUxMFy@JVCD>0W%!gP)!x!Tp(hy?|mu~e^Q6Pv{y$nfXM zM~qyV4e7WPET8$`QFE_FA#~Eu|9uPXn5G`dd@DsaZLJk#)|6%tU{193gUToVftHqF zhAE8Lo)&iozu`p+KYg6G(9|Mz8;CaZeg9z>9- zp!5vlI-c=@3LG;vjQ(U{_@-wm$Cb)3MP@*-AMiEr-@?gVV`kM0eX>awZf)xFv?&9> z+{Ucb$v}&OkW(&C{p0on%b~@QxK})txNZY*_c)`nm>qhj`|90A=H?+FVWNn-Y1+-= zwBz?BTYNJ&?mC9S7f2Wj#=s+VpZerDzOLlCc~ULxvOgxN_ha~Je(IZ+GRGZ0X0oER zF-5AmWVohWpJj0H`r+}pf0yOR;>96&a82B3l!Yh6fkLsd8NDf z$?J?aI-=N^opCTw`96Uy#x;Xwaz;?0IBBk7={6l3en?GJf|R}L^2Rx>zPXCvfU@;@ z`h+)>iL)a4yC1&^hlZ<2(Qk8gaTOK>DS_JorE_yrNo9V`4K2MhMd|t45oXjPhBbDn z4CWuRGxB-OOjb6rNlbg=Rv-O<-9QcJiQ;iwIp)2 zY`rE{Hy>}Qp!AZ(sTu@QiffYyd5Z~gkF*YXq-v~TS;-2#EWE=;^YBmmu=M=A5w6N- z7!vH6L%}+1#))dnXvrqLzqw3eBNWG;$y81#nePcFJWAI+Hp%16m`w}QxqeL)kOvY! z36%fKv%S^+=KS0GwE*fQ5E~6(oP?i??tMYJ^J9&V!6=^C1NHrL!)ApmrAhwb${*`8 zt^e8|7iE{VsU%5Xv|L8HpOKFx&WsJ27sqE8_gt|@rTtWl-kX)decbWo2Drn_$9K)N zw>^v-SR2f{P|?`!>Muw`9^%W+%+*!0}tVky@_N z#`|~RUFg1}$&s$V3>YSUY1TIzf_!QBQfRm$J_P?5w>G$yIqrNA9e3C>D0{1U(%j$1 z)!j7diklR|xxMu(AhRu`JTFkAIMGx!ga48749GvXFvG<^a*bKYsfQI6Z{DN`*=iAS z7JFD)lOuREwif?B*ztT#X@;SIY^9|N!F(FDr8nINQ_Bi^-gm?9i zxi%x}Lm>?kpTe>8$JMKP?k2CgYmSuZ{3gnlWn|p3ilFPS?vyO#Y}V*FB!l~gN0X9_ zzc~)&VZE-OqYeL;kNE0nj{wOH!J}z~2U78n4)PpSZsGPj2LugqCrKj)!kE9#d42Ui z7o#A%0ADRgTC~_GK3fT{o1prNcmv9nz=@$ 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 H1)XrKaHks!HRa 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 z