From 9847c0d39b652fc74cb7d15831988dc85e30b653 Mon Sep 17 00:00:00 2001 From: Julie Bettens Date: Thu, 18 Apr 2024 15:30:01 +0200 Subject: [PATCH 1/6] add points for vdf recover benchmark --- go-ethereum/f3b/vdf/vdf_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum/f3b/vdf/vdf_test.go b/go-ethereum/f3b/vdf/vdf_test.go index 8386e3a..9a9feed 100644 --- a/go-ethereum/f3b/vdf/vdf_test.go +++ b/go-ethereum/f3b/vdf/vdf_test.go @@ -32,7 +32,7 @@ func TestRecoverSecretFromProof(t *testing.T) { } func BenchmarkRecoverSecret(b *testing.B) { - for log2t := 5; log2t <= 20; log2t += 5 { + for log2t := 5; log2t <= 20; log2t++ { b.Run(fmt.Sprintf("log2t=%d", log2t), func(b *testing.B) { label := []byte("test") n, _, _, _ := ShareSecret(label, log2t) From 8819101baa8c38c815513543bb87d4f09c913e9e Mon Sep 17 00:00:00 2001 From: Julie Bettens Date: Tue, 14 May 2024 10:22:01 +0200 Subject: [PATCH 2/6] yeet dela --- dela/.github/workflows/go_lint.yml | 29 - dela/.github/workflows/go_test.yml | 69 - dela/.gitignore | 5 - dela/.gitrepo | 12 - dela/Dockerfile | 13 - dela/LICENSE | 29 - dela/Makefile | 40 - dela/README.md | 21 - dela/cli/cli.go | 94 -- dela/cli/crypto/crypto.go | 37 - dela/cli/crypto/crypto_test.go | 101 -- dela/cli/flag.go | 72 - dela/cli/node/builder.go | 247 ---- dela/cli/node/builder_test.go | 162 --- dela/cli/node/daemon.go | 283 ---- dela/cli/node/daemon_test.go | 344 ----- dela/cli/node/example_test.go | 101 -- dela/cli/node/flagset.go | 94 -- dela/cli/node/flagset_test.go | 68 - dela/cli/node/injector.go | 57 - dela/cli/node/injector_test.go | 28 - dela/cli/node/memcoin/mod.go | 81 -- dela/cli/node/memcoin/mod_test.go | 276 ---- dela/cli/node/node.go | 86 -- dela/cli/ucli/ucli.go | 172 --- dela/cli/ucli/ucli_test.go | 137 -- dela/contracts/access/access.go | 178 --- dela/contracts/access/access_test.go | 142 -- dela/contracts/access/controller/access.json | 1 - dela/contracts/access/controller/action.go | 80 -- .../access/controller/action_test.go | 89 -- .../contracts/access/controller/controller.go | 82 -- .../access/controller/controller_test.go | 111 -- dela/contracts/access/controller/jstore.go | 113 -- .../access/controller/jstore_test.go | 146 -- dela/contracts/value/controller/controller.go | 54 - .../value/controller/controller_test.go | 56 - dela/contracts/value/value.go | 249 ---- dela/contracts/value/value_test.go | 248 ---- dela/core/access/access.go | 62 - dela/core/access/credentials.go | 37 - dela/core/access/credentials_test.go | 19 - dela/core/access/darc/darc.go | 95 -- dela/core/access/darc/darc_test.go | 111 -- dela/core/access/darc/example_test.go | 114 -- dela/core/access/darc/json/json.go | 155 --- dela/core/access/darc/json/json_test.go | 56 - dela/core/access/darc/types/expr.go | 126 -- dela/core/access/darc/types/expr_test.go | 112 -- dela/core/access/darc/types/permission.go | 174 --- .../core/access/darc/types/permission_test.go | 119 -- dela/core/access/darc/types/types.go | 38 - dela/core/execution/execution.go | 36 - dela/core/execution/native/example_test.go | 133 -- dela/core/execution/native/native.go | 68 - dela/core/execution/native/native_test.go | 53 - .../ordering/cosipbft/authority/authority.go | 56 - .../ordering/cosipbft/authority/changeset.go | 130 -- .../cosipbft/authority/changeset_test.go | 75 -- .../ordering/cosipbft/authority/json/json.go | 212 --- .../cosipbft/authority/json/json_test.go | 131 -- .../ordering/cosipbft/authority/roster.go | 303 ----- .../cosipbft/authority/roster_test.go | 240 ---- .../cosipbft/blockstore/blockstore.go | 94 -- .../core/ordering/cosipbft/blockstore/disk.go | 312 ----- .../ordering/cosipbft/blockstore/disk_test.go | 299 ----- .../ordering/cosipbft/blockstore/genesis.go | 145 -- .../cosipbft/blockstore/genesis_test.go | 177 --- dela/core/ordering/cosipbft/blockstore/mem.go | 294 ---- .../ordering/cosipbft/blockstore/mem_test.go | 219 --- .../core/ordering/cosipbft/blockstore/tree.go | 68 - .../ordering/cosipbft/blockstore/tree_test.go | 72 - .../ordering/cosipbft/blocksync/blocksync.go | 37 - .../ordering/cosipbft/blocksync/default.go | 335 ----- .../cosipbft/blocksync/default_test.go | 366 ----- .../ordering/cosipbft/blocksync/json/json.go | 145 -- .../cosipbft/blocksync/json/json_test.go | 134 -- .../cosipbft/blocksync/types/types.go | 186 --- .../cosipbft/blocksync/types/types_test.go | 123 -- .../contracts/viewchange/viewchange.go | 177 --- .../contracts/viewchange/viewchange_test.go | 150 --- .../ordering/cosipbft/controller/action.go | 282 ---- .../cosipbft/controller/action_test.go | 291 ---- .../cosipbft/controller/controller.go | 258 ---- .../cosipbft/controller/controller_test.go | 167 --- dela/core/ordering/cosipbft/cosipbft.go | 797 ----------- dela/core/ordering/cosipbft/cosipbft_test.go | 1157 ---------------- dela/core/ordering/cosipbft/json/chain.go | 84 -- .../core/ordering/cosipbft/json/chain_test.go | 86 -- dela/core/ordering/cosipbft/json/json.go | 467 ------- dela/core/ordering/cosipbft/json/json_test.go | 316 ----- dela/core/ordering/cosipbft/json/link.go | 166 --- dela/core/ordering/cosipbft/json/link_test.go | 205 --- dela/core/ordering/cosipbft/pbft/pbft.go | 856 ------------ dela/core/ordering/cosipbft/pbft/pbft_test.go | 975 -------------- dela/core/ordering/cosipbft/pbft/view.go | 97 -- dela/core/ordering/cosipbft/pbft/view_test.go | 45 - dela/core/ordering/cosipbft/proc.go | 257 ---- dela/core/ordering/cosipbft/proc_test.go | 343 ----- dela/core/ordering/cosipbft/proof.go | 66 - dela/core/ordering/cosipbft/proof_test.go | 85 -- dela/core/ordering/cosipbft/types/block.go | 351 ----- .../ordering/cosipbft/types/block_test.go | 207 --- dela/core/ordering/cosipbft/types/chain.go | 447 ------- .../ordering/cosipbft/types/chain_test.go | 347 ----- dela/core/ordering/cosipbft/types/messages.go | 284 ---- .../ordering/cosipbft/types/messages_test.go | 151 --- dela/core/ordering/cosipbft/types/types.go | 88 -- dela/core/ordering/ordering.go | 48 - dela/core/ordering/pow/block.go | 164 --- dela/core/ordering/pow/block_test.go | 78 -- dela/core/ordering/pow/observer.go | 11 - dela/core/ordering/pow/pow.go | 201 --- dela/core/ordering/pow/pow_test.go | 186 --- dela/core/ordering/pow/proof.go | 46 - .../store/hashtree/binprefix/binprefix.go | 286 ---- .../hashtree/binprefix/binprefix_test.go | 339 ----- dela/core/store/hashtree/binprefix/disk.go | 237 ---- .../store/hashtree/binprefix/disk_test.go | 238 ---- dela/core/store/hashtree/binprefix/format.go | 123 -- dela/core/store/hashtree/binprefix/path.go | 91 -- .../store/hashtree/binprefix/path_test.go | 43 - dela/core/store/hashtree/binprefix/tree.go | 860 ------------ .../store/hashtree/binprefix/tree_test.go | 702 ---------- dela/core/store/hashtree/hashtree.go | 54 - dela/core/store/kv/controller/controller.go | 56 - dela/core/store/kv/default.go | 143 -- dela/core/store/kv/default_test.go | 189 --- dela/core/store/kv/default_unix_test.go | 15 - dela/core/store/kv/default_windows_test.go | 14 - dela/core/store/kv/example_test.go | 68 - dela/core/store/kv/kv.go | 63 - dela/core/store/store.go | 31 - dela/core/txn/pool/controller/action.go | 116 -- dela/core/txn/pool/controller/action_test.go | 113 -- dela/core/txn/pool/controller/controller.go | 78 -- .../txn/pool/controller/controller_test.go | 90 -- dela/core/txn/pool/gatherer.go | 263 ---- dela/core/txn/pool/gatherer_test.go | 187 --- dela/core/txn/pool/gossip/gossip.go | 130 -- dela/core/txn/pool/gossip/gossip_test.go | 300 ----- dela/core/txn/pool/mem/mem.go | 81 -- dela/core/txn/pool/mem/mem_test.go | 126 -- dela/core/txn/pool/pool.go | 71 - dela/core/txn/pool/stats.go | 20 - dela/core/txn/pool/stats_test.go | 20 - dela/core/txn/pool/transactions.go | 57 - dela/core/txn/signed/controller/controller.go | 88 -- dela/core/txn/signed/example_test.go | 50 - dela/core/txn/signed/json/json.go | 142 -- dela/core/txn/signed/json/json_test.go | 108 -- dela/core/txn/signed/signed.go | 334 ----- dela/core/txn/signed/signed_test.go | 199 --- dela/core/txn/txn.go | 59 - dela/core/validation/simple/example_test.go | 129 -- dela/core/validation/simple/json/json.go | 141 -- dela/core/validation/simple/result.go | 212 --- dela/core/validation/simple/result_test.go | 143 -- dela/core/validation/simple/simple.go | 187 --- dela/core/validation/simple/simple_test.go | 162 --- dela/core/validation/validation.go | 68 - dela/core/watcher.go | 70 - dela/core/watcher_test.go | 64 - dela/cosi/cosi.go | 73 - dela/cosi/flatcosi/example_test.go | 97 -- dela/cosi/flatcosi/flatcosi.go | 170 --- dela/cosi/flatcosi/flatcosi_test.go | 194 --- dela/cosi/flatcosi/handler.go | 55 - dela/cosi/flatcosi/handler_test.go | 77 -- dela/cosi/json/json.go | 113 -- dela/cosi/json/json_test.go | 85 -- dela/cosi/messages.go | 98 -- dela/cosi/messages_test.go | 56 - dela/cosi/threshold/actor.go | 137 -- dela/cosi/threshold/actor_test.go | 167 --- dela/cosi/threshold/handler.go | 82 -- dela/cosi/threshold/handler_test.go | 92 -- dela/cosi/threshold/json/json.go | 78 -- dela/cosi/threshold/json/json_test.go | 53 - dela/cosi/threshold/threshold.go | 124 -- dela/cosi/threshold/threshold_test.go | 110 -- dela/cosi/threshold/types/types.go | 293 ---- dela/cosi/threshold/types/types_test.go | 177 --- dela/crypto/bls/bls.go | 464 ------- dela/crypto/bls/bls_test.go | 386 ------ dela/crypto/bls/command/action.go | 118 -- dela/crypto/bls/command/action_test.go | 156 --- dela/crypto/bls/command/command.go | 57 - dela/crypto/bls/command/command_test.go | 52 - dela/crypto/bls/example_test.go | 48 - dela/crypto/bls/json/json.go | 113 -- dela/crypto/bls/json/json_test.go | 99 -- dela/crypto/common/common.go | 177 --- dela/crypto/common/common_test.go | 123 -- dela/crypto/common/example_test.go | 86 -- dela/crypto/common/json/json.go | 70 - dela/crypto/common/json/json_test.go | 39 - dela/crypto/ed25519/ed25519.go | 330 ----- dela/crypto/ed25519/ed25519_test.go | 320 ----- dela/crypto/ed25519/example_test.go | 23 - dela/crypto/ed25519/json/json.go | 90 -- dela/crypto/ed25519/json/json_test.go | 102 -- dela/crypto/hash.go | 25 - dela/crypto/hash_test.go | 12 - dela/crypto/loader/example_test.go | 39 - dela/crypto/loader/file.go | 85 -- dela/crypto/loader/file_test.go | 123 -- dela/crypto/loader/mod.go | 25 - dela/crypto/mod.go | 161 --- dela/crypto/rand.go | 18 - dela/crypto/rand_test.go | 23 - dela/demo.sh | 69 - dela/dkg/ibe/ibe.go | 113 -- dela/dkg/pedersen_bn256/dkgcli/README.md | 33 - dela/dkg/pedersen_bn256/dkgcli/dockerfile | 17 - dela/docker-compose.yml | 8 - dela/docs/.nojekyll | 0 dela/docs/README.md | 3 - dela/docs/architecture.md | 37 - dela/docs/assets/cosipbft.png | Bin 117751 -> 0 bytes dela/docs/assets/dela-flow.png | Bin 119496 -> 0 bytes dela/docs/assets/infograph.png | Bin 672948 -> 0 bytes dela/docs/assets/logo.png | Bin 21579 -> 0 bytes dela/docs/assets/logotype.png | Bin 59557 -> 0 bytes dela/docs/assets/modules.png | Bin 394403 -> 0 bytes dela/docs/assets/overview.png | Bin 227064 -> 0 bytes dela/docs/assets/packages.png | Bin 1351642 -> 0 bytes dela/docs/assets/serde.png | Bin 56420 -> 0 bytes dela/docs/assets/stacks.png | Bin 44498 -> 0 bytes dela/docs/cosipbft.md | 89 -- dela/docs/dela.md | 126 -- dela/docs/diagrams/dela.drawio | 1 - dela/docs/diagrams/ledger.drawio | 1 - dela/docs/diagrams/serde.drawio | 1 - dela/docs/guideline.md | 235 ---- dela/docs/index.html | 42 - dela/docs/ledger.md | 75 -- dela/docs/manual_tests.md | 6 - dela/docs/memcoin.md | 62 - dela/docs/mino.md | 192 --- dela/docs/serde.md | 190 --- dela/docs/sidebar.md | 10 - dela/docs/unicore_logo.png | Bin 8242 -> 0 bytes dela/go.mod | 56 - dela/go.sum | 214 --- dela/internal/depgraph/dep.yml | 24 - dela/internal/depgraph/mod.go | 363 ----- dela/internal/mcheck/mod.go | 126 -- dela/internal/mcheck/mod_test.go | 15 - .../mcheck/testdata/src/comment/mod1.go | 27 - .../mcheck/testdata/src/comment/mod2.go | 5 - .../mcheck/testdata/src/ifcheck/mod1.go | 26 - .../mcheck/testdata/src/ifcheck/mod2.go | 10 - dela/internal/testing/fake/crypto.go | 407 ------ dela/internal/testing/fake/mino.go | 615 --------- dela/internal/testing/fake/mod.go | 183 --- dela/internal/testing/fake/serde.go | 201 --- dela/internal/testing/fake/store.go | 211 --- dela/internal/testing/fake/tracing.go | 15 - dela/internal/testing/mod.go | 36 - dela/internal/tracing/mod.go | 82 -- dela/internal/traffic/mod.go | 447 ------- dela/internal/traffic/mod_test.go | 237 ---- dela/mino/addr.go | 84 -- dela/mino/addr_test.go | 63 - dela/mino/gossip/flat.go | 143 -- dela/mino/gossip/flat_test.go | 111 -- dela/mino/gossip/mod.go | 44 - dela/mino/mino.go | 179 --- dela/mino/mino_test.go | 52 - dela/mino/minoch/address.go | 50 - dela/mino/minoch/address_test.go | 60 - dela/mino/minoch/example_test.go | 79 -- dela/mino/minoch/manager.go | 67 - dela/mino/minoch/manager_test.go | 48 - dela/mino/minoch/mod.go | 135 -- dela/mino/minoch/mod_test.go | 93 -- dela/mino/minoch/rpc.go | 300 ----- dela/mino/minoch/rpc_test.go | 288 ---- dela/mino/minogrpc/certs/disk.go | 185 --- dela/mino/minogrpc/certs/disk_test.go | 227 ---- dela/mino/minogrpc/certs/example_test.go | 46 - dela/mino/minogrpc/certs/mem.go | 129 -- dela/mino/minogrpc/certs/mem_test.go | 148 -- dela/mino/minogrpc/certs/mod.go | 49 - dela/mino/minogrpc/controller/actions.go | 158 --- dela/mino/minogrpc/controller/actions_test.go | 326 ----- dela/mino/minogrpc/controller/mod.go | 325 ----- dela/mino/minogrpc/controller/mod_test.go | 423 ------ dela/mino/minogrpc/example_test.go | 242 ---- dela/mino/minogrpc/mod.go | 437 ------ dela/mino/minogrpc/mod_test.go | 309 ----- dela/mino/minogrpc/ptypes/mod.go | 5 - dela/mino/minogrpc/ptypes/overlay.pb.go | 645 --------- dela/mino/minogrpc/ptypes/overlay.proto | 67 - dela/mino/minogrpc/rpc.go | 261 ---- dela/mino/minogrpc/rpc_test.go | 446 ------ dela/mino/minogrpc/server.go | 859 ------------ dela/mino/minogrpc/server_test.go | 1191 ----------------- dela/mino/minogrpc/session/addr.go | 165 --- dela/mino/minogrpc/session/addr_test.go | 97 -- dela/mino/minogrpc/session/mod.go | 663 --------- dela/mino/minogrpc/session/mod_test.go | 636 --------- dela/mino/minogrpc/session/queue.go | 125 -- dela/mino/minogrpc/session/queue_test.go | 64 - dela/mino/minogrpc/tokens/mod.go | 67 - dela/mino/minogrpc/tokens/mod_test.go | 32 - dela/mino/option.go | 97 -- dela/mino/option_test.go | 77 -- dela/mino/proxy/http/controller/action.go | 72 - .../mino/proxy/http/controller/action_test.go | 176 --- dela/mino/proxy/http/controller/mod.go | 66 - dela/mino/proxy/http/controller/mod_test.go | 92 -- dela/mino/proxy/http/mod.go | 180 --- dela/mino/proxy/http/mod_test.go | 102 -- dela/mino/proxy/mod.go | 25 - dela/mino/response.go | 46 - dela/mino/response_test.go | 33 - dela/mino/router/router.go | 123 -- dela/mino/router/tree/example_test.go | 65 - dela/mino/router/tree/json/json.go | 158 --- dela/mino/router/tree/json/json_test.go | 91 -- dela/mino/router/tree/mod.go | 162 --- dela/mino/router/tree/mod_test.go | 141 -- dela/mino/router/tree/tree.go | 215 --- dela/mino/router/tree/tree_test.go | 78 -- dela/mino/router/tree/types/handshake.go | 97 -- dela/mino/router/tree/types/handshake_test.go | 66 - dela/mino/router/tree/types/packet.go | 145 -- dela/mino/router/tree/types/packet_test.go | 83 -- dela/mino/router/tree/types/types.go | 20 - dela/mod.go | 122 -- dela/serde/context.go | 56 - dela/serde/context_test.go | 39 - dela/serde/example_test.go | 188 --- dela/serde/json/json.go | 51 - dela/serde/json/json_test.go | 46 - dela/serde/registry/registry.go | 23 - dela/serde/registry/simple.go | 65 - dela/serde/registry/simple_test.go | 40 - dela/serde/serde.go | 54 - dela/serde/xml/mod.go | 39 - dela/serde/xml/xml_test.go | 39 - dela/test/README.md | 51 - dela/test/cosidela_test.go | 413 ------ dela/test/dela_test.go | 17 - dela/test/integration_test.go | 148 -- dela/test/testcreatekey.sh | 10 - dela/test/testlist.sh | 15 - dela/test/testsetup.sh | 48 - dela/test/teststart.sh | 38 - dela/test/teststop.sh | 13 - dela/test/teststore.sh | 17 - go-ethereum/go.mod | 8 +- go-ethereum/go.sum | 41 +- go.mod | 64 +- go.sum | 189 +-- setup.sh | 2 +- .../dkg}/controller/action.go | 139 +- .../dkg}/controller/action_test.go | 51 +- .../dkg}/controller/controller.go | 45 +- .../dkg}/controller/controller_test.go | 4 +- dela/dkg/dkg.go => smc/dkg/interfaces.go | 0 .../pedersen_bn256 => smc/dkg}/json/json.go | 2 +- .../dkg}/json/json_test.go | 4 +- .../dkg/pedersen}/dkg.go | 4 +- .../dkg/pedersen}/dkg_test.go | 6 +- .../dkg/pedersen}/handler.go | 0 .../dkg/pedersen}/handler_test.go | 2 +- .../dkg/pedersen}/pedersen.go | 9 +- .../dkg/pedersen}/pedersen_test.go | 10 +- .../dkg/pedersen}/resharing_test.go | 2 +- .../dkg/pedersen}/state.go | 0 .../dkg/pedersen}/state_test.go | 0 .../dkg/pedersen}/types/messages.go | 0 .../dkg/pedersen}/types/messages_test.go | 2 +- .../dkgcli/dkgcli.go => smc/dkgcli/main.go | 2 +- smc/go.mod | 51 + smc/go.sum | 131 ++ 379 files changed, 312 insertions(+), 51990 deletions(-) delete mode 100644 dela/.github/workflows/go_lint.yml delete mode 100644 dela/.github/workflows/go_test.yml delete mode 100644 dela/.gitignore delete mode 100644 dela/.gitrepo delete mode 100644 dela/Dockerfile delete mode 100644 dela/LICENSE delete mode 100644 dela/Makefile delete mode 100644 dela/README.md delete mode 100644 dela/cli/cli.go delete mode 100644 dela/cli/crypto/crypto.go delete mode 100644 dela/cli/crypto/crypto_test.go delete mode 100644 dela/cli/flag.go delete mode 100644 dela/cli/node/builder.go delete mode 100644 dela/cli/node/builder_test.go delete mode 100644 dela/cli/node/daemon.go delete mode 100644 dela/cli/node/daemon_test.go delete mode 100644 dela/cli/node/example_test.go delete mode 100644 dela/cli/node/flagset.go delete mode 100644 dela/cli/node/flagset_test.go delete mode 100644 dela/cli/node/injector.go delete mode 100644 dela/cli/node/injector_test.go delete mode 100644 dela/cli/node/memcoin/mod.go delete mode 100644 dela/cli/node/memcoin/mod_test.go delete mode 100644 dela/cli/node/node.go delete mode 100644 dela/cli/ucli/ucli.go delete mode 100644 dela/cli/ucli/ucli_test.go delete mode 100644 dela/contracts/access/access.go delete mode 100644 dela/contracts/access/access_test.go delete mode 100644 dela/contracts/access/controller/access.json delete mode 100644 dela/contracts/access/controller/action.go delete mode 100644 dela/contracts/access/controller/action_test.go delete mode 100644 dela/contracts/access/controller/controller.go delete mode 100644 dela/contracts/access/controller/controller_test.go delete mode 100644 dela/contracts/access/controller/jstore.go delete mode 100644 dela/contracts/access/controller/jstore_test.go delete mode 100644 dela/contracts/value/controller/controller.go delete mode 100644 dela/contracts/value/controller/controller_test.go delete mode 100644 dela/contracts/value/value.go delete mode 100644 dela/contracts/value/value_test.go delete mode 100644 dela/core/access/access.go delete mode 100644 dela/core/access/credentials.go delete mode 100644 dela/core/access/credentials_test.go delete mode 100644 dela/core/access/darc/darc.go delete mode 100644 dela/core/access/darc/darc_test.go delete mode 100644 dela/core/access/darc/example_test.go delete mode 100644 dela/core/access/darc/json/json.go delete mode 100644 dela/core/access/darc/json/json_test.go delete mode 100644 dela/core/access/darc/types/expr.go delete mode 100644 dela/core/access/darc/types/expr_test.go delete mode 100644 dela/core/access/darc/types/permission.go delete mode 100644 dela/core/access/darc/types/permission_test.go delete mode 100644 dela/core/access/darc/types/types.go delete mode 100644 dela/core/execution/execution.go delete mode 100644 dela/core/execution/native/example_test.go delete mode 100644 dela/core/execution/native/native.go delete mode 100644 dela/core/execution/native/native_test.go delete mode 100644 dela/core/ordering/cosipbft/authority/authority.go delete mode 100644 dela/core/ordering/cosipbft/authority/changeset.go delete mode 100644 dela/core/ordering/cosipbft/authority/changeset_test.go delete mode 100644 dela/core/ordering/cosipbft/authority/json/json.go delete mode 100644 dela/core/ordering/cosipbft/authority/json/json_test.go delete mode 100644 dela/core/ordering/cosipbft/authority/roster.go delete mode 100644 dela/core/ordering/cosipbft/authority/roster_test.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/blockstore.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/disk.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/disk_test.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/genesis.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/genesis_test.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/mem.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/mem_test.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/tree.go delete mode 100644 dela/core/ordering/cosipbft/blockstore/tree_test.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/blocksync.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/default.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/default_test.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/json/json.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/json/json_test.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/types/types.go delete mode 100644 dela/core/ordering/cosipbft/blocksync/types/types_test.go delete mode 100644 dela/core/ordering/cosipbft/contracts/viewchange/viewchange.go delete mode 100644 dela/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go delete mode 100644 dela/core/ordering/cosipbft/controller/action.go delete mode 100644 dela/core/ordering/cosipbft/controller/action_test.go delete mode 100644 dela/core/ordering/cosipbft/controller/controller.go delete mode 100644 dela/core/ordering/cosipbft/controller/controller_test.go delete mode 100644 dela/core/ordering/cosipbft/cosipbft.go delete mode 100644 dela/core/ordering/cosipbft/cosipbft_test.go delete mode 100644 dela/core/ordering/cosipbft/json/chain.go delete mode 100644 dela/core/ordering/cosipbft/json/chain_test.go delete mode 100644 dela/core/ordering/cosipbft/json/json.go delete mode 100644 dela/core/ordering/cosipbft/json/json_test.go delete mode 100644 dela/core/ordering/cosipbft/json/link.go delete mode 100644 dela/core/ordering/cosipbft/json/link_test.go delete mode 100644 dela/core/ordering/cosipbft/pbft/pbft.go delete mode 100644 dela/core/ordering/cosipbft/pbft/pbft_test.go delete mode 100644 dela/core/ordering/cosipbft/pbft/view.go delete mode 100644 dela/core/ordering/cosipbft/pbft/view_test.go delete mode 100644 dela/core/ordering/cosipbft/proc.go delete mode 100644 dela/core/ordering/cosipbft/proc_test.go delete mode 100644 dela/core/ordering/cosipbft/proof.go delete mode 100644 dela/core/ordering/cosipbft/proof_test.go delete mode 100644 dela/core/ordering/cosipbft/types/block.go delete mode 100644 dela/core/ordering/cosipbft/types/block_test.go delete mode 100644 dela/core/ordering/cosipbft/types/chain.go delete mode 100644 dela/core/ordering/cosipbft/types/chain_test.go delete mode 100644 dela/core/ordering/cosipbft/types/messages.go delete mode 100644 dela/core/ordering/cosipbft/types/messages_test.go delete mode 100644 dela/core/ordering/cosipbft/types/types.go delete mode 100644 dela/core/ordering/ordering.go delete mode 100644 dela/core/ordering/pow/block.go delete mode 100644 dela/core/ordering/pow/block_test.go delete mode 100644 dela/core/ordering/pow/observer.go delete mode 100644 dela/core/ordering/pow/pow.go delete mode 100644 dela/core/ordering/pow/pow_test.go delete mode 100644 dela/core/ordering/pow/proof.go delete mode 100644 dela/core/store/hashtree/binprefix/binprefix.go delete mode 100644 dela/core/store/hashtree/binprefix/binprefix_test.go delete mode 100644 dela/core/store/hashtree/binprefix/disk.go delete mode 100644 dela/core/store/hashtree/binprefix/disk_test.go delete mode 100644 dela/core/store/hashtree/binprefix/format.go delete mode 100644 dela/core/store/hashtree/binprefix/path.go delete mode 100644 dela/core/store/hashtree/binprefix/path_test.go delete mode 100644 dela/core/store/hashtree/binprefix/tree.go delete mode 100644 dela/core/store/hashtree/binprefix/tree_test.go delete mode 100644 dela/core/store/hashtree/hashtree.go delete mode 100644 dela/core/store/kv/controller/controller.go delete mode 100644 dela/core/store/kv/default.go delete mode 100644 dela/core/store/kv/default_test.go delete mode 100644 dela/core/store/kv/default_unix_test.go delete mode 100644 dela/core/store/kv/default_windows_test.go delete mode 100644 dela/core/store/kv/example_test.go delete mode 100644 dela/core/store/kv/kv.go delete mode 100644 dela/core/store/store.go delete mode 100644 dela/core/txn/pool/controller/action.go delete mode 100644 dela/core/txn/pool/controller/action_test.go delete mode 100644 dela/core/txn/pool/controller/controller.go delete mode 100644 dela/core/txn/pool/controller/controller_test.go delete mode 100644 dela/core/txn/pool/gatherer.go delete mode 100644 dela/core/txn/pool/gatherer_test.go delete mode 100644 dela/core/txn/pool/gossip/gossip.go delete mode 100644 dela/core/txn/pool/gossip/gossip_test.go delete mode 100644 dela/core/txn/pool/mem/mem.go delete mode 100644 dela/core/txn/pool/mem/mem_test.go delete mode 100644 dela/core/txn/pool/pool.go delete mode 100644 dela/core/txn/pool/stats.go delete mode 100644 dela/core/txn/pool/stats_test.go delete mode 100644 dela/core/txn/pool/transactions.go delete mode 100644 dela/core/txn/signed/controller/controller.go delete mode 100644 dela/core/txn/signed/example_test.go delete mode 100644 dela/core/txn/signed/json/json.go delete mode 100644 dela/core/txn/signed/json/json_test.go delete mode 100644 dela/core/txn/signed/signed.go delete mode 100644 dela/core/txn/signed/signed_test.go delete mode 100644 dela/core/txn/txn.go delete mode 100644 dela/core/validation/simple/example_test.go delete mode 100644 dela/core/validation/simple/json/json.go delete mode 100644 dela/core/validation/simple/result.go delete mode 100644 dela/core/validation/simple/result_test.go delete mode 100644 dela/core/validation/simple/simple.go delete mode 100644 dela/core/validation/simple/simple_test.go delete mode 100644 dela/core/validation/validation.go delete mode 100644 dela/core/watcher.go delete mode 100644 dela/core/watcher_test.go delete mode 100644 dela/cosi/cosi.go delete mode 100644 dela/cosi/flatcosi/example_test.go delete mode 100644 dela/cosi/flatcosi/flatcosi.go delete mode 100644 dela/cosi/flatcosi/flatcosi_test.go delete mode 100644 dela/cosi/flatcosi/handler.go delete mode 100644 dela/cosi/flatcosi/handler_test.go delete mode 100644 dela/cosi/json/json.go delete mode 100644 dela/cosi/json/json_test.go delete mode 100644 dela/cosi/messages.go delete mode 100644 dela/cosi/messages_test.go delete mode 100644 dela/cosi/threshold/actor.go delete mode 100644 dela/cosi/threshold/actor_test.go delete mode 100644 dela/cosi/threshold/handler.go delete mode 100644 dela/cosi/threshold/handler_test.go delete mode 100644 dela/cosi/threshold/json/json.go delete mode 100644 dela/cosi/threshold/json/json_test.go delete mode 100644 dela/cosi/threshold/threshold.go delete mode 100644 dela/cosi/threshold/threshold_test.go delete mode 100644 dela/cosi/threshold/types/types.go delete mode 100644 dela/cosi/threshold/types/types_test.go delete mode 100644 dela/crypto/bls/bls.go delete mode 100644 dela/crypto/bls/bls_test.go delete mode 100644 dela/crypto/bls/command/action.go delete mode 100644 dela/crypto/bls/command/action_test.go delete mode 100644 dela/crypto/bls/command/command.go delete mode 100644 dela/crypto/bls/command/command_test.go delete mode 100644 dela/crypto/bls/example_test.go delete mode 100644 dela/crypto/bls/json/json.go delete mode 100644 dela/crypto/bls/json/json_test.go delete mode 100644 dela/crypto/common/common.go delete mode 100644 dela/crypto/common/common_test.go delete mode 100644 dela/crypto/common/example_test.go delete mode 100644 dela/crypto/common/json/json.go delete mode 100644 dela/crypto/common/json/json_test.go delete mode 100644 dela/crypto/ed25519/ed25519.go delete mode 100644 dela/crypto/ed25519/ed25519_test.go delete mode 100644 dela/crypto/ed25519/example_test.go delete mode 100644 dela/crypto/ed25519/json/json.go delete mode 100644 dela/crypto/ed25519/json/json_test.go delete mode 100644 dela/crypto/hash.go delete mode 100644 dela/crypto/hash_test.go delete mode 100644 dela/crypto/loader/example_test.go delete mode 100644 dela/crypto/loader/file.go delete mode 100644 dela/crypto/loader/file_test.go delete mode 100644 dela/crypto/loader/mod.go delete mode 100644 dela/crypto/mod.go delete mode 100644 dela/crypto/rand.go delete mode 100644 dela/crypto/rand_test.go delete mode 100755 dela/demo.sh delete mode 100644 dela/dkg/ibe/ibe.go delete mode 100644 dela/dkg/pedersen_bn256/dkgcli/README.md delete mode 100644 dela/dkg/pedersen_bn256/dkgcli/dockerfile delete mode 100644 dela/docker-compose.yml delete mode 100644 dela/docs/.nojekyll delete mode 100644 dela/docs/README.md delete mode 100644 dela/docs/architecture.md delete mode 100644 dela/docs/assets/cosipbft.png delete mode 100644 dela/docs/assets/dela-flow.png delete mode 100644 dela/docs/assets/infograph.png delete mode 100644 dela/docs/assets/logo.png delete mode 100644 dela/docs/assets/logotype.png delete mode 100644 dela/docs/assets/modules.png delete mode 100644 dela/docs/assets/overview.png delete mode 100644 dela/docs/assets/packages.png delete mode 100644 dela/docs/assets/serde.png delete mode 100644 dela/docs/assets/stacks.png delete mode 100644 dela/docs/cosipbft.md delete mode 100644 dela/docs/dela.md delete mode 100644 dela/docs/diagrams/dela.drawio delete mode 100644 dela/docs/diagrams/ledger.drawio delete mode 100644 dela/docs/diagrams/serde.drawio delete mode 100644 dela/docs/guideline.md delete mode 100644 dela/docs/index.html delete mode 100644 dela/docs/ledger.md delete mode 100644 dela/docs/manual_tests.md delete mode 100644 dela/docs/memcoin.md delete mode 100644 dela/docs/mino.md delete mode 100644 dela/docs/serde.md delete mode 100644 dela/docs/sidebar.md delete mode 100644 dela/docs/unicore_logo.png delete mode 100644 dela/go.mod delete mode 100644 dela/go.sum delete mode 100644 dela/internal/depgraph/dep.yml delete mode 100644 dela/internal/depgraph/mod.go delete mode 100644 dela/internal/mcheck/mod.go delete mode 100644 dela/internal/mcheck/mod_test.go delete mode 100644 dela/internal/mcheck/testdata/src/comment/mod1.go delete mode 100644 dela/internal/mcheck/testdata/src/comment/mod2.go delete mode 100644 dela/internal/mcheck/testdata/src/ifcheck/mod1.go delete mode 100644 dela/internal/mcheck/testdata/src/ifcheck/mod2.go delete mode 100644 dela/internal/testing/fake/crypto.go delete mode 100644 dela/internal/testing/fake/mino.go delete mode 100644 dela/internal/testing/fake/mod.go delete mode 100644 dela/internal/testing/fake/serde.go delete mode 100644 dela/internal/testing/fake/store.go delete mode 100644 dela/internal/testing/fake/tracing.go delete mode 100644 dela/internal/testing/mod.go delete mode 100644 dela/internal/tracing/mod.go delete mode 100644 dela/internal/traffic/mod.go delete mode 100644 dela/internal/traffic/mod_test.go delete mode 100644 dela/mino/addr.go delete mode 100644 dela/mino/addr_test.go delete mode 100644 dela/mino/gossip/flat.go delete mode 100644 dela/mino/gossip/flat_test.go delete mode 100644 dela/mino/gossip/mod.go delete mode 100644 dela/mino/mino.go delete mode 100644 dela/mino/mino_test.go delete mode 100644 dela/mino/minoch/address.go delete mode 100644 dela/mino/minoch/address_test.go delete mode 100644 dela/mino/minoch/example_test.go delete mode 100644 dela/mino/minoch/manager.go delete mode 100644 dela/mino/minoch/manager_test.go delete mode 100644 dela/mino/minoch/mod.go delete mode 100644 dela/mino/minoch/mod_test.go delete mode 100644 dela/mino/minoch/rpc.go delete mode 100644 dela/mino/minoch/rpc_test.go delete mode 100644 dela/mino/minogrpc/certs/disk.go delete mode 100644 dela/mino/minogrpc/certs/disk_test.go delete mode 100644 dela/mino/minogrpc/certs/example_test.go delete mode 100644 dela/mino/minogrpc/certs/mem.go delete mode 100644 dela/mino/minogrpc/certs/mem_test.go delete mode 100644 dela/mino/minogrpc/certs/mod.go delete mode 100644 dela/mino/minogrpc/controller/actions.go delete mode 100644 dela/mino/minogrpc/controller/actions_test.go delete mode 100644 dela/mino/minogrpc/controller/mod.go delete mode 100644 dela/mino/minogrpc/controller/mod_test.go delete mode 100644 dela/mino/minogrpc/example_test.go delete mode 100644 dela/mino/minogrpc/mod.go delete mode 100644 dela/mino/minogrpc/mod_test.go delete mode 100644 dela/mino/minogrpc/ptypes/mod.go delete mode 100644 dela/mino/minogrpc/ptypes/overlay.pb.go delete mode 100644 dela/mino/minogrpc/ptypes/overlay.proto delete mode 100644 dela/mino/minogrpc/rpc.go delete mode 100644 dela/mino/minogrpc/rpc_test.go delete mode 100644 dela/mino/minogrpc/server.go delete mode 100644 dela/mino/minogrpc/server_test.go delete mode 100644 dela/mino/minogrpc/session/addr.go delete mode 100644 dela/mino/minogrpc/session/addr_test.go delete mode 100644 dela/mino/minogrpc/session/mod.go delete mode 100644 dela/mino/minogrpc/session/mod_test.go delete mode 100644 dela/mino/minogrpc/session/queue.go delete mode 100644 dela/mino/minogrpc/session/queue_test.go delete mode 100644 dela/mino/minogrpc/tokens/mod.go delete mode 100644 dela/mino/minogrpc/tokens/mod_test.go delete mode 100644 dela/mino/option.go delete mode 100644 dela/mino/option_test.go delete mode 100644 dela/mino/proxy/http/controller/action.go delete mode 100644 dela/mino/proxy/http/controller/action_test.go delete mode 100644 dela/mino/proxy/http/controller/mod.go delete mode 100644 dela/mino/proxy/http/controller/mod_test.go delete mode 100644 dela/mino/proxy/http/mod.go delete mode 100644 dela/mino/proxy/http/mod_test.go delete mode 100644 dela/mino/proxy/mod.go delete mode 100644 dela/mino/response.go delete mode 100644 dela/mino/response_test.go delete mode 100644 dela/mino/router/router.go delete mode 100644 dela/mino/router/tree/example_test.go delete mode 100644 dela/mino/router/tree/json/json.go delete mode 100644 dela/mino/router/tree/json/json_test.go delete mode 100644 dela/mino/router/tree/mod.go delete mode 100644 dela/mino/router/tree/mod_test.go delete mode 100644 dela/mino/router/tree/tree.go delete mode 100644 dela/mino/router/tree/tree_test.go delete mode 100644 dela/mino/router/tree/types/handshake.go delete mode 100644 dela/mino/router/tree/types/handshake_test.go delete mode 100644 dela/mino/router/tree/types/packet.go delete mode 100644 dela/mino/router/tree/types/packet_test.go delete mode 100644 dela/mino/router/tree/types/types.go delete mode 100644 dela/mod.go delete mode 100644 dela/serde/context.go delete mode 100644 dela/serde/context_test.go delete mode 100644 dela/serde/example_test.go delete mode 100644 dela/serde/json/json.go delete mode 100644 dela/serde/json/json_test.go delete mode 100644 dela/serde/registry/registry.go delete mode 100644 dela/serde/registry/simple.go delete mode 100644 dela/serde/registry/simple_test.go delete mode 100644 dela/serde/serde.go delete mode 100644 dela/serde/xml/mod.go delete mode 100644 dela/serde/xml/xml_test.go delete mode 100644 dela/test/README.md delete mode 100644 dela/test/cosidela_test.go delete mode 100644 dela/test/dela_test.go delete mode 100644 dela/test/integration_test.go delete mode 100755 dela/test/testcreatekey.sh delete mode 100755 dela/test/testlist.sh delete mode 100755 dela/test/testsetup.sh delete mode 100755 dela/test/teststart.sh delete mode 100755 dela/test/teststop.sh delete mode 100755 dela/test/teststore.sh rename {dela/dkg/pedersen_bn256 => smc/dkg}/controller/action.go (63%) rename {dela/dkg/pedersen_bn256 => smc/dkg}/controller/action_test.go (89%) rename {dela/dkg/pedersen_bn256 => smc/dkg}/controller/controller.go (69%) rename {dela/dkg/pedersen_bn256 => smc/dkg}/controller/controller_test.go (95%) rename dela/dkg/dkg.go => smc/dkg/interfaces.go (100%) rename {dela/dkg/pedersen_bn256 => smc/dkg}/json/json.go (99%) rename {dela/dkg/pedersen_bn256 => smc/dkg}/json/json_test.go (99%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/dkg.go (99%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/dkg_test.go (99%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/handler.go (100%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/handler_test.go (97%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/pedersen.go (97%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/pedersen_test.go (97%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/resharing_test.go (99%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/state.go (100%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/state_test.go (100%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/types/messages.go (100%) rename {dela/dkg/pedersen_bn256 => smc/dkg/pedersen}/types/messages_test.go (99%) rename dela/dkg/pedersen_bn256/dkgcli/dkgcli.go => smc/dkgcli/main.go (93%) create mode 100644 smc/go.mod create mode 100644 smc/go.sum diff --git a/dela/.github/workflows/go_lint.yml b/dela/.github/workflows/go_lint.yml deleted file mode 100644 index bfa0855..0000000 --- a/dela/.github/workflows/go_lint.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Go lint - -on: - push: - branches: [ master ] - pull_request: - types: - - opened - - synchronize - - reopened - -jobs: - - lint: - runs-on: ubuntu-latest - steps: - - name: Set up Go ^1.19 - uses: actions/setup-go@v3 - with: - go-version: ^1.19 - - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - - name: Lint - run: make lint - - - name: Vet - run: make vet \ No newline at end of file diff --git a/dela/.github/workflows/go_test.yml b/dela/.github/workflows/go_test.yml deleted file mode 100644 index a1a36db..0000000 --- a/dela/.github/workflows/go_test.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Go test - -on: - push: - branches: [ master ] - pull_request: - types: - - opened - - synchronize - - reopened - -jobs: - - test: - strategy: - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{matrix.platform}} - env: - LLVL: trace - steps: - - name: Set up Go ^1.19 - uses: actions/setup-go@v3 - with: - go-version: ^1.19 - - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - - name: Test without coverage - env: - CRY_LVL: "warn" - if: matrix.platform == 'macos-latest' || matrix.platform == 'windows-latest' - run: make test - - - name: Test with coverage - env: - CRY_LVL: "warn" - if: matrix.platform == 'ubuntu-latest' - run: make coverage - - - name: Sonarcloud scan - if: matrix.platform == 'ubuntu-latest' - uses: sonarsource/sonarcloud-github-action@master - with: - args: > - -Dsonar.organization=dedis - -Dsonar.projectKey=dedis_dela - -Dsonar.go.tests.reportPaths=report.json - -Dsonar.go.coverage.reportPaths=profile.cov - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - - name: Send coverage - if: matrix.platform == 'ubuntu-latest' - uses: shogo82148/actions-goveralls@v1 - with: - path-to-profile: profile.cov - parallel: true - - # notifies that all test jobs are finished. - finish: - needs: test - runs-on: ubuntu-latest - steps: - - uses: shogo82148/actions-goveralls@v1 - with: - parallel-finished: true \ No newline at end of file diff --git a/dela/.gitignore b/dela/.gitignore deleted file mode 100644 index dd92724..0000000 --- a/dela/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -cli/node/memcoin/memcoin -test/private.key -dkg/logs -dkg/pedersen/dkgcli/dkgcli diff --git a/dela/.gitrepo b/dela/.gitrepo deleted file mode 100644 index dbc8cb6..0000000 --- a/dela/.gitrepo +++ /dev/null @@ -1,12 +0,0 @@ -; DO NOT EDIT (unless you know what you are doing) -; -; This subdirectory is a git "subrepo", and this file is maintained by the -; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme -; -[subrepo] - remote = ssh://git@gitlab.epfl.ch/bettens-f3b/dela_f3b_ibe - branch = main - commit = 868de3cd65a53b2583bd962ff8d2d18f48f4f39d - parent = 4557afa284434fb8b3e71618db9e1facdf25ea20 - method = merge - cmdver = 0.4.6 diff --git a/dela/Dockerfile b/dela/Dockerfile deleted file mode 100644 index 5ed0823..0000000 --- a/dela/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# Specifies a parent image -FROM golang:1.20-alpine - -RUN apk add cmd:bash cmd:tmux cmd:xxd - -# Creates an app directory to hold your app’s source code -WORKDIR /app - -# Copies everything from your root directory into /app -COPY ./ /app/ - -# Installs Go dependencies -RUN go install ./dkg/pedersen_bn256/dkgcli diff --git a/dela/LICENSE b/dela/LICENSE deleted file mode 100644 index 2955582..0000000 --- a/dela/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2022, Decentralized and Distributed Systems Research Lab at EPFL -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of the copyright holder 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. diff --git a/dela/Makefile b/dela/Makefile deleted file mode 100644 index e1ed898..0000000 --- a/dela/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -.PHONY: all tidy generate lint vet test coverage pushdoc - -# Default "make" target to check locally that everything is ok, BEFORE pushing remotely -all: lint vet test - @echo "Done with the standard checks" - -tidy: - go mod tidy - -generate: tidy - go get -u github.com/golang/protobuf/protoc-gen-go@v1.3.5 - go generate ./... - -# Some packages are excluded from staticcheck due to deprecated warnings: #208. -lint: tidy - # Coding style static check. - @go install honnef.co/go/tools/cmd/staticcheck@latest - staticcheck `go list ./... | grep -Ev "(go\.dedis\.ch/dela/internal/testing|go\.dedis\.ch/dela/mino/minogrpc/ptypes)"` - -vet: tidy - @echo "⚠️ Warning: the following only works with go >= 1.14" && \ - go install ./internal/mcheck && \ - go vet -vettool=`go env GOPATH`/bin/mcheck -commentLen -ifInit ./... - -# test runs all tests in DELA without coverage -test: tidy - go test ./... - -# test runs all tests in DELA and generate a coverage output (to be used by sonarcloud) -coverage: tidy - go test -json -covermode=count -coverprofile=profile.cov ./... | tee report.json - -# https://pkg.go.dev/go.dedis.ch/dela needs to be updated on the Go proxy side -# to get the latest master. This command refreshes the proxy with the latest -# commit on the upstream master branch. -# Note: CURL must be installed -pushdoc: - @echo "Requesting the proxy..." - @curl "https://proxy.golang.org/go.dedis.ch/dela/@v/$(shell git log origin/master -1 --format=format:%H).info" - @echo "\nDone." \ No newline at end of file diff --git a/dela/README.md b/dela/README.md deleted file mode 100644 index 454e752..0000000 --- a/dela/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# DELA F3B-IBE - -Software repository delivered as part of the "Optimizing Frontrunning Protection" research project at @dedis. - -The main point of interest is [dkg/pedersen_bn256](./dkg/pedersen_bn256). - -## Demo - -A simple demo simulating a frontrunning protected decentralized exchange is available. -Simply run `docker compose run demo` from the top-level of the repository. -Alternatively, `./demo.sh` can be run directly inside [tmux], -with the dependencies being Vim 9.0 (for `xxd`) and Go. - -[tmux]: https://tmux.github.io - -## Code Coverage - -``` -$ go test -covermode=count -coverprofile=profile.cov ./dkg/pedersen_bn256/ -ok go.dedis.ch/dela/dkg/pedersen_bn256 93.347s coverage: 96.7% of statements -``` diff --git a/dela/cli/cli.go b/dela/cli/cli.go deleted file mode 100644 index 0c8cdaf..0000000 --- a/dela/cli/cli.go +++ /dev/null @@ -1,94 +0,0 @@ -// Package cli defines the Builder type, which allows one to build a CLI -// application in a modular way. -// -// var builder Builder -// builder.SetName("myapp") -// -// cmd := builder.SetCommand("hello") -// cmd.SetDescription("Say hello !") -// cmd.SetAction(func(flags Flags) error { -// fmt.Printf("Hello %s!\n", flags.String("dude")) -// }) -// -// builder.Build().Run(os.Args) -// -// An implementation of the builder is free to provide primitives to create more -// complex action. -// -// Documentation Last Review: 13.10.2020 -package cli - -import ( - "time" -) - -// Builder is an application builder interface. One can set properties of an -// application then build it. -type Builder interface { - Provider - - // Build returns the application. - Build() Application -} - -// Application is the main interface to run the CLI. -type Application interface { - Run(arguments []string) error -} - -// CommandBuilder is a command builder interface. One can set properties of a -// specific command like its name and description and what it should do when -// invoked. -type CommandBuilder interface { - // SetDescription sets the value of the description for this command. - SetDescription(value string) - - // SetFlags sets the flags for this command. - SetFlags(...Flag) - - // SetAction sets the action for this command. - SetAction(Action) - - // SetSubCommand creates a subcommand for this command. - SetSubCommand(name string) CommandBuilder -} - -// Action is a function that will be executed when a command is invoked. -type Action func(Flags) error - -// Flag is an identifier for the definition of the flags. -type Flag interface { - Flag() -} - -// Flags provides the primitives to an action to read the flags. -type Flags interface { - String(name string) string - - StringSlice(name string) []string - - Duration(name string) time.Duration - - Path(name string) string - - Int(name string) int - - Bool(name string) bool -} - -// Initializer defines a primitive for modules to add their commands. A cli will -// gather all the initializers from each desired modules and call the -// SetCommands for each of them. -type Initializer interface { - // SetCommands if the function called by the builder to add the modules' - // commands. The modules implement this function and use the provided - // provider to create its specific commands. - SetCommands(Provider) -} - -// Provider defines a primitive for modules to provide their commands -type Provider interface { - // SetCommand creates a new command with the given name and returns its - // builder. - SetCommand(name string) CommandBuilder -} diff --git a/dela/cli/crypto/crypto.go b/dela/cli/crypto/crypto.go deleted file mode 100644 index 965c936..0000000 --- a/dela/cli/crypto/crypto.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package main provides a cli for crypto operations like generating keys or -// displaying specific key formats. -package main - -import ( - "fmt" - "io" - "os" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/ucli" - bls "go.dedis.ch/dela/crypto/bls/command" -) - -var builder cli.Builder = ucli.NewBuilder("crypto", nil) -var printer io.Writer = os.Stderr - -func main() { - err := run(os.Args, bls.Initializer{}) - if err != nil { - fmt.Fprintf(printer, "%+v\n", err) - } -} - -func run(args []string, inits ...cli.Initializer) error { - for _, init := range inits { - init.SetCommands(builder) - } - - app := builder.Build() - err := app.Run(args) - if err != nil { - return err - } - - return nil -} diff --git a/dela/cli/crypto/crypto_test.go b/dela/cli/crypto/crypto_test.go deleted file mode 100644 index 2946c30..0000000 --- a/dela/cli/crypto/crypto_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" -) - -func TestMain_Happy(t *testing.T) { - oldPrinter := printer - defer func() { - printer = oldPrinter - }() - - builder = &fakeBuilder{} - buf := new(bytes.Buffer) - printer = buf - - main() - - require.Empty(t, buf) -} - -func TestMain_Error(t *testing.T) { - oldPrinter := printer - defer func() { - printer = oldPrinter - }() - - builder = &fakeBuilder{err: errors.New("fake")} - buf := new(bytes.Buffer) - printer = buf - - main() - require.Equal(t, "fake\n", buf.String()) -} - -func TestRun(t *testing.T) { - b := &fakeBuilder{} - builder = b - init := &fakeInit{} - - err := run([]string{"crypto"}, init) - require.NoError(t, err) - - require.True(t, b.called) - require.True(t, init.called) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeBuilder struct { - cli.Builder - err error - called bool -} - -func (f *fakeBuilder) Build() cli.Application { - f.called = true - return &fakeApp{err: f.err} -} - -func (f *fakeBuilder) SetCommand(name string) cli.CommandBuilder { - return fakeCommandBuilder{} -} - -type fakeCommandBuilder struct { -} - -func (b fakeCommandBuilder) SetSubCommand(name string) cli.CommandBuilder { - return b -} - -func (b fakeCommandBuilder) SetDescription(value string) { -} - -func (b fakeCommandBuilder) SetFlags(flags ...cli.Flag) { -} - -func (b fakeCommandBuilder) SetAction(a cli.Action) { -} - -type fakeApp struct { - err error -} - -func (f fakeApp) Run(arguments []string) error { - return f.err -} - -type fakeInit struct { - called bool -} - -func (f *fakeInit) SetCommands(cli.Provider) { - f.called = true -} diff --git a/dela/cli/flag.go b/dela/cli/flag.go deleted file mode 100644 index 78fda33..0000000 --- a/dela/cli/flag.go +++ /dev/null @@ -1,72 +0,0 @@ -package cli - -import "time" - -// StringFlag is a definition of a command flag expected to be parsed as a -// string. -// -// - implements cli.Flag -type StringFlag struct { - Name string - Usage string - Required bool - Value string -} - -// Flag implements cli.Flag. -func (flag StringFlag) Flag() {} - -// StringSliceFlag is a definition of a command flag expected to tbe parsed as a -// slice of strings. -// -// - implements cli.Flag -type StringSliceFlag struct { - Name string - Usage string - Required bool - Value []string -} - -// Flag implements cli.Flag. -func (flag StringSliceFlag) Flag() {} - -// DurationFlag is a definition of a command flag expected to be parsed as a -// duration. -// -// - implements cli.Flag -type DurationFlag struct { - Name string - Usage string - Required bool - Value time.Duration -} - -// Flag implements cli.Flag. -func (flag DurationFlag) Flag() {} - -// IntFlag is a definition of a command flag expected to be parsed as a integer. -// -// - implements cli.Flag -type IntFlag struct { - Name string - Usage string - Required bool - Value int -} - -// Flag implements cli.Flag. -func (flag IntFlag) Flag() {} - -// BoolFlag is a definition of a command flag expected to be parsed as a -// boolean. -// -// - implements cli.Flag -type BoolFlag struct { - Name string - Usage string - Required bool - Value bool -} - -// Flag implements cli.Flag. -func (flag BoolFlag) Flag() {} diff --git a/dela/cli/node/builder.go b/dela/cli/node/builder.go deleted file mode 100644 index 9467773..0000000 --- a/dela/cli/node/builder.go +++ /dev/null @@ -1,247 +0,0 @@ -// This file contains the implementation of a CLI builder. -// -// Documentation Last Review: 13.10.20202 -// - -package node - -import ( - "encoding/binary" - "encoding/json" - "io" - "os" - "os/signal" - "syscall" - - urfave "github.com/urfave/cli/v2" - "go.dedis.ch/dela" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/ucli" - "golang.org/x/xerrors" -) - -// CLIBuilder is an application builder that will build a CLI to start and -// control a node. -// -// - implements node.Builder -// - implements cli.Builder -type CLIBuilder struct { - cli.Builder - - daemonFactory DaemonFactory - injector Injector - actions *actionMap - startFlags []cli.Flag - inits []Initializer - writer io.Writer - - // In production, the daemon is stopped via SIGTERM. In case of testing, the - // channel will be closed instead, because of instability. - enableSignal bool - sigs chan os.Signal -} - -// NewBuilder returns a new empty builder. -func NewBuilder(inits ...Initializer) *CLIBuilder { - return NewBuilderWithCfg(nil, nil, inits...) -} - -// NewBuilderWithCfg returns a new empty builder with specific configurations. -func NewBuilderWithCfg(sigs chan os.Signal, out io.Writer, inits ...Initializer) *CLIBuilder { - if out == nil { - out = os.Stdout - } - - enabled := false - - if sigs == nil { - sigs = make(chan os.Signal, 1) - enabled = true - } - - injector := NewInjector() - - actions := &actionMap{} - - factory := socketFactory{ - injector: injector, - actions: actions, - out: out, - } - - // We are using urfave cli builder - builder := ucli.NewBuilder("Dela", nil, cli.StringFlag{ - Name: "config", - Usage: "path to the config folder", - Value: ".dela", - }) - - return &CLIBuilder{ - Builder: builder, - injector: injector, - actions: actions, - daemonFactory: factory, - enableSignal: enabled, - sigs: sigs, - inits: inits, - writer: out, - } -} - -// SetStartFlags implements node.Builder. It appends the given flags to the list -// of flags that will be used to create the start command. -func (b *CLIBuilder) SetStartFlags(flags ...cli.Flag) { - b.startFlags = append(b.startFlags, flags...) -} - -// MakeAction implements node.Builder. It creates a CLI action from the -// template. -func (b *CLIBuilder) MakeAction(tmpl ActionTemplate) cli.Action { - index := b.actions.Set(tmpl) - - return func(c cli.Flags) error { - client, err := b.daemonFactory.ClientFromContext(c) - if err != nil { - return xerrors.Errorf("couldn't make client: %v", err) - } - - // Encode the action ID over 2 bytes. - id := make([]byte, 2) - binary.LittleEndian.PutUint16(id, index) - - // Prepare a set of flags that will be transmitted to the daemon so that - // the action has access to the same flags and their values. - fset := make(FlagSet) - lookupFlags(fset, c.(*urfave.Context)) - - buf, err := json.Marshal(fset) - if err != nil { - return xerrors.Errorf("failed to marshal flag set: %v", err) - } - - err = client.Send(append(id, buf...)) - if err != nil { - return xerrors.Opaque(err) - } - - return nil - } -} - -func lookupFlags(fset FlagSet, ctx *urfave.Context) { - for _, ancestor := range ctx.Lineage() { - if ancestor.Command != nil { - fill(fset, ancestor.Command.Flags, ancestor) - } - - if ancestor.App != nil { - fill(fset, ancestor.App.Flags, ancestor) - } - } -} - -func fill(fset FlagSet, flags []urfave.Flag, ctx *urfave.Context) { - for _, flag := range flags { - names := flag.Names() - if len(names) > 0 { - fset[names[0]] = convert(ctx.Value(names[0])) - } - } -} - -func convert(v interface{}) interface{} { - switch value := v.(type) { - case urfave.StringSlice: - // StringSlice is an edge-case as it won't serialize correctly with JSON - // so we ask for the actual []string to allow a correct serialization. - return value.Value() - default: - return v - } -} - -// Build implements node.Builder. It returns the application. -func (b *CLIBuilder) Build() cli.Application { - for _, controller := range b.inits { - controller.SetCommands(b) - } - - cmd := b.SetCommand("start") - cmd.SetDescription("start the deamon") - cmd.SetFlags(b.startFlags...) - cmd.SetAction(b.start) - - return b.Builder.Build() -} - -func (b *CLIBuilder) start(flags cli.Flags) error { - if b.enableSignal { - signal.Notify(b.sigs, syscall.SIGINT, syscall.SIGTERM) - - defer signal.Stop(b.sigs) - } - - dir := flags.Path("config") - if dir != "" { - err := os.MkdirAll(dir, 0700) - if err != nil { - return xerrors.Errorf("couldn't make path: %v", err) - } - } - - daemon, err := b.daemonFactory.DaemonFromContext(flags) - if err != nil { - return xerrors.Errorf("couldn't make daemon: %v", err) - } - - for _, controller := range b.inits { - err = controller.OnStart(flags, b.injector) - if err != nil { - return xerrors.Errorf("couldn't run the controller: %v", err) - } - } - - // Daemon is started after the controllers so that everything has started - // when the daemon is available. - err = daemon.Listen() - if err != nil { - return xerrors.Errorf("couldn't start the daemon: %v", err) - } - - defer daemon.Close() - - <-b.sigs - signal.Stop(b.sigs) - - // Controllers are stopped in reverse order so that high level components - // are stopped before lower level ones (i.e. stop a service before the - // database to avoid errors). - for i := len(b.inits) - 1; i >= 0; i-- { - err = b.inits[i].OnStop(b.injector) - if err != nil { - return xerrors.Errorf("couldn't stop controller: %v", err) - } - } - - dela.Logger.Trace().Msg("daemon has been stopped") - - return nil -} - -// ActionMap stores actions and assigns a unique index to each. -type actionMap struct { - list []ActionTemplate -} - -func (m *actionMap) Set(a ActionTemplate) uint16 { - m.list = append(m.list, a) - return uint16(len(m.list) - 1) -} - -func (m *actionMap) Get(index uint16) ActionTemplate { - if int(index) >= len(m.list) { - return nil - } - - return m.list[index] -} diff --git a/dela/cli/node/builder_test.go b/dela/cli/node/builder_test.go deleted file mode 100644 index 65fd634..0000000 --- a/dela/cli/node/builder_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package node - -import ( - "flag" - "syscall" - "testing" - - "github.com/stretchr/testify/require" - urfave "github.com/urfave/cli/v2" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/ucli" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestCliBuilder_SetStartFlags(t *testing.T) { - builder := &CLIBuilder{} - - builder.SetStartFlags(cli.StringFlag{}, cli.IntFlag{}) - require.Len(t, builder.startFlags, 2) -} - -func TestCliBuilder_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{}) - - builder.sigs <- syscall.SIGTERM - close(builder.sigs) - - err := builder.start(FlagSet{}) - require.NoError(t, err) -} - -func TestCliBuilder_ForbiddenFolder_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{}) - - fset := flag.NewFlagSet("", 0) - fset.String("config", "\x00", "") - - ctx := urfave.NewContext(nil, fset, nil) - - err := builder.start(ctx) - require.Error(t, err) - require.Contains(t, err.Error(), "couldn't make path: mkdir \x00: ") - -} - -func TestCliBuilder_FailedDaemon_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{}) - - builder.daemonFactory = fakeFactory{err: fake.GetError()} - - err := builder.start(FlagSet{}) - require.EqualError(t, err, fake.Err("couldn't make daemon")) -} - -func TestCliBuilder_FailStartDaemon_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{}) - - builder.daemonFactory = fakeFactory{errDaemon: fake.GetError()} - - err := builder.start(FlagSet{}) - require.EqualError(t, err, fake.Err("couldn't start the daemon")) -} - -func TestCliBuilder_FailStartComponent_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{err: fake.GetError()}) - - err := builder.start(FlagSet{}) - require.EqualError(t, err, fake.Err("couldn't run the controller")) -} - -func TestCliBuilder_FailStopComponent_Start(t *testing.T) { - builder := NewBuilder(fakeInitializer{errStop: fake.GetError()}) - builder.enableSignal = false - close(builder.sigs) - - err := builder.start(FlagSet{}) - require.EqualError(t, err, fake.Err("couldn't stop controller")) -} - -func TestCliBuilder_MakeAction(t *testing.T) { - calls := &fake.Call{} - builder := &CLIBuilder{ - actions: &actionMap{}, - daemonFactory: fakeFactory{calls: calls}, - } - - fset := flag.NewFlagSet("", 0) - fset.Var(urfave.NewStringSlice("item 1", "item 2"), "flag-1", "") - fset.Int("flag-2", 20, "") - - ctx := urfave.NewContext(makeApp(), fset, nil) - - err := builder.MakeAction(fakeAction{})(ctx) - require.NoError(t, err) - - data := string(calls.Get(0, 0).([]byte)) - require.Equal(t, "\x00\x00"+`{"flag-1":["item 1","item 2"],"flag-2":20}`, data) - - builder.daemonFactory = fakeFactory{err: fake.GetError()} - err = builder.MakeAction(fakeAction{})(ctx) - require.EqualError(t, err, fake.Err("couldn't make client")) - - builder.daemonFactory = fakeFactory{errClient: fake.GetError()} - err = builder.MakeAction(fakeAction{})(ctx) - require.EqualError(t, err, fake.GetError().Error()) -} - -func TestCliBuilder_Build(t *testing.T) { - builder := &CLIBuilder{ - Builder: ucli.NewBuilder("test", nil), - actions: &actionMap{}, - daemonFactory: fakeFactory{}, - inits: []Initializer{fakeInitializer{}}, - } - - cb := builder.SetCommand("test") - cb.SetDescription("test description") - cb.SetAction(builder.MakeAction(fakeAction{})) - cb.SetFlags(cli.StringFlag{Name: "string-flag"}) - - sub := cb.SetSubCommand("subtest") - sub.SetDescription("subtest description") - sub.SetFlags(cli.DurationFlag{}, cli.IntFlag{}, cli.StringSliceFlag{}) - - cb = builder.SetCommand("another") - cb.SetAction(func(cli.Flags) error { - return nil - }) - - cb = builder.SetCommand("last") - cb.SetAction(func(cli.Flags) error { - return nil - }) - - // Build will add the start command, which is why we are expecting 5. - app := builder.Build().(*urfave.App) - require.Len(t, app.Commands, 5) -} - -func TestCliBuilder_UnknownType_BuildFlags(t *testing.T) { - defer func() { - r := recover() - require.Equal(t, "flag type '' not supported", r) - }() - - builder := &CLIBuilder{Builder: ucli.NewBuilder("test", nil)} - builder.SetStartFlags((cli.Flag)(nil)) - - builder.Build() -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeApp() *urfave.App { - return &urfave.App{ - Flags: []urfave.Flag{ - &urfave.StringSliceFlag{Name: "flag-1"}, - &urfave.IntFlag{Name: "flag-2"}, - }, - } -} diff --git a/dela/cli/node/daemon.go b/dela/cli/node/daemon.go deleted file mode 100644 index 8a38fcd..0000000 --- a/dela/cli/node/daemon.go +++ /dev/null @@ -1,283 +0,0 @@ -// This file contains the implementation of a client and a daemon talking -// through a UNIX socket. -// -// Documentation Last Review: 13.10.2020 -// - -package node - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "io" - "net" - "path/filepath" - "sync" - "time" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/cli" - "golang.org/x/xerrors" -) - -const ioTimeout = 30 * time.Second - -// event is the structure sent over the connection between the client and the -// daemon and vice-versa using a JSON encoding. -type event struct { - Err bool - Value string -} - -// SocketClient opens a connection to a unix socket daemon to send commands. -// -// - implements node.Client -type socketClient struct { - socketpath string - out io.Writer - dialTimeout time.Duration - dialFn func(network, addr string, timeout time.Duration) (net.Conn, error) -} - -// Send implements node.Client. It opens a connection and sends the data to the -// daemon. It writes the result of the command to the output. -func (c socketClient) Send(data []byte) error { - conn, err := c.dialFn("unix", c.socketpath, c.dialTimeout) - if err != nil { - return xerrors.Errorf("couldn't open connection: %v", err) - } - - defer conn.Close() - - _, err = conn.Write(data) - if err != nil { - return xerrors.Errorf("couldn't write to daemon: %v", err) - } - - // The client will now wait for incoming messages from the daemon, either - // results of the command, or an error if something goes wrong. - dec := json.NewDecoder(conn) - var evt event - - for { - err = dec.Decode(&evt) - if err == io.EOF { - return nil - } - if err != nil { - return xerrors.Errorf("fail to decode event: %v", err) - } - - if evt.Err { - return xerrors.New(evt.Value) - } - - fmt.Fprintln(c.out, evt.Value) - } -} - -// SocketDaemon is a daemon using UNIX socket. This allows the permissions to be -// managed by the filesystem. A user must have read/write access to send a -// command to the daemon. -// -// - implements node.Daemon -type socketDaemon struct { - sync.WaitGroup - - logger zerolog.Logger - socketpath string - injector Injector - actions *actionMap - closing chan struct{} - readTimeout time.Duration - listenFn func(network, addr string) (net.Listener, error) -} - -// Listen implements node.Daemon. It starts the daemon by creating the unix -// socket file to the path. -func (d *socketDaemon) Listen() error { - socket, err := d.listenFn("unix", d.socketpath) - if err != nil { - return xerrors.Errorf("couldn't bind socket: %v", err) - } - - d.Add(2) - - go func() { - defer d.Done() - - <-d.closing - socket.Close() - }() - - go func() { - defer d.Done() - - for { - fd, err := socket.Accept() - if err != nil { - select { - case <-d.closing: - default: - dela.Logger.Err(err).Msg("daemon closed unexpectedly") - } - return - } - - go d.handleConn(fd) - } - }() - - return nil -} - -func (d *socketDaemon) handleConn(conn net.Conn) { - defer conn.Close() - - d.logger.Trace().Msg("daemon is handling a connection") - - // Read the first two bytes that will be converted into the action ID. - buffer := make([]byte, 2) - - conn.SetReadDeadline(time.Now().Add(d.readTimeout)) - - _, err := conn.Read(buffer) - if err == io.EOF { - // Connection closed upfront so it does not need further handling. This - // happens for instance when testing the connectivity of the daemon. - return - } - if err != nil { - d.sendError(conn, xerrors.Errorf("stream corrupted: %v", err)) - return - } - - dec := json.NewDecoder(conn) - - fset := make(FlagSet) - err = dec.Decode(&fset) - if err != nil { - d.sendError(conn, xerrors.Errorf("failed to decode flags: %v", err)) - return - } - - d.logger.Debug(). - Hex("command", buffer). - Str("flags", fmt.Sprintf("%v", fset)). - Msg("received command on the daemon") - - id := binary.LittleEndian.Uint16(buffer) - action := d.actions.Get(id) - - if action == nil { - d.sendError(conn, xerrors.Errorf("unknown command '%d'", id)) - return - } - - actx := Context{ - Injector: d.injector, - Flags: fset, - Out: newClientWriter(conn), - } - - err = action.Execute(actx) - if err != nil { - d.sendError(conn, xerrors.Errorf("command error: %v", err)) - return - } -} - -func (d *socketDaemon) sendError(conn net.Conn, err error) { - enc := json.NewEncoder(conn) - - d.logger.Debug().Err(err).Msg("sending error to client") - - // The event contains an error which will make the command on the client - // side fail with the value as the error message. - err = enc.Encode(event{Err: true, Value: err.Error()}) - if err != nil { - d.logger.Warn().Err(err).Msg("connection to daemon has error") - } -} - -// Close implements node.Daemon. It closes the daemon and waits for the go -// routines to close. -func (d *socketDaemon) Close() error { - close(d.closing) - d.Wait() - - return nil -} - -// clientWriter is a wrapper around a socket connection that will write the data -// using a JSON message wrapper. -// -// - implements io.Writer -type clientWriter struct { - enc *json.Encoder -} - -func newClientWriter(w io.Writer) *clientWriter { - return &clientWriter{ - enc: json.NewEncoder(w), - } -} - -// Write implements io.Writer. It wraps the data into a JSON message that is -// written to the parent writer. The number of written bytes returned -// corresponds to the input if successful. -func (w *clientWriter) Write(data []byte) (int, error) { - err := w.enc.Encode(event{Value: string(data)}) - if err != nil { - return 0, xerrors.Errorf("while packing data: %v", err) - } - - return len(data), nil -} - -// SocketFactory provides primitives to create a daemon and clients from a CLI -// context. -// -// - implements node.DaemonFactory -type socketFactory struct { - injector Injector - actions *actionMap - out io.Writer -} - -// ClientFromContext implements node.DaemonFactory. It creates a client based on -// the flags of the context. -func (f socketFactory) ClientFromContext(ctx cli.Flags) (Client, error) { - client := socketClient{ - socketpath: f.getSocketPath(ctx), - out: f.out, - dialTimeout: ioTimeout, - dialFn: net.DialTimeout, - } - - return client, nil -} - -// DaemonFromContext implements node.DaemonFactory. It creates a daemon based on -// the flags of the context. -func (f socketFactory) DaemonFromContext(ctx cli.Flags) (Daemon, error) { - socketpath := f.getSocketPath(ctx) - - daemon := &socketDaemon{ - logger: dela.Logger.With().Str("daemon", socketpath).Logger(), - socketpath: socketpath, - injector: f.injector, - actions: f.actions, - closing: make(chan struct{}), - readTimeout: ioTimeout, - listenFn: net.Listen, - } - - return daemon, nil -} - -func (f socketFactory) getSocketPath(ctx cli.Flags) string { - return filepath.Join(ctx.Path("config"), "daemon.sock") -} diff --git a/dela/cli/node/daemon_test.go b/dela/cli/node/daemon_test.go deleted file mode 100644 index 04ee0db..0000000 --- a/dela/cli/node/daemon_test.go +++ /dev/null @@ -1,344 +0,0 @@ -package node - -import ( - "bytes" - "encoding/json" - "net" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/internal/testing/fake" - "golang.org/x/xerrors" -) - -func TestSocketClient_Send(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - out := new(bytes.Buffer) - - client := socketClient{ - socketpath: filepath.Join(dir, "daemon.sock"), - out: out, - dialFn: net.DialTimeout, - } - - listen(t, client.socketpath) - - err = client.Send([]byte("deadbeef")) - require.NoError(t, err) - require.Equal(t, "deadbeef\n", out.String()) -} - -func TestSocketClient_FailDial_Send(t *testing.T) { - client := socketClient{ - socketpath: "", - dialFn: func(network, addr string, timeout time.Duration) (net.Conn, error) { - return nil, fake.GetError() - }, - } - - err := client.Send(nil) - require.EqualError(t, err, fake.Err("couldn't open connection")) -} - -func TestSocketClient_BadOutConn_Send(t *testing.T) { - client := socketClient{ - dialFn: func(network, addr string, timeout time.Duration) (net.Conn, error) { - return badConn{}, nil - }, - } - - err := client.Send([]byte{1, 2, 3}) - require.EqualError(t, err, fake.Err("couldn't write to daemon")) -} - -func TestSocketClient_BadInConn_Send(t *testing.T) { - client := socketClient{ - dialFn: func(network, addr string, timeout time.Duration) (net.Conn, error) { - return badConn{counter: fake.NewCounter(1)}, nil - }, - } - - err := client.Send([]byte{}) - require.EqualError(t, err, fake.Err("fail to decode event")) -} - -func TestSocketDaemon_Listen(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - fset := make(FlagSet) - fset["1"] = 1 - - buf, err := json.Marshal(&fset) - require.NoError(t, err) - - actions := &actionMap{} - actions.Set(fakeAction{ - intFlags: map[string]int{"1": 1}, - }) // id 0 - actions.Set(fakeAction{err: fake.GetError()}) // id 1 - - daemon := &socketDaemon{ - socketpath: filepath.Join(dir, "daemon.sock"), - actions: actions, - closing: make(chan struct{}), - readTimeout: 50 * time.Millisecond, - listenFn: net.Listen, - } - - err = daemon.Listen() - require.NoError(t, err) - - defer daemon.Close() - - out := new(bytes.Buffer) - client := socketClient{ - socketpath: daemon.socketpath, - out: out, - dialTimeout: time.Second, - dialFn: net.DialTimeout, - } - - err = client.Send(append([]byte{0x0, 0x0}, buf...)) - require.NoError(t, err) - require.Equal(t, "deadbeef\n", out.String()) - - err = client.Send(append([]byte{0x1, 0x0}, []byte("{}")...)) - require.EqualError(t, err, fake.Err("command error")) - - err = client.Send(append([]byte{0x2, 0x0}, []byte("{}")...)) - require.EqualError(t, err, "unknown command '2'") - - err = client.Send([]byte{0x0, 0x0, 0x0}) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to decode flags") - - err = client.Send([]byte{}) - require.Error(t, err) - require.Contains(t, err.Error(), "stream corrupted: ") -} - -func TestSocketDaemon_ConnectivityTest_Listen(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - daemon := &socketDaemon{ - socketpath: filepath.Join(dir, "daemon.sock"), - actions: &actionMap{}, - closing: make(chan struct{}), - readTimeout: 50 * time.Millisecond, - listenFn: net.Listen, - } - - err = daemon.Listen() - require.NoError(t, err) - - defer daemon.Close() - - conn, err := net.DialTimeout("unix", daemon.socketpath, 1*time.Second) - require.NoError(t, err) - require.NoError(t, conn.Close()) -} - -func TestSocketDaemon_FailBindSocket_Listen(t *testing.T) { - daemon := &socketDaemon{ - listenFn: func(network, addr string) (net.Listener, error) { - return nil, fake.GetError() - }, - } - - err := daemon.Listen() - require.EqualError(t, err, fake.Err("couldn't bind socket")) -} - -func TestSocketDaemon_ConnClosedFromClient_HandleConn(t *testing.T) { - logger, check := fake.CheckLog("connection to daemon has error") - - daemon := &socketDaemon{ - logger: logger, - actions: &actionMap{}, - closing: make(chan struct{}), - readTimeout: 50 * time.Millisecond, - } - - daemon.handleConn(badConn{}) - - check(t) -} - -func TestClientWriter_Write(t *testing.T) { - buffer := new(bytes.Buffer) - - w := newClientWriter(buffer) - - n, err := w.Write([]byte("deadbeef")) - require.NoError(t, err) - require.Equal(t, 8, n) -} - -func TestClientWriter_BadWriter_Write(t *testing.T) { - w := newClientWriter(fake.NewBadHash()) - - n, err := w.Write([]byte("deadbeef")) - require.Equal(t, 0, n) - require.EqualError(t, err, fake.Err("while packing data")) -} - -func TestSocketFactory_ClientFromContext(t *testing.T) { - factory := socketFactory{} - - client, err := factory.ClientFromContext(fakeContext{path: "cfgdir"}) - require.NoError(t, err) - require.NotNil(t, client) - require.Equal(t, filepath.Join("cfgdir", "daemon.sock"), - client.(socketClient).socketpath) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func listen(t *testing.T, path string) { - socket, err := net.Listen("unix", path) - require.NoError(t, err) - - go func() { - conn, err := socket.Accept() - require.NoError(t, err) - - defer conn.Close() - defer socket.Close() - - buffer := make([]byte, 100) - n, err := conn.Read(buffer) - require.NoError(t, err) - - enc := json.NewEncoder(conn) - err = enc.Encode(event{Value: string(buffer[:n])}) - require.NoError(t, err) - }() -} - -type fakeInitializer struct { - err error - errStop error -} - -func (c fakeInitializer) SetCommands(Builder) {} - -func (c fakeInitializer) OnStart(cli.Flags, Injector) error { - return c.err -} - -func (c fakeInitializer) OnStop(Injector) error { - return c.errStop -} - -type fakeClient struct { - err error - calls *fake.Call -} - -func (c fakeClient) Send(data []byte) error { - c.calls.Add(data) - return c.err -} - -type fakeDaemon struct { - Daemon - err error -} - -func (d fakeDaemon) Listen() error { - return d.err -} - -type fakeFactory struct { - DaemonFactory - err error - errClient error - errDaemon error - calls *fake.Call -} - -func (f fakeFactory) ClientFromContext(cli.Flags) (Client, error) { - return fakeClient{err: f.errClient, calls: f.calls}, f.err -} - -func (f fakeFactory) DaemonFromContext(cli.Flags) (Daemon, error) { - return fakeDaemon{err: f.errDaemon}, f.err -} - -type fakeAction struct { - err error - intFlags map[string]int -} - -func (a fakeAction) Execute(req Context) error { - if a.err != nil { - return a.err - } - - if a.intFlags != nil { - for k, v := range a.intFlags { - if req.Flags.Int(k) != v { - return xerrors.Errorf("missing flag %s", k) - } - } - } - - req.Out.Write([]byte("deadbeef")) - return nil -} - -type fakeContext struct { - cli.Flags - path string -} - -func (ctx fakeContext) Path(name string) string { - return ctx.path -} - -type badConn struct { - net.Conn - - counter *fake.Counter -} - -func (conn badConn) Read(data []byte) (int, error) { - if !conn.counter.Done() { - conn.counter.Decrease() - return len(data), nil - } - - return 0, fake.GetError() -} - -func (conn badConn) Write(data []byte) (int, error) { - if !conn.counter.Done() { - conn.counter.Decrease() - return len(data), nil - } - - return 0, fake.GetError() -} - -func (badConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (badConn) Close() error { - return nil -} diff --git a/dela/cli/node/example_test.go b/dela/cli/node/example_test.go deleted file mode 100644 index 5c2fafb..0000000 --- a/dela/cli/node/example_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package node - -import ( - "fmt" - "os" - - "go.dedis.ch/dela/cli" -) - -func ExampleCLIBuilder_Build() { - builder := NewBuilder(exampleController{}) - - cmd := builder.SetCommand("bye") - - cmd.SetFlags(cli.StringFlag{ - Name: "name", - Usage: "set the name", - Value: "Bob", - }) - - // This action is only executed on the CLI process. It is also possible to - // call commands on the daemon after it has been started with "start". - cmd.SetAction(func(flags cli.Flags) error { - fmt.Printf("Bye, %s!", flags.String("name")) - return nil - }) - - app := builder.Build() - - err := app.Run([]string{os.Args[0], "bye", "--name", "Alice"}) - if err != nil { - panic("app failed: " + err.Error()) - } - - // Output: Bye, Alice! -} - -// Hello is an example of a component that can be injected and resolved on the -// daemon side. -type Hello interface { - SayTo(name string) -} - -type simpleHello struct{} - -func (simpleHello) SayTo(name string) { - fmt.Printf("Hello, %s!", name) -} - -// helloAction is an example of an action template to be executed on the daemon. -// -// - implements node.ActionTemplate -type helloAction struct{} - -// Execute implements node.ActionTemplate. It resolves the hello component and -// say hello to the name defined by the flag. -func (tmpl helloAction) Execute(ctx Context) error { - var hello Hello - err := ctx.Injector.Resolve(&hello) - if err != nil { - return err - } - - hello.SayTo(ctx.Flags.String("name")) - - return nil -} - -// exampleController is an example of a controller passed to the builder. It -// defines the command available and the component that are injected when the -// daemon is started. -// -// - implements node.Initializer -type exampleController struct{} - -// SetCommands implements node.Initializer. It defines the hello command. -func (exampleController) SetCommands(builder Builder) { - cmd := builder.SetCommand("hello") - - // Set an action that will be executed on the daemon. - cmd.SetAction(builder.MakeAction(helloAction{})) - - cmd.SetDescription("Say hello") - cmd.SetFlags(cli.StringFlag{ - Name: "name", - Usage: "set the name", - Value: "Bob", - }) -} - -// OnStart implements node.Initializer. It injects the hello component. -func (exampleController) OnStart(flags cli.Flags, inj Injector) error { - inj.Inject(simpleHello{}) - - return nil -} - -// OnStop implements node.Initializer. -func (exampleController) OnStop(Injector) error { - return nil -} diff --git a/dela/cli/node/flagset.go b/dela/cli/node/flagset.go deleted file mode 100644 index 4d4c903..0000000 --- a/dela/cli/node/flagset.go +++ /dev/null @@ -1,94 +0,0 @@ -// This file contains the implementation of a simplified flag set used on the -// daemon side. -// -// Documentation Last Review: 13.10.2020 -// - -package node - -import ( - "time" -) - -// FlagSet is a serializable flag set implementation. It allows to pack the -// flags coming from a CLI application and send them to a daemon. -// -// - implements cli.Flags -type FlagSet map[string]interface{} - -func (fset FlagSet) String(name string) string { - switch v := fset[name].(type) { - case string: - return v - default: - return "" - } -} - -// StringSlice implements cli.Flags. It returns the slice of strings associated -// with the flag name if it is set, otherwise it returns nil. -func (fset FlagSet) StringSlice(name string) []string { - switch v := fset[name].(type) { - case []interface{}: - values := make([]string, len(v)) - for i, str := range v { - values[i] = str.(string) - } - - return values - default: - return nil - } -} - -// Duration implements cli.Flags. It returns the duration associated with the -// flag name if it is set, otherwise it returns zero. -func (fset FlagSet) Duration(name string) time.Duration { - switch v := fset[name].(type) { - case float64: - return time.Duration(v) - default: - return time.Duration(0) - } -} - -// Path implements cli.Flags. It returns the path associated with the flag name -// if it is set, otherwise it returns an empty string. -func (fset FlagSet) Path(name string) string { - switch v := fset[name].(type) { - case string: - return v - default: - return "" - } -} - -// Int implements cli.Flags. It returns the integer associated with the flag if -// it is set, otherwise it returns zero. -func (fset FlagSet) Int(name string) int { - switch v := fset[name].(type) { - case int: - return v - case float64: - // This is the case where flags have been JSON marshalled/unmarshalled. - // In this case JSON uses the float type for numbers. - if v == float64(int(v)) { - return int(v) - } - - return 0 - default: - return 0 - } -} - -// Bool implements cli.Flags. It return the boolean associated with the flag if -// it is set, otherwise it returns false. -func (fset FlagSet) Bool(name string) bool { - switch v := fset[name].(type) { - case bool: - return v - default: - return false - } -} diff --git a/dela/cli/node/flagset_test.go b/dela/cli/node/flagset_test.go deleted file mode 100644 index 862d5f2..0000000 --- a/dela/cli/node/flagset_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package node - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestFlagSet_String(t *testing.T) { - fset := make(FlagSet) - fset["a"] = "something" - fset["b"] = 20 - - require.Equal(t, "something", fset.String("a")) - require.Equal(t, "", fset.String("b")) -} - -func TestFlagSet_StringSlice(t *testing.T) { - fset := make(FlagSet) - fset["a"] = []interface{}{"1", "2"} - fset["b"] = 123 - - require.Equal(t, []string{"1", "2"}, fset.StringSlice("a")) - require.Nil(t, fset.StringSlice("b")) -} - -func TestFlagSet_Duration(t *testing.T) { - fset := make(FlagSet) - fset["a"] = float64(1000.0) - fset["b"] = 1000 - - require.Equal(t, time.Duration(1000), fset.Duration("a")) - require.Equal(t, time.Duration(0), fset.Duration("b")) -} - -func TestFlagSet_Path(t *testing.T) { - fset := make(FlagSet) - fset["a"] = "/one/path" - fset["b"] = 123 - - require.Equal(t, "/one/path", fset.Path("a")) - require.Equal(t, "", fset.Path("b")) -} - -func TestFlagSet_Int(t *testing.T) { - fset := make(FlagSet) - fset["a"] = 20 - fset["b"] = "oops" - fset["c"] = 30.0 - fset["d"] = 30.1 - - require.Equal(t, 20, fset.Int("a")) - require.Equal(t, 0, fset.Int("b")) - require.Equal(t, 30, fset.Int("c")) - require.Equal(t, 0, fset.Int("d")) -} - -func TestFlagSet_Bool(t *testing.T) { - fset := make(FlagSet) - fset["a"] = true - fset["b"] = "oops" - fset["c"] = false - - require.Equal(t, true, fset.Bool("a")) - require.Equal(t, false, fset.Bool("b")) - require.Equal(t, false, fset.Bool("c")) -} diff --git a/dela/cli/node/injector.go b/dela/cli/node/injector.go deleted file mode 100644 index 8e839f6..0000000 --- a/dela/cli/node/injector.go +++ /dev/null @@ -1,57 +0,0 @@ -// This file contains the implementation of a dependency injector using -// reflection. -// -// Documentation Last Review: 13.10.2020 -// - -package node - -import ( - "reflect" - - "golang.org/x/xerrors" -) - -// ReflectInjector is a dependency injector that uses reflection to resolve -// specific interfaces. -// -// - implements node.Injector -type reflectInjector struct { - mapper map[reflect.Type]interface{} -} - -// NewInjector returns a empty injector. -func NewInjector() Injector { - return &reflectInjector{ - mapper: make(map[reflect.Type]interface{}), - } -} - -// Resolve implements node.Injector. It populates the given interface with the -// first compatible dependency. -func (inj *reflectInjector) Resolve(v interface{}) error { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr { - return xerrors.New("expect a pointer") - } - - if !rv.Elem().IsValid() { - return xerrors.Errorf("reflect value '%v' is invalid", rv) - } - - for typ, value := range inj.mapper { - if typ.AssignableTo(rv.Elem().Type()) { - rv.Elem().Set(reflect.ValueOf(value)) - return nil - } - } - - return xerrors.Errorf("couldn't find dependency for '%v'", rv.Elem().Type()) -} - -// Inject implements node.Injector. It injects the dependency to be available -// later on. -func (inj *reflectInjector) Inject(v interface{}) { - key := reflect.TypeOf(v) - inj.mapper[key] = v -} diff --git a/dela/cli/node/injector_test.go b/dela/cli/node/injector_test.go deleted file mode 100644 index aa9ee36..0000000 --- a/dela/cli/node/injector_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package node - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestReflectInjector_Resolve(t *testing.T) { - inj := NewInjector() - - inj.Inject("abc") - - var dep string - err := inj.Resolve(&dep) - require.NoError(t, err) - require.Equal(t, "abc", dep) - - var dep2 uint64 - err = inj.Resolve(&dep2) - require.EqualError(t, err, "couldn't find dependency for 'uint64'") - - err = inj.Resolve((*interface{})(nil)) - require.EqualError(t, err, "reflect value '' is invalid") - - err = inj.Resolve(dep2) - require.EqualError(t, err, "expect a pointer") -} diff --git a/dela/cli/node/memcoin/mod.go b/dela/cli/node/memcoin/mod.go deleted file mode 100644 index bedb7a1..0000000 --- a/dela/cli/node/memcoin/mod.go +++ /dev/null @@ -1,81 +0,0 @@ -// Package main implements a ledger based on in-memory components. -// -// Unix example: -// -// # Expect GOPATH to be correctly set to have memcoin available. -// go install -// -// memcoin --config /tmp/node1 start --listen tcp://127.0.0.1:2001 & -// memcoin --config /tmp/node2 start --listen tcp://127.0.0.1:2002 & -// memcoin --config /tmp/node3 start --listen tcp://127.0.0.1:2003 & -// -// # Share the different certificates among the participants. -// memcoin --config /tmp/node2 minogrpc join --address //127.0.0.1:2001\ -// $(memcoin --config /tmp/node1 minogrpc token) -// memcoin --config /tmp/node3 minogrpc join --address //127.0.0.1:2001\ -// $(memcoin --config /tmp/node1 minogrpc token) -// -// # Create a chain with two members. -// memcoin --config /tmp/node1 ordering setup\ -// --member $(memcoin --config /tmp/node1 ordering export)\ -// --member $(memcoin --config /tmp/node2 ordering export) -// -// # Add the third after the chain is set up. -// memcoin --config /tmp/node1 ordering roster add\ -// --member $(memcoin --config /tmp/node3 ordering export) -// -package main - -import ( - "fmt" - "io" - "os" - - "go.dedis.ch/dela/cli/node" - access "go.dedis.ch/dela/contracts/access/controller" - cosipbft "go.dedis.ch/dela/core/ordering/cosipbft/controller" - db "go.dedis.ch/dela/core/store/kv/controller" - pool "go.dedis.ch/dela/core/txn/pool/controller" - signed "go.dedis.ch/dela/core/txn/signed/controller" - mino "go.dedis.ch/dela/mino/minogrpc/controller" - proxy "go.dedis.ch/dela/mino/proxy/http/controller" -) - -func main() { - err := run(os.Args) - if err != nil { - fmt.Printf("%+v\n", err) - } -} - -func run(args []string) error { - return runWithCfg(args, config{Writer: os.Stdout}) -} - -type config struct { - Channel chan os.Signal - Writer io.Writer -} - -func runWithCfg(args []string, cfg config) error { - builder := node.NewBuilderWithCfg( - cfg.Channel, - cfg.Writer, - db.NewController(), - mino.NewController(), - cosipbft.NewController(), - signed.NewManagerController(), - pool.NewController(), - access.NewController(), - proxy.NewController(), - ) - - app := builder.Build() - - err := app.Run(args) - if err != nil { - return err - } - - return nil -} diff --git a/dela/cli/node/memcoin/mod_test.go b/dela/cli/node/memcoin/mod_test.go deleted file mode 100644 index 876bbed..0000000 --- a/dela/cli/node/memcoin/mod_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "net" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestMemcoin_Main(t *testing.T) { - main() -} - -// This test creates a chain with initially 3 nodes. It then adds node 4 and 5 -// in two blocks. Node 4 does not share its certificate which means others won't -// be able to communicate, but the chain should proceed because of the -// threshold. -func TestMemcoin_Scenario_SetupAndTransactions(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "memcoin1") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - sigs := make(chan os.Signal) - wg := sync.WaitGroup{} - wg.Add(5) - - node1 := filepath.Join(dir, "node1") - node2 := filepath.Join(dir, "node2") - node3 := filepath.Join(dir, "node3") - node4 := filepath.Join(dir, "node4") - node5 := filepath.Join(dir, "node5") - - cfg := config{Channel: sigs, Writer: io.Discard} - - runNode(t, node1, cfg, 2111, &wg) - runNode(t, node2, cfg, 2112, &wg) - runNode(t, node3, cfg, 2113, &wg) - runNode(t, node4, cfg, 2114, &wg) - runNode(t, node5, cfg, 2115, &wg) - - defer func() { - // Simulate a Ctrl+C - close(sigs) - wg.Wait() - }() - - require.True(t, waitDaemon(t, []string{node1, node2, node3}), "daemon failed to start") - - // Share the certificates. - shareCert(t, node2, node1, "//127.0.0.1:2111") - shareCert(t, node3, node1, "//127.0.0.1:2111") - shareCert(t, node5, node1, "//127.0.0.1:2111") - - // Setup the chain with nodes 1 and 2. - args := append(append( - append( - []string{os.Args[0], "--config", node1, "ordering", "setup"}, - getExport(t, node1)..., - ), - getExport(t, node2)...), - getExport(t, node3)...) - - err = run(args) - require.NoError(t, err) - - // Add node 4 to the current chain. This node is not reachable from the - // others but transactions should work as the threshold is correct. - args = append([]string{ - os.Args[0], - "--config", node1, "ordering", "roster", "add", - "--wait", "60s"}, - getExport(t, node4)..., - ) - - err = run(args) - require.NoError(t, err) - - // Add node 5 which should be participating. - args = append([]string{ - os.Args[0], - "--config", node1, "ordering", "roster", "add", - "--wait", "60s"}, - getExport(t, node5)..., - ) - - err = run(args) - require.NoError(t, err) - - // Run a few transactions. - for i := 0; i < 5; i++ { - err = runWithCfg(args, config{}) - require.EqualError(t, err, "command error: transaction refused: duplicate in roster: 127.0.0.1:2115") - } - - // Test a timeout waiting for a transaction. - args[7] = "1ns" - err = runWithCfg(args, config{}) - require.EqualError(t, err, "command error: transaction not found after timeout") - - // Test a bad command. - err = runWithCfg([]string{os.Args[0], "ordering", "setup"}, cfg) - require.EqualError(t, err, `Required flag "member" not set`) -} - -// This test creates a chain with two nodes, then gracefully close them. It -// finally restarts both of them to make sure the chain can proceed after the -// restart. It basically tests if the components are correctly loaded from the -// persisten storage. -func TestMemcoin_Scenario_RestartNode(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "memcoin2") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - node1 := filepath.Join(dir, "node1") - node2 := filepath.Join(dir, "node2") - - // Setup the chain and closes the node. - setupChain(t, []string{node1, node2}, []uint16{2210, 2211}) - - sigs := make(chan os.Signal) - wg := sync.WaitGroup{} - wg.Add(2) - - cfg := config{Channel: sigs, Writer: io.Discard} - - // Now the node are restarted. It should correctly follow the existing chain - // and then participate to new blocks. - runNode(t, node1, cfg, 2210, &wg) - runNode(t, node2, cfg, 2211, &wg) - - defer func() { - // Simulate a Ctrl+C - close(sigs) - wg.Wait() - }() - - require.True(t, waitDaemon(t, []string{node1, node2}), "daemon failed to start") - - args := append([]string{ - os.Args[0], - "--config", node1, "ordering", "roster", "add", - "--wait", "60s"}, - getExport(t, node1)..., - ) - - err = run(args) - require.EqualError(t, err, "command error: transaction refused: duplicate in roster: 127.0.0.1:2210") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -const testDialTimeout = 500 * time.Millisecond - -func runNode(t *testing.T, node string, cfg config, port uint16, wg *sync.WaitGroup) { - go func() { - defer wg.Done() - - err := runWithCfg(makeNodeArg(node, port), cfg) - require.NoError(t, err) - }() -} - -func setupChain(t *testing.T, nodes []string, ports []uint16) { - sigs := make(chan os.Signal) - wg := sync.WaitGroup{} - wg.Add(len(nodes)) - - cfg := config{Channel: sigs, Writer: io.Discard} - - for i, node := range nodes { - runNode(t, node, cfg, ports[i], &wg) - } - - defer func() { - // Simulate a Ctrl+C - close(sigs) - wg.Wait() - }() - - waitDaemon(t, nodes) - - shareCert(t, nodes[1], nodes[0], fmt.Sprintf("//127.0.0.1:%d", ports[0])) - - args := append(append( - []string{os.Args[0], "--config", nodes[0], "ordering", "setup"}, - getExport(t, nodes[0])...), - getExport(t, nodes[1])..., - ) - - err := run(args) - require.NoError(t, err) -} - -func waitDaemon(t *testing.T, daemons []string) bool { - num := 50 - - for _, daemon := range daemons { - path := filepath.Join(daemon, "daemon.sock") - - for i := 0; i < num; i++ { - // Windows: we have to check the file as Dial on Windows creates the - // file and prevent to listen. - _, err := os.Stat(path) - if !os.IsNotExist(err) { - conn, err := net.DialTimeout("unix", path, testDialTimeout) - if err == nil { - conn.Close() - break - } - } - - time.Sleep(100 * time.Millisecond) - - if i+1 >= num { - return false - } - } - } - - return true -} - -func makeNodeArg(path string, port uint16) []string { - return []string{ - os.Args[0], "--config", path, "start", "--listen", "tcp://127.0.0.1:" + strconv.Itoa(int(port)), - } -} - -func shareCert(t *testing.T, path string, src string, addr string) { - args := append( - []string{os.Args[0], "--config", path, "minogrpc", "join", "--address", addr}, - getToken(t, src)..., - ) - - err := run(args) - require.NoError(t, err) -} - -func getToken(t *testing.T, path string) []string { - buffer := new(bytes.Buffer) - cfg := config{ - Writer: buffer, - } - - args := []string{os.Args[0], "--config", path, "minogrpc", "token"} - err := runWithCfg(args, cfg) - require.NoError(t, err) - - return strings.Split(buffer.String(), " ") -} - -func getExport(t *testing.T, path string) []string { - buffer := bytes.NewBufferString("--member ") - cfg := config{ - Writer: buffer, - } - - args := []string{os.Args[0], "--config", path, "ordering", "export"} - - err := runWithCfg(args, cfg) - require.NoError(t, err) - - return strings.Split(buffer.String(), " ") -} diff --git a/dela/cli/node/node.go b/dela/cli/node/node.go deleted file mode 100644 index 8120697..0000000 --- a/dela/cli/node/node.go +++ /dev/null @@ -1,86 +0,0 @@ -// Package node defines the Builder type, which builds an CLI application to -// controle a node. -// -// The application will have a start command by default and it provides a -// function to create actions that will eventually be executed on the running -// node. See the example. -// -// Document Last Review: 13.10.2020 -package node - -import ( - "io" - - "go.dedis.ch/dela/cli" -) - -// Builder is the builder that will be provided to the initializers, which can -// create commands and actions. -type Builder interface { - // SetCommand creates a new command and returns its builder. - SetCommand(name string) cli.CommandBuilder - - // SetStartFlags appends a list of flags that will be used to create the - // start command. - SetStartFlags(...cli.Flag) - - // MakeAction creates a CLI action from a given template. The template must - // implements the handler that will be executed on the daemon. - MakeAction(ActionTemplate) cli.Action -} - -// ActionTemplate is an extension of the cli.Action interface to allow an action -// to send a request to the daemon. -type ActionTemplate interface { - // Execute processes a command received from the CLI on the daemon. - Execute(Context) error -} - -// Context is the context available to the action when being invoked. It -// provides the dependency injector alongside with the input and output. -type Context struct { - Injector Injector - Flags cli.Flags - Out io.Writer -} - -// Injector is a dependency injection abstraction. -type Injector interface { - // Resolve populates the input with the dependency if any compatible exists. - Resolve(interface{}) error - - // Inject stores the dependency to be resolved later on. - Inject(interface{}) -} - -// Initializer is the interface that a module can implement to set its own -// commands and inject the dependencies that will be resolved in the actions. -type Initializer interface { - // Build populates the builder with the commands of the controller. - SetCommands(Builder) - - // OnStart starts the components of the initializer and populates the - // injector. - OnStart(cli.Flags, Injector) error - - // OnStop stops the components and cleans the resources. - OnStop(Injector) error -} - -// Client is the interface to send a message to the daemon. -type Client interface { - Send([]byte) error -} - -// Daemon is an IPC socket to communicate between a CLI and a node running. -type Daemon interface { - Listen() error - Close() error -} - -// DaemonFactory is an interface to create a daemon and clients to connect to -// it. -type DaemonFactory interface { - ClientFromContext(cli.Flags) (Client, error) - DaemonFromContext(cli.Flags) (Daemon, error) -} diff --git a/dela/cli/ucli/ucli.go b/dela/cli/ucli/ucli.go deleted file mode 100644 index 34bfa1c..0000000 --- a/dela/cli/ucli/ucli.go +++ /dev/null @@ -1,172 +0,0 @@ -// Package ucli provides a cli builder implementation based on the urfave/cli -// library. -package ucli - -import ( - "fmt" - - urfave "github.com/urfave/cli/v2" - "go.dedis.ch/dela/cli" -) - -// Builder implements a cli builder based on urfave/cli -// -// - implements cli.Builder -type Builder struct { - commands []*cmdBuilder - name string - action cli.Action - flags []cli.Flag -} - -// NewBuilder returns a new initialized builder. Action allows one to define a -// primary action, but can be nil if we only needs to define commands. Flags -// provides the global flags available from all the commands/subcommands. -func NewBuilder(name string, action cli.Action, flags ...cli.Flag) cli.Builder { - return &Builder{ - name: name, - action: action, - flags: flags, - } -} - -// Build implements cli.builder. -func (b Builder) Build() cli.Application { - app := &urfave.App{ - Name: b.name, - Commands: buildCommand(b.commands), - Action: makeAction(b.action), - Flags: buildFlags(b.flags), - } - - app.Setup() - - return app -} - -// SetCommand implements cli.Builder. -func (b *Builder) SetCommand(name string) cli.CommandBuilder { - cmd := &cmdBuilder{ - name: name, - } - b.commands = append(b.commands, cmd) - - return cmd -} - -// commandBuilder is the struct provided to build commands. -// -// - implements cli.CommandBuilder -type cmdBuilder struct { - name string - description string - action cli.Action - flags []urfave.Flag - subcommands []*cmdBuilder -} - -// SetDescription implements cli.CommandBuilder. -func (b *cmdBuilder) SetDescription(value string) { - b.description = value -} - -// SetFlags implements cli.CommandBuilder. -func (b *cmdBuilder) SetFlags(flags ...cli.Flag) { - b.flags = buildFlags(flags) -} - -// SetAction implements cli.CommandBuilder. -func (b *cmdBuilder) SetAction(action cli.Action) { - b.action = action -} - -// SetSubCommand implements cli.CommandBuilder. -func (b *cmdBuilder) SetSubCommand(name string) cli.CommandBuilder { - builder := &cmdBuilder{ - name: name, - } - b.subcommands = append(b.subcommands, builder) - - return builder -} - -// buildFlags converts cli.Flag to their corresponding urfave/cli. -func buildFlags(flags []cli.Flag) []urfave.Flag { - res := make([]urfave.Flag, len(flags)) - - for i, f := range flags { - var flag urfave.Flag - - switch e := f.(type) { - case cli.StringFlag: - flag = &urfave.StringFlag{ - Name: e.Name, - Usage: e.Usage, - Required: e.Required, - Value: e.Value, - } - case cli.StringSliceFlag: - flag = &urfave.StringSliceFlag{ - Name: e.Name, - Usage: e.Usage, - Required: e.Required, - Value: urfave.NewStringSlice(e.Value...), - } - case cli.DurationFlag: - flag = &urfave.DurationFlag{ - Name: e.Name, - Usage: e.Usage, - Required: e.Required, - Value: e.Value, - } - case cli.IntFlag: - flag = &urfave.IntFlag{ - Name: e.Name, - Usage: e.Usage, - Required: e.Required, - Value: e.Value, - } - case cli.BoolFlag: - flag = &urfave.BoolFlag{ - Name: e.Name, - Usage: e.Usage, - Required: e.Required, - Value: e.Value, - } - default: - panic(fmt.Sprintf("flag type '%T' not supported", f)) - } - - res[i] = flag - } - - return res -} - -// buildCommand recursively builds the commands from a cmdBuilder struct to a -// urfave commands. -func buildCommand(cmds []*cmdBuilder) []*urfave.Command { - commands := make([]*urfave.Command, len(cmds)) - - for i, cmd := range cmds { - commands[i] = &urfave.Command{ - Name: cmd.name, - Usage: cmd.description, - Action: makeAction(cmd.action), - Flags: cmd.flags, - Subcommands: buildCommand(cmd.subcommands), - } - } - - return commands -} - -// makeAction transforms a cli.Action to its urfave form. -func makeAction(action cli.Action) urfave.ActionFunc { - if action != nil { - return func(ctx *urfave.Context) error { - return action(ctx) - } - } - return nil -} diff --git a/dela/cli/ucli/ucli_test.go b/dela/cli/ucli/ucli_test.go deleted file mode 100644 index 01b3014..0000000 --- a/dela/cli/ucli/ucli_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package ucli - -import ( - "io" - "testing" - "time" - - "github.com/stretchr/testify/require" - urfave "github.com/urfave/cli/v2" - "go.dedis.ch/dela/cli" -) - -func TestBuild(t *testing.T) { - builder := NewBuilder("test", nil) - app := builder.Build().(*urfave.App) - - app.Writer = io.Discard - - require.Equal(t, "test", app.Name) - - err := app.Run([]string{"test"}) - require.NoError(t, err) -} - -func TestSetCommand(t *testing.T) { - builder := NewBuilder("test", nil) - - builder.SetCommand("first") - builder.SetCommand("second") - - app := builder.Build().(*urfave.App) - - require.Len(t, app.Commands, 3) - - require.Equal(t, "first", app.Commands[0].Name) - require.Equal(t, "second", app.Commands[1].Name) - require.Equal(t, "help", app.Commands[2].Name) - -} - -func TestCommandBuilder(t *testing.T) { - builder := NewBuilder("test", nil).(*Builder) - cmd := builder.SetCommand("first") - - fakeAction := func(flags cli.Flags) error { - return nil - } - - cmd.SetAction(fakeAction) - cmd.SetDescription("first action") - cmd.SetFlags(cli.StringFlag{ - Name: "arg", - Usage: "this is a test arg", - Required: true, - Value: "default", - }) - cmd.SetSubCommand("second") - - require.Len(t, builder.commands, 1) - require.Len(t, builder.flags, 0) - - cmd2 := builder.commands[0] - require.Len(t, cmd2.flags, 1) - require.Len(t, cmd2.subcommands, 1) -} - -func TestBuildFlags(t *testing.T) { - in := []cli.Flag{ - cli.StringFlag{ - Name: "name1", - Usage: "usage1", - Required: true, - Value: "value1", - }, - cli.StringSliceFlag{ - Name: "name2", - Usage: "usage2", - Required: true, - Value: []string{}, - }, - cli.DurationFlag{ - Name: "name3", - Usage: "usage3", - Required: true, - Value: time.Minute, - }, - cli.IntFlag{ - Name: "name4", - Usage: "usage4", - Required: true, - Value: 1, - }, - cli.BoolFlag{ - Name: "name5", - Usage: "usage5", - Required: true, - Value: true, - }, - } - - out := buildFlags(in) - require.Len(t, out, 5) - - require.Equal(t, "name1", out[0].Names()[0]) - require.Equal(t, "name2", out[1].Names()[0]) - require.Equal(t, "name3", out[2].Names()[0]) - require.Equal(t, "name4", out[3].Names()[0]) - require.Equal(t, "name5", out[4].Names()[0]) -} - -func TestBuildFlags_Panic(t *testing.T) { - defer func() { - r := recover() - require.Equal(t, "flag type '' not supported", r) - }() - - buildFlags([]cli.Flag{nil}) -} - -func TestMakeAction(t *testing.T) { - res := makeAction(nil) - require.Nil(t, res) - - isCalled := false - fakeAction := func(flags cli.Flags) error { - require.Nil(t, flags) - isCalled = true - return nil - } - - res = makeAction(fakeAction) - require.NotNil(t, res) - - out := res(nil) - require.NoError(t, out) - require.True(t, isCalled) -} diff --git a/dela/contracts/access/access.go b/dela/contracts/access/access.go deleted file mode 100644 index c434758..0000000 --- a/dela/contracts/access/access.go +++ /dev/null @@ -1,178 +0,0 @@ -// Package access implements a native contract to handle access. It allows an -// authorized identity to add access as an {ID, CONTRACT, COMMAND, IDENTITIES} -// quadruplet. -// -// ID is the credential identifier. This identifier is generally defined at the -// contract's creation. -// CONTRACT is the contract name. -// COMMAND specifies the command to grant access to on the contract. -// IDENTITIES is a list of standard base64 encoded bls public keys, separated by -// comas. -// -// Documentation Last Review: 02.02.2021 -package access - -import ( - "encoding/base64" - "encoding/hex" - "strings" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/crypto/bls" - "golang.org/x/xerrors" -) - -const ( - // ContractName is the name of the access contract. - ContractName = "go.dedis.ch/dela.Access" - - // GrantIDArg is the argument's name in the transaction that contains the - // provided id to grant - GrantIDArg = "access:grant_id" - - // GrantContractArg is the argument's name in the transaction that contain - // the provided contract name to grant the access to. - GrantContractArg = "access:grant_contract" - - // GrantCommandArg is the argument's name in the transaction that contains - // the provided command to grant access to. - GrantCommandArg = "access:grant_command" - - // IdentityArg is the argument's name in the transaction that contains the - // provided identity to grant access to. - IdentityArg = "access:identity" - - // CmdArg is the argument's name to indicate the kind of command we want to - // run on the contract. Should be one of the Command type. - CmdArg = "access:command" - - // credentialAllCommand defines the credential command that is allowed to - // perform all commands. - credentialAllCommand = "all" -) - -// Command defines a command for the command contract -type Command string - -const ( - // CmdSet defines the command to grant access - CmdSet Command = "GRANT" -) - -// NewCreds creates new credentials for an access contract execution. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, credentialAllCommand) -} - -// RegisterContract registers the access contract to the given execution -// service. -func RegisterContract(exec *native.Service, c Contract) { - exec.Set(ContractName, c) -} - -// Contract is the access contract that allows one to handle access. -// -// - implements native.Contract -type Contract struct { - // access is the access service that will be modified. - access access.Service - - // accessKey is the credential's ID allowed to use this smart contract - accessKey []byte - - store store.Readable -} - -// NewContract creates a new access contract -func NewContract(aKey []byte, srvc access.Service, store store.Readable) Contract { - return Contract{ - access: srvc, - accessKey: aKey, - store: store, - } -} - -// Execute implements native.Contract -func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - creds := NewCreds(c.accessKey) - - err := c.access.Match(c.store, creds, step.Current.GetIdentity()) - if err != nil { - return xerrors.Errorf("identity not authorized: %v (%v)", step.Current.GetIdentity(), err) - } - - cmd := step.Current.GetArg(CmdArg) - if len(cmd) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", CmdArg) - } - - switch Command(cmd) { - case CmdSet: - err := c.grant(snap, step) - if err != nil { - return xerrors.Errorf("failed to SET: %v", err) - } - default: - return xerrors.Errorf("access, unknown command: %s", cmd) - } - - return nil -} - -// grant perform the GRANT command -func (c Contract) grant(snap store.Snapshot, step execution.Step) error { - idHex := step.Current.GetArg(GrantIDArg) - if len(idHex) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", GrantIDArg) - } - - id, err := hex.DecodeString(string(idHex)) - if err != nil { - return xerrors.Errorf("failed to decode id from tx arg: %v", err) - } - - contractName := step.Current.GetArg(GrantContractArg) - if len(contractName) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", GrantContractArg) - } - - commandName := step.Current.GetArg(GrantCommandArg) - if len(commandName) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", GrantCommandArg) - } - - base64IDs := strings.Split(string(step.Current.GetArg(IdentityArg)), ",") - if len(base64IDs) == 0 || len(base64IDs[0]) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", IdentityArg) - } - - identities := make([]access.Identity, len(base64IDs)) - for i, base64ID := range base64IDs { - identity, err := base64.StdEncoding.DecodeString(string(base64ID)) - if err != nil { - return xerrors.Errorf("failed to decode base64ID: %v", err) - } - - pubKey, err := bls.NewPublicKey(identity) - if err != nil { - return xerrors.Errorf("failed to get public key: %v", err) - } - - identities[i] = pubKey - } - - credential := access.NewContractCreds(id, string(contractName), string(commandName)) - err = c.access.Grant(snap, credential, identities...) - if err != nil { - return xerrors.Errorf("failed to grant: %v", err) - } - - dela.Logger.Info().Str("contract", "access").Msgf("granted %x-%s-%s to %s", - id, contractName, commandName, identities) - - return nil -} diff --git a/dela/contracts/access/access_test.go b/dela/contracts/access/access_test.go deleted file mode 100644 index e5b2ede..0000000 --- a/dela/contracts/access/access_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package access - -import ( - "encoding/base64" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestExecute(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{err: fake.GetError()}, fakeStore{}) - err := contract.Execute(fakeStore{}, makeStep(t, CmdArg, "")) - require.EqualError(t, err, "identity not authorized: fake.PublicKey ("+fake.GetError().Error()+")") - - contract = NewContract([]byte{}, fakeAccess{}, fakeStore{}) - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "")) - require.EqualError(t, err, "'access:command' not found in tx arg") - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "fake")) - require.EqualError(t, err, "access, unknown command: fake") - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, string(CmdSet))) - require.EqualError(t, err, "failed to SET: 'access:grant_id' not found in tx arg") - - signer := bls.NewSigner() - buf, err := signer.GetPublicKey().MarshalBinary() - require.NoError(t, err) - id := base64.StdEncoding.EncodeToString(buf) - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, string(CmdSet), - GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command", - IdentityArg, id)) - require.NoError(t, err) -} - -func TestGrant(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}, fakeStore{}) - err := contract.grant(fakeStore{}, makeStep(t)) - require.EqualError(t, err, "'access:grant_id' not found in tx arg") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "x")) - require.EqualError(t, err, "failed to decode id from tx arg: encoding/hex: invalid byte: U+0078 'x'") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef")) - require.EqualError(t, err, "'access:grant_contract' not found in tx arg") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake")) - require.EqualError(t, err, "'access:grant_command' not found in tx arg") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command")) - require.EqualError(t, err, "'access:identity' not found in tx arg") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command", - IdentityArg, "x")) - require.EqualError(t, err, "failed to decode base64ID: illegal base64 data at input byte 0") - - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command", - IdentityArg, "AA==")) - require.EqualError(t, err, "failed to get public key: bn256.G2: not enough data") - - signer := bls.NewSigner() - buf, err := signer.GetPublicKey().MarshalBinary() - require.NoError(t, err) - id := base64.StdEncoding.EncodeToString(buf) - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command", - IdentityArg, id)) - require.NoError(t, err) - - contract = NewContract([]byte{}, fakeAccess{err: fake.GetError()}, fakeStore{}) - err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", - GrantContractArg, "fake contract", - GrantCommandArg, "fake command", - IdentityArg, id)) - require.EqualError(t, err, fake.Err("failed to grant")) -} - -func TestRegisterContract(t *testing.T) { - RegisterContract(native.NewExecution(), Contract{}) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeStep(t *testing.T, args ...string) execution.Step { - return execution.Step{Current: makeTx(t, args...)} -} - -func makeTx(t *testing.T, args ...string) txn.Transaction { - options := []signed.TransactionOption{} - for i := 0; i < len(args)-1; i += 2 { - options = append(options, signed.WithArg(args[i], []byte(args[i+1]))) - } - - tx, err := signed.NewTransaction(0, fake.PublicKey{}, options...) - require.NoError(t, err) - - return tx -} - -type fakeAccess struct { - access.Service - - err error -} - -func (srvc fakeAccess) Match(store.Readable, access.Credential, ...access.Identity) error { - return srvc.err -} - -func (srvc fakeAccess) Grant(store.Snapshot, access.Credential, ...access.Identity) error { - return srvc.err -} - -type fakeStore struct { - store.Snapshot -} - -func (s fakeStore) Get(key []byte) ([]byte, error) { - return nil, nil -} - -func (s fakeStore) Set(key, value []byte) error { - return nil -} diff --git a/dela/contracts/access/controller/access.json b/dela/contracts/access/controller/access.json deleted file mode 100644 index 9e26dfe..0000000 --- a/dela/contracts/access/controller/access.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/dela/contracts/access/controller/action.go b/dela/contracts/access/controller/action.go deleted file mode 100644 index 49fc3d4..0000000 --- a/dela/contracts/access/controller/action.go +++ /dev/null @@ -1,80 +0,0 @@ -// This file implements the action of the controller. -// -// Documentation Last Review: 02.02.2021 -// - -package controller - -import ( - "encoding/base64" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/cli/node" - accessContract "go.dedis.ch/dela/contracts/access" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/crypto/bls" - "golang.org/x/xerrors" -) - -// addAction is an action to add one or more identities. -// -// - implements node.ActionTemplate -type addAction struct{} - -// Execute implements node.ActionTemplate. It reads the list of identities and -// updates the access. -func (a addAction) Execute(ctx node.Context) error { - var exec *native.Service - err := ctx.Injector.Resolve(&exec) - if err != nil { - return xerrors.Errorf("failed to resolve native service: %v", err) - } - - var asrv access.Service - err = ctx.Injector.Resolve(&asrv) - if err != nil { - return xerrors.Errorf("failed to resolve access service: %v", err) - } - - var accessStore accessStore - err = ctx.Injector.Resolve(&accessStore) - if err != nil { - return xerrors.Errorf("failed to resolve access store: %v", err) - } - - idsStr := ctx.Flags.StringSlice("identity") - identities, err := parseIdentities(idsStr) - if err != nil { - return xerrors.Errorf("failed to parse identities: %v", err) - } - - err = asrv.Grant(accessStore, accessContract.NewCreds(aKey[:]), identities...) - if err != nil { - return xerrors.Errorf("failed to grant: %v", err) - } - - dela.Logger.Info().Msgf("access granted to %v", identities) - - return nil -} - -func parseIdentities(idsStr []string) ([]access.Identity, error) { - identities := make([]access.Identity, len(idsStr)) - - for i, id := range idsStr { - idBuf, err := base64.StdEncoding.DecodeString(id) - if err != nil { - return nil, xerrors.Errorf("failed to decode pub key '%s': %v", id, err) - } - - pk, err := bls.NewPublicKey(idBuf) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal identity '%s': %v", id, err) - } - - identities[i] = pk - } - - return identities, nil -} diff --git a/dela/contracts/access/controller/action_test.go b/dela/contracts/access/controller/action_test.go deleted file mode 100644 index ebe6b08..0000000 --- a/dela/contracts/access/controller/action_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package controller - -import ( - "encoding/base64" - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestAddAction_Execute(t *testing.T) { - ctx := node.Context{ - Injector: node.NewInjector(), - Flags: make(node.FlagSet), - Out: io.Discard, - } - - action := addAction{} - err := action.Execute(ctx) - require.EqualError(t, err, "failed to resolve native service: couldn't find dependency for '*native.Service'") - - native := native.NewExecution() - ctx.Injector.Inject(native) - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to resolve access service: couldn't find dependency for 'access.Service'") - - access := fakeAccess{} - ctx.Injector.Inject(&access) - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to resolve access store: couldn't find dependency for 'controller.accessStore'") - - store := fakeStore{} - ctx.Injector.Inject(&store) - - err = action.Execute(ctx) - require.NoError(t, err) - - access.err = fake.GetError() - - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("failed to grant")) - - flags := fakeFlags{strings: make(map[string][]string)} - ctx.Flags = flags - flags.strings["identity"] = []string{"a"} - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to parse identities: failed to decode pub key 'a': illegal base64 data at input byte 0") - - flags.strings["identity"] = []string{"AA=="} - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to parse identities: failed to unmarshal identity 'AA==': bn256.G2: not enough data") - - signer := bls.NewSigner() - buf, err := signer.GetPublicKey().MarshalBinary() - require.NoError(t, err) - id := base64.StdEncoding.EncodeToString(buf) - flags.strings["identity"] = []string{id} - - access.err = nil - - err = action.Execute(ctx) - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeStore struct { - accessStore -} - -type fakeFlags struct { - cli.Flags - - strings map[string][]string -} - -func (f fakeFlags) StringSlice(name string) []string { - return f.strings[name] -} diff --git a/dela/contracts/access/controller/controller.go b/dela/contracts/access/controller/controller.go deleted file mode 100644 index 4c94899..0000000 --- a/dela/contracts/access/controller/controller.go +++ /dev/null @@ -1,82 +0,0 @@ -// Package controller implements a controller for the access contract. -// -// Documentation Last Review: 02.02.2021 -package controller - -import ( - "path/filepath" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - accessContract "go.dedis.ch/dela/contracts/access" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "golang.org/x/xerrors" -) - -var aKey = [32]byte{1} - -// newStore is the function used to create the new store. It allows us to create -// a different store in the tests. -var newStore = func(path string) (accessStore, error) { - return newJstore(path) -} - -// miniController is a CLI initializer to allow a user to grant access to the -// access contract. -// -// - implements node.Initializer -type miniController struct{} - -// NewController creates a new minimal controller for the access contract. -func NewController() node.Initializer { - return miniController{} -} - -// SetCommands implements node.Initializer. It sets the command to control the -// service. -func (miniController) SetCommands(builder node.Builder) { - cmd := builder.SetCommand("access") - cmd.SetDescription("Handles the access contract") - - sub := cmd.SetSubCommand("add") - sub.SetDescription("add an identity") - sub.SetFlags(cli.StringSliceFlag{ - Name: "identity", - Usage: "identity to add, in the form of bls public keys", - Required: true, - }) - sub.SetAction(builder.MakeAction(addAction{})) -} - -// OnStart implements node.Initializer. It registers the access contract. -func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { - var access access.Service - err := inj.Resolve(&access) - if err != nil { - return xerrors.Errorf("failed to resolve access service: %v", err) - } - - var exec *native.Service - err = inj.Resolve(&exec) - if err != nil { - return xerrors.Errorf("failed to resolve native service: %v", err) - } - - accessStore, err := newStore(filepath.Join(flags.String("config"), "access.json")) - if err != nil { - return xerrors.Errorf("failed to create access store: %v", err) - } - - contract := accessContract.NewContract(aKey[:], access, accessStore) - accessContract.RegisterContract(exec, contract) - - inj.Inject(accessStore) - - return nil -} - -// OnStop implements node.Initializer. -func (miniController) OnStop(inj node.Injector) error { - return nil -} diff --git a/dela/contracts/access/controller/controller_test.go b/dela/contracts/access/controller/controller_test.go deleted file mode 100644 index a70c8aa..0000000 --- a/dela/contracts/access/controller/controller_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package controller - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestSetCommands(t *testing.T) { - ctrl := NewController() - - call := &fake.Call{} - ctrl.SetCommands(fakeBuilder{call: call}) - - require.Equal(t, call.Len(), 7) -} - -func TestOnStart(t *testing.T) { - ctrl := NewController() - - injector := node.NewInjector() - err := ctrl.OnStart(node.FlagSet{}, injector) - require.EqualError(t, err, "failed to resolve access service: couldn't find dependency for 'access.Service'") - - access := fakeAccess{} - injector.Inject(&access) - - err = ctrl.OnStart(node.FlagSet{}, injector) - require.EqualError(t, err, "failed to resolve native service: couldn't find dependency for '*native.Service'") - - native := native.NewExecution() - injector.Inject(native) - - oldStore := newStore - newStore = func(path string) (accessStore, error) { - return nil, fake.GetError() - } - - err = ctrl.OnStart(node.FlagSet{}, injector) - require.EqualError(t, err, fake.Err("failed to create access store")) - - newStore = oldStore - - err = ctrl.OnStart(node.FlagSet{}, injector) - require.NoError(t, err) -} - -func TestOnStop(t *testing.T) { - ctrl := NewController() - - err := ctrl.OnStop(nil) - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeCommandBuilder struct { - call *fake.Call -} - -func (b fakeCommandBuilder) SetSubCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return b -} - -func (b fakeCommandBuilder) SetDescription(value string) { - b.call.Add(value) -} - -func (b fakeCommandBuilder) SetFlags(flags ...cli.Flag) { - b.call.Add(flags) -} - -func (b fakeCommandBuilder) SetAction(a cli.Action) { - b.call.Add(a) -} - -type fakeBuilder struct { - call *fake.Call -} - -func (b fakeBuilder) SetCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return fakeCommandBuilder(b) -} - -func (b fakeBuilder) SetStartFlags(flags ...cli.Flag) { - b.call.Add(flags) -} - -func (b fakeBuilder) MakeAction(tmpl node.ActionTemplate) cli.Action { - b.call.Add(tmpl) - return nil -} - -type fakeAccess struct { - access.Service - - err error -} - -func (a fakeAccess) Grant(store store.Snapshot, creds access.Credential, idents ...access.Identity) error { - return a.err -} diff --git a/dela/contracts/access/controller/jstore.go b/dela/contracts/access/controller/jstore.go deleted file mode 100644 index 9e95fab..0000000 --- a/dela/contracts/access/controller/jstore.go +++ /dev/null @@ -1,113 +0,0 @@ -// This file implements a simple store based on a json file. -// -// Documentation Last Review: 02.02.2021 -// - -package controller - -import ( - "os" - "sync" - - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -// accessStore defines a simple read/write interface to store the access -type accessStore interface { - store.Writable - store.Readable -} - -func newJstore(path string) (accessStore, error) { - data := map[string][]byte{} - - ctx := json.NewContext() - - jstore := &jstore{ - ctx: ctx, - path: path, - data: data, - } - - if fileExist(path) { - buf, err := os.ReadFile(path) - if err != nil { - return nil, xerrors.Errorf("failed to read file '%s': %v", path, err) - } - - err = ctx.Unmarshal(buf, &data) - if err != nil { - return nil, xerrors.Errorf("failed to read json: %v", err) - } - } else { - err := jstore.saveFile() - if err != nil { - return nil, xerrors.Errorf("failed to save empty file: %v", err) - } - } - - return jstore, nil -} - -// jstore implements a simple store to store accesses on the access contract. It -// keeps the data in memory AND in a json file. -// -// - implements accessStore -type jstore struct { - sync.Mutex - - ctx serde.Context - - path string - data map[string][]byte -} - -func (s *jstore) Set(key []byte, value []byte) error { - s.Lock() - defer s.Unlock() - - s.data[string(key)] = value - s.saveFile() - - return nil -} - -func (s *jstore) Delete(key []byte) error { - s.Lock() - defer s.Unlock() - - delete(s.data, string(key)) - s.saveFile() - - return nil -} - -// return a nil value if not found -func (s *jstore) Get(key []byte) ([]byte, error) { - s.Lock() - defer s.Unlock() - - return s.data[string(key)], nil -} - -func (s *jstore) saveFile() error { - buf, err := s.ctx.Marshal(s.data) - if err != nil { - return xerrors.Errorf("failed to marshal data: %v", err) - } - - err = os.WriteFile(s.path, buf, 0644) - if err != nil { - return xerrors.Errorf("failed to save file '%s': %v", s.path, err) - } - - return nil -} - -func fileExist(path string) bool { - _, err := os.Stat(path) - return !os.IsNotExist(err) -} diff --git a/dela/contracts/access/controller/jstore_test.go b/dela/contracts/access/controller/jstore_test.go deleted file mode 100644 index cbf00b4..0000000 --- a/dela/contracts/access/controller/jstore_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package controller - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde/json" -) - -// constant holding the temporary dela directory name -const delaTestDir = "dela-test-" - -func TestJstore_New(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - path := filepath.Join(dir, "store.json") - store, err := newJstore(path) - require.NoError(t, err) - - require.NotNil(t, store) - - _, err = newJstore(dir) - require.Regexp(t, "^failed to read file", err.Error()) - - err = os.WriteFile(path, []byte(""), os.ModePerm) - require.NoError(t, err) - - _, err = newJstore(path) - require.EqualError(t, err, "failed to read json: unexpected end of JSON input") - - _, err = newJstore("/fake/file") - require.Regexp(t, "^failed to save empty file:", err.Error()) -} - -func TestJstore_Set_Get_Delete(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - path := filepath.Join(dir, "store.json") - store, err := newJstore(path) - require.NoError(t, err) - - key := []byte("key") - val := []byte("value") - - resp, err := store.Get(key) - require.NoError(t, err) - require.Nil(t, resp) - - err = store.Set(key, val) - require.NoError(t, err) - - resp, err = store.Get(key) - require.NoError(t, err) - require.Equal(t, val, resp) - - err = store.Delete(key) - require.NoError(t, err) - - resp, err = store.Get(key) - require.NoError(t, err) - require.Nil(t, resp) -} - -func TestJstore_SaveFile(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - path := filepath.Join(dir, "store.json") - store, err := newJstore(path) - require.NoError(t, err) - - store.(*jstore).data["key"] = []byte("value") - - err = store.(*jstore).saveFile() - require.NoError(t, err) - - store.(*jstore).ctx = fake.NewBadContext() - err = store.(*jstore).saveFile() - require.EqualError(t, err, fake.Err("failed to marshal data")) - - store.(*jstore).path = dir - store.(*jstore).ctx = json.NewContext() - err = store.(*jstore).saveFile() - require.Regexp(t, "^failed to save file", err.Error()) -} - -func TestJstore_Scenario(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - path := filepath.Join(dir, "store.json") - store, err := newJstore(path) - require.NoError(t, err) - - key1 := []byte("key1") - val1 := []byte("value1") - - key2 := []byte("key2") - val2 := []byte("value2") - - err = store.Set(key1, val1) - require.NoError(t, err) - - err = store.Set(key2, val2) - require.NoError(t, err) - - // Reading and updating the file with a new store - - store, err = newJstore(path) - require.NoError(t, err) - - resp, err := store.Get(key1) - require.NoError(t, err) - require.Equal(t, val1, resp) - - resp, err = store.Get(key2) - require.NoError(t, err) - require.Equal(t, val2, resp) - - err = store.Delete(key1) - require.NoError(t, err) - - // Reading with a 3rd store to see the update - - store, err = newJstore(path) - require.NoError(t, err) - - resp, err = store.Get(key1) - require.NoError(t, err) - require.Nil(t, resp) - - resp, err = store.Get(key2) - require.NoError(t, err) - require.Equal(t, val2, resp) -} diff --git a/dela/contracts/value/controller/controller.go b/dela/contracts/value/controller/controller.go deleted file mode 100644 index ee287ca..0000000 --- a/dela/contracts/value/controller/controller.go +++ /dev/null @@ -1,54 +0,0 @@ -package controller - -import ( - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/contracts/value" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "golang.org/x/xerrors" -) - -// aKey is the access key used for the value contract -var aKey = [32]byte{2} - -// miniController is a CLI initializer to register the value contract -// -// - implements node.Initializer -type miniController struct { -} - -// NewController creates a new minimal controller for the value contract. -func NewController() node.Initializer { - return miniController{} -} - -// SetCommands implements node.Initializer. -func (miniController) SetCommands(builder node.Builder) { -} - -// OnStart implements node.Initializer. It registers the value contract. -func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { - var access access.Service - err := inj.Resolve(&access) - if err != nil { - return xerrors.Errorf("failed to resolve access service: %v", err) - } - - var exec *native.Service - err = inj.Resolve(&exec) - if err != nil { - return xerrors.Errorf("failed to resolve native service: %v", err) - } - - contract := value.NewContract(aKey[:], access) - - value.RegisterContract(exec, contract) - - return nil -} - -// OnStop implements node.Initializer. -func (miniController) OnStop(inj node.Injector) error { - return nil -} diff --git a/dela/contracts/value/controller/controller_test.go b/dela/contracts/value/controller/controller_test.go deleted file mode 100644 index aa6721a..0000000 --- a/dela/contracts/value/controller/controller_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package controller - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" -) - -func TestSetCommands(t *testing.T) { - ctrl := NewController() - ctrl.SetCommands(nil) -} - -func TestOnStart(t *testing.T) { - ctrl := NewController() - - injector := node.NewInjector() - err := ctrl.OnStart(node.FlagSet{}, injector) - require.EqualError(t, err, "failed to resolve access service: couldn't find dependency for 'access.Service'") - - access := fakeAccess{} - injector.Inject(&access) - - err = ctrl.OnStart(node.FlagSet{}, injector) - require.EqualError(t, err, "failed to resolve native service: couldn't find dependency for '*native.Service'") - - native := native.NewExecution() - injector.Inject(native) - - err = ctrl.OnStart(node.FlagSet{}, injector) - require.NoError(t, err) -} - -func TestOnStop(t *testing.T) { - ctrl := NewController() - - err := ctrl.OnStop(nil) - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeAccess struct { - access.Service - - err error -} - -func (a fakeAccess) Grant(store.Snapshot, access.Credential, ...access.Identity) error { - return a.err -} diff --git a/dela/contracts/value/value.go b/dela/contracts/value/value.go deleted file mode 100644 index b0fb0a8..0000000 --- a/dela/contracts/value/value.go +++ /dev/null @@ -1,249 +0,0 @@ -// Package value implements a simple native contract that can store, delete, and -// display values. -package value - -import ( - "fmt" - "io" - "sort" - "strings" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "golang.org/x/xerrors" -) - -// commands defines the commands of the value contract. This interface helps in -// testing the contract. -type commands interface { - write(snap store.Snapshot, step execution.Step) error - read(snap store.Snapshot, step execution.Step) error - delete(snap store.Snapshot, step execution.Step) error - list(snap store.Snapshot) error -} - -const ( - // ContractName is the name of the contract. - ContractName = "go.dedis.ch/dela.Value" - - // KeyArg is the argument's name in the transaction that contains the - // provided key to update. - KeyArg = "value:key" - - // ValueArg is the argument's name in the transaction that contains the - // provided value to set. - ValueArg = "value:value" - - // CmdArg is the argument's name to indicate the kind of command we want to - // run on the contract. Should be one of the Command type. - CmdArg = "value:command" - - // credentialAllCommand defines the credential command that is allowed to - // perform all commands. - credentialAllCommand = "all" -) - -// Command defines a type of command for the value contract -type Command string - -const ( - // CmdWrite defines the command to write a value - CmdWrite Command = "WRITE" - - // CmdRead defines a command to read a value - CmdRead Command = "READ" - - // CmdDelete defines a command to delete a value - CmdDelete Command = "DELETE" - - // CmdList defines a command to list all values set (and not deleted) - // so far. - CmdList Command = "LIST" -) - -// NewCreds creates new credentials for a value contract execution. We might -// want to use in the future a separate credential for each command. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, credentialAllCommand) -} - -// RegisterContract registers the value contract to the given execution service. -func RegisterContract(exec *native.Service, c Contract) { - exec.Set(ContractName, c) -} - -// Contract is a simple smart contract that allows one to handle the storage by -// performing CRUD operations. -// -// - implements native.Contract -type Contract struct { - // index contains all the keys set (and not delete) by this contract so far - index map[string]struct{} - - // access is the access control service managing this smart contract - access access.Service - - // accessKey is the access identifier allowed to use this smart contract - accessKey []byte - - // cmd provides the commands that can be executed by this smart contract - cmd commands - - // printer is the output used by the READ and LIST commands - printer io.Writer -} - -// NewContract creates a new Value contract -func NewContract(aKey []byte, srvc access.Service) Contract { - contract := Contract{ - index: map[string]struct{}{}, - access: srvc, - accessKey: aKey, - printer: infoLog{}, - } - - contract.cmd = valueCommand{Contract: &contract} - - return contract -} - -// Execute implements native.Contract. It runs the appropriate command. -func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - creds := NewCreds(c.accessKey) - - err := c.access.Match(snap, creds, step.Current.GetIdentity()) - if err != nil { - return xerrors.Errorf("identity not authorized: %v (%v)", - step.Current.GetIdentity(), err) - } - - cmd := step.Current.GetArg(CmdArg) - if len(cmd) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", CmdArg) - } - - switch Command(cmd) { - case CmdWrite: - err := c.cmd.write(snap, step) - if err != nil { - return xerrors.Errorf("failed to WRITE: %v", err) - } - case CmdRead: - err := c.cmd.read(snap, step) - if err != nil { - return xerrors.Errorf("failed to READ: %v", err) - } - case CmdDelete: - err := c.cmd.delete(snap, step) - if err != nil { - return xerrors.Errorf("failed to DELETE: %v", err) - } - case CmdList: - err := c.cmd.list(snap) - if err != nil { - return xerrors.Errorf("failed to LIST: %v", err) - } - default: - return xerrors.Errorf("unknown command: %s", cmd) - } - - return nil -} - -// valueCommand implements the commands of the value contract -// -// - implements commands -type valueCommand struct { - *Contract -} - -// write implements commands. It performs the WRITE command -func (c valueCommand) write(snap store.Snapshot, step execution.Step) error { - key := step.Current.GetArg(KeyArg) - if len(key) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", KeyArg) - } - - value := step.Current.GetArg(ValueArg) - if len(value) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", ValueArg) - } - - err := snap.Set(key, value) - if err != nil { - return xerrors.Errorf("failed to set value: %v", err) - } - - c.index[string(key)] = struct{}{} - - dela.Logger.Info().Str("contract", ContractName).Msgf("setting %x=%s", key, value) - - return nil -} - -// read implements commands. It performs the READ command -func (c valueCommand) read(snap store.Snapshot, step execution.Step) error { - key := step.Current.GetArg(KeyArg) - if len(key) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", KeyArg) - } - - val, err := snap.Get(key) - if err != nil { - return xerrors.Errorf("failed to get key '%s': %v", key, err) - } - - fmt.Fprintf(c.printer, "%x=%s", key, val) - - return nil -} - -// delete implements commands. It performs the DELETE command -func (c valueCommand) delete(snap store.Snapshot, step execution.Step) error { - key := step.Current.GetArg(KeyArg) - if len(key) == 0 { - return xerrors.Errorf("'%s' not found in tx arg", KeyArg) - } - - err := snap.Delete(key) - if err != nil { - return xerrors.Errorf("failed to delete key '%x': %v", key, err) - } - - delete(c.index, string(key)) - - return nil -} - -// list implements commands. It performs the LIST command -func (c valueCommand) list(snap store.Snapshot) error { - res := []string{} - - for k := range c.index { - v, err := snap.Get([]byte(k)) - if err != nil { - return xerrors.Errorf("failed to get key '%s': %v", k, err) - } - - res = append(res, fmt.Sprintf("%x=%s", k, v)) - } - - sort.Strings(res) - fmt.Fprint(c.printer, strings.Join(res, ",")) - - return nil -} - -// infoLog defines an output using zerolog -// -// - implements io.writer -type infoLog struct{} - -func (h infoLog) Write(p []byte) (int, error) { - dela.Logger.Info().Msg(string(p)) - - return len(p), nil -} diff --git a/dela/contracts/value/value_test.go b/dela/contracts/value/value_test.go deleted file mode 100644 index d0f53eb..0000000 --- a/dela/contracts/value/value_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package value - -import ( - "bytes" - "encoding/hex" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestExecute(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{err: fake.GetError()}) - - err := contract.Execute(fakeStore{}, makeStep(t)) - require.EqualError(t, err, "identity not authorized: fake.PublicKey ("+fake.GetError().Error()+")") - - contract = NewContract([]byte{}, fakeAccess{}) - err = contract.Execute(fakeStore{}, makeStep(t)) - require.EqualError(t, err, "'value:command' not found in tx arg") - - contract.cmd = fakeCmd{err: fake.GetError()} - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "WRITE")) - require.EqualError(t, err, fake.Err("failed to WRITE")) - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "READ")) - require.EqualError(t, err, fake.Err("failed to READ")) - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "DELETE")) - require.EqualError(t, err, fake.Err("failed to DELETE")) - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "LIST")) - require.EqualError(t, err, fake.Err("failed to LIST")) - - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "fake")) - require.EqualError(t, err, "unknown command: fake") - - contract.cmd = fakeCmd{} - err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "WRITE")) - require.NoError(t, err) -} - -func TestCommand_Write(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) - - cmd := valueCommand{ - Contract: &contract, - } - - err := cmd.write(fake.NewSnapshot(), makeStep(t)) - require.EqualError(t, err, "'value:key' not found in tx arg") - - err = cmd.write(fake.NewSnapshot(), makeStep(t, KeyArg, "dummy")) - require.EqualError(t, err, "'value:value' not found in tx arg") - - err = cmd.write(fake.NewBadSnapshot(), makeStep(t, KeyArg, "dummy", ValueArg, "value")) - require.EqualError(t, err, fake.Err("failed to set value")) - - snap := fake.NewSnapshot() - - _, found := contract.index["dummy"] - require.False(t, found) - - err = cmd.write(snap, makeStep(t, KeyArg, "dummy", ValueArg, "value")) - require.NoError(t, err) - - _, found = contract.index["dummy"] - require.True(t, found) - - res, err := snap.Get([]byte("dummy")) - require.NoError(t, err) - require.Equal(t, "value", string(res)) -} - -func TestCommand_Read(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) - - cmd := valueCommand{ - Contract: &contract, - } - - key := []byte("dummy") - keyHex := hex.EncodeToString(key) - - err := cmd.read(fake.NewSnapshot(), makeStep(t)) - require.EqualError(t, err, "'value:key' not found in tx arg") - - err = cmd.read(fake.NewBadSnapshot(), makeStep(t, KeyArg, "dummy")) - require.EqualError(t, err, fake.Err("failed to get key 'dummy'")) - - snap := fake.NewSnapshot() - snap.Set(key, []byte("value")) - - buf := &bytes.Buffer{} - cmd.Contract.printer = buf - - err = cmd.read(snap, makeStep(t, KeyArg, "dummy")) - require.NoError(t, err) - - require.Equal(t, keyHex+"=value", buf.String()) -} - -func TestCommand_Delete(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) - - cmd := valueCommand{ - Contract: &contract, - } - - key := []byte("dummy") - keyHex := hex.EncodeToString(key) - keyStr := string(key) - - err := cmd.delete(fake.NewSnapshot(), makeStep(t)) - require.EqualError(t, err, "'value:key' not found in tx arg") - - err = cmd.delete(fake.NewBadSnapshot(), makeStep(t, KeyArg, keyStr)) - require.EqualError(t, err, fake.Err("failed to delete key '"+keyHex+"'")) - - snap := fake.NewSnapshot() - snap.Set(key, []byte("value")) - contract.index[keyStr] = struct{}{} - - err = cmd.delete(snap, makeStep(t, KeyArg, keyStr)) - require.NoError(t, err) - - res, err := snap.Get(key) - require.Nil(t, err) - require.Nil(t, res) - - _, found := contract.index[keyStr] - require.False(t, found) -} - -func TestCommand_List(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) - - key1 := "key1" - key2 := "key2" - - contract.index[key1] = struct{}{} - contract.index[key2] = struct{}{} - - buf := &bytes.Buffer{} - contract.printer = buf - - cmd := valueCommand{ - Contract: &contract, - } - - snap := fake.NewSnapshot() - snap.Set([]byte(key1), []byte("value1")) - snap.Set([]byte(key2), []byte("value2")) - - err := cmd.list(snap) - require.NoError(t, err) - - require.Equal(t, fmt.Sprintf("%x=value1,%x=value2", key1, key2), buf.String()) - - err = cmd.list(fake.NewBadSnapshot()) - // we can't assume an order from the map - require.Regexp(t, "^failed to get key", err.Error()) -} - -func TestInfoLog(t *testing.T) { - log := infoLog{} - - n, err := log.Write([]byte{0b0, 0b1}) - require.NoError(t, err) - require.Equal(t, 2, n) -} - -func TestRegisterContract(t *testing.T) { - RegisterContract(native.NewExecution(), Contract{}) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeStep(t *testing.T, args ...string) execution.Step { - return execution.Step{Current: makeTx(t, args...)} -} - -func makeTx(t *testing.T, args ...string) txn.Transaction { - options := []signed.TransactionOption{} - for i := 0; i < len(args)-1; i += 2 { - options = append(options, signed.WithArg(args[i], []byte(args[i+1]))) - } - - tx, err := signed.NewTransaction(0, fake.PublicKey{}, options...) - require.NoError(t, err) - - return tx -} - -type fakeAccess struct { - access.Service - - err error -} - -func (srvc fakeAccess) Match(store.Readable, access.Credential, ...access.Identity) error { - return srvc.err -} - -func (srvc fakeAccess) Grant(store.Snapshot, access.Credential, ...access.Identity) error { - return srvc.err -} - -type fakeStore struct { - store.Snapshot -} - -func (s fakeStore) Get(key []byte) ([]byte, error) { - return nil, nil -} - -func (s fakeStore) Set(key, value []byte) error { - return nil -} - -type fakeCmd struct { - err error -} - -func (c fakeCmd) write(snap store.Snapshot, step execution.Step) error { - return c.err -} - -func (c fakeCmd) read(snap store.Snapshot, step execution.Step) error { - return c.err -} - -func (c fakeCmd) delete(snap store.Snapshot, step execution.Step) error { - return c.err -} - -func (c fakeCmd) list(snap store.Snapshot) error { - return c.err -} diff --git a/dela/core/access/access.go b/dela/core/access/access.go deleted file mode 100644 index 2ae9c42..0000000 --- a/dela/core/access/access.go +++ /dev/null @@ -1,62 +0,0 @@ -// Package access defines the interfaces for Access Rights Controls. -// -// Documentation Last Review: 08.10.2020 -package access - -import ( - "encoding" - - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/serde" -) - -// Identity is an abstraction to uniquely identify a signer. -type Identity interface { - serde.Message - - encoding.TextMarshaler - - // Equal returns true when the other object is equal to the identity. - Equal(other interface{}) bool -} - -// Credential is an abstraction of an entity that allows one or several -// identities to access a given scope. -// -// As an example, the identifier is the username of a username/password pair. It -// defines the component to compare against. Then the password is the list of -// identities, verified beforehands, that will match, or won't, match to the -// identifier underlying permissions. The rule defines which scope should be -// verified so that the permissions can hold multiple of thoses. -// -// -- 0xdeadbeef -// -- "myContract:sayHello" -// -- Alice -// -- Bob -// -- "myContract:sayBye" -// -- Bob -// -// The example above shows two credentials for the contract "myContract" that is -// allowing two commands "sayHello" and "sayBye". Alice and Bob can say hello, -// but only Bob is allow to say bye. Alice can prove that she's allowed by -// providing the credential with the identifier 0xdeadbeef and the rule -// "myContract:sayHello". -type Credential interface { - // GetID returns the identifier of the credential. - GetID() []byte - - // GetRule returns the rule that is targetted by the credential. - GetRule() string -} - -// Service is an access control service that can read the storage to find -// permissions associated to the credentials, or update existing ones. -type Service interface { - // Match returns nil if the credentials can be matched to the group of - // identities. - Match(store store.Readable, creds Credential, idents ...Identity) error - - // Grant updates the store so that the group of identities will match the - // credentials. - Grant(store store.Snapshot, creds Credential, idents ...Identity) error -} diff --git a/dela/core/access/credentials.go b/dela/core/access/credentials.go deleted file mode 100644 index 23ad9b8..0000000 --- a/dela/core/access/credentials.go +++ /dev/null @@ -1,37 +0,0 @@ -// This file contains the implementation of contract credentials. -// -// Documentation Last Review: 08.10.2020 -// - -package access - -import "fmt" - -// ContractCredential defines the credential for a contract. It contains the -// name of the contract and an associated command. -type ContractCredential struct { - id []byte - contract string - command string -} - -// NewContractCreds creates new credential from the associated identifier, the -// name of the contract and its command. -func NewContractCreds(id []byte, contract, command string) ContractCredential { - return ContractCredential{ - id: id, - contract: contract, - command: command, - } -} - -// GetID implements access.Credential. It returns the identifier for the -// credential. -func (cc ContractCredential) GetID() []byte { - return append([]byte{}, cc.id...) -} - -// GetRule implements access.Credential. It returns the scope of the credential. -func (cc ContractCredential) GetRule() string { - return fmt.Sprintf("%s:%s", cc.contract, cc.command) -} diff --git a/dela/core/access/credentials_test.go b/dela/core/access/credentials_test.go deleted file mode 100644 index ddced5d..0000000 --- a/dela/core/access/credentials_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package access - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestContractCredentials_GetID(t *testing.T) { - creds := NewContractCreds([]byte{0xaa}, "contract", "cmd") - - require.Equal(t, []byte{0xaa}, creds.GetID()) -} - -func TestContractCredentials_GetRule(t *testing.T) { - creds := NewContractCreds([]byte{0xaa}, "contract", "cmd") - - require.Equal(t, "contract:cmd", creds.GetRule()) -} diff --git a/dela/core/access/darc/darc.go b/dela/core/access/darc/darc.go deleted file mode 100644 index b1d8578..0000000 --- a/dela/core/access/darc/darc.go +++ /dev/null @@ -1,95 +0,0 @@ -// Package darc implements Distributed Access Rights Controls. -// -// Documentation Last Review: 08.10.2020 -package darc - -import ( - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/access/darc/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// Service is an implementation of an access service that will allow one to -// store and verify access for a group of identities. -// -// - implements access.Service -type Service struct { - fac types.PermissionFactory - context serde.Context -} - -// NewService creates a new access service. -func NewService(ctx serde.Context) Service { - return Service{ - fac: types.NewFactory(), - context: ctx, - } -} - -// Match implements access.Service. It returns nil if the group of identities -// have access to the given credentials, otherwise a meaningful error on the -// reason if it does not have access. -func (srvc Service) Match(store store.Readable, creds access.Credential, idents ...access.Identity) error { - perm, err := srvc.readPermission(store, creds.GetID()) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - if perm == nil { - return xerrors.Errorf("permission %#x not found", creds.GetID()) - } - - err = perm.Match(creds.GetRule(), idents...) - if err != nil { - return xerrors.Errorf("permission: %v", err) - } - - return nil -} - -// Grant implements access.Service. It updates or creates the credential and -// grants the access to the group of identities. -func (srvc Service) Grant(store store.Snapshot, cred access.Credential, idents ...access.Identity) error { - perm, err := srvc.readPermission(store, cred.GetID()) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - if perm == nil { - perm = types.NewPermission() - } - - perm.Allow(cred.GetRule(), idents...) - - value, err := perm.Serialize(srvc.context) - if err != nil { - return xerrors.Errorf("failed to serialize: %v", err) - } - - err = store.Set(cred.GetID(), value) - if err != nil { - return xerrors.Errorf("store failed to write: %v", err) - } - - return nil -} - -func (srvc Service) readPermission(store store.Readable, key []byte) (types.Permission, error) { - value, err := store.Get(key) - if err != nil { - return nil, xerrors.Errorf("while reading: %v", err) - } - - if value == nil { - return nil, nil - } - - perm, err := srvc.fac.PermissionOf(srvc.context, value) - if err != nil { - return nil, xerrors.Errorf("permission malformed: %v", err) - } - - return perm, nil -} diff --git a/dela/core/access/darc/darc_test.go b/dela/core/access/darc/darc_test.go deleted file mode 100644 index f6a88b6..0000000 --- a/dela/core/access/darc/darc_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package darc - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/access/darc/types" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" -) - -var testCtx = json.NewContext() - -func TestService_Match(t *testing.T) { - store := fake.NewSnapshot() - - alice := bls.NewSigner() - bob := bls.NewSigner() - - creds := access.NewContractCreds([]byte{0xaa}, "test", "match") - - perm := types.NewPermission() - perm.Allow(creds.GetRule(), alice.GetPublicKey()) - data, err := perm.Serialize(testCtx) - require.NoError(t, err) - - store.Set([]byte{0xaa}, data) - store.Set([]byte{0xbb}, []byte{}) - - srvc := NewService(testCtx) - - err = srvc.Match(store, creds, alice.GetPublicKey()) - require.NoError(t, err) - - // Only the key of Alice is necessary, so it should pass. - err = srvc.Match(store, creds, alice.GetPublicKey(), bob.GetPublicKey()) - require.NoError(t, err) - - err = srvc.Match(store, creds, bob.GetPublicKey()) - require.Error(t, err) - require.Regexp(t, - "^permission: rule 'test:match': unauthorized: \\[bls:[[:xdigit:]]+\\]", err.Error()) - - err = srvc.Match(fake.NewBadSnapshot(), creds, alice.GetPublicKey()) - require.EqualError(t, err, fake.Err("store failed: while reading")) - - err = srvc.Match(store, access.NewContractCreds([]byte{0xcc}, "", "")) - require.EqualError(t, err, "permission 0xcc not found") - - err = srvc.Match(store, access.NewContractCreds([]byte{0xbb}, "", ""), alice.GetPublicKey()) - require.EqualError(t, err, - "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") -} - -func TestService_Grant(t *testing.T) { - store := fake.NewSnapshot() - store.Set([]byte{0xbb}, []byte{}) - - creds := access.NewContractCreds([]byte{0xaa}, "test", "grant") - - alice := bls.NewSigner() - bob := bls.NewSigner() - - srvc := NewService(testCtx) - - err := srvc.Grant(store, creds, alice.GetPublicKey()) - require.NoError(t, err) - - err = srvc.Grant(store, creds, bob.GetPublicKey()) - require.NoError(t, err) - - err = srvc.Grant(fake.NewBadSnapshot(), creds) - require.EqualError(t, err, fake.Err("store failed: while reading")) - - err = srvc.Grant(store, access.NewContractCreds([]byte{0xbb}, "", "")) - require.EqualError(t, err, - "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") - - srvc.fac = badFac{} - err = srvc.Grant(store, creds, alice.GetPublicKey()) - require.EqualError(t, err, fake.Err("failed to serialize")) - - badStore := fake.NewSnapshot() - badStore.ErrWrite = fake.GetError() - err = srvc.Grant(badStore, creds, alice.GetPublicKey()) - require.EqualError(t, err, fake.Err("store failed to write")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badFac struct { - types.PermissionFactory -} - -func (badFac) PermissionOf(serde.Context, []byte) (types.Permission, error) { - return badPerm{}, nil -} - -type badPerm struct { - types.Permission -} - -func (badPerm) Allow(string, ...access.Identity) {} - -func (badPerm) Serialize(serde.Context) ([]byte, error) { - return nil, fake.GetError() -} diff --git a/dela/core/access/darc/example_test.go b/dela/core/access/darc/example_test.go deleted file mode 100644 index ce61f03..0000000 --- a/dela/core/access/darc/example_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package darc - -import ( - "fmt" - "sync" - - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/serde/json" -) - -func ExampleService_Grant_alone() { - srvc := NewService(json.NewContext()) - - store := newStore() - - alice := bls.NewSigner() - bob := bls.NewSigner() - - credential := access.NewContractCreds([]byte{1}, "example", "hello") - - err := srvc.Grant(store, credential, alice.GetPublicKey()) - if err != nil { - panic("failed to grant alice: " + err.Error()) - } - - err = srvc.Match(store, credential, alice.GetPublicKey()) - if err != nil { - panic("alice has no access: " + err.Error()) - } else { - fmt.Println("Alice has the access") - } - - err = srvc.Match(store, credential, bob.GetPublicKey()) - if err != nil { - fmt.Println("Bob has no access") - } - - // Output: Alice has the access - // Bob has no access -} - -func ExampleService_Grant_group() { - srvc := NewService(json.NewContext()) - - store := newStore() - - alice := bls.NewSigner() - bob := bls.NewSigner() - - credential := access.NewContractCreds([]byte{1}, "example", "hello") - - err := srvc.Grant(store, credential, alice.GetPublicKey(), bob.GetPublicKey()) - if err != nil { - panic("failed to grant alice: " + err.Error()) - } - - err = srvc.Match(store, credential, alice.GetPublicKey(), bob.GetPublicKey()) - if err != nil { - panic("alice and bob have no access: " + err.Error()) - } else { - fmt.Println("[Alice, Bob] have the access") - } - - err = srvc.Match(store, credential, alice.GetPublicKey()) - if err != nil { - fmt.Println("Alice alone has no access") - } - - // Output: [Alice, Bob] have the access - // Alice alone has no access -} - -// inMemoryStore in a simple implementation of a store using an in-memory -// map. -// -// - implements store.Snapshot -type inMemoryStore struct { - sync.Mutex - - entries map[string][]byte -} - -func newStore() *inMemoryStore { - return &inMemoryStore{ - entries: make(map[string][]byte), - } -} - -// Get implements store.Readable. It returns the value associated to the key. -func (s *inMemoryStore) Get(key []byte) ([]byte, error) { - s.Lock() - defer s.Unlock() - - return s.entries[string(key)], nil -} - -// Set implements store.Writable. It sets the value for the key. -func (s *inMemoryStore) Set(key, value []byte) error { - s.Lock() - s.entries[string(key)] = value - s.Unlock() - - return nil -} - -// Delete implements store.Writable. It deletes the key from the store. -func (s *inMemoryStore) Delete(key []byte) error { - s.Lock() - delete(s.entries, string(key)) - s.Unlock() - - return nil -} diff --git a/dela/core/access/darc/json/json.go b/dela/core/access/darc/json/json.go deleted file mode 100644 index bc825c1..0000000 --- a/dela/core/access/darc/json/json.go +++ /dev/null @@ -1,155 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/access/darc/types" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - types.RegisterPermissionFormat(serde.FormatJSON, permFormat{}) -} - -// PermissionJSON is the JSON message for a permission. -type PermissionJSON struct { - Expressions map[string]ExpressionJSON -} - -// ExpressionJSON is the JSON message for an expression. -type ExpressionJSON struct { - Identities []json.RawMessage - Matches [][]int -} - -// PermFormat is the format to encode and decode permission messages. -// -// - implements serde.FormatEngine -type permFormat struct{} - -// Encode implements serde.FormatEngine. It encodes the permission message if -// appropriate, otherwise it returns an error. -func (permFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - perm, ok := msg.(*types.DisjunctivePermission) - if !ok { - return nil, xerrors.Errorf("invalid permission '%T'", msg) - } - - expressions := make(map[string]ExpressionJSON) - - for key, expr := range perm.GetRules() { - m, err := encodeExpression(ctx, expr) - if err != nil { - return nil, xerrors.Errorf("failed to encode expression: %v", err) - } - - expressions[key] = m - } - - m := PermissionJSON{Expressions: expressions} - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -func encodeExpression(ctx serde.Context, expr *types.Expression) (ExpressionJSON, error) { - - var m ExpressionJSON - identities := make(types.IdentitySet, 0) - - matches := make([][]int, len(expr.GetIdentitySets())) - - for i, match := range expr.GetIdentitySets() { - indices := make([]int, len(match)) - - for j, ident := range match { - index, found := identities.Search(ident) - if !found { - identities = append(identities, ident) - index = len(identities) - 1 - } - - indices[j] = index - } - - matches[i] = indices - } - - identitiesRaw := make([]json.RawMessage, len(identities)) - - for i, ident := range identities { - data, err := ident.Serialize(ctx) - if err != nil { - return m, xerrors.Errorf("failed to serialize identity: %v", err) - } - - identitiesRaw[i] = data - } - - m.Identities = identitiesRaw - m.Matches = matches - - return m, nil -} - -// Decode implements serde.FormatEngine. It populates the permission from the -// data if appropriate, otherwise it returns an error. -func (permFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := PermissionJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - opts := make([]types.PermissionOption, 0, len(m.Expressions)) - - for rule, raw := range m.Expressions { - expr, err := decodeExpr(ctx, raw) - if err != nil { - return nil, xerrors.Errorf("failed to decode expression: %v", err) - } - - opts = append(opts, types.WithExpression(rule, expr)) - } - - return types.NewPermission(opts...), nil -} - -func decodeExpr(ctx serde.Context, m ExpressionJSON) (*types.Expression, error) { - fac := ctx.GetFactory(types.PublicKeyFac{}) - - factory, ok := fac.(common.PublicKeyFactory) - if !ok { - return nil, xerrors.Errorf("invalid public key factory '%T'", fac) - } - - identities := make([]access.Identity, len(m.Identities)) - - for i, raw := range m.Identities { - pubkey, err := factory.PublicKeyOf(ctx, raw) - if err != nil { - return nil, xerrors.Errorf("public key: %v", err) - } - - identities[i] = pubkey - } - - matches := make([]types.IdentitySet, len(m.Matches)) - - for i, indices := range m.Matches { - matches[i] = make(types.IdentitySet, len(indices)) - - for j, index := range indices { - matches[i][j] = identities[index] - } - } - - return types.NewExpression(matches...), nil -} diff --git a/dela/core/access/darc/json/json_test.go b/dela/core/access/darc/json/json_test.go deleted file mode 100644 index e8e1576..0000000 --- a/dela/core/access/darc/json/json_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access/darc/types" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -const testValue = `{"Expressions":{"test":{"Identities":[{}],"Matches":[[0]]}}}` - -func TestPermFormat_Encode(t *testing.T) { - fmt := permFormat{} - - ctx := fake.NewContext() - - perm := types.NewPermission(types.WithRule("test", fake.PublicKey{})) - - data, err := fmt.Encode(ctx, perm) - require.NoError(t, err) - require.Equal(t, testValue, string(data)) - - _, err = fmt.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "invalid permission 'fake.Message'") - - _, err = fmt.Encode(fake.NewBadContext(), perm) - require.EqualError(t, err, fake.Err("failed to marshal")) - - perm = types.NewPermission(types.WithRule("test", fake.NewBadPublicKey())) - _, err = fmt.Encode(ctx, perm) - require.EqualError(t, err, fake.Err("failed to encode expression: failed to serialize identity")) -} - -func TestPermFormat_Decode(t *testing.T) { - fmt := permFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.PublicKeyFac{}, fake.PublicKeyFactory{}) - - msg, err := fmt.Decode(ctx, []byte(testValue)) - require.NoError(t, err) - require.Equal(t, types.NewPermission(types.WithRule("test", fake.PublicKey{})), msg) - - _, err = fmt.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - badCtx := serde.WithFactory(ctx, types.PublicKeyFac{}, nil) - _, err = fmt.Decode(badCtx, []byte(testValue)) - require.EqualError(t, err, "failed to decode expression: invalid public key factory ''") - - badCtx = serde.WithFactory(ctx, types.PublicKeyFac{}, fake.NewBadPublicKeyFactory()) - _, err = fmt.Decode(badCtx, []byte(testValue)) - require.EqualError(t, err, fake.Err("failed to decode expression: public key")) -} diff --git a/dela/core/access/darc/types/expr.go b/dela/core/access/darc/types/expr.go deleted file mode 100644 index ac688ae..0000000 --- a/dela/core/access/darc/types/expr.go +++ /dev/null @@ -1,126 +0,0 @@ -// -// Documentation Last Review: 12.10.2020 -// - -package types - -import ( - "go.dedis.ch/dela/core/access" - "golang.org/x/xerrors" -) - -// IdentitySet is a set of identities that belongs to one of the conjunction. -type IdentitySet []access.Identity - -// NewIdentitySet creates a new identity set from the list of identities by -// removing duplicates. -func NewIdentitySet(idents ...access.Identity) IdentitySet { - set := make(IdentitySet, 0, len(idents)) - if len(idents) == 0 { - return set - } - - for _, ident := range idents { - if !set.Contains(ident) { - set = append(set, ident) - } - } - - return set[:] -} - -// Contains returns true if the identity exists in the set. -func (set IdentitySet) Contains(target access.Identity) bool { - _, found := set.Search(target) - return found -} - -// Search searches for the target in the set and returns the index if it exists, -// otherwise a negative value. -func (set IdentitySet) Search(target access.Identity) (int, bool) { - for i, ident := range set { - if ident.Equal(target) { - return i, true - } - } - - return -1, false -} - -// IsSuperset return true if both sets are the same. -func (set IdentitySet) IsSuperset(o IdentitySet) bool { - if len(set) < len(o) { - return false - } - - for _, ident := range o { - if !set.Contains(ident) { - return false - } - } - - return true -} - -// Expression is the representation of the disjunctive normal form of the -// allowed groups of identities. -type Expression struct { - matches []IdentitySet -} - -// NewExpression creates a new expression from the list of identity sets. -func NewExpression(sets ...IdentitySet) *Expression { - return &Expression{ - matches: sets, - } -} - -// GetIdentitySets returns the list of identity sets. -func (expr *Expression) GetIdentitySets() []IdentitySet { - return append([]IdentitySet{}, expr.matches...) -} - -// Allow adds the group of identities as long as there is no duplicate. -func (expr *Expression) Allow(group []access.Identity) { - iset := NewIdentitySet(group...) - if len(iset) == 0 { - return - } - - for _, match := range expr.matches { - if match.IsSuperset(iset) { - // The group is already allowed. - return - } - } - - expr.matches = append(expr.matches, iset) -} - -// Deny removes the group of identities from the list of matching subsets. -func (expr *Expression) Deny(group []access.Identity) { - iset := NewIdentitySet(group...) - if len(iset) == 0 { - return - } - - for i, match := range expr.matches { - if iset.IsSuperset(match) { - expr.matches = append(expr.matches[:i], expr.matches[i+1:]...) - } - } -} - -// Match returns nil if the group are allowed for the rule, otherwise it returns -// the reason why it failed. -func (expr *Expression) Match(group []access.Identity) error { - iset := NewIdentitySet(group...) - - for _, match := range expr.matches { - if iset.IsSuperset(match) { - return nil - } - } - - return xerrors.Errorf("unauthorized: %v", group) -} diff --git a/dela/core/access/darc/types/expr_test.go b/dela/core/access/darc/types/expr_test.go deleted file mode 100644 index 8412b0a..0000000 --- a/dela/core/access/darc/types/expr_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package types - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" -) - -func TestIdentitySet_New(t *testing.T) { - iset := NewIdentitySet(newIdentity("A"), newIdentity("B"), newIdentity("A")) - require.Len(t, iset, 2) -} - -func TestIdentitySet_Search(t *testing.T) { - iset := NewIdentitySet(newIdentity("A"), newIdentity("B")) - - require.True(t, iset.Contains(newIdentity("A"))) - require.True(t, iset.Contains(newIdentity("B"))) - require.False(t, iset.Contains(newIdentity("C"))) -} - -func TestIdentitySet_IsSuperset(t *testing.T) { - iset := NewIdentitySet(newIdentity("A"), newIdentity("C")) - - require.True(t, iset.IsSuperset(iset)) - require.True(t, iset.IsSuperset(NewIdentitySet(newIdentity("A")))) - require.True(t, iset.IsSuperset(NewIdentitySet())) - require.False(t, iset.IsSuperset(NewIdentitySet(newIdentity("A"), newIdentity("B")))) - require.False(t, iset.IsSuperset(NewIdentitySet(newIdentity("B")))) -} - -func TestExpression_GetIdentitySets(t *testing.T) { - expr := Expression{ - matches: []IdentitySet{{}, {}}, - } - - require.Len(t, expr.GetIdentitySets(), 2) -} - -func TestExpression_Allow(t *testing.T) { - expr := NewExpression() - - expr.Allow(nil) - require.Len(t, expr.matches, 0) - - idents := []access.Identity{newIdentity("A"), newIdentity("B")} - - expr.Allow(idents) - require.Len(t, expr.matches, 1) - require.Len(t, expr.matches[0], 2) - - expr.Allow(idents) - require.Len(t, expr.matches, 1) - - expr.Allow([]access.Identity{newIdentity("A"), newIdentity("C")}) - require.Len(t, expr.matches, 2) -} - -func TestExpression_Deny(t *testing.T) { - expr := NewExpression() - expr.matches = []IdentitySet{ - NewIdentitySet(newIdentity("A"), newIdentity("B")), - NewIdentitySet(newIdentity("C")), - } - - expr.Deny(nil) - require.Len(t, expr.matches, 2) - - expr.Deny([]access.Identity{newIdentity("A")}) - require.Len(t, expr.matches, 2) - - expr.Deny([]access.Identity{newIdentity("A"), newIdentity("B")}) - require.Len(t, expr.matches, 1) -} - -func TestExpression_Match(t *testing.T) { - idents := []access.Identity{newIdentity("A"), newIdentity("B")} - - expr := NewExpression() - expr.Allow(idents) - - err := expr.Match(idents) - require.NoError(t, err) - - err = expr.Match([]access.Identity{newIdentity("A"), newIdentity("C")}) - require.EqualError(t, err, "unauthorized: ['A' 'C']") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeIdentity struct { - access.Identity - - buffer []byte -} - -func newIdentity(value string) fakeIdentity { - return fakeIdentity{buffer: []byte(value)} -} - -func (i fakeIdentity) String() string { - return fmt.Sprintf("'%s'", i.buffer) -} - -func (i fakeIdentity) Equal(o interface{}) bool { - other, ok := o.(fakeIdentity) - return ok && bytes.Equal(i.buffer, other.buffer) -} diff --git a/dela/core/access/darc/types/permission.go b/dela/core/access/darc/types/permission.go deleted file mode 100644 index e1af273..0000000 --- a/dela/core/access/darc/types/permission.go +++ /dev/null @@ -1,174 +0,0 @@ -// -// Documentation Last Review: 12.10.2020 -// - -package types - -import ( - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var permFormats = registry.NewSimpleRegistry() - -// RegisterPermissionFormat registers the engine for the provided format. -func RegisterPermissionFormat(c serde.Format, f serde.FormatEngine) { - permFormats.Register(c, f) -} - -// DisjunctivePermission is a permission implementation that is using the -// Disjunctive Normal Form to represent the groups of identities allowed for a -// given rule. -// -// - implements types.Permission -type DisjunctivePermission struct { - rules map[string]*Expression -} - -// PermissionOption is the option type to create an access control. -type PermissionOption func(*DisjunctivePermission) - -// WithRule is an option to grant a given group access to a rule. -func WithRule(rule string, group ...access.Identity) PermissionOption { - return func(perm *DisjunctivePermission) { - perm.Allow(rule, group...) - } -} - -// WithExpression is an option to set a rule from its expression. -func WithExpression(rule string, expr *Expression) PermissionOption { - return func(perm *DisjunctivePermission) { - perm.rules[rule] = expr - } -} - -// NewPermission returns a new empty instance of an access control. -func NewPermission(opts ...PermissionOption) *DisjunctivePermission { - a := &DisjunctivePermission{ - rules: make(map[string]*Expression), - } - - for _, opt := range opts { - opt(a) - } - - return a -} - -// GetRules returns a map of the expressions. -func (perm *DisjunctivePermission) GetRules() map[string]*Expression { - rules := make(map[string]*Expression) - - for rule, expr := range perm.rules { - rules[rule] = expr - } - - return rules -} - -// Allow implements types.Permission. It grants the permission to the group of -// identities as a single entity. -func (perm *DisjunctivePermission) Allow(rule string, group ...access.Identity) { - expr, ok := perm.rules[rule] - if !ok { - expr = NewExpression() - } - - expr.Allow(group) - - perm.rules[rule] = expr -} - -// Deny implements types.Permission. It denies the permission to the group of -// identities as a single entity by removing every subset matching this -// superset. -func (perm *DisjunctivePermission) Deny(rule string, group ...access.Identity) { - expr, ok := perm.rules[rule] - if !ok { - return - } - - expr.Deny(group) - - // Clean the the rule if it was the last group allowed. - if len(expr.matches) == 0 { - delete(perm.rules, rule) - } -} - -// Match implements types.Permission. It returns true if the rule exists and the -// group of identities is associated with it. -func (perm *DisjunctivePermission) Match(rule string, group ...access.Identity) error { - if len(group) == 0 { - return xerrors.New("expect at least one identity") - } - - expr, ok := perm.rules[rule] - if !ok { - return xerrors.Errorf("rule '%s' not found", rule) - } - - err := expr.Match(group) - if err != nil { - return xerrors.Errorf("rule '%s': %v", rule, err) - } - - return nil -} - -// Serialize implements serde.Message. It looks up the format and returns the -// serialized data of the permission. -func (perm *DisjunctivePermission) Serialize(ctx serde.Context) ([]byte, error) { - format := permFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, perm) - if err != nil { - return nil, xerrors.Errorf("couldn't encode access: %v", err) - } - - return data, nil -} - -// PublicKeyFac is the key of the public key factory. -type PublicKeyFac struct{} - -// permFac is the implementation of a permission factory. -// -// - implements types.PermissionFactory -type permFac struct { - fac common.PublicKeyFactory -} - -// NewFactory returns a new instance of the factory. -func NewFactory() PermissionFactory { - return permFac{ - fac: common.NewPublicKeyFactory(), - } -} - -// Deserialize implements serde.Factory. -func (f permFac) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.PermissionOf(ctx, data) -} - -// PermissionOf implements types.PermissionFactory. -func (f permFac) PermissionOf(ctx serde.Context, data []byte) (Permission, error) { - format := permFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, PublicKeyFac{}, f.fac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("%v format: %v", ctx.GetFormat(), err) - } - - access, ok := msg.(Permission) - if !ok { - return nil, xerrors.Errorf("invalid access '%T'", msg) - } - - return access, nil -} diff --git a/dela/core/access/darc/types/permission_test.go b/dela/core/access/darc/types/permission_test.go deleted file mode 100644 index a540f28..0000000 --- a/dela/core/access/darc/types/permission_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/internal/testing/fake" -) - -func init() { - RegisterPermissionFormat(fake.GoodFormat, fake.Format{Msg: &DisjunctivePermission{}}) - RegisterPermissionFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterPermissionFormat(fake.MsgFormat, fake.NewMsgFormat()) -} - -func TestPermission_WithRule(t *testing.T) { - perm := NewPermission(WithRule("A", newIdentity("AA"), newIdentity("BB"))) - - require.Len(t, perm.rules, 1) - require.Len(t, perm.rules["A"].matches, 1) - require.Len(t, perm.rules["A"].matches[0], 2) -} - -func TestPermission_WithExpression(t *testing.T) { - perm := NewPermission(WithExpression("test", NewExpression())) - require.Len(t, perm.rules, 1) -} - -func TestPermission_GetRules(t *testing.T) { - perm := NewPermission(WithRule("A", newIdentity("a")), WithRule("B", newIdentity("b"))) - - require.Len(t, perm.GetRules(), 2) -} - -func TestPermission_Allow(t *testing.T) { - perm := NewPermission() - - idents := []access.Identity{ - fakeIdentity{buffer: []byte{0xaa}}, - fakeIdentity{buffer: []byte{0xbb}}, - } - - perm.Allow("fake", idents...) - require.Len(t, perm.rules, 1) - - perm.Allow("another", idents...) - require.Len(t, perm.rules, 2) - - perm.Allow("fake") - require.Len(t, perm.rules, 2) - - perm.Deny("fake", idents...) - require.Len(t, perm.rules, 1) - - perm.Deny("fake", idents...) - require.Len(t, perm.rules, 1) -} - -func TestPermission_Deny(t *testing.T) { - perm := NewPermission() - perm.rules["fake"] = NewExpression(NewIdentitySet(newIdentity("A"))) - - perm.Deny("fake") - require.Len(t, perm.rules, 1) - - perm.Deny("fake", newIdentity("B")) - require.Len(t, perm.rules, 1) - - perm.Deny("fake", newIdentity("A")) - require.Len(t, perm.rules, 0) -} - -func TestPermission_Match(t *testing.T) { - idents := []access.Identity{ - fakeIdentity{buffer: []byte{0xaa}}, - fakeIdentity{buffer: []byte{0xbb}}, - } - - perm := NewPermission() - perm.Allow("fake", idents...) - - err := perm.Match("fake", idents...) - require.NoError(t, err) - - err = perm.Match("fake") - require.EqualError(t, err, "expect at least one identity") - - err = perm.Match("unknown", idents...) - require.EqualError(t, err, "rule 'unknown' not found") - - err = perm.Match("fake", newIdentity("C")) - require.EqualError(t, err, "rule 'fake': unauthorized: ['C']") -} - -func TestPermission_Serialize(t *testing.T) { - perm := NewPermission() - - data, err := perm.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = perm.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode access")) -} - -func TestPermissionFactory_Deserialize(t *testing.T) { - factory := NewFactory() - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.IsType(t, &DisjunctivePermission{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("FakeBad format")) - - _, err = factory.Deserialize(fake.NewMsgContext(), nil) - require.EqualError(t, err, "invalid access 'fake.Message'") -} diff --git a/dela/core/access/darc/types/types.go b/dela/core/access/darc/types/types.go deleted file mode 100644 index a8e0933..0000000 --- a/dela/core/access/darc/types/types.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package types implements the darc messages. -// -// The messages are implemented in a separate package to prevent cycle imports -// when importing the serde formats. -// -// Documentation Last Review: 08.10.2020 -package types - -import ( - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/serde" -) - -// Permission is the interface of the underlying permissions used by the -// service. -type Permission interface { - serde.Message - - // Allow grants the permission to the rule to the group of identities as a - // single entity so that it will match if and only if the group agrees. - Allow(rule string, group ...access.Identity) - - // Deny denies the permission to the rule to the group of identities as a - // single entity. - Deny(rule string, group ...access.Identity) - - // Match returns a nil error if the group, or a subset of the group, is - // allowed. - Match(rule string, group ...access.Identity) error -} - -// PermissionFactory is the factory to serialize and deserialize the -// permissions. -type PermissionFactory interface { - serde.Factory - - PermissionOf(serde.Context, []byte) (Permission, error) -} diff --git a/dela/core/execution/execution.go b/dela/core/execution/execution.go deleted file mode 100644 index 3f54cfc..0000000 --- a/dela/core/execution/execution.go +++ /dev/null @@ -1,36 +0,0 @@ -// Package execution defines the service to execute a step in a validation -// batch. -// -// Documentation Last Review: 08.10.2020 -package execution - -import ( - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" -) - -// Step is a context of execution. It allows for example a smart contract to -// execute a given transaction knowing what previous transactions have already -// been accepted and executed in a block. -type Step struct { - Previous []txn.Transaction - Current txn.Transaction -} - -// Result is the result of a transaction execution. -type Result struct { - // Accepted is the success state of the transaction. - Accepted bool - - // Message gives a change to the execution to explain why a transaction has - // failed. - Message string -} - -// Service is the execution service that defines the primitives to execute a -// transaction. -type Service interface { - // Execute must apply the transaction to the trie and return the result of - // it. - Execute(snap store.Snapshot, step Step) (Result, error) -} diff --git a/dela/core/execution/native/example_test.go b/dela/core/execution/native/example_test.go deleted file mode 100644 index 5fcd47d..0000000 --- a/dela/core/execution/native/example_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package native - -import ( - "encoding/binary" - "fmt" - "sync" - - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto/bls" -) - -func ExampleService_Execute() { - srvc := NewExecution() - srvc.Set("example", exampleContract{}) - - store := newStore() - signer := bls.NewSigner() - - increment := make([]byte, 8) - binary.LittleEndian.PutUint64(increment, 5) - - opts := []signed.TransactionOption{ - signed.WithArg("increment", increment), - signed.WithArg(ContractArg, []byte("example")), - } - - tx, err := signed.NewTransaction(0, signer.GetPublicKey(), opts...) - if err != nil { - panic("failed to create transaction: " + err.Error()) - } - - step := execution.Step{ - Current: tx, - } - - for i := 0; i < 2; i++ { - res, err := srvc.Execute(store, step) - if err != nil { - panic("failed to execute: " + err.Error()) - } - - if res.Accepted { - fmt.Println("accepted") - } - } - - value, err := store.Get([]byte("counter")) - if err != nil { - panic("store failed: " + err.Error()) - } - - fmt.Println(binary.LittleEndian.Uint64(value)) - - // Output: accepted - // accepted - // 10 -} - -// exampleContract is an example contract that reads a counter value in the -// store and increase it with the increment in the transaction. -// -// - implements native.Contract -type exampleContract struct{} - -// Execute implements native.Contract. It increases the counter with the -// increment in the transaction. -func (exampleContract) Execute(store store.Snapshot, step execution.Step) error { - value, err := store.Get([]byte("counter")) - if err != nil { - return err - } - - counter := uint64(0) - if len(value) == 8 { - counter = binary.LittleEndian.Uint64(value) - } - - incr := binary.LittleEndian.Uint64(step.Current.GetArg("increment")) - - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer, counter+incr) - - err = store.Set([]byte("counter"), buffer) - if err != nil { - return err - } - - return nil -} - -// inMemoryStore in a simple implementation of a store using an in-memory -// map. -// -// - implements store.Snapshot -type inMemoryStore struct { - sync.Mutex - - entries map[string][]byte -} - -func newStore() *inMemoryStore { - return &inMemoryStore{ - entries: make(map[string][]byte), - } -} - -// Get implements store.Readable. It returns the value associated to the key. -func (s *inMemoryStore) Get(key []byte) ([]byte, error) { - s.Lock() - defer s.Unlock() - - return s.entries[string(key)], nil -} - -// Set implements store.Writable. It sets the value for the key. -func (s *inMemoryStore) Set(key, value []byte) error { - s.Lock() - s.entries[string(key)] = value - s.Unlock() - - return nil -} - -// Delete implements store.Writable. It deletes the key from the store. -func (s *inMemoryStore) Delete(key []byte) error { - s.Lock() - delete(s.entries, string(key)) - s.Unlock() - - return nil -} diff --git a/dela/core/execution/native/native.go b/dela/core/execution/native/native.go deleted file mode 100644 index f9b5c23..0000000 --- a/dela/core/execution/native/native.go +++ /dev/null @@ -1,68 +0,0 @@ -// Package native implements an execution service to run native smart contracts. -// -// A native smart contract is written in Go and packaged with the application. -// -// Documentation Last Review: 08.10.2020 -package native - -import ( - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/store" - "golang.org/x/xerrors" -) - -const ( - // ContractArg is the argument key in the transaction to look up a contract. - ContractArg = "go.dedis.ch/dela.ContractArg" -) - -// Contract is the interface to implement to register a smart contract that will -// be executed natively. -type Contract interface { - Execute(store.Snapshot, execution.Step) error -} - -// Service is an execution service for packaged applications. Those -// applications have complete access to the trie and can directly update it. -// -// - implements execution.Service -type Service struct { - contracts map[string]Contract -} - -// NewExecution returns a new native execution. The given service will be -// executed for every incoming transaction. -func NewExecution() *Service { - return &Service{ - contracts: map[string]Contract{}, - } -} - -// Set stores the contract using the name as the key. A transaction can trigger -// this contract by using the same name as the contract argument. -func (ns *Service) Set(name string, contract Contract) { - ns.contracts[name] = contract -} - -// Execute implements execution.Service. It uses the executor to process the -// incoming transaction and return the result. -func (ns *Service) Execute(snap store.Snapshot, step execution.Step) (execution.Result, error) { - name := string(step.Current.GetArg(ContractArg)) - - contract := ns.contracts[name] - if contract == nil { - return execution.Result{}, xerrors.Errorf("unknown contract '%s'", name) - } - - res := execution.Result{ - Accepted: true, - } - - err := contract.Execute(snap, step) - if err != nil { - res.Accepted = false - res.Message = err.Error() - } - - return res, nil -} diff --git a/dela/core/execution/native/native_test.go b/dela/core/execution/native/native_test.go deleted file mode 100644 index 54f0be7..0000000 --- a/dela/core/execution/native/native_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package native - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestService_Execute(t *testing.T) { - srvc := NewExecution() - srvc.Set("abc", fakeExec{}) - srvc.Set("bad", fakeExec{err: fake.GetError()}) - - step := execution.Step{} - step.Current = fakeTx{contract: "abc"} - - res, err := srvc.Execute(nil, step) - require.NoError(t, err) - require.Equal(t, execution.Result{Accepted: true}, res) - - step.Current = fakeTx{contract: "bad"} - res, err = srvc.Execute(nil, step) - require.NoError(t, err) - require.Equal(t, execution.Result{Message: fake.GetError().Error()}, res) - - step.Current = fakeTx{contract: "none"} - _, err = srvc.Execute(nil, step) - require.EqualError(t, err, "unknown contract 'none'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeExec struct { - err error -} - -func (e fakeExec) Execute(store.Snapshot, execution.Step) error { - return e.err -} - -type fakeTx struct { - txn.Transaction - contract string -} - -func (tx fakeTx) GetArg(key string) []byte { - return []byte(tx.contract) -} diff --git a/dela/core/ordering/cosipbft/authority/authority.go b/dela/core/ordering/cosipbft/authority/authority.go deleted file mode 100644 index f4b5e13..0000000 --- a/dela/core/ordering/cosipbft/authority/authority.go +++ /dev/null @@ -1,56 +0,0 @@ -// Package authority defines the collective authority for cosipbft. -// -// The package also contains an implementation of a roster and the related -// change set. A roster is a list of participants where each of them has an Mino -// address and a corresponding public key that supports aggregation for the -// collective signing. -// -// Documentation Last Review: 13.10.2020 -package authority - -import ( - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -// ChangeSet is the return of a diff between two authorities. -type ChangeSet interface { - serde.Message - - // NumChanges returns the number of changes that will be applied with this - // change set. - NumChanges() int - - // GetNewAddresses returns the list of addresses for the new members. - GetNewAddresses() []mino.Address -} - -// ChangeSetFactory is the factory to deserialize change sets. -type ChangeSetFactory interface { - serde.Factory - - ChangeSetOf(serde.Context, []byte) (ChangeSet, error) -} - -// Authority is an extension of the collective authority to provide primitives -// to append new players to it. -type Authority interface { - serde.Message - serde.Fingerprinter - crypto.CollectiveAuthority - - // Apply must apply the change set to the collective authority. It should - // first remove, then add the new players. - Apply(ChangeSet) Authority - - // Diff should return the change set to apply to get the given authority. - Diff(Authority) ChangeSet -} - -// Factory is the factory to deserialize authorities. -type Factory interface { - serde.Factory - - AuthorityOf(serde.Context, []byte) (Authority, error) -} diff --git a/dela/core/ordering/cosipbft/authority/changeset.go b/dela/core/ordering/cosipbft/authority/changeset.go deleted file mode 100644 index 4f5875e..0000000 --- a/dela/core/ordering/cosipbft/authority/changeset.go +++ /dev/null @@ -1,130 +0,0 @@ -// This file contains the implementation of the change set. -// -// Documentation Last Review: 13.10.2020 -// - -package authority - -import ( - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var csetFormats = registry.NewSimpleRegistry() - -// RegisterChangeSetFormat registers the engine for the provided format. -func RegisterChangeSetFormat(c serde.Format, f serde.FormatEngine) { - csetFormats.Register(c, f) -} - -// RosterChangeSet is the smallest data model to update an authority to another. -// -// - implements authority.ChangeSet -type RosterChangeSet struct { - remove []uint - addrs []mino.Address - pubkeys []crypto.PublicKey -} - -// NewChangeSet creates a new empty change set. -func NewChangeSet() *RosterChangeSet { - return &RosterChangeSet{} -} - -// GetPublicKeys returns the list of public keys of the new participants. -func (set *RosterChangeSet) GetPublicKeys() []crypto.PublicKey { - return append([]crypto.PublicKey{}, set.pubkeys...) -} - -// GetNewAddresses implements authority.ChangeSet. It returns the list of -// addresses of the new members. -func (set *RosterChangeSet) GetNewAddresses() []mino.Address { - return append([]mino.Address{}, set.addrs...) -} - -// GetRemoveIndices returns the list of indices to remove from the authority. -func (set *RosterChangeSet) GetRemoveIndices() []uint { - return append([]uint{}, set.remove...) -} - -// Remove appends the index to the list of removals. -func (set *RosterChangeSet) Remove(index uint) { - set.remove = append(set.remove, index) -} - -// Add appends the address and the public key to the list of new participants. -func (set *RosterChangeSet) Add(addr mino.Address, pubkey crypto.PublicKey) { - set.addrs = append(set.addrs, addr) - set.pubkeys = append(set.pubkeys, pubkey) -} - -// NumChanges implements authority.ChangeSet. It returns the number of changes -// that is applied with the change set. -func (set *RosterChangeSet) NumChanges() int { - return len(set.remove) + len(set.addrs) -} - -// Serialize implements serde.Message. It returns the serialized data for this -// change set. -func (set *RosterChangeSet) Serialize(ctx serde.Context) ([]byte, error) { - format := csetFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, set) - if err != nil { - return nil, xerrors.Errorf("couldn't encode change set: %v", err) - } - - return data, nil -} - -// PubKeyFac is the key for the public key factory. -type PubKeyFac struct{} - -// AddrKeyFac is the key for the address factory. -type AddrKeyFac struct{} - -// SimpleChangeSetFactory is a message factory to deserialize a change set. -// -// - roster.ChangeSetFactory -type SimpleChangeSetFactory struct { - addrFactory mino.AddressFactory - pubkeyFactory crypto.PublicKeyFactory -} - -// NewChangeSetFactory returns a new change set factory. -func NewChangeSetFactory(af mino.AddressFactory, pkf crypto.PublicKeyFactory) ChangeSetFactory { - return SimpleChangeSetFactory{ - addrFactory: af, - pubkeyFactory: pkf, - } -} - -// Deserialize implements serde.Factory. It returns the change set from the data -// if appropriate, otherwise an error. -func (f SimpleChangeSetFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.ChangeSetOf(ctx, data) -} - -// ChangeSetOf implements roster.ChangeSetFactory. It returns the change set -// from the data if appropriate, otherwise an error. -func (f SimpleChangeSetFactory) ChangeSetOf(ctx serde.Context, data []byte) (ChangeSet, error) { - format := csetFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, PubKeyFac{}, f.pubkeyFactory) - ctx = serde.WithFactory(ctx, AddrKeyFac{}, f.addrFactory) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode change set: %v", err) - } - - cset, ok := msg.(ChangeSet) - if !ok { - return nil, xerrors.Errorf("invalid message of type '%T'", msg) - } - - return cset, nil -} diff --git a/dela/core/ordering/cosipbft/authority/changeset_test.go b/dela/core/ordering/cosipbft/authority/changeset_test.go deleted file mode 100644 index 349711a..0000000 --- a/dela/core/ordering/cosipbft/authority/changeset_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package authority - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterChangeSetFormat(fake.GoodFormat, fake.Format{Msg: NewChangeSet()}) - RegisterChangeSetFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterChangeSetFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestChangeSet_GetPublicKeys(t *testing.T) { - cset := NewChangeSet() - require.Len(t, cset.GetPublicKeys(), 0) - - cset.Add(fake.NewAddress(0), fake.PublicKey{}) - require.Len(t, cset.GetPublicKeys(), 1) -} - -func TestChangeSet_GetNewAddresses(t *testing.T) { - cset := NewChangeSet() - require.Len(t, cset.GetNewAddresses(), 0) - - cset.Add(fake.NewAddress(0), fake.PublicKey{}) - require.Len(t, cset.GetNewAddresses(), 1) -} - -func TestChangeSet_GetRemoveIndices(t *testing.T) { - cset := NewChangeSet() - require.Len(t, cset.GetRemoveIndices(), 0) - - cset.Remove(5) - require.Len(t, cset.GetRemoveIndices(), 1) -} - -func TestChangeSet_NumChanges(t *testing.T) { - cset := NewChangeSet() - require.Equal(t, 0, cset.NumChanges()) - - cset.Remove(5) - require.Equal(t, 1, cset.NumChanges()) - - cset.Add(fake.NewAddress(0), fake.PublicKey{}) - require.Equal(t, 2, cset.NumChanges()) -} - -func TestChangeSet_Serialize(t *testing.T) { - cset := RosterChangeSet{} - - data, err := cset.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = cset.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode change set")) -} - -func TestChangeSetFactory_Deserialize(t *testing.T) { - factory := NewChangeSetFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, NewChangeSet(), msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode change set")) - - _, err = factory.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid message of type 'fake.Message'") -} diff --git a/dela/core/ordering/cosipbft/authority/json/json.go b/dela/core/ordering/cosipbft/authority/json/json.go deleted file mode 100644 index 48fd385..0000000 --- a/dela/core/ordering/cosipbft/authority/json/json.go +++ /dev/null @@ -1,212 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - authority.RegisterChangeSetFormat(serde.FormatJSON, changeSetFormat{}) - authority.RegisterRosterFormat(serde.FormatJSON, rosterFormat{}) -} - -// Player is a JSON message that contains the address and the public key of a -// new participant. -type Player struct { - Address []byte - PublicKey json.RawMessage -} - -// ChangeSet is a JSON message of the change set of an authority. -type ChangeSet struct { - Remove []uint - Addresses [][]byte - PublicKeys []json.RawMessage -} - -// Address is a JSON message for an address. -type Address []byte - -// Roster is a JSON message for a authority. -type Roster []Player - -// ChangeSetFormat is the engine to encode and decode change set messages in -// JSON format. -// -// - implements serde.FormatEngine -type changeSetFormat struct{} - -// Encode implements serde.FormatEngine. It returns the data serialized for the -// change set message if appropriate, otherwise an error. -func (f changeSetFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - cset, ok := msg.(*authority.RosterChangeSet) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - addrs := make([][]byte, 0) - for _, addr := range cset.GetNewAddresses() { - raw, err := addr.MarshalText() - if err != nil { - return nil, xerrors.Errorf("couldn't serialize address: %v", err) - } - - addrs = append(addrs, raw) - } - - pubkeys := make([]json.RawMessage, 0) - for _, pubkey := range cset.GetPublicKeys() { - raw, err := pubkey.Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize public key: %v", err) - } - - pubkeys = append(pubkeys, raw) - } - - m := ChangeSet{ - Remove: cset.GetRemoveIndices(), - Addresses: addrs, - PublicKeys: pubkeys, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the message with the JSON -// data if appropriate, otherwise it returns an error. -func (f changeSetFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := ChangeSet{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize change set: %v", err) - } - - factory := ctx.GetFactory(authority.PubKeyFac{}) - - pkFac, ok := factory.(crypto.PublicKeyFactory) - if !ok { - return nil, xerrors.Errorf("invalid public key factory of type '%T'", factory) - } - - factory = ctx.GetFactory(authority.AddrKeyFac{}) - - addrFac, ok := factory.(mino.AddressFactory) - if !ok { - return nil, xerrors.Errorf("invalid address factory of type '%T'", factory) - } - - cset := authority.NewChangeSet() - - for _, index := range m.Remove { - cset.Remove(index) - } - - for i, rawAddr := range m.Addresses { - addr := addrFac.FromText(rawAddr) - - pubkey, err := pkFac.PublicKeyOf(ctx, m.PublicKeys[i]) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize public key: %v", err) - } - - cset.Add(addr, pubkey) - } - - return cset, nil -} - -// RosterFormat is the engine to encode and decode roster messages in JSON -// format. -// -// - implements serde.FormatEngine -type rosterFormat struct{} - -// Encode implements serde.FormatEngine. It returns the data serialized for the -// roster message if appropriate, otherwise an error. -func (f rosterFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - roster, ok := msg.(authority.Roster) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - players := make([]Player, roster.Len()) - - addrIter := roster.AddressIterator() - pkIter := roster.PublicKeyIterator() - for i := 0; addrIter.HasNext() && pkIter.HasNext(); i++ { - addr, err := addrIter.GetNext().MarshalText() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal address: %v", err) - } - - pubkey, err := pkIter.GetNext().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize public key: %v", err) - } - - players[i] = Player{ - Address: addr, - PublicKey: pubkey, - } - } - - m := Roster(players) - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the roster with the JSON -// data if appropriate, otherwise it returns an error. -func (f rosterFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := Roster{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize roster: %v", err) - } - - factory := ctx.GetFactory(authority.PubKeyFac{}) - - pkFac, ok := factory.(crypto.PublicKeyFactory) - if !ok { - return nil, xerrors.Errorf("invalid public key factory of type '%T'", factory) - } - - factory = ctx.GetFactory(authority.AddrKeyFac{}) - - addrFac, ok := factory.(mino.AddressFactory) - if !ok { - return nil, xerrors.Errorf("invalid address factory of type '%T'", factory) - } - - addrs := make([]mino.Address, len(m)) - pubkeys := make([]crypto.PublicKey, len(m)) - - for i, player := range m { - addrs[i] = addrFac.FromText(player.Address) - - pubkey, err := pkFac.PublicKeyOf(ctx, player.PublicKey) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize public key: %v", err) - } - - pubkeys[i] = pubkey - } - - return authority.New(addrs, pubkeys), nil -} diff --git a/dela/core/ordering/cosipbft/authority/json/json_test.go b/dela/core/ordering/cosipbft/authority/json/json_test.go deleted file mode 100644 index ba9678c..0000000 --- a/dela/core/ordering/cosipbft/authority/json/json_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -func TestChangeSetFormat_Encode(t *testing.T) { - cset := authority.NewChangeSet() - cset.Remove(42) - cset.Add(fake.NewAddress(2), fake.PublicKey{}) - - format := changeSetFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - - data, err := format.Encode(ctx, cset) - require.NoError(t, err) - expected := `{"Remove":[42],"Addresses":["AgAAAA=="],"PublicKeys":[{}]}` - require.Equal(t, expected, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), cset) - require.EqualError(t, err, fake.Err("couldn't marshal")) - - cset = authority.NewChangeSet() - cset.Add(fake.NewAddress(0), fake.NewBadPublicKey()) - _, err = format.Encode(ctx, cset) - require.EqualError(t, err, fake.Err("couldn't serialize public key")) - - cset = authority.NewChangeSet() - cset.Add(fake.NewBadAddress(), fake.PublicKey{}) - _, err = format.Encode(ctx, cset) - require.EqualError(t, err, fake.Err("couldn't serialize address")) -} - -func TestChangeSetFormat_Decode(t *testing.T) { - format := changeSetFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - ctx = serde.WithFactory(ctx, authority.AddrKeyFac{}, fake.AddressFactory{}) - ctx = serde.WithFactory(ctx, authority.PubKeyFac{}, fake.PublicKeyFactory{}) - - cset := authority.NewChangeSet() - cset.Add(fake.NewAddress(0), fake.PublicKey{}) - - msg, err := format.Decode(ctx, []byte(`{"Addresses":[[]],"PublicKeys":[{}]}`)) - require.NoError(t, err) - require.Equal(t, cset, msg) - - cset = authority.NewChangeSet() - cset.Remove(1) - cset.Remove(2) - cset.Remove(3) - - msg, err = format.Decode(ctx, []byte(`{"Remove":[1,2,3]}`)) - require.NoError(t, err) - require.Equal(t, cset, msg) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize change set")) - - badCtx := serde.WithFactory(ctx, authority.PubKeyFac{}, fake.NewBadPublicKeyFactory()) - _, err = format.Decode(badCtx, []byte(`{"Addresses":[[]],"PublicKeys":[{}]}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize public key")) - - badCtx = serde.WithFactory(ctx, authority.AddrKeyFac{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Addresses":[[]],"PublicKeys":[{}]}`)) - require.EqualError(t, err, "invalid address factory of type ''") - - badCtx = serde.WithFactory(ctx, authority.PubKeyFac{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Addresses":[[]],"PublicKeys":[{}]}`)) - require.EqualError(t, err, "invalid public key factory of type ''") -} - -func TestRosterFormat_Encode(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(1, fake.NewSigner)) - - format := rosterFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - - data, err := format.Encode(ctx, ro) - require.NoError(t, err) - require.Equal(t, `[{"Address":"AAAAAA==","PublicKey":{}}]`, string(data)) - - _, err = format.Encode(fake.NewContext(), fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), ro) - require.EqualError(t, err, fake.Err("couldn't marshal")) - - ro = authority.New([]mino.Address{fake.NewBadAddress()}, nil) - _, err = format.Encode(ctx, ro) - require.EqualError(t, err, fake.Err("couldn't marshal address")) - - ro = authority.New([]mino.Address{fake.NewAddress(0)}, []crypto.PublicKey{fake.NewBadPublicKey()}) - _, err = format.Encode(ctx, ro) - require.EqualError(t, err, fake.Err("couldn't serialize public key")) -} - -func TestRosterFormat_Decode(t *testing.T) { - format := rosterFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - ctx = serde.WithFactory(ctx, authority.AddrKeyFac{}, fake.AddressFactory{}) - ctx = serde.WithFactory(ctx, authority.PubKeyFac{}, fake.PublicKeyFactory{}) - - ro, err := format.Decode(ctx, []byte(`[{}]`)) - require.NoError(t, err) - require.Equal(t, authority.FromAuthority(fake.NewAuthority(1, fake.NewSigner)), ro) - - _, err = format.Decode(fake.NewBadContext(), []byte(`[]`)) - require.EqualError(t, err, fake.Err("couldn't deserialize roster")) - - badCtx := serde.WithFactory(ctx, authority.PubKeyFac{}, fake.NewBadPublicKeyFactory()) - _, err = format.Decode(badCtx, []byte(`[{}]`)) - require.EqualError(t, err, fake.Err("couldn't deserialize public key")) - - badCtx = serde.WithFactory(ctx, authority.AddrKeyFac{}, nil) - _, err = format.Decode(badCtx, []byte(`[{}]`)) - require.EqualError(t, err, "invalid address factory of type ''") - - badCtx = serde.WithFactory(ctx, authority.PubKeyFac{}, nil) - _, err = format.Decode(badCtx, []byte(`[{}]`)) - require.EqualError(t, err, "invalid public key factory of type ''") -} diff --git a/dela/core/ordering/cosipbft/authority/roster.go b/dela/core/ordering/cosipbft/authority/roster.go deleted file mode 100644 index a060c99..0000000 --- a/dela/core/ordering/cosipbft/authority/roster.go +++ /dev/null @@ -1,303 +0,0 @@ -// This file contains the implementation of a collective authority. -// -// Documentation Last Review: 13.10.2020 -// - -package authority - -import ( - "io" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var rosterFormats = registry.NewSimpleRegistry() - -// RegisterRosterFormat registers the engine for the provided format. -func RegisterRosterFormat(c serde.Format, f serde.FormatEngine) { - rosterFormats.Register(c, f) -} - -// iterator is a generic implementation of an iterator over a list of conodes. -type iterator struct { - index int - roster *Roster -} - -func (i *iterator) Seek(index int) { - i.index = index -} - -func (i *iterator) HasNext() bool { - return i.index < i.roster.Len() -} - -func (i *iterator) GetNext() int { - res := i.index - i.index++ - return res -} - -// addressIterator is an iterator for a list of addresses. -// -// - implements mino.AddressIterator -type addressIterator struct { - *iterator -} - -// GetNext implements mino.AddressIterator. It returns the next address. -func (i *addressIterator) GetNext() mino.Address { - if i.iterator.HasNext() { - return i.roster.addrs[i.iterator.GetNext()] - } - return nil -} - -// publicKeyIterator is an iterator for a list of public keys. -// -// - implements crypto.PublicKeyIterator -type publicKeyIterator struct { - *iterator -} - -// GetNext implements crypto.PublicKeyIterator. It returns the next public key. -func (i *publicKeyIterator) GetNext() crypto.PublicKey { - if i.iterator.HasNext() { - return i.roster.pubkeys[i.iterator.GetNext()] - } - return nil -} - -// Roster contains a list of participants with their addresses and public keys. -// -// - implements authority.Authority -type Roster struct { - addrs []mino.Address - pubkeys []crypto.PublicKey -} - -// New creates a new roster from the list of addresses and public keys. -func New(addrs []mino.Address, pubkeys []crypto.PublicKey) Roster { - return Roster{ - addrs: addrs, - pubkeys: pubkeys, - } -} - -// FromAuthority returns a viewchange roster from a collective authority. -func FromAuthority(authority crypto.CollectiveAuthority) Roster { - addrs := make([]mino.Address, authority.Len()) - pubkeys := make([]crypto.PublicKey, authority.Len()) - - addrIter := authority.AddressIterator() - pubkeyIter := authority.PublicKeyIterator() - for i := 0; addrIter.HasNext() && pubkeyIter.HasNext(); i++ { - addrs[i] = addrIter.GetNext() - pubkeys[i] = pubkeyIter.GetNext() - } - - return New(addrs, pubkeys) -} - -// Fingerprint implements serde.Fingerprinter. It marshals the roster and writes -// the result in the given writer. -func (r Roster) Fingerprint(w io.Writer) error { - for i, addr := range r.addrs { - data, err := addr.MarshalText() - if err != nil { - return xerrors.Errorf("couldn't marshal address: %v", err) - } - - _, err = w.Write(data) - if err != nil { - return xerrors.Errorf("couldn't write address: %v", err) - } - - data, err = r.pubkeys[i].MarshalBinary() - if err != nil { - return xerrors.Errorf("couldn't marshal public key: %v", err) - } - - _, err = w.Write(data) - if err != nil { - return xerrors.Errorf("couldn't write public key: %v", err) - } - } - - return nil -} - -// Take implements mino.Players. It returns a subset of the roster according to -// the filter. -func (r Roster) Take(updaters ...mino.FilterUpdater) mino.Players { - filter := mino.ApplyFilters(updaters) - newRoster := Roster{ - addrs: make([]mino.Address, len(filter.Indices)), - pubkeys: make([]crypto.PublicKey, len(filter.Indices)), - } - - for i, k := range filter.Indices { - newRoster.addrs[i] = r.addrs[k] - newRoster.pubkeys[i] = r.pubkeys[k] - } - - return newRoster -} - -// Apply implements authority.Authority. It returns a new authority after -// applying the change set. The removals must be sorted by descending order and -// unique or the behaviour will be undefined. -func (r Roster) Apply(in ChangeSet) Authority { - changeset, ok := in.(*RosterChangeSet) - if !ok { - dela.Logger.Warn().Msgf("Change set '%T' is not supported. Ignoring.", in) - return r - } - - addrs := make([]mino.Address, r.Len()) - pubkeys := make([]crypto.PublicKey, r.Len()) - - for i, addr := range r.addrs { - addrs[i] = addr - pubkeys[i] = r.pubkeys[i] - } - - for _, i := range changeset.remove { - if int(i) < len(addrs) { - addrs = append(addrs[:i], addrs[i+1:]...) - pubkeys = append(pubkeys[:i], pubkeys[i+1:]...) - } - } - - roster := Roster{ - addrs: append(addrs, changeset.addrs...), - pubkeys: append(pubkeys, changeset.pubkeys...), - } - - return roster -} - -// Diff implements authority.Authority. It returns the change set that must be -// applied to the current authority to get the given one. -func (r Roster) Diff(o Authority) ChangeSet { - changeset := NewChangeSet() - - other, ok := o.(Roster) - if !ok { - return changeset - } - - i := 0 - k := 0 - for i < len(r.addrs) || k < len(other.addrs) { - if i < len(r.addrs) && k < len(other.addrs) { - if r.addrs[i].Equal(other.addrs[k]) { - i++ - k++ - } else { - changeset.remove = append(changeset.remove, uint(i)) - i++ - } - } else if i < len(r.addrs) { - changeset.remove = append(changeset.remove, uint(i)) - i++ - } else { - changeset.addrs = append(changeset.addrs, other.addrs[k]) - changeset.pubkeys = append(changeset.pubkeys, other.pubkeys[k]) - k++ - } - } - - return changeset -} - -// Len implements mino.Players. It returns the length of the authority. -func (r Roster) Len() int { - return len(r.addrs) -} - -// GetPublicKey implements crypto.CollectiveAuthority. It returns the public key -// of the address if it exists, nil otherwise. The second return is the index of -// the public key in the authority. -func (r Roster) GetPublicKey(target mino.Address) (crypto.PublicKey, int) { - for i, addr := range r.addrs { - if addr.Equal(target) { - return r.pubkeys[i], i - } - } - - return nil, -1 -} - -// AddressIterator implements mino.Players. It returns an iterator of the -// addresses of the roster in a deterministic order. -func (r Roster) AddressIterator() mino.AddressIterator { - return &addressIterator{iterator: &iterator{roster: &r}} -} - -// PublicKeyIterator implements crypto.CollectiveAuthority. It returns an -// iterator of the public keys of the roster in a deterministic order. -func (r Roster) PublicKeyIterator() crypto.PublicKeyIterator { - return &publicKeyIterator{iterator: &iterator{roster: &r}} -} - -// Serialize implements serde.Message. It returns the serialized data for this -// roster. -func (r Roster) Serialize(ctx serde.Context) ([]byte, error) { - format := rosterFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, r) - if err != nil { - return nil, xerrors.Errorf("couldn't encode roster: %v", err) - } - - return data, nil -} - -// rosterFac is a factory to deserialize authority. -// -// - implements authority.Factory -type rosterFac struct { - addrFactory mino.AddressFactory - pubkeyFactory crypto.PublicKeyFactory -} - -// NewFactory creates a new instance of the authority factory. -func NewFactory(af mino.AddressFactory, pf crypto.PublicKeyFactory) Factory { - return rosterFac{ - addrFactory: af, - pubkeyFactory: pf, - } -} - -// Deserialize implements serde.Factory. It returns the roster from the data if -// appropriate, otherwise an error. -func (f rosterFac) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.AuthorityOf(ctx, data) -} - -// AuthorityOf implements authority.AuthorityFactory. It returns the roster from -// the data if appropriate, otherwise an error. -func (f rosterFac) AuthorityOf(ctx serde.Context, data []byte) (Authority, error) { - format := rosterFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, PubKeyFac{}, f.pubkeyFactory) - ctx = serde.WithFactory(ctx, AddrKeyFac{}, f.addrFactory) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode roster: %v", err) - } - - roster, ok := msg.(Roster) - if !ok { - return nil, xerrors.Errorf("invalid message of type '%T'", msg) - } - - return roster, nil -} diff --git a/dela/core/ordering/cosipbft/authority/roster_test.go b/dela/core/ordering/cosipbft/authority/roster_test.go deleted file mode 100644 index 0919825..0000000 --- a/dela/core/ordering/cosipbft/authority/roster_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package authority - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterRosterFormat(fake.GoodFormat, fake.Format{Msg: Roster{}}) - RegisterRosterFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterRosterFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestIterator_Seek(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - iter := &iterator{ - roster: &roster, - } - - iter.Seek(2) - require.True(t, iter.HasNext()) - iter.Seek(3) - require.False(t, iter.HasNext()) -} - -func TestIterator_HasNext(t *testing.T) { - iter := &iterator{ - roster: &Roster{addrs: make([]mino.Address, 3)}, - } - - require.True(t, iter.HasNext()) - - iter.index = 1 - require.True(t, iter.HasNext()) - - iter.index = 2 - require.True(t, iter.HasNext()) - - iter.index = 3 - require.False(t, iter.HasNext()) - - iter.index = 10 - require.False(t, iter.HasNext()) -} - -func TestIterator_GetNext(t *testing.T) { - iter := &iterator{ - roster: &Roster{addrs: make([]mino.Address, 3)}, - } - - for i := 0; i < 3; i++ { - c := iter.GetNext() - require.NotNil(t, c) - } - - require.Equal(t, 3, iter.GetNext()) -} - -func TestAddressIterator_GetNext(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - iter := &addressIterator{ - iterator: &iterator{ - roster: &roster, - }, - } - - for _, target := range roster.addrs { - addr := iter.GetNext() - require.Equal(t, target, addr) - } - - require.Nil(t, iter.GetNext()) -} - -func TestPublicKeyIterator_GetNext(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - iter := &publicKeyIterator{ - iterator: &iterator{ - roster: &roster, - }, - } - - for _, target := range roster.pubkeys { - pubkey := iter.GetNext() - require.Equal(t, target, pubkey) - } - - require.Nil(t, iter.GetNext()) -} - -func TestRoster_Fingerprint(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(2, fake.NewSigner)) - - out := new(bytes.Buffer) - err := roster.Fingerprint(out) - require.NoError(t, err) - require.Equal(t, "\x00\x00\x00\x00PK\x01\x00\x00\x00PK", out.String()) - - roster.addrs[0] = fake.NewBadAddress() - err = roster.Fingerprint(out) - require.EqualError(t, err, fake.Err("couldn't marshal address")) - - roster.addrs[0] = fake.NewAddress(0) - roster.pubkeys[0] = fake.NewBadPublicKey() - err = roster.Fingerprint(out) - require.EqualError(t, err, fake.Err("couldn't marshal public key")) - - roster.pubkeys[0] = fake.PublicKey{} - err = roster.Fingerprint(fake.NewBadHash()) - require.EqualError(t, err, fake.Err("couldn't write address")) - - err = roster.Fingerprint(fake.NewBadHashWithDelay(1)) - require.EqualError(t, err, fake.Err("couldn't write public key")) -} - -func TestRoster_Take(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - roster2 := roster.Take(mino.RangeFilter(1, 2)) - require.Equal(t, 1, roster2.Len()) - - roster2 = roster.Take(mino.RangeFilter(1, 3)) - require.Equal(t, 2, roster2.Len()) -} - -func TestRoster_Apply(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - require.Equal(t, roster, roster.Apply(nil)) - - cset := NewChangeSet() - cset.Remove(3) - cset.Remove(2) - cset.Remove(0) - - roster2 := roster.Apply(cset) - require.Equal(t, roster.Len()-2, roster2.Len()) - - cset = NewChangeSet() - cset.Add(fake.NewAddress(5), fake.PublicKey{}) - - roster3 := roster2.Apply(cset) - require.Equal(t, roster.Len()-1, roster3.Len()) -} - -func TestRoster_Diff(t *testing.T) { - roster1 := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - roster2 := FromAuthority(fake.NewAuthority(4, fake.NewSigner)) - diff := roster1.Diff(roster2).(*RosterChangeSet) - require.Len(t, diff.addrs, 1) - require.Len(t, diff.pubkeys, 1) - - roster3 := FromAuthority(fake.NewAuthority(2, fake.NewSigner)) - diff = roster1.Diff(roster3).(*RosterChangeSet) - require.Len(t, diff.remove, 1) - - roster4 := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - roster4.addrs[1] = fake.NewAddress(5) - diff = roster1.Diff(roster4).(*RosterChangeSet) - require.Equal(t, []uint{1, 2}, diff.remove) - require.Len(t, diff.addrs, 2) - require.Len(t, diff.pubkeys, 2) - - diff = roster1.Diff((Authority)(nil)).(*RosterChangeSet) - require.Equal(t, NewChangeSet(), diff) -} - -func TestRoster_Len(t *testing.T) { - roster := FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - require.Equal(t, 3, roster.Len()) -} - -func TestRoster_GetPublicKey(t *testing.T) { - authority := fake.NewAuthority(3, fake.NewSigner) - roster := FromAuthority(authority) - - iter := roster.AddressIterator() - i := 0 - for iter.HasNext() { - pubkey, index := roster.GetPublicKey(iter.GetNext()) - require.Equal(t, authority.GetSigner(i).GetPublicKey(), pubkey) - require.Equal(t, i, index) - i++ - } - - pubkey, index := roster.GetPublicKey(fake.NewAddress(999)) - require.Equal(t, -1, index) - require.Nil(t, pubkey) -} - -func TestRoster_AddressIterator(t *testing.T) { - authority := fake.NewAuthority(3, fake.NewSigner) - roster := FromAuthority(authority) - - iter := roster.AddressIterator() - for i := 0; iter.HasNext(); i++ { - require.Equal(t, authority.GetAddress(i), iter.GetNext()) - } -} - -func TestRoster_PublicKeyIterator(t *testing.T) { - authority := fake.NewAuthority(3, bls.Generate) - roster := FromAuthority(authority) - - iter := roster.PublicKeyIterator() - for i := 0; iter.HasNext(); i++ { - require.Equal(t, authority.GetSigner(i).GetPublicKey(), iter.GetNext()) - } -} - -func TestRoster_Serialize(t *testing.T) { - roster := Roster{} - - data, err := roster.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = roster.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode roster")) -} - -func TestFactory_Deserialize(t *testing.T) { - factory := NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, Roster{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode roster")) - - _, err = factory.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid message of type 'fake.Message'") -} diff --git a/dela/core/ordering/cosipbft/blockstore/blockstore.go b/dela/core/ordering/cosipbft/blockstore/blockstore.go deleted file mode 100644 index c002c24..0000000 --- a/dela/core/ordering/cosipbft/blockstore/blockstore.go +++ /dev/null @@ -1,94 +0,0 @@ -// Package blockstore defines the different storage the ordering service is -// using. -// -// The block store defines the primitives to store a block and read one from the -// disk. It also provide an API to read a chain from the genesis block to the -// latest block. It is important to notice that a block is stored alongside the -// link that has been created during the consensus. -// -// The tree cache stores the latest state of the tree, which is modified after -// each new block. -// -// The genesis store allows to set a definitive genesis block and persist it so -// that it can be reloaded later on. -// -// Documentation Last Review: 13.10.2020 -package blockstore - -import ( - "context" - "errors" - - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" -) - -// ErrNoBlock is the error message returned when the block is unknown. -var ErrNoBlock = errors.New("no block") - -// TreeCache is a cache to store a tree that needs to be accessed in different -// places. -type TreeCache interface { - // Get returns the current value of the cache. - Get() hashtree.Tree - - // GetWithLock implements blockstore.TreeCache. It returns the current value - // of the cache alongside a function to unlock the cache. It allows one to - // delay a set while fetching associated data. The function returned must be - // called. - GetWithLock() (tree hashtree.Tree, unlock func()) - - // Set sets a new tree in the cache. - Set(hashtree.Tree) - - // SetWithLock implements blockstore.TreeCache. It sets the tree while - // holding the lock and returns a function to unlock it. It allows one to - // prevent an access until associated data is updated. The function returned - // must be called. - SetWithLock(hashtree.Tree) (unlock func()) -} - -// GenesisStore is the interface to store and get the genesis block. It is left -// to the implementation to persist it. -type GenesisStore interface { - // Get must return the genesis block if it is set, otherwise an error. - Get() (types.Genesis, error) - - // Set must set the genesis block. - Set(types.Genesis) error - - // Exists returns true if the genesis is already set. - Exists() bool -} - -// BlockStore is the interface to store and get blocks. -type BlockStore interface { - // Len must return the length of the store. - Len() uint64 - - // Store must store the block link only if it matches the latest link, - // otherwise it must return an error. - Store(types.BlockLink) error - - // Get must return the block link associated to the digest, or an error. - Get(id types.Digest) (types.BlockLink, error) - - // GetByIndex return the block link associated to the index, or an error. - GetByIndex(index uint64) (types.BlockLink, error) - - // GetChain returns a chain of the blocks. It can be used to prove the - // integrity of the last block from the genesis. - GetChain() (types.Chain, error) - - // Last must return the latest block link in the store. - Last() (types.BlockLink, error) - - // Watch returns a channel that is filled with new block links. The is - // closed as soon as the context is done. - Watch(context.Context) <-chan types.BlockLink - - // WithTx returns a block store that is using the transaction to perform - // operations on the database. - WithTx(store.Transaction) BlockStore -} diff --git a/dela/core/ordering/cosipbft/blockstore/disk.go b/dela/core/ordering/cosipbft/blockstore/disk.go deleted file mode 100644 index d07c503..0000000 --- a/dela/core/ordering/cosipbft/blockstore/disk.go +++ /dev/null @@ -1,312 +0,0 @@ -// This file contains the implementation of a persistent block store. It stores -// the blocks and their links to a key/value database. -// -// Documentation Last Review: 13.10.2020 -// - -package blockstore - -import ( - "context" - "encoding/binary" - "sync" - - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -type cachedData struct { - sync.Mutex - - length uint64 - last types.BlockLink - indices map[types.Digest]uint64 -} - -// InDisk is a persistent storage implementation for the blocks. -// -// - implements blockstore.BlockStore -type InDisk struct { - *cachedData - - db kv.DB - bucket []byte - context serde.Context - fac types.LinkFactory - watcher core.Observable - - txn store.Transaction -} - -// NewDiskStore creates a new persistent storage. -func NewDiskStore(db kv.DB, fac types.LinkFactory) *InDisk { - return &InDisk{ - db: db, - bucket: []byte("blocks"), - context: json.NewContext(), - fac: fac, - watcher: core.NewWatcher(), - cachedData: &cachedData{ - indices: make(map[types.Digest]uint64), - }, - } -} - -// Len implements blockstore.BlockStore. It returns the number of blocks stored -// in the database. -func (s *InDisk) Len() uint64 { - s.Lock() - defer s.Unlock() - - return s.length -} - -// Load reads the database to rebuild the cache. -func (s *InDisk) Load() error { - s.Lock() - defer s.Unlock() - - return s.doView(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(s.bucket) - if bucket == nil { - return nil - } - - err := bucket.Scan([]byte{}, func(key, value []byte) error { - link, err := s.fac.BlockLinkOf(s.context, value) - if err != nil { - return xerrors.Errorf("malformed block: %v", err) - } - - s.length++ - s.last = link - s.indices[link.GetBlock().GetHash()] = link.GetBlock().GetIndex() - - return nil - }) - - if err != nil { - return xerrors.Errorf("while scanning: %v", err) - } - - return nil - }) -} - -// Store implements blockstore.BlockStore. It stores the link in the database if -// it matches the latest link. -func (s *InDisk) Store(link types.BlockLink) error { - s.Lock() - last := s.last - s.Unlock() - - if last != nil && last.GetTo() != link.GetFrom() { - return xerrors.Errorf("mismatch digests '%v' (new) != '%v' (last)", - link.GetFrom(), last.GetTo()) - } - - data, err := link.Serialize(s.context) - if err != nil { - return xerrors.Errorf("failed to serialize: %v", err) - } - - return s.doUpdate(func(tx kv.WritableTx) error { - bucket, err := tx.GetBucketOrCreate(s.bucket) - if err != nil { - return xerrors.Errorf("bucket failed: %v", err) - } - - index := link.GetBlock().GetIndex() - - key := s.makeKey(index) - - err = bucket.Set(key, data) - if err != nil { - return xerrors.Errorf("while writing: %v", err) - } - - tx.OnCommit(func() { - s.Lock() - - s.length++ - s.last = link - s.indices[link.GetBlock().GetHash()] = index - - s.Unlock() - - s.watcher.Notify(link) - }) - - return nil - }) -} - -// Get implements blockstore.BlockStore. It loads the block with the given -// identifier if it exists, otherwise it returns an error. -func (s *InDisk) Get(id types.Digest) (types.BlockLink, error) { - s.Lock() - index, found := s.indices[id] - s.Unlock() - - if !found { - return nil, xerrors.Errorf("'%v' not found: %w", id, ErrNoBlock) - } - - return s.GetByIndex(index) -} - -// GetByIndex implements blockstore.BlockStore. It returns the block associated -// to the index if it exists, otherwise it returns an error. -func (s *InDisk) GetByIndex(index uint64) (link types.BlockLink, err error) { - key := s.makeKey(index) - - err = s.doView(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(s.bucket) - - value := bucket.Get(key) - - if len(value) == 0 { - return xerrors.Errorf("index %d not found: %w", index, ErrNoBlock) - } - - var err error - link, err = s.fac.BlockLinkOf(s.context, value) - if err != nil { - return xerrors.Errorf("malformed block: %v", err) - } - - return nil - }) - - return -} - -// GetChain implements blockstore.Blockstore. It returns a chain to the latest -// block. -func (s *InDisk) GetChain() (types.Chain, error) { - s.Lock() - length := s.length - s.Unlock() - - if length == 0 { - return nil, xerrors.New("store is empty") - } - - prevs := make([]types.Link, length-1) - - var chain types.Chain - - err := s.doView(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(s.bucket) - - i := uint64(0) - err := bucket.Scan([]byte{}, func(key, value []byte) error { - if i >= length-1 { - link, err := s.fac.BlockLinkOf(s.context, value) - if err != nil { - return xerrors.Errorf("block malformed: %v", err) - } - - chain = types.NewChain(link, prevs) - return nil - } - - link, err := s.fac.LinkOf(s.context, value) - if err != nil { - return xerrors.Errorf("link malformed: %v", err) - } - - prevs[i] = link - i++ - - return nil - }) - - if err != nil { - return xerrors.Errorf("while scanning: %v", err) - } - - return nil - }) - - if err != nil { - return nil, xerrors.Errorf("while reading database: %v", err) - } - - return chain, nil -} - -// Last implements blockstore.BlockStore. It returns the last block stored in -// the database. -func (s *InDisk) Last() (types.BlockLink, error) { - s.Lock() - defer s.Unlock() - - if s.length == 0 { - return nil, xerrors.Errorf("store is empty: %w", ErrNoBlock) - } - - return s.last, nil -} - -// Watch implements blockstore.BlockStore. It returns a channel populated with -// new blocks stored. -func (s *InDisk) Watch(ctx context.Context) <-chan types.BlockLink { - obs := newObserver(ctx, s.watcher) - - return obs.ch -} - -// WithTx implements blockstore.BlockStore. It returns a store that will use the -// transaction for the operations on the database. -func (s *InDisk) WithTx(txn store.Transaction) BlockStore { - store := &InDisk{ - db: s.db, - bucket: s.bucket, - context: s.context, - fac: s.fac, - watcher: s.watcher, - cachedData: s.cachedData, - txn: txn, - } - - return store -} - -func (s *InDisk) doUpdate(fn func(tx kv.WritableTx) error) error { - if s.txn != nil { - tx, ok := s.txn.(kv.WritableTx) - if !ok { - return xerrors.Errorf("transaction '%T' is not writable", s.txn) - } - - return fn(tx) - } - - return s.db.Update(fn) -} - -func (s *InDisk) doView(fn func(tx kv.ReadableTx) error) error { - if s.txn != nil { - tx, ok := s.txn.(kv.ReadableTx) - if !ok { - return xerrors.Errorf("transaction '%T' is not readable", s.txn) - } - - return fn(tx) - } - - return s.db.View(fn) -} - -func (s *InDisk) makeKey(index uint64) []byte { - key := make([]byte, 8) - binary.LittleEndian.PutUint64(key, index) - - return key -} diff --git a/dela/core/ordering/cosipbft/blockstore/disk_test.go b/dela/core/ordering/cosipbft/blockstore/disk_test.go deleted file mode 100644 index 6ac6c70..0000000 --- a/dela/core/ordering/cosipbft/blockstore/disk_test.go +++ /dev/null @@ -1,299 +0,0 @@ -package blockstore - -import ( - "context" - "os" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestInDisk_Len(t *testing.T) { - store := NewDiskStore(nil, nil) - require.Equal(t, uint64(0), store.Len()) - - store.length = 5 - require.Equal(t, uint64(5), store.Len()) -} - -func TestInDisk_Load(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - err := store.Load() - require.NoError(t, err) - - err = store.Store(makeLink(t, types.Digest{})) - require.NoError(t, err) - - err = store.Store(makeLink(t, store.last.GetTo())) - require.NoError(t, err) - - newStore := NewDiskStore(db, makeBlockFac()) - - err = newStore.Load() - require.NoError(t, err) - - store.fac = badLinkFac{} - err = store.Load() - require.EqualError(t, err, fake.Err("while scanning: malformed block")) -} - -func TestInDisk_Store(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - err := store.Store(makeLink(t, types.Digest{}, types.WithIndex(0))) - require.NoError(t, err) - require.Equal(t, uint64(1), store.length) - require.NotNil(t, store.last) - require.Len(t, store.indices, 1) - - err = store.Store(makeLink(t, store.last.GetTo(), types.WithIndex(1))) - require.NoError(t, err) - require.Equal(t, uint64(2), store.length) - require.Len(t, store.indices, 2) - - err = store.Store(makeLink(t, types.Digest{})) - require.EqualError(t, err, "mismatch digests '00000000' (new) != 'b68f5931' (last)") - - store = NewDiskStore(db, makeBlockFac()) - err = store.Store(badLink{}) - require.EqualError(t, err, fake.Err("failed to serialize")) - - store.db = badDB{} - err = store.Store(makeLink(t, types.Digest{})) - require.EqualError(t, err, fake.Err("bucket failed")) - - store.db = badDB{bucket: badBucket{}} - err = store.Store(makeLink(t, types.Digest{})) - require.EqualError(t, err, fake.Err("while writing")) -} - -func TestInDisk_Get(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - link, err := store.Get(types.Digest{}) - require.EqualError(t, err, "'00000000' not found: no block") - require.Nil(t, link) - - err = store.Store(makeLink(t, types.Digest{}, types.WithIndex(2))) - require.NoError(t, err) - - link, err = store.Get(store.last.GetTo()) - require.NoError(t, err) - require.Equal(t, uint64(2), link.GetBlock().GetIndex()) -} - -func TestInDisk_GetByIndex(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - err := store.Store(makeLink(t, types.Digest{}, types.WithIndex(0))) - require.NoError(t, err) - - err = store.Store(makeLink(t, store.last.GetTo(), types.WithIndex(1))) - require.NoError(t, err) - - link, err := store.GetByIndex(1) - require.NoError(t, err) - require.Equal(t, uint64(1), link.GetBlock().GetIndex()) - - _, err = store.GetByIndex(3) - require.EqualError(t, err, "index 3 not found: no block") - - store.fac = badLinkFac{} - _, err = store.GetByIndex(0) - require.EqualError(t, err, fake.Err("malformed block")) -} - -func TestInDisk_GetChain(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - _, err := store.GetChain() - require.EqualError(t, err, "store is empty") - - err = store.Store(makeLink(t, types.Digest{}, types.WithIndex(0))) - require.NoError(t, err) - - err = store.Store(makeLink(t, store.last.GetTo(), types.WithIndex(1))) - require.NoError(t, err) - - chain, err := store.GetChain() - require.NoError(t, err) - require.Equal(t, uint64(1), chain.GetBlock().GetIndex()) - - store.fac = badLinkFac{} - _, err = store.GetChain() - require.EqualError(t, err, fake.Err("while reading database: while scanning: link malformed")) -} - -func TestInDisk_GetChain_BadBlockLink(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - err := store.Store(makeLink(t, types.Digest{}, types.WithIndex(0))) - require.NoError(t, err) - - store.fac = badLinkFac{} - _, err = store.GetChain() - require.EqualError(t, err, fake.Err("while reading database: while scanning: block malformed")) -} - -func TestInDisk_Last(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - last, err := store.Last() - require.EqualError(t, err, "store is empty: no block") - require.Nil(t, last) - - store.length = 1 - store.last = makeLink(t, types.Digest{}) - last, err = store.Last() - require.NoError(t, err) - require.NotNil(t, last) -} - -func TestInDisk_Watch(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - links := store.Watch(ctx) - - err := store.Store(makeLink(t, types.Digest{}, types.WithIndex(2))) - require.NoError(t, err) - - link := <-links - require.Equal(t, uint64(2), link.GetBlock().GetIndex()) - - cancel() - _, more := <-links - require.False(t, more) -} - -func TestInDisk_WithTx(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - store := NewDiskStore(db, makeBlockFac()) - - err := db.Update(func(tx kv.WritableTx) error { - next := store.WithTx(tx) - err := next.Store(makeLink(t, types.Digest{})) - require.NoError(t, err) - - link, err := next.GetByIndex(0) - require.NoError(t, err) - require.NotNil(t, link) - - return nil - }) - require.NoError(t, err) - require.Equal(t, uint64(1), store.length) - - next := NewDiskStore(db, makeBlockFac()).WithTx(dummyTx{}) - - err = next.Store(makeLink(t, types.Digest{})) - require.EqualError(t, err, "transaction 'blockstore.dummyTx' is not writable") - - _, err = next.GetByIndex(0) - require.EqualError(t, err, "transaction 'blockstore.dummyTx' is not readable") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeDB(t *testing.T) (kv.DB, func()) { - file, err := os.CreateTemp(os.TempDir(), "dela-blockstore") - require.NoError(t, err) - - db, err := kv.New(file.Name()) - require.NoError(t, err) - - clean := func() { - db.Close() - file.Close() - } - - return db, clean -} - -func makeBlockFac() types.LinkFactory { - blockFac := types.NewBlockFactory(fakeResultFac{}) - - return types.NewLinkFactory(blockFac, fake.SignatureFactory{}, fakeCsFac{}) -} - -type fakeCsFac struct { - authority.ChangeSetFactory -} - -func (fakeCsFac) ChangeSetOf(serde.Context, []byte) (authority.ChangeSet, error) { - return authority.NewChangeSet(), nil -} - -type fakeResultFac struct { - validation.ResultFactory -} - -func (fakeResultFac) ResultOf(serde.Context, []byte) (validation.Result, error) { - return simple.NewResult(nil), nil -} - -type badLinkFac struct { - types.LinkFactory -} - -func (badLinkFac) BlockLinkOf(serde.Context, []byte) (types.BlockLink, error) { - return nil, fake.GetError() -} - -func (badLinkFac) LinkOf(serde.Context, []byte) (types.Link, error) { - return nil, fake.GetError() -} - -type badLink struct { - types.BlockLink -} - -func (badLink) GetFrom() types.Digest { - return types.Digest{} -} - -func (badLink) Serialize(serde.Context) ([]byte, error) { - return nil, fake.GetError() -} - -type dummyTx struct { - store.Transaction -} diff --git a/dela/core/ordering/cosipbft/blockstore/genesis.go b/dela/core/ordering/cosipbft/blockstore/genesis.go deleted file mode 100644 index 3b03c9e..0000000 --- a/dela/core/ordering/cosipbft/blockstore/genesis.go +++ /dev/null @@ -1,145 +0,0 @@ -// This file contains the implementations of a genesis store. An in-memory and a -// persistent implementation are available. -// -// Documentation Last Review: 13.10.2020 -// - -package blockstore - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -var genesisBucket = []byte("blockstore-genesis") -var genesisKey = []byte("block") - -// CachedGenesis is a store to keep a genesis block in cache. -// -// - implements blockstore.GenesisStore -type cachedGenesis struct { - genesis types.Genesis - set bool -} - -// NewGenesisStore returns a new empty genesis store. -func NewGenesisStore() GenesisStore { - return &cachedGenesis{} -} - -// Get implements blockstore.GenesisStore. It returns the genesis block if it is -// set, otherwise it returns an error. -func (s *cachedGenesis) Get() (types.Genesis, error) { - if !s.set { - return types.Genesis{}, xerrors.New("missing genesis block") - } - - return s.genesis, nil -} - -// Set implements blockstore.GenesisStore. It sets the genesis block only if the -// cache is empty, otherwise it returns an error. -func (s *cachedGenesis) Set(genesis types.Genesis) error { - if s.set { - return xerrors.New("genesis block is already set") - } - - s.genesis = genesis - s.set = true - return nil -} - -// Exists implements blockstore.GenesisStore. It returns true if the genesis -// block is set. -func (s *cachedGenesis) Exists() bool { - return s.set -} - -// PersistentGenesisCache is a store to set a genesis block on disk. It also -// provides a function to load the block from the disk that will then stay in -// memory. -// -// - implements blockstore.GenesisStore -type PersistentGenesisCache struct { - *cachedGenesis - - db kv.DB - context serde.Context - fac serde.Factory -} - -// NewGenesisDiskStore creates a new store that will load or set the genesis -// block using the given database. -func NewGenesisDiskStore(db kv.DB, fac serde.Factory) PersistentGenesisCache { - return PersistentGenesisCache{ - cachedGenesis: &cachedGenesis{}, - db: db, - context: json.NewContext(), - fac: fac, - } -} - -// Load tries to read the genesis block in the database, and set it to memory -// only if it exists. It returns an error if the database cannot be read, or the -// value is malformed. -func (s PersistentGenesisCache) Load() error { - return s.db.View(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(genesisBucket) - if bucket == nil { - // Nothing in the database, so the load is successful but no genesis - // is set. - return nil - } - - data := bucket.Get(genesisKey) - - msg, err := s.fac.Deserialize(s.context, data) - if err != nil { - return xerrors.Errorf("malformed value: %v", err) - } - - genesis, ok := msg.(types.Genesis) - if !ok { - return xerrors.Errorf("unsupported message '%T'", msg) - } - - s.set = true - s.genesis = genesis - - return nil - }) -} - -// Set implements blockstore.GenesisStore. It writes the genesis block in -// disk, and then in memory for fast access. -func (s PersistentGenesisCache) Set(genesis types.Genesis) error { - if !s.set { - data, err := genesis.Serialize(s.context) - if err != nil { - return xerrors.Errorf("failed to serialize genesis: %v", err) - } - - err = s.db.Update(func(tx kv.WritableTx) error { - bucket, err := tx.GetBucketOrCreate(genesisBucket) - if err != nil { - return xerrors.Errorf("bucket: %v", err) - } - - err = bucket.Set(genesisKey, data) - if err != nil { - return xerrors.Errorf("while writing to bucket: %v", err) - } - - return nil - }) - - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - } - - return s.cachedGenesis.Set(genesis) -} diff --git a/dela/core/ordering/cosipbft/blockstore/genesis_test.go b/dela/core/ordering/cosipbft/blockstore/genesis_test.go deleted file mode 100644 index 9686a82..0000000 --- a/dela/core/ordering/cosipbft/blockstore/genesis_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package blockstore - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestCachedGenesis_Get(t *testing.T) { - store := NewGenesisStore().(*cachedGenesis) - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - _, err := store.Get() - require.EqualError(t, err, "missing genesis block") - - block, err := types.NewGenesis(ro) - require.NoError(t, err) - - store.set = true - store.genesis = block - - genesis, err := store.Get() - require.NoError(t, err) - require.Equal(t, block, genesis) -} - -func TestCachedGenesis_Set(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - block, err := types.NewGenesis(ro) - require.NoError(t, err) - - store := NewGenesisStore().(*cachedGenesis) - - err = store.Set(block) - require.NoError(t, err) - require.True(t, store.set) - - err = store.Set(block) - require.EqualError(t, err, "genesis block is already set") -} - -func TestCachedGenesis_Exists(t *testing.T) { - store := NewGenesisStore() - - require.False(t, store.Exists()) - - store.Set(types.Genesis{}) - require.True(t, store.Exists()) -} - -func TestGenesisDiskStore_Load(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-blockstore-") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - store := NewGenesisDiskStore(db, makeFac()) - - err = store.Load() - require.NoError(t, err) - require.False(t, store.set) - - err = store.Set(makeGenesis(t)) - require.NoError(t, err) - - // Reset the store. - store = NewGenesisDiskStore(db, makeFac()) - - err = store.Load() - require.NoError(t, err) - require.True(t, store.set) - - store.fac = fake.NewBadMessageFactory() - err = store.Load() - require.EqualError(t, err, fake.Err("malformed value")) - - store.fac = fake.MessageFactory{} - err = store.Load() - require.EqualError(t, err, "unsupported message 'fake.Message'") -} - -func TestGenesisDiskStore_Set(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-blockstore-") - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - store := NewGenesisDiskStore(db, makeFac()) - - err = store.Set(makeGenesis(t)) - require.NoError(t, err) - - var data []byte - db.View(func(tx kv.ReadableTx) error { - data = tx.GetBucket(genesisBucket).Get(genesisKey) - return nil - }) - - require.NotEmpty(t, data) - - store = NewGenesisDiskStore(badDB{}, makeFac()) - err = store.Set(makeGenesis(t)) - require.EqualError(t, err, fake.Err("store failed: bucket")) - - store.db = badDB{bucket: badBucket{}} - err = store.Set(makeGenesis(t)) - require.EqualError(t, err, fake.Err("store failed: while writing to bucket")) - - store.context = fake.NewBadContext() - err = store.Set(makeGenesis(t)) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to serialize genesis: ") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeFac() types.GenesisFactory { - authFac := authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - return types.NewGenesisFactory(authFac) -} - -func makeGenesis(t *testing.T) types.Genesis { - ro := authority.FromAuthority(fake.NewAuthority(1, fake.NewSigner)) - - genesis, err := types.NewGenesis(ro) - require.NoError(t, err) - - return genesis -} - -type badBucket struct { - kv.Bucket -} - -func (badBucket) Set(key, value []byte) error { - return fake.GetError() -} - -type badTx struct { - kv.WritableTx - - bucket kv.Bucket -} - -func (tx badTx) GetBucketOrCreate(name []byte) (kv.Bucket, error) { - if tx.bucket != nil { - return tx.bucket, nil - } - - return nil, fake.GetError() -} - -type badDB struct { - kv.DB - - bucket kv.Bucket -} - -func (db badDB) Update(fn func(kv.WritableTx) error) error { - return fn(badTx{bucket: db.bucket}) -} diff --git a/dela/core/ordering/cosipbft/blockstore/mem.go b/dela/core/ordering/cosipbft/blockstore/mem.go deleted file mode 100644 index 57d0cb0..0000000 --- a/dela/core/ordering/cosipbft/blockstore/mem.go +++ /dev/null @@ -1,294 +0,0 @@ -// This file contains an in-memory implementation of a block store. -// -// Documentation Last Review: 13.10.2020 -// - -package blockstore - -import ( - "context" - "sync" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "golang.org/x/xerrors" -) - -// sizeWarnLimit defines the limit after which a warning is emitted every time -// the queue keeps growing. -const sizeWarnLimit = 100 - -// InMemory is a block store that only stores the block in-memory which means -// they won't persist. -// -// - implements blockstore.BlockStore -type InMemory struct { - sync.Mutex - blocks []types.BlockLink - watcher core.Observable - withTx bool -} - -// NewInMemory returns a new empty in-memory block store. -func NewInMemory() *InMemory { - return &InMemory{ - blocks: make([]types.BlockLink, 0), - watcher: core.NewWatcher(), - } -} - -// Len implements blockstore.BlockStore. It returns the length of the store. -func (s *InMemory) Len() uint64 { - s.Lock() - defer s.Unlock() - - return uint64(len(s.blocks)) -} - -// Store implements blockstore.BlockStore. It stores the block only if the link -// matches the latest block. -func (s *InMemory) Store(link types.BlockLink) error { - s.Lock() - defer s.Unlock() - - if len(s.blocks) > 0 { - latest := s.blocks[len(s.blocks)-1] - - if latest.GetTo() != link.GetFrom() { - return xerrors.Errorf("mismatch link '%v' != '%v'", link.GetFrom(), latest.GetTo()) - } - } - - s.blocks = append(s.blocks, link) - - if !s.withTx { - // When the store is using a database transaction, it will delay the - // notification until the commit. - s.watcher.Notify(link) - } - - return nil -} - -// Get implements blockstore.BlockStore. It returns the block link associated to -// the digest if it exists, otherwise it returns an error. -func (s *InMemory) Get(id types.Digest) (types.BlockLink, error) { - s.Lock() - defer s.Unlock() - - for _, link := range s.blocks { - if link.GetTo() == id { - return link, nil - } - } - - return nil, xerrors.Errorf("block not found: %w", ErrNoBlock) -} - -// GetByIndex implements blockstore.BlockStore. It returns the block associated -// to the index if it exists. -func (s *InMemory) GetByIndex(index uint64) (types.BlockLink, error) { - s.Lock() - defer s.Unlock() - - if int(index) >= len(s.blocks) { - return nil, xerrors.Errorf("block not found: %w", ErrNoBlock) - } - - return s.blocks[index], nil -} - -// GetChain implements blockstore.BlockStore. It returns the chain to the latest -// block. -func (s *InMemory) GetChain() (types.Chain, error) { - s.Lock() - defer s.Unlock() - - num := len(s.blocks) - 1 - - if num < 0 { - return nil, xerrors.New("store is empty") - } - - prevs := make([]types.Link, num) - for i, block := range s.blocks[:num] { - prevs[i] = block.Reduce() - } - - return types.NewChain(s.blocks[num], prevs), nil -} - -// Last implements blockstore.BlockStore. It returns the latest block of the -// store. -func (s *InMemory) Last() (types.BlockLink, error) { - s.Lock() - defer s.Unlock() - - if len(s.blocks) == 0 { - return nil, xerrors.Errorf("store empty: %w", ErrNoBlock) - } - - return s.blocks[len(s.blocks)-1], nil -} - -// Watch implements blockstore.BlockStore. It returns a channel populated with -// new blocks. -func (s *InMemory) Watch(ctx context.Context) <-chan types.BlockLink { - obs := newObserver(ctx, s.watcher) - - return obs.ch -} - -// WithTx implements blockstore.BlockStore. It returns a new store that will -// apply the list of blocks at the end of the transaction. -func (s *InMemory) WithTx(txn store.Transaction) BlockStore { - store := &InMemory{ - blocks: append([]types.BlockLink{}, s.blocks...), - watcher: s.watcher, - withTx: true, - } - - from := len(store.blocks) - - txn.OnCommit(func() { - s.Lock() - s.blocks = store.blocks - s.withTx = false - - newBlocks := append([]types.BlockLink{}, s.blocks[from:]...) - s.Unlock() - - for _, link := range newBlocks { - s.watcher.Notify(link) - } - }) - - return store -} - -// Observer is an observer that can be added to store watcher. It will announce -// the blocks in order and without blocking the watcher even if the listener is -// not actively emptying the queue. -// -// - implements core.Observer. -type observer struct { - sync.Mutex - - logger zerolog.Logger - buffer []types.BlockLink - running bool - closed bool - working sync.WaitGroup - ch chan types.BlockLink -} - -func newObserver(ctx context.Context, watcher core.Observable) *observer { - // This channel must have a buffer of size 1, no more no less, in order to - // preserve the ordering of the events but also to prevent the observer - // buffer to be used when the channel is correctly listened to. - ch := make(chan types.BlockLink, 1) - - obs := &observer{ - logger: dela.Logger, - ch: ch, - } - - watcher.Add(obs) - - go func() { - <-ctx.Done() - watcher.Remove(obs) - obs.close() - }() - - return obs -} - -// NotifyCallback implements core.Observer. It pushes the event to the channel -// if it is free, otherwise it fills a buffer and waits for the channel to be -// drained. -func (obs *observer) NotifyCallback(evt interface{}) { - obs.Lock() - defer obs.Unlock() - - if obs.closed { - return - } - - if obs.running { - // We know the channel is busy so it goes directly to the buffer. - obs.buffer = append(obs.buffer, evt.(types.BlockLink)) - obs.checkSize() - return - } - - select { - case obs.ch <- evt.(types.BlockLink): - - default: - // The buffer size is not controlled as we assume the event will be read - // shortly by the caller. - obs.buffer = append(obs.buffer, evt.(types.BlockLink)) - - obs.checkSize() - - obs.running = true - - obs.working.Add(1) - - go obs.pushAndWait() - } -} - -func (obs *observer) checkSize() { - if len(obs.buffer) >= sizeWarnLimit { - obs.logger.Warn(). - Int("size", len(obs.buffer)). - Msg("observer queue is growing unexpectedly") - } -} - -func (obs *observer) pushAndWait() { - defer obs.working.Done() - - for { - obs.Lock() - - if len(obs.buffer) == 0 { - obs.running = false - obs.Unlock() - return - } - - msg := obs.buffer[0] - obs.buffer = obs.buffer[1:] - - obs.Unlock() - - // Wait for the channel to be available to writings. - obs.ch <- msg - } -} - -func (obs *observer) close() { - obs.Lock() - - obs.closed = true - obs.running = false - obs.buffer = nil - - // Drain message in transit to close the channel properly. - select { - case <-obs.ch: - default: - } - - close(obs.ch) - - obs.Unlock() - - obs.working.Wait() -} diff --git a/dela/core/ordering/cosipbft/blockstore/mem_test.go b/dela/core/ordering/cosipbft/blockstore/mem_test.go deleted file mode 100644 index 7893ac0..0000000 --- a/dela/core/ordering/cosipbft/blockstore/mem_test.go +++ /dev/null @@ -1,219 +0,0 @@ -package blockstore - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestInMemory_Len(t *testing.T) { - store := NewInMemory() - require.Equal(t, uint64(0), store.Len()) - - store.blocks = []types.BlockLink{makeLink(t, types.Digest{}), makeLink(t, types.Digest{})} - require.Equal(t, uint64(2), store.Len()) -} - -func TestInMemory_Store(t *testing.T) { - store := NewInMemory() - - err := store.Store(makeLink(t, types.Digest{})) - require.NoError(t, err) - - err = store.Store(makeLink(t, store.blocks[0].GetTo())) - require.NoError(t, err) - - err = store.Store(makeLink(t, types.Digest{})) - require.EqualError(t, err, "mismatch link '00000000' != '2c34ce1d'") -} - -func TestInMemory_Get(t *testing.T) { - store := NewInMemory() - - store.blocks = []types.BlockLink{makeLink(t, types.Digest{})} - - block, err := store.Get(store.blocks[0].GetTo()) - require.NoError(t, err) - require.Equal(t, store.blocks[0], block) - - _, err = store.Get(types.Digest{}) - require.EqualError(t, err, "block not found: no block") -} - -func TestInMemory_GetByIndex(t *testing.T) { - store := NewInMemory() - - store.blocks = []types.BlockLink{ - makeLink(t, types.Digest{}, types.WithIndex(0)), - makeLink(t, types.Digest{}, types.WithIndex(1)), - makeLink(t, types.Digest{}, types.WithIndex(2)), - } - - block, err := store.GetByIndex(1) - require.NoError(t, err) - require.Equal(t, uint64(1), block.GetBlock().GetIndex()) - - block, err = store.GetByIndex(2) - require.NoError(t, err) - require.Equal(t, uint64(2), block.GetBlock().GetIndex()) - - _, err = store.GetByIndex(3) - require.EqualError(t, err, "block not found: no block") -} - -func TestInMemory_GetChain(t *testing.T) { - store := NewInMemory() - - store.blocks = []types.BlockLink{ - makeLink(t, types.Digest{}, types.WithIndex(0)), - makeLink(t, types.Digest{}, types.WithIndex(1)), - makeLink(t, types.Digest{}, types.WithIndex(2)), - } - - chain, err := store.GetChain() - require.NoError(t, err) - require.Len(t, chain.GetLinks(), 3) - - store.blocks = store.blocks[:1] - chain, err = store.GetChain() - require.NoError(t, err) - require.Len(t, chain.GetLinks(), 1) - - store.blocks = nil - _, err = store.GetChain() - require.EqualError(t, err, "store is empty") -} - -func TestInMemory_Last(t *testing.T) { - store := NewInMemory() - - _, err := store.Last() - require.EqualError(t, err, "store empty: no block") - - store.blocks = []types.BlockLink{makeLink(t, types.Digest{})} - block, err := store.Last() - require.NoError(t, err) - require.Equal(t, store.blocks[0], block) -} - -func TestInMemory_Watch(t *testing.T) { - num := 20 - store := NewInMemory() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - ch := store.Watch(ctx) - - store.Store(makeLink(t, types.Digest{})) - - for i := 0; i < num; i++ { - store.Store(makeLink(t, store.blocks[i].GetTo(), types.WithIndex(uint64(i+1)))) - } - - for i := 0; i <= num; i++ { - link := <-ch - require.Equal(t, store.blocks[i].GetTo(), link.GetTo(), i) - } -} - -func TestInMemory_WithTx(t *testing.T) { - store := NewInMemory() - - tx := &fakeTx{} - txstore := store.WithTx(tx) - - err := txstore.Store(makeLink(t, types.Digest{})) - require.NoError(t, err) - require.Len(t, store.blocks, 0) - require.Len(t, txstore.(*InMemory).blocks, 1) - - tx.fn() - require.Len(t, store.blocks, 1) -} - -func TestObserver_NotifyCallback(t *testing.T) { - obs := &observer{ - ch: make(chan types.BlockLink, 1), - } - - link := makeLink(t, types.Digest{}) - - // The observer should not block when the event is not drained. - obs.NotifyCallback(link) - obs.NotifyCallback(link) - obs.NotifyCallback(link) - - evt := <-obs.ch - require.NotNil(t, evt) - - evt = <-obs.ch - require.NotNil(t, evt) - - // Closing with events waiting should clean resources. - obs.close() - require.Empty(t, obs.buffer) - require.Empty(t, obs.ch) - require.True(t, obs.closed) - require.False(t, obs.running) - - // Incoming events should now be ignored. - obs.NotifyCallback(link) - require.Empty(t, obs.buffer) - require.Empty(t, obs.ch) -} - -func TestObserver_Flooding_NotifyCallback(t *testing.T) { - logger, check := fake.CheckLog("observer queue is growing unexpectedly") - - obs := &observer{ - logger: logger, - ch: make(chan types.BlockLink), - } - - link := makeLink(t, types.Digest{}) - - for i := 0; i < sizeWarnLimit+1; i++ { - obs.NotifyCallback(link) - } - - check(t) -} - -func TestObserver_WhileEmpty_Close(t *testing.T) { - obs := &observer{ - ch: make(chan types.BlockLink), - running: true, - } - - obs.close() - require.True(t, obs.closed) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeLink(t *testing.T, from types.Digest, opts ...types.BlockOption) types.BlockLink { - to, err := types.NewBlock(simple.NewResult(nil), opts...) - require.NoError(t, err) - - link, err := types.NewBlockLink(from, to, types.WithSignatures(fake.Signature{}, fake.Signature{})) - require.NoError(t, err) - - return link -} - -type fakeTx struct { - store.Transaction - - fn func() -} - -func (tx *fakeTx) OnCommit(fn func()) { - tx.fn = fn -} diff --git a/dela/core/ordering/cosipbft/blockstore/tree.go b/dela/core/ordering/cosipbft/blockstore/tree.go deleted file mode 100644 index c8e385f..0000000 --- a/dela/core/ordering/cosipbft/blockstore/tree.go +++ /dev/null @@ -1,68 +0,0 @@ -// This file contains the implementation of an in-memory tree cache. -// -// Documenation Last Review: 13.10.2020 -// - -package blockstore - -import ( - "sync" - - "go.dedis.ch/dela/core/store/hashtree" -) - -// TreeCache is a storage for a tree. It supports asynchronous calls. -// -// - implements blockstore.TreeCache -type treeCache struct { - sync.Mutex - tree hashtree.Tree -} - -// NewTreeCache creates a new cache with the given tree as the first value. -func NewTreeCache(tree hashtree.Tree) TreeCache { - return &treeCache{ - tree: tree, - } -} - -// Get implements blockstore.TreeCache. It returns the current value of the -// cache. -func (c *treeCache) Get() hashtree.Tree { - c.Lock() - defer c.Unlock() - - return c.tree -} - -// GetWithLock implements blockstore.TreeCache. It returns the current value of -// the cache alongside a function to unlock the cache. It allows one to delay -// a set while fetching associated data. The function returned must be called. -func (c *treeCache) GetWithLock() (hashtree.Tree, func()) { - c.Lock() - - return c.tree, c.unlock -} - -// Set implements blockstore.TreeCache. It stores the new tree as the cache -// value but panic if it is nil. -func (c *treeCache) Set(tree hashtree.Tree) { - c.Lock() - c.tree = tree - c.Unlock() -} - -// SetWithLock implements blockstore.TreeCache. It sets the tree while holding -// the lock and returns a function to unlock it. It allows one to prevent an -// access until associated data is updated. The function returned must be -// called. -func (c *treeCache) SetWithLock(tree hashtree.Tree) func() { - c.Lock() - c.tree = tree - - return c.unlock -} - -func (c *treeCache) unlock() { - c.Unlock() -} diff --git a/dela/core/ordering/cosipbft/blockstore/tree_test.go b/dela/core/ordering/cosipbft/blockstore/tree_test.go deleted file mode 100644 index f92dc8c..0000000 --- a/dela/core/ordering/cosipbft/blockstore/tree_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package blockstore - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/store/hashtree" -) - -func TestTreeCache_Get(t *testing.T) { - cache := NewTreeCache(fakeTree{}) - - require.Equal(t, fakeTree{}, cache.Get()) -} - -func TestTreeCache_GetWithLock(t *testing.T) { - cache := NewTreeCache(fakeTree{}) - - tree, unlock := cache.GetWithLock() - require.NotNil(t, tree) - - unlock() - - tree, unlock = cache.GetWithLock() - require.NotNil(t, tree) - - unlock() -} - -func TestTreeCache_Set(t *testing.T) { - cache := NewTreeCache(fakeTree{}) - - cache.Set(fakeTree{value: 1}) - require.Equal(t, fakeTree{value: 1}, cache.Get()) -} - -func TestTreeCache_SetAndLock(t *testing.T) { - cache := NewTreeCache(fakeTree{}) - - unlock := cache.SetWithLock(fakeTree{}) - - ch := make(chan struct{}) - go func() { - cache.Get() - close(ch) - }() - - time.Sleep(50 * time.Millisecond) - - select { - case <-ch: - t.Fatal("get should be locked") - default: - } - - unlock() - - select { - case <-ch: - case <-time.After(time.Second): - t.Fatal("get should be released") - } -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeTree struct { - hashtree.Tree - value int -} diff --git a/dela/core/ordering/cosipbft/blocksync/blocksync.go b/dela/core/ordering/cosipbft/blocksync/blocksync.go deleted file mode 100644 index 5dc5145..0000000 --- a/dela/core/ordering/cosipbft/blocksync/blocksync.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package blocksync defines a block synchronizer for the ordering service. -// -// The package also implements a default synchronizer that will send an -// announcement with the latest known block, and share the chain to the nodes -// that have fallen behind. -// -// Documentation Last Review: 13.10.2020 -package blocksync - -import ( - "context" - - "go.dedis.ch/dela/mino" -) - -// Config is the configuration to change the behaviour of the synchronization. -type Config struct { - // MinSoft is the number of participants that have soft-synchronized, - // meaning they know the latest index of the leader. - MinSoft int - - // MinHard is the number of participants that have hard-synchronized, - // meaning they have the latest block stored. - MinHard int -} - -// Synchronizer is an interface to synchronize a leader with the participants. -type Synchronizer interface { - // GetLatest returns the latest known synchronization update. It can be used - // to wait for a complete chain update as this index has been proven to - // exist. - GetLatest() uint64 - - // Sync sends a synchronization message to all the participants in order to - // announce the current state of the chain. - Sync(ctx context.Context, players mino.Players, cfg Config) error -} diff --git a/dela/core/ordering/cosipbft/blocksync/default.go b/dela/core/ordering/cosipbft/blocksync/default.go deleted file mode 100644 index 7e99364..0000000 --- a/dela/core/ordering/cosipbft/blocksync/default.go +++ /dev/null @@ -1,335 +0,0 @@ -// This file contains a default implementation of a block synchronizer. -// -// Documentation Last Review: 13.10.2020 -// - -package blocksync - -import ( - "context" - "io" - "sync" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync/types" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - otypes "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/tracing" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -var ( - // protocolName denotes the value of the protocol span tag associated with - // the `blocksync` protocol. - protocolName = "blocksync" -) - -// DefaultSync is a block synchronizer that allow soft and hard synchronization -// of the participants. A soft threshold means that a given number of -// participants have updated the latest index, whereas a hard one means that -// they have stored all the blocks up to the latest index. -// -// - implements blocksync.Synchronizer -type defaultSync struct { - logger zerolog.Logger - rpc mino.RPC - pbftsm pbft.StateMachine - blocks blockstore.BlockStore - - latest *uint64 - catchUpLock *sync.Mutex -} - -// SyncParam is the parameter object to create a new synchronizer. -type SyncParam struct { - Mino mino.Mino - PBFT pbft.StateMachine - Blocks blockstore.BlockStore - Genesis blockstore.GenesisStore - LinkFactory otypes.LinkFactory - ChainFactory otypes.ChainFactory - VerifierFactory crypto.VerifierFactory -} - -// NewSynchronizer creates a new block synchronizer. -func NewSynchronizer(param SyncParam) Synchronizer { - latest := param.Blocks.Len() - - logger := dela.Logger.With().Str("addr", param.Mino.GetAddress().String()).Logger() - - h := &handler{ - latest: &latest, - catchUpLock: new(sync.Mutex), - logger: logger, - genesis: param.Genesis, - blocks: param.Blocks, - pbftsm: param.PBFT, - verifierFac: param.VerifierFactory, - } - - fac := types.NewMessageFactory(param.LinkFactory, param.ChainFactory) - - s := defaultSync{ - logger: logger, - rpc: mino.MustCreateRPC(param.Mino, "blocksync", h, fac), - pbftsm: param.PBFT, - blocks: param.Blocks, - latest: &latest, - catchUpLock: h.catchUpLock, - } - - return s -} - -// GetLatest implements blocksync.Synchronizer. It returns the latest index -// known by the instance. -func (s defaultSync) GetLatest() uint64 { - s.catchUpLock.Lock() - defer s.catchUpLock.Unlock() - - return *s.latest -} - -// Sync implements blocksync.Synchronizer. it starts a routine to first -// soft-sync the participants and then send the blocks when necessary. It will -// synchronize other nodes as long as the context is not done. -func (s defaultSync) Sync(ctx context.Context, players mino.Players, cfg Config) error { - ctx = context.WithValue(ctx, tracing.ProtocolKey, protocolName) - - if s.blocks.Len() == 0 { - // When the store is empty, that means that the participants are all - // synchronized anyway as there is no block. - return nil - } - - sender, rcvr, err := s.rpc.Stream(ctx, players) - if err != nil { - return xerrors.Errorf("stream failed: %v", err) - } - - // 1. Send the announcement message to everyone so that they can learn about - // the latest block. - chain, err := s.blocks.GetChain() - if err != nil { - return xerrors.Errorf("failed to read chain: %v", err) - } - - errs := sender.Send(types.NewSyncMessage(chain), iter2arr(players.AddressIterator())...) - for err := range errs { - if err != nil { - s.logger.Warn().Err(err).Msg("announcement failed") - } - } - - // 2. Wait for the hard synchronization to end. It can be interrupted with - // the context. - wg := sync.WaitGroup{} - wg.Add(1) - once := sync.Once{} - - // The synchronization is run in background so that it continues even after - // the threshold is reached, which allow other nodes to complete a catch up - // while the round is performing. - go func() { - defer once.Do(wg.Done) - - soft := map[mino.Address]struct{}{} - hard := map[mino.Address]struct{}{} - - for { - from, msg, err := rcvr.Recv(ctx) - if err == context.Canceled || err == context.DeadlineExceeded || err == io.EOF { - return - } - if err != nil { - s.logger.Warn().Err(err).Msg("sync finished") - return - } - - switch in := msg.(type) { - case types.SyncRequest: - _, found := soft[from] - if found { - s.logger.Warn().Msg("found duplicate request") - continue - } - - soft[from] = struct{}{} - - go s.syncNode(in.GetFrom(), sender, from) - - case types.SyncAck: - soft[from] = struct{}{} - hard[from] = struct{}{} - } - - if len(soft) >= cfg.MinSoft && len(hard) >= cfg.MinHard { - once.Do(wg.Done) - } - } - }() - - wg.Wait() - - return nil -} - -func (s defaultSync) syncNode(from uint64, sender mino.Sender, to mino.Address) { - for i := from; i < s.blocks.Len(); i++ { - link, err := s.blocks.GetByIndex(i) - if err != nil { - s.logger.Err(err).Msgf("while synchronizing %v", to) - return - } - - s.logger.Debug(). - Uint64("index", link.GetBlock().GetIndex()). - Stringer("to", to). - Msg("send block") - - err = <-sender.Send(types.NewSyncReply(link), to) - if err != nil { - s.logger.Err(err).Msgf("while synchronizing %v", to) - return - } - } -} - -// handler is a Mino handler for the synchronization messages. -// -// - implements mino.Handler -type handler struct { - mino.UnsupportedHandler - - latest *uint64 - catchUpLock *sync.Mutex - - logger zerolog.Logger - blocks blockstore.BlockStore - genesis blockstore.GenesisStore - pbftsm pbft.StateMachine - verifierFac crypto.VerifierFactory -} - -// Stream implements mino.Handler. It waits for an announcement message and then -// wait for the block message if any is needed. -func (h *handler) Stream(out mino.Sender, in mino.Receiver) error { - ctx := context.Background() - - m, orch, err := h.waitAnnounce(ctx, in) - if err != nil { - return xerrors.Errorf("no announcement: %v", err) - } - - h.logger.Debug(). - Uint64("index", m.GetLatestIndex()). - Msg("received synchronization message") - - genesis, err := h.genesis.Get() - if err != nil { - return xerrors.Errorf("reading genesis: %v", err) - } - - from := genesis.GetHash() - - // We trust our storage, thus we won't check links on blocks we already - // have. - bl, err := h.blocks.Last() - if err == nil { - from = bl.GetFrom() - } - - err = m.GetChain().Verify(genesis, from, h.verifierFac) - if err != nil { - return xerrors.Errorf("failed to verify chain: %v", err) - } - - if m.GetLatestIndex() < h.blocks.Len() { - // The block storage has already all the block known so far so we can - // send the hard-sync acknowledgement. - return h.ack(out, orch) - } - - // At this point, the synchronization can only happen on one thread, so it - // waits for the lock to be free, which means that in the meantime some - // blocks might have been stored but the request is sent with the most - // up-to-date block index, so it won't catch up twice the same block. - h.catchUpLock.Lock() - defer h.catchUpLock.Unlock() - - // Update the latest index through atomic operations as it can be read - // asynchronously from the getter. - if m.GetLatestIndex() > *h.latest { - *h.latest = m.GetLatestIndex() - } - - err = <-out.Send(types.NewSyncRequest(h.blocks.Len()), orch) - if err != nil { - return xerrors.Errorf("sending request failed: %v", err) - } - - for h.blocks.Len() <= m.GetLatestIndex() { - _, msg, err := in.Recv(ctx) - if err != nil { - return xerrors.Errorf("receiver failed: %v", err) - } - - reply, ok := msg.(types.SyncReply) - if ok { - h.logger.Debug(). - Uint64("index", reply.GetLink().GetBlock().GetIndex()). - Msg("catch up block") - - err = h.pbftsm.CatchUp(reply.GetLink()) - if err != nil { - return xerrors.Errorf("pbft catch up failed: %v", err) - } - } - } - - h.logger.Debug().Msg("catch up done") - - return h.ack(out, orch) -} - -func (h *handler) waitAnnounce(ctx context.Context, - in mino.Receiver) (*types.SyncMessage, mino.Address, error) { - - for { - orch, msg, err := in.Recv(ctx) - if err != nil { - return nil, nil, xerrors.Errorf("receiver failed: %v", err) - } - - // The SyncMessage contains the chain to the latest block known by the - // leader which allows to verify if it is not lying. - m, ok := msg.(types.SyncMessage) - if ok { - return &m, orch, nil - } - } -} - -func (h *handler) ack(out mino.Sender, orch mino.Address) error { - // Send the acknowledgement to the orchestrator that the blocks have been - // caught up. - err := <-out.Send(types.NewSyncAck(), orch) - if err != nil { - return xerrors.Errorf("sending ack failed: %v", err) - } - - return nil -} - -func iter2arr(iter mino.AddressIterator) []mino.Address { - addrs := []mino.Address{} - for iter.HasNext() { - addrs = append(addrs, iter.GetNext()) - } - - return addrs -} diff --git a/dela/core/ordering/cosipbft/blocksync/default_test.go b/dela/core/ordering/cosipbft/blocksync/default_test.go deleted file mode 100644 index 0be0cbe..0000000 --- a/dela/core/ordering/cosipbft/blocksync/default_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package blocksync - -import ( - "context" - "fmt" - "strconv" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync/types" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - otypes "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/minoch" -) - -func TestDefaultSync_Basic(t *testing.T) { - n := 20 - k := 8 - num := 10 - - syncs, genesis, roster := makeNodes(t, n) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := syncs[0].Sync(ctx, roster, Config{ - MinSoft: roster.Len(), - MinHard: roster.Len(), - }) - require.NoError(t, err) - - storeBlocks(t, syncs[0].blocks, num, genesis.GetHash().Bytes()...) - - // Test only a subset of the roster to prepare for the next test. - err = syncs[0].Sync(ctx, roster.Take(mino.RangeFilter(0, k)), Config{ - MinSoft: k, - MinHard: k, - }) - require.NoError(t, err) - - for i := 0; i < k; i++ { - require.Equal(t, uint64(num), syncs[i].blocks.Len(), strconv.Itoa(i)) - } - - // Test that two parrallel synchronizations for the same latest index don't - // mix each other. Also test that already updated participants won't fail. - wg := sync.WaitGroup{} - wg.Add(2) - - cfg := Config{ - MinSoft: n, - MinHard: n, - } - - go func() { - defer wg.Done() - require.NoError(t, syncs[0].Sync(ctx, roster, cfg)) - }() - go func() { - defer wg.Done() - require.NoError(t, syncs[k-1].Sync(ctx, roster, cfg)) - }() - - wg.Wait() - - for i := 0; i < n; i++ { - require.Equal(t, uint64(num), syncs[i].blocks.Len(), strconv.Itoa(i)) - } -} - -func TestDefaultSync_GetLatest(t *testing.T) { - latest := uint64(5) - - sync := defaultSync{ - latest: &latest, - catchUpLock: new(sync.Mutex), - } - - require.Equal(t, uint64(5), sync.GetLatest()) -} - -func TestDefaultSync_Sync(t *testing.T) { - rcvr := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncRequest(0)), - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncRequest(0)), - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncAck()), - ) - sender := fake.Sender{} - - sync := defaultSync{ - rpc: fake.NewStreamRPC(rcvr, sender), - blocks: blockstore.NewInMemory(), - } - - storeBlocks(t, sync.blocks, 1) - - ctx := context.Background() - - err := sync.Sync(ctx, mino.NewAddresses(), Config{MinSoft: 1, MinHard: 1}) - require.NoError(t, err) - - sync.blocks = badBlockStore{errChain: fake.GetError()} - err = sync.Sync(ctx, mino.NewAddresses(), Config{}) - require.EqualError(t, err, fake.Err("failed to read chain")) - - sync.blocks = blockstore.NewInMemory() - storeBlocks(t, sync.blocks, 1) - sync.rpc = fake.NewBadRPC() - err = sync.Sync(ctx, mino.NewAddresses(), Config{}) - require.EqualError(t, err, fake.Err("stream failed")) - - logger, check := fake.CheckLog("announcement failed") - - sync.logger = logger - sync.rpc = fake.NewStreamRPC(fake.NewReceiver(), fake.NewBadSender()) - err = sync.Sync(ctx, mino.NewAddresses(), Config{}) - require.NoError(t, err) - check(t) - - logger, check = fake.CheckLog("sync finished") - - sync.logger = logger - sync.rpc = fake.NewStreamRPC(fake.NewBadReceiver(), fake.Sender{}) - err = sync.Sync(ctx, mino.NewAddresses(fake.NewAddress(0)), Config{MinSoft: 1}) - require.NoError(t, err) - check(t) - - logger, wait := fake.WaitLog("while synchronizing fake.Address[0]", time.Second) - - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncRequest(0)), - ) - - sync.logger = logger - sync.rpc = fake.NewStreamRPC(recv, sender) - sync.blocks = badBlockStore{} - err = sync.Sync(ctx, mino.NewAddresses(), Config{MinSoft: 1}) - require.NoError(t, err) - wait(t) -} - -func TestDefaultSync_SyncNode(t *testing.T) { - sync := defaultSync{ - blocks: blockstore.NewInMemory(), - } - - storeBlocks(t, sync.blocks, 5) - - logger, check := fake.CheckLog("while synchronizing fake.Address[0]") - - sync.logger = logger - sync.syncNode(0, fake.NewBadSender(), fake.NewAddress(0)) - - check(t) -} - -func TestHandler_Stream(t *testing.T) { - latest := uint64(0) - blocks := blockstore.NewInMemory() - storeBlocks(t, blocks, 3) - - handler := &handler{ - latest: &latest, - catchUpLock: new(sync.Mutex), - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - verifierFac: fake.VerifierFactory{}, - } - handler.genesis.Set(otypes.Genesis{}) - handler.pbftsm = testSM{blocks: handler.blocks} - storeBlocks(t, handler.blocks, 1) - - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, 0))), - ) - - err := handler.Stream(fake.Sender{}, recv) - require.NoError(t, err) - - msgs := []fake.ReceiverMessage{ - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, blocks.Len()-1))), - } - for i := uint64(0); i < blocks.Len(); i++ { - link, err := blocks.GetByIndex(i) - require.NoError(t, err) - - msgs = append(msgs, fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncReply(link))) - } - - handler.blocks = blockstore.NewInMemory() - handler.pbftsm = testSM{blocks: handler.blocks} - err = handler.Stream(fake.Sender{}, fake.NewReceiver(msgs...)) - require.NoError(t, err) - require.Equal(t, blocks.Len(), handler.blocks.Len()) - - err = handler.Stream(fake.Sender{}, fake.NewBadReceiver()) - require.EqualError(t, err, fake.Err("no announcement: receiver failed")) - - handler.genesis = blockstore.NewGenesisStore() - err = handler.Stream(fake.Sender{}, fake.NewReceiver(msgs...)) - require.EqualError(t, err, "reading genesis: missing genesis block") - - recv = fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(fakeChain{err: fake.GetError()})), - ) - - handler.genesis.Set(otypes.Genesis{}) - err = handler.Stream(fake.Sender{}, recv) - require.EqualError(t, err, fake.Err("failed to verify chain")) - - recv = fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, 6))), - ) - - err = handler.Stream(fake.NewBadSender(), recv) - require.EqualError(t, err, fake.Err("sending request failed")) - - recv = fake.NewBadReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, 6))), - ) - - err = handler.Stream(fake.Sender{}, recv) - require.EqualError(t, err, fake.Err("receiver failed")) - - recv = fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, 6))), - msgs[1], - ) - - err = handler.Stream(fake.Sender{}, recv) - require.Error(t, err) - require.Regexp(t, "pbft catch up failed: mismatch link '[0]{8}' != '[0-9a-f]{8}'", err.Error()) - - recv = fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), types.NewSyncMessage(makeChain(t, 0))), - ) - - err = handler.Stream(fake.NewBadSender(), recv) - require.EqualError(t, err, fake.Err("sending ack failed")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeChain(t *testing.T, index uint64) otypes.Chain { - block, err := otypes.NewBlock(simple.NewResult(nil), otypes.WithIndex(index)) - require.NoError(t, err) - - return fakeChain{block: block} -} - -func makeNodes(t *testing.T, n int) ([]defaultSync, otypes.Genesis, mino.Players) { - manager := minoch.NewManager() - - syncs := make([]defaultSync, n) - addrs := make([]mino.Address, n) - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := otypes.NewGenesis(ro) - require.NoError(t, err) - - for i := 0; i < n; i++ { - m := minoch.MustCreate(manager, fmt.Sprintf("node%d", i)) - - addrs[i] = m.GetAddress() - - genstore := blockstore.NewGenesisStore() - genstore.Set(genesis) - - blocks := blockstore.NewInMemory() - blockFac := otypes.NewBlockFactory(simple.NewResultFactory(signed.NewTransactionFactory())) - csFac := authority.NewChangeSetFactory(m.GetAddressFactory(), fake.PublicKeyFactory{}) - linkFac := otypes.NewLinkFactory(blockFac, fake.SignatureFactory{}, csFac) - - param := SyncParam{ - Mino: m, - Blocks: blocks, - Genesis: genstore, - LinkFactory: linkFac, - ChainFactory: otypes.NewChainFactory(linkFac), - PBFT: testSM{blocks: blocks}, - VerifierFactory: fake.VerifierFactory{}, - } - - syncs[i] = NewSynchronizer(param).(defaultSync) - } - - return syncs, genesis, mino.NewAddresses(addrs...) -} - -func storeBlocks(t *testing.T, blocks blockstore.BlockStore, n int, from ...byte) { - prev := otypes.Digest{} - copy(prev[:], from) - - for i := 0; i < n; i++ { - block, err := otypes.NewBlock(simple.NewResult(nil), otypes.WithIndex(uint64(i))) - require.NoError(t, err) - - link, err := otypes.NewBlockLink(prev, block, - otypes.WithSignatures(fake.Signature{}, fake.Signature{})) - require.NoError(t, err) - - err = blocks.Store(link) - require.NoError(t, err) - - prev = block.GetHash() - } -} - -type testSM struct { - pbft.StateMachine - - blocks blockstore.BlockStore -} - -func (sm testSM) CatchUp(link otypes.BlockLink) error { - err := sm.blocks.Store(link) - if err != nil { - return err - } - - return nil -} - -type badBlockStore struct { - blockstore.BlockStore - - errChain error -} - -func (s badBlockStore) Len() uint64 { - return 5 -} - -func (s badBlockStore) GetChain() (otypes.Chain, error) { - return nil, s.errChain -} - -func (s badBlockStore) GetByIndex(index uint64) (otypes.BlockLink, error) { - return nil, fake.GetError() -} - -type fakeChain struct { - otypes.Chain - - block otypes.Block - err error -} - -func (c fakeChain) GetBlock() otypes.Block { - return c.block -} - -func (c fakeChain) Verify(otypes.Genesis, otypes.Digest, crypto.VerifierFactory) error { - return c.err -} diff --git a/dela/core/ordering/cosipbft/blocksync/json/json.go b/dela/core/ordering/cosipbft/blocksync/json/json.go deleted file mode 100644 index 492f309..0000000 --- a/dela/core/ordering/cosipbft/blocksync/json/json.go +++ /dev/null @@ -1,145 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync/types" - otypes "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - types.RegisterMessageFormat(serde.FormatJSON, msgFormat{}) -} - -// SyncMessageJSON is the JSON representation of a sync announcement. -type SyncMessageJSON struct { - Chain json.RawMessage -} - -// SyncRequestJSON is the JSON representation of a sync request. -type SyncRequestJSON struct { - From uint64 -} - -// SyncReplyJSON is the JSON representation of a sync reply. -type SyncReplyJSON struct { - Link json.RawMessage -} - -// SyncAckJSON is the JSON representation of a sync acknowledgement. -type SyncAckJSON struct{} - -// MessageJSON is the JSON representation of a sync message. -type MessageJSON struct { - Message *SyncMessageJSON `json:",omitempty"` - Request *SyncRequestJSON `json:",omitempty"` - Reply *SyncReplyJSON `json:",omitempty"` - Ack *SyncAckJSON `json:",omitempty"` -} - -// MsgFormat is the format engine to encode and decode sync messages. -// -// - implements serde.FormatEngine -type msgFormat struct{} - -// Encode implements serde.FormatEngine. It returns the JSON data of the message -// if appropriate, otherwise an error. -func (fmt msgFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - var m MessageJSON - - switch in := msg.(type) { - case types.SyncMessage: - chain, err := in.GetChain().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to encode chain: %v", err) - } - - sm := SyncMessageJSON{ - Chain: chain, - } - - m.Message = &sm - case types.SyncRequest: - req := SyncRequestJSON{ - From: in.GetFrom(), - } - - m.Request = &req - case types.SyncReply: - link, err := in.GetLink().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("link serialization failed: %v", err) - } - - reply := SyncReplyJSON{ - Link: link, - } - - m.Reply = &reply - case types.SyncAck: - m.Ack = &SyncAckJSON{} - default: - return nil, xerrors.Errorf("unsupported message '%T'", msg) - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("marshal failed: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It returns the message associated to -// the data if appropriate, otherwise an error. -func (fmt msgFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := MessageJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("unmarshal failed: %v", err) - } - - if m.Message != nil { - fac := ctx.GetFactory(types.ChainKey{}) - - factory, ok := fac.(otypes.ChainFactory) - if !ok { - return nil, xerrors.Errorf("invalid chain factory '%T'", fac) - } - - chain, err := factory.ChainOf(ctx, m.Message.Chain) - if err != nil { - return nil, xerrors.Errorf("failed to decode chain: %v", err) - } - - return types.NewSyncMessage(chain), nil - } - - if m.Request != nil { - return types.NewSyncRequest(m.Request.From), nil - } - - if m.Reply != nil { - fac := ctx.GetFactory(types.LinkKey{}) - - factory, ok := fac.(otypes.LinkFactory) - if !ok { - return nil, xerrors.Errorf("invalid link factory '%T'", fac) - } - - link, err := factory.BlockLinkOf(ctx, m.Reply.Link) - if err != nil { - return nil, xerrors.Errorf("couldn't decode link: %v", err) - } - - return types.NewSyncReply(link), nil - } - - if m.Ack != nil { - return types.NewSyncAck(), nil - } - - return nil, xerrors.New("message is empty") -} diff --git a/dela/core/ordering/cosipbft/blocksync/json/json_test.go b/dela/core/ordering/cosipbft/blocksync/json/json_test.go deleted file mode 100644 index 4ede970..0000000 --- a/dela/core/ordering/cosipbft/blocksync/json/json_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync/types" - otypes "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestMsgFormat_Encode(t *testing.T) { - format := msgFormat{} - - ctx := fake.NewContext() - - data, err := format.Encode(ctx, types.NewSyncMessage(fakeChain{})) - require.NoError(t, err) - require.Equal(t, `{"Message":{"Chain":{}}}`, string(data)) - - data, err = format.Encode(ctx, types.NewSyncRequest(3)) - require.NoError(t, err) - require.Equal(t, `{"Request":{"From":3}}`, string(data)) - - data, err = format.Encode(ctx, types.NewSyncReply(fakeLink{})) - require.NoError(t, err) - require.Equal(t, `{"Reply":{"Link":{}}}`, string(data)) - - data, err = format.Encode(ctx, types.NewSyncAck()) - require.NoError(t, err) - require.Equal(t, `{"Ack":{}}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message 'fake.Message'") - - _, err = format.Encode(ctx, types.NewSyncMessage(fakeChain{err: fake.GetError()})) - require.EqualError(t, err, fake.Err("failed to encode chain")) - - _, err = format.Encode(ctx, types.NewSyncReply(fakeLink{err: fake.GetError()})) - require.EqualError(t, err, fake.Err("link serialization failed")) - - _, err = format.Encode(fake.NewBadContext(), types.NewSyncAck()) - require.EqualError(t, err, fake.Err("marshal failed")) -} - -func TestMsgFormat_Decode(t *testing.T) { - format := msgFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.LinkKey{}, fakeLinkFac{}) - ctx = serde.WithFactory(ctx, types.ChainKey{}, fakeChainFac{}) - - msg, err := format.Decode(ctx, []byte(`{"Message":{}}`)) - require.NoError(t, err) - require.Equal(t, types.NewSyncMessage(fakeChain{}), msg) - - msg, err = format.Decode(ctx, []byte(`{"Request":{}}`)) - require.NoError(t, err) - require.Equal(t, types.NewSyncRequest(0), msg) - - msg, err = format.Decode(ctx, []byte(`{"Reply":{"Link":{}}}`)) - require.NoError(t, err) - require.Equal(t, types.NewSyncReply(fakeLink{}), msg) - - msg, err = format.Decode(ctx, []byte(`{"Ack":{}}`)) - require.NoError(t, err) - require.Equal(t, types.NewSyncAck(), msg) - - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, "message is empty") - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("unmarshal failed")) - - ctx = serde.WithFactory(ctx, types.ChainKey{}, fakeChainFac{err: fake.GetError()}) - _, err = format.Decode(ctx, []byte(`{"Message":{}}`)) - require.EqualError(t, err, fake.Err("failed to decode chain")) - - ctx = serde.WithFactory(ctx, types.ChainKey{}, fake.MessageFactory{}) - _, err = format.Decode(ctx, []byte(`{"Message":{}}`)) - require.EqualError(t, err, "invalid chain factory 'fake.MessageFactory'") - - ctx = serde.WithFactory(ctx, types.LinkKey{}, fakeLinkFac{err: fake.GetError()}) - _, err = format.Decode(ctx, []byte(`{"Reply":{"Link":{}}}`)) - require.EqualError(t, err, fake.Err("couldn't decode link")) - - ctx = serde.WithFactory(ctx, types.LinkKey{}, fake.MessageFactory{}) - _, err = format.Decode(ctx, []byte(`{"Reply":{"Link":{}}}`)) - require.EqualError(t, err, "invalid link factory 'fake.MessageFactory'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeChain struct { - otypes.Chain - - err error -} - -func (chain fakeChain) Serialize(serde.Context) ([]byte, error) { - return []byte("{}"), chain.err -} - -type fakeChainFac struct { - otypes.ChainFactory - - err error -} - -func (fac fakeChainFac) ChainOf(serde.Context, []byte) (otypes.Chain, error) { - return fakeChain{}, fac.err -} - -type fakeLink struct { - otypes.BlockLink - - err error -} - -func (link fakeLink) Serialize(serde.Context) ([]byte, error) { - return []byte("{}"), link.err -} - -type fakeLinkFac struct { - otypes.LinkFactory - - err error -} - -func (fac fakeLinkFac) BlockLinkOf(serde.Context, []byte) (otypes.BlockLink, error) { - return fakeLink{}, fac.err -} diff --git a/dela/core/ordering/cosipbft/blocksync/types/types.go b/dela/core/ordering/cosipbft/blocksync/types/types.go deleted file mode 100644 index 86ddd14..0000000 --- a/dela/core/ordering/cosipbft/blocksync/types/types.go +++ /dev/null @@ -1,186 +0,0 @@ -// Package types implements the network messages for a synchronization. -// -// The messages are implemented in a different package to prevent cycle imports -// when importing the serde formats. -// -// Documentation Last Review: 13.10.2020 -package types - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var msgFormats = registry.NewSimpleRegistry() - -// RegisterMessageFormat registers the engine for the given format. -func RegisterMessageFormat(f serde.Format, e serde.FormatEngine) { - msgFormats.Register(f, e) -} - -// SyncMessage is the announcement sent to the participants with the latest -// index of the leader. The chain is provided to prove the validity of the -// index. -// -// - implements serde.Message -type SyncMessage struct { - chain types.Chain -} - -// NewSyncMessage creates a new announcement message. -func NewSyncMessage(chain types.Chain) SyncMessage { - return SyncMessage{ - chain: chain, - } -} - -// GetChain returns the chain that proves the latest index. -func (m SyncMessage) GetChain() types.Chain { - return m.chain -} - -// GetLatestIndex returns the latest index. -func (m SyncMessage) GetLatestIndex() uint64 { - return m.chain.GetBlock().GetIndex() -} - -// Serialize implements serde.Message. It returns the serialized data for this -// message. -func (m SyncMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// SyncRequest is a message to request missing blocks from a given index. -// -// - implements serde.Message -type SyncRequest struct { - from uint64 -} - -// NewSyncRequest creates a new sync request. -func NewSyncRequest(from uint64) SyncRequest { - return SyncRequest{ - from: from, - } -} - -// GetFrom returns the expected index of the first block when catching up. -func (m SyncRequest) GetFrom() uint64 { - return m.from -} - -// Serialize implements serde.Message. It returns the serialized data for this -// message. -func (m SyncRequest) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// SyncReply is a message to send a block to a participant. -// -// - implements serde.Message -type SyncReply struct { - link types.BlockLink -} - -// NewSyncReply creates a new sync reply. -func NewSyncReply(link types.BlockLink) SyncReply { - return SyncReply{ - link: link, - } -} - -// GetLink returns the link to a block to catch up. -func (m SyncReply) GetLink() types.BlockLink { - return m.link -} - -// Serialize implements serde.Message. It returns the serialized data for this -// message. -func (m SyncReply) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// SyncAck is a message sent to confirm a hard synchronization, which is when -// the node has all the blocks. -// -// - implements serde.Message -type SyncAck struct{} - -// NewSyncAck creates a new sync acknowledgement. -func NewSyncAck() SyncAck { - return SyncAck{} -} - -// Serialize implements serde.Message. It returns the serialized data for this -// message. -func (m SyncAck) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// LinkKey is the key of the block link factory. -type LinkKey struct{} - -// ChainKey is the key of the chain factory. -type ChainKey struct{} - -// MessageFactory is a message factory for sync messages. -// -// - implements serde.Factory -type MessageFactory struct { - linkFac types.LinkFactory - chainFac types.ChainFactory -} - -// NewMessageFactory createsa new message factory. -func NewMessageFactory(fac types.LinkFactory, chainFac types.ChainFactory) MessageFactory { - return MessageFactory{ - linkFac: fac, - chainFac: chainFac, - } -} - -// Deserialize implements serde.Factory. It returns the message associated to -// the data if appropriate, otherwise an error. -func (fac MessageFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := msgFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, LinkKey{}, fac.linkFac) - ctx = serde.WithFactory(ctx, ChainKey{}, fac.chainFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding failed: %v", err) - } - - return msg, nil -} diff --git a/dela/core/ordering/cosipbft/blocksync/types/types_test.go b/dela/core/ordering/cosipbft/blocksync/types/types_test.go deleted file mode 100644 index b32deb7..0000000 --- a/dela/core/ordering/cosipbft/blocksync/types/types_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -var testCalls = &fake.Call{} - -func init() { - RegisterMessageFormat(fake.GoodFormat, fake.Format{Msg: SyncMessage{}, Call: testCalls}) - RegisterMessageFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestSyncMessage_GetChain(t *testing.T) { - m := NewSyncMessage(makeChain(t, 5)) - - require.NotNil(t, m.GetChain()) -} - -func TestSyncMessage_GetLatestIndex(t *testing.T) { - m := NewSyncMessage(makeChain(t, 5)) - - require.Equal(t, uint64(5), m.GetLatestIndex()) -} - -func TestSyncMessage_Serialize(t *testing.T) { - m := NewSyncMessage(makeChain(t, 6)) - - data, err := m.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = m.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestSyncRequest_GetFrom(t *testing.T) { - m := NewSyncRequest(2) - - require.Equal(t, uint64(2), m.GetFrom()) -} - -func TestSyncRequest_Serialize(t *testing.T) { - m := NewSyncRequest(3) - - data, err := m.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = m.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestSyncReply_GetLink(t *testing.T) { - link, err := types.NewBlockLink(types.Digest{1}, types.Block{}) - require.NoError(t, err) - - m := NewSyncReply(link) - - require.Equal(t, link, m.GetLink()) -} - -func TestSyncReply_Serialize(t *testing.T) { - link, err := types.NewBlockLink(types.Digest{}, types.Block{}) - require.NoError(t, err) - - m := NewSyncReply(link) - - data, err := m.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = m.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestSyncAck_Serialize(t *testing.T) { - m := NewSyncAck() - - data, err := m.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = m.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestMessageFactory_Deserialize(t *testing.T) { - testCalls.Clear() - - linkFac := types.NewLinkFactory(nil, nil, nil) - - fac := NewMessageFactory(linkFac, types.NewChainFactory(linkFac)) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, SyncMessage{}, msg) - - factory := testCalls.Get(0, 0).(serde.Context).GetFactory(LinkKey{}) - require.NotNil(t, factory) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding failed")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeChain(t *testing.T, index uint64) types.Chain { - block, err := types.NewBlock(simple.NewResult(nil), types.WithIndex(index)) - require.NoError(t, err) - - link, err := types.NewBlockLink(types.Digest{}, block) - require.NoError(t, err) - - return types.NewChain(link, nil) -} diff --git a/dela/core/ordering/cosipbft/contracts/viewchange/viewchange.go b/dela/core/ordering/cosipbft/contracts/viewchange/viewchange.go deleted file mode 100644 index 5ac9a2c..0000000 --- a/dela/core/ordering/cosipbft/contracts/viewchange/viewchange.go +++ /dev/null @@ -1,177 +0,0 @@ -// Package viewchange implements a native smart contract to update the roster of -// a chain. -// -// Documentation Last Review: 08.10.2020 -package viewchange - -import ( - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -const ( - // ContractName is the name of the contract. - ContractName = "go.dedis.ch/dela.ViewChange" - - // AuthorityArg is the key of the argument for the new authority. - AuthorityArg = "viewchange:authority" - - messageOnlyOne = "only one view change per block is allowed" - messageArgMissing = "authority not found in transaction" - messageStorageEmpty = "authority not found in storage" - messageStorageCorrupted = "invalid authority data in storage" - messageTooManyChanges = "too many changes" - messageStorageFailure = "storage failure" - messageDuplicate = "duplicate in roster" - messageUnauthorized = "unauthorized identity" -) - -// RegisterContract registers the view change contract to the given execution -// service. -func RegisterContract(exec *native.Service, c Contract) { - exec.Set(ContractName, c) -} - -// NewCreds creates new credentials for a view change contract execution. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, "update") -} - -// Manager is an extension of a normal transaction manager to help creating view -// change ones. -type Manager struct { - manager txn.Manager - context serde.Context -} - -// NewManager returns a view change manager from the transaction manager. -func NewManager(mgr txn.Manager) Manager { - return Manager{ - manager: mgr, - context: json.NewContext(), - } -} - -// Make creates a new transaction using the provided manager. It contains the -// new roster that the transaction should apply. -func (mgr Manager) Make(roster authority.Authority) (txn.Transaction, error) { - data, err := roster.Serialize(mgr.context) - if err != nil { - return nil, xerrors.Errorf("failed to serialize roster: %v", err) - } - - tx, err := mgr.manager.Make( - txn.Arg{Key: native.ContractArg, Value: []byte(ContractName)}, - txn.Arg{Key: AuthorityArg, Value: data}, - ) - if err != nil { - return nil, xerrors.Errorf("creating transaction: %v", err) - } - - return tx, nil -} - -// Contract is a contract to update the roster at a given key in the storage. It -// only allows one member change per transaction. -// -// - implements native.Contract -type Contract struct { - rosterKey []byte - rosterFac authority.Factory - accessKey []byte - access access.Service - context serde.Context -} - -// NewContract creates a new viewchange contract. -func NewContract(rKey, aKey []byte, rFac authority.Factory, srvc access.Service) Contract { - return Contract{ - rosterKey: rKey, - rosterFac: rFac, - accessKey: aKey, - access: srvc, - context: json.NewContext(), - } -} - -// Execute implements native.Contract. It looks for the roster in the -// transaction and updates the storage if there is at most one membership -// change. -func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - for _, tx := range step.Previous { - // Only one view change transaction is allowed per block to prevent - // malicious peers to reach the threshold. - if string(tx.GetArg(native.ContractArg)) == ContractName { - return xerrors.New(messageOnlyOne) - } - } - - roster, err := c.rosterFac.AuthorityOf(c.context, step.Current.GetArg(AuthorityArg)) - if err != nil { - reportErr(step.Current, xerrors.Errorf("incoming roster: %v", err)) - - return xerrors.New(messageArgMissing) - } - - currData, err := snap.Get(c.rosterKey) - if err != nil { - reportErr(step.Current, xerrors.Errorf("reading store: %v", err)) - - return xerrors.New(messageStorageEmpty) - } - - curr, err := c.rosterFac.AuthorityOf(c.context, currData) - if err != nil { - reportErr(step.Current, xerrors.Errorf("stored roster: %v", err)) - - return xerrors.New(messageStorageCorrupted) - } - - changeset := curr.Diff(roster) - - if changeset.NumChanges() > 1 { - return xerrors.New(messageTooManyChanges) - } - - for _, addr := range changeset.GetNewAddresses() { - _, index := curr.GetPublicKey(addr) - if index >= 0 { - return xerrors.Errorf("%s: %v", messageDuplicate, addr) - } - } - - creds := NewCreds(c.accessKey) - - err = c.access.Match(snap, creds, step.Current.GetIdentity()) - if err != nil { - reportErr(step.Current, xerrors.Errorf("access control: %v", err)) - - return xerrors.Errorf("%s: %v", messageUnauthorized, step.Current.GetIdentity()) - } - - err = snap.Set(c.rosterKey, step.Current.GetArg(AuthorityArg)) - if err != nil { - reportErr(step.Current, xerrors.Errorf("writing store: %v", err)) - - return xerrors.New(messageStorageFailure) - } - - return nil -} - -// reportErr prints a log with the actual error while the transaction will -// contain a simplified explanation. -func reportErr(tx txn.Transaction, err error) { - dela.Logger.Warn(). - Hex("ID", tx.GetID()). - Err(err). - Msg("transaction refused") -} diff --git a/dela/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go b/dela/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go deleted file mode 100644 index d030e3d..0000000 --- a/dela/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package viewchange - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestRegisterContract(t *testing.T) { - srvc := native.NewExecution() - - RegisterContract(srvc, Contract{}) -} - -func TestNewTransaction(t *testing.T) { - mgr := NewManager(signed.NewManager(fake.NewSigner(), nil)) - - tx, err := mgr.Make(authority.New(nil, nil)) - require.NoError(t, err) - require.NotNil(t, tx) - require.Equal(t, "[]", string(tx.GetArg(AuthorityArg))) - - _, err = mgr.Make(badRoster{}) - require.EqualError(t, err, fake.Err("failed to serialize roster")) - - mgr.manager = badManager{} - _, err = mgr.Make(authority.New(nil, nil)) - require.EqualError(t, err, fake.Err("creating transaction")) -} - -func TestContract_Execute(t *testing.T) { - fac := authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - contract := NewContract([]byte("roster"), []byte("access"), fac, fakeAccess{}) - - err := contract.Execute(fakeStore{}, makeStep(t, "[]")) - require.NoError(t, err) - - err = contract.Execute(fakeStore{}, execution.Step{Previous: []txn.Transaction{makeTx(t, "")}}) - require.EqualError(t, err, "only one view change per block is allowed") - - contract.rosterFac = badRosterFac{} - err = contract.Execute(fakeStore{}, makeStep(t, "[]")) - require.EqualError(t, err, messageArgMissing) - - contract.rosterFac = fac - err = contract.Execute(fakeStore{errGet: fake.GetError()}, makeStep(t, "[]")) - require.EqualError(t, err, messageStorageEmpty) - - contract.rosterFac = badRosterFac{counter: fake.NewCounter(1)} - err = contract.Execute(fakeStore{}, makeStep(t, "[]")) - require.EqualError(t, err, messageStorageCorrupted) - - contract.rosterFac = fac - err = contract.Execute(fakeStore{}, makeStep(t, "[{},{},{}]")) - require.EqualError(t, err, messageTooManyChanges) - - err = contract.Execute(fakeStore{}, makeStep(t, "[{},{}]")) - require.EqualError(t, err, "duplicate in roster: fake.Address[0]") - - err = contract.Execute(fakeStore{errSet: fake.GetError()}, makeStep(t, "[]")) - require.EqualError(t, err, messageStorageFailure) - - contract.access = fakeAccess{err: fake.GetError()} - err = contract.Execute(fakeStore{}, makeStep(t, "[]")) - require.EqualError(t, err, "unauthorized identity: fake.PublicKey") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeStep(t *testing.T, arg string) execution.Step { - return execution.Step{Current: makeTx(t, arg)} -} - -func makeTx(t *testing.T, arg string) txn.Transaction { - args := []signed.TransactionOption{ - signed.WithArg(AuthorityArg, []byte(arg)), - signed.WithArg(native.ContractArg, []byte(ContractName)), - } - - tx, err := signed.NewTransaction(0, fake.PublicKey{}, args...) - require.NoError(t, err) - - return tx -} - -type fakeStore struct { - store.Snapshot - - errGet error - errSet error -} - -func (snap fakeStore) Get(key []byte) ([]byte, error) { - return []byte("[{}]"), snap.errGet -} - -func (snap fakeStore) Set(key, value []byte) error { - return snap.errSet -} - -type badRosterFac struct { - authority.Factory - counter *fake.Counter -} - -func (fac badRosterFac) AuthorityOf(serde.Context, []byte) (authority.Authority, error) { - if fac.counter.Done() { - return nil, fake.GetError() - } - - fac.counter.Decrease() - return nil, nil -} - -type badRoster struct { - authority.Authority -} - -func (ro badRoster) Serialize(serde.Context) ([]byte, error) { - return nil, fake.GetError() -} - -type badManager struct { - txn.Manager -} - -func (badManager) Make(opts ...txn.Arg) (txn.Transaction, error) { - return nil, fake.GetError() -} - -type fakeAccess struct { - access.Service - - err error -} - -func (srvc fakeAccess) Match(store.Readable, access.Credential, ...access.Identity) error { - return srvc.err -} diff --git a/dela/core/ordering/cosipbft/controller/action.go b/dela/core/ordering/cosipbft/controller/action.go deleted file mode 100644 index 0eb7efa..0000000 --- a/dela/core/ordering/cosipbft/controller/action.go +++ /dev/null @@ -1,282 +0,0 @@ -// This file contains the implementation of the controller actions. -// -// -// Documentation Last Review: 13.10.20202 - -package controller - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "strings" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -const separator = ":" - -// Service is the expected interface of the ordering service that is extended -// with some additional functions. -type Service interface { - ordering.Service - - GetRoster() (authority.Authority, error) - - Setup(ctx context.Context, ca crypto.CollectiveAuthority) error -} - -// SetupAction is an action to create a new chain with a list of participants. -// -// - implements node.ActionTemplate -type setupAction struct{} - -// Execute implements node.ActionTemplate. It reads the list of members and -// request the setup to the service. -func (a setupAction) Execute(ctx node.Context) error { - roster, err := a.readMembers(ctx) - if err != nil { - return xerrors.Errorf("failed to read roster: %v", err) - } - - var srvc Service - err = ctx.Injector.Resolve(&srvc) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - timeout := ctx.Flags.Duration("timeout") - - setupCtx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - err = srvc.Setup(setupCtx, roster) - if err != nil { - return xerrors.Errorf("failed to setup: %v", err) - } - - return nil -} - -func (a setupAction) readMembers(ctx node.Context) (authority.Authority, error) { - members := ctx.Flags.StringSlice("member") - - addrs := make([]mino.Address, len(members)) - pubkeys := make([]crypto.PublicKey, len(members)) - - for i, member := range members { - addr, pubkey, err := decodeMember(ctx, member) - if err != nil { - return nil, xerrors.Errorf("failed to decode: %v", err) - } - - addrs[i] = addr - pubkeys[i] = pubkey - } - - return authority.New(addrs, pubkeys), nil -} - -// ExportAction is an action to display a base64 string describing the node. It -// can be used to transmit the identity of a node to another one. -// -// - implements node.ActionTemplate -type exportAction struct{} - -// Execute implements node.ActionTemplate. It looks for the node address and -// public key and prints "$ADDR_BASE64:$PUBLIC_KEY_BASE64". -func (a exportAction) Execute(ctx node.Context) error { - var m mino.Mino - err := ctx.Injector.Resolve(&m) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - addr, err := m.GetAddress().MarshalText() - if err != nil { - return xerrors.Errorf("failed to marshal address: %v", err) - } - - var c cosi.CollectiveSigning - err = ctx.Injector.Resolve(&c) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - pubkey, err := c.GetSigner().GetPublicKey().MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal public key: %v", err) - } - - desc := base64.StdEncoding.EncodeToString(addr) + separator + - base64.StdEncoding.EncodeToString(pubkey) - - fmt.Fprint(ctx.Out, desc) - - return nil -} - -// RosterAddAction is an action to require a roster change in the change by -// adding a new member. -// -// - implements node.ActionTemplate -type rosterAddAction struct{} - -// Execute implements node.ActionTemplate. It reads the new member and send a -// transaction to require a roster change. -func (rosterAddAction) Execute(ctx node.Context) error { - var srvc Service - err := ctx.Injector.Resolve(&srvc) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - tx, err := prepareRosterTx(ctx, srvc) - if err != nil { - return xerrors.Errorf("while preparing tx: %v", err) - } - - var p pool.Pool - err = ctx.Injector.Resolve(&p) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - wait := ctx.Flags.Duration("wait") - - // Start listening for new transactions before sending the new one, to - // be sure the event will be received. - watchCtx, cancel := context.WithTimeout(context.Background(), wait) - defer cancel() - - events := srvc.Watch(watchCtx) - - err = p.Add(tx) - if err != nil { - return xerrors.Errorf("failed to add transaction: %v", err) - } - - if wait > 0 { - dela.Logger.Debug(). - Hex("id", tx.GetID()). - Msg("wait for the transaction to be included") - - for event := range events { - for _, res := range event.Transactions { - if !bytes.Equal(res.GetTransaction().GetID(), tx.GetID()) { - continue - } - - dela.Logger.Debug(). - Hex("id", tx.GetID()). - Msg("transaction included in the block") - - accepted, msg := res.GetStatus() - if !accepted { - return xerrors.Errorf("transaction refused: %s", msg) - } - - return nil - } - } - - return xerrors.New("transaction not found after timeout") - } - - return nil -} - -func prepareRosterTx(ctx node.Context, srvc Service) (txn.Transaction, error) { - roster, err := srvc.GetRoster() - if err != nil { - return nil, xerrors.Errorf("failed to read roster: %v", err) - } - - addr, pubkey, err := decodeMember(ctx, ctx.Flags.String("member")) - if err != nil { - return nil, xerrors.Errorf("failed to decode member: %v", err) - } - - cset := authority.NewChangeSet() - cset.Add(addr, pubkey) - - mgr, err := makeManager(ctx) - if err != nil { - return nil, xerrors.Errorf("txn manager: %v", err) - } - - tx, err := viewchange.NewManager(mgr).Make(roster.Apply(cset)) - if err != nil { - return nil, xerrors.Errorf("transaction: %v", err) - } - - return tx, nil -} - -func makeManager(ctx node.Context) (txn.Manager, error) { - var mgr txn.Manager - err := ctx.Injector.Resolve(&mgr) - if err != nil { - return nil, xerrors.Errorf("injector: %v", err) - } - - // Synchronize the manager with the latest state of the chain so that it can - // create valid transactions. - err = mgr.Sync() - if err != nil { - return nil, xerrors.Errorf("sync: %v", err) - } - - return mgr, nil -} - -func decodeMember(ctx node.Context, str string) (mino.Address, crypto.PublicKey, error) { - parts := strings.Split(str, separator) - if len(parts) != 2 { - return nil, nil, xerrors.New("invalid member base64 string") - } - - // 1. Deserialize the address. - var m mino.Mino - err := ctx.Injector.Resolve(&m) - if err != nil { - return nil, nil, xerrors.Errorf("injector: %v", err) - } - - addrBuf, err := base64.StdEncoding.DecodeString(parts[0]) - if err != nil { - return nil, nil, xerrors.Errorf("base64 address: %v", err) - } - - addr := m.GetAddressFactory().FromText(addrBuf) - - // 2. Deserialize the public key. - var c cosi.CollectiveSigning - err = ctx.Injector.Resolve(&c) - if err != nil { - return nil, nil, xerrors.Errorf("injector: %v", err) - } - - pubkeyBuf, err := base64.StdEncoding.DecodeString(parts[1]) - if err != nil { - return nil, nil, xerrors.Errorf("base64 public key: %v", err) - } - - pubkey, err := c.GetPublicKeyFactory().FromBytes(pubkeyBuf) - if err != nil { - return nil, nil, xerrors.Errorf("failed to decode public key: %v", err) - } - - return addr, pubkey, nil -} diff --git a/dela/core/ordering/cosipbft/controller/action_test.go b/dela/core/ordering/cosipbft/controller/action_test.go deleted file mode 100644 index adb6223..0000000 --- a/dela/core/ordering/cosipbft/controller/action_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package controller - -import ( - "bytes" - "context" - "io" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/core/txn/pool/mem" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" -) - -func TestSetupAction_Execute(t *testing.T) { - action := setupAction{} - - calls := &fake.Call{} - ctx := prepContext(calls) - ctx.Flags.(node.FlagSet)["member"] = []interface{}{"YQ==:YQ==", "YQ==:YQ=="} - - err := action.Execute(ctx) - require.NoError(t, err) - require.Equal(t, 1, calls.Len()) - require.Equal(t, 2, calls.Get(0, 1).(mino.Players).Len()) - - ctx.Flags.(node.FlagSet)["member"] = []interface{}{""} - err = action.Execute(ctx) - require.EqualError(t, err, "failed to read roster: failed to decode: invalid member base64 string") - - ctx.Flags = make(node.FlagSet) - ctx.Injector = node.NewInjector() - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'controller.Service'") - - ctx.Injector.Inject(fakeService{err: fake.GetError()}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("failed to setup")) -} - -func TestExportAction_Execute(t *testing.T) { - action := exportAction{} - - ctx := prepContext(nil) - - buffer := new(bytes.Buffer) - ctx.Out = buffer - - err := action.Execute(ctx) - require.NoError(t, err) - require.Equal(t, "AAAAAA==:UEs=", buffer.String()) - - ctx.Injector = node.NewInjector() - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'mino.Mino'") - - ctx.Injector.Inject(fake.NewBadMino()) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("failed to marshal address")) - - ctx.Injector.Inject(fake.Mino{}) - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'cosi.CollectiveSigning'") - - ctx.Injector.Inject(fakeCosi{err: true}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("failed to marshal public key")) -} - -func TestRosterAddAction_Execute(t *testing.T) { - action := rosterAddAction{} - - ctx := prepContext(nil) - ctx.Flags.(node.FlagSet)["member"] = "YQ==:YQ==" - ctx.Flags.(node.FlagSet)["wait"] = float64(time.Second) - - err := action.Execute(ctx) - require.NoError(t, err) - - ctx.Flags.(node.FlagSet)["wait"] = float64(0) - - err = action.Execute(ctx) - require.NoError(t, err) - - var p pool.Pool - require.NoError(t, ctx.Injector.Resolve(&p)) - require.Equal(t, 1, p.Stats().TxCount) - - ctx.Injector = node.NewInjector() - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'controller.Service'") - - ctx.Injector.Inject(fakeService{err: fake.GetError()}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("while preparing tx: failed to read roster")) - - ctx.Injector.Inject(fakeService{}) - err = action.Execute(ctx) - require.EqualError(t, err, - "while preparing tx: failed to decode member: injector: couldn't find dependency for 'mino.Mino'") - - ctx.Injector.Inject(fake.Mino{}) - ctx.Injector.Inject(fakeCosi{}) - err = action.Execute(ctx) - require.EqualError(t, err, - "while preparing tx: txn manager: injector: couldn't find dependency for 'txn.Manager'") - - ctx.Injector.Inject(fakeTxManager{errSync: fake.GetError()}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("while preparing tx: txn manager: sync")) - - ctx.Injector.Inject(fakeTxManager{errMake: fake.GetError()}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("while preparing tx: transaction: creating transaction")) - - ctx.Injector.Inject(fakeTxManager{}) - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'pool.Pool'") - - ctx.Injector.Inject(badPool{}) - err = action.Execute(ctx) - require.EqualError(t, err, fake.Err("failed to add transaction")) - - events := []ordering.Event{ - {Transactions: []validation.TransactionResult{fakeResult{refused: true}}}, - } - ctx = prepContext(nil) - ctx.Flags.(node.FlagSet)["member"] = "YQ==:YQ==" - ctx.Flags.(node.FlagSet)["wait"] = float64(time.Second) - ctx.Injector.Inject(fakeService{events: events}) - err = action.Execute(ctx) - require.EqualError(t, err, "transaction refused: message") - - ctx.Injector.Inject(fakeService{events: nil}) - err = action.Execute(ctx) - require.EqualError(t, err, "transaction not found after timeout") -} - -func TestDecodeMember(t *testing.T) { - ctx := prepContext(nil) - - _, _, err := decodeMember(ctx, "a:a") - require.EqualError(t, err, "base64 address: illegal base64 data at input byte 0") - - _, _, err = decodeMember(ctx, ":a") - require.EqualError(t, err, "base64 public key: illegal base64 data at input byte 0") - - ctx.Injector = node.NewInjector() - ctx.Injector.Inject(fake.Mino{}) - _, _, err = decodeMember(ctx, ":") - require.EqualError(t, err, "injector: couldn't find dependency for 'cosi.CollectiveSigning'") - - ctx.Injector.Inject(fakeCosi{err: true}) - _, _, err = decodeMember(ctx, ":") - require.EqualError(t, err, fake.Err("failed to decode public key")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func prepContext(calls *fake.Call) node.Context { - ctx := node.Context{ - Injector: node.NewInjector(), - Flags: make(node.FlagSet), - Out: io.Discard, - } - - events := []ordering.Event{ - {Transactions: []validation.TransactionResult{fakeResult{}}}, - } - - ctx.Injector.Inject(fake.Mino{}) - ctx.Injector.Inject(fakeCosi{}) - ctx.Injector.Inject(fakeService{calls: calls, events: events}) - ctx.Injector.Inject(mem.NewPool()) - ctx.Injector.Inject(fakeTxManager{}) - - return ctx -} - -type fakeService struct { - ordering.Service - calls *fake.Call - events []ordering.Event - err error -} - -func (s fakeService) GetRoster() (authority.Authority, error) { - return authority.New(nil, nil), s.err -} - -func (s fakeService) Setup(ctx context.Context, ca crypto.CollectiveAuthority) error { - s.calls.Add(ctx, ca) - return s.err -} - -func (s fakeService) Watch(context.Context) <-chan ordering.Event { - ch := make(chan ordering.Event, len(s.events)) - for _, evt := range s.events { - ch <- evt - } - close(ch) - - return ch -} - -func (s fakeService) Close() error { - return s.err -} - -type fakeCosi struct { - cosi.CollectiveSigning - err bool -} - -func (c fakeCosi) GetPublicKeyFactory() crypto.PublicKeyFactory { - if c.err { - return fake.NewBadPublicKeyFactory() - } - - return fake.NewPublicKeyFactory(fake.PublicKey{}) -} - -func (c fakeCosi) GetSigner() crypto.Signer { - if c.err { - return fake.NewSignerWithPublicKey(fake.NewBadPublicKey()) - } - - return fake.NewSigner() -} - -type fakeTx struct { - txn.Transaction -} - -func (fakeTx) GetNonce() uint64 { - return 0 -} - -func (fakeTx) GetIdentity() access.Identity { - return fake.PublicKey{} -} - -func (fakeTx) GetID() []byte { - return []byte{0xaa} -} - -type fakeResult struct { - validation.TransactionResult - refused bool -} - -func (fakeResult) GetTransaction() txn.Transaction { - return fakeTx{} -} - -func (res fakeResult) GetStatus() (bool, string) { - return !res.refused, "message" -} - -type fakeTxManager struct { - txn.Manager - errMake error - errSync error -} - -func (mgr fakeTxManager) Make(args ...txn.Arg) (txn.Transaction, error) { - return fakeTx{}, mgr.errMake -} - -func (mgr fakeTxManager) Sync() error { - return mgr.errSync -} - -type badPool struct { - pool.Pool -} - -func (p badPool) Add(txn.Transaction) error { - return fake.GetError() -} diff --git a/dela/core/ordering/cosipbft/controller/controller.go b/dela/core/ordering/cosipbft/controller/controller.go deleted file mode 100644 index eaad146..0000000 --- a/dela/core/ordering/cosipbft/controller/controller.go +++ /dev/null @@ -1,258 +0,0 @@ -// Package controller implements a minimal controller for cosipbft. -// -// Documentation Last Review: 13.10.2020 -package controller - -import ( - "encoding" - "path/filepath" - "time" - - "go.dedis.ch/dela/contracts/value" - "go.dedis.ch/dela/crypto" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access/darc" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/ordering/cosipbft" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store/hashtree/binprefix" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn/pool" - poolimpl "go.dedis.ch/dela/core/txn/pool/gossip" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/cosi/threshold" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/crypto/loader" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/gossip" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -const privateKeyFile = "private.key" - -// valueAccessKey is the access key used for the value contract. -var valueAccessKey = [32]byte{2} - -func blsSigner() encoding.BinaryMarshaler { - return bls.NewSigner() -} - -// miniController is a CLI initializer to inject an ordering service that is -// using collective signatures and PBFT for the consensus. -// -// - implements node.Initializer -type miniController struct { - signerFn func() encoding.BinaryMarshaler -} - -// NewController creates a new minimal controller for cosipbft. -func NewController() node.Initializer { - return miniController{ - signerFn: blsSigner, - } -} - -// SetCommands implements node.Initializer. It sets the command to control the -// service. -func (miniController) SetCommands(builder node.Builder) { - cmd := builder.SetCommand("ordering") - cmd.SetDescription("Ordering service administration") - - sub := cmd.SetSubCommand("setup") - sub.SetDescription("Creates a new chain") - sub.SetFlags( - cli.DurationFlag{ - Name: "timeout", - Usage: "maximum amount of time to setup", - Value: 20 * time.Second, - }, - cli.StringSliceFlag{ - Name: "member", - Required: true, - Usage: "one or several member of the new chain", - }, - ) - sub.SetAction(builder.MakeAction(setupAction{})) - - sub = cmd.SetSubCommand("export") - sub.SetDescription("Export the node information") - sub.SetAction(builder.MakeAction(exportAction{})) - - sub = cmd.SetSubCommand("roster") - sub.SetDescription("Roster administration") - - sub = sub.SetSubCommand("add") - sub.SetDescription("Add a member to the chain") - sub.SetFlags( - cli.StringFlag{ - Name: "member", - Required: true, - Usage: "base64 description of the member to add", - }, - cli.DurationFlag{ - Name: "wait", - Usage: "wait for the transaction to be processed", - }, - ) - sub.SetAction(builder.MakeAction(rosterAddAction{})) -} - -// OnStart implements node.Initializer. It starts the ordering components and -// inject them. -func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { - var onet mino.Mino - err := inj.Resolve(&onet) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - signer, err := m.getSigner(flags) - if err != nil { - return xerrors.Errorf("signer: %v", err) - } - - cosi := threshold.NewThreshold(onet.WithSegment("cosi"), signer) - cosi.SetThreshold(threshold.ByzantineThreshold) - - exec := native.NewExecution() - access := darc.NewService(json.NewContext()) - - rosterFac := authority.NewFactory(onet.GetAddressFactory(), cosi.GetPublicKeyFactory()) - cosipbft.RegisterRosterContract(exec, rosterFac, access) - - value.RegisterContract(exec, value.NewContract(valueAccessKey[:], access)) - - txFac := signed.NewTransactionFactory() - vs := simple.NewService(exec, txFac) - - pool, err := poolimpl.NewPool(gossip.NewFlat(onet.WithSegment("pool"), txFac)) - if err != nil { - return xerrors.Errorf("pool: %v", err) - } - - var db kv.DB - err = inj.Resolve(&db) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - tree := binprefix.NewMerkleTree(db, binprefix.Nonce{}) - - param := cosipbft.ServiceParam{ - Mino: onet, - Cosi: cosi, - Validation: vs, - Access: access, - Pool: pool, - DB: db, - Tree: tree, - } - - err = tree.Load() - if err != nil { - return xerrors.Errorf("failed to load tree: %v", err) - } - - genstore := blockstore.NewGenesisDiskStore(db, types.NewGenesisFactory(rosterFac)) - - err = genstore.Load() - if err != nil { - return xerrors.Errorf("failed to load genesis: %v", err) - } - - blockFac := types.NewBlockFactory(vs.GetFactory()) - csFac := authority.NewChangeSetFactory(onet.GetAddressFactory(), cosi.GetPublicKeyFactory()) - linkFac := types.NewLinkFactory(blockFac, cosi.GetSignatureFactory(), csFac) - - blocks := blockstore.NewDiskStore(db, linkFac) - - err = blocks.Load() - if err != nil { - return xerrors.Errorf("failed to load blocks: %v", err) - } - - srvc, err := cosipbft.NewService(param, cosipbft.WithGenesisStore(genstore), cosipbft.WithBlockStore(blocks)) - if err != nil { - return xerrors.Errorf("service: %v", err) - } - - inj.Inject(srvc) - inj.Inject(cosi) - inj.Inject(pool) - inj.Inject(vs) - inj.Inject(exec) - inj.Inject(&access) - - return nil -} - -// OnStop implements node.Initializer. It stops the service and the transaction -// pool. -func (miniController) OnStop(inj node.Injector) error { - var srvc ordering.Service - err := inj.Resolve(&srvc) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - err = srvc.Close() - if err != nil { - return xerrors.Errorf("while closing service: %v", err) - } - - var p pool.Pool - err = inj.Resolve(&p) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - err = p.Close() - if err != nil { - return xerrors.Errorf("while closing pool: %v", err) - } - - return nil -} - -func (m miniController) getSigner(flags cli.Flags) (crypto.AggregateSigner, error) { - loader := loader.NewFileLoader(filepath.Join(flags.Path("config"), privateKeyFile)) - - signerdata, err := loader.LoadOrCreate(generator{newFn: m.signerFn}) - if err != nil { - return nil, xerrors.Errorf("while loading: %v", err) - } - - signer, err := bls.NewSignerFromBytes(signerdata) - if err != nil { - return nil, xerrors.Errorf("while unmarshaling: %v", err) - } - - return signer, nil -} - -// generator is an implementation to generate a private key. -// -// - implements loader.Generator -type generator struct { - newFn func() encoding.BinaryMarshaler -} - -// Generate implements loader.Generator. It returns the marshaled data of a -// private key. -func (g generator) Generate() ([]byte, error) { - signer := g.newFn() - - data, err := signer.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("failed to marshal signer: %v", err) - } - - return data, nil -} diff --git a/dela/core/ordering/cosipbft/controller/controller_test.go b/dela/core/ordering/cosipbft/controller/controller_test.go deleted file mode 100644 index 1daf337..0000000 --- a/dela/core/ordering/cosipbft/controller/controller_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package controller - -import ( - "encoding" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestMinimal_SetCommands(t *testing.T) { - m := NewController() - - b := node.NewBuilder() - m.SetCommands(b) -} - -func TestMinimal_OnStart(t *testing.T) { - flags, dir, clean := makeFlags(t) - defer clean() - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - m := NewController().(miniController) - - inj := node.NewInjector() - inj.Inject(fake.Mino{}) - inj.Inject(db) - - err = m.OnStart(flags, inj) - require.NoError(t, err) -} - -func TestMinimal_MissingMino_OnStart(t *testing.T) { - m := NewController() - - err := m.OnStart(make(node.FlagSet), node.NewInjector()) - require.EqualError(t, err, - "injector: couldn't find dependency for 'mino.Mino'") -} - -func TestMinimal_FailLoadKey_OnStart(t *testing.T) { - flags, _, clean := makeFlags(t) - defer clean() - - m := NewController().(miniController) - - inj := node.NewInjector() - inj.Inject(fake.Mino{}) - inj.Inject(fake.NewInMemoryDB()) - - m.signerFn = badFn - - err := m.OnStart(flags, inj) - require.EqualError(t, err, - fake.Err("signer: while loading: generator failed: failed to marshal signer")) -} - -func TestMinimal_MissingDB_OnStart(t *testing.T) { - flags, _, clean := makeFlags(t) - defer clean() - - m := NewController().(miniController) - - inj := node.NewInjector() - inj.Inject(fake.Mino{}) - - err := m.OnStart(flags, inj) - require.EqualError(t, err, "injector: couldn't find dependency for 'kv.DB'") -} - -func TestMinimal_MalformedKey_OnStart(t *testing.T) { - flags, dir, clean := makeFlags(t) - defer clean() - - m := NewController().(miniController) - - inj := node.NewInjector() - inj.Inject(fake.Mino{}) - inj.Inject(fake.NewInMemoryDB()) - - file, err := os.Create(filepath.Join(dir, privateKeyFile)) - require.NoError(t, err) - - file.Close() - - err = m.OnStart(flags, inj) - require.Error(t, err) - require.Contains(t, err.Error(), "signer: while unmarshaling: ") -} - -func TestMinimal_OnStop(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-test-") - require.NoError(t, err) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - m := NewController() - - fset := make(node.FlagSet) - fset["config"] = dir - - inj := node.NewInjector() - inj.Inject(fake.Mino{}) - inj.Inject(db) - - err = m.OnStart(fset, inj) - require.NoError(t, err) - - err = m.OnStop(inj) - require.NoError(t, err) - - inj = node.NewInjector() - err = m.OnStop(inj) - require.EqualError(t, err, - "injector: couldn't find dependency for 'ordering.Service'") - - inj.Inject(fakeService{err: fake.GetError()}) - err = m.OnStop(inj) - require.EqualError(t, err, fake.Err("while closing service")) - - inj.Inject(fakeService{}) - err = m.OnStop(inj) - require.EqualError(t, err, - "injector: couldn't find dependency for 'pool.Pool'") - - inj.Inject(fakePool{err: fake.GetError()}) - err = m.OnStop(inj) - require.EqualError(t, err, fake.Err("while closing pool")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeFlags(t *testing.T) (cli.Flags, string, func()) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-") - require.NoError(t, err) - - fset := make(node.FlagSet) - fset["config"] = dir - - return fset, dir, func() { os.RemoveAll(dir) } -} - -func badFn() encoding.BinaryMarshaler { - return fake.NewBadHash() -} - -type fakePool struct { - pool.Pool - - err error -} - -func (p fakePool) Close() error { - return p.err -} diff --git a/dela/core/ordering/cosipbft/cosipbft.go b/dela/core/ordering/cosipbft/cosipbft.go deleted file mode 100644 index 647877a..0000000 --- a/dela/core/ordering/cosipbft/cosipbft.go +++ /dev/null @@ -1,797 +0,0 @@ -// Package cosipbft implements an ordering service using collective signatures -// for the consensus. -// -// The consensus follows the PBFT algorithm using collective signatures to -// perform the prepare and commit phases. The leader is orchestrating the -// protocol and the followers wait for incoming messages to update their own -// state machines and reply with signatures when the leader candidate is valid. -// If the leader fails to send a candidate, or finalize it, the followers will -// timeout after some time and move to a view change state. -// -// The view change procedure is always waiting on the leader+1 confirmation -// before moving to leader+2, leader+3, etc. It means that if not enough nodes -// are online to create a block, the round will fail until enough wakes up and -// confirm leader+1. If leader+1 fails to create a block within the round -// timeout, a new view change starts for leader+2 and so on until a block is -// created. -// -// Before each PBFT round, a synchronization is run from the leader to allow -// nodes that have fallen behind (or are new) to catch missing blocks. Only a -// PBFT threshold of nodes needs to confirm a hard synchronization (having all -// the blocks) for the round to proceed, but others will keep catching up. -// -// Related Papers: -// -// Enhancing Bitcoin Security and Performance with Strong Consistency via -// Collective Signing (2016) -// https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_kokoris-kogias.pdf -// -// Documentation Last Review: 12.10.2020 -package cosipbft - -import ( - "context" - "fmt" - "math" - "time" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync" - "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/cosi/threshold" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -const ( - // DefaultRoundTimeout is the maximum round time the service waits - // for an event to happen. - DefaultRoundTimeout = 1 * time.Second - - // DefaultFailedRoundTimeout is the maximum round time the service waits - // for an event to happen, after a round has failed, thus letting time - // for a view change to establish a new leader. - // DefaultFailedRoundTimeout is generally bigger than DefaultRoundTimeout - DefaultFailedRoundTimeout = 2 * time.Second - - // DefaultTransactionTimeout is the maximum allowed age of transactions - // before a view change is executed. - DefaultTransactionTimeout = 10 * time.Second - - // RoundWait is the constant value of the exponential backoff use between - // round failures. - RoundWait = 5 * time.Millisecond - - // RoundMaxWait is the maximum amount for the backoff. - RoundMaxWait = 5 * time.Minute - - rpcName = "cosipbft" -) - -// RegisterRosterContract registers the native smart contract to update the -// roster to the given service. -func RegisterRosterContract(exec *native.Service, rFac authority.Factory, srvc access.Service) { - contract := viewchange.NewContract(keyRoster[:], keyAccess[:], rFac, srvc) - - viewchange.RegisterContract(exec, contract) -} - -// Service is an ordering service using collective signatures combined with PBFT -// to create a chain of blocks. -// -// - implements ordering.Service -type Service struct { - *processor - - me mino.Address - rpc mino.RPC - actor cosi.Actor - val validation.Service - verifierFac crypto.VerifierFactory - - timeoutRound time.Duration - timeoutRoundAfterFailure time.Duration - transactionTimeout time.Duration - - events chan ordering.Event - closing chan struct{} - closed chan struct{} - failedRound bool -} - -type serviceTemplate struct { - hashFac crypto.HashFactory - blocks blockstore.BlockStore - genesis blockstore.GenesisStore -} - -// ServiceOption is the type of option to set some fields of the service. -type ServiceOption func(*serviceTemplate) - -// WithGenesisStore is an option to set the genesis store. -func WithGenesisStore(store blockstore.GenesisStore) ServiceOption { - return func(tmpl *serviceTemplate) { - tmpl.genesis = store - } -} - -// WithBlockStore is an option to set the block store. -func WithBlockStore(store blockstore.BlockStore) ServiceOption { - return func(tmpl *serviceTemplate) { - tmpl.blocks = store - } -} - -// WithHashFactory is an option to set the hash factory used by the service. -func WithHashFactory(fac crypto.HashFactory) ServiceOption { - return func(tmpl *serviceTemplate) { - tmpl.hashFac = fac - } -} - -// ServiceParam is the different components to provide to the service. All the -// fields are mandatory and it will panic if any is nil. -type ServiceParam struct { - Mino mino.Mino - Cosi cosi.CollectiveSigning - Validation validation.Service - Access access.Service - Pool pool.Pool - Tree hashtree.Tree - DB kv.DB -} - -// NewService starts a new ordering service. -func NewService(param ServiceParam, opts ...ServiceOption) (*Service, error) { - tmpl := serviceTemplate{ - hashFac: crypto.NewSha256Factory(), - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - } - - for _, opt := range opts { - opt(&tmpl) - } - - proc := newProcessor() - proc.hashFactory = tmpl.hashFac - proc.blocks = tmpl.blocks - proc.genesis = tmpl.genesis - proc.pool = param.Pool - proc.rosterFac = authority.NewFactory(param.Mino.GetAddressFactory(), param.Cosi.GetPublicKeyFactory()) - proc.tree = blockstore.NewTreeCache(param.Tree) - proc.access = param.Access - proc.logger = dela.Logger.With().Str("addr", param.Mino.GetAddress().String()).Logger() - - pcparam := pbft.StateMachineParam{ - Logger: proc.logger, - Validation: param.Validation, - Signer: param.Cosi.GetSigner(), - VerifierFactory: param.Cosi.GetVerifierFactory(), - Blocks: tmpl.blocks, - Genesis: tmpl.genesis, - Tree: proc.tree, - AuthorityReader: proc.readRoster, - DB: param.DB, - } - - proc.pbftsm = pbft.NewStateMachine(pcparam) - - blockFac := types.NewBlockFactory(param.Validation.GetFactory()) - csFac := authority.NewChangeSetFactory(param.Mino.GetAddressFactory(), param.Cosi.GetPublicKeyFactory()) - linkFac := types.NewLinkFactory(blockFac, param.Cosi.GetSignatureFactory(), csFac) - chainFac := types.NewChainFactory(linkFac) - - syncparam := blocksync.SyncParam{ - Mino: param.Mino, - Blocks: tmpl.blocks, - Genesis: tmpl.genesis, - PBFT: proc.pbftsm, - LinkFactory: linkFac, - ChainFactory: chainFac, - VerifierFactory: param.Cosi.GetVerifierFactory(), - } - - bs := blocksync.NewSynchronizer(syncparam) - - proc.sync = bs - - fac := types.NewMessageFactory( - types.NewGenesisFactory(proc.rosterFac), - blockFac, - param.Mino.GetAddressFactory(), - param.Cosi.GetSignatureFactory(), - csFac, - ) - - proc.MessageFactory = fac - - actor, err := param.Cosi.Listen(proc) - if err != nil { - return nil, xerrors.Errorf("creating cosi failed: %v", err) - } - - s := &Service{ - processor: proc, - me: param.Mino.GetAddress(), - rpc: mino.MustCreateRPC(param.Mino, rpcName, proc, fac), - actor: actor, - val: param.Validation, - verifierFac: param.Cosi.GetVerifierFactory(), - timeoutRound: DefaultRoundTimeout, - timeoutRoundAfterFailure: DefaultFailedRoundTimeout, - transactionTimeout: DefaultTransactionTimeout, - events: make(chan ordering.Event, 1), - closing: make(chan struct{}), - closed: make(chan struct{}), - } - - // Pool will filter the transaction that are already accepted by this - // service. - param.Pool.AddFilter(poolFilter{tree: proc.tree, srvc: param.Validation}) - - go s.main() - - go s.watchBlocks() - - if s.genesis.Exists() { - // If the genesis already exists, the service can start right away to - // participate in the chain. - close(s.started) - } - - return s, nil -} - -// Setup creates a genesis block and sends it to the collective authority. -func (s *Service) Setup(ctx context.Context, ca crypto.CollectiveAuthority) error { - err := s.storeGenesis(authority.FromAuthority(ca), nil) - if err != nil { - return xerrors.Errorf("creating genesis: %v", err) - } - - genesis, err := s.genesis.Get() - if err != nil { - return xerrors.Errorf("failed to read genesis: %v", err) - } - - resps, err := s.rpc.Call(ctx, types.NewGenesisMessage(genesis), ca) - if err != nil { - return xerrors.Errorf("sending genesis: %v", err) - } - - for resp := range resps { - _, err := resp.GetMessageOrError() - if err != nil { - return xerrors.Errorf("one request failed: %v", err) - } - } - - s.logger.Info(). - Int("roster", ca.Len()). - Stringer("digest", genesis.GetHash()). - Msg("new chain has been created") - - return nil -} - -// GetProof implements ordering.Service. It returns the proof of absence or -// inclusion for the latest block. The proof integrity is not verified as this -// is assumed the node is acting correctly so the data is anyway consistent. The -// proof must be verified by the caller when leaving the trusted environment, -// for instance when the proof is sent over the network. -func (s *Service) GetProof(key []byte) (ordering.Proof, error) { - tree, unlock := s.tree.GetWithLock() - defer unlock() - - path, err := tree.GetPath(key) - if err != nil { - return nil, xerrors.Errorf("reading path: %v", err) - } - - // The chain is fetched while having the lock of the tree cache so that - // there is no race between the two stores when finalizing a block. - chain, err := s.blocks.GetChain() - if err != nil { - return nil, xerrors.Errorf("reading chain: %v", err) - } - - return newProof(path, chain), nil -} - -// GetStore implements ordering.Service. It returns the current tree as a -// read-only storage. -func (s *Service) GetStore() store.Readable { - return s.tree.Get() -} - -// GetRoster returns the current roster of the service. -func (s *Service) GetRoster() (authority.Authority, error) { - return s.getCurrentRoster() -} - -// Watch implements ordering.Service. It returns a channel that will be -// populated with new incoming blocks and some information about them. The -// channel must be listened at all time and the context must be closed when -// done. -func (s *Service) Watch(ctx context.Context) <-chan ordering.Event { - obs := observer{ch: make(chan ordering.Event, 1)} - - s.watcher.Add(obs) - - go func() { - <-ctx.Done() - s.watcher.Remove(obs) - close(obs.ch) - }() - - return obs.ch -} - -// Close implements ordering.Service. It gracefully closes the service. It will -// announce the closing request and wait for the current to end before -// returning. -func (s *Service) Close() error { - close(s.closing) - <-s.closed - - return nil -} - -func (s *Service) watchBlocks() { - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - <-s.closing - cancel() - }() - - linkCh := s.blocks.Watch(ctx) - - for link := range linkCh { - // 1. Remove the transactions from the pool to avoid duplicates. - for _, res := range link.GetBlock().GetData().GetTransactionResults() { - err := s.pool.Remove(res.GetTransaction()) - if err != nil { - s.logger.Err(err).Msg("removing transaction") - } - } - - // 2. Update the current membership. - err := s.refreshRoster() - if err != nil { - s.logger.Err(err).Msg("roster refresh failed") - } - - event := ordering.Event{ - Index: link.GetBlock().GetIndex(), - Transactions: link.GetBlock().GetData().GetTransactionResults(), - } - - // 3. Notify the main loop that a new block has been created, but ignore - // if the channel is busy. - select { - case s.events <- event: - default: - } - - // 4. Notify the new block to potential listeners. - s.watcher.Notify(event) - - s.logger.Info(). - Uint64("index", link.GetBlock().GetIndex()). - Stringer("root", link.GetBlock().GetTreeRoot()). - Msg("block event") - } -} - -func (s *Service) refreshRoster() error { - roster, err := s.getCurrentRoster() - if err != nil { - return xerrors.Errorf("reading roster: %v", err) - } - - err = s.pool.SetPlayers(roster) - if err != nil { - return xerrors.Errorf("updating tx pool: %v", err) - } - - return nil -} - -func (s *Service) main() error { - defer close(s.closed) - - select { - case <-s.started: - // A genesis block has been set, the node will then follow the chain - // related to it. - s.logger.Info().Msg("node has started following the chain") - - case <-s.closing: - return nil - } - - // Update the components that need to learn about the participants like the - // transaction pool. - err := s.refreshRoster() - if err != nil { - return xerrors.Errorf("refreshing roster: %v", err) - } - - s.logger.Debug().Msg("node has started") - - backoff := float64(0) - - for { - // When a round failure occurs, it sleeps with a given backoff to give a - // chance to the system to recover without exhausting the resources. - time.Sleep(calculateBackoff(backoff)) - - select { - case <-s.closing: - return nil - - default: - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - select { - case <-s.closing: - cancel() - case <-ctx.Done(): - } - }() - - err := s.doRound(ctx) - cancel() - - if err != nil { - if calculateBackoff(backoff+1) < RoundMaxWait { - backoff++ - } - - s.logger.Err(err).Msg("round failed") - } else { - backoff = 0 - } - } - } -} - -func (s *Service) doRound(ctx context.Context) error { - roster, err := s.getCurrentRoster() - if err != nil { - return xerrors.Errorf("reading roster: %v", err) - } - - timeout := s.timeoutRound - if s.failedRound { - timeout = s.timeoutRoundAfterFailure - } - - leader, err := s.pbftsm.GetLeader() - if err != nil { - return xerrors.Errorf("reading leader: %v", err) - } - - if s.me.Equal(leader) { - s.logger.Debug().Msgf("Starting a leader round with a %.1f seconds timeout", timeout.Seconds()) - return s.doLeaderRound(ctx, roster, timeout) - } - - s.logger.Debug().Msgf("Starting a follower round with a %.1f seconds timeout", timeout.Seconds()) - return s.doFollowerRound(ctx, roster) -} - -func (s *Service) doLeaderRound(ctx context.Context, roster authority.Authority, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - s.logger.Debug().Uint64("index", s.blocks.Len()).Msg("round has started") - - // Send a synchronization to the roster so that they can learn about the - // latest block of the chain. - err := s.sync.Sync(ctx, roster, blocksync.Config{MinHard: threshold.ByzantineThreshold(roster.Len())}) - if err != nil { - return xerrors.Errorf("sync failed: %v", err) - } - - s.logger.Debug().Uint64("index", s.blocks.Len()).Msg("pbft has started") - - err = s.doPBFT(ctx) - if err != nil { - return xerrors.Errorf("pbft failed: %v", err) - } - - // The leader can be a new leader coming from a view change, so it resets - // the value as a round has finished. - s.failedRound = false - - return nil -} - -func (s *Service) doFollowerRound(ctx context.Context, roster authority.Authority) error { - // A follower has to wait for the new block, or the round timeout, to proceed. - select { - case <-time.After(s.timeoutRound): - if !s.roundHasFailed() { - return nil - } - - s.logger.Info().Msg("round has failed, do a view change !") - - s.pool.ResetStats() // avoid infinite view change - - view, err := s.pbftsm.Expire(s.me) // start the viewChange - if err != nil { - return xerrors.Errorf("pbft expire failed: %v", err) - } - - viewMsg := types.NewViewMessage(view.GetID(), view.GetLeader(), view.GetSignature()) - - ctx, cancel := context.WithTimeout(ctx, s.timeoutRound) - defer cancel() - - resps, err := s.rpc.Call(ctx, viewMsg, roster) - if err != nil { - return xerrors.Errorf("rpc failed to send views: %v", err) - } - - for resp := range resps { - _, err = resp.GetMessageOrError() - if err != nil { - s.logger.Warn().Err(err).Str("to", resp.GetFrom().String()).Msg("view propagation failure") - } - } - - statesCh := s.pbftsm.Watch(ctx) - - state := s.pbftsm.GetState() - var more bool - - for state == pbft.ViewChangeState { - state, more = <-statesCh - if !more { - return xerrors.New("view change failed") - } - } - - s.logger.Info().Msgf("view change for %d", viewMsg.GetLeader()) - - return nil - - case <-s.events: - // As a child, a block has been committed thus the previous view - // change succeeded. - s.failedRound = false - - // A block has been created meaning that the round is over. - return nil - - case <-s.closing: - return nil - } -} - -func (s *Service) roundHasFailed() bool { - stats := s.pool.Stats() - - if stats.TxCount == 0 { - // When the pool of transactions is empty, the round is aborted - // and everything restart. - return false - } - - if time.Since(stats.OldestTx) > s.transactionTimeout { - s.logger.Warn().Msg("found a rotten transaction") - s.failedRound = true - } - - return s.failedRound -} - -func (s *Service) doPBFT(ctx context.Context) error { - var id types.Digest - var block types.Block - - if s.pbftsm.GetState() >= pbft.CommitState { - // The node is already committed to a block, which means enough nodes - // have accepted, but somehow the finalization failed. - id, block = s.pbftsm.GetCommit() - } else { - txs := s.pool.Gather(ctx, pool.Config{Min: 1}) - if len(txs) == 0 { - s.logger.Debug().Msg("no transaction in pool") - - return nil - } - - s.logger.Debug(). - Int("num", len(txs)). - Msg("transactions have been found") - - if ctx.Err() != nil { - // Don't bother trying PBFT if the context is done. - return ctx.Err() - } - - data, root, err := s.prepareData(txs) - if err != nil { - return xerrors.Errorf("failed to prepare data: %v", err) - } - - block, err = types.NewBlock( - data, - types.WithTreeRoot(root), - types.WithIndex(uint64(s.blocks.Len())), - types.WithHashFactory(s.hashFactory)) - - if err != nil { - return xerrors.Errorf("creating block failed: %v", err) - } - - id, err = s.pbftsm.Prepare(s.me, block) - if err != nil { - return xerrors.Errorf("pbft prepare failed: %v", err) - } - } - - roster, err := s.getCurrentRoster() - if err != nil { - return xerrors.Errorf("read roster failed: %v", err) - } - - // 1. Prepare phase - req := types.NewBlockMessage(block, s.prepareViews()) - - sig, err := s.actor.Sign(ctx, req, roster) - if err != nil { - return xerrors.Errorf("prepare signature failed: %v", err) - } - - s.logger.Debug().Str("signature", fmt.Sprintf("%v", sig)).Msg("prepare done") - - // 2. Commit phase - commit := types.NewCommit(id, sig) - - sig, err = s.actor.Sign(ctx, commit, roster) - if err != nil { - return xerrors.Errorf("commit signature failed: %v", err) - } - - s.logger.Debug().Str("signature", fmt.Sprintf("%v", sig)).Msg("commit done") - - // 3. Propagation phase - done := types.NewDone(id, sig) - - resps, err := s.rpc.Call(ctx, done, roster) - if err != nil { - return xerrors.Errorf("propagation failed: %v", err) - } - - for resp := range resps { - _, err = resp.GetMessageOrError() - if err != nil { - s.logger.Warn().Err(err).Msg("propagation failed") - } - } - - // 4. Wake up new participants so that they can learn about the chain. - err = s.wakeUp(ctx, roster) - if err != nil { - return xerrors.Errorf("wake up failed: %v", err) - } - - return nil -} - -func (s *Service) prepareViews() map[mino.Address]types.ViewMessage { - views := s.pbftsm.GetViews() - msgs := make(map[mino.Address]types.ViewMessage) - - for addr, view := range views { - msgs[addr] = types.NewViewMessage(view.GetID(), view.GetLeader(), view.GetSignature()) - } - - return msgs -} - -func (s *Service) prepareData(txs []txn.Transaction) (data validation.Result, id types.Digest, err error) { - var stageTree hashtree.StagingTree - - stageTree, err = s.tree.Get().Stage(func(snap store.Snapshot) error { - data, err = s.val.Validate(snap, txs) - if err != nil { - return xerrors.Errorf("validation failed: %v", err) - } - - return nil - }) - - if err != nil { - err = xerrors.Errorf("staging tree failed: %v", err) - return - } - - copy(id[:], stageTree.GetRoot()) - - return -} - -func (s *Service) wakeUp(ctx context.Context, ro authority.Authority) error { - newRoster, err := s.getCurrentRoster() - if err != nil { - return xerrors.Errorf("read roster failed: %v", err) - } - - changeset := ro.Diff(newRoster) - - genesis, err := s.genesis.Get() - if err != nil { - return xerrors.Errorf("read genesis failed: %v", err) - } - - resps, err := s.rpc.Call(ctx, types.NewGenesisMessage(genesis), mino.NewAddresses(changeset.GetNewAddresses()...)) - if err != nil { - return xerrors.Errorf("rpc failed: %v", err) - } - - for resp := range resps { - _, err := resp.GetMessageOrError() - if err != nil { - s.logger.Warn().Err(err).Msg("wake up failed") - } - } - - return nil -} - -type observer struct { - ch chan ordering.Event -} - -func (obs observer) NotifyCallback(event interface{}) { - obs.ch <- event.(ordering.Event) -} - -func calculateBackoff(backoff float64) time.Duration { - return time.Duration(math.Pow(2, backoff)) * RoundWait -} - -// PoolFilter is a filter to drop transactions which are already included in the -// block or simply with an invalid nonce. -// -// - implements pool.Filter -type poolFilter struct { - tree blockstore.TreeCache - srvc validation.Service -} - -// Accept implements pool.Filter. It returns an error if the transaction exists -// already or the nonce is invalid. -func (f poolFilter) Accept(tx txn.Transaction, leeway validation.Leeway) error { - s := f.tree.Get() - - err := f.srvc.Accept(s, tx, leeway) - if err != nil { - return xerrors.Errorf("unacceptable transaction: %v", err) - } - - return nil -} diff --git a/dela/core/ordering/cosipbft/cosipbft_test.go b/dela/core/ordering/cosipbft/cosipbft_test.go deleted file mode 100644 index e504565..0000000 --- a/dela/core/ordering/cosipbft/cosipbft_test.go +++ /dev/null @@ -1,1157 +0,0 @@ -package cosipbft - -import ( - "context" - "fmt" - "os" - "path/filepath" - "runtime/debug" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/access/darc" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree/binprefix" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - poolgossip "go.dedis.ch/dela/core/txn/pool/gossip" - "go.dedis.ch/dela/core/txn/pool/mem" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/cosi/flatcosi" - "go.dedis.ch/dela/cosi/threshold" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/gossip" - "go.dedis.ch/dela/mino/minoch" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" -) - -func TestService_Scenario_Basic(t *testing.T) { - nodes, ro, clean := makeAuthority(t, 5) - defer clean() - - signer := nodes[0].signer - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - initial := ro.Take(mino.RangeFilter(0, 4)).(crypto.CollectiveAuthority) - - err := nodes[0].service.Setup(ctx, initial) - require.NoError(t, err) - - events := nodes[2].service.Watch(ctx) - - err = nodes[0].pool.Add(makeTx(t, 0, signer)) - require.NoError(t, err) - - evt := waitEvent(t, events, 2*DefaultRoundTimeout) - require.Equal(t, uint64(0), evt.Index) - - err = nodes[1].pool.Add(makeTx(t, 1, signer)) - require.NoError(t, err) - - evt = waitEvent(t, events, 20*DefaultRoundTimeout) - require.Equal(t, uint64(1), evt.Index) - - err = nodes[1].pool.Add(makeRosterTx(t, 2, ro, signer)) - require.NoError(t, err) - - evt = waitEvent(t, events, 20*DefaultRoundTimeout) - require.Equal(t, uint64(2), evt.Index) - for i := 0; i < 3; i++ { - err = nodes[1].pool.Add(makeTx(t, uint64(i+3), signer)) - require.NoError(t, err) - - evt = waitEvent(t, events, 20*DefaultRoundTimeout) - require.Equal(t, uint64(i+3), evt.Index) - } - - proof, err := nodes[0].service.GetProof(keyRoster[:]) - require.NoError(t, err) - require.NotNil(t, proof.GetValue()) - - require.Equal(t, keyRoster[:], proof.GetKey()) - require.NotNil(t, proof.GetValue()) - - checkProof(t, proof.(Proof), nodes[0].service) -} - -func TestService_Scenario_ViewChange(t *testing.T) { - nodes, ro, clean := makeAuthority(t, 4) - defer clean() - - // Simulate an issue with the leader transaction pool so that it does not - // receive any of them. - nodes[0].pool.Close() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := nodes[1].service.Setup(ctx, ro) - require.NoError(t, err) - - events := nodes[2].service.Watch(ctx) - - // Other nodes will detect a transaction but no block incoming => timeout - err = nodes[1].pool.Add(makeTx(t, 0, nodes[1].signer)) - require.NoError(t, err) - - evt := waitEvent(t, events, DefaultTransactionTimeout+2*time.Second) - require.Equal(t, uint64(0), evt.Index) -} - -func TestService_Scenario_ViewChangeRequest(t *testing.T) { - nodes, ro, clean := makeAuthority(t, 4) - defer clean() - nodes[3].service.pool = fakePool{ - Pool: nodes[3].service.pool, - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := nodes[1].service.Setup(ctx, ro) - require.NoError(t, err) - - leader, err := nodes[0].service.pbftsm.GetLeader() - require.NoError(t, err) - require.Equal(t, leader, nodes[0].onet.GetAddress()) - - // let enough time for a round to run - time.Sleep(DefaultRoundTimeout + 100*time.Millisecond) - - require.Equal(t, nodes[3].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[2].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[1].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[0].service.pbftsm.GetState(), pbft.ViewChangeState) - - leader, err = nodes[0].service.pbftsm.GetLeader() - require.NoError(t, err) - // assert that node 0 is still the leader - require.Equal(t, leader, nodes[0].onet.GetAddress()) -} - -func TestService_Scenario_NoViewChangeRequest(t *testing.T) { - nodes, ro, clean := makeAuthority(t, 4) - defer clean() - - signer := nodes[0].signer - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - initial := ro.Take(mino.RangeFilter(0, 4)).(crypto.CollectiveAuthority) - - err := nodes[0].service.Setup(ctx, initial) - require.NoError(t, err) - - leader, err := nodes[0].service.pbftsm.GetLeader() - require.NoError(t, err) - require.Equal(t, leader, nodes[0].onet.GetAddress()) - - err = nodes[0].pool.Add(makeTx(t, 0, signer)) - require.NoError(t, err) - - // let enough time for a round to run - time.Sleep(DefaultRoundTimeout + 100*time.Millisecond) - - require.NotEqual(t, nodes[3].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[2].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[1].service.pbftsm.GetState(), pbft.ViewChangeState) - require.NotEqual(t, nodes[0].service.pbftsm.GetState(), pbft.ViewChangeState) - - leader, err = nodes[0].service.pbftsm.GetLeader() - require.NoError(t, err) - // assert that node 0 is still the leader - require.Equal(t, leader, nodes[0].onet.GetAddress()) -} - -// Test that a block committed will be eventually finalized even if the -// propagation failed. -// -// Expected log warnings and errors: -// - timeout from the followers -// - block not from the leader -// - round failed on node 0 -// - mismatch state viewchange != (initial|prepare) -func TestService_Scenario_FinalizeFailure(t *testing.T) { - nodes, ro, clean := makeAuthority(t, 4) - defer clean() - - filter := func(req mino.Request) bool { - switch req.Message.(type) { - case types.DoneMessage: - // Ignore propagation from node 0 which produces a committed block - // without finalization. Node 1 will take over and finalize it. - return !req.Address.Equal(nodes[0].service.me) - default: - return true - } - } - - for i := 0; i < 4; i++ { - nodes[i].onet.AddFilter(filter) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := nodes[0].service.Setup(ctx, ro) - require.NoError(t, err) - - events := nodes[1].service.Watch(ctx) - - err = nodes[0].pool.Add(makeTx(t, 0, nodes[0].signer)) - require.NoError(t, err) - - evt := waitEvent(t, events, DefaultTransactionTimeout+2*time.Second) - require.Equal(t, uint64(0), evt.Index) -} - -func TestService_New(t *testing.T) { - param := ServiceParam{ - Mino: fake.Mino{}, - Cosi: flatcosi.NewFlat(fake.Mino{}, fake.NewAggregateSigner()), - Tree: fakeTree{}, - Validation: simple.NewService(nil, nil), - Pool: badPool{}, - } - - genesis := blockstore.NewGenesisStore() - genesis.Set(types.Genesis{}) - - opts := []ServiceOption{ - WithHashFactory(fake.NewHashFactory(&fake.Hash{})), - WithGenesisStore(genesis), - WithBlockStore(blockstore.NewInMemory()), - } - - srvc, err := NewService(param, opts...) - require.NoError(t, err) - require.NotNil(t, srvc) - - <-srvc.closed - - param.Cosi = badCosi{} - _, err = NewService(param) - require.EqualError(t, err, fake.Err("creating cosi failed")) -} - -func TestService_Setup(t *testing.T) { - rpc := fake.NewRPC() - - srvc := &Service{processor: newProcessor()} - srvc.rpc = rpc - srvc.hashFactory = crypto.NewSha256Factory() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.genesis = blockstore.NewGenesisStore() - srvc.access = fakeAccess{} - - rpc.Done() - - a := fake.NewAuthority(3, fake.NewSigner) - ctx := context.Background() - - err := srvc.Setup(ctx, a) - require.NoError(t, err) - - _, more := <-srvc.started - require.False(t, more) - - genesis, err := srvc.genesis.Get() - require.NoError(t, err) - require.Equal(t, 3, genesis.GetRoster().Len()) -} - -func TestService_AlreadySet_Setup(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.access = fakeAccess{} - srvc.genesis = blockstore.NewGenesisStore() - srvc.genesis.Set(types.Genesis{}) - - a := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.Setup(ctx, a) - require.EqualError(t, err, - "creating genesis: set genesis failed: genesis block is already set") -} - -func TestService_FailReadGenesis_Setup(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.access = fakeAccess{} - srvc.genesis = fakeGenesisStore{errGet: fake.GetError()} - - a := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.Setup(ctx, a) - require.EqualError(t, err, fake.Err("failed to read genesis")) -} - -func TestService_FailPropagate_Setup(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.access = fakeAccess{} - srvc.genesis = blockstore.NewGenesisStore() - srvc.rpc = fake.NewBadRPC() - - a := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.Setup(ctx, a) - require.EqualError(t, err, fake.Err("sending genesis")) -} - -func TestService_RequestFailure_Setup(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.access = fakeAccess{} - srvc.genesis = blockstore.NewGenesisStore() - - rpc := fake.NewRPC() - rpc.SendResponseWithError(fake.NewAddress(1), fake.GetError()) - srvc.rpc = rpc - - a := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.Setup(ctx, a) - require.EqualError(t, err, fake.Err("one request failed")) -} - -func TestService_Main(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.closing = make(chan struct{}) - srvc.closed = make(chan struct{}) - - close(srvc.closing) - - err := srvc.main() - require.NoError(t, err) - - srvc.tree = blockstore.NewTreeCache(fakeTree{err: fake.GetError()}) - srvc.closing = make(chan struct{}) - srvc.started = make(chan struct{}) - srvc.closed = make(chan struct{}) - close(srvc.started) - err = srvc.main() - require.EqualError(t, err, fake.Err("refreshing roster: reading roster: read from tree")) - - srvc.tree.Set(fakeTree{}) - srvc.pool = badPool{} - srvc.closed = make(chan struct{}) - err = srvc.main() - require.EqualError(t, err, fake.Err("refreshing roster: updating tx pool")) - - logger, wait := fake.WaitLog("round failed", 2*time.Second) - go func() { - wait(t) - close(srvc.closing) - }() - - srvc.logger = logger - srvc.pool = mem.NewPool() - srvc.pbftsm = fakeSM{errLeader: fake.GetError()} - srvc.closed = make(chan struct{}) - err = srvc.main() - require.NoError(t, err) -} - -func TestService_DoRound(t *testing.T) { - rpc := fake.NewRPC() - ch := make(chan pbft.State) - - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - rpc: rpc, - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - closing: make(chan struct{}), - } - srvc.blocks = blockstore.NewInMemory() - srvc.sync = fakeSync{} - srvc.pool = mem.NewPool() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{ - state: pbft.ViewChangeState, - ch: ch, - } - - rpc.SendResponse(fake.NewAddress(3), nil) - rpc.SendResponseWithError(fake.NewAddress(2), fake.GetError()) - rpc.Done() - - ctx := context.Background() - - // Round with timeout but no transaction in the pool. - err := srvc.doRound(ctx) - require.NoError(t, err) - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - go func() { - ch <- pbft.InitialState - close(ch) - }() - - // Round with timeout and a transaction in the pool. - err = srvc.doRound(ctx) - require.NoError(t, err) -} - -func TestService_ViewchangeFailed_DoRound(t *testing.T) { - pbftsm := fakeSM{ - state: pbft.ViewChangeState, - ch: make(chan pbft.State), - } - // Stuck to view change state thus causing the view to fail. - close(pbftsm.ch) - - rpc := fake.NewRPC() - rpc.Done() - - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - rpc: rpc, - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.blocks = blockstore.NewInMemory() - srvc.pool = mem.NewPool() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = pbftsm - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, "view change failed") -} - -func TestService_FailPBFTExpire_DoRound(t *testing.T) { - rpc := fake.NewRPC() - rpc.Done() - - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - rpc: rpc, - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.blocks = blockstore.NewInMemory() - srvc.pool = mem.NewPool() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{ - err: fake.GetError(), - state: pbft.InitialState, - } - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, fake.Err("pbft expire failed")) -} - -func TestService_FailSendViews_DoRound(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - rpc: fake.NewBadRPC(), - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.blocks = blockstore.NewInMemory() - srvc.pool = mem.NewPool() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{} - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, fake.Err("rpc failed to send views")) -} - -func TestService_FailReadRoster_DoRound(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = badRosterFac{} - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, fake.Err("reading roster: decode failed")) -} - -func TestService_FailReadLeader_DoRound(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(1), - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{errLeader: fake.GetError()} - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, fake.Err("reading leader")) -} - -func TestService_FailSync_DoRound(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(0), - timeoutRound: time.Millisecond, - timeoutRoundAfterFailure: time.Millisecond, - } - - srvc.blocks = blockstore.NewInMemory() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{} - srvc.sync = fakeSync{err: fake.GetError()} - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, fake.Err("sync failed")) -} - -func TestService_FailPBFT_DoRound(t *testing.T) { - srvc := &Service{ - processor: newProcessor(), - me: fake.NewAddress(0), - timeoutRound: DefaultRoundTimeout, - timeoutRoundAfterFailure: DefaultFailedRoundTimeout, - val: fakeValidation{err: fake.GetError()}, - } - - srvc.blocks = blockstore.NewInMemory() - srvc.pool = mem.NewPool() - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.pbftsm = fakeSM{} - srvc.sync = fakeSync{} - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doRound(ctx) - require.EqualError(t, err, - fake.Err("pbft failed: failed to prepare data: staging tree failed: validation failed")) -} - -func TestService_DoPBFT(t *testing.T) { - rpc := fake.NewRPC() - - srvc := &Service{processor: newProcessor()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.val = fakeValidation{} - srvc.blocks = blockstore.NewInMemory() - srvc.genesis = blockstore.NewGenesisStore() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.pbftsm = fakeSM{} - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.actor = fakeCosiActor{} - srvc.pool = mem.NewPool() - srvc.rpc = rpc - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - rpc.SendResponseWithError(fake.NewAddress(5), fake.GetError()) - rpc.Done() - srvc.genesis.Set(types.Genesis{}) - - // Context timed out and no transaction are in the pool. - err := srvc.doPBFT(ctx) - require.NoError(t, err) - - // This time the gathering succeeds. - ctx = context.Background() - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - err = srvc.doPBFT(ctx) - require.NoError(t, err) -} - -func TestService_ContextCanceld_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{err: fake.GetError()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, "context canceled") -} - -func TestService_FailValidation_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{err: fake.GetError()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - srvc.val = fakeValidation{err: fake.GetError()} - err := srvc.doPBFT(ctx) - require.EqualError(t, err, - fake.Err("failed to prepare data: staging tree failed: validation failed")) -} - -func TestService_FailCreateBlock_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - srvc.blocks = blockstore.NewInMemory() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, - fake.Err("creating block failed: fingerprint failed: couldn't write index")) -} - -func TestService_FailPrepare_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{err: fake.GetError()} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, fake.Err("pbft prepare failed")) -} - -func TestService_FailReadRoster_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{err: fake.GetError()}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, fake.Err("read roster failed: read from tree")) -} - -func TestService_FailPrepareSig_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - srvc.actor = fakeCosiActor{err: fake.GetError()} - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, fake.Err("prepare signature failed")) -} - -func TestService_FailCommitSign_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - srvc.actor = fakeCosiActor{ - err: fake.GetError(), - counter: fake.NewCounter(1), - } - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, fake.Err("commit signature failed")) -} - -func TestService_FailPropagation_DoPBFT(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - srvc.actor = fakeCosiActor{} - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.rpc = fake.NewBadRPC() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, fake.Err("propagation failed")) -} - -func TestService_FailWakeUp_DoPBFT(t *testing.T) { - rpc := fake.NewRPC() - rpc.Done() - - srvc := &Service{processor: newProcessor()} - srvc.val = fakeValidation{} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.pbftsm = fakeSM{} - srvc.pool = mem.NewPool() - srvc.hashFactory = crypto.NewSha256Factory() - srvc.blocks = blockstore.NewInMemory() - srvc.actor = fakeCosiActor{} - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.rpc = rpc - srvc.genesis = blockstore.NewGenesisStore() - - srvc.pool.Add(makeTx(t, 0, fake.NewSigner())) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - err := srvc.doPBFT(ctx) - require.EqualError(t, err, "wake up failed: read genesis failed: missing genesis block") -} - -func TestService_WakeUp(t *testing.T) { - rpc := fake.NewRPC() - - srvc := &Service{processor: newProcessor()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.genesis = blockstore.NewGenesisStore() - srvc.genesis.Set(types.Genesis{}) - srvc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - srvc.rpc = rpc - - ctx := context.Background() - - rpc.SendResponseWithError(fake.NewAddress(5), fake.GetError()) - rpc.Done() - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - err := srvc.wakeUp(ctx, ro) - require.NoError(t, err) - - srvc.tree.Set(fakeTree{err: fake.GetError()}) - err = srvc.wakeUp(ctx, ro) - require.EqualError(t, err, fake.Err("read roster failed: read from tree")) - - srvc.tree.Set(fakeTree{}) - srvc.rpc = fake.NewBadRPC() - err = srvc.wakeUp(ctx, ro) - require.EqualError(t, err, fake.Err("rpc failed")) -} - -func TestService_GetProof(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.blocks = blockstore.NewInMemory() - srvc.blocks.Store(makeBlock(t, types.Digest{})) - - proof, err := srvc.GetProof([]byte("A")) - require.NoError(t, err) - require.NotNil(t, proof) - - srvc.tree.Set(fakeTree{err: fake.GetError()}) - _, err = srvc.GetProof([]byte("A")) - require.EqualError(t, err, fake.Err("reading path")) - - srvc.tree.Set(fakeTree{}) - srvc.blocks = blockstore.NewInMemory() - _, err = srvc.GetProof([]byte("A")) - require.EqualError(t, err, "reading chain: store is empty") -} - -func TestService_GetStore(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - - require.IsType(t, fakeTree{}, srvc.GetStore()) -} - -func TestService_GetRoster(t *testing.T) { - srvc := &Service{processor: newProcessor()} - srvc.tree = blockstore.NewTreeCache(fakeTree{}) - srvc.rosterFac = fakeRosterFac{} - - roster, err := srvc.GetRoster() - require.NoError(t, err) - require.Equal(t, 3, roster.Len()) -} - -func TestService_PoolFilter(t *testing.T) { - filter := poolFilter{ - tree: blockstore.NewTreeCache(fakeTree{}), - srvc: fakeValidation{}, - } - - err := filter.Accept(makeTx(t, 0, fake.NewSigner()), validation.Leeway{}) - require.NoError(t, err) - - filter.srvc = fakeValidation{err: fake.GetError()} - err = filter.Accept(makeTx(t, 0, fake.NewSigner()), validation.Leeway{}) - require.EqualError(t, err, fake.Err("unacceptable transaction")) -} - -// ----------------------------------------------------------------------------- -// Utility functions -func checkProof(t *testing.T, p Proof, s *Service) { - genesis, err := s.genesis.Get() - require.NoError(t, err) - - err = p.Verify(genesis, s.verifierFac) - require.NoError(t, err) -} - -type testNode struct { - onet *minoch.Minoch - service *Service - pool *poolgossip.Pool - db kv.DB - dbpath string - signer crypto.Signer -} - -const testContractName = "abc" - -type testExec struct { - err error -} - -func (e testExec) Execute(store.Snapshot, execution.Step) error { - return e.err -} - -func makeTx(t *testing.T, nonce uint64, signer crypto.Signer) txn.Transaction { - opts := []signed.TransactionOption{ - signed.WithArg(native.ContractArg, []byte(testContractName)), - } - - tx, err := signed.NewTransaction(nonce, signer.GetPublicKey(), opts...) - require.NoError(t, err) - - require.NoError(t, tx.Sign(signer)) - - return tx -} - -func makeRosterTx(t *testing.T, nonce uint64, roster authority.Authority, signer crypto.Signer) txn.Transaction { - data, err := roster.Serialize(json.NewContext()) - require.NoError(t, err) - - tx, err := signed.NewTransaction( - nonce, - signer.GetPublicKey(), - signed.WithArg(native.ContractArg, []byte(viewchange.ContractName)), - signed.WithArg(viewchange.AuthorityArg, data), - ) - require.NoError(t, err) - - require.NoError(t, tx.Sign(signer)) - - return tx -} - -func waitEvent(t *testing.T, events <-chan ordering.Event, timeout time.Duration) ordering.Event { - select { - case <-time.After(timeout): - t.Log(string(debug.Stack())) - t.Fatal("no event received before the timeout") - return ordering.Event{} - case evt := <-events: - return evt - } -} - -func makeAuthority(t *testing.T, n int) ([]testNode, authority.Authority, func()) { - manager := minoch.NewManager() - - addrs := make([]mino.Address, n) - pubkeys := make([]crypto.PublicKey, n) - nodes := make([]testNode, n) - - for i := 0; i < n; i++ { - m := minoch.MustCreate(manager, fmt.Sprintf("node%d", i)) - - addrs[i] = m.GetAddress() - - signer := bls.NewSigner() - pubkeys[i] = signer.GetPublicKey() - - c := threshold.NewThreshold(m, signer) - c.SetThreshold(threshold.ByzantineThreshold) - - dir, err := os.MkdirTemp(os.TempDir(), "cosipbft") - require.NoError(t, err) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - txFac := signed.NewTransactionFactory() - - p, err := poolgossip.NewPool(gossip.NewFlat(m, txFac)) - require.NoError(t, err) - - tree := binprefix.NewMerkleTree(db, binprefix.Nonce{}) - - exec := native.NewExecution() - exec.Set(testContractName, testExec{}) - - accessSrvc := darc.NewService(json.NewContext()) - - rosterFac := authority.NewFactory(m.GetAddressFactory(), c.GetPublicKeyFactory()) - RegisterRosterContract(exec, rosterFac, accessSrvc) - - vs := simple.NewService(exec, txFac) - - param := ServiceParam{ - Mino: m, - Cosi: c, - Validation: vs, - Access: accessSrvc, - Pool: p, - Tree: tree, - DB: db, - } - - srv, err := NewService(param) - require.NoError(t, err) - - nodes[i] = testNode{ - onet: m, - service: srv, - pool: p, - db: db, - dbpath: dir, - signer: c.GetSigner(), - } - } - - ro := authority.New(addrs, pubkeys) - - clean := func() { - for _, node := range nodes { - require.NoError(t, node.service.Close()) - require.NoError(t, node.db.Close()) - require.NoError(t, os.RemoveAll(node.dbpath)) - } - } - - return nodes, ro, clean -} - -type badRosterFac struct { - authority.Factory -} - -func (fac badRosterFac) AuthorityOf(serde.Context, []byte) (authority.Authority, error) { - return nil, fake.GetError() -} - -type fakePool struct { - pool.Pool -} - -func (f fakePool) Stats() pool.Stats { - return pool.Stats{ - OldestTx: time.Now().Add(-100 * time.Hour), - TxCount: 1, - } -} - -type badPool struct { - pool.Pool -} - -func (p badPool) SetPlayers(mino.Players) error { - return fake.GetError() -} - -func (p badPool) AddFilter(pool.Filter) {} - -type badCosi struct { - cosi.CollectiveSigning -} - -func (c badCosi) GetSigner() crypto.Signer { - return fake.NewBadSigner() -} - -func (c badCosi) GetPublicKeyFactory() crypto.PublicKeyFactory { - return fake.NewPublicKeyFactory(fake.PublicKey{}) -} - -func (c badCosi) GetSignatureFactory() crypto.SignatureFactory { - return fake.NewSignatureFactory(fake.Signature{}) -} - -func (c badCosi) GetVerifierFactory() crypto.VerifierFactory { - return fake.NewVerifierFactory(fake.Verifier{}) -} - -func (c badCosi) Listen(cosi.Reactor) (cosi.Actor, error) { - return nil, fake.GetError() -} - -type fakeValidation struct { - validation.Service - - err error -} - -func (val fakeValidation) Accept(store.Readable, txn.Transaction, validation.Leeway) error { - return val.err -} - -func (val fakeValidation) Validate(store.Snapshot, []txn.Transaction) (validation.Result, error) { - return simple.NewResult(nil), val.err -} - -type fakeCosiActor struct { - cosi.Actor - - counter *fake.Counter - err error -} - -func (c fakeCosiActor) Sign(ctx context.Context, msg serde.Message, - ca crypto.CollectiveAuthority) (crypto.Signature, error) { - - if c.counter.Done() { - return fake.Signature{}, c.err - } - - c.counter.Decrease() - return fake.Signature{}, nil -} - -type fakeRosterFac struct { - authority.Factory -} - -func (fakeRosterFac) AuthorityOf(serde.Context, []byte) (authority.Authority, error) { - return authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)), nil -} - -type fakeAccess struct { - access.Service - - err error -} - -func (srvc fakeAccess) Grant(store.Snapshot, access.Credential, ...access.Identity) error { - return srvc.err -} diff --git a/dela/core/ordering/cosipbft/json/chain.go b/dela/core/ordering/cosipbft/json/chain.go deleted file mode 100644 index 515d19a..0000000 --- a/dela/core/ordering/cosipbft/json/chain.go +++ /dev/null @@ -1,84 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// ChainFormat is the JSON format to encode and decode chains. -// -// - implements serde.FormatEngine -type chainFormat struct{} - -// Encode implements serde.FormatEngine. It serializes the chain if appropriate, -// otherwise it returns an error. -func (fmt chainFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - chain, ok := msg.(types.Chain) - if !ok { - return nil, xerrors.Errorf("unsupported message '%T'", msg) - } - - links := chain.GetLinks() - raws := make([]json.RawMessage, len(links)) - - for i, link := range links { - raw, err := link.Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize link: %v", err) - } - - raws[i] = raw - } - - m := ChainJSON{ - Links: raws, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It deserializes the chain if -// appropriate, otherwise it returns an error. -func (fmt chainFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := ChainJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - if len(m.Links) == 0 { - return nil, xerrors.New("chain cannot be empty") - } - - fac := ctx.GetFactory(types.LinkKey{}) - - factory, ok := fac.(types.LinkFactory) - if !ok { - return nil, xerrors.Errorf("invalid link factory '%T'", fac) - } - - prevs := make([]types.Link, len(m.Links)-1) - for i, raw := range m.Links[:len(m.Links)-1] { - link, err := factory.LinkOf(ctx, raw) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize link: %v", err) - } - - prevs[i] = link - } - - last, err := factory.BlockLinkOf(ctx, m.Links[len(m.Links)-1]) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize block link: %v", err) - } - - return types.NewChain(last, prevs), nil -} diff --git a/dela/core/ordering/cosipbft/json/chain_test.go b/dela/core/ordering/cosipbft/json/chain_test.go deleted file mode 100644 index 8784768..0000000 --- a/dela/core/ordering/cosipbft/json/chain_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestChainFormat_Encode(t *testing.T) { - format := chainFormat{} - - ctx := fake.NewContext() - - data, err := format.Encode(ctx, types.NewChain(fakeLink{}, nil)) - require.NoError(t, err) - require.Equal(t, `{"Links":[{}]}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message 'fake.Message'") - - _, err = format.Encode(ctx, types.NewChain(fakeLink{err: fake.GetError()}, nil)) - require.EqualError(t, err, fake.Err("couldn't serialize link")) - - _, err = format.Encode(fake.NewBadContext(), types.NewChain(fakeLink{}, nil)) - require.EqualError(t, err, fake.Err("failed to marshal")) -} - -func TestChainFormat_Decode(t *testing.T) { - format := chainFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.LinkKey{}, fakeLinkFac{}) - - chain, err := format.Decode(ctx, []byte(`{"Links":[{}, {}, {}]}`)) - require.NoError(t, err) - require.Equal(t, types.NewChain(fakeLink{}, []types.Link{fakeLink{}, fakeLink{}}), chain) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, "chain cannot be empty") - - badCtx := serde.WithFactory(ctx, types.LinkKey{}, fake.MessageFactory{}) - _, err = format.Decode(badCtx, []byte(`{"Links":[{}]}`)) - require.EqualError(t, err, "invalid link factory 'fake.MessageFactory'") - - badCtx = serde.WithFactory(ctx, types.LinkKey{}, fakeLinkFac{errLink: fake.GetError()}) - _, err = format.Decode(badCtx, []byte(`{"Links":[{}, {}]}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize link")) - - badCtx = serde.WithFactory(ctx, types.LinkKey{}, fakeLinkFac{errBlockLink: fake.GetError()}) - _, err = format.Decode(badCtx, []byte(`{"Links": [{}]}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize block link")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeLink struct { - types.BlockLink - - err error -} - -func (link fakeLink) Serialize(serde.Context) ([]byte, error) { - return []byte(`{}`), link.err -} - -type fakeLinkFac struct { - types.LinkFactory - - errLink error - errBlockLink error -} - -func (link fakeLinkFac) LinkOf(serde.Context, []byte) (types.Link, error) { - return fakeLink{}, link.errLink -} - -func (link fakeLinkFac) BlockLinkOf(serde.Context, []byte) (types.BlockLink, error) { - return fakeLink{}, link.errBlockLink -} diff --git a/dela/core/ordering/cosipbft/json/json.go b/dela/core/ordering/cosipbft/json/json.go deleted file mode 100644 index 4d5ecd4..0000000 --- a/dela/core/ordering/cosipbft/json/json.go +++ /dev/null @@ -1,467 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - types.RegisterGenesisFormat(serde.FormatJSON, genesisFormat{}) - types.RegisterMessageFormat(serde.FormatJSON, msgFormat{}) - types.RegisterBlockFormat(serde.FormatJSON, blockFormat{}) - types.RegisterLinkFormat(serde.FormatJSON, linkFormat{}) - types.RegisterChainFormat(serde.FormatJSON, chainFormat{}) -} - -// GenesisJSON is the JSON message for a genesis block. -type GenesisJSON struct { - Roster json.RawMessage - TreeRoot []byte -} - -// BlockJSON is the JSON message for a block. -type BlockJSON struct { - Index uint64 - TreeRoot []byte - Data json.RawMessage -} - -// LinkJSON is the JSON message for a link. -type LinkJSON struct { - From []byte - To []byte `json:",omitempty"` - PrepareSignature json.RawMessage - CommitSignature json.RawMessage - ChangeSet json.RawMessage - Block json.RawMessage `json:",omitempty"` -} - -// ChainJSON is the JSON message for a chain. -type ChainJSON struct { - Links []json.RawMessage -} - -// GenesisMessageJSON is the JSON message to send a genesis block. -type GenesisMessageJSON struct { - Genesis json.RawMessage -} - -// BlockMessageJSON is the JSON message to send a block. -type BlockMessageJSON struct { - Block json.RawMessage - Views map[string]ViewMessageJSON -} - -// CommitMessageJSON is the JSON message to send a commit request. -type CommitMessageJSON struct { - ID []byte - Signature json.RawMessage -} - -// DoneMessageJSON is the JSON message to send a block confirmation. -type DoneMessageJSON struct { - ID []byte - Signature json.RawMessage -} - -// ViewMessageJSON is the JSON message to send a view change request. -type ViewMessageJSON struct { - Leader uint16 - ID []byte - Signature json.RawMessage -} - -// MessageJSON is the JSON message that wraps the different kinds of messages. -type MessageJSON struct { - Genesis *GenesisMessageJSON `json:",omitempty"` - Block *BlockMessageJSON `json:",omitempty"` - Commit *CommitMessageJSON `json:",omitempty"` - Done *DoneMessageJSON `json:",omitempty"` - View *ViewMessageJSON `json:",omitempty"` -} - -// GenesisFormat is a format engine to serialize and deserialize the genesis -// blocks. -// -// - implements serde.FormatEngine -type genesisFormat struct { - hashFac crypto.HashFactory -} - -// Encode implements serde.FormatEngine. It returns the serialized data of the -// genesis if appropriate, otherwise it returns an error. -func (f genesisFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - genesis, ok := msg.(types.Genesis) - if !ok { - return nil, xerrors.Errorf("invalid genesis '%T'", msg) - } - - roster, err := genesis.GetRoster().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize roster: %v", err) - } - - m := GenesisJSON{ - Roster: roster, - TreeRoot: genesis.GetRoot().Bytes(), - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the genesis block if -// appropriate, otherwise it returns an error. -func (f genesisFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := GenesisJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - factory := ctx.GetFactory(types.RosterKey{}) - - fac, ok := factory.(authority.Factory) - if !ok { - return nil, xerrors.Errorf("invalid roster factory '%T'", factory) - } - - roster, err := fac.AuthorityOf(ctx, m.Roster) - if err != nil { - return nil, xerrors.Errorf("authority factory failed: %v", err) - } - - root := types.Digest{} - copy(root[:], m.TreeRoot) - - opts := []types.GenesisOption{types.WithGenesisRoot(root)} - - if f.hashFac != nil { - opts = append(opts, types.WithGenesisHashFactory(f.hashFac)) - } - - genesis, err := types.NewGenesis(roster, opts...) - if err != nil { - return nil, xerrors.Errorf("creating genesis: %v", err) - } - - return genesis, nil -} - -// BlockFormat is the format engine to serialize and deserialize the blocks. -// -// - implements serde.FormatEngine -type blockFormat struct { - hashFac crypto.HashFactory -} - -// Encode implements serde.FormatEngine. It returns the serialized data of the -// block if appropritate, otherwise it returns an error. -func (f blockFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - block, ok := msg.(types.Block) - if !ok { - return nil, xerrors.Errorf("invalid block '%T'", msg) - } - - blockdata, err := block.GetData().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize data: %v", err) - } - - m := BlockJSON{ - Index: block.GetIndex(), - TreeRoot: block.GetTreeRoot().Bytes(), - Data: blockdata, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the block if appropriate, -// otherwise it returns an error. -func (f blockFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := BlockJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - factory := ctx.GetFactory(types.DataKey{}) - - fac, ok := factory.(validation.ResultFactory) - if !ok { - return nil, xerrors.Errorf("invalid data factory '%T'", factory) - } - - blockdata, err := fac.ResultOf(ctx, m.Data) - if err != nil { - return nil, xerrors.Errorf("data factory failed: %v", err) - } - - root := types.Digest{} - copy(root[:], m.TreeRoot) - - opts := []types.BlockOption{ - types.WithTreeRoot(root), - types.WithIndex(m.Index), - } - - if f.hashFac != nil { - opts = append(opts, types.WithHashFactory(f.hashFac)) - } - - block, err := types.NewBlock(blockdata, opts...) - if err != nil { - return nil, xerrors.Errorf("creating block: %v", err) - } - - return block, nil -} - -// MsgFormat is the format engine to serialize and deserialize the messages. -// -// - implements serde.FormatEngine -type msgFormat struct{} - -// Encode implements serde.FormatEngine. It returns the serialized data of the -// message if appropriate, otherwise it returns an error. -func (f msgFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - var m MessageJSON - - switch in := msg.(type) { - case types.GenesisMessage: - genesis, err := in.GetGenesis().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize genesis: %v", err) - } - - gm := GenesisMessageJSON{ - Genesis: genesis, - } - - m = MessageJSON{Genesis: &gm} - case types.BlockMessage: - block, err := in.GetBlock().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("block: %v", err) - } - - views := make(map[string]ViewMessageJSON) - for addr, view := range in.GetViews() { - key, err := addr.MarshalText() - if err != nil { - return nil, xerrors.Errorf("failed to serialize address: %v", err) - } - - rawView, err := encodeView(view, ctx) - if err != nil { - return nil, xerrors.Errorf("view: %v", err) - } - - views[string(key)] = *rawView - } - - bm := BlockMessageJSON{ - Block: block, - Views: views, - } - - m = MessageJSON{Block: &bm} - case types.CommitMessage: - sig, err := in.GetSignature().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize signature: %v", err) - } - - cm := CommitMessageJSON{ - ID: in.GetID().Bytes(), - Signature: sig, - } - - m = MessageJSON{Commit: &cm} - case types.DoneMessage: - sig, err := in.GetSignature().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize signature: %v", err) - } - - dm := DoneMessageJSON{ - ID: in.GetID().Bytes(), - Signature: sig, - } - - m = MessageJSON{Done: &dm} - case types.ViewMessage: - vm, err := encodeView(in, ctx) - if err != nil { - return nil, xerrors.Errorf("view: %v", err) - } - - m = MessageJSON{View: vm} - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -func encodeView(in types.ViewMessage, ctx serde.Context) (*ViewMessageJSON, error) { - sig, err := in.GetSignature().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to serialize signature: %v", err) - } - - vm := &ViewMessageJSON{ - ID: in.GetID().Bytes(), - Leader: in.GetLeader(), - Signature: sig, - } - - return vm, nil -} - -// Decode implements serde.FormatEngine. It populates the message if -// appropriate, otherwise it returns an error. -func (f msgFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := MessageJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - if m.Genesis != nil { - factory := ctx.GetFactory(types.GenesisKey{}) - if factory == nil { - return nil, xerrors.New("missing genesis factory") - } - - msg, err := factory.Deserialize(ctx, m.Genesis.Genesis) - if err != nil { - return nil, xerrors.Errorf("failed to deserialize genesis: %v", err) - } - - genesis, ok := msg.(types.Genesis) - if !ok { - return nil, xerrors.Errorf("invalid genesis '%T'", msg) - } - - return types.NewGenesisMessage(genesis), nil - } - - if m.Block != nil { - // 1. Decode the block. - factory := ctx.GetFactory(types.BlockKey{}) - if factory == nil { - return nil, xerrors.New("missing block factory") - } - - msg, err := factory.Deserialize(ctx, m.Block.Block) - if err != nil { - return nil, xerrors.Errorf("failed to deserialize block: %v", err) - } - - block, ok := msg.(types.Block) - if !ok { - return nil, xerrors.Errorf("invalid block '%T'", msg) - } - - // 2. Decode the view messages if any. - factory = ctx.GetFactory(types.AddressKey{}) - - fac, ok := factory.(mino.AddressFactory) - if !ok { - return nil, xerrors.Errorf("invalid address factory '%T'", factory) - } - - views := make(map[mino.Address]types.ViewMessage) - for key, rawView := range m.Block.Views { - addr := fac.FromText([]byte(key)) - - view, err := decodeView(ctx, &rawView) - if err != nil { - return nil, xerrors.Errorf("view: %v", err) - } - - views[addr] = view - } - - return types.NewBlockMessage(block, views), nil - } - - if m.Commit != nil { - sig, err := decodeSignature(ctx, m.Commit.Signature, types.AggregateKey{}) - if err != nil { - return nil, xerrors.Errorf("commit failed: %v", err) - } - - id := types.Digest{} - copy(id[:], m.Commit.ID) - - return types.NewCommit(id, sig), nil - } - - if m.Done != nil { - sig, err := decodeSignature(ctx, m.Done.Signature, types.AggregateKey{}) - if err != nil { - return nil, xerrors.Errorf("done failed: %v", err) - } - - id := types.Digest{} - copy(id[:], m.Done.ID) - - return types.NewDone(id, sig), nil - } - - if m.View != nil { - return decodeView(ctx, m.View) - } - - return nil, xerrors.New("message is empty") -} - -func decodeView(ctx serde.Context, view *ViewMessageJSON) (types.ViewMessage, error) { - sig, err := decodeSignature(ctx, view.Signature, types.SignatureKey{}) - if err != nil { - return types.ViewMessage{}, xerrors.Errorf("signature: %v", err) - } - - id := types.Digest{} - copy(id[:], view.ID) - - return types.NewViewMessage(id, view.Leader, sig), nil -} - -func decodeSignature(ctx serde.Context, data []byte, key interface{}) (crypto.Signature, error) { - factory := ctx.GetFactory(key) - - fac, ok := factory.(crypto.SignatureFactory) - if !ok { - return nil, xerrors.Errorf("invalid signature factory '%T'", factory) - } - - sig, err := fac.SignatureOf(ctx, data) - if err != nil { - return nil, xerrors.Errorf("factory failed: %v", err) - } - - return sig, nil -} diff --git a/dela/core/ordering/cosipbft/json/json_test.go b/dela/core/ordering/cosipbft/json/json_test.go deleted file mode 100644 index cdb8e3e..0000000 --- a/dela/core/ordering/cosipbft/json/json_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package json - -import ( - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -func init() { - types.RegisterGenesisFormat(fake.GoodFormat, fakeGenesisFormat{}) - types.RegisterGenesisFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestGenesisFormat_Encode(t *testing.T) { - format := genesisFormat{} - - ctx := fake.NewContext() - - genesis, err := types.NewGenesis(fakeRoster{}, types.WithGenesisRoot(types.Digest{1})) - require.NoError(t, err) - - data, err := format.Encode(ctx, genesis) - require.NoError(t, err) - require.Regexp(t, `{"Roster":{},"TreeRoot":"[^"]+"}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "invalid genesis 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), genesis) - require.EqualError(t, err, fake.Err("failed to marshal")) - - genesis, err = types.NewGenesis(fakeRoster{err: fake.GetError()}) - require.NoError(t, err) - - _, err = format.Encode(ctx, genesis) - require.EqualError(t, err, fake.Err("failed to serialize roster")) -} - -func TestGenesisFormat_Decode(t *testing.T) { - format := genesisFormat{} - - genesis, err := types.NewGenesis(fakeRoster{}) - require.NoError(t, err) - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.RosterKey{}, fakeRosterFac{}) - - msg, err := format.Decode(ctx, []byte(`{}`)) - require.NoError(t, err) - require.NotNil(t, msg, genesis) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - badCtx := serde.WithFactory(ctx, types.RosterKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, "invalid roster factory ''") - - badCtx = serde.WithFactory(ctx, types.RosterKey{}, fakeRosterFac{err: fake.GetError()}) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("authority factory failed")) - - format.hashFac = fake.NewHashFactory(fake.NewBadHash()) - _, err = format.Decode(ctx, []byte(`{}`)) - require.Error(t, err) - require.Contains(t, err.Error(), "creating genesis: fingerprint failed: ") -} - -func TestBlockFormat_Encode(t *testing.T) { - format := blockFormat{} - - ctx := fake.NewContext() - - block, err := types.NewBlock(fakeResult{}) - require.NoError(t, err) - - data, err := format.Encode(ctx, block) - require.NoError(t, err) - require.Regexp(t, `{"Index":0,"TreeRoot":"[^"]+","Data":{}}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "invalid block 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), block) - require.EqualError(t, err, fake.Err("failed to marshal")) - - block, err = types.NewBlock(fakeResult{err: fake.GetError()}) - require.NoError(t, err) - - _, err = format.Encode(ctx, block) - require.EqualError(t, err, fake.Err("failed to serialize data")) -} - -func TestBlockFormat_Decode(t *testing.T) { - format := blockFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.DataKey{}, fakeResultFac{}) - - block, err := types.NewBlock(fakeResult{}) - require.NoError(t, err) - - msg, err := format.Decode(ctx, []byte(`{}`)) - require.NoError(t, err) - require.Equal(t, block, msg) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - badCtx := serde.WithFactory(ctx, types.DataKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, "invalid data factory ''") - - badCtx = serde.WithFactory(ctx, types.DataKey{}, fakeResultFac{err: fake.GetError()}) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("data factory failed")) - - format.hashFac = fake.NewHashFactory(fake.NewBadHash()) - _, err = format.Decode(ctx, []byte(`{}`)) - require.Error(t, err) - require.Contains(t, err.Error(), "creating block: fingerprint failed: ") -} - -func TestMsgFormat_Encode(t *testing.T) { - format := msgFormat{} - - genesis, err := types.NewGenesis(fakeRoster{}) - require.NoError(t, err) - - block, err := types.NewBlock(fakeResult{}) - require.NoError(t, err) - - ctx := fake.NewContext() - - data, err := format.Encode(ctx, types.NewGenesisMessage(genesis)) - require.NoError(t, err) - require.Equal(t, `{"Genesis":{"Genesis":{}}}`, string(data)) - - _, err = format.Encode(fake.NewBadContext(), types.NewGenesisMessage(genesis)) - require.EqualError(t, err, fake.Err("failed to serialize genesis: encoding failed")) - - views := map[mino.Address]types.ViewMessage{ - fake.NewAddress(0): types.NewViewMessage(types.Digest{1}, 5, fake.Signature{}), - } - data, err = format.Encode(ctx, types.NewBlockMessage(block, views)) - require.NoError(t, err) - require.Regexp(t, - `{"Block":{"Block":{},"Views":{"[^"]+":{"Leader":5,"ID":"[^"]+","Signature":{}}}}}`, string(data)) - - views[fake.NewAddress(0)] = types.NewViewMessage(types.Digest{}, 0, fake.NewBadSignature()) - _, err = format.Encode(ctx, types.NewBlockMessage(block, views)) - require.EqualError(t, err, fake.Err("view: failed to serialize signature")) - - delete(views, fake.NewAddress(0)) - views[fake.NewBadAddress()] = types.NewViewMessage(types.Digest{}, 0, fake.Signature{}) - _, err = format.Encode(ctx, types.NewBlockMessage(block, views)) - require.EqualError(t, err, fake.Err("failed to serialize address")) - - _, err = format.Encode(fake.NewBadContext(), types.NewBlockMessage(block, nil)) - require.EqualError(t, err, fake.Err("block: encoding failed")) - - data, err = format.Encode(ctx, types.NewCommit(types.Digest{}, fake.Signature{})) - require.NoError(t, err) - require.Regexp(t, `{"Commit":{"ID":"[^"]+","Signature":{}}}`, string(data)) - - _, err = format.Encode(ctx, types.NewCommit(types.Digest{}, fake.NewBadSignature())) - require.EqualError(t, err, fake.Err("failed to serialize signature")) - - data, err = format.Encode(ctx, types.NewDone(types.Digest{}, fake.Signature{})) - require.NoError(t, err) - require.Regexp(t, `{"Done":{"ID":"[^"]+","Signature":{}}}`, string(data)) - - _, err = format.Encode(ctx, types.NewDone(types.Digest{}, fake.NewBadSignature())) - require.EqualError(t, err, fake.Err("failed to serialize signature")) - - data, err = format.Encode(ctx, types.NewViewMessage(types.Digest{}, 5, fake.Signature{})) - require.NoError(t, err) - require.Regexp(t, `{"View":{"Leader":5,"ID":"[^"]+","Signature":{}}}`, string(data)) - - _, err = format.Encode(ctx, types.NewViewMessage(types.Digest{}, 0, fake.NewBadSignature())) - require.EqualError(t, err, fake.Err("view: failed to serialize signature")) - - _, err = format.Encode(fake.NewBadContext(), types.NewViewMessage(types.Digest{}, 0, fake.Signature{})) - require.EqualError(t, err, fake.Err("failed to marshal")) -} - -func TestMsgFormat_Decode(t *testing.T) { - format := msgFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.GenesisKey{}, types.GenesisFactory{}) - ctx = serde.WithFactory(ctx, types.BlockKey{}, types.BlockFactory{}) - ctx = serde.WithFactory(ctx, types.AggregateKey{}, fake.SignatureFactory{}) - ctx = serde.WithFactory(ctx, types.SignatureKey{}, fake.SignatureFactory{}) - ctx = serde.WithFactory(ctx, types.AddressKey{}, fake.AddressFactory{}) - - msg, err := format.Decode(ctx, []byte(`{"Genesis":{}}`)) - require.NoError(t, err) - require.IsType(t, types.GenesisMessage{}, msg) - - badCtx := serde.WithFactory(ctx, types.GenesisKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Genesis":{}}`)) - require.EqualError(t, err, "missing genesis factory") - - badCtx = serde.WithFactory(ctx, types.GenesisKey{}, fake.NewBadMessageFactory()) - _, err = format.Decode(badCtx, []byte(`{"Genesis":{}}`)) - require.EqualError(t, err, fake.Err("failed to deserialize genesis")) - - badCtx = serde.WithFactory(ctx, types.GenesisKey{}, fake.MessageFactory{}) - _, err = format.Decode(badCtx, []byte(`{"Genesis":{}}`)) - require.EqualError(t, err, "invalid genesis 'fake.Message'") - - msg, err = format.Decode(ctx, []byte(`{"Block":{"Views":{"":{}}}}`)) - require.NoError(t, err) - require.IsType(t, types.BlockMessage{}, msg) - require.Len(t, msg.(types.BlockMessage).GetViews(), 1) - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, "missing block factory") - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, fake.NewBadMessageFactory()) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, fake.Err("failed to deserialize block")) - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, fake.MessageFactory{}) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, "invalid block 'fake.Message'") - - badCtx = serde.WithFactory(ctx, types.AddressKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Block":{"Views":{"":{}}}}`)) - require.EqualError(t, err, "invalid address factory ''") - - badCtx = serde.WithFactory(ctx, types.SignatureKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Block":{"Views":{"":{}}}}`)) - require.EqualError(t, err, "view: signature: invalid signature factory ''") - - msg, err = format.Decode(ctx, []byte(`{"Commit":{}}`)) - require.NoError(t, err) - require.IsType(t, types.CommitMessage{}, msg) - - badCtx = serde.WithFactory(ctx, types.AggregateKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Commit":{}}`)) - require.EqualError(t, err, "commit failed: invalid signature factory ''") - - msg, err = format.Decode(ctx, []byte(`{"Done":{}}`)) - require.NoError(t, err) - require.IsType(t, types.DoneMessage{}, msg) - - _, err = format.Decode(badCtx, []byte(`{"Done":{}}`)) - require.EqualError(t, err, "done failed: invalid signature factory ''") - - msg, err = format.Decode(ctx, []byte(`{"View":{}}`)) - require.NoError(t, err) - require.IsType(t, types.ViewMessage{}, msg) - - badCtx = serde.WithFactory(ctx, types.SignatureKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"View":{}}`)) - require.EqualError(t, err, "signature: invalid signature factory ''") - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, "message is empty") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeRoster struct { - authority.Authority - - err error -} - -func (ro fakeRoster) Serialize(serde.Context) ([]byte, error) { - return []byte(`{}`), ro.err -} - -func (fakeRoster) Fingerprint(io.Writer) error { - return nil -} - -type fakeRosterFac struct { - authority.Factory - - err error -} - -func (fac fakeRosterFac) AuthorityOf(serde.Context, []byte) (authority.Authority, error) { - return fakeRoster{}, fac.err -} - -type fakeGenesisFormat struct { - serde.FormatEngine -} - -func (fakeGenesisFormat) Encode(serde.Context, serde.Message) ([]byte, error) { - return []byte(`{}`), nil -} - -func (fakeGenesisFormat) Decode(serde.Context, []byte) (serde.Message, error) { - genesis, err := types.NewGenesis(authority.New(nil, nil)) - if err != nil { - return nil, err - } - - return genesis, nil -} diff --git a/dela/core/ordering/cosipbft/json/link.go b/dela/core/ordering/cosipbft/json/link.go deleted file mode 100644 index d505c90..0000000 --- a/dela/core/ordering/cosipbft/json/link.go +++ /dev/null @@ -1,166 +0,0 @@ -package json - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// LinkFormat is the JSON format engine to serialize and deserialize the links. -// -// - implements serde.FormatEngine -type linkFormat struct { - hashFac crypto.HashFactory -} - -// Encode implements serde.FormatEngine. It serializes the link or the block -// link if appropriate, otherwise it returns an error. -func (fmt linkFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - var m LinkJSON - - switch link := msg.(type) { - case types.BlockLink: - err := fmt.encodeLink(ctx, link, &m) - if err != nil { - return nil, err - } - - block, err := link.GetBlock().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize block: %v", err) - } - - m.Block = block - case types.Link: - err := fmt.encodeLink(ctx, link, &m) - if err != nil { - return nil, err - } - - to := link.GetTo() - - m.To = to.Bytes() - default: - return nil, xerrors.Errorf("unsupported message '%T'", msg) - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -func (fmt linkFormat) encodeLink(ctx serde.Context, link types.Link, m *LinkJSON) error { - prepare, err := link.GetPrepareSignature().Serialize(ctx) - if err != nil { - return xerrors.Errorf("couldn't serialize prepare: %v", err) - } - - commit, err := link.GetCommitSignature().Serialize(ctx) - if err != nil { - return xerrors.Errorf("couldn't serialize commit: %v", err) - } - - changeset, err := link.GetChangeSet().Serialize(ctx) - if err != nil { - return xerrors.Errorf("couldn't serialize change set: %v", err) - } - - m.From = link.GetFrom().Bytes() - m.PrepareSignature = prepare - m.CommitSignature = commit - m.ChangeSet = changeset - - return nil -} - -// Decode implements serde.FormatEngine. It populates the link or the block link -// if appropriate, otherwise it returns an error. -func (fmt linkFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := LinkJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - prepare, err := decodeSignature(ctx, m.PrepareSignature, types.AggregateKey{}) - if err != nil { - return nil, xerrors.Errorf("failed to decode prepare: %v", err) - } - - commit, err := decodeSignature(ctx, m.CommitSignature, types.AggregateKey{}) - if err != nil { - return nil, xerrors.Errorf("failed to decode commit: %v", err) - } - - changeset, err := decodeChangeSet(ctx, m.ChangeSet) - if err != nil { - return nil, xerrors.Errorf("failed to decode change set: %v", err) - } - - from := types.Digest{} - copy(from[:], m.From) - - opts := []types.LinkOption{ - types.WithSignatures(prepare, commit), - types.WithChangeSet(changeset), - } - - if fmt.hashFac != nil { - opts = append(opts, types.WithLinkHashFactory(fmt.hashFac)) - } - - if len(m.Block) > 0 { - factory := ctx.GetFactory(types.BlockKey{}) - if factory == nil { - return nil, xerrors.New("missing block factory") - } - - msg, err := factory.Deserialize(ctx, m.Block) - if err != nil { - return nil, xerrors.Errorf("failed to decode block: %v", err) - } - - block, ok := msg.(types.Block) - if !ok { - return nil, xerrors.Errorf("invalid block '%T'", msg) - } - - link, err := types.NewBlockLink(from, block, opts...) - if err != nil { - return nil, xerrors.Errorf("creating block link: %v", err) - } - - return link, nil - } - - to := types.Digest{} - copy(to[:], m.To) - - link, err := types.NewForwardLink(from, to, opts...) - if err != nil { - return nil, xerrors.Errorf("creating forward link: %v", err) - } - - return link, nil -} - -func decodeChangeSet(ctx serde.Context, data []byte) (authority.ChangeSet, error) { - factory := ctx.GetFactory(types.ChangeSetKey{}) - - fac, ok := factory.(authority.ChangeSetFactory) - if !ok { - return nil, xerrors.Errorf("invalid factory '%T'", factory) - } - - changeset, err := fac.ChangeSetOf(ctx, data) - if err != nil { - return nil, xerrors.Errorf("factory failed: %v", err) - } - - return changeset, nil -} diff --git a/dela/core/ordering/cosipbft/json/link_test.go b/dela/core/ordering/cosipbft/json/link_test.go deleted file mode 100644 index f71f90c..0000000 --- a/dela/core/ordering/cosipbft/json/link_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package json - -import ( - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - types.RegisterBlockFormat(fake.GoodFormat, fakeBlockFormat{}) - types.RegisterBlockFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestLinkFormat_Encode(t *testing.T) { - format := linkFormat{} - - ctx := fake.NewContext() - - data, err := format.Encode(ctx, makeLink(t)) - require.NoError(t, err) - re := `{"From":"[^"]+","To":"[^"]+",` + - `"PrepareSignature":{},"CommitSignature":{},"ChangeSet":{}}` - require.Regexp(t, re, string(data)) - - data, err = format.Encode(ctx, makeBlockLink(t)) - require.NoError(t, err) - re = `{"From":"[^"]+","PrepareSignature":{},` + - `"CommitSignature":{},"ChangeSet":{},"Block":{}}` - require.Regexp(t, re, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message 'fake.Message'") - - opt := types.WithSignatures(fake.NewBadSignature(), fake.Signature{}) - _, err = format.Encode(ctx, makeLink(t, opt)) - require.EqualError(t, err, fake.Err("couldn't serialize prepare")) - - opt = types.WithSignatures(fake.Signature{}, fake.NewBadSignature()) - _, err = format.Encode(ctx, makeLink(t, opt)) - require.EqualError(t, err, fake.Err("couldn't serialize commit")) - - opt = types.WithChangeSet(fakeChangeSet{err: fake.GetError()}) - _, err = format.Encode(ctx, makeBlockLink(t, opt)) - require.EqualError(t, err, fake.Err("couldn't serialize change set")) - - _, err = format.Encode(fake.NewBadContext(), makeBlockLink(t)) - require.EqualError(t, err, fake.Err("couldn't serialize block: encoding failed")) - - _, err = format.Encode(fake.NewBadContext(), makeLink(t)) - require.EqualError(t, err, fake.Err("failed to marshal")) -} - -func TestLinkFormat_Decode(t *testing.T) { - format := linkFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.AggregateKey{}, fake.SignatureFactory{}) - ctx = serde.WithFactory(ctx, types.ChangeSetKey{}, fakeChangeSetFac{}) - ctx = serde.WithFactory(ctx, types.BlockKey{}, types.BlockFactory{}) - - msg, err := format.Decode(ctx, []byte(`{"From":[1],"To":[2]}`)) - require.NoError(t, err) - require.Equal(t, makeLink(t), msg) - - msg, err = format.Decode(ctx, []byte(`{"From":[1],"Block":{}}`)) - require.NoError(t, err) - require.Equal(t, makeBlockLink(t), msg) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - badCtx := serde.WithFactory(ctx, types.AggregateKey{}, fake.NewBadSignatureFactory()) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to decode prepare: factory failed")) - - badCtx = serde.WithFactory(ctx, types.AggregateKey{}, fake.NewBadSignatureFactoryWithDelay(1)) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to decode commit: factory failed")) - - badCtx = serde.WithFactory(ctx, types.ChangeSetKey{}, fake.MessageFactory{}) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, "failed to decode change set: invalid factory 'fake.MessageFactory'") - - badCtx = serde.WithFactory(ctx, types.ChangeSetKey{}, fakeChangeSetFac{err: fake.GetError()}) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to decode change set: factory failed")) - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, "missing block factory") - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, fake.NewBadMessageFactory()) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, fake.Err("failed to decode block")) - - badCtx = serde.WithFactory(ctx, types.BlockKey{}, fake.MessageFactory{}) - _, err = format.Decode(badCtx, []byte(`{"Block":{}}`)) - require.EqualError(t, err, "invalid block 'fake.Message'") - - format.hashFac = fake.NewHashFactory(fake.NewBadHash()) - _, err = format.Decode(ctx, []byte(`{}`)) - require.Error(t, err) - require.Contains(t, err.Error(), "creating forward link: failed to fingerprint: ") - - _, err = format.Decode(ctx, []byte(`{"Block":{}}`)) - require.Error(t, err) - require.Contains(t, err.Error(), "creating block link: creating forward link: failed to fingerprint: ") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeLink(t *testing.T, opts ...types.LinkOption) types.Link { - sigs := types.WithSignatures(fake.Signature{}, fake.Signature{}) - cs := types.WithChangeSet(fakeChangeSet{}) - - opts = append([]types.LinkOption{sigs, cs}, opts...) - - link, err := types.NewForwardLink(types.Digest{1}, types.Digest{2}, opts...) - require.NoError(t, err) - - return link -} - -func makeBlockLink(t *testing.T, opts ...types.LinkOption) types.BlockLink { - block, err := types.NewBlock(fakeResult{}) - require.NoError(t, err) - - sigs := types.WithSignatures(fake.Signature{}, fake.Signature{}) - cs := types.WithChangeSet(fakeChangeSet{}) - - opts = append([]types.LinkOption{sigs, cs}, opts...) - - link, err := types.NewBlockLink(types.Digest{1}, block, opts...) - require.NoError(t, err) - - return link -} - -type fakeChangeSet struct { - authority.ChangeSet - - err error -} - -func (cs fakeChangeSet) Serialize(serde.Context) ([]byte, error) { - return []byte(`{}`), cs.err -} - -type fakeChangeSetFac struct { - authority.ChangeSetFactory - - err error -} - -func (fac fakeChangeSetFac) ChangeSetOf(serde.Context, []byte) (authority.ChangeSet, error) { - return fakeChangeSet{}, fac.err -} - -type fakeResult struct { - validation.Result - - err error -} - -func (data fakeResult) Serialize(serde.Context) ([]byte, error) { - return []byte(`{}`), data.err -} - -func (fakeResult) Fingerprint(io.Writer) error { - return nil -} - -type fakeResultFac struct { - validation.ResultFactory - - err error -} - -func (fac fakeResultFac) ResultOf(serde.Context, []byte) (validation.Result, error) { - return fakeResult{}, fac.err -} - -type fakeBlockFormat struct { - serde.FormatEngine -} - -func (fakeBlockFormat) Encode(serde.Context, serde.Message) ([]byte, error) { - return []byte(`{}`), nil -} - -func (fakeBlockFormat) Decode(serde.Context, []byte) (serde.Message, error) { - block, err := types.NewBlock(fakeResult{}) - if err != nil { - return nil, err - } - - return block, nil -} diff --git a/dela/core/ordering/cosipbft/pbft/pbft.go b/dela/core/ordering/cosipbft/pbft/pbft.go deleted file mode 100644 index bf31fe5..0000000 --- a/dela/core/ordering/cosipbft/pbft/pbft.go +++ /dev/null @@ -1,856 +0,0 @@ -// Package pbft defines a state machine to perform PBFT using collective -// signatures. -// -// The package also implements a default state machine that allows only one -// block candidate per leader so that after a successful prepare phase, it -// expects the block to be committed and finalized. The only other state allowed -// is the view change if the round has expired. -// -// The view change can be fixed only by providing enough valid views from unique -// participants to comply to the 2f threshold, or if a catch up that provides a -// proof of acceptance of the block. -// -// Documentation Last Review: 13.10.2020 -package pbft - -import ( - "context" - "sync" - - "github.com/prometheus/client_golang/prometheus" - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -// State is the type of the different possible states for the PBFT state -// machine. -type State byte - -func (s State) String() string { - switch s { - case NoneState: - return "none" - case InitialState: - return "initial" - case PrepareState: - return "prepare" - case CommitState: - return "commit" - case ViewChangeState: - return "viewchange" - default: - return "unknown" - } -} - -// defines prometheus metrics -var ( - promBlocks = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "dela_cosipbft_blocks_total", - Help: "total number of blocks", - }) - - promTxs = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "dela_cosipbft_transactions_block", - Help: "total number of transactions in the last block", - Buckets: []float64{0, 1, 2, 3, 5, 8, 13, 20, 30, 50, 100}, - }) - - promRejectedTxs = prometheus.NewHistogram(prometheus.HistogramOpts{ - Name: "dela_cosipbft_transactions_rejected_block", - Help: "total number of rejected transactions in the last block", - Buckets: []float64{0, 1, 2, 3, 5, 8, 13, 20, 30, 50, 100}, - }) - - promLeader = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "dela_cosipbft_leader", - Help: "leader index from the roster", - }) -) - -const ( - // NoneState is the very first state of the machine where nothing is set. - NoneState State = iota - - // InitialState is the entry state which means the beginning of the PBFT - // protocol. - InitialState - - // PrepareState is the state to indicate that a proposal has been received - // and the machine is waiting for confirmations. - PrepareState - - // CommitState is the state to indicate that the participants have accepted - // the proposal and the machine is waiting for the second confirmation. - CommitState - - // ViewChangeState is the state to indicate that the protocol is failing and - // the machine is waiting for view change requests. - ViewChangeState -) - -func init() { - dela.PromCollectors = append(dela.PromCollectors, promBlocks, promTxs, - promRejectedTxs, promLeader) -} - -// StateMachine is the interface to implement to support a PBFT protocol. -type StateMachine interface { - // GetState returns the current state. - GetState() State - - // GetLeader returns the address of the round leader. - GetLeader() (mino.Address, error) - - // GetViews returns the list of views for which the round has been accepted, - // after a successful view change. Otherwise it is empty. - GetViews() map[mino.Address]View - - // GetCommit returns the candidate digest and the associated block if the - // state machine is committed to a candidate, otherwise the behaviour is - // undefined. - GetCommit() (types.Digest, types.Block) - - // Prepare processes the candidate block and moves the state machine if it - // is valid and from the correct leader. - Prepare(from mino.Address, block types.Block) (types.Digest, error) - - // Commit moves the state machine to the next state if the signature is - // valid for the candidate. - Commit(types.Digest, crypto.Signature) error - - // Finalize finalizes a round if the signature is a valid commit signature. - Finalize(types.Digest, crypto.Signature) error - - // Accept processes the view during a view change state, and moves to a new - // round if enough have been received. - Accept(View) error - - // AcceptAll processes the list of views so that it may proceed to a future - // round if the list contains enough valid views. - AcceptAll([]View) error - - // Expire announces that the round has expired and moves the state machine - // to a view change state. - Expire(addr mino.Address) (View, error) - - // CatchUp forces a valid block to be processed by the state machine without - // doing the intermediate phases. - CatchUp(types.BlockLink) error - - // Watch returns a channel that is populated with the changes of states from - // the state machine. - Watch(context.Context) <-chan State -} - -type round struct { - leader uint16 - threshold int - id types.Digest - block types.Block - tree hashtree.StagingTree - prepareSig crypto.Signature - changeset authority.ChangeSet - committed bool - prevViews map[mino.Address]View - views map[mino.Address]View - - // allows a node to catch up on a new leader - tentativeRound types.Digest - tentativeLeader uint16 -} - -// AuthorityReader is a function to help the state machine to read the current -// authority for a given tree. -type AuthorityReader func(tree hashtree.Tree) (authority.Authority, error) - -// pbftsm is an implementation of a state machine to perform PBFT rounds. -// -// - implements pbft.Statemachine -type pbftsm struct { - sync.Mutex - - logger zerolog.Logger - watcher core.Observable - hashFac crypto.HashFactory - val validation.Service - blocks blockstore.BlockStore - genesis blockstore.GenesisStore - tree blockstore.TreeCache - authReader AuthorityReader - db kv.DB - - // verifierFac creates a verifier for the aggregated signature. - verifierFac crypto.VerifierFactory - // signer signs and verify single signature for the view change. - signer crypto.Signer - - state State - round round -} - -// StateMachineParam is a structure to pass the different components of the PBFT -// state machine. -type StateMachineParam struct { - Logger zerolog.Logger - Validation validation.Service - VerifierFactory crypto.VerifierFactory - Signer crypto.Signer - Blocks blockstore.BlockStore - Genesis blockstore.GenesisStore - Tree blockstore.TreeCache - AuthorityReader AuthorityReader - DB kv.DB -} - -// NewStateMachine returns a new state machine. -func NewStateMachine(param StateMachineParam) StateMachine { - return &pbftsm{ - logger: param.Logger, - watcher: core.NewWatcher(), - hashFac: crypto.NewSha256Factory(), - val: param.Validation, - verifierFac: param.VerifierFactory, - signer: param.Signer, - blocks: param.Blocks, - genesis: param.Genesis, - tree: param.Tree, - db: param.DB, - state: NoneState, - authReader: param.AuthorityReader, - } -} - -// GetState implements pbft.StateMachine. It returns the current state of the -// machine. -func (m *pbftsm) GetState() State { - m.Lock() - defer m.Unlock() - - return m.state -} - -// GetLeader implements pbft.StateMachine. It returns the current leader of the -// round, or nil if the roster is not yet defined. -func (m *pbftsm) GetLeader() (mino.Address, error) { - m.Lock() - defer m.Unlock() - - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return nil, xerrors.Errorf("failed to read roster: %v", err) - } - - iter := roster.AddressIterator() - iter.Seek(int(m.round.leader)) - - return iter.GetNext(), nil -} - -// GetViews implements pbft.StateMachine. It returns the views for which the -// current round has been accepted. -func (m *pbftsm) GetViews() map[mino.Address]View { - m.Lock() - - views := make(map[mino.Address]View) - for key, value := range m.round.prevViews { - views[key] = value - } - - m.Unlock() - - return views -} - -// GetCommit implements pbft.StateMachine. It returns the proposal identifier -// and the block that have been proposed to the state machine. The values are -// valid only if the state is at least PrepareState. -func (m *pbftsm) GetCommit() (types.Digest, types.Block) { - m.Lock() - defer m.Unlock() - - return m.round.id, m.round.block -} - -// Prepare implements pbft.StateMachine. It receives the proposal from the -// leader and the current tree, and produces the next tree alongside the ID of -// the proposal that will be signed. -func (m *pbftsm) Prepare(from mino.Address, block types.Block) (types.Digest, error) { - m.Lock() - defer m.Unlock() - - id := m.round.id - - if m.state == ViewChangeState { - // When in view change mode, it must refuse any proposal incoming until - // the node leaves the state. - return id, xerrors.New("cannot be in view change state during prepare") - } - - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return id, xerrors.Errorf("failed to read roster: %v", err) - } - - _, index := roster.GetPublicKey(from) - - if uint16(index) != m.round.leader { - // Allows the node to catchup on the leader. It rejects the proposal, - // but will accept this leader later if the block is finalized and - // synced to us. - m.round.tentativeRound = block.GetHash() - m.round.tentativeLeader = uint16(index) - return id, xerrors.Errorf("'%v' is not the leader", from) - } - - // Check the state after verifying that the proposal comes from the right - // leader. - if m.state == PrepareState || m.state == CommitState { - // The leader should only propose one block, therefore the accepted - // proposal identifier is sent back, whatever the input is. - return id, nil - } - - m.round.threshold = calculateThreshold(roster.Len()) - - err = m.verifyPrepare(m.tree.Get(), block, &m.round, roster) - if err != nil { - return id, err - } - - m.setState(PrepareState) - - return m.round.id, nil -} - -// Commit implements pbft.StateMachine. It commits the state machine to the -// proposal if the signature is verified. -func (m *pbftsm) Commit(id types.Digest, sig crypto.Signature) error { - m.Lock() - defer m.Unlock() - - if m.state != PrepareState && m.state != CommitState { - return xerrors.Errorf("cannot commit from %v state", m.state) - } - - if id != m.round.id { - return xerrors.Errorf("mismatch id '%v' != '%v'", id, m.round.id) - } - - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return xerrors.Errorf("failed to read roster: %v", err) - } - - err = m.verifyCommit(&m.round, sig, roster) - if err != nil { - return err - } - - // At this point, the proposal must be finalized whatever happens. - m.round.committed = true - - m.setState(CommitState) - - return nil -} - -// Finalize implements pbft.StateMachine. It makes sure the commit signature is -// correct and then moves to the initial state. -func (m *pbftsm) Finalize(id types.Digest, sig crypto.Signature) error { - m.Lock() - defer m.Unlock() - - if m.state != CommitState { - return xerrors.Errorf("mismatch state %v != %v", m.state, CommitState) - } - - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return xerrors.Errorf("failed to read roster: %v", err) - } - - err = m.verifyFinalize(&m.round, sig, roster) - if err != nil { - return err - } - - dela.Logger.Info().Msgf("finalize round with leader: %d", m.round.leader) - - m.round.prevViews = nil - m.round.views = nil - m.round.committed = false - - m.setState(InitialState) - - return nil -} - -// Accept implements pbft.StateMachine. It processes view change messages and -// reset the current PBFT if it receives enough of them. -func (m *pbftsm) Accept(view View) error { - m.Lock() - defer m.Unlock() - - _, err := m.init() - if err != nil { - return xerrors.Errorf("init: %v", err) - } - - if view.leader == m.round.leader { - // Ignore view coming for the current leader as we already accepted this - // leader. - return nil - } - - err = m.verifyViews(false, view) - if err != nil { - return xerrors.Errorf("invalid view: %v", err) - } - - if m.round.views == nil { - m.round.views = make(map[mino.Address]View) - } - - m.logger.Trace(). - Str("from", view.from.String()). - Msg("view accepted") - - m.round.views[view.from] = view - - m.checkViewChange(view) - - return nil -} - -// AcceptAll implements pbft.StateMachine. It accepts a list of views which -// allows a node falling behind to catch up. The list must contain enough views -// to reach the threshold, otherwise it will be ignored. -func (m *pbftsm) AcceptAll(views []View) error { - m.Lock() - defer m.Unlock() - - _, err := m.init() - if err != nil { - return xerrors.Errorf("init: %v", err) - } - - if len(views) <= m.round.threshold { - return xerrors.Errorf("not enough views: %d <= %d", - len(views), m.round.threshold) - } - - if views[0].leader == m.round.leader { - // Skip verifying the views if the leader will anyway be the same. - return nil - } - - err = m.verifyViews(true, views...) - if err != nil { - return xerrors.Errorf("invalid view: %v", err) - } - - set := make(map[mino.Address]View) - for _, view := range views { - set[view.from] = view - } - - m.round.views = set - m.state = ViewChangeState - m.checkViewChange(views[0]) - - return nil -} - -func (m *pbftsm) verifyViews(skip bool, views ...View) error { - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return xerrors.Errorf("failed to read roster: %v", err) - } - - for _, view := range views { - pubkey, _ := roster.GetPublicKey(view.from) - if pubkey == nil { - return xerrors.Errorf("unknown peer: %v", view.from) - } - - err := view.Verify(pubkey) - if err != nil { - return xerrors.Errorf("invalid signature: %v", err) - } - - nextLeader := (m.round.leader + 1) % uint16(roster.Len()) - if !skip && view.leader != nextLeader { - // The state machine ignore view messages from different rounds. It only - // accepts views for the next leader even if the state machine is not in - // ViewChange state. - // - // This check can be skipped when enough views are received for a - // given leader index. - return xerrors.Errorf("mismatch leader %d != %d", view.leader, nextLeader) - } - - latestID, err := m.getLatestID() - if err != nil { - return xerrors.Errorf("failed to read latest id: %v", err) - } - - if view.id != latestID { - return xerrors.Errorf("mismatch id %v != %v", view.id, latestID) - } - } - - return nil -} - -// Expire implements pbft.StateMachine. It moves the state machine to the -// ViewChange state. If it has already received enough messages, it will move to -// the next leader. -func (m *pbftsm) Expire(addr mino.Address) (View, error) { - m.Lock() - defer m.Unlock() - - m.logger.Warn().Msgf("expire: current leader is %d", m.round.leader) - - roster, err := m.init() - if err != nil { - return View{}, xerrors.Errorf("init: %v", err) - } - - lastID, err := m.getLatestID() - if err != nil { - return View{}, xerrors.Errorf("couldn't get latest digest: %v", err) - } - - newLeader := (m.round.leader + 1) % uint16(roster.Len()) - - param := ViewParam{ - From: addr, - ID: lastID, - Leader: newLeader, - } - - view, err := NewViewAndSign(param, m.signer) - if err != nil { - return view, xerrors.Errorf("create view: %v", err) - } - - m.setState(ViewChangeState) - - if m.round.views == nil { - m.round.views = make(map[mino.Address]View) - } - - m.round.views[addr] = view - - m.checkViewChange(view) - - return view, nil -} - -// CatchUp implements pbft.StateMachine. It can force a block link to be -// inserted to reset the state machine to a initial state. -func (m *pbftsm) CatchUp(link types.BlockLink) error { - m.Lock() - defer m.Unlock() - - if m.state == CommitState && m.round.id != link.GetHash() { - return xerrors.Errorf("already committed to '%v'", m.round.id) - } - - r := round{ - threshold: m.round.threshold, - } - - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return xerrors.Errorf("failed to read roster: %v", err) - } - - err = m.verifyPrepare(m.tree.Get(), link.GetBlock(), &r, roster) - if err != nil { - return xerrors.Errorf("prepare failed: %v", err) - } - - err = m.verifyCommit(&r, link.GetPrepareSignature(), roster) - if err != nil { - return xerrors.Errorf("commit failed: %v", err) - } - - err = m.verifyFinalize(&r, link.GetCommitSignature(), roster) - if err != nil { - return xerrors.Errorf("finalize failed: %v", err) - } - - if link.GetTo() == m.round.tentativeRound { - m.round.leader = m.round.tentativeLeader - dela.Logger.Info().Msgf("accepting to set leader to: %d", m.round.leader) - } - - m.round.views = nil - m.round.prevViews = nil - m.setState(InitialState) - - return nil -} - -// Watch implements pbft.StateMachine. It returns a channel that will be -// populated with stage changes. -func (m *pbftsm) Watch(ctx context.Context) <-chan State { - ch := make(chan State, 100) - - obs := observer{ch: ch} - m.watcher.Add(obs) - - go func() { - <-ctx.Done() - m.watcher.Remove(obs) - close(ch) - }() - - return ch -} - -func (m *pbftsm) verifyPrepare(tree hashtree.Tree, block types.Block, r *round, ro authority.Authority) error { - stageTree, err := tree.Stage(func(snap store.Snapshot) error { - txs := block.GetTransactions() - rejected := 0 - - res, err := m.val.Validate(snap, txs) - if err != nil { - return xerrors.Errorf("validation failed: %v", err) - } - - for _, r := range res.GetTransactionResults() { - accepted, reason := r.GetStatus() - if !accepted { - m.logger.Warn().Str("reason", reason).Msg("transaction not accepted") - rejected++ - } - } - - promTxs.Observe(float64(len(txs))) - promRejectedTxs.Observe(float64(rejected)) - - return nil - }) - - if err != nil { - return xerrors.Errorf("while updating tree: %v", err) - } - - root := types.Digest{} - copy(root[:], stageTree.GetRoot()) - - if root != block.GetTreeRoot() { - return xerrors.Errorf("mismatch tree root '%v' != '%v'", root, block.GetTreeRoot()) - } - - if m.blocks.Len() != block.GetIndex() { - return xerrors.Errorf("mismatch index %d != %d", block.GetIndex(), m.blocks.Len()) - } - - lastID, err := m.getLatestID() - if err != nil { - return xerrors.Errorf("couldn't get latest digest: %v", err) - } - - // The roster will be used to find the differential with the previous one so - // that the forward link can be populated. - roster, err := m.authReader(stageTree) - if err != nil { - return xerrors.Errorf("failed to read next roster: %v", err) - } - - changeset := ro.Diff(roster) - opts := []types.LinkOption{ - types.WithChangeSet(changeset), - types.WithLinkHashFactory(m.hashFac), - } - - link, err := types.NewForwardLink(lastID, block.GetHash(), opts...) - if err != nil { - return xerrors.Errorf("failed to create link: %v", err) - } - - r.id = link.GetHash() - r.tree = stageTree - r.block = block - r.changeset = changeset - - return nil -} - -func (m *pbftsm) verifyCommit(r *round, sig crypto.Signature, ro authority.Authority) error { - verifier, err := m.verifierFac.FromAuthority(ro) - if err != nil { - return xerrors.Errorf("couldn't make verifier: %v", err) - } - - err = verifier.Verify(r.id[:], sig) - if err != nil { - return xerrors.Errorf("verifier failed: %v", err) - } - - r.prepareSig = sig - - return nil -} - -func (m *pbftsm) verifyFinalize(r *round, sig crypto.Signature, ro authority.Authority) error { - verifier, err := m.verifierFac.FromAuthority(ro) - if err != nil { - return xerrors.Errorf("couldn't make verifier: %v", err) - } - - buffer, err := r.prepareSig.MarshalBinary() - if err != nil { - return xerrors.Errorf("couldn't marshal signature: %v", err) - } - - err = verifier.Verify(buffer, sig) - if err != nil { - return xerrors.Errorf("verifier failed: %v", err) - } - - lastID, err := m.getLatestID() - if err != nil { - return xerrors.Errorf("couldn't get latest digest: %v", err) - } - - // Persist to the database in a transaction so that it can revert to the - // previous state for either the tree or the block if something goes wrong. - err = m.db.Update(func(txn kv.WritableTx) error { - // 1. Persist the tree through the transaction and update the cache. - err := r.tree.WithTx(txn).Commit() - if err != nil { - return xerrors.Errorf("while committing tree: %v", err) - } - - var unlock func() - - txn.OnCommit(func() { - // The cache is updated only after both are committed with the tree - // using the database as the transaction is done. - unlock = m.tree.SetWithLock(r.tree) - }) - - // 2. Persist the block and its forward link. - opts := []types.LinkOption{ - types.WithSignatures(r.prepareSig, sig), - types.WithChangeSet(r.changeset), - types.WithLinkHashFactory(m.hashFac), - } - - link, err := types.NewBlockLink(lastID, r.block, opts...) - if err != nil { - return xerrors.Errorf("creating link: %v", err) - } - - err = m.blocks.WithTx(txn).Store(link) - if err != nil { - return xerrors.Errorf("store block: %v", err) - } - - // Only release the tree cache at the very end of the transaction, so - // that a call to get the tree will hold until the block is stored. - txn.OnCommit(func() { - promBlocks.Set(float64(m.blocks.Len())) - promLeader.Set(float64(m.round.leader)) - unlock() - }) - - return nil - }) - - if err != nil { - return xerrors.Errorf("database failed: %v", err) - } - - return nil -} - -func (m *pbftsm) init() (authority.Authority, error) { - roster, err := m.authReader(m.tree.Get()) - if err != nil { - return nil, xerrors.Errorf("failed to read roster: %v", err) - } - - if m.state != NoneState { - return roster, nil - } - - m.round.threshold = calculateThreshold(roster.Len()) - - m.setState(InitialState) - - return roster, nil -} - -func (m *pbftsm) setState(s State) { - m.state = s - m.watcher.Notify(s) -} - -func (m *pbftsm) checkViewChange(view View) { - if m.state == ViewChangeState && len(m.round.views) > m.round.threshold { - m.round.prevViews = m.round.views - m.round.views = nil - m.round.leader = view.leader - - if m.round.committed { - m.setState(CommitState) - } else { - m.setState(InitialState) - } - } -} - -func (m *pbftsm) getLatestID() (types.Digest, error) { - if m.blocks.Len() == 0 { - genesis, err := m.genesis.Get() - if err != nil { - return types.Digest{}, err - } - - return genesis.GetHash(), nil - } - - last, err := m.blocks.Last() - if err != nil { - return types.Digest{}, err - } - - return last.GetTo(), nil -} - -type observer struct { - ch chan State -} - -func (obs observer) NotifyCallback(event interface{}) { - obs.ch <- event.(State) -} - -// CalculateThreshold returns the number of messages that a node needs to -// receive before confirming the view change. The threshold is 2*f where f can -// be found with n = 3*f+1 where n is the number of participants. -func calculateThreshold(n int) int { - f := (n - 1) / 3 - if f == 0 { - return n - } - - return 2 * f -} diff --git a/dela/core/ordering/cosipbft/pbft/pbft_test.go b/dela/core/ordering/cosipbft/pbft/pbft_test.go deleted file mode 100644 index 6c453d6..0000000 --- a/dela/core/ordering/cosipbft/pbft/pbft_test.go +++ /dev/null @@ -1,975 +0,0 @@ -package pbft - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/store/hashtree/binprefix" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" -) - -func TestState_String(t *testing.T) { - var state State = 99 - require.Equal(t, "unknown", state.String()) - - state = PrepareState - require.Equal(t, "prepare", state.String()) - - state = 0 - require.Equal(t, "none", state.String()) -} - -func TestStateMachine_GetState(t *testing.T) { - sm := &pbftsm{} - require.Equal(t, NoneState, sm.GetState()) - - sm.state = CommitState - require.Equal(t, CommitState, sm.GetState()) -} - -func TestStateMachine_GetLeader(t *testing.T) { - roster := fake.NewAuthority(3, fake.NewSigner) - - sm := &pbftsm{ - tree: blockstore.NewTreeCache(badTree{}), - authReader: func(hashtree.Tree) (authority.Authority, error) { - return authority.FromAuthority(roster), nil - }, - } - - leader, err := sm.GetLeader() - require.NoError(t, err) - require.Equal(t, roster.GetAddress(0), leader) - - sm.round.leader = 2 - leader, err = sm.GetLeader() - require.NoError(t, err) - require.Equal(t, roster.GetAddress(2), leader) - - sm.authReader = badReader - _, err = sm.GetLeader() - require.EqualError(t, err, fake.Err("failed to read roster")) -} - -func TestStateMachine_GetViews(t *testing.T) { - sm := &pbftsm{} - require.Len(t, sm.GetViews(), 0) - - sm.round.prevViews = map[mino.Address]View{ - fake.NewAddress(0): {}, - fake.NewAddress(1): {}, - } - require.Len(t, sm.GetViews(), 2) -} - -func TestStateMachine_GetCommit(t *testing.T) { - sm := &pbftsm{} - - id, block := sm.GetCommit() - require.Equal(t, types.Digest{}, id) - require.Equal(t, types.Block{}, block) - - block, err := types.NewBlock(simple.Result{}, types.WithIndex(1)) - require.NoError(t, err) - - sm.round.id = types.Digest{1} - sm.round.block = block - id, block = sm.GetCommit() - require.Equal(t, types.Digest{1}, id) - require.Equal(t, block, block) -} - -func TestStateMachine_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - param := StateMachineParam{ - Validation: simple.NewService(fakeExec{}, nil), - Blocks: blockstore.NewInMemory(), - Genesis: blockstore.NewGenesisStore(), - Tree: blockstore.NewTreeCache(tree), - AuthorityReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - DB: db, - } - - param.Genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root), types.WithIndex(0)) - require.NoError(t, err) - - from := fake.NewAddress(0) - - sm := NewStateMachine(param).(*pbftsm) - sm.state = InitialState - - id, err := sm.Prepare(from, block) - require.NoError(t, err) - require.NotEqual(t, types.Digest{}, id) - require.Equal(t, PrepareState, sm.state) - require.Equal(t, id, sm.round.id) - - id, err = sm.Prepare(from, block) - require.NoError(t, err) - require.Equal(t, sm.round.id, id) -} - -func TestStateMachine_WhileViewChange_Prepare(t *testing.T) { - sm := &pbftsm{ - state: ViewChangeState, - } - - _, err := sm.Prepare(fake.NewAddress(0), types.Block{}) - require.EqualError(t, err, "cannot be in view change state during prepare") -} - -func TestStateMachine_WrongLeader_Prepare(t *testing.T) { - tree, _, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - authReader: goodReader, - tree: blockstore.NewTreeCache(tree), - } - - link := makeLink(t) - - _, err := sm.Prepare(fake.NewAddress(1), link.GetBlock()) - require.EqualError(t, err, "'fake.Address[1]' is not the leader") -} - -func TestStateMachine_FailedValidation_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - val: badValidation{}, - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: goodReader, - } - - link := makeLink(t) - - _, err := sm.Prepare(fake.NewAddress(0), link.GetBlock()) - require.EqualError(t, err, fake.Err("while updating tree: callback failed: validation failed")) -} - -func TestStateMachine_MismatchTreeRoot_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - val: badValidation{}, - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: goodReader, - } - - other, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(types.Digest{})) - require.NoError(t, err) - - sm.val = unacceptedTxsValidation{} - _, err = sm.Prepare(fake.NewAddress(0), other) - require.EqualError(t, err, "mismatch tree root '71b6c1d5' != '00000000'") -} - -func TestStateMachine_MissingGenesis_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - val: simple.NewService(fakeExec{}, nil), - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: goodReader, - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - } - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root)) - require.NoError(t, err) - - _, err = sm.Prepare(fake.NewAddress(0), block) - require.EqualError(t, err, "couldn't get latest digest: missing genesis block") -} - -func TestStateMachine_FailReadCurrentRoster_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - val: simple.NewService(fakeExec{}, nil), - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: badReader, - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - } - - sm.genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root)) - require.NoError(t, err) - - _, err = sm.Prepare(fake.NewAddress(0), block) - require.EqualError(t, err, fake.Err("failed to read roster")) -} - -func TestStateMachine_FailReadRosterInStageTree_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - counter := fake.NewCounter(1) - - sm := &pbftsm{ - state: InitialState, - val: simple.NewService(fakeExec{}, nil), - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: func(hashtree.Tree) (authority.Authority, error) { - if !counter.Done() { - counter.Decrease() - return authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)), nil - } - - return nil, fake.GetError() - }, - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - hashFac: crypto.NewSha256Factory(), - watcher: core.NewWatcher(), - } - - sm.genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root)) - require.NoError(t, err) - - // Failure to read the roster of the staging tree. - _, err = sm.Prepare(fake.NewAddress(0), block) - require.EqualError(t, err, fake.Err("failed to read next roster")) -} - -func TestStateMachine_FailCreateLink_Prepare(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: InitialState, - val: simple.NewService(fakeExec{}, nil), - tree: blockstore.NewTreeCache(tree), - db: db, - authReader: goodReader, - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - hashFac: fake.NewHashFactory(fake.NewBadHash()), - watcher: core.NewWatcher(), - } - - sm.genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root)) - require.NoError(t, err) - - _, err = sm.Prepare(fake.NewAddress(0), block) - require.EqualError(t, err, - fake.Err("failed to create link: failed to fingerprint: couldn't write from")) -} - -func TestStateMachine_Commit(t *testing.T) { - sm := &pbftsm{ - state: PrepareState, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - watcher: core.NewWatcher(), - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - round: round{ - id: types.Digest{1}, - }, - } - - err := sm.Commit(types.Digest{1}, fake.Signature{}) - require.NoError(t, err) - require.Equal(t, CommitState, sm.state) - require.True(t, sm.round.committed) - require.NotNil(t, sm.round.prepareSig) -} - -func TestStateMachine_WhileViewChange_Commit(t *testing.T) { - sm := &pbftsm{ - state: ViewChangeState, - } - - err := sm.Commit(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, "cannot commit from viewchange state") -} - -func TestStateMachine_MismatchCandidate_Commit(t *testing.T) { - sm := &pbftsm{ - state: PrepareState, - round: round{ - id: types.Digest{1}, - }, - } - - err := sm.Commit(types.Digest{2}, fake.Signature{}) - require.EqualError(t, err, "mismatch id '02000000' != '01000000'") -} - -func TestStateMachine_FailCreateVerifier_Commit(t *testing.T) { - sm := &pbftsm{ - state: PrepareState, - verifierFac: fake.NewBadVerifierFactory(), - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - } - - err := sm.Commit(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("couldn't make verifier")) -} - -func TestStateMachine_WrongSignature_Commit(t *testing.T) { - sm := &pbftsm{ - state: PrepareState, - verifierFac: fake.NewVerifierFactory(fake.NewBadVerifier()), - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - } - - err := sm.Commit(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("verifier failed")) -} - -func TestStateMachine_FailReadCurrentRoster_Commit(t *testing.T) { - sm := &pbftsm{ - state: PrepareState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: badReader, - } - - err := sm.Commit(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("failed to read roster")) -} - -func TestStateMachine_Finalize(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - param := StateMachineParam{ - VerifierFactory: fake.NewVerifierFactory(fake.Verifier{}), - Blocks: blockstore.NewInMemory(), - Genesis: blockstore.NewGenesisStore(), - Tree: blockstore.NewTreeCache(tree), - AuthorityReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - DB: db, - } - - param.Genesis.Set(types.Genesis{}) - - sm := NewStateMachine(param).(*pbftsm) - sm.state = CommitState - sm.round.tree = tree.(hashtree.StagingTree) - sm.round.prepareSig = fake.Signature{} - - err := sm.Finalize(types.Digest{1}, fake.Signature{}) - require.NoError(t, err) -} - -func TestStateMachine_NotCommitted_Finalize(t *testing.T) { - sm := &pbftsm{ - state: InitialState, - } - - err := sm.Finalize(types.Digest{1}, fake.Signature{}) - require.EqualError(t, err, "mismatch state initial != commit") -} - -func TestStateMachine_FailReadCurrentRoster_Finalize(t *testing.T) { - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: badReader, - } - - err := sm.Finalize(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("failed to read roster")) -} - -func TestStateMachine_FailCreateVerifier_Finalize(t *testing.T) { - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - verifierFac: fake.NewBadVerifierFactory(), - } - - err := sm.Finalize(types.Digest{1}, fake.Signature{}) - require.EqualError(t, err, fake.Err("couldn't make verifier")) -} - -func TestStateMachine_WrongSignature_Finalize(t *testing.T) { - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.NewBadVerifier()), - round: round{ - prepareSig: fake.Signature{}, - }, - } - - err := sm.Finalize(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("verifier failed")) -} - -func TestStateMachine_MissingGenesis_Finalize(t *testing.T) { - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - round: round{ - prepareSig: fake.Signature{}, - }, - } - - err := sm.Finalize(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, "couldn't get latest digest: missing genesis block") -} - -func TestStateMachine_BadBlockStore_Finalize(t *testing.T) { - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(badTree{}), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - genesis: blockstore.NewGenesisStore(), - blocks: badBlockStore{length: 1}, - round: round{ - prepareSig: fake.Signature{}, - }, - } - - err := sm.Finalize(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("couldn't get latest digest")) -} - -func TestStateMachine_FailCommitTree_Finalize(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(tree), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - db: db, - round: round{ - prepareSig: fake.Signature{}, - tree: badTree{}, - }, - } - - sm.blocks.Store(makeLink(t)) - - err := sm.Finalize(types.Digest{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("database failed: while committing tree")) -} - -func TestStateMachine_FailCreateLink_Finalize(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(tree), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - genesis: blockstore.NewGenesisStore(), - blocks: blockstore.NewInMemory(), - db: db, - hashFac: fake.NewHashFactory(fake.NewBadHash()), - round: round{ - prepareSig: fake.Signature{}, - tree: tree.(hashtree.StagingTree), - }, - } - - sm.blocks.Store(makeLink(t)) - - err := sm.Finalize(types.Digest{1}, fake.Signature{}) - require.Error(t, err) - require.Contains(t, err.Error(), "database failed: creating link:") -} - -func TestStateMachine_FailStoreBlock_Finalize(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - sm := &pbftsm{ - state: CommitState, - tree: blockstore.NewTreeCache(tree), - authReader: goodReader, - verifierFac: fake.NewVerifierFactory(fake.Verifier{}), - genesis: blockstore.NewGenesisStore(), - blocks: badBlockStore{}, - db: db, - hashFac: crypto.NewSha256Factory(), - round: round{ - prepareSig: fake.Signature{}, - tree: tree.(hashtree.StagingTree), - }, - } - - sm.genesis.Set(types.Genesis{}) - - err := sm.Finalize(types.Digest{1}, fake.Signature{}) - require.EqualError(t, err, fake.Err("database failed: store block")) -} - -func TestStateMachine_Accept(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(4, fake.NewSigner)) - - sm := &pbftsm{ - state: ViewChangeState, - blocks: blockstore.NewInMemory(), - genesis: blockstore.NewGenesisStore(), - watcher: core.NewWatcher(), - signer: fake.NewSigner(), - tree: blockstore.NewTreeCache(badTree{}), - authReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - } - - sm.genesis.Set(types.Genesis{}) - sm.round.threshold = 2 - - err := sm.Accept(View{from: fake.NewAddress(0), leader: 1}) - require.NoError(t, err) - require.Equal(t, 2, sm.round.threshold) - require.Len(t, sm.round.views, 1) - - err = sm.Accept(View{from: fake.NewAddress(1), leader: 1}) - require.NoError(t, err) - require.Len(t, sm.round.views, 2) - - // Ignore view for the same leader. - err = sm.Accept(View{from: fake.NewAddress(2), leader: 0}) - require.NoError(t, err) - require.Len(t, sm.round.views, 2) - - // Ignore duplicate. - err = sm.Accept(View{from: fake.NewAddress(0), leader: 1}) - require.NoError(t, err) - require.Len(t, sm.round.views, 2) - - // Finalize view change from a commit state - sm.round.committed = true - err = sm.Accept(View{from: fake.NewAddress(3), leader: 1}) - require.NoError(t, err) - require.Equal(t, CommitState, sm.state) - - // Ignore views for a different leader than the next one. - err = sm.Accept(View{from: fake.NewAddress(2), leader: 5}) - require.EqualError(t, err, "invalid view: mismatch leader 5 != 2") - require.Len(t, sm.round.views, 0) - - sm.genesis = blockstore.NewGenesisStore() - err = sm.Accept(View{from: fake.NewAddress(0), leader: 2}) - require.EqualError(t, err, "invalid view: failed to read latest id: missing genesis block") - - // Only accept views for the current round ID. - sm.genesis.Set(types.Genesis{}) - err = sm.Accept(View{from: fake.NewAddress(3), leader: 2, id: types.Digest{1}}) - require.EqualError(t, err, "invalid view: mismatch id 01000000 != 00000000") - - sm.authReader = badReader - err = sm.Accept(View{leader: 2}) - require.EqualError(t, err, fake.Err("init: failed to read roster")) - - // Ignore view with an invalid signature. - sm.state = InitialState - sm.authReader = func(hashtree.Tree) (authority.Authority, error) { - ro := authority.New( - []mino.Address{fake.NewAddress(0)}, - []crypto.PublicKey{fake.NewBadPublicKey()}, - ) - return ro, nil - } - err = sm.Accept(View{from: fake.NewAddress(0), leader: 2}) - require.EqualError(t, err, fake.Err("invalid view: invalid signature: verify")) -} - -func TestStateMachine_verifyViews(t *testing.T) { - sm := &pbftsm{ - tree: blockstore.NewTreeCache(badTree{}), - authReader: badReader, - } - - err := sm.verifyViews(false) - require.EqualError(t, err, fake.Err("failed to read roster")) -} - -func TestStateMachine_AcceptAll(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(4, fake.NewSigner)) - - sm := &pbftsm{ - blocks: blockstore.NewInMemory(), - genesis: blockstore.NewGenesisStore(), - watcher: core.NewWatcher(), - signer: fake.NewSigner(), - tree: blockstore.NewTreeCache(badTree{}), - authReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - } - - sm.genesis.Set(types.Genesis{}) - - err := sm.AcceptAll([]View{ - {from: fake.NewAddress(0), leader: 5}, - {from: fake.NewAddress(1), leader: 5}, - {from: fake.NewAddress(2), leader: 5}, - }) - require.NoError(t, err) - require.Equal(t, 2, sm.round.threshold) - require.Equal(t, uint16(5), sm.round.leader) - require.Equal(t, InitialState, sm.state) - require.Nil(t, sm.round.views) - require.Len(t, sm.round.prevViews, 3) - - sm.round.threshold = 0 - err = sm.AcceptAll([]View{{leader: 5}}) - require.NoError(t, err) - - // Only accept if there are enough views. - err = sm.AcceptAll([]View{}) - require.EqualError(t, err, "not enough views: 0 <= 0") - - err = sm.AcceptAll([]View{{from: fake.NewAddress(4), leader: 6}}) - require.EqualError(t, err, "invalid view: unknown peer: fake.Address[4]") - - sm.state = NoneState - sm.authReader = badReader - err = sm.AcceptAll([]View{{}}) - require.EqualError(t, err, fake.Err("init: failed to read roster")) -} - -func TestStateMachine_Expire(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(4, fake.NewSigner)) - - sm := &pbftsm{ - watcher: core.NewWatcher(), - blocks: blockstore.NewInMemory(), - genesis: blockstore.NewGenesisStore(), - signer: bls.NewSigner(), - tree: blockstore.NewTreeCache(badTree{}), - authReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - } - - sm.genesis.Set(types.Genesis{}) - - view, err := sm.Expire(fake.NewAddress(0)) - require.NoError(t, err) - require.Equal(t, 2, sm.round.threshold) - require.Equal(t, uint16(1), view.leader) - require.NoError(t, view.Verify(sm.signer.GetPublicKey())) - - sm.signer = fake.NewBadSigner() - _, err = sm.Expire(fake.NewAddress(0)) - require.EqualError(t, err, fake.Err("create view: signer")) - - sm.signer = fake.NewSigner() - sm.genesis = blockstore.NewGenesisStore() - _, err = sm.Expire(fake.NewAddress(0)) - require.EqualError(t, err, "couldn't get latest digest: missing genesis block") - - sm.authReader = badReader - sm.state = NoneState - _, err = sm.Expire(fake.NewAddress(0)) - require.EqualError(t, err, fake.Err("init: failed to read roster")) -} - -func TestStateMachine_CatchUp(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - param := StateMachineParam{ - Validation: simple.NewService(fakeExec{}, nil), - VerifierFactory: fake.VerifierFactory{}, - Blocks: blockstore.NewInMemory(), - Genesis: blockstore.NewGenesisStore(), - Tree: blockstore.NewTreeCache(tree), - AuthorityReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - DB: db, - } - - param.Genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root), types.WithIndex(0)) - require.NoError(t, err) - - sm := NewStateMachine(param).(*pbftsm) - - opts := []types.LinkOption{ - types.WithSignatures(fake.Signature{}, fake.Signature{}), - types.WithChangeSet(authority.NewChangeSet()), - } - - link, err := types.NewBlockLink(types.Digest{}, block, opts...) - require.NoError(t, err) - - err = sm.CatchUp(link) - require.NoError(t, err) - - sm.state = CommitState - sm.round.id = types.Digest{} - err = sm.CatchUp(link) - require.EqualError(t, err, "already committed to '00000000'") - - sm.state = InitialState - sm.round.id = link.GetHash() - err = sm.CatchUp(link) - require.EqualError(t, err, "prepare failed: mismatch index 0 != 1") - - sm.authReader = badReader - err = sm.CatchUp(link) - require.EqualError(t, err, fake.Err("failed to read roster")) - - sm.authReader = param.AuthorityReader - sm.blocks = blockstore.NewInMemory() - sm.verifierFac = fake.NewVerifierFactory(fake.NewBadVerifier()) - err = sm.CatchUp(link) - require.EqualError(t, err, fake.Err("commit failed: verifier failed")) - - opts = []types.LinkOption{ - types.WithSignatures(fake.NewBadSignature(), fake.Signature{}), - types.WithChangeSet(authority.NewChangeSet()), - } - - link, err = types.NewBlockLink(types.Digest{}, block, opts...) - require.NoError(t, err) - sm.verifierFac = fake.VerifierFactory{} - err = sm.CatchUp(link) - require.EqualError(t, err, fake.Err("finalize failed: couldn't marshal signature")) -} - -// checks that the tentative leader is set in case the tentative round is equal -// to the proposed block. -func TestStateMachine_CatchUp_Tentative_Leader_Accept(t *testing.T) { - tree, db, clean := makeTree(t) - defer clean() - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - param := StateMachineParam{ - Validation: simple.NewService(fakeExec{}, nil), - VerifierFactory: fake.VerifierFactory{}, - Blocks: blockstore.NewInMemory(), - Genesis: blockstore.NewGenesisStore(), - Tree: blockstore.NewTreeCache(tree), - AuthorityReader: func(hashtree.Tree) (authority.Authority, error) { - return ro, nil - }, - DB: db, - } - - param.Genesis.Set(types.Genesis{}) - - root := types.Digest{} - copy(root[:], tree.GetRoot()) - - block, err := types.NewBlock(simple.NewResult(nil), types.WithTreeRoot(root), types.WithIndex(0)) - require.NoError(t, err) - - sm := NewStateMachine(param).(*pbftsm) - - opts := []types.LinkOption{ - types.WithSignatures(fake.Signature{}, fake.Signature{}), - types.WithChangeSet(authority.NewChangeSet()), - } - - link, err := types.NewBlockLink(types.Digest{}, block, opts...) - require.NoError(t, err) - - tentativeLeader := uint16(9) - sm.round.tentativeRound = link.GetTo() - sm.round.tentativeLeader = tentativeLeader - - err = sm.CatchUp(link) - require.NoError(t, err) - - require.Equal(t, tentativeLeader, sm.round.leader) -} - -func TestStateMachine_Watch(t *testing.T) { - sm := &pbftsm{ - watcher: core.NewWatcher(), - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - statesCh := sm.Watch(ctx) - - sm.setState(ViewChangeState) - state := <-statesCh - require.Equal(t, ViewChangeState, state) - - cancel() - _, more := <-statesCh - require.False(t, more) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeTree(t *testing.T) (hashtree.Tree, kv.DB, func()) { - dir, err := os.MkdirTemp(os.TempDir(), "pbft") - require.NoError(t, err) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - tree := binprefix.NewMerkleTree(db, binprefix.Nonce{}) - stage, err := tree.Stage(func(store.Snapshot) error { return nil }) - require.NoError(t, err) - - return stage, db, func() { os.RemoveAll(dir) } -} - -func makeLink(t *testing.T) types.BlockLink { - block, err := types.NewBlock(simple.NewResult(nil)) - require.NoError(t, err) - - link, err := types.NewBlockLink(types.Digest{}, block) - require.NoError(t, err) - - return link -} - -type fakeExec struct { - err error -} - -func (e fakeExec) Execute(store.Snapshot, execution.Step) (execution.Result, error) { - return execution.Result{}, e.err -} - -type badValidation struct { - validation.Service -} - -func (v badValidation) Validate(store.Snapshot, []txn.Transaction) (validation.Result, error) { - return nil, fake.GetError() -} - -type unacceptedTxsValidation struct { - validation.Service -} - -func (v unacceptedTxsValidation) Validate(store.Snapshot, []txn.Transaction) (validation.Result, error) { - return simple.NewResult([]simple.TransactionResult{ - simple.NewTransactionResult(nil, false, "unaccepted"), - }), nil -} - -type badBlockStore struct { - blockstore.BlockStore - length uint64 -} - -func (s badBlockStore) WithTx(store.Transaction) blockstore.BlockStore { - return s -} - -func (s badBlockStore) Len() uint64 { - return s.length -} - -func (s badBlockStore) Last() (types.BlockLink, error) { - return nil, fake.GetError() -} - -func (s badBlockStore) Store(types.BlockLink) error { - return fake.GetError() -} - -type badTree struct { - hashtree.StagingTree -} - -func (t badTree) WithTx(store.Transaction) hashtree.StagingTree { - return t -} - -func (t badTree) Commit() error { - return fake.GetError() -} - -func goodReader(hashtree.Tree) (authority.Authority, error) { - return authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)), nil -} - -func badReader(hashtree.Tree) (authority.Authority, error) { - return nil, fake.GetError() -} diff --git a/dela/core/ordering/cosipbft/pbft/view.go b/dela/core/ordering/cosipbft/pbft/view.go deleted file mode 100644 index bf1249d..0000000 --- a/dela/core/ordering/cosipbft/pbft/view.go +++ /dev/null @@ -1,97 +0,0 @@ -// This file contains the implementation of the views sent by the nodes during a -// view change. -// -// Documentation Last Review: 13.10.2020 -// - -package pbft - -import ( - "encoding/binary" - - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -// View is the view change request sent to other participants. -type View struct { - from mino.Address - id types.Digest - leader uint16 - signature crypto.Signature -} - -// ViewParam contains the parameters to create a view. -type ViewParam struct { - From mino.Address - ID types.Digest - Leader uint16 -} - -// NewView creates a new view. -func NewView(param ViewParam, sig crypto.Signature) View { - return View{ - from: param.From, - id: param.ID, - leader: param.Leader, - signature: sig, - } -} - -// NewViewAndSign creates a new view and uses the signer to make the signature -// that will be verified by other participants. -func NewViewAndSign(param ViewParam, signer crypto.Signer) (View, error) { - view := View{ - from: param.From, - id: param.ID, - leader: param.Leader, - } - - sig, err := signer.Sign(view.bytes()) - if err != nil { - return view, xerrors.Errorf("signer: %v", err) - } - - view.signature = sig - - return view, nil -} - -// GetFrom returns the address the view is coming from. -func (v View) GetFrom() mino.Address { - return v.from -} - -// GetID returns the block digest the view is targeting. -func (v View) GetID() types.Digest { - return v.id -} - -// GetLeader returns the index of the leader proposed by the view. -func (v View) GetLeader() uint16 { - return v.leader -} - -// GetSignature returns the signature of the view. -func (v View) GetSignature() crypto.Signature { - return v.signature -} - -// Verify takes the public key to verify the signature of the view. -func (v View) Verify(pubkey crypto.PublicKey) error { - err := pubkey.Verify(v.bytes(), v.signature) - if err != nil { - return xerrors.Errorf("verify: %v", err) - } - - return nil -} - -func (v View) bytes() []byte { - buffer := make([]byte, 2) - binary.LittleEndian.PutUint16(buffer, v.leader) - - return append(buffer, v.id.Bytes()...) -} diff --git a/dela/core/ordering/cosipbft/pbft/view_test.go b/dela/core/ordering/cosipbft/pbft/view_test.go deleted file mode 100644 index 236f60f..0000000 --- a/dela/core/ordering/cosipbft/pbft/view_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package pbft - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestView_Getters(t *testing.T) { - param := ViewParam{ - From: fake.NewAddress(0), - ID: types.Digest{1}, - Leader: 5, - } - - view := NewView(param, fake.Signature{}) - - require.Equal(t, fake.NewAddress(0), view.GetFrom()) - require.Equal(t, types.Digest{1}, view.GetID()) - require.Equal(t, uint16(5), view.GetLeader()) - require.Equal(t, fake.Signature{}, view.GetSignature()) -} - -func TestView_Verify(t *testing.T) { - param := ViewParam{ - From: fake.NewAddress(0), - ID: types.Digest{2}, - Leader: 3, - } - - signer := bls.NewSigner() - - view, err := NewViewAndSign(param, signer) - require.NoError(t, err) - require.NoError(t, view.Verify(signer.GetPublicKey())) - - _, err = NewViewAndSign(param, fake.NewBadSigner()) - require.EqualError(t, err, fake.Err("signer")) - - err = view.Verify(fake.NewBadPublicKey()) - require.EqualError(t, err, fake.Err("verify")) -} diff --git a/dela/core/ordering/cosipbft/proc.go b/dela/core/ordering/cosipbft/proc.go deleted file mode 100644 index 5c24cae..0000000 --- a/dela/core/ordering/cosipbft/proc.go +++ /dev/null @@ -1,257 +0,0 @@ -// Theis file contains the network message handler implementations for the -// collective signing reactor and the module rpc. -// -// Documentation Last Review: 12.10.2020 -// - -package cosipbft - -import ( - "context" - - "github.com/rs/zerolog" - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync" - "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "golang.org/x/xerrors" -) - -var ( - keyRoster = [32]byte{} - keyAccess = [32]byte{1} -) - -// Processor processes the messages to run a collective signing PBFT consensus. -// -// - implements cosi.Reactor -// - implements mino.Handler -type processor struct { - mino.UnsupportedHandler - types.MessageFactory - - logger zerolog.Logger - pbftsm pbft.StateMachine - sync blocksync.Synchronizer - tree blockstore.TreeCache - pool pool.Pool - watcher core.Observable - rosterFac authority.Factory - hashFactory crypto.HashFactory - access access.Service - - context serde.Context - genesis blockstore.GenesisStore - blocks blockstore.BlockStore - - started chan struct{} -} - -func newProcessor() *processor { - return &processor{ - watcher: core.NewWatcher(), - context: json.NewContext(), - started: make(chan struct{}), - } -} - -// Invoke implements cosi.Reactor. It processes the messages from the collective -// signature module. The messages are either from the the prepare or the commit -// phase. -func (h *processor) Invoke(from mino.Address, msg serde.Message) ([]byte, error) { - switch in := msg.(type) { - case types.BlockMessage: - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - blocks := h.blocks.Watch(ctx) - - // In case the node is falling behind the chain, it gives it a chance to - // catch up before moving forward. - latest := h.sync.GetLatest() - - if latest > h.blocks.Len() { - for link := range blocks { - if link.GetBlock().GetIndex() >= latest { - cancel() - } - } - } - - viewMsgs := in.GetViews() - if len(viewMsgs) > 0 { - h.logger.Debug().Int("num", len(viewMsgs)).Msg("process views") - - views := make([]pbft.View, 0, len(viewMsgs)) - for addr, view := range viewMsgs { - param := pbft.ViewParam{ - From: addr, - ID: view.GetID(), - Leader: view.GetLeader(), - } - - views = append(views, pbft.NewView(param, view.GetSignature())) - } - - // Force a view change if enough views are provided in the situation - // where the current node is falling behind the others. - err := h.pbftsm.AcceptAll(views) - if err != nil { - return nil, xerrors.Errorf("accept all: %v", err) - } - } - - digest, err := h.pbftsm.Prepare(from, in.GetBlock()) - if err != nil { - return nil, xerrors.Errorf("pbft prepare failed: %v", err) - } - - return digest[:], nil - case types.CommitMessage: - err := h.pbftsm.Commit(in.GetID(), in.GetSignature()) - if err != nil { - h.logger.Debug().Msg("commit failed") - - return nil, xerrors.Errorf("pbft commit failed: %v", err) - } - - buffer, err := in.GetSignature().MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal signature: %v", err) - } - - return buffer, nil - default: - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } -} - -// Process implements mino.Handler. It processes the messages from the RPC. -func (h *processor) Process(req mino.Request) (serde.Message, error) { - switch msg := req.Message.(type) { - case types.GenesisMessage: - if h.genesis.Exists() { - return nil, nil - } - - root := msg.GetGenesis().GetRoot() - - return nil, h.storeGenesis(msg.GetGenesis().GetRoster(), &root) - case types.DoneMessage: - err := h.pbftsm.Finalize(msg.GetID(), msg.GetSignature()) - if err != nil { - return nil, xerrors.Errorf("pbftsm finalized failed: %v", err) - } - case types.ViewMessage: - param := pbft.ViewParam{ - From: req.Address, - ID: msg.GetID(), - Leader: msg.GetLeader(), - } - - err := h.pbftsm.Accept(pbft.NewView(param, msg.GetSignature())) - if err != nil { - h.logger.Warn().Err(err).Msg("view message refused") - } - default: - return nil, xerrors.Errorf("unsupported message of type '%T'", req.Message) - } - - return nil, nil -} - -func (h *processor) getCurrentRoster() (authority.Authority, error) { - return h.readRoster(h.tree.Get()) -} - -func (h *processor) readRoster(tree hashtree.Tree) (authority.Authority, error) { - data, err := tree.Get(keyRoster[:]) - if err != nil { - return nil, xerrors.Errorf("read from tree: %v", err) - } - - roster, err := h.rosterFac.AuthorityOf(h.context, data) - if err != nil { - return nil, xerrors.Errorf("decode failed: %v", err) - } - - return roster, nil -} - -func (h *processor) storeGenesis(roster authority.Authority, match *types.Digest) error { - value, err := roster.Serialize(h.context) - if err != nil { - return xerrors.Errorf("failed to serialize roster: %v", err) - } - - stageTree, err := h.tree.Get().Stage(func(snap store.Snapshot) error { - err := h.makeAccess(snap, roster) - if err != nil { - return xerrors.Errorf("failed to set access: %v", err) - } - - err = snap.Set(keyRoster[:], value) - if err != nil { - return xerrors.Errorf("failed to store roster: %v", err) - } - - return nil - }) - if err != nil { - return xerrors.Errorf("while updating tree: %v", err) - } - - root := types.Digest{} - copy(root[:], stageTree.GetRoot()) - - if match != nil && *match != root { - return xerrors.Errorf("mismatch tree root '%v' != '%v'", match, root) - } - - genesis, err := types.NewGenesis(roster, types.WithGenesisRoot(root)) - if err != nil { - return xerrors.Errorf("creating genesis: %v", err) - } - - err = stageTree.Commit() - if err != nil { - return xerrors.Errorf("tree commit failed: %v", err) - } - - h.tree.Set(stageTree) - - err = h.genesis.Set(genesis) - if err != nil { - return xerrors.Errorf("set genesis failed: %v", err) - } - - close(h.started) - - return nil -} - -func (h *processor) makeAccess(store store.Snapshot, roster authority.Authority) error { - creds := viewchange.NewCreds(keyAccess[:]) - - iter := roster.PublicKeyIterator() - for iter.HasNext() { - // Grant each member of the roster an access to change the roster. - err := h.access.Grant(store, creds, iter.GetNext()) - if err != nil { - return err - } - } - - return nil -} diff --git a/dela/core/ordering/cosipbft/proc_test.go b/dela/core/ordering/cosipbft/proc_test.go deleted file mode 100644 index df4b3c3..0000000 --- a/dela/core/ordering/cosipbft/proc_test.go +++ /dev/null @@ -1,343 +0,0 @@ -package cosipbft - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" - "go.dedis.ch/dela/core/ordering/cosipbft/blocksync" - "go.dedis.ch/dela/core/ordering/cosipbft/pbft" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde/json" -) - -func TestProcessor_BlockMessage_Invoke(t *testing.T) { - expected := types.Digest{1} - - proc := newProcessor() - proc.rosterFac = authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - proc.sync = fakeSync{latest: 1} - proc.blocks = fakeStore{} - proc.pbftsm = fakeSM{ - state: pbft.InitialState, - id: expected, - } - - msg := types.NewBlockMessage(types.Block{}, nil) - - id, err := proc.Invoke(fake.NewAddress(0), msg) - require.NoError(t, err) - require.Equal(t, expected[:], id) - - proc.pbftsm = fakeSM{state: pbft.InitialState, err: fake.GetError()} - _, err = proc.Invoke(fake.NewAddress(0), msg) - require.EqualError(t, err, fake.Err("pbft prepare failed")) - - views := map[mino.Address]types.ViewMessage{fake.NewAddress(0): {}} - msg = types.NewBlockMessage(types.Block{}, views) - proc.pbftsm = fakeSM{err: fake.GetError()} - _, err = proc.Invoke(fake.NewAddress(0), msg) - require.EqualError(t, err, fake.Err("accept all")) -} - -func TestProcessor_CommitMessage_Invoke(t *testing.T) { - proc := newProcessor() - proc.pbftsm = fakeSM{} - - msg := types.NewCommit(types.Digest{1}, fake.Signature{}) - - id, err := proc.Invoke(fake.NewAddress(0), msg) - require.NoError(t, err) - require.Equal(t, []byte{0xfe}, id) - - proc.pbftsm = fakeSM{err: fake.GetError()} - _, err = proc.Invoke(fake.NewAddress(0), msg) - require.EqualError(t, err, fake.Err("pbft commit failed")) - - proc.pbftsm = fakeSM{} - msg = types.NewCommit(types.Digest{}, fake.NewBadSignature()) - _, err = proc.Invoke(fake.NewAddress(0), msg) - require.EqualError(t, err, fake.Err("couldn't marshal signature")) - - _, err = proc.Invoke(fake.NewAddress(0), fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") -} - -func TestProcessor_GenesisMessage_Process(t *testing.T) { - proc := newProcessor() - proc.tree = blockstore.NewTreeCache(fakeTree{}) - proc.genesis = blockstore.NewGenesisStore() - proc.access = fakeAccess{} - - root := types.Digest{} - copy(root[:], []byte("root")) - - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := types.NewGenesis(ro, types.WithGenesisRoot(root)) - require.NoError(t, err) - - req := mino.Request{ - Message: types.NewGenesisMessage(genesis), - } - - msg, err := proc.Process(req) - require.NoError(t, err) - require.Nil(t, msg) - - proc.genesis = blockstore.NewGenesisStore() - proc.context = fake.NewContext() - _, err = proc.Process(req) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to serialize roster: couldn't encode roster: ") - - wrongGenesis, err := types.NewGenesis(ro) - require.NoError(t, err) - - proc.context = json.NewContext() - _, err = proc.Process(mino.Request{Message: types.NewGenesisMessage(wrongGenesis)}) - require.EqualError(t, err, "mismatch tree root '00000000' != '726f6f74'") - - proc.access = fakeAccess{err: fake.GetError()} - _, err = proc.Process(req) - require.EqualError(t, err, fake.Err("while updating tree: failed to set access")) - - proc.access = fakeAccess{} - proc.tree = blockstore.NewTreeCache(fakeTree{errStore: fake.GetError()}) - _, err = proc.Process(req) - require.EqualError(t, err, fake.Err("while updating tree: failed to store roster")) - - proc.tree = blockstore.NewTreeCache(fakeTree{errCommit: fake.GetError()}) - _, err = proc.Process(req) - require.EqualError(t, err, fake.Err("tree commit failed")) - - proc.tree = blockstore.NewTreeCache(fakeTree{}) - proc.genesis = fakeGenesisStore{errSet: fake.GetError()} - _, err = proc.Process(req) - require.EqualError(t, err, fake.Err("set genesis failed")) -} - -func TestProcessor_DoneMessage_Process(t *testing.T) { - proc := newProcessor() - proc.pbftsm = fakeSM{} - proc.blocks = blockstore.NewInMemory() - proc.blocks.Store(makeBlock(t, types.Digest{})) - - req := mino.Request{ - Message: types.NewDone(types.Digest{}, fake.Signature{}), - } - - resp, err := proc.Process(req) - require.NoError(t, err) - require.Nil(t, resp) - - proc.pbftsm = fakeSM{err: fake.GetError()} - _, err = proc.Process(req) - require.EqualError(t, err, fake.Err("pbftsm finalized failed")) -} - -func TestProcessor_ViewMessage_Process(t *testing.T) { - proc := newProcessor() - proc.pbftsm = fakeSM{} - - req := mino.Request{ - Message: types.NewViewMessage(types.Digest{}, 0, fake.Signature{}), - } - - resp, err := proc.Process(req) - require.NoError(t, err) - require.Nil(t, resp) - - proc.pbftsm = fakeSM{err: fake.GetError()} - _, err = proc.Process(req) - require.NoError(t, err) -} - -func TestProcessor_Unsupported_Process(t *testing.T) { - proc := newProcessor() - - req := mino.Request{Message: fake.Message{}} - - _, err := proc.Process(req) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeBlock(t *testing.T, from types.Digest, opts ...types.LinkOption) types.BlockLink { - block, err := types.NewBlock(simple.NewResult(nil)) - require.NoError(t, err) - - link, err := types.NewBlockLink(from, block, opts...) - require.NoError(t, err) - - return link -} - -type fakeSM struct { - pbft.StateMachine - - err error - errLeader error - state pbft.State - id types.Digest - ch chan pbft.State -} - -func (sm fakeSM) GetState() pbft.State { - return sm.state -} - -func (sm fakeSM) GetLeader() (mino.Address, error) { - return fake.NewAddress(0), sm.errLeader -} - -func (sm fakeSM) GetViews() map[mino.Address]pbft.View { - return nil -} - -func (sm fakeSM) PrePrepare(authority.Authority) error { - return sm.err -} - -func (sm fakeSM) Prepare(mino.Address, types.Block) (types.Digest, error) { - return sm.id, sm.err -} - -func (sm fakeSM) Commit(types.Digest, crypto.Signature) error { - return sm.err -} - -func (sm fakeSM) Finalize(types.Digest, crypto.Signature) error { - return sm.err -} - -func (sm fakeSM) Expire(mino.Address) (pbft.View, error) { - return pbft.View{}, sm.err -} - -func (sm fakeSM) Accept(pbft.View) error { - return sm.err -} - -func (sm fakeSM) AcceptAll([]pbft.View) error { - return sm.err -} - -func (sm fakeSM) Watch(context.Context) <-chan pbft.State { - return sm.ch -} - -type fakeSync struct { - blocksync.Synchronizer - - latest uint64 - err error -} - -func (sync fakeSync) GetLatest() uint64 { - return sync.latest -} - -func (sync fakeSync) Sync(ctx context.Context, players mino.Players, cfg blocksync.Config) error { - return sync.err -} - -type fakeSnapshot struct { - store.Snapshot - - err error -} - -func (snap fakeSnapshot) Get(key []byte) ([]byte, error) { - return []byte{}, snap.err -} - -func (snap fakeSnapshot) Set(key []byte, value []byte) error { - return snap.err -} - -func (snap fakeSnapshot) Delete(key []byte) error { - return snap.err -} - -type fakeTree struct { - hashtree.StagingTree - - err error - errStage error - errCommit error - errStore error -} - -func (t fakeTree) GetRoot() []byte { - return []byte("root") -} - -func (t fakeTree) GetPath(key []byte) (hashtree.Path, error) { - return nil, t.err -} - -func (t fakeTree) Get(key []byte) ([]byte, error) { - return []byte("[]"), t.err -} - -func (t fakeTree) Stage(fn func(store.Snapshot) error) (hashtree.StagingTree, error) { - err := fn(fakeSnapshot{err: t.errStore}) - if err != nil { - return nil, err - } - - return t, t.errStage -} - -func (t fakeTree) Commit() error { - return t.errCommit -} - -type fakeGenesisStore struct { - blockstore.GenesisStore - - errGet error - errSet error -} - -func (s fakeGenesisStore) Exists() bool { - return false -} - -func (s fakeGenesisStore) Get() (types.Genesis, error) { - return types.Genesis{}, s.errGet -} - -func (s fakeGenesisStore) Set(types.Genesis) error { - return s.errSet -} - -type fakeStore struct { - blockstore.BlockStore -} - -func (fakeStore) Len() uint64 { - return 0 -} - -func (fakeStore) Watch(context.Context) <-chan types.BlockLink { - ch := make(chan types.BlockLink, 1) - - block, _ := types.NewBlock(simple.NewResult(nil), types.WithIndex(1)) - link, _ := types.NewBlockLink(types.Digest{}, block) - ch <- link - close(ch) - - return ch -} diff --git a/dela/core/ordering/cosipbft/proof.go b/dela/core/ordering/cosipbft/proof.go deleted file mode 100644 index 41ef6ed..0000000 --- a/dela/core/ordering/cosipbft/proof.go +++ /dev/null @@ -1,66 +0,0 @@ -// This file contains the implementation of a proof for this ordering service. -// -// Documentation Last Review: 12.10.2020 -// - -package cosipbft - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -// Proof is a combination of elements that will prove the inclusion or the -// absence of a key/value pair in the given block. -// -// - implements ordering.Proof -type Proof struct { - path hashtree.Path - chain types.Chain -} - -func newProof(path hashtree.Path, chain types.Chain) Proof { - return Proof{ - path: path, - chain: chain, - } -} - -// GetKey implements ordering.Proof. It returns the key associated to the proof. -func (p Proof) GetKey() []byte { - return p.path.GetKey() -} - -// GetValue implements ordering.Proof. It returns the value associated to the -// proof if the key exists, otherwise it returns nil. -func (p Proof) GetValue() []byte { - return p.path.GetValue() -} - -// Verify takes the genesis block and the verifier factory to verify the chain -// up to the latest block. It verifies the whole chain. -func (p Proof) Verify(genesis types.Genesis, fac crypto.VerifierFactory) error { - err := p.chain.Verify(genesis, genesis.GetHash(), fac) - if err != nil { - return xerrors.Errorf("failed to verify chain: %v", err) - } - - last := p.chain.GetBlock() - - // The path object is transmitted with enough information so that when it is - // instanciated, it can calculate the Merkle root. It is therefore - // unnecessary to do it again here. - root := types.Digest{} - copy(root[:], p.path.GetRoot()) - - // The Merkle root must match the one stored in the block to prove that the - // chain is correct. - if last.GetTreeRoot() != root { - return xerrors.Errorf("mismatch tree root: '%v' != '%v'", - last.GetTreeRoot(), root) - } - - return nil -} diff --git a/dela/core/ordering/cosipbft/proof_test.go b/dela/core/ordering/cosipbft/proof_test.go deleted file mode 100644 index 6bd3cea..0000000 --- a/dela/core/ordering/cosipbft/proof_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package cosipbft - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/ordering/cosipbft/types" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestProof_GetKey(t *testing.T) { - p := Proof{ - path: fakePath{}, - } - - require.Equal(t, []byte("key"), p.GetKey()) -} - -func TestProof_GetValue(t *testing.T) { - p := Proof{ - path: fakePath{}, - } - - require.Equal(t, []byte("value"), p.GetValue()) -} - -func TestProof_Verify(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := types.NewGenesis(ro) - require.NoError(t, err) - - block, err := types.NewBlock(simple.NewResult(nil)) - require.NoError(t, err) - - p := Proof{ - path: fakePath{}, - chain: fakeChain{block: block}, - } - - err = p.Verify(genesis, fake.VerifierFactory{}) - require.EqualError(t, err, "mismatch tree root: '00000000' != '01020300'") - - p.chain = fakeChain{err: fake.GetError()} - err = p.Verify(genesis, fake.VerifierFactory{}) - require.EqualError(t, err, fake.Err("failed to verify chain")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakePath struct { - hashtree.Path -} - -func (p fakePath) GetKey() []byte { - return []byte("key") -} - -func (p fakePath) GetValue() []byte { - return []byte("value") -} - -func (p fakePath) GetRoot() []byte { - return types.Digest{1, 2, 3}.Bytes() -} - -type fakeChain struct { - types.Chain - - block types.Block - err error -} - -func (c fakeChain) GetBlock() types.Block { - return c.block -} - -func (c fakeChain) Verify(types.Genesis, types.Digest, crypto.VerifierFactory) error { - return c.err -} diff --git a/dela/core/ordering/cosipbft/types/block.go b/dela/core/ordering/cosipbft/types/block.go deleted file mode 100644 index d7a5912..0000000 --- a/dela/core/ordering/cosipbft/types/block.go +++ /dev/null @@ -1,351 +0,0 @@ -// This file implements the block and the link that will form a chain. -// -// Documentation Last review: 13.10.2020 -// - -package types - -import ( - "encoding/binary" - "fmt" - "io" - - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var ( - genesisFormats = registry.NewSimpleRegistry() - blockFormats = registry.NewSimpleRegistry() -) - -// RegisterGenesisFormat registers the engine for the provided format. -func RegisterGenesisFormat(f serde.Format, e serde.FormatEngine) { - genesisFormats.Register(f, e) -} - -// RegisterBlockFormat registers the engine for the provided format. -func RegisterBlockFormat(f serde.Format, e serde.FormatEngine) { - blockFormats.Register(f, e) -} - -// Digest defines the result of a fingerprint. It expects a digest of 256 bits. -// -// - implements fmt.Stringer -type Digest [32]byte - -// String implements fmt.Stringer. It returns a short representation of the -// digest. -func (d Digest) String() string { - return fmt.Sprintf("%x", d[:])[:8] -} - -// Bytes return the bytes of the digest. -func (d Digest) Bytes() []byte { - return d[:] -} - -// Genesis is the very first block of a chain. It contains the initial roster -// and tree root. -// -// - implements serde.Message -type Genesis struct { - digest Digest - roster authority.Authority - treeRoot Digest -} - -type genesisTemplate struct { - Genesis - hashFactory crypto.HashFactory -} - -// GenesisOption is the option type to set some fields of a genesis block. -type GenesisOption func(*genesisTemplate) - -// WithGenesisRoot is an option to set the tree root of the genesis block. -func WithGenesisRoot(root Digest) GenesisOption { - return func(tmpl *genesisTemplate) { - tmpl.treeRoot = root - } -} - -// WithGenesisHashFactory is an option to set the hash factory. -func WithGenesisHashFactory(fac crypto.HashFactory) GenesisOption { - return func(tmpl *genesisTemplate) { - tmpl.hashFactory = fac - } -} - -// NewGenesis creates a new genesis block with the provided roster. -func NewGenesis(ro authority.Authority, opts ...GenesisOption) (Genesis, error) { - tmpl := genesisTemplate{ - Genesis: Genesis{ - roster: ro, - treeRoot: Digest{}, - }, - hashFactory: crypto.NewSha256Factory(), - } - - for _, opt := range opts { - opt(&tmpl) - } - - h := tmpl.hashFactory.New() - err := tmpl.Fingerprint(h) - if err != nil { - return tmpl.Genesis, xerrors.Errorf("fingerprint failed: %v", err) - } - - copy(tmpl.digest[:], h.Sum(nil)) - - return tmpl.Genesis, nil -} - -// GetHash returns the digest of the block. -func (g Genesis) GetHash() Digest { - return g.digest -} - -// GetRoster returns the roster of the genesis block. -func (g Genesis) GetRoster() authority.Authority { - return g.roster -} - -// GetRoot returns the tree root. -func (g Genesis) GetRoot() Digest { - return g.treeRoot -} - -// Serialize implements serde.Message. It returns the serialized data for this -// genesis block. -func (g Genesis) Serialize(ctx serde.Context) ([]byte, error) { - format := genesisFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, g) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// Fingerprint implements serde.Fingerprinter. It deterministically writes a -// binary representation of the genesis block into the writer. -func (g Genesis) Fingerprint(w io.Writer) error { - _, err := w.Write(g.treeRoot[:]) - if err != nil { - return xerrors.Errorf("couldn't write root: %v", err) - } - - err = g.roster.Fingerprint(w) - if err != nil { - return xerrors.Errorf("roster fingerprint failed: %v", err) - } - - return nil -} - -// RosterKey is the key of the roster factory. -type RosterKey struct{} - -// GenesisFactory is a factory to deserialize the genesis messages. -// -// - implements serde.Factory -type GenesisFactory struct { - rosterFac authority.Factory -} - -// NewGenesisFactory creates a new genesis factory. -func NewGenesisFactory(rf authority.Factory) GenesisFactory { - return GenesisFactory{ - rosterFac: rf, - } -} - -// Deserialize implements serde.Factory. It populates the genesis block if -// appropriate, otherwise it returns an error. -func (f GenesisFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := genesisFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, RosterKey{}, f.rosterFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding failed: %v", err) - } - - return msg, nil -} - -// Block is a block of a chain. It holds an index which is the height of the -// block from the genesis block, the Merkle tree root and the validation result -// of the transactions. -// -// - implements serde.Message -type Block struct { - digest Digest - index uint64 - data validation.Result - treeRoot Digest -} - -type blockTemplate struct { - Block - hashFactory crypto.HashFactory -} - -// BlockOption is the type of option to set some fields of a block. -type BlockOption func(*blockTemplate) - -// WithIndex is an option to set the index of the block. -func WithIndex(index uint64) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.index = index - } -} - -// WithTreeRoot is an option to set the tree root for the block. -func WithTreeRoot(root Digest) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.treeRoot = root - } -} - -// WithHashFactory is an option to set the hash factory for the block. -func WithHashFactory(fac crypto.HashFactory) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.hashFactory = fac - } -} - -// NewBlock creates a new block. -func NewBlock(data validation.Result, opts ...BlockOption) (Block, error) { - tmpl := blockTemplate{ - Block: Block{ - data: data, - treeRoot: Digest{}, - }, - hashFactory: crypto.NewSha256Factory(), - } - - for _, opt := range opts { - opt(&tmpl) - } - - h := tmpl.hashFactory.New() - err := tmpl.Fingerprint(h) - if err != nil { - return tmpl.Block, xerrors.Errorf("fingerprint failed: %v", err) - } - - copy(tmpl.digest[:], h.Sum(nil)) - - return tmpl.Block, nil -} - -// GetHash returns the digest of the block. -func (b Block) GetHash() Digest { - return b.digest -} - -// GetIndex returns the index of the block. -func (b Block) GetIndex() uint64 { - return b.index -} - -// GetData returns the validated data of the block. -func (b Block) GetData() validation.Result { - return b.data -} - -// GetTransactions is a helper to extract the transactions from the validation -// result. -func (b Block) GetTransactions() []txn.Transaction { - results := b.data.GetTransactionResults() - txs := make([]txn.Transaction, len(results)) - - for i, res := range results { - txs[i] = res.GetTransaction() - } - - return txs -} - -// GetTreeRoot returns the tree root of the block. -func (b Block) GetTreeRoot() Digest { - return b.treeRoot -} - -// Fingerprint implements serde.Fingerprinter. It deterministically writes a -// binary representation of the block into the writer. -func (b Block) Fingerprint(w io.Writer) error { - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer, b.index) - _, err := w.Write(buffer) - if err != nil { - return xerrors.Errorf("couldn't write index: %v", err) - } - - _, err = w.Write(b.treeRoot[:]) - if err != nil { - return xerrors.Errorf("couldn't write root: %v", err) - } - - err = b.data.Fingerprint(w) - if err != nil { - return xerrors.Errorf("data fingerprint failed: %v", err) - } - - return nil -} - -// Serialize implements serde.Message. It returns the serialized data of the -// block. -func (b Block) Serialize(ctx serde.Context) ([]byte, error) { - format := blockFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, b) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// DataKey is the key for the validated data factory. -type DataKey struct{} - -// BlockFactory is a factory to deserialize block messages. -// -// - implements serde.Factory -type BlockFactory struct { - dataFac validation.ResultFactory -} - -// NewBlockFactory creates a new block factory. -func NewBlockFactory(fac validation.ResultFactory) BlockFactory { - return BlockFactory{ - dataFac: fac, - } -} - -// Deserialize implements serde.Factory. It populates the block from the data if -// appropriate, otherwise it returns an error. -func (f BlockFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := blockFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, DataKey{}, f.dataFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding block failed: %v", err) - } - - return msg, nil -} diff --git a/dela/core/ordering/cosipbft/types/block_test.go b/dela/core/ordering/cosipbft/types/block_test.go deleted file mode 100644 index 1bcd6a8..0000000 --- a/dela/core/ordering/cosipbft/types/block_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package types - -import ( - "bytes" - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/internal/testing/fake" -) - -func init() { - RegisterGenesisFormat(fake.GoodFormat, fake.Format{Msg: Genesis{}}) - RegisterGenesisFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterBlockFormat(fake.GoodFormat, fake.Format{Msg: Block{}}) - RegisterBlockFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestDigest_String(t *testing.T) { - digest := Digest{1, 2, 3, 4} - - require.Equal(t, "01020304", digest.String()) -} - -func TestDigest_Bytes(t *testing.T) { - digest := Digest{1, 2, 3, 4} - - require.Equal(t, digest[:], digest.Bytes()) -} - -func TestGenesis_GetHash(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - require.NotEqual(t, Digest{}, genesis.GetHash()) - - id := Digest{1, 2, 3} - genesis.digest = id - require.Equal(t, id, genesis.GetHash()) -} - -func TestGenesis_GetRoster(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - require.Equal(t, 3, genesis.GetRoster().Len()) -} - -func TestGenesis_GetRoot(t *testing.T) { - genesis := Genesis{treeRoot: Digest{5}} - - require.Equal(t, Digest{5}, genesis.GetRoot()) -} - -func TestGenesis_Serialize(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - data, err := genesis.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = genesis.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestGenesis_Fingerprint(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(1, fake.NewSigner)) - - genesis, err := NewGenesis(ro, WithGenesisRoot(Digest{5})) - require.NoError(t, err) - - buffer := new(bytes.Buffer) - err = genesis.Fingerprint(buffer) - require.NoError(t, err) - require.Regexp(t, "^\x05(\x00){35,}PK", buffer.String()) - - _, err = NewGenesis(ro, WithGenesisHashFactory(fake.NewHashFactory(fake.NewBadHash()))) - require.EqualError(t, err, fake.Err("fingerprint failed: couldn't write root")) - - genesis.roster = badRoster{} - err = genesis.Fingerprint(buffer) - require.EqualError(t, err, fake.Err("roster fingerprint failed")) -} - -func TestGenesisFactory_Deserialize(t *testing.T) { - fac := NewGenesisFactory(authority.NewFactory(nil, nil)) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.IsType(t, Genesis{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding failed")) -} - -func TestBlock_GetHash(t *testing.T) { - block, err := NewBlock(simple.NewResult(nil), WithTreeRoot(Digest{2})) - require.NoError(t, err) - require.NotEqual(t, Digest{}, block.GetHash()) -} - -func TestBlock_GetIndex(t *testing.T) { - block, err := NewBlock(simple.NewResult(nil), WithIndex(2)) - require.NoError(t, err) - require.Equal(t, uint64(2), block.GetIndex()) -} - -func TestBlock_GetData(t *testing.T) { - block := Block{data: simple.NewResult(nil)} - - require.Equal(t, simple.NewResult(nil), block.GetData()) -} - -func TestBlock_GetTransactions(t *testing.T) { - block := Block{data: simple.NewResult(nil)} - require.Len(t, block.GetTransactions(), 0) - - block.data = simple.NewResult([]simple.TransactionResult{{}}) - require.Len(t, block.GetTransactions(), 1) -} - -func TestBlock_GetTreeRoot(t *testing.T) { - block := Block{treeRoot: Digest{3}} - - require.Equal(t, Digest{3}, block.GetTreeRoot()) -} - -func TestBlock_Fingerprint(t *testing.T) { - block := Block{ - index: 3, - treeRoot: Digest{4}, - data: simple.NewResult(nil), - } - - buffer := new(bytes.Buffer) - - err := block.Fingerprint(buffer) - require.NoError(t, err) - require.Regexp(t, "^\x03(\x00){7}\x04(\x00){31}$", buffer.String()) - - err = block.Fingerprint(fake.NewBadHash()) - require.EqualError(t, err, fake.Err("couldn't write index")) - - err = block.Fingerprint(fake.NewBadHashWithDelay(1)) - require.EqualError(t, err, fake.Err("couldn't write root")) - - block.data = badData{} - err = block.Fingerprint(io.Discard) - require.EqualError(t, err, fake.Err("data fingerprint failed")) - - _, err = NewBlock(block.data, WithHashFactory(fake.NewHashFactory(fake.NewBadHash()))) - require.EqualError(t, err, fake.Err("fingerprint failed: couldn't write index")) -} - -func TestBlock_Serialize(t *testing.T) { - block, err := NewBlock(simple.NewResult(nil)) - require.NoError(t, err) - - data, err := block.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = block.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestBlockFactory_Deserialize(t *testing.T) { - txFac := signed.NewTransactionFactory() - fac := NewBlockFactory(simple.NewResultFactory(txFac)) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.IsType(t, Block{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding block failed")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badRoster struct { - authority.Authority -} - -func (r badRoster) Fingerprint(io.Writer) error { - return fake.GetError() -} - -type badData struct { - validation.Result -} - -func (d badData) Fingerprint(io.Writer) error { - return fake.GetError() -} diff --git a/dela/core/ordering/cosipbft/types/chain.go b/dela/core/ordering/cosipbft/types/chain.go deleted file mode 100644 index 20b486f..0000000 --- a/dela/core/ordering/cosipbft/types/chain.go +++ /dev/null @@ -1,447 +0,0 @@ -// This file contains the implementation of a chain of block links. -// -// Documentation Last Review: 13.10.2020 -// - -package types - -import ( - "io" - - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var ( - chainFormats = registry.NewSimpleRegistry() - linkFormats = registry.NewSimpleRegistry() -) - -// RegisterLinkFormat registers the engine for the provided format. -func RegisterLinkFormat(f serde.Format, e serde.FormatEngine) { - linkFormats.Register(f, e) -} - -// RegisterChainFormat registers the engine for the provided format. -func RegisterChainFormat(f serde.Format, e serde.FormatEngine) { - chainFormats.Register(f, e) -} - -// ForwardLink is a link between two blocks that is only using their different -// digests to reduce the serialization footprint. -// -// - implements types.Link -// - implements serde.Fingerprinter -type forwardLink struct { - digest Digest - from Digest - to Digest - changeset authority.ChangeSet - prepareSig crypto.Signature - commitSig crypto.Signature -} - -type linkTemplate struct { - forwardLink - - hashFac crypto.HashFactory -} - -// LinkOption is the type of option to set some optional fields of the -// link. -type LinkOption func(*linkTemplate) - -// WithSignatures is the option to set the signatures of the link. -func WithSignatures(prep, commit crypto.Signature) LinkOption { - return func(tmpl *linkTemplate) { - tmpl.prepareSig = prep - tmpl.commitSig = commit - } -} - -// WithChangeSet is the option to set the change set of the roster for this -// link. -func WithChangeSet(cs authority.ChangeSet) LinkOption { - return func(tmpl *linkTemplate) { - tmpl.changeset = cs - } -} - -// WithLinkHashFactory is the option to set the hash factory for the link. -func WithLinkHashFactory(fac crypto.HashFactory) LinkOption { - return func(tmpl *linkTemplate) { - tmpl.hashFac = fac - } -} - -// NewForwardLink creates a new forward link between the two block digests. -func NewForwardLink(from, to Digest, opts ...LinkOption) (Link, error) { - tmpl := linkTemplate{ - forwardLink: forwardLink{ - from: from, - to: to, - changeset: authority.NewChangeSet(), - }, - hashFac: crypto.NewSha256Factory(), - } - - for _, opt := range opts { - opt(&tmpl) - } - - h := tmpl.hashFac.New() - err := tmpl.Fingerprint(h) - if err != nil { - return nil, xerrors.Errorf("failed to fingerprint: %v", err) - } - - copy(tmpl.digest[:], h.Sum(nil)) - - return tmpl.forwardLink, nil -} - -// GetHash implements types.Link. It returns the digest of the link. -func (link forwardLink) GetHash() Digest { - return link.digest -} - -// GetFrom implements types.Link. It returns the digest of the source block. -func (link forwardLink) GetFrom() Digest { - return link.from -} - -// GetTo implements types.Link. It returns the block the link is pointing to. -func (link forwardLink) GetTo() Digest { - return link.to -} - -// GetPrepareSignature implements types.Link. It returns the prepare signature -// if it is set, otherwise it returns nil. -func (link forwardLink) GetPrepareSignature() crypto.Signature { - return link.prepareSig -} - -// GetCommitSignature implements types.Link. It returns the commit signature if -// it is set, otherwise it returns nil. -func (link forwardLink) GetCommitSignature() crypto.Signature { - return link.commitSig -} - -// GetChangeSet implements types.Link. It returns the change set of the roster -// for this link. -func (link forwardLink) GetChangeSet() authority.ChangeSet { - return link.changeset -} - -// Fingerprint implements serde.Fingerprinter. It deterministically writes a -// binary representation of the block link. -func (link forwardLink) Fingerprint(w io.Writer) error { - _, err := w.Write(link.from[:]) - if err != nil { - return xerrors.Errorf("couldn't write from: %v", err) - } - - id := link.GetTo() - - _, err = w.Write(id[:]) - if err != nil { - return xerrors.Errorf("couldn't write to: %v", err) - } - - return nil -} - -// Serialize implements serde.Message. It returns the data of the serialized -// forward link. -func (link forwardLink) Serialize(ctx serde.Context) ([]byte, error) { - format := linkFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, link) - if err != nil { - return nil, xerrors.Errorf("encoding link failed: %v", err) - } - - return data, nil -} - -// BlockLink is a link between two blocks but only keep the previous block -// digest. -// -// - implements types.BlockLink -type blockLink struct { - forwardLink - - block Block -} - -// NewBlockLink creates a new block link between from and to. -func NewBlockLink(from Digest, to Block, opts ...LinkOption) (BlockLink, error) { - link, err := NewForwardLink(from, to.digest, opts...) - if err != nil { - return nil, xerrors.Errorf("creating forward link: %v", err) - } - - bl := blockLink{ - forwardLink: link.(forwardLink), - block: to, - } - - return bl, nil -} - -// GetBlock implements types.BlockLink. It returns the block that the link is -// pointing at. -func (link blockLink) GetBlock() Block { - return link.block -} - -// Reduce implements types.BlockLink. It reduces the block link to its -// minimalistic shape. -func (link blockLink) Reduce() Link { - return link.forwardLink -} - -// Serialize implements serde.Message. It returns the serialized data for this -// block link. -func (link blockLink) Serialize(ctx serde.Context) ([]byte, error) { - format := linkFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, link) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// ChangeSetKey is the key of the change set factory. -type ChangeSetKey struct{} - -// BlockLinkFac is the factory to deserialize block link messages. -// -// - implements types.LinkFactory -type linkFac struct { - blockFac serde.Factory - sigFac crypto.SignatureFactory - csFac authority.ChangeSetFactory -} - -// NewLinkFactory creates a new block link factory. -func NewLinkFactory(blockFac serde.Factory, - sigFac crypto.SignatureFactory, csFac authority.ChangeSetFactory) LinkFactory { - - return linkFac{ - blockFac: blockFac, - sigFac: sigFac, - csFac: csFac, - } -} - -// Deserialize implements serde.Factory. It populates the block link if -// appropriate, otherwise it returns an error. -func (fac linkFac) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := linkFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, BlockKey{}, fac.blockFac) - ctx = serde.WithFactory(ctx, AggregateKey{}, fac.sigFac) - ctx = serde.WithFactory(ctx, ChangeSetKey{}, fac.csFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding link failed: %v", err) - } - - return msg, nil -} - -// LinkOf implements types.LinkFactory. It populates the link if appropriate, -// otherwise it returns an error. -func (fac linkFac) LinkOf(ctx serde.Context, data []byte) (Link, error) { - msg, err := fac.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - link, ok := msg.(Link) - if !ok { - return nil, xerrors.Errorf("invalid forward link '%T'", msg) - } - - return link, nil -} - -// BlockLinkOf implements types.LinkFactory. It populates the block link if -// appropriate, otherwise it returns an error. -func (fac linkFac) BlockLinkOf(ctx serde.Context, data []byte) (BlockLink, error) { - msg, err := fac.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - link, ok := msg.(BlockLink) - if !ok { - return nil, xerrors.Errorf("invalid block link '%T'", msg) - } - - return link, nil -} - -// Chain is a combination of ordered links that will define a proof of existence -// for a block. It does not include the genesis block which is assumed to be -// known beforehands. -// -// - implements types.Chain -type chain struct { - last BlockLink - prevs []Link -} - -// NewChain creates a new chain from the block link and the previous forward -// links. -func NewChain(last BlockLink, prevs []Link) Chain { - return chain{ - last: last, - prevs: prevs, - } -} - -// GetLinks implements types.Chain. It returns all the links of the chain in -// order. -func (c chain) GetLinks() []Link { - return append(append([]Link{}, c.prevs...), c.last) -} - -// GetBlock implements types.Chain. It returns the block the chain is pointing -// at. -func (c chain) GetBlock() Block { - return c.last.GetBlock() -} - -// Verify implements types.Chain. It verifies the integrity of the chain using -// the genesis block and the verifier factory. It starts the verification at the -// link whose previous block equals the given Digest. If the genesis hash is -// provided, the whole chain is going to be validated. -func (c chain) Verify(genesis Genesis, from Digest, fac crypto.VerifierFactory) error { - authority := genesis.GetRoster() - - prev := genesis.GetHash() - - toProcess := false - - for _, link := range c.GetLinks() { - // Skip the verification until we reach the provided Digest. We still - // have to update the roster though. - if !toProcess && link.GetFrom() != from { - prev = link.GetTo() - authority = authority.Apply(link.GetChangeSet()) - continue - } - - toProcess = true - - // It makes sure that the chain of links is consistent. - if prev != link.GetFrom() { - return xerrors.Errorf("mismatch from: '%v' != '%v'", link.GetFrom(), prev) - } - - // The verifier can be used to verify the signature of the link, but it - // needs to be created for every link as the roster can change. - verifier, err := fac.FromAuthority(authority) - if err != nil { - return xerrors.Errorf("verifier factory failed: %v", err) - } - - if link.GetPrepareSignature() == nil { - return xerrors.New("unexpected nil prepare signature in link") - } - - if link.GetCommitSignature() == nil { - return xerrors.New("unexpected nil commit signature in link") - } - - // 1. Verify the prepare signature that signs the integrity of the - // forward link. - err = verifier.Verify(link.GetHash().Bytes(), link.GetPrepareSignature()) - if err != nil { - return xerrors.Errorf("invalid prepare signature: %v", err) - } - - // 2. Verify the commit signature that signs the binary representation - // of the prepare signature. - msg, err := link.GetPrepareSignature().MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal signature: %v", err) - } - - err = verifier.Verify(msg, link.GetCommitSignature()) - if err != nil { - return xerrors.Errorf("invalid commit signature: %v", err) - } - - prev = link.GetTo() - - authority = authority.Apply(link.GetChangeSet()) - } - - if !toProcess { - return xerrors.Errorf("no verification made (from Digest %v)", from) - } - - return nil -} - -// Serialize implements serde.Message. It returns the data of the serialized -// chain. -func (c chain) Serialize(ctx serde.Context) ([]byte, error) { - format := chainFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, c) - if err != nil { - return nil, xerrors.Errorf("encoding chain failed: %v", err) - } - - return data, nil -} - -// ChainFactory is a factory to serialize and deserialize a chain. -// -// - implements types.ChainFactory -type chainFactory struct { - linkFac LinkFactory -} - -// NewChainFactory creates a new factory from the link factory. -func NewChainFactory(fac LinkFactory) ChainFactory { - return chainFactory{ - linkFac: fac, - } -} - -// Deserialize implements serde.Factory. It returns the chain from the data if -// appropriate, otherwise it returns an error. -func (fac chainFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return fac.ChainOf(ctx, data) -} - -// ChainOf implements types.ChainFactory. It returns the chain from the data if -// appropriate, otherwise it returns an error. -func (fac chainFactory) ChainOf(ctx serde.Context, data []byte) (Chain, error) { - format := chainFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, LinkKey{}, fac.linkFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding chain failed: %v", err) - } - - chain, ok := msg.(Chain) - if !ok { - return nil, xerrors.Errorf("invalid chain '%T'", msg) - } - - return chain, nil -} diff --git a/dela/core/ordering/cosipbft/types/chain_test.go b/dela/core/ordering/cosipbft/types/chain_test.go deleted file mode 100644 index 93d6139..0000000 --- a/dela/core/ordering/cosipbft/types/chain_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package types - -import ( - "bytes" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterLinkFormat(fake.GoodFormat, fake.Format{Msg: blockLink{}}) - RegisterLinkFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterLinkFormat(serde.Format("badtype"), fake.Format{Msg: fake.Message{}}) - RegisterChainFormat(fake.GoodFormat, fake.Format{Msg: chain{}}) - RegisterChainFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterChainFormat(serde.Format("badtype"), fake.Format{Msg: fake.Message{}}) -} - -func TestForwardLink_New(t *testing.T) { - link, err := NewForwardLink(Digest{1}, Digest{2}) - require.NoError(t, err) - require.Equal(t, Digest{1}, link.GetFrom()) - - opts := []LinkOption{ - WithSignatures(fake.Signature{}, fake.Signature{}), - WithChangeSet(authority.NewChangeSet()), - } - - link, err = NewForwardLink(Digest{1}, Digest{2}, opts...) - require.NoError(t, err) - require.Equal(t, fake.Signature{}, link.GetPrepareSignature()) - require.Equal(t, fake.Signature{}, link.GetCommitSignature()) - - opts = []LinkOption{ - WithLinkHashFactory(fake.NewHashFactory(fake.NewBadHash())), - } - - _, err = NewForwardLink(Digest{1}, Digest{2}, opts...) - require.EqualError(t, err, fake.Err("failed to fingerprint: couldn't write from")) -} - -func TestForwardLink_GetHash(t *testing.T) { - link := forwardLink{digest: Digest{1}} - - require.Equal(t, Digest{1}, link.GetHash()) -} - -func TestForwardLink_GetFrom(t *testing.T) { - link := forwardLink{from: Digest{2}} - - require.Equal(t, Digest{2}, link.GetFrom()) -} - -func TestForwardLink_GetTo(t *testing.T) { - link := forwardLink{to: Digest{3}} - - require.Equal(t, Digest{3}, link.GetTo()) -} - -func TestForwardLink_GetPrepareSignature(t *testing.T) { - link := forwardLink{prepareSig: fake.Signature{}} - - require.NotNil(t, link.GetPrepareSignature()) - require.Nil(t, link.GetCommitSignature()) -} - -func TestForwardLink_GetCommitSignature(t *testing.T) { - link := forwardLink{commitSig: fake.Signature{}} - - require.NotNil(t, link.GetCommitSignature()) - require.Nil(t, link.GetPrepareSignature()) -} - -func TestForwardLink_GetChangeSet(t *testing.T) { - link := forwardLink{ - changeset: authority.NewChangeSet(), - } - - require.Equal(t, authority.NewChangeSet(), link.GetChangeSet()) -} - -func TestForwardLink_Serialize(t *testing.T) { - link := forwardLink{} - - data, err := link.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = link.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding link failed")) -} - -func TestForwardLink_Fingerprint(t *testing.T) { - link, err := NewForwardLink(Digest{1}, Digest{2}) - require.NoError(t, err) - - buffer := new(bytes.Buffer) - - err = link.Fingerprint(buffer) - require.NoError(t, err) - require.Regexp(t, "^\x01\x00{31}\x02\x00{31}$", buffer.String()) - - err = link.Fingerprint(fake.NewBadHash()) - require.EqualError(t, err, fake.Err("couldn't write from")) - - err = link.Fingerprint(fake.NewBadHashWithDelay(1)) - require.EqualError(t, err, fake.Err("couldn't write to")) -} - -func TestBlockLink_New(t *testing.T) { - link, err := NewBlockLink(Digest{}, Block{}) - require.NoError(t, err) - require.Equal(t, Digest{}, link.GetFrom()) - - opt := WithLinkHashFactory(fake.NewHashFactory(fake.NewBadHash())) - - _, err = NewBlockLink(Digest{}, Block{}, opt) - require.EqualError(t, err, - fake.Err("creating forward link: failed to fingerprint: couldn't write from")) -} - -func TestBlockLink_GetBlock(t *testing.T) { - link := blockLink{ - block: Block{index: 1}, - } - - require.Equal(t, uint64(1), link.GetBlock().GetIndex()) -} - -func TestBlockLink_Reduce(t *testing.T) { - link := blockLink{ - forwardLink: forwardLink{ - from: Digest{1}, - to: Digest{2}, - prepareSig: fake.Signature{}, - commitSig: fake.Signature{}, - }, - } - - require.Equal(t, link.forwardLink, link.Reduce()) -} - -func TestBlockLink_Serialize(t *testing.T) { - link := blockLink{} - - data, err := link.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = link.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestLinkFac_LinkOf(t *testing.T) { - csFac := authority.NewChangeSetFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - fac := NewLinkFactory(BlockFactory{}, fake.SignatureFactory{}, csFac) - - msg, err := fac.LinkOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, blockLink{}, msg) - - _, err = fac.LinkOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding link failed")) - - _, err = fac.LinkOf(fake.NewContextWithFormat(serde.Format("badtype")), nil) - require.EqualError(t, err, "invalid forward link 'fake.Message'") -} - -func TestLinkFac_BlockLinkOf(t *testing.T) { - csFac := authority.NewChangeSetFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - fac := NewLinkFactory(BlockFactory{}, fake.SignatureFactory{}, csFac) - - msg, err := fac.BlockLinkOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, blockLink{}, msg) - - _, err = fac.BlockLinkOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding link failed")) - - _, err = fac.BlockLinkOf(fake.NewContextWithFormat(serde.Format("badtype")), nil) - require.EqualError(t, err, "invalid block link 'fake.Message'") -} - -func TestChain_GetLinks(t *testing.T) { - chain := NewChain(blockLink{}, []Link{forwardLink{}, forwardLink{}}) - - require.Len(t, chain.GetLinks(), 3) -} - -func TestChain_GetBlock(t *testing.T) { - chain := NewChain(blockLink{block: Block{index: 2}}, nil) - - require.Equal(t, uint64(2), chain.GetBlock().GetIndex()) -} - -func TestChain_Verify(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - c := NewChain(makeLink(t, genesis.digest, Digest{}), nil) - - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.NoError(t, err) - - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.NoError(t, err) - - l := makeLink(t, Digest{}, Digest{}) - c = NewChain(l, nil) - err = c.Verify(genesis, l.GetFrom(), fake.VerifierFactory{}) - require.EqualError(t, err, fmt.Sprintf("mismatch from: '00000000' != '%v'", genesis.GetHash())) - - c = NewChain(makeLink(t, genesis.digest, Digest{}), nil) - err = c.Verify(genesis, genesis.GetHash(), fake.NewBadVerifierFactory()) - require.EqualError(t, err, fake.Err("verifier factory failed")) - - err = c.Verify(genesis, genesis.GetHash(), fake.NewVerifierFactory(fake.NewBadVerifier())) - require.EqualError(t, err, fake.Err("invalid prepare signature")) - - link := makeLink(t, genesis.digest, Digest{}).(blockLink) - link.prepareSig = nil - c = NewChain(link, nil) - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.EqualError(t, err, "unexpected nil prepare signature in link") - - link.prepareSig = fake.Signature{} - link.commitSig = nil - c = NewChain(link, nil) - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.EqualError(t, err, "unexpected nil commit signature in link") - - link.prepareSig = fake.NewBadSignature() - link.commitSig = fake.Signature{} - c = NewChain(link, nil) - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.EqualError(t, err, fake.Err("failed to marshal signature")) - - c = NewChain(makeLink(t, genesis.digest, Digest{}), nil) - err = c.Verify(genesis, genesis.GetHash(), fake.NewVerifierFactory(fake.NewBadVerifierWithDelay(1))) - require.EqualError(t, err, fake.Err("invalid commit signature")) -} - -func TestChain_Verify_Skip(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - link1 := makeLink(t, genesis.digest, digest(0x1)) - link2 := makeLink(t, digest(0x1), digest(0x2)) - link3 := makeLink(t, digest(0x2), digest(0x3)) - - c := NewChain(link3, []Link{link1, link2}) - - // Check the whole chain - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.NoError(t, err) - - // Check from link2 - err = c.Verify(genesis, digest(0x2), fake.VerifierFactory{}) - require.NoError(t, err) -} - -func TestChain_Verify_Skip_Invalid(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - link1 := makeLink(t, genesis.digest, digest(0x10)) - link2 := makeLink(t, digest(0x1), digest(0x2)) - link3 := makeLink(t, digest(0x2), digest(0x3)) - - c := NewChain(link3, []Link{link1, link2}) - - // Check the whole chain, should be an error with invalid link 1 - err = c.Verify(genesis, genesis.GetHash(), fake.VerifierFactory{}) - require.Error(t, err) - - // Check from link3, no error since we skip - err = c.Verify(genesis, digest(0x2), fake.VerifierFactory{}) - require.NoError(t, err) -} - -func TestChain_Verify_Skip_NoCheck(t *testing.T) { - ro := authority.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - - genesis, err := NewGenesis(ro) - require.NoError(t, err) - - link1 := makeLink(t, genesis.digest, digest(0x10)) - link2 := makeLink(t, digest(0x1), digest(0x2)) - link3 := makeLink(t, digest(0x2), digest(0x3)) - - c := NewChain(link3, []Link{link1, link2}) - - // Check with an inexistent digest - err = c.Verify(genesis, digest(0x33), fake.VerifierFactory{}) - require.EqualError(t, err, "no verification made (from Digest 33000000)") -} - -func TestChain_Serialize(t *testing.T) { - chain := chain{} - - data, err := chain.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = chain.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding chain failed")) -} - -func TestChainFactory_Deserialize(t *testing.T) { - fac := NewChainFactory(linkFac{}) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, chain{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding chain failed")) - - _, err = fac.Deserialize(fake.NewContextWithFormat(serde.Format("badtype")), nil) - require.EqualError(t, err, "invalid chain 'fake.Message'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeLink(t *testing.T, from, to Digest) BlockLink { - link, err := NewForwardLink(from, to, WithSignatures(fake.Signature{}, fake.Signature{})) - require.NoError(t, err) - - return blockLink{forwardLink: link.(forwardLink)} -} - -func digest(b byte) Digest { - var d Digest - d[0] = b - return d -} diff --git a/dela/core/ordering/cosipbft/types/messages.go b/dela/core/ordering/cosipbft/types/messages.go deleted file mode 100644 index ac8e5d3..0000000 --- a/dela/core/ordering/cosipbft/types/messages.go +++ /dev/null @@ -1,284 +0,0 @@ -// This file contains the implementation of the wrapper messages. -// -// Documentation Last Review: 13.10.2020 -// - -package types - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var msgFormats = registry.NewSimpleRegistry() - -// RegisterMessageFormat registers the engine for the provided format. -func RegisterMessageFormat(f serde.Format, e serde.FormatEngine) { - msgFormats.Register(f, e) -} - -// GenesisMessage is a message to send a genesis to distant participants. -// -// - implements serde.Message -type GenesisMessage struct { - genesis *Genesis -} - -// NewGenesisMessage creates a new genesis message. -func NewGenesisMessage(genesis Genesis) GenesisMessage { - return GenesisMessage{ - genesis: &genesis, - } -} - -// GetGenesis returns the genesis block contained in the message. -func (m GenesisMessage) GetGenesis() *Genesis { - return m.genesis -} - -// Serialize implements serde.Message. It returns the serialized data for this -// message. -func (m GenesisMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// BlockMessage is a message sent to participants to share a block. -// -// - implements serde.Message -type BlockMessage struct { - block Block - views map[mino.Address]ViewMessage -} - -// NewBlockMessage creates a new block message with the provided block. -func NewBlockMessage(block Block, views map[mino.Address]ViewMessage) BlockMessage { - return BlockMessage{ - block: block, - views: views, - } -} - -// GetBlock returns the block of the message. -func (m BlockMessage) GetBlock() Block { - return m.block -} - -// GetViews returns the view messages if any. -func (m BlockMessage) GetViews() map[mino.Address]ViewMessage { - return m.views -} - -// Serialize implements serde.Message. It returns the serialized data of the -// block. -func (m BlockMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// CommitMessage is a message containing the signature of the prepare phase of a -// PBFT execution. -// -// - implements serde.Message -type CommitMessage struct { - id Digest - signature crypto.Signature -} - -// NewCommit creates a new commit message. -func NewCommit(id Digest, sig crypto.Signature) CommitMessage { - return CommitMessage{ - id: id, - signature: sig, - } -} - -// GetID returns the block digest to commit. -func (m CommitMessage) GetID() Digest { - return m.id -} - -// GetSignature returns the prepare signature. -func (m CommitMessage) GetSignature() crypto.Signature { - return m.signature -} - -// Serialize implements serde.Message. It returns the serialized data of the -// commit message. -func (m CommitMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// DoneMessage is a message containing the signature of the commit phase of a -// PBFT execution. -// -// - implements serde.Message -type DoneMessage struct { - id Digest - signature crypto.Signature -} - -// NewDone creates a new done message. -func NewDone(id Digest, sig crypto.Signature) DoneMessage { - return DoneMessage{ - id: id, - signature: sig, - } -} - -// GetID returns the digest of the block that has been accepted. -func (m DoneMessage) GetID() Digest { - return m.id -} - -// GetSignature returns the commit signature that proves the commitment of the -// block. -func (m DoneMessage) GetSignature() crypto.Signature { - return m.signature -} - -// Serialize implements serde.Message. It returns the serialized data of the -// done message. -func (m DoneMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// ViewMessage is a message to announce a view change request. -// -// - implements serde.Message -type ViewMessage struct { - id Digest - leader uint16 - signature crypto.Signature -} - -// NewViewMessage creates a new view message. -func NewViewMessage(id Digest, leader uint16, sig crypto.Signature) ViewMessage { - return ViewMessage{ - id: id, - leader: leader, - signature: sig, - } -} - -// GetID returns the digest of the latest block. -func (m ViewMessage) GetID() Digest { - return m.id -} - -// GetLeader returns the leader index of the view change. -func (m ViewMessage) GetLeader() uint16 { - return m.leader -} - -// GetSignature returns the signature of the view. -func (m ViewMessage) GetSignature() crypto.Signature { - return m.signature -} - -// Serialize implements serde.Message. It returns the serialized data for this -// view message. -func (m ViewMessage) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, m) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// GenesisKey is the key of the genesis factory. -type GenesisKey struct{} - -// BlockKey is the key of the block factory. -type BlockKey struct{} - -// LinkKey is the key of the link factory. -type LinkKey struct{} - -// AggregateKey is the key of the collective signature factory. -type AggregateKey struct{} - -// SignatureKey is the key of the view signature factory. -type SignatureKey struct{} - -// AddressKey is the key of the address factory. -type AddressKey struct{} - -// MessageFactory is the factory to deserialize messages. -// -// - implements serde.Factory -type MessageFactory struct { - genesisFac serde.Factory - blockFac serde.Factory - aggFac crypto.SignatureFactory - sigFac crypto.SignatureFactory - csFac authority.ChangeSetFactory - addrFac mino.AddressFactory -} - -// NewMessageFactory creates a new message factory. -func NewMessageFactory(gf, bf serde.Factory, addrFac mino.AddressFactory, - aggFac crypto.SignatureFactory, csf authority.ChangeSetFactory) MessageFactory { - return MessageFactory{ - genesisFac: gf, - blockFac: bf, - aggFac: aggFac, - sigFac: common.NewSignatureFactory(), - csFac: csf, - addrFac: addrFac, - } -} - -// Deserialize implements serde.Factory. It populates the message if -// appropriate, otherwise it returns an error. -func (f MessageFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := msgFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, GenesisKey{}, f.genesisFac) - ctx = serde.WithFactory(ctx, BlockKey{}, f.blockFac) - ctx = serde.WithFactory(ctx, AggregateKey{}, f.aggFac) - ctx = serde.WithFactory(ctx, SignatureKey{}, f.sigFac) - ctx = serde.WithFactory(ctx, LinkKey{}, NewLinkFactory(f.blockFac, f.aggFac, f.csFac)) - ctx = serde.WithFactory(ctx, AddressKey{}, f.addrFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding failed: %v", err) - } - - return msg, nil -} diff --git a/dela/core/ordering/cosipbft/types/messages_test.go b/dela/core/ordering/cosipbft/types/messages_test.go deleted file mode 100644 index 0661dc7..0000000 --- a/dela/core/ordering/cosipbft/types/messages_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" -) - -func init() { - RegisterMessageFormat(fake.GoodFormat, fake.Format{Msg: GenesisMessage{}}) - RegisterMessageFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestGenesisMessage_GetGenesis(t *testing.T) { - msg := NewGenesisMessage(Genesis{}) - - require.NotNil(t, msg.GetGenesis()) -} - -func TestGenesisMessage_Serialize(t *testing.T) { - msg := NewGenesisMessage(Genesis{}) - - data, err := msg.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = msg.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestBlockMessage_GetBlock(t *testing.T) { - expected := Block{index: 1} - msg := NewBlockMessage(expected, nil) - - block := msg.GetBlock() - require.Equal(t, expected, block) -} - -func TestBlockMessage_GetViews(t *testing.T) { - msg := NewBlockMessage(Block{}, nil) - require.Len(t, msg.GetViews(), 0) - - msg = NewBlockMessage(Block{}, map[mino.Address]ViewMessage{fake.NewAddress(0): {}}) - require.Len(t, msg.GetViews(), 1) -} - -func TestBlockMessage_Serialize(t *testing.T) { - msg := NewBlockMessage(Block{}, nil) - - data, err := msg.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = msg.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestCommitMessage_GetID(t *testing.T) { - msg := NewCommit(Digest{1}, fake.Signature{}) - - require.Equal(t, Digest{1}, msg.GetID()) -} - -func TestCommitMessage_GetSignature(t *testing.T) { - msg := NewCommit(Digest{}, fake.Signature{}) - - require.Equal(t, fake.Signature{}, msg.GetSignature()) -} - -func TestCommitMessage_Serialize(t *testing.T) { - msg := NewCommit(Digest{}, fake.Signature{}) - - data, err := msg.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = msg.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestDoneMessage_GetID(t *testing.T) { - msg := NewDone(Digest{1}, fake.Signature{}) - - require.Equal(t, Digest{1}, msg.GetID()) -} - -func TestDoneMessage_GetSignature(t *testing.T) { - msg := NewDone(Digest{}, fake.Signature{}) - - require.Equal(t, fake.Signature{}, msg.GetSignature()) -} - -func TestDoneMessage_Serialize(t *testing.T) { - msg := NewDone(Digest{}, fake.Signature{}) - - data, err := msg.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = msg.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestViewMessage_GetID(t *testing.T) { - msg := NewViewMessage(Digest{1}, 0, nil) - - require.Equal(t, Digest{1}, msg.GetID()) -} - -func TestViewMessage_GetLeader(t *testing.T) { - msg := NewViewMessage(Digest{}, 2, nil) - - require.Equal(t, uint16(2), msg.GetLeader()) -} - -func TestViewMessage_GetSignature(t *testing.T) { - msg := NewViewMessage(Digest{}, 0, fake.Signature{}) - - require.Equal(t, fake.Signature{}, msg.GetSignature()) -} - -func TestViewMessage_Serialize(t *testing.T) { - msg := NewViewMessage(Digest{}, 3, fake.Signature{}) - - data, err := msg.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = msg.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestMessageFactory_Deserialize(t *testing.T) { - fac := NewMessageFactory( - GenesisFactory{}, - BlockFactory{}, - fake.AddressFactory{}, - fake.SignatureFactory{}, - authority.NewChangeSetFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}), - ) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, GenesisMessage{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding failed")) -} diff --git a/dela/core/ordering/cosipbft/types/types.go b/dela/core/ordering/cosipbft/types/types.go deleted file mode 100644 index f36e8c8..0000000 --- a/dela/core/ordering/cosipbft/types/types.go +++ /dev/null @@ -1,88 +0,0 @@ -// Package types implements the network messages for cosipbft. -// -// The messages are implemented in a different package to prevent cycle imports -// when importing the serde formats. -// -// Documentation Last Review: 13.10.2020 -package types - -import ( - "go.dedis.ch/dela/core/ordering/cosipbft/authority" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" -) - -// Link is the interface of a link between two blocks. -type Link interface { - serde.Message - serde.Fingerprinter - - // GetHash returns the digest of the forward link that is signed with the - // prepare signature. - GetHash() Digest - - // GetFrom returns the digest of the previous block. - GetFrom() Digest - - // GetTo returns the digest of the block the link is pointing at. - GetTo() Digest - - // GetPrepareSignature returns the signature that proves the integrity of - // the link. - GetPrepareSignature() crypto.Signature - - // GetCommitSignature returns the signature that proves the block has been - // committed. - GetCommitSignature() crypto.Signature - - // GetChangeSet returns the roster change set for this link. - GetChangeSet() authority.ChangeSet -} - -// BlockLink is an extension of the Link interface to include the block the link -// is pointing at. It also provides a function to get a lighter link without the -// block. -type BlockLink interface { - Link - - // GetBlock returns the block the link is pointing at. - GetBlock() Block - - // Reduce returns the forward link equivalent to this block link but without - // the block to allow a lighter serialization. - Reduce() Link -} - -// LinkFactory is the interface of the block link factory. -type LinkFactory interface { - serde.Factory - - LinkOf(serde.Context, []byte) (Link, error) - - BlockLinkOf(serde.Context, []byte) (BlockLink, error) -} - -// Chain is the interface to combine several links in order to create a chain -// that can prove the integrity of the blocks from the genesis block. -type Chain interface { - serde.Message - - // GetLinks returns all the links that defines the chain in order. - GetLinks() []Link - - // GetBlock returns the latest block that the chain is pointing at. - GetBlock() Block - - // Verify takes the genesis block and the verifier factory that should - // verify the chain. Performs the verification starting at the link Digest. - Verify(genesis Genesis, from Digest, fac crypto.VerifierFactory) error -} - -// ChainFactory is the interface to serialize and deserialize chains. -type ChainFactory interface { - serde.Factory - - // ChainOf returns the chain from the data if appropriate, otherwise it - // returns an error. - ChainOf(serde.Context, []byte) (Chain, error) -} diff --git a/dela/core/ordering/ordering.go b/dela/core/ordering/ordering.go deleted file mode 100644 index de63bef..0000000 --- a/dela/core/ordering/ordering.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package ordering defines the interface of the ordering service. The -// high-level purpose of this service is to order the transactions from the -// pool. -// -// Depending on the implementation, the service can be composed of multiple -// sub-components. For instance, an ordering service using CoSiPBFT will need to -// elect a leader every round but one running PoW will only do an ordering -// locally and creates a block with the proof of work. -package ordering - -import ( - "context" - - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/validation" -) - -// Proof contains the value of a specific key. -type Proof interface { - // GetKey returns the key of the proof. - GetKey() []byte - - // GetValue returns the value of the key. - GetValue() []byte -} - -// Event describes the current state of the service after an update. -type Event struct { - Index uint64 - Transactions []validation.TransactionResult -} - -// Service is the interface of an ordering service. It provides the primitives -// to order transactions from a pool. -type Service interface { - // GetProof must return a proof of the value at the provided key. - GetProof(key []byte) (Proof, error) - - // GetStore returns the store used by the service. - GetStore() store.Readable - - // Watch returns a channel populated with events when transactions are - // accepted. - Watch(ctx context.Context) <-chan Event - - // Close closes the service and cleans the resources. - Close() error -} diff --git a/dela/core/ordering/pow/block.go b/dela/core/ordering/pow/block.go deleted file mode 100644 index 282a5d6..0000000 --- a/dela/core/ordering/pow/block.go +++ /dev/null @@ -1,164 +0,0 @@ -package pow - -import ( - "context" - "encoding" - "encoding/binary" - "math/big" - - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -// Difficulty is the default difficulty. -const Difficulty = 1 - -// Block is a representation of a batch of transactions for a Proof-of-Work -// consensus. Each block has a fingerprint as a proof of correctness. -type Block struct { - index uint64 - nonce uint64 - root []byte - data validation.Result - hash []byte -} - -type blockTemplate struct { - Block - - hashFactory crypto.HashFactory - difficulty uint -} - -// BlockOption is the type of options to create a block. -type BlockOption func(*blockTemplate) - -// WithIndex is an option to set the block index. -func WithIndex(index uint64) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.index = index - } -} - -// WithNonce is an option to set the nonce of a block. -func WithNonce(nonce uint64) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.nonce = nonce - tmpl.difficulty = 0 - } -} - -// WithRoot is an option to set the root of a block. -func WithRoot(root []byte) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.root = root - } -} - -// WithDifficulty is an option to set the difficulty of the proof of work. If -// the difficulty is set to 0, the hash will be calculated according to the -// current nonce. -func WithDifficulty(diff uint) BlockOption { - return func(tmpl *blockTemplate) { - tmpl.difficulty = diff - } -} - -// NewBlock creates a new block. -func NewBlock(ctx context.Context, data validation.Result, opts ...BlockOption) (Block, error) { - tmpl := blockTemplate{ - Block: Block{ - data: data, - }, - hashFactory: crypto.NewSha256Factory(), - difficulty: Difficulty, - } - - for _, opt := range opts { - opt(&tmpl) - } - - err := tmpl.Block.prepare(ctx, tmpl.hashFactory, tmpl.difficulty) - if err != nil { - return tmpl.Block, xerrors.Errorf("couldn't prepare block: %v", err) - } - - return tmpl.Block, nil -} - -// Prepare is the actual proof of work on the block. It will find the nonce to -// match the difficulty level. -func (b *Block) prepare(ctx context.Context, fac crypto.HashFactory, diff uint) error { - h := fac.New() - - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer, b.index) - _, err := h.Write(buffer) - if err != nil { - return xerrors.Errorf("failed to write index: %v", err) - } - - _, err = h.Write(b.root) - if err != nil { - return xerrors.Errorf("failed to write root: %v", err) - } - - err = b.data.Fingerprint(h) - if err != nil { - return xerrors.Errorf("failed to fingerprint data: %v", err) - } - - // The state before writing the nonce is saved so it does not need to be - // computed all the time. - inter, err := h.(encoding.BinaryMarshaler).MarshalBinary() - if err != nil { - return xerrors.Errorf("couldn't marshal digest: %v", err) - } - - bitstring := make([]byte, h.Size()) - for i := range bitstring { - bitstring[i] = 0xff - } - - target := new(big.Int) - target.SetBytes(bitstring) - target.Rsh(target, diff) - - for { - // Allow the proof of work to be aborted at any time if the context is - // cancelled earlier. - if ctx.Err() != nil { - return xerrors.Errorf("context error: %v", ctx.Err()) - } - - // Copy h to get the state before the nonce is written. - err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(inter) - if err != nil { - return xerrors.Errorf("couldn't unmarshal digest: %v", err) - } - - binary.LittleEndian.PutUint64(buffer, b.nonce) - _, err = h.Write(buffer) - if err != nil { - return xerrors.Errorf("failed to write nonce: %v", err) - } - - res := h.Sum(nil) - // If no difficulty is set, the provided nonce defines the hash, - // otherwise it looks for a hash that matches the difficulty. - if diff == 0 || matchDifficulty(res, target) { - b.hash = res - return nil - } - - b.nonce++ - } -} - -func matchDifficulty(hash []byte, limit *big.Int) bool { - value := new(big.Int) - value.SetBytes(hash) - - return value.Cmp(limit) == -1 -} diff --git a/dela/core/ordering/pow/block_test.go b/dela/core/ordering/pow/block_test.go deleted file mode 100644 index b24f155..0000000 --- a/dela/core/ordering/pow/block_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package pow - -import ( - "context" - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestBlock_New(t *testing.T) { - block, err := NewBlock(context.Background(), fakeData{}, WithIndex(1), WithNonce(2), WithRoot([]byte{3})) - require.NoError(t, err) - require.Equal(t, uint64(1), block.index) - require.Equal(t, uint64(2), block.nonce) - require.Equal(t, []byte{3}, block.root) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - _, err = NewBlock(ctx, fakeData{}) - require.EqualError(t, err, "couldn't prepare block: context error: context canceled") -} - -func TestBlock_Prepare(t *testing.T) { - block := &Block{ - data: fakeData{}, - } - - ctx := context.Background() - - err := block.prepare(ctx, crypto.NewSha256Factory(), 1) - require.NoError(t, err) - require.Len(t, block.hash, 32) - - err = block.prepare(ctx, crypto.NewSha256Factory(), 0) - require.NoError(t, err) - require.Len(t, block.hash, 32) - - err = block.prepare(ctx, fake.NewHashFactory(fake.NewBadHash()), 0) - require.EqualError(t, err, fake.Err("failed to write index")) - - err = block.prepare(ctx, fake.NewHashFactory(fake.NewBadHashWithDelay(1)), 0) - require.EqualError(t, err, fake.Err("failed to write root")) - - block.data = fakeData{err: fake.GetError()} - err = block.prepare(ctx, crypto.NewSha256Factory(), 0) - require.EqualError(t, err, fake.Err("failed to fingerprint data")) - - block.data = fakeData{} - err = block.prepare(ctx, fake.NewHashFactory(fake.NewBadHashWithDelay(2)), 0) - require.EqualError(t, err, fake.Err("couldn't marshal digest")) - - err = block.prepare(ctx, fake.NewHashFactory(fake.NewBadHashWithDelay(3)), 0) - require.EqualError(t, err, fake.Err("couldn't unmarshal digest")) - - err = block.prepare(ctx, fake.NewHashFactory(fake.NewBadHashWithDelay(4)), 0) - require.EqualError(t, err, fake.Err("failed to write nonce")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeData struct { - validation.Result - err error -} - -func (d fakeData) GetTransactionResults() []validation.TransactionResult { - return nil -} - -func (d fakeData) Fingerprint(io.Writer) error { - return d.err -} diff --git a/dela/core/ordering/pow/observer.go b/dela/core/ordering/pow/observer.go deleted file mode 100644 index 219953b..0000000 --- a/dela/core/ordering/pow/observer.go +++ /dev/null @@ -1,11 +0,0 @@ -package pow - -import "go.dedis.ch/dela/core/ordering" - -type observer struct { - events chan ordering.Event -} - -func (o observer) NotifyCallback(event interface{}) { - o.events <- event.(ordering.Event) -} diff --git a/dela/core/ordering/pow/pow.go b/dela/core/ordering/pow/pow.go deleted file mode 100644 index 91c8487..0000000 --- a/dela/core/ordering/pow/pow.go +++ /dev/null @@ -1,201 +0,0 @@ -// Package pow implements a Proof-of-Work ordering service. This implementation -// very naive and only support one single node. It demonstrates a permissionless -// blockchain. -// -// TODO: later improve or remove -package pow - -import ( - "context" - "sync" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/core" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -type epoch struct { - block Block - store hashtree.Tree -} - -// Service is an ordering service powered by a Proof-of-Work consensus -// algorithm. -// -// - implements ordering.Service -type Service struct { - pool pool.Pool - validation validation.Service - epochs []epoch - hashFactory crypto.HashFactory - difficulty uint - watcher core.Observable - closing chan struct{} - closed sync.WaitGroup -} - -// NewService creates a new service. -func NewService(pool pool.Pool, val validation.Service, trie hashtree.Tree) *Service { - return &Service{ - pool: pool, - validation: val, - epochs: []epoch{{store: trie}}, - hashFactory: crypto.NewSha256Factory(), - difficulty: 1, - watcher: core.NewWatcher(), - } -} - -// Listen implements ordering.Service. -func (s *Service) Listen() error { - if s.closing != nil { - return xerrors.New("service already started") - } - - s.closing = make(chan struct{}) - s.closed = sync.WaitGroup{} - s.closed.Add(1) - - go func() { - defer s.closed.Done() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // This is the main loop to create a block. It will wait for - // transactions at first and then try to create a new block. If another - // peer successfully finds a correct block, it will abort and restart. - for { - errCh := make(chan error, 1) - go func() { - errCh <- s.createBlock(ctx) - }() - - select { - case <-s.closing: - // This will cancel the context thus stopping the go routine. - return - case err := <-errCh: - if err != nil { - // Something went wrong when creating the block. The main - // loop is stopped as this is a critical error. - dela.Logger.Err(err).Msg("failed to create block") - return - } - - } - } - }() - - return nil -} - -// Stop implements ordering.Service. -func (s *Service) Stop() error { - if s.closing == nil { - return xerrors.New("service not started") - } - - close(s.closing) - s.closed.Wait() - - s.closing = nil - - return nil -} - -// GetProof implements ordering.Service. -func (s *Service) GetProof(key []byte) (ordering.Proof, error) { - last := s.epochs[len(s.epochs)-1] - - path, err := last.store.GetPath(key) - if err != nil { - return nil, xerrors.Errorf("couldn't read share: %v", err) - } - - blocks := make([]Block, len(s.epochs)) - for i, epoch := range s.epochs { - blocks[i] = epoch.block - } - - pr, err := NewProof(blocks, path) - if err != nil { - return nil, xerrors.Errorf("couldn't create proof: %v", err) - } - - return pr, nil -} - -// Watch implements ordering.Service. -func (s *Service) Watch(ctx context.Context) <-chan ordering.Event { - events := make(chan ordering.Event, 1) - - obs := observer{events: events} - s.watcher.Add(obs) - - go func() { - <-ctx.Done() - s.watcher.Remove(obs) - }() - - return events -} - -func (s *Service) createBlock(ctx context.Context) error { - // Wait for at least one transaction before creating a block. - txs := s.pool.Gather(ctx, pool.Config{Min: 1}) - - if ctx.Err() != nil { - // Context is closed so we don't proceed in the block creation. - return nil - } - - latestEpoch := s.epochs[len(s.epochs)-1] - - var data validation.Result - newTrie, err := latestEpoch.store.Stage(func(rwt store.Snapshot) error { - var err error - data, err = s.validation.Validate(rwt, txs) - if err != nil { - return xerrors.Errorf("failed to validate: %v", err) - } - - return nil - }) - - if err != nil { - return xerrors.Errorf("couldn't stage store: %v", err) - } - - block, err := NewBlock( - ctx, - data, - WithIndex(uint64(len(s.epochs))), - WithRoot(newTrie.GetRoot()), - WithDifficulty(s.difficulty), - ) - if err != nil { - return xerrors.Errorf("couldn't create block: %v", err) - } - - s.epochs = append(s.epochs, epoch{ - block: block, - store: newTrie, - }) - - for _, txres := range block.data.GetTransactionResults() { - s.pool.Remove(txres.GetTransaction()) - } - - s.watcher.Notify(ordering.Event{Index: block.index}) - - dela.Logger.Trace().Uint64("index", block.index).Msg("block append") - - return nil -} diff --git a/dela/core/ordering/pow/pow_test.go b/dela/core/ordering/pow/pow_test.go deleted file mode 100644 index 34f9a41..0000000 --- a/dela/core/ordering/pow/pow_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package pow - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - tree "go.dedis.ch/dela/core/store/hashtree/binprefix" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/core/txn" - pool "go.dedis.ch/dela/core/txn/pool/mem" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation" - val "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "golang.org/x/xerrors" -) - -func TestService_Basic(t *testing.T) { - tree, clean := makeTree(t) - defer clean() - - exec := native.NewExecution() - exec.Set(testContractName, testExec{}) - - pool := pool.NewPool() - srvc := NewService( - pool, - val.NewService(exec, signed.NewTransactionFactory()), - tree, - ) - - // 1. Start the ordering service. - require.NoError(t, srvc.Listen()) - defer srvc.Stop() - - // 2. Watch for new events before sending a transaction. - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - evts := srvc.Watch(ctx) - - signer := bls.NewSigner() - - // 3. Send a transaction to the pool. It should be detected by the ordering - // service and start a new block. - require.NoError(t, pool.Add(makeTx(t, 0, signer))) - - evt := <-evts - require.Equal(t, uint64(1), evt.Index) - - pr, err := srvc.GetProof([]byte("ping")) - require.NoError(t, err) - require.Equal(t, []byte("pong"), pr.GetValue()) - - // 4. Send another transaction to the pool. This time it should creates a - // block appended to the previous one. - require.NoError(t, pool.Add(makeTx(t, 1, signer))) - - evt = <-evts - require.Equal(t, uint64(2), evt.Index) -} - -func TestService_Listen(t *testing.T) { - tree, clean := makeTree(t) - defer clean() - - vs := val.NewService(native.NewExecution(), signed.NewTransactionFactory()) - - pool := pool.NewPool() - srvc := NewService(pool, vs, tree) - - err := srvc.Listen() - require.NoError(t, err) - - err = srvc.Listen() - require.EqualError(t, err, "service already started") - - err = srvc.Stop() - require.NoError(t, err) - - err = srvc.Stop() - require.EqualError(t, err, "service not started") - - pool.Add(makeTx(t, 0, fake.NewSigner())) - srvc = NewService(pool, badValidation{}, tree) - err = srvc.Listen() - require.NoError(t, err) - - srvc.closed.Wait() -} - -func TestService_GetProof(t *testing.T) { - tree, clean := makeTree(t) - defer clean() - - srvc := &Service{ - epochs: []epoch{{store: tree}}, - } - - pr, err := srvc.GetProof([]byte("A")) - require.NoError(t, err) - require.Equal(t, []byte("A"), pr.GetKey()) - - srvc.epochs[0].block.root = []byte{1} - _, err = srvc.GetProof([]byte("A")) - require.EqualError(t, err, - "couldn't create proof: mismatch block and share store root 0x01 != ") - - srvc.epochs[0].store = badTrie{} - _, err = srvc.GetProof([]byte("A")) - require.EqualError(t, err, fake.Err("couldn't read share")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -const testContractName = "abc" - -func makeTree(t *testing.T) (hashtree.Tree, func()) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-pow") - require.NoError(t, err) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - tree := tree.NewMerkleTree(db, tree.Nonce{}) - tree.Stage(func(store.Snapshot) error { return nil }) - - return tree, func() { os.RemoveAll(dir) } -} - -func makeTx(t *testing.T, nonce uint64, signer crypto.Signer) txn.Transaction { - tx, err := signed.NewTransaction( - nonce, - signer.GetPublicKey(), - signed.WithArg("key", []byte("ping")), - signed.WithArg("value", []byte("pong")), - signed.WithArg(native.ContractArg, []byte(testContractName)), - ) - require.NoError(t, err) - - return tx -} - -type testExec struct{} - -func (e testExec) Execute(store store.Snapshot, step execution.Step) error { - key := step.Current.GetArg("key") - value := step.Current.GetArg("value") - - if len(key) == 0 || len(value) == 0 { - return xerrors.New("key or value is nil") - } - - err := store.Set(key, value) - if err != nil { - return err - } - - return nil -} - -type badValidation struct { - validation.Service -} - -func (v badValidation) Validate(store.Snapshot, []txn.Transaction) (validation.Result, error) { - return nil, fake.GetError() -} - -type badTrie struct { - hashtree.Tree -} - -func (s badTrie) GetPath([]byte) (hashtree.Path, error) { - return nil, fake.GetError() -} diff --git a/dela/core/ordering/pow/proof.go b/dela/core/ordering/pow/proof.go deleted file mode 100644 index 0530ced..0000000 --- a/dela/core/ordering/pow/proof.go +++ /dev/null @@ -1,46 +0,0 @@ -package pow - -import ( - "bytes" - - "go.dedis.ch/dela/core/store/hashtree" - "golang.org/x/xerrors" -) - -// Proof is a proof that the chain of blocks has or has not the key in the -// store. If the key exists, the proof also contains the value. -// -// - implements ordering.Proof -type Proof struct { - blocks []Block - path hashtree.Path -} - -// NewProof creates a new valid proof. -func NewProof(blocks []Block, path hashtree.Path) (Proof, error) { - pr := Proof{ - blocks: blocks, - path: path, - } - - if len(blocks) == 0 { - return pr, xerrors.New("empty list of blocks") - } - - last := blocks[len(blocks)-1] - if !bytes.Equal(last.root, path.GetRoot()) { - return pr, xerrors.Errorf("mismatch block and share store root %#x != %#x", last.root, path.GetRoot()) - } - - return pr, nil -} - -// GetKey implements ordering.Proof. -func (p Proof) GetKey() []byte { - return p.path.GetKey() -} - -// GetValue implements ordering.Proof. -func (p Proof) GetValue() []byte { - return p.path.GetValue() -} diff --git a/dela/core/store/hashtree/binprefix/binprefix.go b/dela/core/store/hashtree/binprefix/binprefix.go deleted file mode 100644 index fb053d2..0000000 --- a/dela/core/store/hashtree/binprefix/binprefix.go +++ /dev/null @@ -1,286 +0,0 @@ -// Package binprefix implements the hash tree interface by following the merkle -// binary prefix tree algorithm. -// -// https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-melara.pdf -// -// The merkle tree is stored in-memory until it reaches a certain threshold of -// depth where it will write the nodes in disk. The leaf are always stored in -// disk because of the value it holds. -// -// Interior (Root) -// / \ -// 0 / \ 1 -// / \ -// Interior Interior -// / \ / \ -// 0 / \ 1 0 / \ 1 -// / \ / \ -// DiskNode Interior Empty Interior -// / \ / \ -// - - - - - -/- - -\- - - - - - / - - -\- - - - - - - - - - - Memory Depth -// 0 / \ 1 0 / \ 1 -// / \ / \ -// DiskNode DiskNode DiskNode DiskNode -// -// The drawing above demonstrates an example of a tree. Here the memory depth is -// set at 3 which means that every node after this level will be a disk node. It -// will be loaded to its in-memory type when traversing the tree. The node at -// prefix 00 is an example of a leaf node which is a disk node even above the -// memory depth level. -// -// Documentation Last Review: 08.10.2020 -package binprefix - -import ( - "sync" - - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -// MerkleTree is an implementation of a Merkle prefix binary tree. This -// particular implementation assumes the keys will have the same length so that -// only the longest unique prefix along the path can be a leaf node. -// -// The leafs of the tree will be stored on the disk when committing the tree. -// Modifications on a staged tree are done in-memory. -// -// - implements hashtree.Tree -type MerkleTree struct { - sync.Mutex - - tree *Tree - db kv.DB - tx store.Transaction - bucket []byte - hashFactory crypto.HashFactory -} - -// NewMerkleTree creates a new Merkle tree-based storage. -func NewMerkleTree(db kv.DB, nonce Nonce) *MerkleTree { - return &MerkleTree{ - tree: NewTree(nonce), - db: db, - bucket: []byte("hashtree"), - hashFactory: crypto.NewSha256Factory(), - } -} - -// Load tries to read the bucket and scan it for existing leafs and populate the -// tree with them. -func (t *MerkleTree) Load() error { - t.Lock() - defer t.Unlock() - - return t.doUpdate(func(tx kv.WritableTx) error { - bucket := tx.GetBucket(t.bucket) - - err := t.tree.FillFromBucket(bucket) - if err != nil { - return xerrors.Errorf("failed to load: %v", err) - } - - err = t.tree.CalculateRoot(t.hashFactory, bucket) - if err != nil { - return xerrors.Errorf("while updating: %v", err) - } - - return nil - }) -} - -// Get implements store.Readable. It returns the value associated with the key -// if it exists, otherwise it returns nil. -func (t *MerkleTree) Get(key []byte) ([]byte, error) { - t.Lock() - defer t.Unlock() - - var value []byte - - err := t.doView(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(t.bucket) - - var err error - value, err = t.tree.Search(key, nil, bucket) - - return err - }) - - if err != nil { - return nil, xerrors.Errorf("couldn't search key: %v", err) - } - - return value, nil -} - -// GetRoot implements hashtree.Tree. It returns the root hash of the tree. -func (t *MerkleTree) GetRoot() []byte { - t.Lock() - defer t.Unlock() - - return t.tree.root.GetHash() -} - -// GetPath implements hashtree.Tree. It returns a path to a given key that can -// be used to prove the inclusion or the absence of a key. -func (t *MerkleTree) GetPath(key []byte) (hashtree.Path, error) { - t.Lock() - defer t.Unlock() - - path := newPath(t.tree.nonce[:], key) - - err := t.doView(func(tx kv.ReadableTx) error { - bucket := tx.GetBucket(t.bucket) - - _, err := t.tree.Search(key, &path, bucket) - return err - }) - - if err != nil { - return nil, xerrors.Errorf("couldn't search key: %v", err) - } - - return path, nil -} - -// Stage implements hashtree.Tree. It executes the callback over a clone of the -// current tree and return the clone with the root calculated. -func (t *MerkleTree) Stage(fn func(store.Snapshot) error) (hashtree.StagingTree, error) { - clone := t.clone() - - err := t.doUpdate(func(tx kv.WritableTx) error { - b, err := tx.GetBucketOrCreate(t.bucket) - if err != nil { - return xerrors.Errorf("read bucket failed: %v", err) - } - - err = fn(writableMerkleTree{ - MerkleTree: clone, - bucket: b, - }) - - if err != nil { - return xerrors.Errorf("callback failed: %v", err) - } - - err = clone.tree.CalculateRoot(t.hashFactory, b) - if err != nil { - return xerrors.Errorf("couldn't update tree: %v", err) - } - - return nil - }) - - return clone, err -} - -// Commit implements hashtree.StagingTree. It writes the leaf nodes to the disk -// and a trade-off of other nodes. -func (t *MerkleTree) Commit() error { - t.Lock() - defer t.Unlock() - - err := t.doUpdate(func(tx kv.WritableTx) error { - bucket, err := tx.GetBucketOrCreate(t.bucket) - if err != nil { - return xerrors.Errorf("read bucket failed: %v", err) - } - - return t.tree.Persist(bucket) - }) - - if err != nil { - return xerrors.Errorf("failed to persist tree: %v", err) - } - - return nil -} - -// WithTx implements hashtree.StagingTree. It returns a tree that will share the -// same underlying data but it will perform operations on the database through -// the transaction. -func (t *MerkleTree) WithTx(tx store.Transaction) hashtree.StagingTree { - return &MerkleTree{ - tree: t.tree, - db: t.db, - tx: tx, - bucket: t.bucket, - hashFactory: t.hashFactory, - } -} - -func (t *MerkleTree) clone() *MerkleTree { - return &MerkleTree{ - tree: t.tree.Clone(), - db: t.db, - tx: t.tx, - bucket: t.bucket, - hashFactory: t.hashFactory, - } -} - -func (t *MerkleTree) doUpdate(fn func(kv.WritableTx) error) error { - if t.tx != nil { - tx, ok := t.tx.(kv.WritableTx) - if !ok { - return xerrors.Errorf("transaction '%T' is not writable", t.tx) - } - - return fn(tx) - } - - return t.db.Update(fn) -} - -func (t *MerkleTree) doView(fn func(kv.ReadableTx) error) error { - if t.tx != nil { - tx, ok := t.tx.(kv.ReadableTx) - if !ok { - return xerrors.Errorf("transaction '%T' is not readable", t.tx) - } - - return fn(tx) - } - - return t.db.View(fn) -} - -// WritableMerkleTree is a wrapper around the merkle tree implementation so that -// it can be written into but the tree is not updated at every operation. -// -// - implements store.Writable -type writableMerkleTree struct { - *MerkleTree - - bucket kv.Bucket -} - -// Set implements store.Writable. It adds or updates the key in the internal -// tree. -func (t writableMerkleTree) Set(key, value []byte) error { - t.Lock() - defer t.Unlock() - - err := t.tree.Insert(key, value, t.bucket) - if err != nil { - return xerrors.Errorf("couldn't insert pair: %v", err) - } - - return nil -} - -// Delete implements store.Writable. It removes the key from the tree. -func (t writableMerkleTree) Delete(key []byte) error { - t.Lock() - defer t.Unlock() - - err := t.tree.Delete(key, t.bucket) - if err != nil { - return xerrors.Errorf("couldn't delete key: %v", err) - } - - return nil -} diff --git a/dela/core/store/hashtree/binprefix/binprefix_test.go b/dela/core/store/hashtree/binprefix/binprefix_test.go deleted file mode 100644 index 3dc232c..0000000 --- a/dela/core/store/hashtree/binprefix/binprefix_test.go +++ /dev/null @@ -1,339 +0,0 @@ -package binprefix - -import ( - "bytes" - "crypto/rand" - "os" - "path/filepath" - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/store/hashtree" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestMerkleTree_IntegrationTest(t *testing.T) { - db, clean := makeDB(t) - defer clean() - - tree := NewMerkleTree(db, Nonce{}) - tree.tree.memDepth = 5 - fac := tree.hashFactory - values := map[[MaxDepth]byte][]byte{} - - next, err := tree.Stage(func(snap store.Snapshot) error { - for i := 0; i < 1000; i++ { - key := [MaxDepth]byte{} - rand.Read(key[:]) - - value := make([]byte, 8) - rand.Read(value) - - err := snap.Set(key[:], value) - require.NoError(t, err) - - values[key] = value - } - return nil - }) - - require.NoError(t, err) - require.NotEmpty(t, values) - tree = next.(*MerkleTree) - - // Test read and write in a transaction. - err = db.Update(func(txn kv.WritableTx) error { - txtree := tree.WithTx(txn) - - err := txtree.Commit() - require.NoError(t, err) - - for key := range values { - path, err := txtree.GetPath(key[:]) - require.NoError(t, err) - - root, err := path.(Path).computeRoot(fac) - require.NoError(t, err) - require.Equal(t, root, tree.GetRoot()) - require.Equal(t, values[key], path.GetValue()) - } - - return nil - }) - require.NoError(t, err) - - next, err = tree.Stage(func(snap store.Snapshot) error { - for key := range values { - err = snap.Delete(key[:]) - require.NoError(t, err) - } - - return nil - }) - require.NoError(t, err) - tree = next.(*MerkleTree) - - require.Len(t, tree.GetRoot(), 32) - require.IsType(t, (*EmptyNode)(nil), tree.tree.root) - - err = tree.Commit() - require.NoError(t, err) - - db.View(func(txn kv.ReadableTx) error { - b := txn.GetBucket(tree.bucket) - require.NotNil(t, b) - - return b.ForEach(func(k, v []byte) error { - t.Fatal("database should be empty") - return nil - }) - }) -} - -func TestMerkleTree_Random_IntegrationTest(t *testing.T) { - f := func(nonce Nonce, n uint8, mem uint8) bool { - t.Logf("Step nonce:%x n:%d mem:%d", nonce, n, mem%32) - - db, clean := makeDB(t) - defer clean() - - tree := NewMerkleTree(db, nonce) - tree.tree.memDepth = int(mem) % 32 - - values := map[[4]byte][]byte{} - - var err error - var stage hashtree.StagingTree = tree - for k := 0; k < 2; k++ { - stage, err = stage.Stage(func(snap store.Snapshot) error { - for i := 0; i < int(n); i++ { - key := [4]byte{} - rand.Read(key[:]) - - value := make([]byte, 4) - rand.Read(value) - - values[key] = value - - snap.Set(key[:], value) - } - return nil - }) - require.NoError(t, err) - } - - // Test that all keys are present in-memory. - for key, value := range values { - v, err := stage.Get(key[:]) - require.NoError(t, err) - require.Equal(t, value, v) - } - - err = stage.Commit() - require.NoError(t, err) - - tree = stage.(*MerkleTree) - - // Test that the keys are still present after persiting to the disk, - // alongside with the computed hash for the nodes. - for key, value := range values { - path, err := tree.GetPath(key[:]) - require.NoError(t, err) - require.Equal(t, value, path.GetValue()) - - root, err := path.(Path).computeRoot(crypto.NewSha256Factory()) - require.NoError(t, err) - require.Equal(t, tree.GetRoot(), root) - } - - newTree := NewMerkleTree(db, nonce) - err = newTree.Load() - require.NoError(t, err) - - // Test that the keys are still present after loading the tree from the - // disk. - for key, value := range values { - path, err := newTree.GetPath(key[:]) - require.NoError(t, err) - require.Equal(t, value, path.GetValue()) - - root, err := path.(Path).computeRoot(crypto.NewSha256Factory()) - require.NoError(t, err) - require.Equal(t, newTree.GetRoot(), root) - } - - return true - } - - err := quick.Check(f, &quick.Config{MaxCount: 20}) - require.NoError(t, err) -} - -func TestMerkleTree_Load(t *testing.T) { - tree := NewMerkleTree(fakeDB{}, Nonce{}) - - err := tree.Load() - require.NoError(t, err) - - tree.tx = fakeTx{bucket: &fakeBucket{errScan: fake.GetError()}} - err = tree.Load() - require.EqualError(t, err, fake.Err("failed to load: while scanning")) - - tree.tx = nil - tree.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - err = tree.Load() - require.EqualError(t, err, fake.Err("while updating: failed to prepare: empty node failed")) -} - -func TestMerkleTree_Get(t *testing.T) { - tree := NewMerkleTree(fakeDB{}, Nonce{}) - - err := tree.tree.Insert([]byte("ping"), []byte("pong"), &fakeBucket{}) - require.NoError(t, err) - - value, err := tree.Get([]byte("ping")) - require.NoError(t, err) - require.Equal(t, []byte("pong"), value) - - value, err = tree.Get([]byte("pong")) - require.NoError(t, err) - require.Nil(t, value) - - _, err = tree.Get(make([]byte, MaxDepth+1)) - require.EqualError(t, err, "couldn't search key: mismatch key length 33 > 32") - - tree.tx = wrongTx{} - _, err = tree.Get([]byte{}) - require.EqualError(t, err, - "couldn't search key: transaction 'binprefix.wrongTx' is not readable") -} - -func TestMerkleTree_GetRoot(t *testing.T) { - tree := NewMerkleTree(fakeDB{}, Nonce{}) - // Tree is not yet updated. - require.Empty(t, tree.GetRoot()) - - empty, err := tree.Stage(func(store.Snapshot) error { return nil }) - require.NoError(t, err) - // Tree is updated even without any leafs. - require.NotEmpty(t, empty.GetRoot()) - - filled, err := tree.Stage(func(snap store.Snapshot) error { - return snap.Set([]byte("ping"), []byte("pong")) - }) - require.NoError(t, err) - require.NotEmpty(t, filled.GetRoot()) - require.NotEqual(t, empty.GetRoot(), filled.GetRoot()) -} - -func TestMerkleTree_GetPath(t *testing.T) { - tree := NewMerkleTree(fakeDB{}, Nonce{}) - - f := func(key [8]byte, value []byte) bool { - err := tree.tree.Insert(key[:], value, &fakeBucket{}) - require.NoError(t, err) - - err = tree.tree.CalculateRoot(tree.hashFactory, &fakeBucket{}) - require.NoError(t, err) - - path, err := tree.GetPath(key[:]) - require.NoError(t, err) - - root, err := path.(Path).computeRoot(tree.hashFactory) - require.NoError(t, err) - require.Equal(t, root, path.GetRoot()) - - return bytes.Equal(value, path.GetValue()) - } - - err := quick.Check(f, nil) - require.NoError(t, err) - - _, err = tree.GetPath(make([]byte, MaxDepth+1)) - require.EqualError(t, err, "couldn't search key: mismatch key length 33 > 32") -} - -func TestMerkleTree_Stage(t *testing.T) { - tree := NewMerkleTree(fakeDB{}, Nonce{}) - - next, err := tree.Stage(func(snap store.Snapshot) error { return nil }) - require.NoError(t, err) - require.NotEmpty(t, next.GetRoot()) - - _, err = tree.Stage(func(store.Snapshot) error { return fake.GetError() }) - require.EqualError(t, err, fake.Err("callback failed")) - - tree.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - _, err = tree.Stage(func(store.Snapshot) error { return nil }) - require.EqualError(t, err, - fake.Err("couldn't update tree: failed to prepare: empty node failed")) - - tree.tx = badTx{} - _, err = tree.Stage(func(store.Snapshot) error { return nil }) - require.EqualError(t, err, fake.Err("read bucket failed")) - - tree.tx = wrongTx{} - _, err = tree.Stage(func(store.Snapshot) error { return nil }) - require.EqualError(t, err, "transaction 'binprefix.wrongTx' is not writable") -} - -func TestMerkleTree_Commit(t *testing.T) { - tree := NewMerkleTree(fakeDB{err: fake.GetError()}, Nonce{}) - - err := tree.Commit() - require.EqualError(t, err, fake.Err("failed to persist tree")) - - tree.tx = badTx{} - err = tree.Commit() - require.EqualError(t, err, fake.Err("failed to persist tree: read bucket failed")) -} - -func TestWritableMerkleTree_Set(t *testing.T) { - tree := writableMerkleTree{MerkleTree: NewMerkleTree(fakeDB{}, Nonce{})} - - err := tree.Set([]byte("ping"), []byte("pong")) - require.NoError(t, err) - require.Equal(t, 1, tree.tree.Len()) - - err = tree.Set(make([]byte, MaxDepth+1), nil) - require.EqualError(t, err, "couldn't insert pair: mismatch key length 33 > 32") -} - -func TestWritableMerkleTree_Delete(t *testing.T) { - tree := writableMerkleTree{MerkleTree: NewMerkleTree(fakeDB{}, Nonce{})} - - err := tree.Delete([]byte("ping")) - require.NoError(t, err) - - err = tree.Delete(make([]byte, MaxDepth+1)) - require.EqualError(t, err, "couldn't delete key: mismatch key length 33 > 32") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeDB(t *testing.T) (kv.DB, func()) { - dir, err := os.MkdirTemp(os.TempDir(), "dela-pow") - require.NoError(t, err) - - db, err := kv.New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - return db, func() { os.RemoveAll(dir) } -} - -type badTx struct { - kv.WritableTx -} - -func (tx badTx) GetBucketOrCreate([]byte) (kv.Bucket, error) { - return nil, fake.GetError() -} - -type wrongTx struct { - store.Transaction -} diff --git a/dela/core/store/hashtree/binprefix/disk.go b/dela/core/store/hashtree/binprefix/disk.go deleted file mode 100644 index 5e45ef2..0000000 --- a/dela/core/store/hashtree/binprefix/disk.go +++ /dev/null @@ -1,237 +0,0 @@ -// -// Documentation Last Review: 08.10.2020 -// - -package binprefix - -import ( - "math/big" - - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// DiskNode is an implementation of a tree node which is stored on the -// disk. -// -// - implements binprefix.TreeNode -type DiskNode struct { - depth uint16 - hash []byte - context serde.Context - factory serde.Factory -} - -// NewDiskNode creates a new disk node. -func NewDiskNode(depth uint16, hash []byte, ctx serde.Context, factory serde.Factory) *DiskNode { - return &DiskNode{ - depth: depth, - hash: hash, - context: ctx, - factory: factory, - } -} - -// GetHash implements binprefix.TreeNode. It returns the hash of the disk node -// if it is set, otherwise it returns nil. -func (n *DiskNode) GetHash() []byte { - return n.hash -} - -// GetType returns the type of the node. -func (n *DiskNode) GetType() byte { - return diskNodeType -} - -// Search implements binprefix.TreeNode. It loads the disk node and then search -// for the key. -func (n *DiskNode) Search(key *big.Int, path *Path, bucket kv.Bucket) ([]byte, error) { - if bucket == nil { - return nil, xerrors.New("bucket is nil") - } - - node, err := n.load(key, bucket) - if err != nil { - return nil, xerrors.Errorf("failed to load node: %v", err) - } - - value, err := node.Search(key, path, bucket) - if err != nil { - // No wrapping to prevent very long error message from recursive calls. - return nil, err - } - - return value, nil -} - -// Insert implements binprefix.TreeNode. It loads the node and inserts the -// key/value pair using in-memory operations. The whole path to the key will be -// loaded and kept in-memory until the tree is persisted. -func (n *DiskNode) Insert(key *big.Int, value []byte, bucket kv.Bucket) (TreeNode, error) { - node, err := n.load(key, bucket) - if err != nil { - return nil, xerrors.Errorf("failed to load node: %v", err) - } - - next, err := node.Insert(key, value, bucket) - if err != nil { - // No wrapping to prevent very long error message from recursive calls. - return nil, err - } - - return next, nil -} - -// Delete implements binprefix.TreeNode. It loads the node and deletes the key -// if it exists. The whole path to the key is loaded in-memory until the tree is -// persisted. -func (n *DiskNode) Delete(key *big.Int, bucket kv.Bucket) (TreeNode, error) { - node, err := n.load(key, bucket) - if err != nil { - return nil, xerrors.Errorf("failed to load node: %v", err) - } - - next, err := node.Delete(key, bucket) - if err != nil { - return nil, err - } - - return next, nil -} - -// Prepare implements binprefix.TreeNode. It loads the node and calculates its -// hash. The subtree might be loaded in-memory if deeper hashes have not been -// computed yet. -func (n *DiskNode) Prepare(nonce []byte, prefix *big.Int, - bucket kv.Bucket, fac crypto.HashFactory) ([]byte, error) { - - if len(n.hash) > 0 { - // Hash is already calculated so we can skip and return. - return n.hash, nil - } - - node, err := n.load(prefix, bucket) - if err != nil { - return nil, xerrors.Errorf("failed to load node: %v", err) - } - - digest, err := node.Prepare(nonce, prefix, bucket, fac) - if err != nil { - // No wrapping to prevent very long error message from recursive calls. - return nil, err - } - - err = n.store(prefix, node, bucket) - if err != nil { - return nil, xerrors.Errorf("failed to store node: %v", err) - } - - n.hash = digest - - return digest, nil -} - -// Visit implements binprefix.TreeNode. -func (n *DiskNode) Visit(fn func(TreeNode) error) error { - return fn(n) -} - -// Clone implements binprefix.TreeNode. It clones the disk node but both the old -// and the new will read the same bucket. -func (n *DiskNode) Clone() TreeNode { - return NewDiskNode(n.depth, n.hash, n.context, n.factory) -} - -// Serialize implements serde.Message. It always returns an error as a disk node -// cannot be serialized. -func (n *DiskNode) Serialize(ctx serde.Context) ([]byte, error) { - return nil, xerrors.New("not implemented") -} - -func (n *DiskNode) load(index *big.Int, bucket kv.Bucket) (TreeNode, error) { - key := n.prepareKey(index) - - data := bucket.Get(key) - if len(data) == 0 { - return nil, xerrors.Errorf("prefix %b (depth %d) not in database", index, n.depth) - } - - msg, err := n.factory.Deserialize(n.context, data) - if err != nil { - return nil, xerrors.Errorf("failed to deserialize: %v", err) - } - - node, ok := msg.(TreeNode) - if !ok { - return nil, xerrors.Errorf("invalid node of type '%T'", msg) - } - - return node, nil -} - -func (n *DiskNode) store(index *big.Int, node TreeNode, b kv.Bucket) error { - data, err := node.Serialize(n.context) - if err != nil { - return xerrors.Errorf("failed to serialize: %v", err) - } - - key := n.prepareKey(index) - - err = b.Set(key, data) - if err != nil { - return xerrors.Errorf("failed to set key: %v", err) - } - - return nil -} - -func (n *DiskNode) cleanSubtree(depth uint16, index *big.Int, b kv.Bucket) error { - prefix := n.prepareKey(index) - if len(prefix) > 0 { - // It needs to scan a bitwise prefix thus it removes the last byte. - prefix = prefix[:len(prefix)-1] - } - - // The database can scan over prefix at the *byte* level but the node keys - // are bitwise so it manually compares the bits of the last byte of the - // prefix. - return b.Scan(prefix, func(k, _ []byte) error { - key := new(big.Int) - key.SetBytes(reverse(k)) - - for i := len(prefix) * 8; i < int(depth); i++ { - if key.Bit(i) != index.Bit(i) { - return nil - } - } - - return b.Delete(k) - }) -} - -func (n *DiskNode) prepareKey(index *big.Int) []byte { - prefix := new(big.Int) - - // First fill the prefix until the depth bit which will create a unique key - // for the node... - for i := 0; i < int(n.depth); i++ { - prefix.SetBit(prefix, i, index.Bit(i)) - } - - // ... but we set the bit at _depth_ to one to differentiate prefixes that - // end with 0s. - prefix.SetBit(prefix, int(n.depth), 1) - - return reverse(prefix.Bytes()) -} - -func reverse(buffer []byte) []byte { - buffer = append([]byte{}, buffer...) - for i, j := 0, len(buffer)-1; i < j; i, j = i+1, j-1 { - buffer[i], buffer[j] = buffer[j], buffer[i] - } - - return buffer -} diff --git a/dela/core/store/hashtree/binprefix/disk_test.go b/dela/core/store/hashtree/binprefix/disk_test.go deleted file mode 100644 index 2edb913..0000000 --- a/dela/core/store/hashtree/binprefix/disk_test.go +++ /dev/null @@ -1,238 +0,0 @@ -package binprefix - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde/json" -) - -var testCtx = json.NewContext() - -func TestDiskNode_GetHash(t *testing.T) { - node := &DiskNode{hash: []byte{0xaa}} - - require.Equal(t, []byte{0xaa}, node.GetHash()) -} - -func TestDiskNode_GetType(t *testing.T) { - node := &DiskNode{} - - require.Equal(t, diskNodeType, node.GetType()) -} - -func TestDiskNode_Search(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - // Test if a leaf node can be loaded and searched for. - leaf := NewLeafNode(0, big.NewInt(0), []byte("pong")) - data, err := leaf.Serialize(testCtx) - require.NoError(t, err) - - bucketKey := node.prepareKey(big.NewInt(0)) - - value, err := node.Search(big.NewInt(0), nil, newFakeBucket(bucketKey, data)) - require.NoError(t, err) - require.Equal(t, []byte("pong"), value) - - // Error if the node is not stored in the database. - _, err = node.Search(big.NewInt(0), nil, &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 0) not in database") - - inter := NewInteriorNode(0, big.NewInt(0)) - data, err = inter.Serialize(testCtx) - require.NoError(t, err) - - // Error deeper in the tree. - _, err = node.Search(big.NewInt(0), nil, newFakeBucket(bucketKey, data)) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 1) not in database") - - _, err = node.Search(big.NewInt(0), nil, nil) - require.EqualError(t, err, "bucket is nil") -} - -func TestDiskNode_Insert(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - empty := NewEmptyNode(0, big.NewInt(0)) - data, err := empty.Serialize(testCtx) - require.NoError(t, err) - - bucket := newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - next, err := node.Insert(big.NewInt(2), []byte("pong"), bucket) - require.NoError(t, err) - require.IsType(t, (*LeafNode)(nil), next) - - _, err = node.Insert(big.NewInt(2), nil, &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 10 (depth 0) not in database") - - inter := NewInteriorNode(0, big.NewInt(0)) - data, err = inter.Serialize(testCtx) - require.NoError(t, err) - - bucket = newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - _, err = node.Insert(big.NewInt(2), []byte("pong"), bucket) - require.EqualError(t, err, "failed to load node: prefix 10 (depth 1) not in database") -} - -func TestDiskNode_Delete(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - empty := NewEmptyNode(0, big.NewInt(0)) - data, err := empty.Serialize(testCtx) - require.NoError(t, err) - - bucket := newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - next, err := node.Delete(big.NewInt(2), bucket) - require.NoError(t, err) - require.IsType(t, (*EmptyNode)(nil), next) - - _, err = node.Delete(big.NewInt(2), &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 10 (depth 0) not in database") - - inter := NewInteriorNode(0, big.NewInt(0)) - data, err = inter.Serialize(testCtx) - require.NoError(t, err) - - bucket = newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - _, err = node.Delete(big.NewInt(2), bucket) - require.EqualError(t, err, "failed to load node: prefix 10 (depth 1) not in database") - - _, err = node.Delete(big.NewInt(3), bucket) - require.EqualError(t, err, "failed to load node: prefix 11 (depth 1) not in database") -} - -func TestDiskNode_Prepare(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - empty := NewEmptyNode(0, big.NewInt(0)) - data, err := empty.Serialize(testCtx) - require.NoError(t, err) - - bucket := newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - hash, err := node.Prepare([]byte{1}, big.NewInt(0), bucket, crypto.NewSha256Factory()) - require.NoError(t, err) - require.Len(t, hash, 32) - require.Equal(t, hash, node.hash) - - node.hash = nil - _, err = node.Prepare([]byte{}, big.NewInt(0), &fakeBucket{}, nil) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 0) not in database") - - bucket.errSet = fake.GetError() - _, err = node.Prepare([]byte{}, big.NewInt(0), bucket, crypto.NewSha256Factory()) - require.EqualError(t, err, fake.Err("failed to store node: failed to set key")) - - inter := NewInteriorNode(0, big.NewInt(0)) - data, err = inter.Serialize(testCtx) - require.NoError(t, err) - - bucket = newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - _, err = node.Prepare([]byte{}, big.NewInt(0), bucket, crypto.NewSha256Factory()) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 1) not in database") -} - -func TestDiskNode_Visit(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - counter := 0 - node.Visit(func(n TreeNode) error { - require.Same(t, node, n) - counter++ - return nil - }) - - require.Equal(t, 1, counter) -} - -func TestDiskNode_Clone(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - clone := node.Clone() - require.Equal(t, node, clone) -} - -func TestDiskNode_Serialize(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - _, err := node.Serialize(testCtx) - require.Error(t, err) -} - -func TestDiskNode_Load(t *testing.T) { - node := NewDiskNode(0, nil, testCtx, NodeFactory{}) - - empty := NewEmptyNode(0, big.NewInt(0)) - data, err := empty.Serialize(testCtx) - require.NoError(t, err) - - bucket := newFakeBucket(node.prepareKey(big.NewInt(0)), data) - - n, err := node.load(big.NewInt(0), bucket) - require.NoError(t, err) - require.IsType(t, empty, n) - - node.factory = fake.NewBadMessageFactory() - _, err = node.load(big.NewInt(0), bucket) - require.EqualError(t, err, fake.Err("failed to deserialize")) - - node.factory = fake.MessageFactory{} - _, err = node.load(big.NewInt(0), bucket) - require.EqualError(t, err, "invalid node of type 'fake.Message'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeBucket struct { - kv.Bucket - values map[string][]byte - errSet error - errScan error -} - -func newFakeBucket(key, value []byte) *fakeBucket { - return &fakeBucket{ - values: map[string][]byte{ - string(key): value, - }, - } -} - -func (b *fakeBucket) Get(key []byte) []byte { - return b.values[string(key)] -} - -func (b *fakeBucket) Set(key, value []byte) error { - if b.values == nil { - b.values = make(map[string][]byte) - } - - b.values[string(key)] = value - return b.errSet -} - -func (b *fakeBucket) Delete(key []byte) error { - return nil -} - -func (b *fakeBucket) Scan(prefix []byte, fn func(k, v []byte) error) error { - for key, value := range b.values { - err := fn([]byte(key), value) - if err != nil { - return err - } - } - - return b.errScan -} diff --git a/dela/core/store/hashtree/binprefix/format.go b/dela/core/store/hashtree/binprefix/format.go deleted file mode 100644 index 6152aa8..0000000 --- a/dela/core/store/hashtree/binprefix/format.go +++ /dev/null @@ -1,123 +0,0 @@ -package binprefix - -import ( - "math/big" - - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// LeafNodeJSON is the JSON representation of a leaf node. -type LeafNodeJSON struct { - Digest []byte - Depth uint16 - Prefix []byte - Value []byte -} - -// InteriorNodeJSON is the JSON representation of an interior node. -type InteriorNodeJSON struct { - Digest []byte - Depth uint16 - Prefix []byte -} - -// EmptyNodeJSON is the JSON representation of an empty node. -type EmptyNodeJSON struct { - Digest []byte - Depth uint16 - Prefix []byte -} - -// NodeJSON is the wrapper around the different types of a tree node. -type NodeJSON struct { - Leaf *LeafNodeJSON `json:",omitempty"` - Interior *InteriorNodeJSON `json:",omitempty"` - Empty *EmptyNodeJSON `json:",omitempty"` -} - -type nodeFormat struct{} - -func (f nodeFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - var m NodeJSON - - switch node := msg.(type) { - case *LeafNode: - leaf := LeafNodeJSON{ - Digest: node.GetHash(), - Depth: node.GetDepth(), - Prefix: node.GetKey(), - Value: node.GetValue(), - } - - m = NodeJSON{Leaf: &leaf} - case *EmptyNode: - empty := EmptyNodeJSON{ - Digest: node.GetHash(), - Depth: node.GetDepth(), - Prefix: node.GetPrefix().Bytes(), - } - - m = NodeJSON{Empty: &empty} - case *InteriorNode: - inter := InteriorNodeJSON{ - Digest: node.GetHash(), - Depth: node.GetDepth(), - Prefix: node.GetPrefix().Bytes(), - } - - m = NodeJSON{Interior: &inter} - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -func (f nodeFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := NodeJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - if m.Leaf != nil { - key := new(big.Int) - key.SetBytes(m.Leaf.Prefix) - - node := NewLeafNodeWithDigest(m.Leaf.Depth, key, m.Leaf.Value, m.Leaf.Digest) - - return node, nil - } - - if m.Empty != nil { - prefix := new(big.Int) - prefix.SetBytes(m.Empty.Prefix) - - node := NewEmptyNodeWithDigest(m.Empty.Depth, prefix, m.Empty.Digest) - - return node, nil - } - - if m.Interior != nil { - factory := ctx.GetFactory(NodeKey{}) - - prefix := new(big.Int) - prefix.SetBytes(m.Interior.Prefix) - - node := NewInteriorNodeWithChildren( - m.Interior.Depth, - prefix, - m.Interior.Digest, - NewDiskNode(m.Interior.Depth+1, nil, ctx, factory), - NewDiskNode(m.Interior.Depth+1, nil, ctx, factory), - ) - - return node, nil - } - - return nil, xerrors.New("message is empty") -} diff --git a/dela/core/store/hashtree/binprefix/path.go b/dela/core/store/hashtree/binprefix/path.go deleted file mode 100644 index bbbb78e..0000000 --- a/dela/core/store/hashtree/binprefix/path.go +++ /dev/null @@ -1,91 +0,0 @@ -// -// Documentation Last Review: 08.10.2020 -// - -package binprefix - -import ( - "math/big" - - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -// Path is a path from the root to a leaf, represented as a series of interior -// nodes hashes. The end of the path is either a leaf with a key holding a -// value, or an empty node. -// -// - implements hashtree.Path -type Path struct { - nonce []byte - key []byte - value []byte - // Root is the root of the hash tree. This value is not serialized and - // reproduced from the leaf and the interior nodes when deserializing. - root []byte - interiors [][]byte -} - -// newPath creates an empty path for the provided key. It must be filled to be -// valid. -func newPath(nonce, key []byte) Path { - return Path{ - nonce: nonce, - key: key, - } -} - -// GetKey implements hashtree.Path. It returns the key associated to the path. -func (s Path) GetKey() []byte { - return s.key -} - -// GetValue implements hashtree.Path. It returns the value pointed by the path. -func (s Path) GetValue() []byte { - return s.value -} - -// GetRoot implements hashtree.Path. It returns the hash of the root node -// calculated from the leaf up to the root. -func (s Path) GetRoot() []byte { - return s.root -} - -func (s Path) computeRoot(fac crypto.HashFactory) ([]byte, error) { - key := new(big.Int) - key.SetBytes(s.key) - - var node TreeNode - if s.value != nil { - node = NewLeafNode(uint16(len(s.interiors)), key, s.value) - } else { - node = NewEmptyNode(uint16(len(s.interiors)), key) - } - - // Reproduce the shortest unique prefix for the key. - prefix := new(big.Int) - for i := 0; i < len(s.interiors); i++ { - prefix.SetBit(prefix, i, key.Bit(i)) - } - - curr, err := node.Prepare(s.nonce, prefix, nil, fac) - if err != nil { - return nil, xerrors.Errorf("while preparing: %v", err) - } - - for i := len(s.interiors) - 1; i >= 0; i-- { - h := fac.New() - - if key.Bit(i) == 0 { - h.Write(curr) - h.Write(s.interiors[i]) - } else { - h.Write(s.interiors[i]) - h.Write(curr) - } - - curr = h.Sum(nil) - } - - return curr, nil -} diff --git a/dela/core/store/hashtree/binprefix/path_test.go b/dela/core/store/hashtree/binprefix/path_test.go deleted file mode 100644 index 462cb15..0000000 --- a/dela/core/store/hashtree/binprefix/path_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package binprefix - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestPath_GetKey(t *testing.T) { - path := newPath([]byte{}, []byte("ping")) - - require.Equal(t, []byte("ping"), path.GetKey()) -} - -func TestPath_GetValue(t *testing.T) { - path := newPath([]byte{}, []byte("ping")) - - require.Nil(t, path.GetValue()) - - path.value = []byte("pong") - require.Equal(t, []byte("pong"), path.GetValue()) -} - -func TestPath_GetRoot(t *testing.T) { - path := newPath([]byte{}, []byte("ping")) - - require.Nil(t, path.GetRoot()) - - path.root = []byte("pong") - require.Equal(t, []byte("pong"), path.GetRoot()) -} - -func TestPath_ComputeRoot(t *testing.T) { - path := newPath([]byte{1, 2, 3}, []byte("A")) - - root, err := path.computeRoot(fake.NewHashFactory(&fake.Hash{})) - require.NoError(t, err) - require.NotEmpty(t, root) - - _, err = path.computeRoot(fake.NewHashFactory(fake.NewBadHash())) - require.EqualError(t, err, fake.Err("while preparing: empty node failed")) -} diff --git a/dela/core/store/hashtree/binprefix/tree.go b/dela/core/store/hashtree/binprefix/tree.go deleted file mode 100644 index 2948996..0000000 --- a/dela/core/store/hashtree/binprefix/tree.go +++ /dev/null @@ -1,860 +0,0 @@ -// -// Documentation Last Review: 08.10.2020 -// - -package binprefix - -import ( - "encoding/binary" - "math" - "math/big" - - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/json" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -func init() { - nodeFormats.Register(serde.FormatJSON, nodeFormat{}) -} - -// Nonce is the type of the tree nonce. -type Nonce [8]byte - -const ( - // DepthLength is the length in bytes of the binary representation of the - // depth. - DepthLength = 2 - - // MaxDepth is the maximum depth the tree should reach. It is equivalent to - // the maximum key length in bytes. - MaxDepth = 32 -) - -const ( - emptyNodeType byte = iota - interiorNodeType - leafNodeType - diskNodeType -) - -var nodeFormats = registry.NewSimpleRegistry() - -// TreeNode is the interface for the different types of nodes that a Merkle tree -// could have. -type TreeNode interface { - serde.Message - - GetHash() []byte - - GetType() byte - - Search(key *big.Int, path *Path, bucket kv.Bucket) ([]byte, error) - - Insert(key *big.Int, value []byte, bucket kv.Bucket) (TreeNode, error) - - Delete(key *big.Int, bucket kv.Bucket) (TreeNode, error) - - Prepare(nonce []byte, prefix *big.Int, bucket kv.Bucket, fac crypto.HashFactory) ([]byte, error) - - Visit(func(node TreeNode) error) error - - Clone() TreeNode -} - -// Tree is an implementation of a Merkle binary prefix tree. Due to the -// structure of the tree, any prefix of a longer prefix is overridden which -// means that the key should have the same length. -// -// Mutable operations on the tree don't update the hash root. It can be done -// after a batch of operations or a single one by using the CalculateRoot -// function. -type Tree struct { - nonce Nonce - maxDepth int - memDepth int - root TreeNode - context serde.Context - factory serde.Factory -} - -// NewTree creates a new empty tree. -func NewTree(nonce Nonce) *Tree { - return &Tree{ - nonce: nonce, - maxDepth: MaxDepth, - memDepth: MaxDepth * 8, - root: NewEmptyNode(0, big.NewInt(0)), - factory: NodeFactory{}, - context: json.NewContext(), - } -} - -// Len returns the number of leaves in the tree. -func (t *Tree) Len() int { - counter := 0 - - t.root.Visit(func(n TreeNode) error { - if n.GetType() == leafNodeType { - counter++ - } - - return nil - }) - - return counter -} - -// FillFromBucket scans the bucket for leafs to insert them in the tree. It will -// then persist the tree to restore the memory depth limit. -func (t *Tree) FillFromBucket(bucket kv.Bucket) error { - if bucket == nil { - return nil - } - - t.root = NewInteriorNode(0, big.NewInt(0)) - - err := bucket.Scan([]byte{}, func(key, value []byte) error { - msg, err := t.factory.Deserialize(t.context, value) - if err != nil { - return xerrors.Errorf("tree node malformed: %v", err) - } - - var diskNode *DiskNode - var prefix *big.Int - - switch node := msg.(type) { - case *InteriorNode: - diskNode = NewDiskNode(node.depth, node.hash, t.context, t.factory) - prefix = node.prefix - case *EmptyNode: - diskNode = NewDiskNode(node.depth, node.hash, t.context, t.factory) - prefix = node.prefix - case *LeafNode: - diskNode = NewDiskNode(node.depth, node.hash, t.context, t.factory) - prefix = node.key - } - - t.restore(t.root, prefix, diskNode) - - return nil - }) - - if err != nil { - return xerrors.Errorf("while scanning: %v", err) - } - - return nil -} - -// Restore is a recursive function that will append the node to the tree by -// recreating the interior nodes from the root to its natural position defined -// by the prefix. -func (t *Tree) restore(curr TreeNode, prefix *big.Int, node *DiskNode) TreeNode { - var interior *InteriorNode - - switch n := curr.(type) { - case *InteriorNode: - interior = n - case *DiskNode: - return curr - case *EmptyNode: - if node.depth == n.depth { - return node - } - - interior = NewInteriorNode(n.depth, n.prefix) - } - - if prefix.Bit(int(interior.depth)) == 0 { - interior.left = t.restore(interior.left, prefix, node) - } else { - interior.right = t.restore(interior.right, prefix, node) - } - - return interior -} - -// Search returns the value associated to the key if it exists, otherwise nil. -// When path is defined, it will be filled with the interior nodes and the leaf -// node so that it can prove the inclusion or the absence of the key. -func (t *Tree) Search(key []byte, path *Path, b kv.Bucket) ([]byte, error) { - if len(key) > t.maxDepth { - return nil, xerrors.Errorf("mismatch key length %d > %d", len(key), t.maxDepth) - } - - value, err := t.root.Search(makeKey(key), path, b) - if err != nil { - return nil, xerrors.Errorf("failed to search: %v", err) - } - - if path != nil { - path.root = t.root.GetHash() - } - - return value, nil -} - -// Insert inserts the key in the tree. -func (t *Tree) Insert(key, value []byte, b kv.Bucket) error { - if len(key) > t.maxDepth { - return xerrors.Errorf("mismatch key length %d > %d", len(key), t.maxDepth) - } - - var err error - t.root, err = t.root.Insert(makeKey(key), value, b) - if err != nil { - return xerrors.Errorf("failed to insert: %v", err) - } - - return nil -} - -// Delete removes a key from the tree. -func (t *Tree) Delete(key []byte, b kv.Bucket) error { - if len(key) > t.maxDepth { - return xerrors.Errorf("mismatch key length %d > %d", len(key), t.maxDepth) - } - - var err error - t.root, err = t.root.Delete(makeKey(key), b) - if err != nil { - return xerrors.Errorf("failed to delete: %v", err) - } - - return nil -} - -// CalculateRoot updates the hashes of the tree. -func (t *Tree) CalculateRoot(fac crypto.HashFactory, b kv.Bucket) error { - prefix := new(big.Int) - - _, err := t.root.Prepare(t.nonce[:], prefix, b, fac) - if err != nil { - return xerrors.Errorf("failed to prepare: %v", err) - } - - return nil -} - -// Persist visits the whole tree and stores the leaf node in the database and -// replaces the node with disk nodes. Depending of the parameter, it also stores -// intermediate nodes on the disk. -func (t *Tree) Persist(b kv.Bucket) error { - return t.root.Visit(func(n TreeNode) error { - switch node := n.(type) { - case *InteriorNode: - if int(node.depth) > t.memDepth { - return t.toDisk(node.depth, node.prefix, node, b, false, true) - } - - _, ok := node.left.(*LeafNode) - if ok || int(node.depth) == t.memDepth { - node.left = NewDiskNode(node.depth+1, node.left.GetHash(), t.context, t.factory) - } - - _, ok = node.right.(*LeafNode) - if ok || int(node.depth) == t.memDepth { - node.right = NewDiskNode(node.depth+1, node.right.GetHash(), t.context, t.factory) - } - case *LeafNode: - return t.toDisk(node.depth, node.key, node, b, true, true) - case *EmptyNode: - shouldStore := int(node.depth) >= t.memDepth - - return t.toDisk(node.depth, node.prefix, node, b, true, shouldStore) - } - - return nil - }) -} - -func (t *Tree) toDisk(depth uint16, prefix *big.Int, node TreeNode, b kv.Bucket, clean, store bool) error { - disknode := NewDiskNode(depth, nil, t.context, t.factory) - - if clean { - err := disknode.cleanSubtree(depth, prefix, b) - if err != nil { - return xerrors.Errorf("failed to clean subtree: %v", err) - } - } - - if store { - err := disknode.store(prefix, node, b) - if err != nil { - return xerrors.Errorf("failed to store node: %v", err) - } - } - - return nil -} - -// Clone returns a deep copy of the tree. -func (t *Tree) Clone() *Tree { - return &Tree{ - nonce: t.nonce, - maxDepth: t.maxDepth, - memDepth: t.memDepth, - root: t.root.Clone(), - context: t.context, - factory: t.factory, - } -} - -// EmptyNode is leaf node with no value. -// -// - implements binprefix.TreeNode -type EmptyNode struct { - depth uint16 - prefix *big.Int - hash []byte -} - -// NewEmptyNode creates a new empty node. -func NewEmptyNode(depth uint16, key *big.Int) *EmptyNode { - return NewEmptyNodeWithDigest(depth, key, nil) -} - -// NewEmptyNodeWithDigest creates a new empty node with its digest. -func NewEmptyNodeWithDigest(depth uint16, key *big.Int, hash []byte) *EmptyNode { - return &EmptyNode{ - depth: depth, - prefix: key, - hash: hash, - } -} - -// GetHash implements binprefix.TreeNode. It returns the hash of the node. -func (n *EmptyNode) GetHash() []byte { - return append([]byte{}, n.hash...) -} - -// GetType implements binprefix.TreeNode. It returns the empty node type. -func (n *EmptyNode) GetType() byte { - return emptyNodeType -} - -// GetDepth returns the depth of the node. -func (n *EmptyNode) GetDepth() uint16 { - return n.depth -} - -// GetPrefix returns the prefix of the node. -func (n *EmptyNode) GetPrefix() *big.Int { - return n.prefix -} - -// Search implements binprefix.TreeNode. It always return a empty value. -func (n *EmptyNode) Search(key *big.Int, path *Path, b kv.Bucket) ([]byte, error) { - if path != nil { - path.value = nil - } - - return nil, nil -} - -// Insert implements binprefix.TreeNode. It replaces the empty node by a leaf -// node that contains the key and the value. -func (n *EmptyNode) Insert(key *big.Int, value []byte, b kv.Bucket) (TreeNode, error) { - return NewLeafNode(n.depth, key, value), nil -} - -// Delete implements binprefix.TreeNode. It ignores the delete as an empty node -// already means the key is missing. -func (n *EmptyNode) Delete(key *big.Int, b kv.Bucket) (TreeNode, error) { - return n, nil -} - -// Prepare implements binprefix.TreeNode. It updates the hash of the node if not -// already set and returns the digest. -func (n *EmptyNode) Prepare(nonce []byte, - prefix *big.Int, b kv.Bucket, fac crypto.HashFactory) ([]byte, error) { - - if len(n.hash) > 0 { - // Hash is already calculated so we can skip and return. - return n.hash, nil - } - - h := fac.New() - - data := make([]byte, 1+len(nonce)+bilen(prefix)+DepthLength) - cursor := 1 - data[0] = emptyNodeType - copy(data[cursor:], nonce) - cursor += len(nonce) - copy(data[cursor:], prefix.Bytes()) - cursor += bilen(prefix) - copy(data[cursor:], int2buffer(n.depth)) - - _, err := h.Write(data) - if err != nil { - return nil, xerrors.Errorf("empty node failed: %v", err) - } - - n.hash = h.Sum(nil) - - return n.GetHash(), nil -} - -// Visit implements binprefix.TreeNode. It executes the callback with the node. -func (n *EmptyNode) Visit(fn func(node TreeNode) error) error { - err := fn(n) - if err != nil { - return xerrors.Errorf("visiting empty: %v", err) - } - - return nil -} - -// Clone implements binprefix.TreeNode. It returns a deep copy of the empty -// node. -func (n *EmptyNode) Clone() TreeNode { - return NewEmptyNode(n.depth, n.prefix) -} - -// Serialize implements serde.Message. It returns the JSON data of the empty -// node. -func (n *EmptyNode) Serialize(ctx serde.Context) ([]byte, error) { - format := nodeFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, n) - if err != nil { - return nil, xerrors.Errorf("failed to encode empty node: %v", err) - } - - return data, nil -} - -// InteriorNode is a node with two children. -// -// - implements binprefix.TreeNode -type InteriorNode struct { - hash []byte - depth uint16 - prefix *big.Int - left TreeNode - right TreeNode -} - -// NewInteriorNode creates a new interior node with two empty nodes as children. -func NewInteriorNode(depth uint16, prefix *big.Int) *InteriorNode { - return NewInteriorNodeWithChildren( - depth, - prefix, - nil, - NewEmptyNode(depth+1, new(big.Int).SetBit(prefix, int(depth), 0)), - NewEmptyNode(depth+1, new(big.Int).SetBit(prefix, int(depth), 1)), - ) -} - -// NewInteriorNodeWithChildren creates a new interior node with the two given -// children. -func NewInteriorNodeWithChildren(depth uint16, prefix *big.Int, hash []byte, left, right TreeNode) *InteriorNode { - return &InteriorNode{ - depth: depth, - prefix: prefix, - hash: hash, - left: left, - right: right, - } -} - -// GetHash implements binprefix.TreeNode. It returns the hash of the node. -func (n *InteriorNode) GetHash() []byte { - return append([]byte{}, n.hash...) -} - -// GetType implements binprefix.TreeNode. It returns the interior node type. -func (n *InteriorNode) GetType() byte { - return interiorNodeType -} - -// GetDepth returns the depth of the node. -func (n *InteriorNode) GetDepth() uint16 { - return n.depth -} - -// GetPrefix returns the prefix of the node. -func (n *InteriorNode) GetPrefix() *big.Int { - return n.prefix -} - -// Search implements binprefix.TreeNode. It recursively search for the value in -// the correct child. -func (n *InteriorNode) Search(key *big.Int, path *Path, b kv.Bucket) ([]byte, error) { - if key.Bit(int(n.depth)) == 0 { - n.right, _ = n.load(n.right, key, 1, b) - - if path != nil { - path.interiors = append(path.interiors, n.right.GetHash()) - } - - return n.left.Search(key, path, b) - } - - n.left, _ = n.load(n.left, key, 0, b) - - if path != nil { - path.interiors = append(path.interiors, n.left.GetHash()) - } - - return n.right.Search(key, path, b) -} - -// Insert implements binprefix.TreeNode. It inserts the key/value pair to the -// right path. -func (n *InteriorNode) Insert(key *big.Int, value []byte, b kv.Bucket) (TreeNode, error) { - var err error - if key.Bit(int(n.depth)) == 0 { - n.left, err = n.left.Insert(key, value, b) - } else { - n.right, err = n.right.Insert(key, value, b) - } - - // Reset the hash as the subtree will change and thus invalidate this - // current value. - n.hash = nil - - return n, err -} - -// Delete implements binprefix.TreeNode. It deletes the leaf node associated to -// the key if it exists, otherwise nothing will change. -func (n *InteriorNode) Delete(key *big.Int, b kv.Bucket) (TreeNode, error) { - var err error - - // Depending on the path to follow, it will delete the key from the correct - // path and it will load the first node of the opposite path if it is a disk - // node so that the type can be compared. Errors are not wrapper to prevent - // very long error message. - if key.Bit(int(n.depth)) == 0 { - n.left, err = n.left.Delete(key, b) - if err != nil { - return nil, err - } - - n.right, err = n.load(n.right, key, 1, b) - if err != nil { - return nil, err - } - } else { - n.right, err = n.right.Delete(key, b) - if err != nil { - return nil, err - } - - n.left, err = n.load(n.left, key, 0, b) - if err != nil { - return nil, err - } - } - - if n.left.GetType() == emptyNodeType && n.right.GetType() == emptyNodeType { - // If an interior node points to two empty nodes, it is itself an empty - // one. - return NewEmptyNode(n.depth, n.prefix), nil - } - - return n, nil -} - -func (n *InteriorNode) load(node TreeNode, key *big.Int, bit uint, b kv.Bucket) (TreeNode, error) { - diskn, ok := node.(*DiskNode) - if !ok { - return node, nil - } - - node, err := diskn.load(new(big.Int).SetBit(key, int(n.depth), bit), b) - if err != nil { - return nil, xerrors.Errorf("failed to load node: %v", err) - } - - return node, nil -} - -// Prepare implements binprefix.TreeNode. It updates the hash of the node if not -// already set and returns the digest. -func (n *InteriorNode) Prepare(nonce []byte, - prefix *big.Int, b kv.Bucket, fac crypto.HashFactory) ([]byte, error) { - - if len(n.hash) > 0 { - // Hash is already calculated so we can skip and return. - return n.hash, nil - } - - h := fac.New() - - left, err := n.left.Prepare(nonce, new(big.Int).SetBit(prefix, int(n.depth), 0), b, fac) - if err != nil { - // No wrapping to prevent recursive calls to create huge error messages. - return nil, err - } - - right, err := n.right.Prepare(nonce, new(big.Int).SetBit(prefix, int(n.depth), 1), b, fac) - if err != nil { - // No wrapping to prevent recursive calls to create huge error messages. - return nil, err - } - - _, err = h.Write(append(left, right...)) - if err != nil { - return nil, xerrors.Errorf("interior node failed: %v", err) - } - - n.hash = h.Sum(nil) - - return n.GetHash(), nil -} - -// Visit implements binprefix.TreeNode. It executes the callback with the node -// and recursively with the children. -func (n *InteriorNode) Visit(fn func(TreeNode) error) error { - err := n.left.Visit(fn) - if err != nil { - // No wrapping to prevent long error message from recursive calls. - return err - } - - err = n.right.Visit(fn) - if err != nil { - // No wrapping to prevent long error message from recursive calls. - return err - } - - err = fn(n) - if err != nil { - return xerrors.Errorf("visiting interior: %v", err) - } - - return nil -} - -// Clone implements binprefix.TreeNode. It returns a deep copy of the interior -// node. -func (n *InteriorNode) Clone() TreeNode { - return &InteriorNode{ - depth: n.depth, - prefix: n.prefix, - left: n.left.Clone(), - right: n.right.Clone(), - } -} - -// Serialize implements serde.Message. It returns the JSON data of the interior -// node. -func (n *InteriorNode) Serialize(ctx serde.Context) ([]byte, error) { - format := nodeFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, n) - if err != nil { - return nil, xerrors.Errorf("failed to encode interior node: %v", err) - } - - return data, nil -} - -// LeafNode is a leaf node with a key and a value. -// -// - implements binprefix.TreeNode -type LeafNode struct { - hash []byte - depth uint16 - key *big.Int - value []byte -} - -// NewLeafNode creates a new leaf node. -func NewLeafNode(depth uint16, key *big.Int, value []byte) *LeafNode { - return NewLeafNodeWithDigest(depth, key, value, nil) -} - -// NewLeafNodeWithDigest creates a new leaf node with its digest. -func NewLeafNodeWithDigest(depth uint16, key *big.Int, value, hash []byte) *LeafNode { - return &LeafNode{ - hash: hash, - depth: depth, - key: key, - value: value, - } -} - -// GetHash implements binprefix.TreeNode. It returns the hash of the node. -func (n *LeafNode) GetHash() []byte { - return append([]byte{}, n.hash...) -} - -// GetDepth returns the depth of the node. -func (n *LeafNode) GetDepth() uint16 { - return n.depth -} - -// GetKey returns the key of the leaf node. -func (n *LeafNode) GetKey() []byte { - return n.key.Bytes() -} - -// GetValue returns the value of the leaf node. -func (n *LeafNode) GetValue() []byte { - return n.value -} - -// GetType implements binprefix.TreeNode. It returns the leaf node type. -func (n *LeafNode) GetType() byte { - return leafNodeType -} - -// Search implements binprefix.TreeNode. It returns the value if the key -// matches. -func (n *LeafNode) Search(key *big.Int, path *Path, b kv.Bucket) ([]byte, error) { - if path != nil { - path.value = n.value - } - - if n.key.Cmp(key) == 0 { - return n.value, nil - } - - return nil, nil -} - -// Insert implements binprefix.TreeNode. It replaces the leaf node by an -// interior node that contains both the current pair and the new one to insert. -func (n *LeafNode) Insert(key *big.Int, value []byte, b kv.Bucket) (TreeNode, error) { - if n.key.Cmp(key) == 0 { - n.hash = nil - n.value = value - return n, nil - } - - prefix := new(big.Int) - for i := 0; i < int(n.depth); i++ { - prefix.SetBit(prefix, i, key.Bit(i)) - } - - node := NewInteriorNode(n.depth, prefix) - - // As the node is freshly created, the operations are in-memory and thus it - // doesn't trigger any error. - node.Insert(n.key, n.value, b) - node.Insert(key, value, b) - - return node, nil -} - -// Delete implements binprefix.TreeNode. It removes the leaf if the key matches. -func (n *LeafNode) Delete(key *big.Int, b kv.Bucket) (TreeNode, error) { - if n.key.Cmp(key) == 0 { - return NewEmptyNode(n.depth, key), nil - } - - return n, nil -} - -// Prepare implements binprefix.TreeNode. It updates the hash of the node if not -// already set and returns the digest. -func (n *LeafNode) Prepare(nonce []byte, - prefix *big.Int, b kv.Bucket, fac crypto.HashFactory) ([]byte, error) { - - if len(n.hash) > 0 { - // Hash is already calculated so we can skip and return. - return n.hash, nil - } - - h := fac.New() - - data := make([]byte, 1+len(nonce)+DepthLength+bilen(prefix)+bilen(n.key)+len(n.value)) - data[0] = leafNodeType - cursor := 1 - copy(data[cursor:], nonce) - cursor += len(nonce) - copy(data[cursor:], int2buffer(n.depth)) - cursor += DepthLength - copy(data[cursor:], prefix.Bytes()) - cursor += bilen(prefix) - copy(data[cursor:], n.key.Bytes()) - cursor += bilen(n.key) - copy(data[cursor:], n.value) - - _, err := h.Write(data) - if err != nil { - return nil, xerrors.Errorf("leaf node failed: %v", err) - } - - n.hash = h.Sum(nil) - - return n.GetHash(), nil -} - -// Visit implements binprefix.TreeNode. It executes the callback with the node. -func (n *LeafNode) Visit(fn func(TreeNode) error) error { - err := fn(n) - if err != nil { - return xerrors.Errorf("visiting leaf: %v", err) - } - - return nil -} - -// Clone implements binprefix.TreeNode. It returns a copy of the leaf node. -func (n *LeafNode) Clone() TreeNode { - return NewLeafNode(n.depth, n.key, n.value) -} - -// Serialize implements serde.Message. It returns the JSON data of the leaf -// node. -func (n *LeafNode) Serialize(ctx serde.Context) ([]byte, error) { - format := nodeFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, n) - if err != nil { - return nil, xerrors.Errorf("failed to encode leaf node: %v", err) - } - - return data, nil -} - -// NodeKey is the key for the node factory. -type NodeKey struct{} - -// NodeFactory is the factory to deserialize tree nodes. -// -// - implements serde.Factory -type NodeFactory struct{} - -// Deserialize implements serde.Factory. It populates the tree node associated -// to the data if appropriate, otherwise it returns an error. -func (f NodeFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := nodeFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, NodeKey{}, f) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("format failed: %v", err) - } - - return msg, nil -} - -func int2buffer(depth uint16) []byte { - buffer := make([]byte, 2) - binary.LittleEndian.PutUint16(buffer, depth) - - return buffer -} - -// makeKey is a helper to transform a key in bytes to its big number -// equivalence. -func makeKey(key []byte) *big.Int { - bi := new(big.Int) - bi.SetBytes(key) - - return bi -} - -func bilen(n *big.Int) int { - return int(math.Ceil(float64(n.BitLen()) / 8.0)) -} diff --git a/dela/core/store/hashtree/binprefix/tree_test.go b/dela/core/store/hashtree/binprefix/tree_test.go deleted file mode 100644 index 05c02b4..0000000 --- a/dela/core/store/hashtree/binprefix/tree_test.go +++ /dev/null @@ -1,702 +0,0 @@ -package binprefix - -import ( - "math" - "math/big" - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/store/kv" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "golang.org/x/xerrors" -) - -func init() { - nodeFormats.Register(fake.BadFormat, fake.NewBadFormat()) -} - -func TestTree_Len(t *testing.T) { - tree := NewTree(Nonce{}) - require.Equal(t, 0, tree.Len()) - - tree.root = NewInteriorNode(0, big.NewInt(0)) - require.Equal(t, 0, tree.Len()) - - tree.root.(*InteriorNode).left = NewLeafNode(1, nil, nil) - require.Equal(t, 1, tree.Len()) - - tree.root.(*InteriorNode).right = NewLeafNode(1, nil, nil) - require.Equal(t, 2, tree.Len()) -} - -func TestTree_Load(t *testing.T) { - tree := NewTree(Nonce{}) - - bucket := makeBucket(t) - - err := tree.FillFromBucket(bucket) - require.NoError(t, err) - - tree.factory = fake.NewBadMessageFactory() - err = tree.FillFromBucket(bucket) - require.EqualError(t, err, fake.Err("while scanning: tree node malformed")) -} - -func TestTree_Search(t *testing.T) { - tree := NewTree(Nonce{}) - - value, err := tree.Search(nil, nil, &fakeBucket{}) - require.NoError(t, err) - require.Nil(t, value) - - tree.root = NewLeafNode(0, makeKey([]byte("A")), []byte("B")) - value, err = tree.Search([]byte("A"), nil, &fakeBucket{}) - require.NoError(t, err) - require.Equal(t, []byte("B"), value) - - _, err = tree.Search(make([]byte, MaxDepth+1), nil, &fakeBucket{}) - require.EqualError(t, err, "mismatch key length 33 > 32") - - tree.root = fakeNode{err: fake.GetError()} - _, err = tree.Search([]byte("A"), nil, nil) - require.EqualError(t, err, fake.Err("failed to search")) -} - -func TestTree_Insert(t *testing.T) { - tree := NewTree(Nonce{}) - - err := tree.Insert([]byte("ping"), []byte("pong"), &fakeBucket{}) - require.NoError(t, err) - require.Equal(t, 1, tree.Len()) - - err = tree.Insert(make([]byte, MaxDepth+1), nil, &fakeBucket{}) - require.EqualError(t, err, "mismatch key length 33 > 32") - - tree.root = fakeNode{err: fake.GetError()} - err = tree.Insert([]byte("A"), []byte("B"), nil) - require.EqualError(t, err, fake.Err("failed to insert")) -} - -func TestTree_Insert_UpdateLeaf(t *testing.T) { - bucket := &fakeBucket{} - hashFactory := crypto.NewSha256Factory() - tree := NewTree(Nonce{}) - - for i := 0; i <= math.MaxUint8; i++ { - key := []byte{byte(i)} - err := tree.Insert(key[:], key[:], bucket) - require.NoError(t, err) - } - - err := tree.CalculateRoot(hashFactory, bucket) - require.NoError(t, err) - - updateKey := makeKey([]byte{byte(math.MaxUint8 - 1)}) - - var existingHash []byte - tree.root.Visit(func(node TreeNode) error { - lnode, ok := node.(*LeafNode) - if ok && lnode.key.Cmp(updateKey) == 0 { - existingHash = lnode.GetHash() - } - - return nil - }) - - require.NotEmpty(t, existingHash) - - err = tree.Persist(bucket) - require.NoError(t, err) - - clone := tree.Clone() - - err = clone.Insert([]byte{byte(math.MaxUint8 - 1)}, []byte{0}, bucket) - require.NoError(t, err) - - err = clone.CalculateRoot(hashFactory, bucket) - require.NoError(t, err) - - var updatedHash []byte - clone.root.Visit(func(node TreeNode) error { - lnode, ok := node.(*LeafNode) - if ok && lnode.key.Cmp(updateKey) == 0 { - updatedHash = lnode.GetHash() - } - return nil - }) - - require.NotEmpty(t, updatedHash) - require.NotEqual(t, existingHash, updatedHash) -} - -func TestTree_Delete(t *testing.T) { - tree := NewTree(Nonce{}) - - err := tree.Delete(nil, &fakeBucket{}) - require.NoError(t, err) - - err = tree.Delete([]byte("ping"), &fakeBucket{}) - require.NoError(t, err) - - tree.root = NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - err = tree.Delete([]byte("ping"), &fakeBucket{}) - require.NoError(t, err) - require.IsType(t, (*EmptyNode)(nil), tree.root) - - err = tree.Delete(make([]byte, MaxDepth+1), &fakeBucket{}) - require.EqualError(t, err, "mismatch key length 33 > 32") - - tree.root = fakeNode{err: fake.GetError()} - err = tree.Delete([]byte("A"), nil) - require.EqualError(t, err, fake.Err("failed to delete")) -} - -func TestTree_Persist(t *testing.T) { - bucket := &fakeBucket{} - - tree := NewTree(Nonce{}) - - // 1. Test if the tree can be stored in disk with the default parameter to - // only store leaf nodes. - for i := 0; i <= math.MaxUint8; i++ { - key := []byte{byte(i)} - - err := tree.Insert(key[:], key[:], bucket) - require.NoError(t, err) - } - - err := tree.Persist(bucket) - require.NoError(t, err) - - count := 0 - for _, value := range bucket.values { - require.Regexp(t, `{"Leaf":{[^}]+}}`, string(value)) - count++ - } - require.Equal(t, 256, count) - - // 2. Test with a memory depth of 6 which means that only disk nodes should - // exist after this level. - tree.memDepth = 6 - err = tree.Persist(bucket) - require.NoError(t, err) - - tree.root.Visit(func(n TreeNode) error { - switch node := n.(type) { - case *InteriorNode: - require.LessOrEqual(t, int(node.depth), 6) - case *EmptyNode: - require.LessOrEqual(t, int(node.depth), 6) - case *LeafNode: - t.Fatal("no leaf node expected in-memory") - } - return nil - }) - - count = 0 - for _, value := range bucket.values { - require.Regexp(t, `{"(Leaf|Interior)":{[^}]+}}`, string(value)) - count++ - } - // 2^9-1 - (2^7-1) = 384 - require.Equal(t, 384, count) - - // 3. Test with only the root in-memory. - tree.memDepth = 0 - err = tree.Persist(bucket) - require.NoError(t, err) - // 2^9-1 = 511 (root is not on disk) - require.Equal(t, 510, len(bucket.values)) - - tree.root.Visit(func(n TreeNode) error { - switch node := n.(type) { - case *InteriorNode: - // Only the root is allow as an interior node. - require.Equal(t, uint16(0), node.depth) - case *EmptyNode: - t.Fatal("expect only disk nodes") - case *LeafNode: - t.Fatal("expect only disk nodes") - } - return nil - }) - - tree.root = NewInteriorNode(0, big.NewInt(0)) - - err = tree.Persist(&fakeBucket{errSet: fake.GetError()}) - require.EqualError(t, err, - fake.Err("visiting empty: failed to store node: failed to set key")) - - err = tree.Persist(&fakeBucket{errScan: fake.GetError()}) - require.EqualError(t, err, - fake.Err("visiting empty: failed to clean subtree")) -} - -func TestTree_Clone(t *testing.T) { - tree := NewTree(Nonce{}) - - f := func(key [8]byte, value []byte) bool { - return tree.Insert(key[:], value, &fakeBucket{}) == nil - } - - err := quick.Check(f, nil) - require.NoError(t, err) - - clone := tree.Clone() - require.Equal(t, tree.Len(), clone.Len()) - - require.NoError(t, tree.CalculateRoot(crypto.NewSha256Factory(), &fakeBucket{})) - require.NoError(t, clone.CalculateRoot(crypto.NewSha256Factory(), &fakeBucket{})) - require.Equal(t, tree.root.GetHash(), clone.root.GetHash()) -} - -func TestEmptyNode_GetHash(t *testing.T) { - node := NewEmptyNode(0, big.NewInt(0)) - require.Empty(t, node.GetHash()) - - node.hash = []byte("ping") - require.Equal(t, []byte("ping"), node.GetHash()) -} - -func TestEmptyNode_GetType(t *testing.T) { - node := NewEmptyNode(0, big.NewInt(0)) - require.Equal(t, emptyNodeType, node.GetType()) -} - -func TestEmptyNode_Search(t *testing.T) { - node := NewEmptyNode(0, big.NewInt(0)) - path := newPath([]byte{}, nil) - - value, err := node.Search(new(big.Int), &path, nil) - require.NoError(t, err) - require.Nil(t, value) - require.Nil(t, path.value) -} - -func TestEmptyNode_Insert(t *testing.T) { - node := NewEmptyNode(0, big.NewInt(0)) - - next, err := node.Insert(big.NewInt(0), []byte("pong"), nil) - require.NoError(t, err) - require.IsType(t, (*LeafNode)(nil), next) -} - -func TestEmptyNode_Delete(t *testing.T) { - node := NewEmptyNode(0, big.NewInt(0)) - - next, err := node.Delete(nil, nil) - require.NoError(t, err) - require.Same(t, node, next) -} - -func TestEmptyNode_Prepare(t *testing.T) { - node := NewEmptyNode(3, big.NewInt(0)) - - fac := crypto.NewSha256Factory() - - hash, err := node.Prepare([]byte("nonce"), new(big.Int), nil, fac) - require.NoError(t, err) - require.Equal(t, hash, node.hash) - - calls := &fake.Call{} - node.hash = nil - _, err = node.Prepare([]byte{1}, new(big.Int).SetBytes([]byte{2}), nil, fake.NewHashFactory(&fake.Hash{Call: calls})) - require.NoError(t, err) - require.Equal(t, 1, calls.Len()) - require.Equal(t, "\x00\x01\x02\x03\x00", string(calls.Get(0, 0).([]byte))) - - node.hash = nil - _, err = node.Prepare([]byte{1}, new(big.Int), nil, fake.NewHashFactory(fake.NewBadHash())) - require.EqualError(t, err, fake.Err("empty node failed")) -} - -func TestEmptyNode_Visit(t *testing.T) { - node := NewEmptyNode(3, big.NewInt(0)) - count := 0 - - node.Visit(func(tn TreeNode) error { - require.Same(t, node, tn) - count++ - - return nil - }) - - require.Equal(t, 1, count) -} - -func TestEmptyNode_Clone(t *testing.T) { - node := NewEmptyNode(3, big.NewInt(0)) - - clone := node.Clone() - require.Equal(t, node, clone) -} - -func TestEmptyNode_Serialize(t *testing.T) { - node := NewEmptyNode(3, big.NewInt(1)) - - data, err := node.Serialize(testCtx) - require.Equal(t, `{"Empty":{"Digest":"","Depth":3,"Prefix":"AQ=="}}`, string(data)) - require.NoError(t, err) - - _, err = node.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("failed to encode empty node")) -} - -func TestInteriorNode_GetHash(t *testing.T) { - node := NewInteriorNode(3, big.NewInt(0)) - - require.Empty(t, node.GetHash()) - - node.hash = []byte{1, 2, 3} - require.Equal(t, []byte{1, 2, 3}, node.GetHash()) -} - -func TestInteriorNode_GetType(t *testing.T) { - node := NewInteriorNode(3, big.NewInt(0)) - - require.Equal(t, interiorNodeType, node.GetType()) -} - -func TestInteriorNode_Search(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - node.left = NewLeafNode(1, big.NewInt(0), []byte("ping")) - node.right = NewLeafNode(1, big.NewInt(1), []byte("pong")) - - path := newPath([]byte{}, nil) - value, err := node.Search(big.NewInt(0), &path, nil) - require.NoError(t, err) - require.Equal(t, "ping", string(value)) - require.Len(t, path.interiors, 1) - - path = newPath([]byte{}, nil) - value, err = node.Search(big.NewInt(1), &path, nil) - require.NoError(t, err) - require.Equal(t, "pong", string(value)) - require.Len(t, path.interiors, 1) -} - -func TestInteriorNode_Search_Error(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - node.left = NewDiskNode(0, nil, testCtx, NodeFactory{}) - - _, err := node.Search(big.NewInt(0), nil, &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 0) not in database") - - node.left = nil - node.right = NewDiskNode(0, nil, testCtx, NodeFactory{}) - - _, err = node.Search(big.NewInt(1), nil, &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 1 (depth 0) not in database") -} - -func TestInteriorNode_Insert(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - - next, err := node.Insert(big.NewInt(0), []byte("ping"), nil) - require.NoError(t, err) - require.Same(t, node, next) - - next, err = node.Insert(big.NewInt(1), []byte("pong"), nil) - require.NoError(t, err) - require.Same(t, node, next) -} - -func TestInteriorNode_Delete(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - node.left = NewLeafNode(1, big.NewInt(0), []byte("ping")) - node.right = NewLeafNode(1, big.NewInt(1), []byte("pong")) - - next, err := node.Delete(big.NewInt(0), nil) - require.NoError(t, err) - require.Same(t, node, next) - - next, err = node.Delete(big.NewInt(1), nil) - require.NoError(t, err) - require.IsType(t, (*EmptyNode)(nil), next) - - node.right = NewDiskNode(1, nil, testCtx, NodeFactory{}) - _, err = node.Delete(big.NewInt(0), &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 1 (depth 1) not in database") - - node.right = NewEmptyNode(1, big.NewInt(2)) - node.left = NewDiskNode(1, nil, testCtx, NodeFactory{}) - _, err = node.Delete(big.NewInt(1), &fakeBucket{}) - require.EqualError(t, err, "failed to load node: prefix 0 (depth 1) not in database") -} - -func TestInteriorNode_Prepare(t *testing.T) { - node := NewInteriorNode(1, big.NewInt(0)) - node.left = fakeNode{data: []byte{0xaa}} - node.right = fakeNode{data: []byte{0xbb}} - calls := &fake.Call{} - - _, err := node.Prepare([]byte{1}, big.NewInt(1), nil, fake.NewHashFactory(&fake.Hash{Call: calls})) - require.NoError(t, err) - require.Equal(t, 1, calls.Len()) - require.Equal(t, "\xaa\xbb", string(calls.Get(0, 0).([]byte))) - - node.hash = nil - node.left = fakeNode{err: xerrors.New("bad node error")} - _, err = node.Prepare([]byte{1}, big.NewInt(2), nil, crypto.NewSha256Factory()) - require.EqualError(t, err, "bad node error") - - node.left = fakeNode{} - node.right = fakeNode{err: xerrors.New("bad node error")} - _, err = node.Prepare([]byte{1}, big.NewInt(2), nil, crypto.NewSha256Factory()) - require.EqualError(t, err, "bad node error") - - node.right = fakeNode{} - _, err = node.Prepare([]byte{1}, big.NewInt(2), nil, fake.NewHashFactory(fake.NewBadHash())) - require.EqualError(t, err, fake.Err("interior node failed")) -} - -func TestInteriorNode_Visit(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - - counter := 0 - err := node.Visit(func(n TreeNode) error { - if counter == 2 { - require.IsType(t, node, n) - } else { - require.IsType(t, (*EmptyNode)(nil), n) - } - counter++ - - return nil - }) - require.NoError(t, err) - require.Equal(t, 3, counter) - - node.left = fakeNode{} - node.right = fakeNode{} - err = node.Visit(func(TreeNode) error { return fake.GetError() }) - require.EqualError(t, err, fake.Err("visiting interior")) - - node.right = NewEmptyNode(1, big.NewInt(2)) - err = node.Visit(func(TreeNode) error { return fake.GetError() }) - require.EqualError(t, err, fake.Err("visiting empty")) -} - -func TestInteriorNode_Clone(t *testing.T) { - node := NewInteriorNode(0, big.NewInt(0)) - - clone := node.Clone() - require.Equal(t, node, clone) -} - -func TestInteriorNode_Serialize(t *testing.T) { - node := NewInteriorNode(2, big.NewInt(3)) - - data, err := node.Serialize(testCtx) - require.NoError(t, err) - require.Equal(t, `{"Interior":{"Digest":"","Depth":2,"Prefix":"Aw=="}}`, string(data)) - - _, err = node.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("failed to encode interior node")) -} - -func TestLeafNode_GetHash(t *testing.T) { - node := NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - - require.Empty(t, node.GetHash()) - - _, err := node.Prepare([]byte{1}, big.NewInt(2), nil, crypto.NewSha256Factory()) - require.NoError(t, err) - require.Len(t, node.GetHash(), 32) -} - -func TestLeafNode_GetType(t *testing.T) { - node := NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - - require.Equal(t, leafNodeType, node.GetType()) -} - -func TestLeafNode_Search(t *testing.T) { - node := NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - path := newPath([]byte{}, []byte("ping")) - - value, err := node.Search(makeKey([]byte("ping")), &path, nil) - require.NoError(t, err) - require.Equal(t, []byte("pong"), value) - require.Equal(t, []byte("pong"), path.value) - - value, err = node.Search(makeKey([]byte("pong")), nil, nil) - require.NoError(t, err) - require.Nil(t, value) -} - -func TestLeafNode_Insert(t *testing.T) { - node := NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - - next, err := node.Insert(makeKey([]byte("ping")), []byte("abc"), nil) - require.NoError(t, err) - require.Same(t, node, next) - require.Equal(t, []byte("abc"), next.(*LeafNode).value) - - node = NewLeafNode(0, makeKey([]byte{0}), []byte{0xaa}) - next, err = node.Insert(makeKey([]byte{1}), []byte{0xbb}, nil) - require.NoError(t, err) - require.IsType(t, (*InteriorNode)(nil), next) - require.IsType(t, (*LeafNode)(nil), next.(*InteriorNode).left) - require.IsType(t, (*LeafNode)(nil), next.(*InteriorNode).right) - - node = NewLeafNode(0, makeKey([]byte{1}), []byte{0xaa}) - next, err = node.Insert(makeKey([]byte{0}), []byte{0xbb}, nil) - require.NoError(t, err) - require.IsType(t, (*InteriorNode)(nil), next) - require.IsType(t, (*LeafNode)(nil), next.(*InteriorNode).left) - require.IsType(t, (*LeafNode)(nil), next.(*InteriorNode).right) -} - -func TestLeafNode_Delete(t *testing.T) { - node := NewLeafNode(0, makeKey([]byte("ping")), []byte("pong")) - - next, err := node.Delete(makeKey([]byte("pong")), nil) - require.NoError(t, err) - require.Same(t, node, next) - - next, err = node.Delete(makeKey([]byte("ping")), nil) - require.NoError(t, err) - require.IsType(t, (*EmptyNode)(nil), next) -} - -func TestLeafNode_Prepare(t *testing.T) { - node := NewLeafNode(3, makeKey([]byte("ping")), []byte("pong")) - calls := &fake.Call{} - - _, err := node.Prepare([]byte{1}, big.NewInt(2), nil, fake.NewHashFactory(&fake.Hash{Call: calls})) - require.NoError(t, err) - require.Equal(t, 1, calls.Len()) - require.Equal(t, "\x02\x01\x03\x00\x02pingpong", string(calls.Get(0, 0).([]byte))) - - node.hash = nil - _, err = node.Prepare(nil, big.NewInt(0), nil, fake.NewHashFactory(fake.NewBadHash())) - require.EqualError(t, err, fake.Err("leaf node failed")) -} - -func TestLeafNode_Visit(t *testing.T) { - node := NewLeafNode(3, makeKey([]byte("ping")), []byte("pong")) - - counter := 0 - err := node.Visit(func(n TreeNode) error { - counter++ - require.IsType(t, node, n) - - return nil - }) - require.NoError(t, err) - require.Equal(t, 1, counter) - - err = node.Visit(func(n TreeNode) error { return fake.GetError() }) - require.EqualError(t, err, fake.Err("visiting leaf")) -} - -func TestLeafNode_Clone(t *testing.T) { - node := NewLeafNode(3, makeKey([]byte("ping")), []byte("pong")) - - clone := node.Clone() - require.Equal(t, node, clone) -} - -func TestLeafNode_Serialize(t *testing.T) { - node := NewLeafNode(2, big.NewInt(2), []byte{0xaa}) - - data, err := node.Serialize(testCtx) - require.NoError(t, err) - require.Equal(t, `{"Leaf":{"Digest":"","Depth":2,"Prefix":"Ag==","Value":"qg=="}}`, string(data)) - - _, err = node.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("failed to encode leaf node")) -} - -func TestNodeFactory_Deserialize(t *testing.T) { - fac := NodeFactory{} - - msg, err := fac.Deserialize(testCtx, []byte(`{"Empty":{}}`)) - require.NoError(t, err) - require.IsType(t, &EmptyNode{}, msg) - - _, err = fac.Deserialize(testCtx, []byte(`{}`)) - require.EqualError(t, err, "format failed: message is empty") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeBucket(t *testing.T) *fakeBucket { - emptyNode, err := NewEmptyNode(1, big.NewInt(0)).Serialize(testCtx) - require.NoError(t, err) - - leafNode, err := NewLeafNode(1, big.NewInt(1), []byte{1, 2, 3}).Serialize(testCtx) - require.NoError(t, err) - - bucket := &fakeBucket{ - values: map[string][]byte{ - string([]byte{0}): emptyNode, - string([]byte{1}): leafNode, - }, - } - - return bucket -} - -type fakeTx struct { - kv.WritableTx - - bucket kv.Bucket -} - -func (tx fakeTx) GetBucket(name []byte) kv.Bucket { - return tx.bucket -} - -func (tx fakeTx) GetBucketOrCreate(name []byte) (kv.Bucket, error) { - return nil, nil -} - -type fakeDB struct { - kv.DB - err error -} - -func (db fakeDB) View(fn func(kv.ReadableTx) error) error { - return fn(fakeTx{}) -} - -func (db fakeDB) Update(fn func(kv.WritableTx) error) error { - if db.err != nil { - return db.err - } - return fn(fakeTx{}) -} - -type fakeNode struct { - TreeNode - - data []byte - err error -} - -func (n fakeNode) Search(key *big.Int, path *Path, bucket kv.Bucket) ([]byte, error) { - return nil, n.err -} - -func (n fakeNode) Insert(key *big.Int, value []byte, b kv.Bucket) (TreeNode, error) { - return n, n.err -} - -func (n fakeNode) Delete(key *big.Int, b kv.Bucket) (TreeNode, error) { - return nil, n.err -} - -func (n fakeNode) Prepare(nonce []byte, - prefix *big.Int, b kv.Bucket, fac crypto.HashFactory) ([]byte, error) { - - return n.data, n.err -} - -func (n fakeNode) Visit(func(TreeNode) error) error { - return n.err -} diff --git a/dela/core/store/hashtree/hashtree.go b/dela/core/store/hashtree/hashtree.go deleted file mode 100644 index 59295d5..0000000 --- a/dela/core/store/hashtree/hashtree.go +++ /dev/null @@ -1,54 +0,0 @@ -// Package hashtree defines the specialization of the store as a Merkle tree. -// -// Merkle trees allow the creation of proofs to demonstrate if a key/value pair -// is stored in the tree, or if it is not. -// -// Documentation Last Review: 08.10.2020 -package hashtree - -import "go.dedis.ch/dela/core/store" - -// Path is a path along the tree to a key and its value, or none if the key is -// not set. -type Path interface { - // GetKey returns the key of the path. - GetKey() []byte - - // GetValue returns the value of the path, or nil if it is not set. - GetValue() []byte - - // GetRoot returns the store root calculated from the key. It should match - // the tree root for the path to be valid. - GetRoot() []byte -} - -// Tree is a specialization of a store. It uses the Merkle tree structure to -// create a root hash that represents the state of the tree and can be used to -// create proof of inclusion/proof of absence. -type Tree interface { - store.Readable - - // GetRoot returns the root hash of this tree. - GetRoot() []byte - - // GetPath returns a path to a key and its value in the tree. It can be used - // as a proof of inclusion or a proof of absence in the contrary. - GetPath(key []byte) (Path, error) - - // Stage must create a writable tree from the current one that will be - // passed to the callback, then return it. - Stage(func(store.Snapshot) error) (StagingTree, error) -} - -// StagingTree is a tree that has been modified in-memory but is yet to be -// committed to the disk. -type StagingTree interface { - Tree - - // WithTx decorates the staging tree to use a transaction to perform - // operations on the database while using the same underlying data. - WithTx(store.Transaction) StagingTree - - // Commit writes the tree to a persistent storage. - Commit() error -} diff --git a/dela/core/store/kv/controller/controller.go b/dela/core/store/kv/controller/controller.go deleted file mode 100644 index b0b3d8c..0000000 --- a/dela/core/store/kv/controller/controller.go +++ /dev/null @@ -1,56 +0,0 @@ -// Package controller implements a CLI controller for the key/value database. -// -// Documentation Last Review: 08.10.2020 -package controller - -import ( - "path/filepath" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/store/kv" - "golang.org/x/xerrors" -) - -// MinimalController is a CLI controller to inject a key/value database. -// -// - implements node.Initializer -type minimalController struct{} - -// NewController returns a minimal controller that will inject a key/value -// database. -func NewController() node.Initializer { - return minimalController{} -} - -// SetCommands implements node.Initializer. It does not register any command. -func (m minimalController) SetCommands(builder node.Builder) {} - -// OnStart implements node.Initializer. It opens the database in a file using -// the config path as the base. -func (m minimalController) OnStart(flags cli.Flags, inj node.Injector) error { - db, err := kv.New(filepath.Join(flags.String("config"), "dela.db")) - if err != nil { - return xerrors.Errorf("db: %v", err) - } - - inj.Inject(db) - - return nil -} - -// OnStop implements node.Initializer. It closes the database. -func (m minimalController) OnStop(inj node.Injector) error { - var db kv.DB - err := inj.Resolve(&db) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - err = db.Close() - if err != nil { - return xerrors.Errorf("while closing db: %v", err) - } - - return nil -} diff --git a/dela/core/store/kv/default.go b/dela/core/store/kv/default.go deleted file mode 100644 index 5fa4ad6..0000000 --- a/dela/core/store/kv/default.go +++ /dev/null @@ -1,143 +0,0 @@ -// This file contains the implementation of a key/value database using bbolt. -// -// See https://github.com/etcd-io/bbolt. -// -// Documentation Last Review: 08.10.2020 -// - -package kv - -import ( - "bytes" - - "go.etcd.io/bbolt" - "golang.org/x/xerrors" -) - -// BoltDB is an adapter of the KV database using bboltdb. -// -// - implements kv.DB -type boltDB struct { - bolt *bbolt.DB -} - -// New opens a new database to the given file. -func New(path string) (DB, error) { - db, err := bbolt.Open(path, 0666, &bbolt.Options{}) - if err != nil { - return nil, xerrors.Errorf("failed to open db: %v", err) - } - - bdb := boltDB{ - bolt: db, - } - - return bdb, nil -} - -// View implements kv.DB. It executes the read-only transaction in the context -// of the database. -func (db boltDB) View(fn func(ReadableTx) error) error { - return db.bolt.View(func(txn *bbolt.Tx) error { - return fn(boltTx{txn: txn}) - }) -} - -// Update implements kv.DB. It executes the writable transaction in the context -// of the database. -func (db boltDB) Update(fn func(WritableTx) error) error { - return db.bolt.Update(func(txn *bbolt.Tx) error { - return fn(boltTx{txn: txn}) - }) -} - -// Close implements kv.DB. It closes the database. Any view or update call will -// result in an error after this function is called. -func (db boltDB) Close() error { - return db.bolt.Close() -} - -// BoltTx is the adapter of a bbolt transaction for the key/value database. -// -// - implements kv.ReadableTx -// - implements kv.WritableTx -type boltTx struct { - txn *bbolt.Tx -} - -// GetBucket implements kv.ReadableTx. It returns the bucket with the given name -// or nil if it does not exist. -func (tx boltTx) GetBucket(name []byte) Bucket { - bucket := tx.txn.Bucket(name) - if bucket == nil { - return nil - } - - return boltBucket{bucket: bucket} -} - -// GetBucketOrCreate implements kv.WritableTx. It creates the bucket if it does -// not exist and then return it. -func (tx boltTx) GetBucketOrCreate(name []byte) (Bucket, error) { - bucket, err := tx.txn.CreateBucketIfNotExists(name) - if err != nil { - return nil, xerrors.Errorf("create bucket failed: %v", err) - } - - return boltBucket{bucket: bucket}, nil -} - -// OnCommit implements store.Transaction. It registers a callback that is called -// after the transaction is successful. -func (tx boltTx) OnCommit(fn func()) { - tx.txn.OnCommit(fn) -} - -// BoltBucket is the adapter of a bbolt bucket for the key/value database. -// -// - implements kv.Bucket -type boltBucket struct { - bucket *bbolt.Bucket -} - -// Get implements kv.Bucket. It returns the value associated to the key, or nil -// if it does not exist. -func (txn boltBucket) Get(key []byte) []byte { - return txn.bucket.Get(key) -} - -// Set implements kv.Bucket. It sets the provided key to the value. -func (txn boltBucket) Set(key, value []byte) error { - return txn.bucket.Put(key, value) -} - -// Delete implements kv.Bucket. It deletes the key from the bucket. -func (txn boltBucket) Delete(key []byte) error { - return txn.bucket.Delete(key) -} - -// ForEach implements kv.Bucket. It iterates over the whole bucket in an -// unspecified order. If the callback returns an error, the iteration is stopped -// and the error returned to the caller. -func (txn boltBucket) ForEach(fn func(k, v []byte) error) error { - return txn.bucket.ForEach(fn) -} - -// Scan implements kv.Bucket. It iterates over the keys matching the prefix in a -// sorted order. If the callback returns an error, the iteration is stopped and -// the error returned to the caller. -func (txn boltBucket) Scan(prefix []byte, fn func(k, v []byte) error) error { - cursor := txn.bucket.Cursor() - cursor.Seek(prefix) - - for k, v := cursor.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = cursor.Next() { - err := fn(k, v) - if err != nil { - // The caller is responsible for wrapping the errors inside the - // callback, as it returns the exact error to allow comparison. - return err - } - } - - return nil -} diff --git a/dela/core/store/kv/default_test.go b/dela/core/store/kv/default_test.go deleted file mode 100644 index 2f8b084..0000000 --- a/dela/core/store/kv/default_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package kv - -import ( - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" -) - -const delaTestDir = "dela-core-kv" - -func TestBoltDB_UpdateAndView(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - ch := make(chan struct{}) - err = db.Update(func(txn WritableTx) error { - txn.OnCommit(func() { close(ch) }) - - bucket, err := txn.GetBucketOrCreate([]byte("bucket")) - require.NoError(t, err) - - return bucket.Set([]byte("ping"), []byte("pong")) - }) - require.NoError(t, err) - - select { - case <-ch: - case <-time.After(time.Second): - t.Fatal("timeout") - } - - err = db.View(func(txn ReadableTx) error { - bucket := txn.GetBucket([]byte("bucket")) - require.NotNil(t, bucket) - - value := bucket.Get([]byte("ping")) - require.Equal(t, []byte("pong"), value) - - return nil - }) - require.NoError(t, err) -} - -func TestBoltDB_Close(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - err = db.Close() - require.NoError(t, err) - require.Error(t, db.(boltDB).bolt.Sync()) -} - -func TestBoltTx_GetBucket(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - err = db.Update(func(tx WritableTx) error { - require.Nil(t, tx.GetBucket([]byte("unknown"))) - - _, err := tx.GetBucketOrCreate([]byte("A")) - require.NoError(t, err) - require.NotNil(t, tx.GetBucket([]byte("A"))) - - _, err = tx.GetBucketOrCreate(nil) - require.EqualError(t, err, "create bucket failed: bucket name required") - - return nil - }) - require.NoError(t, err) -} - -func TestBoltBucket_Get_Set_Delete(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - err = db.Update(func(txn WritableTx) error { - b, err := txn.GetBucketOrCreate([]byte("bucket")) - require.NoError(t, err) - - require.NoError(t, b.Set([]byte("ping"), []byte("pong"))) - - value := b.Get([]byte("ping")) - require.Equal(t, []byte("pong"), value) - - value = b.Get([]byte("pong")) - require.Nil(t, value) - - require.NoError(t, b.Delete([]byte("ping"))) - - value = b.Get([]byte("ping")) - require.Nil(t, value) - - return nil - }) - - require.NoError(t, err) -} - -func TestBoltBucket_ForEach(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - err = db.Update(func(txn WritableTx) error { - b, err := txn.GetBucketOrCreate([]byte("test")) - require.NoError(t, err) - - require.NoError(t, b.Set([]byte{2}, []byte{2})) - require.NoError(t, b.Set([]byte{1}, []byte{1})) - require.NoError(t, b.Set([]byte{0}, []byte{0})) - - var i byte = 0 - return b.ForEach(func(k, v []byte) error { - require.Equal(t, []byte{i}, k) - require.Equal(t, []byte{i}, v) - i++ - return nil - }) - }) - require.NoError(t, err) -} - -func TestBoltBucket_Scan(t *testing.T) { - dir, err := os.MkdirTemp(os.TempDir(), delaTestDir) - require.NoError(t, err) - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "test.db")) - require.NoError(t, err) - - err = db.Update(func(txn WritableTx) error { - b, err := txn.GetBucketOrCreate([]byte("bucket")) - require.NoError(t, err) - - require.NoError(t, b.Set([]byte{7}, []byte{7})) - require.NoError(t, b.Set([]byte{0}, []byte{0})) - - var i byte = 0 - b.Scan(nil, func(k, v []byte) error { - require.Equal(t, []byte{i}, k) - require.Equal(t, []byte{i}, v) - i += 7 - return nil - }) - require.Equal(t, byte(14), i) - - err = b.Scan([]byte{1}, func(k, v []byte) error { - return xerrors.New("callback error") - }) - require.NoError(t, err) - - err = b.Scan([]byte{}, func(k, v []byte) error { - return xerrors.New("callback error") - }) - require.EqualError(t, err, "callback error") - - return nil - }) - require.NoError(t, err) -} diff --git a/dela/core/store/kv/default_unix_test.go b/dela/core/store/kv/default_unix_test.go deleted file mode 100644 index 564e59c..0000000 --- a/dela/core/store/kv/default_unix_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build linux darwin - -package kv - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBoltDB_New(t *testing.T) { - db, err := New("") - require.Nil(t, db) - require.EqualError(t, err, "failed to open db: open : no such file or directory") -} diff --git a/dela/core/store/kv/default_windows_test.go b/dela/core/store/kv/default_windows_test.go deleted file mode 100644 index aa3a244..0000000 --- a/dela/core/store/kv/default_windows_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package kv - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBoltDB_New(t *testing.T) { - db, err := New("") - require.Nil(t, db) - require.EqualError(t, err, - "failed to open db: open : The system cannot find the file specified.") -} diff --git a/dela/core/store/kv/example_test.go b/dela/core/store/kv/example_test.go deleted file mode 100644 index be9113f..0000000 --- a/dela/core/store/kv/example_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package kv - -import ( - "fmt" - "os" - "path/filepath" -) - -func ExampleBucket_Scan() { - dir, err := os.MkdirTemp(os.TempDir(), "example") - if err != nil { - panic("failed to create folder: " + err.Error()) - } - - defer os.RemoveAll(dir) - - db, err := New(filepath.Join(dir, "example.db")) - if err != nil { - panic("failed to open db: " + err.Error()) - } - - pairs := [][]byte{ - {0b0101}, - {0b1111}, - {0b0000}, - {0b1110}, - } - - err = db.Update(func(tx WritableTx) error { - bucket, err := tx.GetBucketOrCreate([]byte("example_bucket")) - if err != nil { - return err - } - - for _, pair := range pairs { - err = bucket.Set(pair, pair) - if err != nil { - return err - } - } - - return nil - }) - if err != nil { - panic("database write failed: " + err.Error()) - } - - err = db.View(func(tx ReadableTx) error { - bucket := tx.GetBucket([]byte("example_bucket")) - if bucket == nil { - return nil - } - - return bucket.Scan(nil, func(key, value []byte) error { - fmt.Printf("%04b", key) - fmt.Println("") - return nil - }) - }) - if err != nil { - panic("database read failed: " + err.Error()) - } - - // Output: [0000] - // [0101] - // [1110] - // [1111] -} diff --git a/dela/core/store/kv/kv.go b/dela/core/store/kv/kv.go deleted file mode 100644 index f4d1cbd..0000000 --- a/dela/core/store/kv/kv.go +++ /dev/null @@ -1,63 +0,0 @@ -// Package kv defines the abstraction for a key/value database. -// -// The package also implements a default database implementation that is using -// bbolt as the engine (https://github.com/etcd-io/bbolt). -// -// Documentation Last Review: 08.10.2020 -package kv - -import "go.dedis.ch/dela/core/store" - -// Bucket is a general interface to operate on a database bucket. -type Bucket interface { - // Get reads the key from the bucket and returns the value, or nil if the - // key does not exist. - Get(key []byte) []byte - - // Set assigns the value to the provided key. - Set(key, value []byte) error - - // Delete deletes the key from the bucket. - Delete(key []byte) error - - // ForEach iterates over all the items in the bucket in a unspecified order. - // The iteration stops when the callback returns an error. - ForEach(func(k, v []byte) error) error - - // Scan iterates over every key that matches the prefix in an order - // determined by the implementation. The iteration stops when the callback - // returns an error. - Scan(prefix []byte, fn func(k, v []byte) error) error -} - -// ReadableTx allows one to perform read-only atomic operations on the database. -type ReadableTx interface { - // GetBucket returns the bucket of the given name if it exists, otherwise it - // returns nil. - GetBucket(name []byte) Bucket -} - -// WritableTx allows one to perform atomic operations on the database. -type WritableTx interface { - store.Transaction - - ReadableTx - - // GetBucketOrCreate returns the bucket of the given name if it exists, or - // it creates it. - GetBucketOrCreate(name []byte) (Bucket, error) -} - -// DB is a general interface to operate over a key/value database. -type DB interface { - // View executes the provided read-only transaction in the context of the - // database. - View(fn func(ReadableTx) error) error - - // Update executes the provided writable transaction in the context of the - // database. - Update(fn func(WritableTx) error) error - - // Close closes the database and free the resources. - Close() error -} diff --git a/dela/core/store/store.go b/dela/core/store/store.go deleted file mode 100644 index a1e20de..0000000 --- a/dela/core/store/store.go +++ /dev/null @@ -1,31 +0,0 @@ -// Package store defines the primitives of a simple key/value storage. -// -// Documentation Last Review: 08.10.2020 -package store - -// Readable is the interface for a readable store. -type Readable interface { - Get(key []byte) ([]byte, error) -} - -// Writable is the interface for a writable store. -type Writable interface { - Set(key []byte, value []byte) error - - Delete(key []byte) error -} - -// Snapshot is a state of the store that can be read and write independently. A -// write is applied only to the snapshot reference. -type Snapshot interface { - Readable - Writable -} - -// Transaction is a generic interface that store implementations can use to -// provide atomicity. -type Transaction interface { - // OnCommit adds a callback to be executed after the transaction - // successfully commits. - OnCommit(func()) -} diff --git a/dela/core/txn/pool/controller/action.go b/dela/core/txn/pool/controller/action.go deleted file mode 100644 index 1f9762d..0000000 --- a/dela/core/txn/pool/controller/action.go +++ /dev/null @@ -1,116 +0,0 @@ -// This file implements the actions of the controller -// -// Documentation Last Review: 02.02.2021 -// - -package controller - -import ( - "sync" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/crypto/loader" - - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/core/txn/signed" - "golang.org/x/xerrors" -) - -// getManager is the function called when we need a transaction manager. It -// allows us to use a different manager for the tests. -var getManager = func(signer crypto.Signer, s signed.Client) txn.Manager { - return signed.NewManager(signer, s) -} - -// addAction describes an action to add an new transaction to the pool. -// -// - implements node.ActionTemplate -type addAction struct { - sync.Mutex - - client *client -} - -// Execute implements node.ActionTemplate -func (a *addAction) Execute(ctx node.Context) error { - a.Lock() - defer a.Unlock() - - var p pool.Pool - err := ctx.Injector.Resolve(&p) - if err != nil { - return xerrors.Errorf("injector: %v", err) - } - - args, err := getArgs(ctx) - if err != nil { - return xerrors.Errorf("failed to get args: %v", err) - } - - signer, err := getSigner(ctx) - if err != nil { - return xerrors.Errorf("failed to get signer: %v", err) - } - - nonce := ctx.Flags.Int(nonceFlag) - if nonce != -1 { - a.client.nonce = uint64(nonce) - } - - manager := getManager(signer, a.client) - - err = manager.Sync() - if err != nil { - return xerrors.Errorf("failed to sync manager: %v", err) - } - - tx, err := manager.Make(args...) - if err != nil { - return xerrors.Errorf("creating transaction: %v", err) - } - - err = p.Add(tx) - if err != nil { - return xerrors.Errorf("failed to include tx: %v", err) - } - - return nil -} - -// getArgs extracts and parses arguments from the context. -func getArgs(ctx node.Context) ([]txn.Arg, error) { - inArgs := ctx.Flags.StringSlice("args") - if len(inArgs)%2 != 0 { - return nil, xerrors.New("number of args should be even") - } - - args := make([]txn.Arg, len(inArgs)/2) - for i := 0; i < len(args); i++ { - args[i] = txn.Arg{ - Key: inArgs[i*2], - Value: []byte(inArgs[i*2+1]), - } - } - - return args, nil -} - -// getSigner creates a signer from the signerFlag flag in context. -func getSigner(ctx node.Context) (crypto.Signer, error) { - l := loader.NewFileLoader(ctx.Flags.Path(signerFlag)) - - signerdata, err := l.Load() - if err != nil { - return nil, xerrors.Errorf("failed to load signer: %v", err) - } - - signer, err := bls.NewSignerFromBytes(signerdata) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal signer: %v", err) - } - - return signer, nil -} diff --git a/dela/core/txn/pool/controller/action_test.go b/dela/core/txn/pool/controller/action_test.go deleted file mode 100644 index bdf4c56..0000000 --- a/dela/core/txn/pool/controller/action_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package controller - -import ( - "errors" - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/core/txn/pool/mem" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestExecute(t *testing.T) { - ctx := node.Context{ - Injector: node.NewInjector(), - Flags: make(node.FlagSet), - Out: io.Discard, - } - - ctx.Flags.(node.FlagSet)["args"] = []interface{}{"1", "2"} - - action := addAction{client: &client{}} - ctx.Injector.Inject(mem.NewPool()) - - buf, err := bls.NewSigner().MarshalBinary() - require.NoError(t, err) - - keyFile := filepath.Join(os.TempDir(), "key.buf") - ctx.Flags.(node.FlagSet)[signerFlag] = keyFile - - err = os.WriteFile(keyFile, buf, os.ModePerm) - require.NoError(t, err) - defer os.RemoveAll(keyFile) - - err = action.Execute(ctx) - require.NoError(t, err) - - ctx.Injector = node.NewInjector() - ctx.Injector.Inject(&badPool{}) - err = action.Execute(ctx) - require.EqualError(t, err, "failed to include tx: "+fake.Err("failed to add")) - - getManager = func(c crypto.Signer, s signed.Client) txn.Manager { - return badManager{} - } - - err = action.Execute(ctx) - require.EqualError(t, err, "creating transaction: "+fake.Err("make fail")) - - getManager = func(c crypto.Signer, s signed.Client) txn.Manager { - return badManager{failSync: true} - } - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to sync manager: "+fake.Err("sync fail")) - - err = os.WriteFile(keyFile, []byte("bad signer"), os.ModePerm) - require.NoError(t, err) - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to get signer: failed to unmarshal signer: while unmarshaling scalar: UnmarshalBinary: wrong size buffer") - - ctx.Flags.(node.FlagSet)[signerFlag] = "/not/exist" - - err = action.Execute(ctx) - // the error message can be different based on the platform - require.Regexp(t, "^failed to get signer: failed to load signer: while opening file: open /not/exist:", err.Error()) - - ctx.Flags.(node.FlagSet)["args"] = []interface{}{"1"} - - err = action.Execute(ctx) - require.EqualError(t, err, "failed to get args: number of args should be even") - - ctx.Injector = node.NewInjector() - err = action.Execute(ctx) - require.EqualError(t, err, "injector: couldn't find dependency for 'pool.Pool'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badPool struct { - pool.Pool -} - -func (p *badPool) Add(txn.Transaction) error { - return errors.New(fake.Err("failed to add")) -} - -type badManager struct { - txn.Manager - failSync bool -} - -func (m badManager) Sync() error { - if m.failSync { - return errors.New(fake.Err("sync fail")) - } - - return nil -} - -func (m badManager) Make(args ...txn.Arg) (txn.Transaction, error) { - return nil, errors.New(fake.Err("make fail")) -} diff --git a/dela/core/txn/pool/controller/controller.go b/dela/core/txn/pool/controller/controller.go deleted file mode 100644 index 9d70373..0000000 --- a/dela/core/txn/pool/controller/controller.go +++ /dev/null @@ -1,78 +0,0 @@ -// Package controller implements a controller for the pool -// -// Documentation Last Review: 02.02.2021 -package controller - -import ( - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access" -) - -const ( - // signerFlag is the flag name containing the path to the private keyfile. - signerFlag = "key" - - // nonceFlag is the flag name containing the nonce. - nonceFlag = "nonce" -) - -type miniController struct { -} - -// NewController creates a new minimal controller for the pool -// -// - implements node.Initializer -func NewController() node.Initializer { - return miniController{} -} - -// SetCommands implements mode.Initializer. It sets the command to interact with -// the pool. -func (miniController) SetCommands(builder node.Builder) { - cmd := builder.SetCommand("pool") - cmd.SetDescription("interact with the pool") - - sub := cmd.SetSubCommand("add") - sub.SetDescription("add a transaction to the pool") - sub.SetFlags(cli.StringSliceFlag{ - Name: "args", - Usage: "list of key-value pairs", - }, cli.IntFlag{ - Name: nonceFlag, - Usage: "nonce to use", - Required: false, - Value: -1, - }, cli.StringFlag{ - Name: signerFlag, - Usage: "path to the private keyfile", - Required: true, - }) - sub.SetAction(builder.MakeAction(&addAction{ - client: &client{}, - })) -} - -// OnStart implements node.Initializer -func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { - return nil -} - -// OnStop implements node.Initializer -func (miniController) OnStop(inj node.Injector) error { - return nil -} - -// client return monotically increasing nonce -// -// - implements signed.Client -type client struct { - nonce uint64 -} - -// GetNonce implements signed.Client -func (c *client) GetNonce(access.Identity) (uint64, error) { - res := c.nonce - c.nonce++ - return res, nil -} diff --git a/dela/core/txn/pool/controller/controller_test.go b/dela/core/txn/pool/controller/controller_test.go deleted file mode 100644 index a4d8dbd..0000000 --- a/dela/core/txn/pool/controller/controller_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package controller - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestMiniController_Build(t *testing.T) { - ctrl := NewController() - - call := &fake.Call{} - ctrl.SetCommands(fakeBuilder{call: call}) - - require.Equal(t, 7, call.Len()) - require.Equal(t, "pool", call.Get(0, 0)) - require.Equal(t, "interact with the pool", call.Get(1, 0)) - require.Equal(t, "add", call.Get(2, 0)) - require.Equal(t, "add a transaction to the pool", call.Get(3, 0)) - require.Len(t, call.Get(4, 0), 3) - require.IsType(t, &addAction{}, call.Get(5, 0)) - require.Nil(t, call.Get(6, 0)) // our fake MakeAction() returns nil -} - -func TestMiniController_OnStart(t *testing.T) { - res := NewController().OnStart(node.FlagSet{}, nil) - require.Nil(t, res) -} - -func TestMiniController_OnStop(t *testing.T) { - res := NewController().OnStop(nil) - require.Nil(t, res) -} - -func TestClient(t *testing.T) { - c := client{} - - n, err := c.GetNonce(nil) - require.NoError(t, err) - require.Equal(t, uint64(0), n) - - n, err = c.GetNonce(nil) - require.NoError(t, err) - require.Equal(t, uint64(1), n) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeCommandBuilder struct { - call *fake.Call -} - -func (b fakeCommandBuilder) SetSubCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return b -} - -func (b fakeCommandBuilder) SetDescription(value string) { - b.call.Add(value) -} - -func (b fakeCommandBuilder) SetFlags(flags ...cli.Flag) { - b.call.Add(flags) -} - -func (b fakeCommandBuilder) SetAction(a cli.Action) { - b.call.Add(a) -} - -type fakeBuilder struct { - call *fake.Call -} - -func (b fakeBuilder) SetCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return fakeCommandBuilder(b) -} - -func (b fakeBuilder) SetStartFlags(flags ...cli.Flag) { - b.call.Add(flags) -} - -func (b fakeBuilder) MakeAction(tmpl node.ActionTemplate) cli.Action { - b.call.Add(tmpl) - return nil -} diff --git a/dela/core/txn/pool/gatherer.go b/dela/core/txn/pool/gatherer.go deleted file mode 100644 index 5b0fcac..0000000 --- a/dela/core/txn/pool/gatherer.go +++ /dev/null @@ -1,263 +0,0 @@ -package pool - -import ( - "context" - "sync" - "time" - - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "golang.org/x/xerrors" -) - -// DefaultIdentitySize is the default size defined for each identity to store -// transactions. -const DefaultIdentitySize = 100 - -// Gatherer is a common tool to the pool implementations that helps to implement -// the gathering process. -type Gatherer interface { - // AddFilter adds the filter to the list that a transaction will go through - // before being accepted by the gatherer. - AddFilter(Filter) - - // Add adds the transaction to the list of pending transactions. - Add(tx txn.Transaction) error - - // Remove removes a transaction from the list of pending ones. - Remove(tx txn.Transaction) error - - // Wait waits for a notification with sufficient transactions to return the - // array, or nil if the context ends. - Wait(ctx context.Context, cfg Config) []txn.Transaction - - // Close closes current operations and cleans the resources. - Close() - - // Stats gets the transaction statistics. - Stats() Stats - - // ResetStats resets the transaction statistics. - ResetStats() -} - -type item struct { - cfg Config - ch chan []txn.Transaction -} - -// SimpleGatherer is a gatherer of transactions that will use filters to drop -// invalid transactions. It limits the size for each identity, *as long as* a -// filter is set, otherwise it can grow indefinitely. -// -// - implements pool.Gatherer -type simpleGatherer struct { - sync.Mutex - - limit int - queue []item - validators []Filter - - // A string key is generated for each unique identity, which will have its - // own list of transactions, so that a limited size can be enforced - // independently of each other. - txs map[string]transactions -} - -// NewSimpleGatherer creates a new gatherer. -func NewSimpleGatherer() Gatherer { - return &simpleGatherer{ - limit: DefaultIdentitySize, - txs: make(map[string]transactions), - } -} - -// AddFilter implements pool.Gatherer. It adds the filter to the list that a -// transaction will go through before being accepted by the gatherer. -func (g *simpleGatherer) AddFilter(filter Filter) { - if filter == nil { - return - } - - g.validators = append(g.validators, filter) -} - -// Add implements pool.Gatherer. It adds the transaction to the set of available -// transactions and notify the queue of the new length. -func (g *simpleGatherer) Add(tx txn.Transaction) error { - - for _, val := range g.validators { - // Make sure the transaction is not already known, or that is not in a - // distant future to limit the pool storage size. - err := val.Accept(tx, validation.Leeway{MaxSequenceDifference: g.limit}) - if err != nil { - return xerrors.Errorf("invalid transaction: %v", err) - } - } - - key, err := makeKey(tx.GetIdentity()) - if err != nil { - return xerrors.Errorf("identity key failed: %v", err) - } - - g.Lock() - - g.txs[key] = g.txs[key].Add(transactionStats{ - tx, - time.Now(), - }) - - g.notify(g.calculateLength()) - - g.Unlock() - - return nil -} - -// Remove implements pool.Gatherer. It removes the transaction from the set of -// available transactions and add the key in the history to prevent duplicates. -func (g *simpleGatherer) Remove(tx txn.Transaction) error { - key, err := makeKey(tx.GetIdentity()) - if err != nil { - return xerrors.Errorf("identity key failed: %v", err) - } - - g.Lock() - - g.txs[key] = g.txs[key].Remove(tx) - - g.Unlock() - - return nil -} - -// Wait implements pool.Gatherer. It waits for enough transactions before -// returning the list, or it returns nil if the context ends. -func (g *simpleGatherer) Wait(ctx context.Context, cfg Config) []txn.Transaction { - ch := make(chan []txn.Transaction, 1) - - g.Lock() - - if g.calculateLength() >= cfg.Min { - txs := g.makeArray() - g.Unlock() - - return txs - } - - g.queue = append(g.queue, item{cfg: cfg, ch: ch}) - - g.Unlock() - - if cfg.Callback != nil { - cfg.Callback() - } - - select { - case txs := <-ch: - return txs - case <-ctx.Done(): - return nil - } -} - -// Stats implements pool.Gatherer. It gets the transaction statistics. -func (g *simpleGatherer) Stats() Stats { - g.Lock() - defer g.Unlock() - - txs := g.makeStatsArray() - stats := Stats{ - TxCount: len(txs), - OldestTx: time.Now(), - } - - for _, tx := range txs { - if tx.insertionTime.Before(stats.OldestTx) { - stats.OldestTx = tx.insertionTime - } - } - - return stats -} - -// ResetStats implements pool.Gatherer. It resets the transactions statistics. -func (g *simpleGatherer) ResetStats() { - g.Lock() - defer g.Unlock() - - txs := g.makeStatsArray() - for _, tx := range txs { - tx.ResetStats() - } -} - -// Close implements pool.Gatherer. It closes the operations and cleans the -// resources. -func (g *simpleGatherer) Close() { - g.Lock() - - g.txs = make(map[string]transactions) - - for _, item := range g.queue { - close(item.ch) - } - - g.queue = nil - - g.Unlock() -} - -// Notify triggers the elements of the queue that are waiting for at least the -// length in parameter and remove them from the queue. -func (g *simpleGatherer) notify(length int) { - // Iterating by descending order to allow the deletion of the element inside - // the loop. - for i := len(g.queue) - 1; i >= 0; i-- { - item := g.queue[i] - - if item.cfg.Min <= length { - item.ch <- g.makeArray() - g.queue = append(g.queue[:i], g.queue[i+1:]...) - } - } -} - -func (g *simpleGatherer) calculateLength() int { - num := 0 - for _, list := range g.txs { - num += len(list) - } - - return num -} - -func (g *simpleGatherer) makeStatsArray() []transactionStats { - txs := make([]transactionStats, 0, g.calculateLength()) - for _, list := range g.txs { - txs = append(txs, list...) - } - - return txs -} - -func (g *simpleGatherer) makeArray() []txn.Transaction { - stxs := g.makeStatsArray() - txs := make([]txn.Transaction, 0, len(stxs)) - - for _, t := range stxs { - txs = append(txs, t.Transaction) - } - - return txs -} - -func makeKey(id access.Identity) (string, error) { - data, err := id.MarshalText() - if err != nil { - return "", err - } - - return string(data), nil -} diff --git a/dela/core/txn/pool/gatherer_test.go b/dela/core/txn/pool/gatherer_test.go deleted file mode 100644 index 5726671..0000000 --- a/dela/core/txn/pool/gatherer_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package pool - -import ( - "context" - "sync" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestSimpleGatherer_Len(t *testing.T) { - gatherer := NewSimpleGatherer().(*simpleGatherer) - require.Equal(t, 0, gatherer.Stats().TxCount) - - gatherer.txs["Alice"] = transactions{emptyTx()} - require.Equal(t, 1, gatherer.Stats().TxCount) - - gatherer.txs["Bob"] = transactions{emptyTx(), emptyTx()} - require.Equal(t, 3, gatherer.Stats().TxCount) -} - -func TestSimpleGatherer_Add(t *testing.T) { - gatherer := NewSimpleGatherer().(*simpleGatherer) - gatherer.AddFilter(nil) - gatherer.AddFilter(fakeFilter{}) - - for i := 0; i < DefaultIdentitySize; i++ { - err := gatherer.Add(newTx(uint64(i), "Alice")) - require.NoError(t, err) - } - - require.Equal(t, DefaultIdentitySize, gatherer.Stats().TxCount) - - err := gatherer.Add(newTx(DefaultIdentitySize-1, "Alice")) - require.NoError(t, err) - - err = gatherer.Add(newTx(5, "Bob")) - require.NoError(t, err) - - err = gatherer.Add(newTx(2, "Bob")) - require.NoError(t, err) - - require.Len(t, gatherer.txs["Bob"], 2) - require.Equal(t, uint64(2), gatherer.txs["Bob"][0].GetNonce()) - - err = gatherer.Add(newTx(DefaultIdentitySize+1, "Alice")) - require.EqualError(t, err, fake.Err("invalid transaction")) - - err = gatherer.Add(fakeTx{identity: fake.NewBadPublicKey()}) - require.EqualError(t, err, fake.Err("identity key failed")) -} - -func TestSimpleGatherer_Remove(t *testing.T) { - gatherer := NewSimpleGatherer().(*simpleGatherer) - gatherer.txs["Alice"] = transactions{newTx(0, "Alice"), newTx(1, "Alice")} - - err := gatherer.Remove(newTx(0, "Alice")) - require.NoError(t, err) - require.Len(t, gatherer.txs["Alice"], 1) - - err = gatherer.Remove(newTx(0, "Alice")) - require.NoError(t, err) - require.Len(t, gatherer.txs["Alice"], 1) - - err = gatherer.Remove(newTx(1, "Alice")) - require.NoError(t, err) - require.Len(t, gatherer.txs["Alice"], 0) - - err = gatherer.Remove(fakeTx{identity: fake.NewBadPublicKey()}) - require.EqualError(t, err, fake.Err("identity key failed")) -} - -func TestSimpleGatherer_Wait(t *testing.T) { - gatherer := NewSimpleGatherer().(*simpleGatherer) - - ctx := context.Background() - - cb := func() { - gatherer.Lock() - require.Len(t, gatherer.queue, 1) - gatherer.Unlock() - - require.NoError(t, gatherer.Add(newTx(0xa, "Alice"))) - } - - txs := gatherer.Wait(ctx, Config{Min: 1, Callback: cb}) - require.Len(t, txs, 1) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - txs = gatherer.Wait(ctx, Config{Min: 1}) - require.Len(t, txs, 1) - - txs = gatherer.Wait(ctx, Config{Min: 2}) - require.Nil(t, txs) -} - -func TestSimpleGatherer_Close(t *testing.T) { - gatherer := NewSimpleGatherer().(*simpleGatherer) - - require.NoError(t, gatherer.Add(newTx(0, "Alice"))) - require.NoError(t, gatherer.Add(newTx(1, "Alice"))) - require.NoError(t, gatherer.Remove(newTx(0, "Alice"))) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - wg := sync.WaitGroup{} - wg.Add(1) - - go func() { - defer wg.Done() - - txs := gatherer.Wait(ctx, Config{Min: 100, Callback: wg.Done}) - require.Empty(t, txs) - }() - - wg.Wait() - wg.Add(1) - - gatherer.Close() - - require.Empty(t, gatherer.queue) - require.Empty(t, gatherer.txs) - - wg.Wait() -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeTx struct { - txn.Transaction - id uint64 - identity access.Identity -} - -func emptyTx() transactionStats { - return transactionStats{ - Transaction: fakeTx{}, - } -} - -func newTx(nonce uint64, identity string) transactionStats { - return transactionStats{ - Transaction: fakeTx{ - id: nonce, - identity: fakeIdentity{text: identity}, - }, - } -} - -func (tx fakeTx) GetID() []byte { - return []byte{byte(tx.id)} -} - -func (tx fakeTx) GetNonce() uint64 { - return tx.id -} - -func (tx fakeTx) GetIdentity() access.Identity { - return tx.identity -} - -type fakeIdentity struct { - access.Identity - text string -} - -func (id fakeIdentity) MarshalText() ([]byte, error) { - return []byte(id.text), nil -} - -type fakeFilter struct{} - -func (fakeFilter) Accept(tx txn.Transaction, leeway validation.Leeway) error { - if tx.GetNonce() >= uint64(leeway.MaxSequenceDifference) { - return fake.GetError() - } - - return nil -} diff --git a/dela/core/txn/pool/gossip/gossip.go b/dela/core/txn/pool/gossip/gossip.go deleted file mode 100644 index c14c14e..0000000 --- a/dela/core/txn/pool/gossip/gossip.go +++ /dev/null @@ -1,130 +0,0 @@ -// Package gossip implements a transaction pool that is using a gossip protocol -// to spread the transactions to other participants. -package gossip - -import ( - "context" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/gossip" - "golang.org/x/xerrors" -) - -// Pool is a transaction pool that is using gossip to send the transactions to -// the other participants. -// -// - implements pool.Pool -type Pool struct { - logger zerolog.Logger - actor gossip.Actor - gatherer pool.Gatherer - closing chan struct{} -} - -// NewPool creates a new empty pool and starts to gossip incoming transaction. -func NewPool(gossiper gossip.Gossiper) (*Pool, error) { - actor, err := gossiper.Listen() - if err != nil { - return nil, xerrors.Errorf("failed to listen: %v", err) - } - - p := &Pool{ - logger: dela.Logger, - actor: actor, - gatherer: pool.NewSimpleGatherer(), - closing: make(chan struct{}), - } - - go p.listenRumors(gossiper.Rumors()) - - return p, nil -} - -// SetPlayers implements pool.Pool. It sets the list of participants the -// transactions should be gossiped to. -func (p *Pool) SetPlayers(players mino.Players) error { - p.actor.SetPlayers(players) - return nil -} - -// AddFilter implements pool.Pool. It adds the filter to the gatherer. -func (p *Pool) AddFilter(filter pool.Filter) { - p.gatherer.AddFilter(filter) -} - -// Add implements pool.Pool. It adds the transaction to the pool and gossips it -// to other participants. -func (p *Pool) Add(tx txn.Transaction) error { - err := p.gatherer.Add(tx) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - err = p.actor.Add(tx) - if err != nil { - return xerrors.Errorf("failed to gossip tx: %v", err) - } - - return nil -} - -// Remove implements pool.Pool. It removes the transaction from the pool. -func (p *Pool) Remove(tx txn.Transaction) error { - err := p.gatherer.Remove(tx) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - return nil -} - -// Gather implements pool.Pool. It blocks until the pool has enough transactions -// according to the configuration and then returns the transactions. -func (p *Pool) Gather(ctx context.Context, cfg pool.Config) []txn.Transaction { - return p.gatherer.Wait(ctx, cfg) -} - -// Stats implements pool.Pool. It gets the transaction statistics. -func (p *Pool) Stats() pool.Stats { - return p.gatherer.Stats() -} - -// ResetStats implements pool.Pool. It resets the transaction statistics. -func (p *Pool) ResetStats() { - p.gatherer.ResetStats() -} - -// Close stops the gossiper and terminate the routine that listens for rumors. -func (p *Pool) Close() error { - p.gatherer.Close() - - close(p.closing) - - err := p.actor.Close() - if err != nil { - return xerrors.Errorf("failed to close gossiper: %v", err) - } - - return nil -} - -func (p *Pool) listenRumors(ch <-chan gossip.Rumor) { - for { - select { - case rumor := <-ch: - tx, ok := rumor.(txn.Transaction) - if ok { - err := p.gatherer.Add(tx) - if err != nil { - p.logger.Debug().Err(err).Msg("failed to add transaction") - } - } - case <-p.closing: - return - } - } -} diff --git a/dela/core/txn/pool/gossip/gossip_test.go b/dela/core/txn/pool/gossip/gossip_test.go deleted file mode 100644 index a56d0aa..0000000 --- a/dela/core/txn/pool/gossip/gossip_test.go +++ /dev/null @@ -1,300 +0,0 @@ -package gossip - -import ( - "bytes" - "context" - "fmt" - "testing" - "time" - - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/gossip" - "go.dedis.ch/dela/mino/minoch" - "go.dedis.ch/dela/serde" -) - -func TestPool_Basic(t *testing.T) { - _, pools := makeRoster(t, 10) - defer func() { - for _, p := range pools { - require.NoError(t, p.Close()) - } - }() - - go func() { - for i := 0; i < 50; i++ { - err := pools[0].Add(makeFakeTx(uint64(i))) - require.NoError(t, err) - } - }() - - go func() { - for i := 0; i < 50; i++ { - err := pools[2].Add(makeFakeTx(uint64(i + 50))) - require.NoError(t, err) - } - }() - - go func() { - for i := 0; i < 50; i++ { - err := pools[7].Add(makeFakeTx(uint64(i + 100))) - require.NoError(t, err) - } - }() - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - txs := pools[0].Gather(ctx, pool.Config{Min: 150}) - require.Len(t, txs, 150) -} - -func TestPool_New(t *testing.T) { - p, err := NewPool(fakeGossiper{}) - require.NoError(t, err) - require.NotNil(t, p) - - err = p.Close() - require.NoError(t, err) - - _, err = NewPool(fakeGossiper{err: fake.GetError()}) - require.EqualError(t, err, fake.Err("failed to listen")) -} - -func TestPool_Len(t *testing.T) { - p := &Pool{ - gatherer: pool.NewSimpleGatherer(), - } - - require.Equal(t, 0, p.Stats().TxCount) - - p.gatherer.Add(makeFakeTx(0)) - require.Equal(t, 1, p.Stats().TxCount) -} - -func TestPool_AddFilter(t *testing.T) { - p := &Pool{ - gatherer: pool.NewSimpleGatherer(), - } - - p.AddFilter(nil) -} - -func TestPool_Add(t *testing.T) { - p := &Pool{ - actor: fakeActor{}, - gatherer: pool.NewSimpleGatherer(), - } - - err := p.Add(makeFakeTx(0)) - require.NoError(t, err) - - p.gatherer = badGatherer{} - err = p.Add(makeFakeTx(0)) - require.EqualError(t, err, fake.Err("store failed")) - - p.gatherer = pool.NewSimpleGatherer() - p.actor = fakeActor{err: fake.GetError()} - err = p.Add(makeFakeTx(0)) - require.EqualError(t, err, fake.Err("failed to gossip tx")) -} - -func TestPool_Remove(t *testing.T) { - p := &Pool{ - actor: fakeActor{}, - gatherer: pool.NewSimpleGatherer(), - } - - tx := makeFakeTx(0) - - require.NoError(t, p.gatherer.Add(tx)) - - err := p.Remove(tx) - require.NoError(t, err) - - p.gatherer = badGatherer{} - err = p.Remove(tx) - require.EqualError(t, err, fake.Err("store failed")) -} - -func TestPool_Gather(t *testing.T) { - p := &Pool{ - actor: fakeActor{}, - gatherer: pool.NewSimpleGatherer(), - } - - ctx := context.Background() - - cb := func() { - require.NoError(t, p.Add(makeFakeTx(0))) - require.NoError(t, p.Add(makeFakeTx(1))) - } - - txs := p.Gather(ctx, pool.Config{Min: 2, Callback: cb}) - require.Len(t, txs, 2) - - txs = p.Gather(ctx, pool.Config{Min: 2}) - require.Len(t, txs, 2) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - txs = p.Gather(ctx, pool.Config{Min: 3}) - require.Len(t, txs, 0) -} - -func TestPool_Close(t *testing.T) { - p := &Pool{ - gatherer: pool.NewSimpleGatherer(), - closing: make(chan struct{}), - actor: fakeActor{}, - } - - err := p.Close() - require.NoError(t, err) - - p.closing = make(chan struct{}) - p.actor = fakeActor{err: fake.GetError()} - err = p.Close() - require.EqualError(t, err, fake.Err("failed to close gossiper")) -} - -func TestPool_ListenRumors(t *testing.T) { - buffer := new(bytes.Buffer) - - p := &Pool{ - logger: zerolog.New(buffer), - closing: make(chan struct{}), - gatherer: pool.NewSimpleGatherer(), - } - - ch := make(chan gossip.Rumor) - go func() { - ch <- makeFakeTx(0) - close(p.closing) - }() - - p.listenRumors(ch) - require.Empty(t, buffer.String()) - - p.gatherer = badGatherer{} - p.closing = make(chan struct{}) - - ch = make(chan gossip.Rumor) - go func() { - ch <- makeFakeTx(0) - close(p.closing) - }() - - p.listenRumors(ch) - require.NotEmpty(t, buffer.String()) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeFakeTx(nonce uint64) txn.Transaction { - return fakeTx{nonce: nonce} -} - -func makeRoster(t *testing.T, n int) (mino.Players, []*Pool) { - manager := minoch.NewManager() - - pools := make([]*Pool, n) - addrs := make([]mino.Address, n) - - for i := 0; i < n; i++ { - m := minoch.MustCreate(manager, fmt.Sprintf("node%d", i)) - - addrs[i] = m.GetAddress() - - g := gossip.NewFlat(m, fakeTxFac{}) - - p, err := NewPool(g) - require.NoError(t, err) - - pools[i] = p - } - - players := mino.NewAddresses(addrs...) - for _, p := range pools { - p.SetPlayers(players) - } - - return players, pools -} - -type fakeTx struct { - txn.Transaction - - nonce uint64 -} - -func (tx fakeTx) GetNonce() uint64 { - return tx.nonce -} - -func (tx fakeTx) GetIdentity() access.Identity { - return fake.PublicKey{} -} - -func (tx fakeTx) GetID() []byte { - return []byte{byte(tx.nonce)} -} - -func (tx fakeTx) Serialize(serde.Context) ([]byte, error) { - return tx.GetID(), nil -} - -type fakeTxFac struct { - txn.Factory -} - -func (fakeTxFac) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return fakeTx{nonce: uint64(data[0])}, nil -} - -type fakeActor struct { - gossip.Actor - call *fake.Call - err error -} - -func (a fakeActor) Add(r gossip.Rumor) error { - a.call.Add(r) - return a.err -} - -func (a fakeActor) Close() error { - return a.err -} - -type fakeGossiper struct { - err error -} - -func (g fakeGossiper) Listen() (gossip.Actor, error) { - return fakeActor{}, g.err -} - -func (g fakeGossiper) Rumors() <-chan gossip.Rumor { - return nil -} - -type badGatherer struct { - pool.Gatherer -} - -func (g badGatherer) Add(tx txn.Transaction) error { - return fake.GetError() -} - -func (g badGatherer) Remove(tx txn.Transaction) error { - return fake.GetError() -} diff --git a/dela/core/txn/pool/mem/mem.go b/dela/core/txn/pool/mem/mem.go deleted file mode 100644 index 4f377cd..0000000 --- a/dela/core/txn/pool/mem/mem.go +++ /dev/null @@ -1,81 +0,0 @@ -package mem - -import ( - "context" - - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/mino" - "golang.org/x/xerrors" -) - -// Pool is a in-memory transaction pool. It only accepts transactions from a -// local client and it does not support asynchronous calls. -// -// - implements pool.Pool -type Pool struct { - gatherer pool.Gatherer -} - -// NewPool creates a new service. -func NewPool() *Pool { - return &Pool{ - gatherer: pool.NewSimpleGatherer(), - } -} - -// AddFilter implements pool.Pool. It adds the filter to the gatherer. -func (p *Pool) AddFilter(filter pool.Filter) { - p.gatherer.AddFilter(filter) -} - -// Add implements pool.Pool. It adds the transaction to the pool of waiting -// transactions. -func (p *Pool) Add(tx txn.Transaction) error { - err := p.gatherer.Add(tx) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - return nil -} - -// Remove implements pool.Pool. It removes the transaction from the pool if it -// exists, otherwise it returns an error. -func (p *Pool) Remove(tx txn.Transaction) error { - err := p.gatherer.Remove(tx) - if err != nil { - return xerrors.Errorf("store failed: %v", err) - } - - return nil -} - -// SetPlayers implements pool.Pool. It does nothing as the pool is in-memory and -// only shares the transactions to the host. -func (p *Pool) SetPlayers(mino.Players) error { - return nil -} - -// Gather implements pool.Pool. It gathers the transactions of the pool and -// return them. -func (p *Pool) Gather(ctx context.Context, cfg pool.Config) []txn.Transaction { - return p.gatherer.Wait(ctx, cfg) -} - -// Close implements pool.Pool. It cleans the resources of the gatherer. -func (p *Pool) Close() error { - p.gatherer.Close() - - return nil -} - -// Stats implements pool.Pool. It gets the transaction statistics. -func (p *Pool) Stats() pool.Stats { - return p.gatherer.Stats() -} - -// ResetStats implements pool.Pool. It resets the transaction statistics. -func (p *Pool) ResetStats() { - p.gatherer.ResetStats() -} diff --git a/dela/core/txn/pool/mem/mem_test.go b/dela/core/txn/pool/mem/mem_test.go deleted file mode 100644 index 4de2a78..0000000 --- a/dela/core/txn/pool/mem/mem_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package mem - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/pool" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestPool_Len(t *testing.T) { - p := NewPool() - require.Equal(t, 0, p.Stats().TxCount) - - p.gatherer.Add(fakeTx{}) - require.Equal(t, 1, p.Stats().TxCount) -} - -func TestPool_AddFilter(t *testing.T) { - p := NewPool() - - p.AddFilter(nil) -} - -func TestPool_Add(t *testing.T) { - p := NewPool() - - err := p.Add(fakeTx{id: []byte{1}}) - require.NoError(t, err) - - err = p.Add(fakeTx{id: []byte{2}}) - require.NoError(t, err) - - // A transaction that exists in the active queue can simply be overwritten - // thus no error is expected. - err = p.Add(fakeTx{id: []byte{1}}) - require.NoError(t, err) - - p.gatherer = badGatherer{} - err = p.Add(fakeTx{}) - require.EqualError(t, err, fake.Err("store failed")) -} - -func TestPool_Remove(t *testing.T) { - p := NewPool() - - require.NoError(t, p.gatherer.Add(fakeTx{id: []byte{1}})) - - err := p.Remove(fakeTx{id: []byte{1}}) - require.NoError(t, err) - - p.gatherer = badGatherer{} - err = p.Remove(fakeTx{id: []byte{1}}) - require.EqualError(t, err, fake.Err("store failed")) -} - -func TestPool_SetPlayers(t *testing.T) { - p := NewPool() - - require.NoError(t, p.SetPlayers(nil)) -} - -func TestPool_Gather(t *testing.T) { - p := NewPool() - - ctx := context.Background() - - cb := func() { - require.NoError(t, p.Add(fakeTx{})) - } - - txs := p.Gather(ctx, pool.Config{Min: 1, Callback: cb}) - require.Len(t, txs, 1) - - txs = p.Gather(ctx, pool.Config{Min: 1}) - require.Len(t, txs, 1) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - txs = p.Gather(ctx, pool.Config{Min: 2}) - require.Len(t, txs, 0) -} - -func TestPool_Close(t *testing.T) { - p := NewPool() - - err := p.Close() - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeTx struct { - txn.Transaction - - id []byte -} - -func (tx fakeTx) GetNonce() uint64 { - return 0 -} - -func (tx fakeTx) GetIdentity() access.Identity { - return fake.PublicKey{} -} - -func (tx fakeTx) GetID() []byte { - return tx.id -} - -type badGatherer struct { - pool.Gatherer -} - -func (g badGatherer) Add(tx txn.Transaction) error { - return fake.GetError() -} - -func (g badGatherer) Remove(tx txn.Transaction) error { - return fake.GetError() -} diff --git a/dela/core/txn/pool/pool.go b/dela/core/txn/pool/pool.go deleted file mode 100644 index 15cd72a..0000000 --- a/dela/core/txn/pool/pool.go +++ /dev/null @@ -1,71 +0,0 @@ -// Package pool defines the interface for a transaction pool. It will hold the -// transactions of the clients until an ordering service read them and it will -// broadcast the state of the pool to other known participants. -package pool - -import ( - "context" - "time" - - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/mino" -) - -// Config is the set of parameters that allows one to change the behavior of the -// gathering process. -type Config struct { - // Min indicates what is minimum number of transactions that is required - // before returning. - Min int - - // Callback is a function called when the pool doesn't have enough - // transactions at the moment of calling and must therefore wait for new - // transactions to come. It allows one to take action to stop the gathering - // if necessary. - Callback func() -} - -// Filter is the interface to implement to validate if a transaction will be -// accepted and thus is allowed to be pushed in the pool. -type Filter interface { - // Accept returns an error when the transaction is going to be rejected. - Accept(tx txn.Transaction, leeway validation.Leeway) error -} - -// Pool is the maintainer of the list of transactions. -type Pool interface { - // SetPlayers updates the list of participants that should eventually - // receive the transactions. - SetPlayers(mino.Players) error - - AddFilter(Filter) - - // Add adds the transaction to the pool. - Add(txn.Transaction) error - - // Remove removes the transaction from the pool. - Remove(txn.Transaction) error - - // Gather is a blocking function to gather transactions from the pool. The - // configuration allows one to specify criterion before returning. - Gather(context.Context, Config) []txn.Transaction - - // Stats gets the transactions statistics - Stats() Stats - - // ResetStats resets the transaction statistics. - ResetStats() - - // Close closes the pool and cleans the resources. - Close() error -} - -// Stats groups statistics used to manage the pool -type Stats struct { - // OldestTx is the time at which the oldest transaction was added to the pool. - OldestTx time.Time - - // TxCount is the number of transactions available in the pool. - TxCount int -} diff --git a/dela/core/txn/pool/stats.go b/dela/core/txn/pool/stats.go deleted file mode 100644 index d96c6ab..0000000 --- a/dela/core/txn/pool/stats.go +++ /dev/null @@ -1,20 +0,0 @@ -package pool - -import ( - "time" - - "go.dedis.ch/dela/core/txn" -) - -// transactionStats enhances a transaction with some statistics -// to allow the detection of rotten transactions in a pool. -type transactionStats struct { - txn.Transaction - insertionTime time.Time -} - -// ResetStats resets the insertion time to now. -// It is used when a leader view change is initiated. -func (t *transactionStats) ResetStats() { - t.insertionTime = time.Now() -} diff --git a/dela/core/txn/pool/stats_test.go b/dela/core/txn/pool/stats_test.go deleted file mode 100644 index 1ab4286..0000000 --- a/dela/core/txn/pool/stats_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package pool - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestStats_Reset(t *testing.T) { - stats := transactionStats{ - insertionTime: time.Now().Add(-time.Hour), - } - - isRotten := time.Since(stats.insertionTime) > time.Minute - require.True(t, isRotten) - stats.ResetStats() - isRotten = time.Since(stats.insertionTime) > time.Minute - require.False(t, isRotten) -} diff --git a/dela/core/txn/pool/transactions.go b/dela/core/txn/pool/transactions.go deleted file mode 100644 index 416de01..0000000 --- a/dela/core/txn/pool/transactions.go +++ /dev/null @@ -1,57 +0,0 @@ -package pool - -import ( - "bytes" - "sort" - - "go.dedis.ch/dela/core/txn" -) - -// Transactions is a sortable list of transactions. -// -// - implements sort.Interface -type transactions []transactionStats - -// Len implements sort.Interface. It returns the length of the list. -func (txs transactions) Len() int { - return len(txs) -} - -// Less implements sort.Interface. It returns true if the nonce of the ith -// transaction is smaller than the jth. -func (txs transactions) Less(i, j int) bool { - return txs[i].GetNonce() < txs[j].GetNonce() -} - -// Swap implements sort.Interface. It swaps the ith and the jth transactions. -func (txs transactions) Swap(i, j int) { - txs[i], txs[j] = txs[j], txs[i] -} - -// Add adds the transaction to the list if and only if the nonce is unique. The -// resulting list will be sorted by nonce. -func (txs transactions) Add(other transactionStats) transactions { - for _, tx := range txs { - if tx.GetNonce() == other.GetNonce() { - return txs - } - } - - list := append(txs, other) - sort.Sort(list) - - return list -} - -// Remove removes the transaction from the list if it exists, while preserving -// the order of the transactions. -func (txs transactions) Remove(other txn.Transaction) transactions { - for i, tx := range txs { - if bytes.Equal(tx.GetID(), other.GetID()) { - txs = append(txs[:i], txs[i+1:]...) - break - } - } - - return txs -} diff --git a/dela/core/txn/signed/controller/controller.go b/dela/core/txn/signed/controller/controller.go deleted file mode 100644 index 0873c4e..0000000 --- a/dela/core/txn/signed/controller/controller.go +++ /dev/null @@ -1,88 +0,0 @@ -// Package controller implements a CLI controller to inject a transaction -// manager. -// -// Documentation Last Review: 08.10.2020 -package controller - -import ( - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/ordering" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/cosi" -) - -// MgrController is a CLI controller that will inject a transaction manager -// using the signer of the collective signing component. -// -// - implements node.Initializer -type mgrController struct{} - -// NewManagerController creates a new controller that will inject a transaction -// manager in the context. -func NewManagerController() node.Initializer { - return mgrController{} -} - -// SetCommands implements node.Initializer. It does not register any command. -func (mgrController) SetCommands(node.Builder) {} - -// OnStart implements node.Initializer. It creates a transaction manager using -// the signer of the collective signing component and injects it. -func (mgrController) OnStart(flags cli.Flags, inj node.Injector) error { - var srvc ordering.Service - err := inj.Resolve(&srvc) - if err != nil { - return err - } - - var nonceMgr validation.Service - err = inj.Resolve(&nonceMgr) - if err != nil { - return err - } - - var c cosi.CollectiveSigning - err = inj.Resolve(&c) - if err != nil { - return err - } - - mgr := signed.NewManager(c.GetSigner(), client{ - srvc: srvc, - mgr: nonceMgr, - }) - - inj.Inject(mgr) - - return nil -} - -// OnStop implements node.initializer. It does nothing. -func (mgrController) OnStop(node.Injector) error { - return nil -} - -// Client is a local client for the manager to read the current identity's nonce -// from the ordering service. -// -// - implements signed.Client -type client struct { - srvc ordering.Service - mgr validation.Service -} - -// GetNonce implements signed.Client. It reads the store of the ordering service -// to get the next nonce of the identity and returns it. -func (c client) GetNonce(ident access.Identity) (uint64, error) { - store := c.srvc.GetStore() - - nonce, err := c.mgr.GetNonce(store, ident) - if err != nil { - return 0, err - } - - return nonce, nil -} diff --git a/dela/core/txn/signed/example_test.go b/dela/core/txn/signed/example_test.go deleted file mode 100644 index 51e6fae..0000000 --- a/dela/core/txn/signed/example_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package signed - -import ( - "fmt" - - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/crypto/bls" -) - -func ExampleTransactionManager_Make() { - signer := bls.NewSigner() - - manager := NewManager(signer, exampleClient{nonce: 5}) - - tx, err := manager.Make() - if err != nil { - panic("failed to create first transaction: " + err.Error()) - } - - fmt.Println(tx.GetNonce()) - - err = manager.Sync() - if err != nil { - panic("failed to synchronize: " + err.Error()) - } - - tx, err = manager.Make() - if err != nil { - panic("failed to create second transaction: " + err.Error()) - } - - fmt.Println(tx.GetNonce()) - - // Output: 0 - // 5 -} - -// exampleClient is an example of a manager client. It always synchronize the -// manager to the nonce value. -// -// - implements signed.Client -type exampleClient struct { - nonce uint64 -} - -// GetNonce implements signed.Client. It always return the same nonce for -// simplicity. -func (cl exampleClient) GetNonce(identity access.Identity) (uint64, error) { - return cl.nonce, nil -} diff --git a/dela/core/txn/signed/json/json.go b/dela/core/txn/signed/json/json.go deleted file mode 100644 index 91ac9e4..0000000 --- a/dela/core/txn/signed/json/json.go +++ /dev/null @@ -1,142 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - signed.RegisterTransactionFormat(serde.FormatJSON, txFormat{}) -} - -// TransactionJSON is the JSON message of a transaction. -type TransactionJSON struct { - Nonce uint64 - Args map[string][]byte - PublicKey json.RawMessage - Signature json.RawMessage -} - -// TxFormat is the JSON format engine for transactions. -// -// - implements serde.FormatEngine -type txFormat struct { - hashFactory crypto.HashFactory -} - -// Encode implements serde.FormatEngine. It returns the JSON data of the -// provided transaction if appropriate, otherwise it returns an error. -func (fmt txFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - tx, ok := msg.(*signed.Transaction) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - if tx.GetSignature() == nil { - return nil, xerrors.New("signature is missing") - } - - args := map[string][]byte{} - for _, arg := range tx.GetArgs() { - args[arg] = tx.GetArg(arg) - } - - pubkey, err := tx.GetIdentity().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to encode public key: %v", err) - } - - sig, err := tx.GetSignature().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("failed to encode signature: %v", err) - } - - m := TransactionJSON{ - Nonce: tx.GetNonce(), - Args: args, - PublicKey: pubkey, - Signature: sig, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("failed to marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It returns the transaction from the -// JSON data if appropriate, otherwise it returns an error. -func (fmt txFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := TransactionJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal: %v", err) - } - - pubkey, err := decodeIdentity(ctx, m.PublicKey) - if err != nil { - return nil, xerrors.Errorf("public key: %v", err) - } - - sig, err := decodeSignature(ctx, m.Signature) - if err != nil { - return nil, xerrors.Errorf("signature: %v", err) - } - - args := make([]signed.TransactionOption, 0, len(m.Args)+2) - for key, value := range m.Args { - args = append(args, signed.WithArg(key, value)) - } - - args = append(args, signed.WithSignature(sig)) - - if fmt.hashFactory != nil { - args = append(args, signed.WithHashFactory(fmt.hashFactory)) - } - - tx, err := signed.NewTransaction(m.Nonce, pubkey, args...) - if err != nil { - return nil, xerrors.Errorf("failed to create tx: %v", err) - } - - return tx, nil -} - -func decodeIdentity(ctx serde.Context, data []byte) (crypto.PublicKey, error) { - fac := ctx.GetFactory(signed.PublicKeyFac{}) - - factory, ok := fac.(common.PublicKeyFactory) - if !ok { - return nil, xerrors.Errorf("invalid factory '%T'", fac) - } - - pubkey, err := factory.PublicKeyOf(ctx, data) - if err != nil { - return nil, xerrors.Errorf("malformed: %v", err) - } - - return pubkey, nil -} - -func decodeSignature(ctx serde.Context, data []byte) (crypto.Signature, error) { - fac := ctx.GetFactory(signed.SignatureFac{}) - - factory, ok := fac.(crypto.SignatureFactory) - if !ok { - return nil, xerrors.Errorf("invalid factory '%T'", fac) - } - - sig, err := factory.SignatureOf(ctx, data) - if err != nil { - return nil, xerrors.Errorf("malformed: %v", err) - } - - return sig, nil -} diff --git a/dela/core/txn/signed/json/json_test.go b/dela/core/txn/signed/json/json_test.go deleted file mode 100644 index c8fbe89..0000000 --- a/dela/core/txn/signed/json/json_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestTxFormat_Encode(t *testing.T) { - format := txFormat{} - - ctx := fake.NewContext() - - tx := makeTx(t, 1, fake.PublicKey{}, signed.WithArg("A", []byte{1})) - - data, err := format.Encode(ctx, tx) - require.NoError(t, err) - require.Equal(t, `{"Nonce":1,"Args":{"A":"AQ=="},"PublicKey":{},"Signature":{}}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(ctx, &signed.Transaction{}) - require.EqualError(t, err, "signature is missing") - - badTx := makeTx(t, 0, fake.PublicKey{}, signed.WithSignature(fake.NewBadSignature())) - _, err = format.Encode(ctx, badTx) - require.EqualError(t, err, fake.Err("failed to encode signature")) - - _, err = format.Encode(fake.NewBadContext(), tx) - require.EqualError(t, err, fake.Err("failed to marshal")) - - tx = makeTx(t, 0, badPublicKey{}) - _, err = format.Encode(fake.NewBadContextWithDelay(1), tx) - require.EqualError(t, err, fake.Err("failed to encode public key")) -} - -func TestTxFormat_Decode(t *testing.T) { - format := txFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, signed.PublicKeyFac{}, fake.PublicKeyFactory{}) - ctx = serde.WithFactory(ctx, signed.SignatureFac{}, fake.SignatureFactory{}) - - msg, err := format.Decode(ctx, []byte(`{"Nonce":2,"Args":{"B":"AQ=="}}`)) - require.NoError(t, err) - expected := makeTx(t, 2, fake.PublicKey{}, signed.WithArg("B", []byte{1})) - require.Equal(t, expected, msg) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("failed to unmarshal")) - - format.hashFactory = fake.NewHashFactory(fake.NewBadHash()) - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, - fake.Err("failed to create tx: couldn't fingerprint tx: couldn't write nonce")) - - badCtx := serde.WithFactory(ctx, signed.PublicKeyFac{}, nil) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, "public key: invalid factory ''") - - badCtx = serde.WithFactory(ctx, signed.PublicKeyFac{}, fake.NewBadPublicKeyFactory()) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("public key: malformed")) - - badCtx = serde.WithFactory(ctx, signed.SignatureFac{}, nil) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, "signature: invalid factory ''") - - badCtx = serde.WithFactory(ctx, signed.SignatureFac{}, fake.NewBadSignatureFactory()) - _, err = format.Decode(badCtx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("signature: malformed")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func makeTx(t *testing.T, nonce uint64, - pk crypto.PublicKey, opts ...signed.TransactionOption) txn.Transaction { - - opts = append([]signed.TransactionOption{signed.WithSignature(fake.Signature{})}, opts...) - - tx, err := signed.NewTransaction(nonce, pk, opts...) - require.NoError(t, err) - - return tx -} - -type badPublicKey struct { - crypto.PublicKey -} - -func (badPublicKey) Serialize(serde.Context) ([]byte, error) { - return nil, fake.GetError() -} - -func (badPublicKey) MarshalBinary() ([]byte, error) { - return []byte{}, nil -} - -func (badPublicKey) Verify([]byte, crypto.Signature) error { - return nil -} diff --git a/dela/core/txn/signed/signed.go b/dela/core/txn/signed/signed.go deleted file mode 100644 index 6578789..0000000 --- a/dela/core/txn/signed/signed.go +++ /dev/null @@ -1,334 +0,0 @@ -// Package signed is an implementation of the transaction abstraction. -// -// It uses a signature to make sure the identity owns the transaction. The nonce -// is a monotonically increasing number that is used to prevent a replay attack -// of an existing transaction. -// -// Documentation Last Review: 08.10.2020 -package signed - -import ( - "encoding/binary" - "io" - "sort" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var txFormats = registry.NewSimpleRegistry() - -// RegisterTransactionFormat registers the engine for the provided format. -func RegisterTransactionFormat(f serde.Format, e serde.FormatEngine) { - txFormats.Register(f, e) -} - -// Transaction is a signed transaction using a nonce to protect itself against -// replay attack. -// -// - implements txn.Transaction -type Transaction struct { - nonce uint64 - args map[string][]byte - pubkey crypto.PublicKey - sig crypto.Signature - hash []byte -} - -type template struct { - Transaction - - hashFactory crypto.HashFactory -} - -// TransactionOption is the type of options to create a transaction. -type TransactionOption func(*template) - -// WithArg is an option to set an argument with the key and the value. -func WithArg(key string, value []byte) TransactionOption { - return func(tmpl *template) { - tmpl.args[key] = value - } -} - -// WithSignature is an option to set a valid signature. The signature will be -// verified against the identity. -func WithSignature(sig crypto.Signature) TransactionOption { - return func(tmpl *template) { - tmpl.sig = sig - } -} - -// WithHashFactory is an option to set a different hash factory when creating a -// transaction. -func WithHashFactory(f crypto.HashFactory) TransactionOption { - return func(tmpl *template) { - tmpl.hashFactory = f - } -} - -// NewTransaction creates a new transaction with the provided nonce. -func NewTransaction(nonce uint64, pk crypto.PublicKey, opts ...TransactionOption) (*Transaction, error) { - tmpl := template{ - Transaction: Transaction{ - nonce: nonce, - pubkey: pk, - args: make(map[string][]byte), - }, - hashFactory: crypto.NewSha256Factory(), - } - - for _, opt := range opts { - opt(&tmpl) - } - - h := tmpl.hashFactory.New() - err := tmpl.Fingerprint(h) - if err != nil { - return nil, xerrors.Errorf("couldn't fingerprint tx: %v", err) - } - - tmpl.hash = h.Sum(nil) - - if tmpl.sig != nil { - err := tmpl.pubkey.Verify(tmpl.hash, tmpl.sig) - if err != nil { - return nil, xerrors.Errorf("invalid signature: %v", err) - } - } - - return &tmpl.Transaction, nil -} - -// GetID implements txn.Transaction. It returns the ID of the transaction. -func (t *Transaction) GetID() []byte { - return t.hash -} - -// GetNonce implements txn.Transaction. It returns the nonce of the transaction. -func (t *Transaction) GetNonce() uint64 { - return t.nonce -} - -// GetIdentity implements txn.Transaction. It returns nil. -func (t *Transaction) GetIdentity() access.Identity { - return t.pubkey -} - -// GetSignature returns the signature of the transaction. -func (t *Transaction) GetSignature() crypto.Signature { - return t.sig -} - -// GetArgs returns the list of arguments available. -func (t *Transaction) GetArgs() []string { - args := make([]string, 0, len(t.args)) - for key := range t.args { - args = append(args, key) - } - - return args -} - -// GetArg implements txn.Transaction. It returns the value of the argument if it -// is set, otherwise nil. -func (t *Transaction) GetArg(key string) []byte { - return t.args[key] -} - -// Sign signs the transaction and stores the signature. -func (t *Transaction) Sign(signer crypto.Signer) error { - if len(t.hash) == 0 { - return xerrors.New("missing digest in transaction") - } - - if !signer.GetPublicKey().Equal(t.pubkey) { - return xerrors.New("mismatch signer and identity") - } - - sig, err := signer.Sign(t.hash) - if err != nil { - return xerrors.Errorf("signer: %v", err) - } - - t.sig = sig - - return nil -} - -// Fingerprint implements serde.Fingerprinter. It writes a deterministic binary -// representation of the transaction. -func (t *Transaction) Fingerprint(w io.Writer) error { - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer, t.nonce) - - _, err := w.Write(buffer) - if err != nil { - return xerrors.Errorf("couldn't write nonce: %v", err) - } - - // Sort the argument to deterministically write them to the hash. - args := make(sort.StringSlice, 0, len(t.args)) - for key := range t.args { - args = append(args, key) - } - - sort.Sort(args) - - for _, key := range args { - _, err = w.Write(append([]byte(key), t.args[key]...)) - if err != nil { - return xerrors.Errorf("couldn't write arg: %v", err) - } - } - - buffer, err = t.pubkey.MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal public key: %v", err) - } - - _, err = w.Write(buffer) - if err != nil { - return xerrors.Errorf("couldn't write public key: %v", err) - } - - return nil -} - -// Serialize implements serde.Message. It returns the serialized data of the -// transaction. -func (t *Transaction) Serialize(ctx serde.Context) ([]byte, error) { - format := txFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, t) - if err != nil { - return nil, xerrors.Errorf("failed to encode: %v", err) - } - - return data, nil -} - -// PublicKeyFac is the key of the public key factory. -type PublicKeyFac struct{} - -// SignatureFac is the key of the signature factory. -type SignatureFac struct{} - -// TransactionFactory is a factory to deserialize transactions. -// -// - implements serde.Factory -type TransactionFactory struct { - pubkeyFac common.PublicKeyFactory - sigFac common.SignatureFactory -} - -// NewTransactionFactory returns a new factory. -func NewTransactionFactory() TransactionFactory { - return TransactionFactory{ - pubkeyFac: common.NewPublicKeyFactory(), - sigFac: common.NewSignatureFactory(), - } -} - -// Deserialize implements serde.Factory. It populates the transaction from the -// data if appropriate, otherwise it returns an error. -func (f TransactionFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.TransactionOf(ctx, data) -} - -// TransactionOf implements txn.TransactionFactory. It populates the transaction -// from the data if appropriate, otherwise it returns an error. -func (f TransactionFactory) TransactionOf(ctx serde.Context, data []byte) (txn.Transaction, error) { - format := txFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, PublicKeyFac{}, f.pubkeyFac) - ctx = serde.WithFactory(ctx, SignatureFac{}, f.sigFac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("failed to decode: %v", err) - } - - tx, ok := msg.(*Transaction) - if !ok { - return nil, xerrors.Errorf("invalid transaction of type '%T'", msg) - } - - return tx, nil -} - -// Client is the interface the manager is using to get the nonce of an identity. -// It allows a local implementation, or through a network client. -type Client interface { - GetNonce(access.Identity) (uint64, error) -} - -// TransactionManager is a manager to create signed transactions. It manages the -// nonce by itself, except if the transaction is refused by the ledger. In that -// case the manager should be synchronized before creating a new one. -// -// - implements txn.Manager -type TransactionManager struct { - client Client - signer crypto.Signer - nonce uint64 - hashFac crypto.HashFactory -} - -// NewManager creates a new transaction manager. -// -// - implements txn.Manager -func NewManager(signer crypto.Signer, client Client) *TransactionManager { - return &TransactionManager{ - client: client, - signer: signer, - nonce: 0, - hashFac: crypto.NewSha256Factory(), - } -} - -// Make implements txn.Manager. It creates a transaction populated with the -// arguments. -func (mgr *TransactionManager) Make(args ...txn.Arg) (txn.Transaction, error) { - opts := make([]TransactionOption, len(args), len(args)+1) - for i, arg := range args { - opts[i] = WithArg(arg.Key, arg.Value) - } - - opts = append(opts, WithHashFactory(mgr.hashFac)) - - tx, err := NewTransaction(mgr.nonce, mgr.signer.GetPublicKey(), opts...) - if err != nil { - return nil, xerrors.Errorf("failed to create tx: %v", err) - } - - err = tx.Sign(mgr.signer) - if err != nil { - return nil, xerrors.Errorf("failed to sign: %v", err) - } - - mgr.nonce++ - - return tx, nil -} - -// Sync implements txn.Manager. It fetches the latest nonce of the signer to -// create valid transactions. -func (mgr *TransactionManager) Sync() error { - nonce, err := mgr.client.GetNonce(mgr.signer.GetPublicKey()) - if err != nil { - return xerrors.Errorf("client: %v", err) - } - - mgr.nonce = nonce - - dela.Logger.Debug().Uint64("nonce", nonce).Msg("manager synchronized") - - return nil -} diff --git a/dela/core/txn/signed/signed_test.go b/dela/core/txn/signed/signed_test.go deleted file mode 100644 index b47f796..0000000 --- a/dela/core/txn/signed/signed_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package signed - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterTransactionFormat(fake.GoodFormat, fake.Format{Msg: &Transaction{}}) - RegisterTransactionFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterTransactionFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) -} - -func TestTransaction_New(t *testing.T) { - signer := bls.NewSigner() - - tx, err := NewTransaction(0, signer.GetPublicKey()) - require.NoError(t, err) - require.NotNil(t, tx) - - require.NoError(t, tx.Sign(signer)) - - tx, err = NewTransaction(0, signer.GetPublicKey(), WithSignature(tx.GetSignature())) - require.NoError(t, err) - require.NotNil(t, tx.GetSignature()) - - _, err = NewTransaction(0, fake.PublicKey{}, WithHashFactory(fake.NewHashFactory(fake.NewBadHash()))) - require.EqualError(t, err, fake.Err("couldn't fingerprint tx: couldn't write nonce")) - - _, err = NewTransaction(1, signer.GetPublicKey(), WithSignature(tx.GetSignature())) - require.EqualError(t, err, "invalid signature: bls verify failed: bls: invalid signature") -} - -func TestTransaction_GetID(t *testing.T) { - tx, err := NewTransaction(0, fake.PublicKey{}) - require.NoError(t, err) - - id := tx.GetID() - require.Len(t, id, 32) -} - -func TestTransaction_GetNonce(t *testing.T) { - tx, err := NewTransaction(123, fake.PublicKey{}) - require.NoError(t, err) - - nonce := tx.GetNonce() - require.Equal(t, uint64(123), nonce) -} - -func TestTransaction_GetIdentity(t *testing.T) { - tx, err := NewTransaction(1, fake.PublicKey{}) - require.NoError(t, err) - require.Equal(t, fake.PublicKey{}, tx.GetIdentity()) -} - -func TestTransaction_GetArgs(t *testing.T) { - tx, err := NewTransaction(5, fake.PublicKey{}, WithArg("A", []byte{1}), WithArg("B", []byte{2})) - require.NoError(t, err) - - args := tx.GetArgs() - require.Contains(t, args, "A") - require.Contains(t, args, "B") -} - -func TestTransaction_GetArg(t *testing.T) { - tx, err := NewTransaction(5, fake.PublicKey{}, WithArg("A", []byte{1}), WithArg("B", []byte{2})) - require.NoError(t, err) - - value := tx.GetArg("A") - require.Equal(t, []byte{1}, value) - - value = tx.GetArg("B") - require.Equal(t, []byte{2}, value) - - value = tx.GetArg("C") - require.Nil(t, value) -} - -func TestTransaction_Sign(t *testing.T) { - signer := bls.NewSigner() - - tx, err := NewTransaction(2, signer.GetPublicKey(), WithArg("A", []byte{123})) - require.NoError(t, err) - - err = tx.Sign(signer) - require.NoError(t, err) - require.NoError(t, signer.GetPublicKey().Verify(tx.hash, tx.GetSignature())) - - tx.hash = nil - err = tx.Sign(signer) - require.EqualError(t, err, "missing digest in transaction") - - tx.hash = []byte{1} - err = tx.Sign(fake.Signer{}) - require.EqualError(t, err, "mismatch signer and identity") - - tx.pubkey = fake.PublicKey{} - err = tx.Sign(fake.NewBadSigner()) - require.EqualError(t, err, fake.Err("signer")) -} - -func TestTransaction_Fingerprint(t *testing.T) { - tx, err := NewTransaction(2, fake.PublicKey{}, WithArg("A", []byte{1, 2, 3})) - require.NoError(t, err) - - buffer := new(bytes.Buffer) - err = tx.Fingerprint(buffer) - require.NoError(t, err) - require.Equal(t, "\x02\x00\x00\x00\x00\x00\x00\x00A\x01\x02\x03PK", buffer.String()) - - err = tx.Fingerprint(fake.NewBadHash()) - require.EqualError(t, err, fake.Err("couldn't write nonce")) - - err = tx.Fingerprint(fake.NewBadHashWithDelay(1)) - require.EqualError(t, err, fake.Err("couldn't write arg")) - - err = tx.Fingerprint(fake.NewBadHashWithDelay(2)) - require.EqualError(t, err, fake.Err("couldn't write public key")) - - tx.pubkey = fake.NewBadPublicKey() - err = tx.Fingerprint(buffer) - require.EqualError(t, err, fake.Err("failed to marshal public key")) -} - -func TestTransaction_Serialize(t *testing.T) { - tx, err := NewTransaction(0, fake.PublicKey{}) - require.NoError(t, err) - - data, err := tx.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = tx.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("failed to encode")) -} - -func TestTransactionFactory_Deserialize(t *testing.T) { - factory := NewTransactionFactory() - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.IsType(t, &Transaction{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("failed to decode")) - - _, err = factory.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid transaction of type 'fake.Message'") -} - -func TestManager_Make(t *testing.T) { - mgr := NewManager(fake.NewSigner(), nil) - - tx, err := mgr.Make(txn.Arg{Key: "a", Value: []byte{1, 2, 3}}) - require.NoError(t, err) - require.Equal(t, uint64(0), tx.(*Transaction).nonce) - require.Equal(t, []byte{1, 2, 3}, tx.GetArg("a")) - - mgr.hashFac = fake.NewHashFactory(fake.NewBadHash()) - _, err = mgr.Make() - require.Error(t, err) - require.Contains(t, err.Error(), "failed to create tx: ") - - mgr.hashFac = crypto.NewSha256Factory() - mgr.signer = fake.NewBadSigner() - _, err = mgr.Make() - require.EqualError(t, err, fake.Err("failed to sign: signer")) -} - -func TestManager_Sync(t *testing.T) { - mgr := NewManager(fake.NewSigner(), fakeClient{}) - - err := mgr.Sync() - require.NoError(t, err) - require.Equal(t, uint64(42), mgr.nonce) - - mgr = NewManager(fake.NewSigner(), fakeClient{err: fake.GetError()}) - err = mgr.Sync() - require.EqualError(t, err, fake.Err("client")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeClient struct { - err error -} - -func (c fakeClient) GetNonce(access.Identity) (uint64, error) { - return 42, c.err -} diff --git a/dela/core/txn/txn.go b/dela/core/txn/txn.go deleted file mode 100644 index bf2d955..0000000 --- a/dela/core/txn/txn.go +++ /dev/null @@ -1,59 +0,0 @@ -// Package txn defines the abstraction of transactions. -// -// A transaction is a smart contract input. It is uniquely identifiable via a -// digest and it can be sorted with the nonce that acts as a sequence number. -// The transaction is also created by an identity that can be used for access -// control for instance. -// -// The manager helps to create transactions as the nonce needs to be correct for -// the transaction to be valid. -// -// Documentation Last Review: 08.10.2020 -package txn - -import ( - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/serde" -) - -// Transaction is what triggers a smart contract execution by passing it as part -// of the input. -type Transaction interface { - serde.Message - serde.Fingerprinter - - // GetID returns the unique identifier for the transaction. - GetID() []byte - - // GetNonce returns the nonce of the transaction which corresponds to the - // sequence number of a unique identity. - GetNonce() uint64 - - // GetIdentity returns the identity that created the transaction. - GetIdentity() access.Identity - - // GetArg is a getter for the arguments of the transaction. - GetArg(key string) []byte -} - -// Factory is the definition of a factory to deserialize transaction -// messages. -type Factory interface { - serde.Factory - - TransactionOf(serde.Context, []byte) (Transaction, error) -} - -// Arg is a generic argument that can be stored in a transaction. -type Arg struct { - Key string - Value []byte -} - -// Manager is a manager to create transaction. It can help creating -// transactions when some information is required like the current nonce. -type Manager interface { - Make(args ...Arg) (Transaction, error) - - Sync() error -} diff --git a/dela/core/validation/simple/example_test.go b/dela/core/validation/simple/example_test.go deleted file mode 100644 index 93afd92..0000000 --- a/dela/core/validation/simple/example_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package simple - -import ( - "fmt" - "sync" - - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/execution/native" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/txn/signed" - "go.dedis.ch/dela/crypto/bls" -) - -func ExampleService_GetNonce() { - - exec := native.NewExecution() - - srvc := NewService(exec, signed.NewTransactionFactory()) - - signer := bls.NewSigner() - store := newStore() - - nonce, err := srvc.GetNonce(store, signer.GetPublicKey()) - if err != nil { - panic("cannot get nonce: " + err.Error()) - } - - fmt.Println(nonce) - - // Output: 0 -} - -func ExampleService_Validate() { - - exec := native.NewExecution() - exec.Set("example", exampleContract{}) - - srvc := NewService(exec, signed.NewTransactionFactory()) - - signer := bls.NewSigner() - arg := signed.WithArg(native.ContractArg, []byte("example")) - - txA, err := signed.NewTransaction(0, signer.GetPublicKey(), arg) - if err != nil { - panic("cannot create transaction A: " + err.Error()) - } - txB, err := signed.NewTransaction(1, signer.GetPublicKey(), arg) - if err != nil { - panic("cannot create transaction B: " + err.Error()) - } - txC, err := signed.NewTransaction(3, signer.GetPublicKey(), arg) - if err != nil { - panic("cannot create transaction C: " + err.Error()) - } - - store := newStore() - - res, err := srvc.Validate(store, []txn.Transaction{txA, txB, txC}) - if err != nil { - panic("validation failed: " + err.Error()) - } - - for _, txRes := range res.GetTransactionResults() { - accepted, reason := txRes.GetStatus() - - if accepted { - fmt.Println("accepted") - } else { - fmt.Println("refused", reason) - } - } - - // Output: accepted - // accepted - // refused nonce is invalid, expected 2, got 3 -} - -// exampleContract is an example of a contract doing nothing. -// -// - implements baremetal.Contract -type exampleContract struct{} - -// Execute implements baremetal.Contract -func (exampleContract) Execute(store.Snapshot, execution.Step) error { - return nil -} - -// inMemoryStore in a simple implementation of a store using an in-memory -// map. -// -// - implements store.Snapshot -type inMemoryStore struct { - sync.Mutex - - entries map[string][]byte -} - -func newStore() *inMemoryStore { - return &inMemoryStore{ - entries: make(map[string][]byte), - } -} - -// Get implements store.Readable. It returns the value associated to the key. -func (s *inMemoryStore) Get(key []byte) ([]byte, error) { - s.Lock() - defer s.Unlock() - - return s.entries[string(key)], nil -} - -// Set implements store.Writable. It sets the value for the key. -func (s *inMemoryStore) Set(key, value []byte) error { - s.Lock() - s.entries[string(key)] = value - s.Unlock() - - return nil -} - -// Delete implements store.Writable. It deletes the key from the store. -func (s *inMemoryStore) Delete(key []byte) error { - s.Lock() - delete(s.entries, string(key)) - s.Unlock() - - return nil -} diff --git a/dela/core/validation/simple/json/json.go b/dela/core/validation/simple/json/json.go deleted file mode 100644 index 1d09926..0000000 --- a/dela/core/validation/simple/json/json.go +++ /dev/null @@ -1,141 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation/simple" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - simple.RegisterTransactionResultFormat(serde.FormatJSON, txResFormat{}) - simple.RegisterResultFormat(serde.FormatJSON, resFormat{}) -} - -// TransactionResultJSON is the JSON message for transaction results. -type TransactionResultJSON struct { - Transaction json.RawMessage - Accepted bool - Reason string -} - -// ResultJSON is the JSON message for results. -type ResultJSON struct { - Results []json.RawMessage -} - -type txResFormat struct{} - -func (f txResFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - txres, ok := msg.(simple.TransactionResult) - if !ok { - return nil, xerrors.Errorf("unsupported message") - } - - tx, err := txres.GetTransaction().Serialize(ctx) - if err != nil { - return nil, err - } - - accepted, reason := txres.GetStatus() - - m := TransactionResultJSON{ - Transaction: tx, - Accepted: accepted, - Reason: reason, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, err - } - - return data, nil -} - -func (f txResFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := TransactionResultJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, err - } - - factory := ctx.GetFactory(simple.TransactionKey{}) - - fac, ok := factory.(txn.Factory) - if !ok { - return nil, xerrors.Errorf("invalid transaction factory") - } - - tx, err := fac.TransactionOf(ctx, m.Transaction) - if err != nil { - return nil, err - } - - res := simple.NewTransactionResult(tx, m.Accepted, m.Reason) - - return res, nil -} - -type resFormat struct{} - -func (f resFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - res, ok := msg.(simple.Result) - if !ok { - return nil, xerrors.Errorf("unsupported message") - } - - results := res.GetTransactionResults() - raws := make([]json.RawMessage, len(results)) - - for i, res := range results { - buffer, err := res.Serialize(ctx) - if err != nil { - return nil, err - } - - raws[i] = buffer - } - - m := ResultJSON{ - Results: raws, - } - - buffer, err := ctx.Marshal(m) - if err != nil { - return nil, err - } - - return buffer, nil -} - -func (f resFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := ResultJSON{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, err - } - - factory := ctx.GetFactory(simple.ResultKey{}) - - results := make([]simple.TransactionResult, len(m.Results)) - for i, raw := range m.Results { - msg, err := factory.Deserialize(ctx, raw) - if err != nil { - return nil, err - } - - res, ok := msg.(simple.TransactionResult) - if !ok { - return nil, xerrors.Errorf("invalid transaction result") - } - - results[i] = res - } - - res := simple.NewResult(results) - - return res, nil -} diff --git a/dela/core/validation/simple/result.go b/dela/core/validation/simple/result.go deleted file mode 100644 index 988aa8d..0000000 --- a/dela/core/validation/simple/result.go +++ /dev/null @@ -1,212 +0,0 @@ -// This file contains the implementation of the result returned by the service. -// -// Documentation Last Review: 08.10.2020 -// - -package simple - -import ( - "io" - - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var ( - txResFormats = registry.NewSimpleRegistry() - resFormats = registry.NewSimpleRegistry() -) - -// RegisterTransactionResultFormat registers the engine for the provided format. -func RegisterTransactionResultFormat(f serde.Format, e serde.FormatEngine) { - txResFormats.Register(f, e) -} - -// RegisterResultFormat registers the engine for the provided format. -func RegisterResultFormat(f serde.Format, e serde.FormatEngine) { - resFormats.Register(f, e) -} - -// TransactionResult is the result of a transaction processing. It contains the -// transaction and its state of success. -// -// - implements validation.TransactionResult -type TransactionResult struct { - tx txn.Transaction - accepted bool - reason string -} - -// NewTransactionResult creates a new transaction result for the provided -// transaction. -func NewTransactionResult(tx txn.Transaction, accepted bool, reason string) TransactionResult { - return TransactionResult{ - tx: tx, - accepted: accepted, - reason: reason, - } -} - -// GetTransaction implements validation.TransactionResult. It returns the -// transaction associated to the result. -func (res TransactionResult) GetTransaction() txn.Transaction { - return res.tx -} - -// GetStatus implements validation.TransactionResult. It returns true if the -// transaction has been accepted, otherwise false with the reason. -func (res TransactionResult) GetStatus() (bool, string) { - return res.accepted, res.reason -} - -// Serialize implements serde.Message. It returns the transaction result -// serialized. -func (res TransactionResult) Serialize(ctx serde.Context) ([]byte, error) { - format := txResFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, res) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// TransactionKey is the key of the transaction factory. -type TransactionKey struct{} - -// TransactionResultFactory is the factory to deserialize transaction results. -// -// - implements serde.Factory -type TransactionResultFactory struct { - fac txn.Factory -} - -// NewTransactionResultFactory creates a new transaction result factory. -func NewTransactionResultFactory(f txn.Factory) TransactionResultFactory { - return TransactionResultFactory{ - fac: f, - } -} - -// Deserialize implements serde.Factory. It populates the transaction result if -// appropriate, otherwise it returns an error. -func (f TransactionResultFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := txResFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, TransactionKey{}, f.fac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding failed: %v", err) - } - - return msg, nil -} - -// Result is the result of a standard validation. -// -// - implements validation.Result -type Result struct { - txs []TransactionResult -} - -// NewResult creates a new result from a list of transaction results. -func NewResult(results []TransactionResult) Result { - return Result{ - txs: results, - } -} - -// GetTransactionResults implements validation.Result. It returns the -// transaction results. -func (d Result) GetTransactionResults() []validation.TransactionResult { - res := make([]validation.TransactionResult, len(d.txs)) - for i, r := range d.txs { - res[i] = r - } - - return res -} - -// Fingerprint implements serde.Fingerprinter. It writes a deterministic binary -// representation of the result. -func (d Result) Fingerprint(w io.Writer) error { - for _, res := range d.txs { - err := res.tx.Fingerprint(w) - if err != nil { - return xerrors.Errorf("couldn't fingerprint tx: %v", err) - } - - bit := []byte{0} - if res.accepted { - bit[0] = 1 - } - - _, err = w.Write(bit) - if err != nil { - return xerrors.Errorf("couldn't write accepted: %v", err) - } - } - - return nil -} - -// Serialize implements serde.Message. It returns the serialized data of the -// result. -func (d Result) Serialize(ctx serde.Context) ([]byte, error) { - format := resFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, d) - if err != nil { - return nil, xerrors.Errorf("encoding failed: %v", err) - } - - return data, nil -} - -// ResultKey is the key of the transaction result factory. -type ResultKey struct{} - -// ResultFactory is the factory to deserialize results. -// -// - implements validation.ResultFactory -type ResultFactory struct { - fac serde.Factory -} - -// NewResultFactory creates a new result factory. -func NewResultFactory(f txn.Factory) ResultFactory { - return ResultFactory{ - fac: NewTransactionResultFactory(f), - } -} - -// Deserialize implements serde.Factory. It populates the result if appropriate, -// otherwise it returns an error. -func (f ResultFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.ResultOf(ctx, data) -} - -// ResultOf implements validation.ResultFactory. It returns the result from the -// serialized data if appropriate, otherwise it returns an error. -func (f ResultFactory) ResultOf(ctx serde.Context, data []byte) (validation.Result, error) { - format := resFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, ResultKey{}, f.fac) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("decoding failed: %v", err) - } - - res, ok := msg.(Result) - if !ok { - return nil, xerrors.Errorf("invalid result type '%T'", msg) - } - - return res, nil -} diff --git a/dela/core/validation/simple/result_test.go b/dela/core/validation/simple/result_test.go deleted file mode 100644 index bcb836f..0000000 --- a/dela/core/validation/simple/result_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package simple - -import ( - "bytes" - "io" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterResultFormat(fake.GoodFormat, fake.Format{Msg: Result{}}) - RegisterResultFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterResultFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterTransactionResultFormat(fake.GoodFormat, fake.Format{Msg: TransactionResult{}}) - RegisterTransactionResultFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestTransactionResult_GetTransaction(t *testing.T) { - res := NewTransactionResult(fakeTx{}, true, "") - require.Equal(t, fakeTx{}, res.GetTransaction()) -} - -func TestTransactionResult_GetStatus(t *testing.T) { - res := NewTransactionResult(fakeTx{}, true, "") - - accepted, reason := res.GetStatus() - require.True(t, accepted) - require.Equal(t, "", reason) -} - -func TestTransactionResult_Serialize(t *testing.T) { - res := NewTransactionResult(fakeTx{}, true, "") - - data, err := res.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = res.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestTransactionResultFactory_Deserialize(t *testing.T) { - fac := NewTransactionResultFactory(nil) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, TransactionResult{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding failed")) -} - -func TestResult_GetTransactionResults(t *testing.T) { - res := Result{ - txs: []TransactionResult{{}, {}}, - } - - require.Len(t, res.GetTransactionResults(), 2) -} - -func TestResult_Fingerprint(t *testing.T) { - res := Result{ - txs: []TransactionResult{ - {tx: fakeTx{}}, - {tx: fakeTx{}, accepted: true}, - }, - } - - buffer := new(bytes.Buffer) - err := res.Fingerprint(buffer) - require.NoError(t, err) - - err = res.Fingerprint(fake.NewBadHash()) - require.EqualError(t, err, fake.Err("couldn't write accepted")) - - res.txs[0].tx = fakeTx{err: fake.GetError()} - err = res.Fingerprint(buffer) - require.EqualError(t, err, fake.Err("couldn't fingerprint tx")) -} - -func TestResult_Serialize(t *testing.T) { - res := NewResult(nil) - - data, err := res.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = res.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("encoding failed")) -} - -func TestResultFactory_Deserialize(t *testing.T) { - fac := NewResultFactory(nil) - - msg, err := fac.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, Result{}, msg) - - _, err = fac.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("decoding failed")) - - _, err = fac.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid result type 'fake.Message'") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeTx struct { - txn.Transaction - - nonce uint64 - pubkey crypto.PublicKey - err error -} - -func newTx() fakeTx { - return fakeTx{ - pubkey: fake.PublicKey{}, - } -} - -func (tx fakeTx) GetID() []byte { - return []byte{0xa, 0xb, 0xc, 0xd} -} - -func (tx fakeTx) GetIdentity() access.Identity { - return tx.pubkey -} - -func (tx fakeTx) GetNonce() uint64 { - return tx.nonce -} - -func (tx fakeTx) Fingerprint(io.Writer) error { - return tx.err -} diff --git a/dela/core/validation/simple/simple.go b/dela/core/validation/simple/simple.go deleted file mode 100644 index 5f901a5..0000000 --- a/dela/core/validation/simple/simple.go +++ /dev/null @@ -1,187 +0,0 @@ -// Package simple implements a validation service that executes a batch -// of transactions sequentially. -// -// Documentation Last Review: 08.10.2020 -package simple - -import ( - "encoding/binary" - "fmt" - - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/crypto" - "golang.org/x/xerrors" -) - -// Service is a standard validation service that will process the batch and -// update the snapshot accordingly. -// -// - implements validation.Service -type Service struct { - execution execution.Service - fac validation.ResultFactory - hashFac crypto.HashFactory -} - -// NewService creates a new validation service. -func NewService(exec execution.Service, f txn.Factory) Service { - return Service{ - execution: exec, - fac: NewResultFactory(f), - hashFac: crypto.NewSha256Factory(), - } -} - -// GetFactory implements validation.Service. It returns the result factory. -func (s Service) GetFactory() validation.ResultFactory { - return s.fac -} - -// GetNonce implements validation.Service. It reads the latest nonce in the -// storage for the given identity and returns the next valid nonce. -func (s Service) GetNonce(store store.Readable, ident access.Identity) (uint64, error) { - if ident == nil { - return 0, xerrors.New("missing identity in transaction") - } - - key, err := s.keyFromIdentity(ident) - if err != nil { - return 0, xerrors.Errorf("key: %v", err) - } - - value, err := store.Get(key) - if err != nil { - return 0, xerrors.Errorf("store: %v", err) - } - - if value == nil || len(value) != 8 { - return 0, nil - } - - return binary.LittleEndian.Uint64(value) + 1, nil -} - -// Accept implements validation.Service. It returns nil if the transaction would -// be accepted by the service given some leeway and a snapshot of the storage. -func (s Service) Accept(store store.Readable, tx txn.Transaction, leeway validation.Leeway) error { - nonce, err := s.GetNonce(store, tx.GetIdentity()) - if err != nil { - return xerrors.Errorf("while reading nonce: %v", err) - } - - if tx.GetNonce() < nonce { - return xerrors.Errorf("nonce '%d' < '%d'", tx.GetNonce(), nonce) - } - - limit := nonce + uint64(leeway.MaxSequenceDifference) - - if tx.GetNonce() > limit { - return xerrors.Errorf("nonce '%d' above the limit '%d'", tx.GetNonce(), limit) - } - - return nil -} - -// Validate implements validation.Service. It processes the list of transactions -// while updating the snapshot then returns a bundle of the transaction results. -func (s Service) Validate(store store.Snapshot, txs []txn.Transaction) (validation.Result, error) { - results := make([]TransactionResult, len(txs)) - - step := execution.Step{ - Previous: make([]txn.Transaction, 0, len(txs)), - } - - for i, tx := range txs { - res := TransactionResult{tx: tx} - - step.Current = tx - - err := s.validateTx(store, step, &res) - if err != nil { - return nil, xerrors.Errorf("tx %#x: %v", tx.GetID()[:4], err) - } - - if res.accepted { - step.Previous = append(step.Previous, tx) - } - - results[i] = res - } - - res := Result{ - txs: results, - } - - return res, nil -} - -func (s Service) validateTx(store store.Snapshot, step execution.Step, r *TransactionResult) error { - expectedNonce, err := s.GetNonce(store, step.Current.GetIdentity()) - if err != nil { - return xerrors.Errorf("nonce: %v", err) - } - - if expectedNonce != step.Current.GetNonce() { - r.reason = fmt.Sprintf("nonce is invalid, expected %d, got %d", - expectedNonce, step.Current.GetNonce()) - r.accepted = false - - return nil - } - - res, err := s.execution.Execute(store, step) - // if the execution fail, we don't return an error, but we take it as an - // invalid transaction. - if err != nil { - r.reason = xerrors.Errorf("failed to execute transaction: %v", err).Error() - r.accepted = false - } else { - r.reason = res.Message - r.accepted = res.Accepted - } - - // Update the nonce associated to the identity so that this transaction - // cannot be applied again. - err = s.set(store, step.Current.GetIdentity(), step.Current.GetNonce()) - if err != nil { - return xerrors.Errorf("failed to set nonce: %v", err) - } - - return nil -} - -func (s Service) set(store store.Snapshot, ident access.Identity, nonce uint64) error { - key, err := s.keyFromIdentity(ident) - if err != nil { - return xerrors.Errorf("key: %v", err) - } - - buffer := make([]byte, 8) - binary.LittleEndian.PutUint64(buffer, nonce) - - err = store.Set(key, buffer) - if err != nil { - return xerrors.Errorf("store: %v", err) - } - - return nil -} - -func (s Service) keyFromIdentity(ident access.Identity) ([]byte, error) { - data, err := ident.MarshalText() - if err != nil { - return nil, xerrors.Errorf("failed to marshal identity: %v", err) - } - - h := s.hashFac.New() - _, err = h.Write(data) - if err != nil { - return nil, xerrors.Errorf("failed to write identity: %v", err) - } - - return h.Sum(nil), nil -} diff --git a/dela/core/validation/simple/simple_test.go b/dela/core/validation/simple/simple_test.go deleted file mode 100644 index b8e81ee..0000000 --- a/dela/core/validation/simple/simple_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package simple - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/core/execution" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/core/validation" - "go.dedis.ch/dela/internal/testing/fake" - "golang.org/x/xerrors" -) - -func TestService_GetFactory(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - require.NotNil(t, srvc.GetFactory()) -} - -func TestService_GetNonce(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - nonce, err := srvc.GetNonce(fakeSnapshot{}, fake.PublicKey{}) - require.NoError(t, err) - require.Equal(t, uint64(0), nonce) - - buffer := make([]byte, 8) - buffer[0] = 2 - nonce, err = srvc.GetNonce(fakeSnapshot{value: buffer}, fake.PublicKey{}) - require.NoError(t, err) - require.Equal(t, uint64(3), nonce) - - _, err = srvc.GetNonce(fakeSnapshot{}, fake.NewBadPublicKey()) - require.EqualError(t, err, fake.Err("key: failed to marshal identity")) - - _, err = srvc.GetNonce(fakeSnapshot{errGet: fake.GetError()}, fake.PublicKey{}) - require.EqualError(t, err, fake.Err("store")) -} - -func TestService_Accept(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - tx := newTx() - tx.nonce = 5 - - err := srvc.Accept(fakeSnapshot{}, tx, validation.Leeway{MaxSequenceDifference: 5}) - require.NoError(t, err) -} - -func TestService_NilIdentity_Accept(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - err := srvc.Accept(fakeSnapshot{}, fakeTx{}, validation.Leeway{}) - require.EqualError(t, err, "while reading nonce: missing identity in transaction") -} - -func TestService_OlderNonce_Accept(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - value := make([]byte, 8) - value[0] = 5 - - err := srvc.Accept(fakeSnapshot{value: value}, newTx(), validation.Leeway{}) - require.EqualError(t, err, "nonce '0' < '6'") -} - -func TestService_FutureNonce_Accept(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - tx := newTx() - tx.nonce = 5 - - err := srvc.Accept(fakeSnapshot{}, tx, validation.Leeway{MaxSequenceDifference: 1}) - require.EqualError(t, err, "nonce '5' above the limit '1'") -} - -func TestService_Validate(t *testing.T) { - exec := &fakeExec{check: true} - srvc := NewService(exec, nil) - - res, err := srvc.Validate(fakeSnapshot{}, []txn.Transaction{newTx(), newTx(), newTx()}) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, 3, exec.count) - - tx := newTx() - tx.nonce = 1 - res, err = srvc.Validate(fakeSnapshot{}, []txn.Transaction{tx}) - require.NoError(t, err) - - status, _ := res.GetTransactionResults()[0].GetStatus() - require.False(t, status) -} - -func TestService_NilIdentity_Validate(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - _, err := srvc.Validate(fakeSnapshot{}, []txn.Transaction{fakeTx{}}) - require.EqualError(t, err, "tx 0x0a0b0c0d: nonce: missing identity in transaction") -} - -func TestService_FailStore_Validate(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - - store := fakeSnapshot{errSet: fake.GetError()} - - _, err := srvc.Validate(store, []txn.Transaction{newTx()}) - require.EqualError(t, err, fake.Err("tx 0x0a0b0c0d: failed to set nonce: store")) -} - -func TestService_FailIdentityToKey_Validate(t *testing.T) { - srvc := NewService(&fakeExec{}, nil) - srvc.hashFac = fake.NewHashFactory(fake.NewBadHash()) - - err := srvc.set(fakeSnapshot{}, fake.PublicKey{}, 0) - require.EqualError(t, err, fake.Err("key: failed to write identity")) -} - -func TestService_FailExecuteTx_Validate(t *testing.T) { - srvc := NewService(&fakeExec{err: fake.GetError()}, nil) - - res, err := srvc.Validate(fakeSnapshot{}, []txn.Transaction{newTx()}) - require.NoError(t, err) - - status, msg := res.GetTransactionResults()[0].GetStatus() - require.False(t, status) - require.Equal(t, fake.Err("failed to execute transaction"), msg) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeExec struct { - err error - count int - check bool -} - -func (e *fakeExec) Execute(store store.Snapshot, step execution.Step) (execution.Result, error) { - if e.check && e.count != len(step.Previous) { - return execution.Result{}, xerrors.New("missing previous txs") - } - - e.count++ - return execution.Result{Accepted: true}, e.err -} - -type fakeSnapshot struct { - store.Snapshot - - value []byte - errGet error - errSet error -} - -func (s fakeSnapshot) Get(key []byte) ([]byte, error) { - return s.value, s.errGet -} - -func (s fakeSnapshot) Set(key, value []byte) error { - return s.errSet -} diff --git a/dela/core/validation/validation.go b/dela/core/validation/validation.go deleted file mode 100644 index 1054bb8..0000000 --- a/dela/core/validation/validation.go +++ /dev/null @@ -1,68 +0,0 @@ -// Package validation defines a validation service that will apply a batch of -// transactions to a store snapshot. -// -// Documentation Last Review: 08.10.2020 -package validation - -import ( - "go.dedis.ch/dela/core/access" - "go.dedis.ch/dela/core/store" - "go.dedis.ch/dela/core/txn" - "go.dedis.ch/dela/serde" -) - -// TransactionResult is the result of a transaction execution. -type TransactionResult interface { - serde.Message - - // GetTransaction returns the transaction associated to the result. - GetTransaction() txn.Transaction - - // GetStatus returns the status of the execution. It returns true if the - // transaction has been accepted, otherwise false with a message to explain - // the reason. - GetStatus() (bool, string) -} - -// Result is the result of a validation. -type Result interface { - serde.Message - serde.Fingerprinter - - // GetTransactionResults returns the results. - GetTransactionResults() []TransactionResult -} - -// ResultFactory is the factory for results. -type ResultFactory interface { - serde.Factory - - ResultOf(serde.Context, []byte) (Result, error) -} - -// Leeway is the configuration when asserting if a transaction will be accepted -// to lighten some of the constraints. -type Leeway struct { - // MaxSequenceDifference defines how much from the current sequence the - // transaction can differ. - MaxSequenceDifference int -} - -// Service is the validation service that will process a batch of transactions -// into a result that can be used as a payload of a block. -type Service interface { - // GetFactory returns the result factory. - GetFactory() ResultFactory - - // GetNonce returns the nonce associated with the identity. The value - // returned should be used for the next transaction to be valid. - GetNonce(store.Readable, access.Identity) (uint64, error) - - // Accept returns nil if the transaction will be accepted by the service. - // The leeway parameter allows to reduce some constraints. - Accept(store.Readable, txn.Transaction, Leeway) error - - // Validate takes a snapshot and a list of transactions and returns a - // result. - Validate(store.Snapshot, []txn.Transaction) (Result, error) -} diff --git a/dela/core/watcher.go b/dela/core/watcher.go deleted file mode 100644 index ccba401..0000000 --- a/dela/core/watcher.go +++ /dev/null @@ -1,70 +0,0 @@ -// Package core implements commonly used tools. -// -// Documentation Last Review: 08.10.2020 -// -package core - -import "sync" - -// Observer is the interface to implement to watch events. -type Observer interface { - NotifyCallback(event interface{}) -} - -// Observable provides primitives to add and remove observers and to notify -// them of new events. -type Observable interface { - // Add adds the observer to the list of observers that will be notified of - // new events. - Add(observer Observer) - - // Remove removes the observer from the list thus stopping it from receiving - // new events. - Remove(observer Observer) - - // Notify notifies the observers of a new event. - Notify(event interface{}) -} - -// Watcher is an implementation of the Observable interface. -// -// - implements core.Observable -type Watcher struct { - sync.RWMutex - - observers map[Observer]struct{} -} - -// NewWatcher creates a new empty watcher. -func NewWatcher() *Watcher { - return &Watcher{ - observers: make(map[Observer]struct{}), - } -} - -// Add implements core.Observable. It adds the observer to the list of observers -// that will be notified of new events. -func (w *Watcher) Add(observer Observer) { - w.Lock() - w.observers[observer] = struct{}{} - w.Unlock() -} - -// Remove implements core.Observable. It removes the observer from the list thus -// stopping it from receiving new events. -func (w *Watcher) Remove(observer Observer) { - w.Lock() - delete(w.observers, observer) - w.Unlock() -} - -// Notify implements core.Observable. It notifies the whole list of observers -// one after each other. -func (w *Watcher) Notify(event interface{}) { - w.RLock() - defer w.RUnlock() - - for w := range w.observers { - w.NotifyCallback(event) - } -} diff --git a/dela/core/watcher_test.go b/dela/core/watcher_test.go deleted file mode 100644 index e37a039..0000000 --- a/dela/core/watcher_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package core - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestWatcher_Add(t *testing.T) { - watcher := NewWatcher() - - watcher.Add(fakeObserver{ch: make(chan interface{})}) - require.Len(t, watcher.observers, 1) - - obs := fakeObserver{ch: make(chan interface{})} - watcher.Add(obs) - require.Len(t, watcher.observers, 2) - - watcher.Add(obs) - require.Len(t, watcher.observers, 2) -} - -func TestWatcher_Remove(t *testing.T) { - watcher := NewWatcher() - watcher.observers[newFakeObserver()] = struct{}{} - - obs := newFakeObserver() - watcher.observers[obs] = struct{}{} - require.Len(t, watcher.observers, 2) - - watcher.Remove(obs) - require.Len(t, watcher.observers, 1) - - watcher.Remove(obs) - require.Len(t, watcher.observers, 1) -} - -func TestWatcher_Notify(t *testing.T) { - watcher := NewWatcher() - - obs := newFakeObserver() - watcher.observers[obs] = struct{}{} - - watcher.Notify(struct{}{}) - evt := <-obs.ch - require.NotNil(t, evt) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeObserver struct { - ch chan interface{} -} - -func (o fakeObserver) NotifyCallback(evt interface{}) { - o.ch <- evt -} - -func newFakeObserver() fakeObserver { - return fakeObserver{ - ch: make(chan interface{}, 1), - } -} diff --git a/dela/cosi/cosi.go b/dela/cosi/cosi.go deleted file mode 100644 index bf36f14..0000000 --- a/dela/cosi/cosi.go +++ /dev/null @@ -1,73 +0,0 @@ -// Package cosi defines a collective signing protocol abstraction. A set of -// participants will work with each others to sign a unique message collectively -// in the sense that the protocol produces a single signature that will verify -// the full, or partial, aggregated public key. -// -// Related Papers: -// -// Enhancing Bitcoin Security and Performance with Strong Consistency via -// Collective Signing (2016) -// https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_kokoris-kogias.pdf -// -// On the Security of Two-Round Multi-Signatures (2019) -// https://eprint.iacr.org/2018/417.pdf -// -// Documentation Last Review: 05.10.2020 -package cosi - -import ( - "context" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -// Reactor is a collective signature event handler. Every participant must react -// to an incoming signature request from the leader, and this abstraction -// provides the primitive that allows to do so. -type Reactor interface { - serde.Factory - - // Invoke is provided with the message and the address of the sender and it - // should return the unique hash for this message, or an error for malformed - // messages. - Invoke(addr mino.Address, in serde.Message) ([]byte, error) -} - -// Actor provides a primitive to sign a message. -type Actor interface { - // Sign collects the signature of the collective authority and creates an - // aggregated signature. - Sign(ctx context.Context, msg serde.Message, - ca crypto.CollectiveAuthority) (crypto.Signature, error) -} - -// Threshold is a function that returns the threshold to reach for a given n, -// which means it is always positive and below or equal to n. -type Threshold func(int) int - -// CollectiveSigning is the interface that provides the primitives to sign a -// message by members of a network. -type CollectiveSigning interface { - // GetSigner returns the individual signer assigned to the instance. One - // should not use it to verify a collective signature but only for identity - // verification. - GetSigner() crypto.Signer - - // GetPublicKeyFactory returns the aggregate public key factory. - GetPublicKeyFactory() crypto.PublicKeyFactory - - // GetSignatureFactory returns the aggregate signature factory. - GetSignatureFactory() crypto.SignatureFactory - - // GetVerifierFactory returns a factory that can create a verifier to check - // the validity of a signature. - GetVerifierFactory() crypto.VerifierFactory - - // SetThreshold updates the threshold required by a collective signature. - SetThreshold(Threshold) - - // Listen starts the collective signing so that it will answer to requests. - Listen(Reactor) (Actor, error) -} diff --git a/dela/cosi/flatcosi/example_test.go b/dela/cosi/flatcosi/example_test.go deleted file mode 100644 index df02fc0..0000000 --- a/dela/cosi/flatcosi/example_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package flatcosi - -import ( - "context" - "errors" - "fmt" - "time" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/minoch" - "go.dedis.ch/dela/serde" -) - -func Example() { - // Create the network overlay instances. It uses channels to communicate. - manager := minoch.NewManager() - - mA := minoch.MustCreate(manager, "A") - mB := minoch.MustCreate(manager, "B") - - // The list of participants to the signature. - roster := fake.NewAuthorityFromMino(bls.Generate, mA, mB) - - // Create the collective signing endpoints for both A and B. - cosiA := NewFlat(mA, roster.GetSigner(0).(crypto.AggregateSigner)) - - actor, err := cosiA.Listen(exampleReactor{}) - if err != nil { - panic(fmt.Sprintf("failed to listen on root: %+v", err)) - } - - cosiB := NewFlat(mB, roster.GetSigner(1).(crypto.AggregateSigner)) - _, err = cosiB.Listen(exampleReactor{}) - if err != nil { - panic(fmt.Sprintf("failed to listen on child: %+v", err)) - } - - // Context to timeout after 30 seconds if no signature is received. - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - msg := exampleMessage{Value: "42"} - - signature, err := actor.Sign(ctx, msg, roster) - if err != nil { - panic(fmt.Sprintf("failed to sign: %+v", err)) - } - - // We need a verifier implementation to support collective signatures. - verifier, err := cosiA.GetVerifierFactory().FromAuthority(roster) - if err != nil { - panic(fmt.Sprintf("verifier failed: %+v", err)) - } - - err = verifier.Verify([]byte(msg.Value), signature) - if err != nil { - panic(fmt.Sprintf("signature is invalid: %+v", err)) - } - - fmt.Println("Success", err == nil) - - // Output: Signing value 42 - // Signing value 42 - // Signing value 42 - // Success true -} - -type exampleMessage struct { - Value string -} - -func (msg exampleMessage) Serialize(ctx serde.Context) ([]byte, error) { - return ctx.Marshal(msg) -} - -type exampleReactor struct{} - -func (exampleReactor) Invoke(from mino.Address, msg serde.Message) ([]byte, error) { - example, ok := msg.(exampleMessage) - if !ok { - return nil, errors.New("unsupported message") - } - - fmt.Println("Signing value", example.Value) - - return []byte(example.Value), nil -} - -func (exampleReactor) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - var msg exampleMessage - err := ctx.Unmarshal(data, &msg) - - return msg, err -} diff --git a/dela/cosi/flatcosi/flatcosi.go b/dela/cosi/flatcosi/flatcosi.go deleted file mode 100644 index 8d9b5b5..0000000 --- a/dela/cosi/flatcosi/flatcosi.go +++ /dev/null @@ -1,170 +0,0 @@ -// Package flatcosi is a flat implementation of a collective signing so that the -// orchestrator will contact all the participants to require their signatures -// and then aggregate them to the final one. -// -// Documentation Last Review: 05.10.2020 -package flatcosi - -import ( - "context" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -const ( - rpcName = "cosi" -) - -// Flat is an implementation of the collective signing interface by -// using BLS signatures. It ignores the threshold and always requests a -// signature from every participant. -// -// - implements cosi.CollectiveSigning -type Flat struct { - mino mino.Mino - signer crypto.AggregateSigner -} - -// NewFlat returns a new collective signing instance. -func NewFlat(o mino.Mino, signer crypto.AggregateSigner) *Flat { - return &Flat{ - mino: o, - signer: signer, - } -} - -// GetSigner implements cosi.CollectiveSigning. It returns the signer of the -// instance. -func (flat *Flat) GetSigner() crypto.Signer { - return flat.signer -} - -// GetPublicKeyFactory implements cosi.CollectiveSigning. It returns the public -// key factory. -func (flat *Flat) GetPublicKeyFactory() crypto.PublicKeyFactory { - return flat.signer.GetPublicKeyFactory() -} - -// GetSignatureFactory implements cosi.CollectiveSigning. It returns the -// signature factory. -func (flat *Flat) GetSignatureFactory() crypto.SignatureFactory { - return flat.signer.GetSignatureFactory() -} - -// GetVerifierFactory implements cosi.CollectiveSigning. It returns the verifier -// factory. -func (flat *Flat) GetVerifierFactory() crypto.VerifierFactory { - return flat.signer.GetVerifierFactory() -} - -// SetThreshold implements cosi.CollectiveSigning. It ignores the new threshold -// as this implementation only accepts full participation. -func (flat *Flat) SetThreshold(fn cosi.Threshold) {} - -// Listen implements cosi.CollectiveSigning. It creates an actor that starts an -// RPC called cosi and respond to signing requests. The actor can also be used -// to sign a message. -func (flat *Flat) Listen(r cosi.Reactor) (cosi.Actor, error) { - actor := flatActor{ - logger: dela.Logger, - me: flat.mino.GetAddress(), - signer: flat.signer, - reactor: r, - } - - factory := cosi.NewMessageFactory(r, flat.signer.GetSignatureFactory()) - - actor.rpc = mino.MustCreateRPC(flat.mino, rpcName, newHandler(flat.signer, r), factory) - - return actor, nil -} - -// FlatActor is the active component of the flat collective signing. It provides -// a primitive to trigger a request for signatures from the participants. -// -// - implements cosi.Actor -type flatActor struct { - logger zerolog.Logger - me mino.Address - rpc mino.RPC - signer crypto.AggregateSigner - reactor cosi.Reactor -} - -// Sign implements cosi.Actor. It returns the collective signature of the -// message if every participant returns its signature. -func (a flatActor) Sign(ctx context.Context, msg serde.Message, - ca crypto.CollectiveAuthority) (crypto.Signature, error) { - - verifier, err := a.signer.GetVerifierFactory().FromAuthority(ca) - if err != nil { - return nil, xerrors.Errorf("couldn't make verifier: %v", err) - } - - req := cosi.SignatureRequest{ - Value: msg, - } - - msgs, err := a.rpc.Call(ctx, req, ca) - if err != nil { - return nil, xerrors.Errorf("call aborted: %v", err) - } - - digest, err := a.reactor.Invoke(a.me, msg) - if err != nil { - return nil, xerrors.Errorf("couldn't react to message: %v", err) - } - - var agg crypto.Signature - for { - resp, more := <-msgs - if !more { - if agg == nil { - return nil, xerrors.New("signature is nil") - } - - err = verifier.Verify(digest, agg) - if err != nil { - return nil, xerrors.Errorf("couldn't verify the aggregation: %v", err) - } - - return agg, nil - } - - reply, err := resp.GetMessageOrError() - if err != nil { - return nil, xerrors.Errorf("one request has failed: %v", err) - } - - agg, err = a.processResponse(reply, agg) - if err != nil { - return nil, xerrors.Errorf("couldn't process response: %v", err) - } - } -} - -func (a flatActor) processResponse(resp serde.Message, agg crypto.Signature) (crypto.Signature, error) { - reply, ok := resp.(cosi.SignatureResponse) - if !ok { - return nil, xerrors.Errorf("invalid response type '%T'", resp) - } - - var err error - - if agg == nil { - agg = reply.Signature - } else { - agg, err = a.signer.Aggregate(agg, reply.Signature) - if err != nil { - return nil, xerrors.Errorf("couldn't aggregate: %v", err) - } - } - - return agg, nil -} diff --git a/dela/cosi/flatcosi/flatcosi_test.go b/dela/cosi/flatcosi/flatcosi_test.go deleted file mode 100644 index 5988c34..0000000 --- a/dela/cosi/flatcosi/flatcosi_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package flatcosi - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestFlat_GetSigner(t *testing.T) { - flat := NewFlat(nil, fake.NewAggregateSigner()) - require.NotNil(t, flat.GetSigner()) -} - -func TestFlat_GetPublicKeyFactory(t *testing.T) { - flat := NewFlat(nil, fake.NewAggregateSigner()) - require.NotNil(t, flat.GetPublicKeyFactory()) -} - -func TestFlat_GetSignatureFactory(t *testing.T) { - flat := NewFlat(nil, fake.NewAggregateSigner()) - require.NotNil(t, flat.GetSignatureFactory()) -} - -func TestFlat_GetVerifierFactory(t *testing.T) { - flat := NewFlat(nil, fake.NewAggregateSigner()) - require.NotNil(t, flat.GetVerifierFactory()) -} - -func TestFlat_Listen(t *testing.T) { - flat := NewFlat(fake.Mino{}, bls.NewSigner()) - - a, err := flat.Listen(fakeReactor{}) - require.NoError(t, err) - actor := a.(flatActor) - require.NotNil(t, actor.signer) - require.NotNil(t, actor.rpc) -} - -func TestActor_Sign(t *testing.T) { - message := fake.Message{} - ca := fake.NewAuthority(1, fake.NewSigner) - - rpc := fake.NewRPC() - actor := flatActor{ - signer: fake.NewAggregateSigner(), - rpc: rpc, - reactor: fakeReactor{}, - } - - rpc.SendResponse(nil, cosi.SignatureResponse{Signature: fake.Signature{}}) - rpc.SendResponse(nil, cosi.SignatureResponse{Signature: fake.Signature{}}) - rpc.Done() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - sig, err := actor.Sign(ctx, message, ca) - require.NoError(t, err) - require.NotNil(t, sig) -} - -func TestActor_NetworkError_Sign(t *testing.T) { - actor := flatActor{ - signer: fake.NewAggregateSigner(), - rpc: fake.NewBadRPC(), - reactor: fakeReactor{}, - } - - ctx := context.Background() - message := fake.Message{} - roster := fake.NewAuthority(3, fake.NewSigner) - - _, err := actor.Sign(ctx, message, roster) - require.EqualError(t, err, fake.Err("call aborted")) -} - -func TestActor_FailVerifier_Sign(t *testing.T) { - actor := flatActor{ - signer: fake.NewSignerWithVerifierFactory(fake.NewBadVerifierFactory()), - rpc: fake.NewBadRPC(), - reactor: fakeReactor{}, - } - - ctx := context.Background() - message := fake.Message{} - roster := fake.NewAuthority(3, fake.NewSigner) - - _, err := actor.Sign(ctx, message, roster) - require.EqualError(t, err, fake.Err("couldn't make verifier")) -} - -func TestActor_DenyingReactor_Sign(t *testing.T) { - actor := flatActor{ - signer: fake.NewAggregateSigner(), - rpc: fake.NewRPC(), - reactor: fakeReactor{err: fake.GetError()}, - } - - ctx := context.Background() - message := fake.Message{} - roster := fake.NewAuthority(3, fake.NewSigner) - - _, err := actor.Sign(ctx, message, roster) - require.EqualError(t, err, fake.Err("couldn't react to message")) -} - -func TestActor_SignWrongSignature(t *testing.T) { - message := fake.Message{} - ca := fake.NewAuthority(1, fake.NewSigner) - - rpc := fake.NewRPC() - actor := flatActor{ - signer: fake.NewSignerWithVerifierFactory(fake.NewVerifierFactory(fake.NewBadVerifier())), - rpc: rpc, - reactor: fakeReactor{}, - } - - rpc.SendResponse(nil, cosi.SignatureResponse{Signature: fake.Signature{}}) - rpc.Done() - - ctx := context.Background() - - _, err := actor.Sign(ctx, message, ca) - require.EqualError(t, err, fake.Err("couldn't verify the aggregation")) -} - -func TestActor_RPCError_Sign(t *testing.T) { - message := fake.Message{} - ca := fake.NewAuthority(1, fake.NewSigner) - - rpc := fake.NewRPC() - actor := flatActor{ - signer: ca.GetSigner(0).(crypto.AggregateSigner), - rpc: rpc, - reactor: fakeReactor{}, - } - - rpc.Done() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - sig, err := actor.Sign(ctx, message, ca) - require.EqualError(t, err, "signature is nil") - require.Nil(t, sig) -} - -func TestActor_Context_Sign(t *testing.T) { - message := fake.Message{} - ca := fake.NewAuthority(1, fake.NewSigner) - rpc := fake.NewRPC() - - actor := flatActor{ - signer: ca.GetSigner(0).(crypto.AggregateSigner), - rpc: rpc, - reactor: fakeReactor{}, - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - rpc.SendResponseWithError(nil, fake.GetError()) - rpc.Done() - - sig, err := actor.Sign(ctx, message, ca) - require.EqualError(t, err, fake.Err("one request has failed")) - require.Nil(t, sig) -} - -func TestActor_SignProcessError(t *testing.T) { - ca := fake.NewAuthority(1, fake.NewSigner) - - rpc := fake.NewRPC() - actor := flatActor{ - signer: ca.GetSigner(0).(crypto.AggregateSigner), - reactor: fakeReactor{}, - rpc: rpc, - } - - rpc.SendResponse(nil, fake.Message{}) - rpc.Done() - _, err := actor.Sign(context.Background(), fake.Message{}, ca) - require.EqualError(t, err, - "couldn't process response: invalid response type 'fake.Message'") - - actor.signer = fake.NewBadSigner() - _, err = actor.processResponse(cosi.SignatureResponse{}, fake.Signature{}) - require.EqualError(t, err, fake.Err("couldn't aggregate")) -} diff --git a/dela/cosi/flatcosi/handler.go b/dela/cosi/flatcosi/handler.go deleted file mode 100644 index d685367..0000000 --- a/dela/cosi/flatcosi/handler.go +++ /dev/null @@ -1,55 +0,0 @@ -package flatcosi - -import ( - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// Handler is the RPC callback when a participant of a collective signing -// receives a request. It will invoke the reactor and sign the unique value, or -// return an error if the reactor refuses the message. -// -// - implements mino.Handler -type handler struct { - mino.UnsupportedHandler - signer crypto.Signer - reactor cosi.Reactor -} - -func newHandler(s crypto.Signer, r cosi.Reactor) handler { - return handler{ - signer: s, - reactor: r, - } -} - -// Process implements mino.Handler. It sends the message to the reactor and -// sends back the signature if the message is correctly processed, otherwise it -// returns an error. -func (h handler) Process(req mino.Request) (serde.Message, error) { - switch msg := req.Message.(type) { - - case cosi.SignatureRequest: - buf, err := h.reactor.Invoke(req.Address, msg.Value) - if err != nil { - return nil, xerrors.Errorf("couldn't hash message: %v", err) - } - - sig, err := h.signer.Sign(buf) - if err != nil { - return nil, xerrors.Errorf("couldn't sign: %v", err) - } - - resp := cosi.SignatureResponse{ - Signature: sig, - } - - return resp, nil - - default: - return nil, xerrors.Errorf("invalid message type '%T'", msg) - } -} diff --git a/dela/cosi/flatcosi/handler_test.go b/dela/cosi/flatcosi/handler_test.go deleted file mode 100644 index e62469f..0000000 --- a/dela/cosi/flatcosi/handler_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package flatcosi - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -func TestHandler_Process(t *testing.T) { - signer := bls.NewSigner() - - h := newHandler(signer, fakeReactor{}) - req := mino.Request{ - Message: cosi.SignatureRequest{Value: fake.Message{}}, - } - - msg, err := h.Process(req) - require.NoError(t, err) - - resp, ok := msg.(cosi.SignatureResponse) - require.True(t, ok) - - err = signer.GetPublicKey().Verify(testValue, resp.Signature) - require.NoError(t, err) -} - -func TestHandler_InvalidMessage_Process(t *testing.T) { - h := newHandler(fake.NewSigner(), fakeReactor{}) - - resp, err := h.Process(mino.Request{Message: fake.Message{}}) - require.EqualError(t, err, "invalid message type 'fake.Message'") - require.Nil(t, resp) -} - -func TestHandler_DenyingReactor_Process(t *testing.T) { - h := newHandler(fake.NewSigner(), fakeReactor{err: fake.GetError()}) - - req := mino.Request{ - Message: cosi.SignatureRequest{Value: fake.Message{}}, - } - - _, err := h.Process(req) - require.EqualError(t, err, fake.Err("couldn't hash message")) -} - -func TestHandler_FailSign_Process(t *testing.T) { - h := newHandler(fake.NewBadSigner(), fakeReactor{}) - - req := mino.Request{ - Message: cosi.SignatureRequest{Value: fake.Message{}}, - } - - _, err := h.Process(req) - require.EqualError(t, err, fake.Err("couldn't sign")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -var testValue = []byte{0xab} - -type fakeReactor struct { - err error -} - -func (h fakeReactor) Invoke(mino.Address, serde.Message) ([]byte, error) { - return testValue, h.err -} - -func (h fakeReactor) Deserialize(serde.Context, []byte) (serde.Message, error) { - return fake.Message{}, h.err -} diff --git a/dela/cosi/json/json.go b/dela/cosi/json/json.go deleted file mode 100644 index 7243c18..0000000 --- a/dela/cosi/json/json.go +++ /dev/null @@ -1,113 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - cosi.RegisterMessageFormat(serde.FormatJSON, msgFormat{}) -} - -// Request is the JSON message sent to request signature. -type Request struct { - Value json.RawMessage -} - -// Response is the JSON message sent to respond with a signature. -type Response struct { - Signature json.RawMessage -} - -// Message is a JSON container to differentiate the different messages of flat -// collective signing. -type Message struct { - Request *Request `json:",omitempty"` - Response *Response `json:",omitempty"` -} - -// MsgFormat is the engine to encode and decode collective signing messages in -// JSON format. -// -// - implements serde.FormatEngine -type msgFormat struct{} - -// Decode implements serde.FormatEngine. It returns the serialized data of a -// message in JSON format. -func (f msgFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - m := Message{} - - switch message := msg.(type) { - case cosi.SignatureRequest: - value, err := message.Value.Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize message: %v", err) - } - - m.Request = &Request{ - Value: value, - } - case cosi.SignatureResponse: - sig, err := message.Signature.Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize signature: %v", err) - } - - m.Response = &Response{ - Signature: sig, - } - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the message with the JSON -// data if appropriate, otherwise it returns an error. -func (f msgFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := Message{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) - } - - if m.Request != nil { - factory := ctx.GetFactory(cosi.MsgKey{}) - if factory == nil { - return nil, xerrors.New("factory is nil") - } - - value, err := factory.Deserialize(ctx, m.Request.Value) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize value: %v", err) - } - - return cosi.SignatureRequest{Value: value}, nil - } - - if m.Response != nil { - factory := ctx.GetFactory(cosi.SigKey{}) - - fac, ok := factory.(crypto.SignatureFactory) - if !ok { - return nil, xerrors.Errorf("invalid factory of type '%T'", factory) - } - - sig, err := fac.SignatureOf(ctx, m.Response.Signature) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize signature: %v", err) - } - - return cosi.SignatureResponse{Signature: sig}, nil - } - - return nil, xerrors.Errorf("message is empty") -} diff --git a/dela/cosi/json/json_test.go b/dela/cosi/json/json_test.go deleted file mode 100644 index cd99570..0000000 --- a/dela/cosi/json/json_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestMsgFormat_Encode(t *testing.T) { - req := cosi.SignatureRequest{ - Value: fake.Message{}, - } - - format := msgFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := format.Encode(ctx, req) - require.NoError(t, err) - require.Equal(t, `{"Request":{"Value":{}}}`, string(data)) - - req.Value = fake.NewBadPublicKey() - _, err = format.Encode(ctx, req) - require.EqualError(t, err, fake.Err("couldn't serialize message")) - - req.Value = fake.PublicKey{} - _, err = format.Encode(fake.NewBadContext(), req) - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestMsgFormat_SignatureResponse_Encode(t *testing.T) { - resp := cosi.SignatureResponse{ - Signature: fake.Signature{}, - } - - format := msgFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := format.Encode(ctx, resp) - require.NoError(t, err) - require.Equal(t, `{"Response":{"Signature":{}}}`, string(data)) - - resp.Signature = fake.NewBadSignature() - _, err = format.Encode(ctx, resp) - require.EqualError(t, err, fake.Err("couldn't serialize signature")) -} - -func TestMsgFormat_Decode(t *testing.T) { - format := msgFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - ctx = serde.WithFactory(ctx, cosi.MsgKey{}, fake.MessageFactory{}) - ctx = serde.WithFactory(ctx, cosi.SigKey{}, fake.SignatureFactory{}) - - msg, err := format.Decode(ctx, []byte(`{"Request":{}}`)) - require.NoError(t, err) - require.Equal(t, cosi.SignatureRequest{Value: fake.Message{}}, msg) - - badCtx := serde.WithFactory(ctx, cosi.MsgKey{}, fake.NewBadMessageFactory()) - _, err = format.Decode(badCtx, []byte(`{"Request":{}}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize value")) - - badCtx = serde.WithFactory(ctx, cosi.MsgKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Request":{}}`)) - require.EqualError(t, err, "factory is nil") - - msg, err = format.Decode(ctx, []byte(`{"Response":{}}`)) - require.NoError(t, err) - require.Equal(t, cosi.SignatureResponse{Signature: fake.Signature{}}, msg) - - badCtx = serde.WithFactory(ctx, cosi.SigKey{}, fake.NewBadSignatureFactory()) - _, err = format.Decode(badCtx, []byte(`{"Response":{}}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize signature")) - - badCtx = serde.WithFactory(ctx, cosi.SigKey{}, nil) - _, err = format.Decode(badCtx, []byte(`{"Response":{}}`)) - require.EqualError(t, err, "invalid factory of type ''") - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't unmarshal message")) - - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, "message is empty") -} diff --git a/dela/cosi/messages.go b/dela/cosi/messages.go deleted file mode 100644 index 1826f0e..0000000 --- a/dela/cosi/messages.go +++ /dev/null @@ -1,98 +0,0 @@ -// -// Documentation Last Review: 05.10.2020 -// - -package cosi - -import ( - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var msgFormats = registry.NewSimpleRegistry() - -// RegisterMessageFormat registers the format for the given format name. -func RegisterMessageFormat(name serde.Format, f serde.FormatEngine) { - msgFormats.Register(name, f) -} - -// SignatureRequest is the message sent to require a signature from the other -// participants. -// -// - implements serde.Message -type SignatureRequest struct { - Value serde.Message -} - -// Serialize implements serde.Message. It looks up the format and returns the -// serialized data if appropriate, otherwise an error. -func (req SignatureRequest) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, req) - if err != nil { - return nil, xerrors.Errorf("couldn't encode request: %v", err) - } - - return data, nil -} - -// SignatureResponse is the message sent by the participants. -// -// - implements serde.Message -type SignatureResponse struct { - Signature crypto.Signature -} - -// Serialize implements serde.Message. It looks up the format and returns the -// serialized data if appropriate, otherwise an error. -func (resp SignatureResponse) Serialize(ctx serde.Context) ([]byte, error) { - format := msgFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, resp) - if err != nil { - return nil, xerrors.Errorf("couldn't encode response: %v", err) - } - - return data, nil -} - -// MsgKey is the key of the message factory. -type MsgKey struct{} - -// SigKey is the key of the signature factory. -type SigKey struct{} - -// MessageFactory is the message factory for the flat collective signing RPC. -// -// - implements serde.Factory -type MessageFactory struct { - msgFactory serde.Factory - sigFactory crypto.SignatureFactory -} - -// NewMessageFactory returns a new message factory that uses the message and -// signature factories. -func NewMessageFactory(msg serde.Factory, sig crypto.SignatureFactory) MessageFactory { - return MessageFactory{ - msgFactory: msg, - sigFactory: sig, - } -} - -// Deserialize implements serde.Factory. -func (f MessageFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := msgFormats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, MsgKey{}, f.msgFactory) - ctx = serde.WithFactory(ctx, SigKey{}, f.sigFactory) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode message: %v", err) - } - - return m, nil -} diff --git a/dela/cosi/messages_test.go b/dela/cosi/messages_test.go deleted file mode 100644 index f523f00..0000000 --- a/dela/cosi/messages_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package cosi - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -var testCalls = &fake.Call{} - -func init() { - RegisterMessageFormat(fake.GoodFormat, fake.Format{Msg: SignatureRequest{}, Call: testCalls}) - RegisterMessageFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestSignatureRequest(t *testing.T) { - req := SignatureRequest{} - - data, err := req.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = req.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode request")) -} - -func TestSignatureResponse(t *testing.T) { - resp := SignatureResponse{} - - data, err := resp.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = resp.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode response")) -} - -func TestMessageFactory_Deserialize(t *testing.T) { - factory := NewMessageFactory(fake.MessageFactory{}, fake.SignatureFactory{}) - - testCalls.Clear() - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, SignatureRequest{}, msg) - - require.Equal(t, 1, testCalls.Len()) - ctx := testCalls.Get(0, 0).(serde.Context) - require.Equal(t, fake.MessageFactory{}, ctx.GetFactory(MsgKey{})) - require.Equal(t, fake.SignatureFactory{}, ctx.GetFactory(SigKey{})) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode message")) -} diff --git a/dela/cosi/threshold/actor.go b/dela/cosi/threshold/actor.go deleted file mode 100644 index 7d044b0..0000000 --- a/dela/cosi/threshold/actor.go +++ /dev/null @@ -1,137 +0,0 @@ -// -// This file contains the implementation of the collective signing actor that -// provides a primitive to send a signature request to participants. -// -// Document Last Review: 05.10.2020 -// - -package threshold - -import ( - "context" - - "go.dedis.ch/dela" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/cosi/threshold/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/tracing" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// thresholdActor is an implementation of a collective signing actor. -// -// - implements cosi.Actor -type thresholdActor struct { - *Threshold - - me mino.Address - rpc mino.RPC - reactor cosi.Reactor -} - -// Sign implements cosi.Actor. It returns the collective signature from the -// collective authority, or an error if it failed. The signature may be composed -// of only a subset of the participants, depending on the threshold. The -// function will return as soon as a valid signature is available. -// The context must be cancel at some point, and it will interrupt the protocol -// if it is not done yet. -func (a thresholdActor) Sign(ctx context.Context, msg serde.Message, - ca crypto.CollectiveAuthority) (crypto.Signature, error) { - - ctx = context.WithValue(ctx, tracing.ProtocolKey, protocolName) - - sender, rcvr, err := a.rpc.Stream(ctx, ca) - if err != nil { - return nil, xerrors.Errorf("couldn't open stream: %v", err) - } - - digest, err := a.reactor.Invoke(a.me, msg) - if err != nil { - return nil, xerrors.Errorf("couldn't react to message: %v", err) - } - - // The aggregated signature needs to include at least a threshold number of - // signatures. - thres := a.thresholdFn.Load().(cosi.Threshold)(ca.Len()) - - req := cosi.SignatureRequest{ - Value: msg, - } - - errs := sender.Send(req, iter2slice(ca)...) - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - go a.waitResp(errs, ca.Len()-thres, cancel) - - count := 0 - signature := new(types.Signature) - for count < thres { - addr, resp, err := rcvr.Recv(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't receive more messages: %v", err) - } - - pubkey, index := ca.GetPublicKey(addr) - if index >= 0 { - err = a.merge(signature, resp, index, pubkey, digest) - if err != nil { - a.logger.Warn().Err(err).Msg("failed to process signature response") - } else { - count++ - } - } - } - - // Each signature is individually verified so we can assume the aggregated - // signature is correct. - return signature, nil -} - -func (a thresholdActor) waitResp(errs <-chan error, maxErrs int, cancel func()) { - errCount := 0 - for err := range errs { - a.logger.Warn().Err(err).Msg("signature request to a peer failed") - errCount++ - - if errCount > maxErrs { - dela.Logger.Warn().Msg("aborting collective signing due to too many errors") - cancel() - return - } - } -} - -func (a thresholdActor) merge(signature *types.Signature, m serde.Message, - index int, pubkey crypto.PublicKey, digest []byte) error { - - resp, ok := m.(cosi.SignatureResponse) - if !ok { - return xerrors.Errorf("invalid message type '%T'", m) - } - - err := pubkey.Verify(digest, resp.Signature) - if err != nil { - return xerrors.Errorf("couldn't verify: %v", err) - } - - err = signature.Merge(a.signer, index, resp.Signature) - if err != nil { - return xerrors.Errorf("couldn't merge signature: %v", err) - } - - return nil -} - -func iter2slice(players mino.Players) []mino.Address { - addrs := make([]mino.Address, 0, players.Len()) - iter := players.AddressIterator() - for iter.HasNext() { - addrs = append(addrs, iter.GetNext()) - } - - return addrs -} diff --git a/dela/cosi/threshold/actor_test.go b/dela/cosi/threshold/actor_test.go deleted file mode 100644 index d287d66..0000000 --- a/dela/cosi/threshold/actor_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package threshold - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestActor_Sign(t *testing.T) { - roster := fake.NewAuthority(3, fake.NewSigner) - - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureResponse{Signature: fake.Signature{}}), - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureResponse{Signature: fake.Signature{}}), - fake.NewRecvMsg(fake.NewAddress(1), cosi.SignatureResponse{Signature: fake.Signature{}}), - ) - rpc := fake.NewStreamRPC(recv, fake.Sender{}) - - actor := thresholdActor{ - Threshold: &Threshold{ - signer: roster.GetSigner(0).(crypto.AggregateSigner), - }, - rpc: rpc, - reactor: fakeReactor{}, - } - - actor.SetThreshold(OneThreshold) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - sig, err := actor.Sign(ctx, fake.Message{}, roster) - require.NoError(t, err) - require.NotNil(t, sig) -} - -func TestActor_BadNetwork_Sign(t *testing.T) { - actor := thresholdActor{ - Threshold: &Threshold{}, - rpc: fake.NewBadRPC(), - reactor: fakeReactor{err: fake.GetError()}, - } - - roster := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, fake.Err("couldn't open stream")) -} - -func TestActor_BadReactor_Sign(t *testing.T) { - actor := thresholdActor{ - Threshold: &Threshold{}, - rpc: fake.NewRPC(), - reactor: fakeReactor{err: fake.GetError()}, - } - - roster := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, fake.Err("couldn't react to message")) -} - -func TestActor_MalformedResponse_Sign(t *testing.T) { - logger, check := fake.CheckLog("failed to process signature response") - - recv := fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), fake.Message{})) - rpc := fake.NewStreamRPC(recv, fake.Sender{}) - rpc.Done() - - actor := thresholdActor{ - Threshold: &Threshold{ - logger: logger, - }, - rpc: rpc, - reactor: fakeReactor{}, - } - actor.thresholdFn.Store(cosi.Threshold(defaultThreshold)) - - roster := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, "couldn't receive more messages: EOF") - check(t) -} - -func TestActor_CanceledContext_Sign(t *testing.T) { - actor := thresholdActor{ - Threshold: &Threshold{}, - rpc: fake.NewStreamRPC(fake.NewBlockingReceiver(), fake.Sender{}), - reactor: fakeReactor{}, - } - actor.thresholdFn.Store(cosi.Threshold(defaultThreshold)) - - roster := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, "couldn't receive more messages: context canceled") -} - -func TestActor_TooManyErrors_Sign(t *testing.T) { - rpc := fake.NewStreamRPC(fake.NewBlockingReceiver(), fake.NewBadSender()) - - logger, check := fake.CheckLog("signature request to a peer failed") - - actor := thresholdActor{ - Threshold: &Threshold{ - logger: logger, - }, - rpc: rpc, - reactor: fakeReactor{}, - } - actor.thresholdFn.Store(cosi.Threshold(defaultThreshold)) - - roster := fake.NewAuthority(3, fake.NewSigner) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, "couldn't receive more messages: context canceled") - check(t) -} - -func TestActor_InvalidSignature_Sign(t *testing.T) { - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureResponse{Signature: fake.Signature{}}), - ) - rpc := fake.NewStreamRPC(recv, fake.Sender{}) - - logger, check := fake.CheckLog("failed to process signature response") - - actor := thresholdActor{ - Threshold: &Threshold{ - logger: logger, - }, - rpc: rpc, - reactor: fakeReactor{}, - } - actor.thresholdFn.Store(cosi.Threshold(defaultThreshold)) - - roster := fake.NewAuthority(3, func() crypto.Signer { - return fake.NewSignerWithPublicKey(fake.NewBadPublicKey()) - }) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := actor.Sign(ctx, fake.Message{}, roster) - require.EqualError(t, err, "couldn't receive more messages: EOF") - check(t) -} diff --git a/dela/cosi/threshold/handler.go b/dela/cosi/threshold/handler.go deleted file mode 100644 index ec6966b..0000000 --- a/dela/cosi/threshold/handler.go +++ /dev/null @@ -1,82 +0,0 @@ -// This file contains the implementation of the RPC handler. -// -// Documentation Last Review: 05.10.2020 -// - -package threshold - -import ( - "context" - "io" - - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -// thresholdHandler is an implementation of mino.Handler for a threshold -// collective signing. -type thresholdHandler struct { - *Threshold - mino.UnsupportedHandler - - reactor cosi.Reactor -} - -func newHandler(c *Threshold, hasher cosi.Reactor) thresholdHandler { - return thresholdHandler{ - Threshold: c, - reactor: hasher, - } -} - -// Stream implements mino.RPC. It listens for incoming messages and tries to -// send back the signature. If the message is malformed, it is ignored. -func (h thresholdHandler) Stream(out mino.Sender, in mino.Receiver) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - for { - addr, msg, err := in.Recv(ctx) - if err == io.EOF { - return nil - } - if err != nil { - return xerrors.Errorf("failed to receive: %v", err) - } - - err = h.processRequest(out, msg, addr) - if err != nil { - h.logger.Warn().Err(err).Send() - } - } -} - -func (h thresholdHandler) processRequest(sender mino.Sender, msg serde.Message, addr mino.Address) error { - req, ok := msg.(cosi.SignatureRequest) - if !ok { - return xerrors.Errorf("invalid request type '%T'", msg) - } - - buffer, err := h.reactor.Invoke(addr, req.Value) - if err != nil { - return xerrors.Errorf("couldn't hash message: %v", err) - } - - signature, err := h.signer.Sign(buffer) - if err != nil { - return xerrors.Errorf("couldn't sign: %v", err) - } - - resp := cosi.SignatureResponse{ - Signature: signature, - } - - err = <-sender.Send(resp, addr) - if err != nil { - return xerrors.Errorf("couldn't send the response: %v", err) - } - - return nil -} diff --git a/dela/cosi/threshold/handler_test.go b/dela/cosi/threshold/handler_test.go deleted file mode 100644 index 277f472..0000000 --- a/dela/cosi/threshold/handler_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package threshold - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestThresholdHandler_Stream(t *testing.T) { - handler := newHandler( - &Threshold{signer: fake.NewAggregateSigner()}, - fakeReactor{}, - ) - - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureRequest{Value: fake.Message{}}), - ) - sender := fake.NewBadSender() - - err := handler.Stream(sender, recv) - require.NoError(t, err) -} - -func TestThresholdHandler_BadStream_Stream(t *testing.T) { - handler := newHandler( - &Threshold{}, - fakeReactor{}, - ) - - recv := fake.NewBadReceiver() - - err := handler.Stream(fake.Sender{}, recv) - require.EqualError(t, err, fake.Err("failed to receive")) -} - -func TestThresholdHandler_UnsupportedMessage_Stream(t *testing.T) { - logger, check := fake.CheckLog("invalid request type 'fake.Message'") - - handler := newHandler( - &Threshold{ - logger: logger, - }, - fakeReactor{}, - ) - - recv := fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), fake.Message{})) - - err := handler.Stream(fake.Sender{}, recv) - require.NoError(t, err) - check(t) -} - -func TestThresholdHandler_BadReactor_Stream(t *testing.T) { - logger, check := fake.CheckLog(fake.Err("couldn't hash message")) - - handler := newHandler( - &Threshold{ - logger: logger, - }, - fakeReactor{err: fake.GetError()}, - ) - - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureRequest{Value: fake.Message{}}), - ) - - err := handler.Stream(fake.Sender{}, recv) - require.NoError(t, err) - check(t) -} - -func TestThresholdHandler_BadSigner_Stream(t *testing.T) { - logger, check := fake.CheckLog(fake.Err("couldn't sign")) - - handler := newHandler( - &Threshold{ - logger: logger, - }, - fakeReactor{}, - ) - - handler.signer = fake.NewBadSigner() - recv := fake.NewReceiver( - fake.NewRecvMsg(fake.NewAddress(0), cosi.SignatureRequest{Value: fake.Message{}}), - ) - - err := handler.Stream(fake.Sender{}, recv) - require.NoError(t, err) - check(t) -} diff --git a/dela/cosi/threshold/json/json.go b/dela/cosi/threshold/json/json.go deleted file mode 100644 index 6bea157..0000000 --- a/dela/cosi/threshold/json/json.go +++ /dev/null @@ -1,78 +0,0 @@ -package json - -import ( - "encoding/json" - - "go.dedis.ch/dela/cosi/threshold/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - types.RegisterSignatureFormat(serde.FormatJSON, sigFormat{}) -} - -// Signature is the JSON message for the signature. -type Signature struct { - Mask []byte - Aggregate json.RawMessage -} - -// SigFormat is the engine to encode and decode collective signature messages in -// JSON format. -// -// - implements serde.FormatEngine -type sigFormat struct{} - -// Encode implements serde.FormatEngine. It returns the serialized data of the -// signature message if appropriate, otherwise an error. -func (f sigFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - sig, ok := msg.(*types.Signature) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - agg, err := sig.GetAggregate().Serialize(ctx) - if err != nil { - return nil, xerrors.Errorf("couldn't serialize aggregate: %v", err) - } - - m := Signature{ - Mask: sig.GetMask(), - Aggregate: json.RawMessage(agg), - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the signature of the JSON -// data if appropriate, otherwise it returns an error. -func (f sigFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := Signature{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal message: %v", err) - } - - factory := ctx.GetFactory(types.AggKey{}) - - fac, ok := factory.(crypto.SignatureFactory) - if !ok { - return nil, xerrors.Errorf("invalid factory of type '%T'", factory) - } - - agg, err := fac.SignatureOf(ctx, m.Aggregate) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize signature: %v", err) - } - - s := types.NewSignature(agg, m.Mask) - - return s, nil -} diff --git a/dela/cosi/threshold/json/json_test.go b/dela/cosi/threshold/json/json_test.go deleted file mode 100644 index ea30108..0000000 --- a/dela/cosi/threshold/json/json_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi/threshold/types" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestFormat_Encode(t *testing.T) { - format := sigFormat{} - sig := types.NewSignature(fake.Signature{}, []byte{0xab}) - - ctx := fake.NewContext() - - data, err := format.Encode(ctx, sig) - require.NoError(t, err) - require.Equal(t, `{"Mask":"qw==","Aggregate":{}}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), sig) - require.EqualError(t, err, fake.Err("couldn't marshal")) - - sig = types.NewSignature(fake.NewBadSignature(), nil) - _, err = format.Encode(ctx, sig) - require.EqualError(t, err, fake.Err("couldn't serialize aggregate")) -} - -func TestFormat_Decode(t *testing.T) { - format := sigFormat{} - - ctx := fake.NewContext() - ctx = serde.WithFactory(ctx, types.AggKey{}, fake.SignatureFactory{}) - - sig, err := format.Decode(ctx, []byte(`{"Mask":[1],"Aggregate":{}}`)) - require.NoError(t, err) - require.Equal(t, []byte{1}, sig.(*types.Signature).GetMask()) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't unmarshal message")) - - ctx = serde.WithFactory(ctx, types.AggKey{}, fake.NewBadSignatureFactory()) - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize signature")) - - ctx = serde.WithFactory(ctx, types.AggKey{}, nil) - _, err = format.Decode(ctx, []byte(`{}`)) - require.EqualError(t, err, "invalid factory of type ''") -} diff --git a/dela/cosi/threshold/threshold.go b/dela/cosi/threshold/threshold.go deleted file mode 100644 index c24bd1c..0000000 --- a/dela/cosi/threshold/threshold.go +++ /dev/null @@ -1,124 +0,0 @@ -// Package threshold is a stream-based implementation of a collective signing so -// that the orchestrator contacts only a subset of the participants. The -// collective signature allows a given threshold to be valid, which means that -// not all the participants need to return their signature for the protocol to -// end. -// -// Documentation Last Review: 05.10.2020 -package threshold - -import ( - "sync/atomic" - - "github.com/rs/zerolog" - "go.dedis.ch/dela" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/cosi/threshold/types" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/mino" -) - -var ( - // protocolName denotes the value of the protocol span tag associated with - // the `sign` protocol. - protocolName = "sign" -) - -func defaultThreshold(n int) int { - return n -} - -// OneThreshold is a threshold function to allow one failure. -func OneThreshold(n int) int { - if n <= 0 { - return 0 - } - - return n - 1 -} - -// ByzantineThreshold returns the minimum number of honest nodes required given -// `n` total nodes in a Byzantine Fault Tolerant system. -func ByzantineThreshold(n int) int { - if n <= 0 { - return 0 - } - - f := (n - 1) / 3 - - return n - f -} - -// Threshold is an implementation of the cosi.CollectiveSigning interface that -// is using streams to parallelize the work. -type Threshold struct { - logger zerolog.Logger - mino mino.Mino - signer crypto.AggregateSigner - // Stores the cosi.Threshold function. It will always contain a valid - // function by construction. - thresholdFn atomic.Value -} - -// NewThreshold returns a new instance of a threshold collective signature. -func NewThreshold(m mino.Mino, signer crypto.AggregateSigner) *Threshold { - c := &Threshold{ - logger: dela.Logger.With().Str("addr", m.GetAddress().String()).Logger(), - mino: m, - signer: signer, - } - - // Force the cosi.Threshold type to allow later updates of the same type. - c.thresholdFn.Store(cosi.Threshold(defaultThreshold)) - - return c -} - -// GetSigner implements cosi.CollectiveSigning. It returns the signer of the -// instance. -func (c *Threshold) GetSigner() crypto.Signer { - return c.signer -} - -// GetPublicKeyFactory implements cosi.CollectiveSigning. It returns the public -// key factory. -func (c *Threshold) GetPublicKeyFactory() crypto.PublicKeyFactory { - return c.signer.GetPublicKeyFactory() -} - -// GetSignatureFactory implements cosi.CollectiveSigning. It returns the -// signature factory. -func (c *Threshold) GetSignatureFactory() crypto.SignatureFactory { - return types.NewSignatureFactory(c.signer.GetSignatureFactory()) -} - -// GetVerifierFactory implements cosi.CollectiveSigning. It returns the verifier -// factory. -func (c *Threshold) GetVerifierFactory() crypto.VerifierFactory { - return types.NewThresholdVerifierFactory(c.signer.GetVerifierFactory()) -} - -// SetThreshold implements cosi.CollectiveSigning. It sets a new threshold -// function. -func (c *Threshold) SetThreshold(fn cosi.Threshold) { - if fn == nil { - return - } - - c.thresholdFn.Store(fn) -} - -// Listen implements cosi.CollectiveSigning. It creates the rpc endpoint and -// returns the actor that can trigger a collective signature. -func (c *Threshold) Listen(r cosi.Reactor) (cosi.Actor, error) { - factory := cosi.NewMessageFactory(r, c.signer.GetSignatureFactory()) - - actor := thresholdActor{ - Threshold: c, - me: c.mino.GetAddress(), - rpc: mino.MustCreateRPC(c.mino, "cosi", newHandler(c, r), factory), - reactor: r, - } - - return actor, nil -} diff --git a/dela/cosi/threshold/threshold_test.go b/dela/cosi/threshold/threshold_test.go deleted file mode 100644 index ea7a195..0000000 --- a/dela/cosi/threshold/threshold_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package threshold - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cosi" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/mino/minoch" - "go.dedis.ch/dela/serde" -) - -func TestThreshold_Scenario_Basic(t *testing.T) { - manager := minoch.NewManager() - - m1 := minoch.MustCreate(manager, "A") - m2 := minoch.MustCreate(manager, "B") - - ca := fake.NewAuthorityFromMino(bls.Generate, m1, m2) - c1 := NewThreshold(m1, ca.GetSigner(0).(crypto.AggregateSigner)) - c1.SetThreshold(OneThreshold) - - actor, err := c1.Listen(fakeReactor{}) - require.NoError(t, err) - - c2 := NewThreshold(m2, ca.GetSigner(1).(crypto.AggregateSigner)) - _, err = c2.Listen(fakeReactor{err: fake.GetError()}) - require.NoError(t, err) - - ctx := context.Background() - sig, err := actor.Sign(ctx, fake.Message{}, ca) - require.NoError(t, err) - require.NotNil(t, sig) - - verifier, err := c1.GetVerifierFactory().FromAuthority(ca) - require.NoError(t, err) - require.NoError(t, verifier.Verify([]byte{0xff}, sig)) -} - -func TestDefaultThreshold(t *testing.T) { - require.Equal(t, 2, defaultThreshold(2)) - require.Equal(t, 5, defaultThreshold(5)) -} - -func TestOneThreshold(t *testing.T) { - require.Equal(t, 0, OneThreshold(-10)) - require.Equal(t, 0, OneThreshold(0)) - require.Equal(t, 5, OneThreshold(6)) -} - -func TestByzantineThreshold(t *testing.T) { - require.Equal(t, 0, ByzantineThreshold(-10)) - require.Equal(t, 0, ByzantineThreshold(0)) - require.Equal(t, 2, ByzantineThreshold(2)) - require.Equal(t, 3, ByzantineThreshold(4)) - require.Equal(t, 5, ByzantineThreshold(7)) -} - -func TestThreshold_GetSigner(t *testing.T) { - c := &Threshold{signer: fake.NewAggregateSigner()} - require.NotNil(t, c.GetSigner()) -} - -func TestThreshold_GetPublicKeyFactory(t *testing.T) { - c := &Threshold{signer: fake.NewAggregateSigner()} - require.NotNil(t, c.GetPublicKeyFactory()) -} - -func TestThreshold_GetSignatureFactory(t *testing.T) { - c := &Threshold{signer: fake.NewAggregateSigner()} - require.NotNil(t, c.GetSignatureFactory()) -} - -func TestThreshold_SetThreshold(t *testing.T) { - c := NewThreshold(fake.Mino{}, nil) - - c.SetThreshold(nil) - require.NotNil(t, c.thresholdFn.Load()) - - c.SetThreshold(ByzantineThreshold) - require.Equal(t, ByzantineThreshold(999), c.thresholdFn.Load().(cosi.Threshold)(999)) -} - -func TestThreshold_Listen(t *testing.T) { - c := &Threshold{ - mino: fake.Mino{}, - signer: fake.NewAggregateSigner(), - } - - actor, err := c.Listen(fakeReactor{}) - require.NoError(t, err) - require.NotNil(t, actor) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeReactor struct { - fake.MessageFactory - - err error -} - -func (h fakeReactor) Invoke(addr mino.Address, in serde.Message) ([]byte, error) { - return []byte{0xff}, h.err -} diff --git a/dela/cosi/threshold/types/types.go b/dela/cosi/threshold/types/types.go deleted file mode 100644 index 1dc214d..0000000 --- a/dela/cosi/threshold/types/types.go +++ /dev/null @@ -1,293 +0,0 @@ -// Package types implements the threshold collective signature and its verifier. -// -// It wraps a signature implementation in order to extract the correct -// aggregated public key. -// -// The messages have been implemented in this isolated package so that it does -// not create cycle imports when importing the serde formats. -// -// Documentation Last Review: 05.10.2020 -package types - -import ( - "bytes" - "fmt" - "math/big" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -const ( - wordlength = 8 - // shift is used to divide by 8. - shift = 3 - // mask is used to get the remainder of a division by 8. - mask = 0x7 -) - -var formats = registry.NewSimpleRegistry() - -// RegisterSignatureFormat saves the format to be used when -// serializing/deserializing signature messages for the given codec. -func RegisterSignatureFormat(c serde.Format, f serde.FormatEngine) { - formats.Register(c, f) -} - -// Signature is a threshold signature which includes an aggregated signature and -// the mask of signers from the associated collective authority. -// -// - implements crypto.Signature -type Signature struct { - agg crypto.Signature - mask []byte -} - -// NewSignature returns a new threshold signature. -func NewSignature(agg crypto.Signature, mask []byte) *Signature { - return &Signature{ - agg: agg, - mask: mask, - } -} - -// GetAggregate returns the aggregate of the signature which corresponds to the -// addition of the public keys enabled in the mask. -func (s *Signature) GetAggregate() crypto.Signature { - return s.agg -} - -// GetMask returns a bit mask of which public key is enabled. -func (s *Signature) GetMask() []byte { - return append([]byte{}, s.mask...) -} - -// HasBit returns true when the bit at the given index is set to 1. -func (s *Signature) HasBit(index int) bool { - if index < 0 { - return false - } - - i := index >> shift - if i >= len(s.mask) { - return false - } - - return s.mask[i]&(1<> shift - for i >= len(s.mask) { - s.mask = append(s.mask, 0) - } - - s.mask[i] |= 1 << uint(index&mask) -} - -// Serialize implements serde.Message. It serializes the signature into JSON -// format. -func (s *Signature) Serialize(ctx serde.Context) ([]byte, error) { - format := formats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, s) - if err != nil { - return nil, xerrors.Errorf("couldn't encode signature: %v", err) - } - - return data, nil -} - -// MarshalBinary implements encoding.BinaryMarshaler. -func (s *Signature) MarshalBinary() ([]byte, error) { - buffer, err := s.agg.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal signature: %v", err) - } - - buffer = append(buffer, s.mask...) - - return buffer, nil -} - -// Equal implements crypto.Signature. -func (s *Signature) Equal(o crypto.Signature) bool { - other, ok := o.(*Signature) - return ok && other.agg.Equal(s.agg) && bytes.Equal(s.mask, other.mask) -} - -// String implements fmt.Stringer. It returns a string representation of the -// signature. -func (s *Signature) String() string { - mask := new(big.Int).SetBytes(s.mask) - - return fmt.Sprintf("thres[%b]:%s", mask, s.agg) -} - -// AggKey is the key for the aggregate signature factory. -type AggKey struct{} - -// SignatureFactory is the factory to deserialize collective signature. -// -// - implements crypto.SignatureFactory -// - implements serde.Factory -type SignatureFactory struct { - aggFactory crypto.SignatureFactory -} - -// NewSignatureFactory returns a new signature factory. -func NewSignatureFactory(f crypto.SignatureFactory) SignatureFactory { - return SignatureFactory{ - aggFactory: f, - } -} - -// Deserialize implements serde.Factory. It populates the signature from the -// data if appropriate, otherwise it returns an error. -func (f SignatureFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.SignatureOf(ctx, data) -} - -// SignatureOf implements crypto.SignatureFactory. It populates the signature -// from the data if appropriate, otherwise it returns an error. -func (f SignatureFactory) SignatureOf(ctx serde.Context, data []byte) (crypto.Signature, error) { - format := formats.Get(ctx.GetFormat()) - - ctx = serde.WithFactory(ctx, AggKey{}, f.aggFactory) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode signature: %v", err) - } - - sig, ok := m.(*Signature) - if !ok { - return nil, xerrors.Errorf("invalid signature of type '%T'", m) - } - - return sig, nil -} - -// Verifier is a threshold verifier which can verify threshold signatures by -// aggregating public keys according to the mask. -// -// - implements crypto.Verifier -type Verifier struct { - pubkeys []crypto.PublicKey - factory crypto.VerifierFactory -} - -func newVerifier(ca crypto.CollectiveAuthority, f crypto.VerifierFactory) Verifier { - pubkeys := make([]crypto.PublicKey, 0, ca.Len()) - iter := ca.PublicKeyIterator() - for iter.HasNext() { - pubkeys = append(pubkeys, iter.GetNext()) - } - - return newVerifierArr(pubkeys, f) -} - -func newVerifierArr(pubkeys []crypto.PublicKey, f crypto.VerifierFactory) Verifier { - return Verifier{ - pubkeys: pubkeys, - factory: f, - } -} - -// Verify implements crypto.Verifier. It returns nil if the signature matches -// the aggregate public key for the mask associated to the signature. -func (v Verifier) Verify(msg []byte, s crypto.Signature) error { - signature, ok := s.(*Signature) - if !ok { - return xerrors.Errorf("invalid signature type '%T' != '%T'", s, signature) - } - - pubkeys := make([]crypto.PublicKey, 0, len(v.pubkeys)) - for _, index := range signature.GetIndices() { - pubkeys = append(pubkeys, v.pubkeys[index]) - } - - verifier, err := v.factory.FromArray(pubkeys) - if err != nil { - return xerrors.Errorf("couldn't make verifier: %v", err) - } - - err = verifier.Verify(msg, signature.agg) - if err != nil { - return xerrors.Errorf("invalid signature: %v", err) - } - - return nil -} - -// VerifierFactory is a factory to create a verifier from a list of -// participants. -type verifierFactory struct { - factory crypto.VerifierFactory -} - -// NewThresholdVerifierFactory creates a new verifier factory from the -// underlying verifier factory. -func NewThresholdVerifierFactory(fac crypto.VerifierFactory) crypto.VerifierFactory { - return verifierFactory{ - factory: fac, - } -} - -// FromAuthority implements crypto.VerifierFactory. It creates a verifier from -// the authority so that the mask's signature will pick the participants that -// have participated. The ordering of the authority must be the same. -func (f verifierFactory) FromAuthority(authority crypto.CollectiveAuthority) (crypto.Verifier, error) { - return newVerifier(authority, f.factory), nil -} - -// FromArray implements crypto.VerifierFactory. It creates a verifier from the -// list of public keys so that the mask's signature will pick the participants -// that have participated. The ordering of the keys must be the same. -func (f verifierFactory) FromArray(pubkeys []crypto.PublicKey) (crypto.Verifier, error) { - return newVerifierArr(pubkeys, f.factory), nil -} diff --git a/dela/cosi/threshold/types/types_test.go b/dela/cosi/threshold/types/types_test.go deleted file mode 100644 index 59a4f31..0000000 --- a/dela/cosi/threshold/types/types_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func init() { - RegisterSignatureFormat(fake.GoodFormat, fake.Format{Msg: &Signature{}}) - RegisterSignatureFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterSignatureFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestSignature_GetAggregate(t *testing.T) { - sig := NewSignature(fake.Signature{}, nil) - - require.Equal(t, fake.Signature{}, sig.GetAggregate()) -} - -func TestSignature_GetMask(t *testing.T) { - sig := NewSignature(nil, []byte{1}) - - require.Equal(t, []byte{1}, sig.GetMask()) -} - -func TestSignature_HasBit(t *testing.T) { - sig := &Signature{mask: []byte{0b00000010, 0b10000001}} - - require.True(t, sig.HasBit(1)) - require.True(t, sig.HasBit(8)) - require.True(t, sig.HasBit(15)) - require.False(t, sig.HasBit(16)) - require.False(t, sig.HasBit(-1)) -} - -func TestSignature_GetIndices(t *testing.T) { - sig := &Signature{mask: []byte{0x0c, 0x01}} - - require.Equal(t, []int{2, 3, 8}, sig.GetIndices()) -} - -func TestSignature_Merge(t *testing.T) { - sig := &Signature{} - - err := sig.Merge(fake.NewAggregateSigner(), 2, fake.Signature{}) - require.NoError(t, err) - require.NotNil(t, sig.agg) - - err = sig.Merge(fake.NewAggregateSigner(), 1, fake.Signature{}) - require.NoError(t, err) - - err = sig.Merge(fake.NewAggregateSigner(), 1, fake.Signature{}) - require.EqualError(t, err, "index 1 already merged") - - err = sig.Merge(fake.NewBadSigner(), 0, fake.Signature{}) - require.EqualError(t, err, fake.Err("couldn't aggregate")) - require.Equal(t, []byte{0b00000110}, sig.mask) -} - -func TestSignature_SetBit(t *testing.T) { - sig := &Signature{} - - sig.setBit(-1) - require.Nil(t, sig.mask) - - sig.setBit(8) - require.Len(t, sig.mask, 2) - require.Equal(t, sig.mask[1], uint8(1)) - - sig.setBit(9) - require.Len(t, sig.mask, 2) - require.Equal(t, sig.mask[1], uint8(3)) -} - -func TestSignature_Serialize(t *testing.T) { - sig := Signature{} - - data, err := sig.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = sig.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode signature")) -} - -func TestSignature_MarshalBinary(t *testing.T) { - sig := &Signature{ - agg: fake.Signature{}, - mask: []byte{0xff}, - } - - buffer, err := sig.MarshalBinary() - require.NoError(t, err) - require.Equal(t, []byte{fake.SignatureByte, 0xff}, buffer) - - sig.agg = fake.NewBadSignature() - _, err = sig.MarshalBinary() - require.EqualError(t, err, fake.Err("couldn't marshal signature")) -} - -func TestSignature_Equal(t *testing.T) { - sig := &Signature{ - agg: fake.Signature{}, - mask: []byte{0xff}, - } - - require.True(t, sig.Equal(sig)) - require.False(t, sig.Equal(nil)) -} - -func TestSignature_String(t *testing.T) { - sig := NewSignature(fake.Signature{}, []byte{0xa}) - - require.Equal(t, "thres[1010]:fakeSignature", sig.String()) -} - -func TestSignatureFactory_Deserialize(t *testing.T) { - factory := NewSignatureFactory(fake.SignatureFactory{}) - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, &Signature{}, msg) -} - -func TestSignatureFactory_SignatureOf(t *testing.T) { - factory := NewSignatureFactory(fake.SignatureFactory{}) - - sig, err := factory.SignatureOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, &Signature{}, sig) - - _, err = factory.SignatureOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode signature")) - - _, err = factory.SignatureOf(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid signature of type 'fake.Message'") -} - -func TestVerifier_Verify(t *testing.T) { - call := &fake.Call{} - - verifier := newVerifier( - fake.NewAuthority(3, fake.NewSigner), - fake.NewVerifierFactoryWithCalls(call)) - - err := verifier.Verify([]byte{0xff}, &Signature{mask: []byte{0x3}}) - require.NoError(t, err) - require.Equal(t, 1, call.Len()) - require.Len(t, call.Get(0, 0), 2) - - err = verifier.Verify([]byte{}, nil) - require.EqualError(t, err, "invalid signature type '' != '*types.Signature'") - - verifier.factory = fake.NewBadVerifierFactory() - err = verifier.Verify([]byte{}, &Signature{}) - require.EqualError(t, err, fake.Err("couldn't make verifier")) - - verifier.factory = fake.NewVerifierFactory(fake.NewBadVerifier()) - err = verifier.Verify([]byte{}, &Signature{}) - require.EqualError(t, err, fake.Err("invalid signature")) -} - -func TestVerifierFactory_FromArray(t *testing.T) { - fac := NewThresholdVerifierFactory(fake.NewVerifierFactory(fake.Verifier{})) - - verifier, err := fac.FromArray([]crypto.PublicKey{fake.PublicKey{}}) - require.NoError(t, err) - require.Len(t, verifier.(Verifier).pubkeys, 1) - - verifier, err = fac.FromAuthority(fake.NewAuthority(3, fake.NewSigner)) - require.NoError(t, err) - require.Len(t, verifier.(Verifier).pubkeys, 3) -} diff --git a/dela/crypto/bls/bls.go b/dela/crypto/bls/bls.go deleted file mode 100644 index db1c488..0000000 --- a/dela/crypto/bls/bls.go +++ /dev/null @@ -1,464 +0,0 @@ -// Package bls implements the cryptographic primitives using the BLS signature -// scheme and the BN256 elliptic curve. -// -// Related Papers: -// -// https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html -// -// Documentation Last Review: 05.10.2020 -package bls - -import ( - "bytes" - "fmt" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/pairing" - - //lint:ignore SA1019 we need to fix this, issues opened in #166 - "go.dedis.ch/kyber/v3/sign/bls" - "go.dedis.ch/kyber/v3/util/key" - "golang.org/x/xerrors" -) - -const ( - // Algorithm is the name of the curve used for the BLS signature. - Algorithm = "BLS-CURVE-BN256" -) - -var ( - suite = pairing.NewSuiteBn256() - - pubkeyFormats = registry.NewSimpleRegistry() - sigFormats = registry.NewSimpleRegistry() -) - -// RegisterPublicKeyFormat registers the engine for the provided format. -func RegisterPublicKeyFormat(c serde.Format, f serde.FormatEngine) { - pubkeyFormats.Register(c, f) -} - -// RegisterSignatureFormat registers the engine for the provided format. -func RegisterSignatureFormat(c serde.Format, f serde.FormatEngine) { - sigFormats.Register(c, f) -} - -// PublicKey is the adapter a of BN256 public key. -// -// - implements crypto.PublicKey -type PublicKey struct { - point kyber.Point -} - -// NewPublicKey creates a new public key by unmarshaling the data into BN256 -// point. -func NewPublicKey(data []byte) (PublicKey, error) { - point := suite.Point() - err := point.UnmarshalBinary(data) - if err != nil { - return PublicKey{}, err - } - - return PublicKey{point: point}, nil -} - -// NewPublicKeyFromPoint creates a new public key from an existing point. -func NewPublicKeyFromPoint(point kyber.Point) PublicKey { - return PublicKey{ - point: point, - } -} - -// MarshalBinary implements encoding.BinaryMarshaler. It produces a slice of -// bytes representing the public key. -func (pk PublicKey) MarshalBinary() ([]byte, error) { - return pk.point.MarshalBinary() -} - -// Serialize implements serde.Message. It returns the serialized data of the -// public key. -func (pk PublicKey) Serialize(ctx serde.Context) ([]byte, error) { - format := pubkeyFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, pk) - if err != nil { - return nil, xerrors.Errorf("couldn't encode public key: %v", err) - } - - return data, nil -} - -// Verify implements crypto.PublicKey. It returns nil if the signature matches -// the message for this public key. -func (pk PublicKey) Verify(msg []byte, sig crypto.Signature) error { - signature, ok := sig.(Signature) - if !ok { - return xerrors.Errorf("invalid signature type '%T'", sig) - } - - err := bls.Verify(suite, pk.point, msg, signature.data) - if err != nil { - return xerrors.Errorf("bls verify failed: %v", err) - } - - return nil -} - -// Equal implements crypto.PublicKey. It returns true if the other public key -// is the same. -func (pk PublicKey) Equal(other interface{}) bool { - pubkey, ok := other.(PublicKey) - if !ok { - return false - } - - return pubkey.point.Equal(pk.point) -} - -// MarshalText implements encoding.TextMarshaler. It returns a text -// representation of the public key. -func (pk PublicKey) MarshalText() ([]byte, error) { - buffer, err := pk.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return []byte(fmt.Sprintf("bls:%x", buffer)), nil -} - -// GetPoint returns the kyber.Point. -func (pk PublicKey) GetPoint() kyber.Point { - return pk.point -} - -// String implements fmt.String. It returns a string representation of the -// point. -func (pk PublicKey) String() string { - buffer, err := pk.MarshalText() - if err != nil { - return "bls:malformed_point" - } - - // Output only the prefix and 16 characters of the buffer in hexadecimal. - return string(buffer)[:4+16] -} - -// Signature is the adapter of a BN256 signature. -// -// - implements crypto.Signature -type Signature struct { - data []byte -} - -// NewSignature creates a new signature from the provided data. -func NewSignature(data []byte) Signature { - return Signature{ - data: data, - } -} - -// MarshalBinary implements encoding.BinaryMarshaler. It returns a slice of -// bytes representing the signature. -func (sig Signature) MarshalBinary() ([]byte, error) { - return sig.data, nil -} - -// Serialize implements serde.Message. It returns the serialized data of the -// signature. -func (sig Signature) Serialize(ctx serde.Context) ([]byte, error) { - format := sigFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, sig) - if err != nil { - return nil, xerrors.Errorf("couldn't encode signature: %v", err) - } - - return data, nil -} - -// Equal implements crypto.Signature. It returns true if both signatures are the -// same. -func (sig Signature) Equal(other crypto.Signature) bool { - otherSig, ok := other.(Signature) - if !ok { - return false - } - - return bytes.Equal(sig.data, otherSig.data) -} - -// String implements fmt.Stringer. It returns a string representation of the -// signature. -func (sig Signature) String() string { - return fmt.Sprintf("bls:%x", sig.data) -} - -// publicKeyFactory is a factory to deserialize public keys of the BN256 -// elliptic curve. -// -// - implements crypto.PublicKeyFactory -type publicKeyFactory struct{} - -// NewPublicKeyFactory returns a new instance of the factory. -func NewPublicKeyFactory() crypto.PublicKeyFactory { - return publicKeyFactory{} -} - -// Deserialize implements serde.Factory. It returns the public key of the data -// if appropriate, otherwise an error. -func (f publicKeyFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := pubkeyFormats.Get(ctx.GetFormat()) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode public key: %v", err) - } - - return m, nil -} - -// PublicKeyOf implements crypto.PublicKeyFactory. It returns the public key of -// the data if appropriate, otherwise an error. -func (f publicKeyFactory) PublicKeyOf(ctx serde.Context, data []byte) (crypto.PublicKey, error) { - m, err := f.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - pubkey, ok := m.(crypto.PublicKey) - if !ok { - return nil, xerrors.Errorf("invalid public key of type '%T'", m) - } - - return pubkey, nil -} - -// FromBytes implements crypto.PublicKeyFactory. It returns the public key -// unmarshaled from the bytes. -func (f publicKeyFactory) FromBytes(data []byte) (crypto.PublicKey, error) { - pubkey, err := NewPublicKey(data) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal key: %v", err) - } - - return pubkey, nil -} - -// signatureFactory is a factory to deserialize signatures of the BN256 elliptic -// curve. -// -// - implements crypto.SignatureFactory -type signatureFactory struct{} - -// NewSignatureFactory returns a new instance of the factory. -func NewSignatureFactory() crypto.SignatureFactory { - return signatureFactory{} -} - -// Deserialize implements serde.Factory. It returns the signature of the data if -// appropriate, otherwise an error. -func (f signatureFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := sigFormats.Get(ctx.GetFormat()) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode signature: %v", err) - } - - return m, nil -} - -// SignatureOf implements crypto.SignatureFactory. It returns the signature of -// the data if appropriate, otherwise an error. -func (f signatureFactory) SignatureOf(ctx serde.Context, data []byte) (crypto.Signature, error) { - m, err := f.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - sig, ok := m.(Signature) - if !ok { - return nil, xerrors.Errorf("invalid signature of type '%T'", m) - } - - return sig, nil -} - -// blsVerifier is a verifier for BLS signatures to match against a message and -// the public key associated to one or several identities. -// -// - implements crypto.Verifier -type blsVerifier struct { - points []kyber.Point -} - -// NewVerifier returns a new verifier that can verify BLS signatures. -func newVerifier(points []kyber.Point) crypto.Verifier { - return blsVerifier{points: points} -} - -// Verify implements crypto.Verifier. It returns nil if the signature matches -// the message, or an error otherwise. -func (v blsVerifier) Verify(msg []byte, sig crypto.Signature) error { - aggKey := bls.AggregatePublicKeys(suite, v.points...) - - err := bls.Verify(suite, aggKey, msg, sig.(Signature).data) - if err != nil { - return err - } - - return nil -} - -// verifierFactory is a factory to create verifiers from an authority or a list -// of public keys. -// -// - implements crypto.VerifierFactory -type verifierFactory struct{} - -// FromIterator implements crypto.VerifierFactory. It returns a verifier that -// will verify the signatures collectively signed by all the signers associated -// with the public keys. -func (v verifierFactory) FromAuthority(ca crypto.CollectiveAuthority) (crypto.Verifier, error) { - if ca == nil { - return nil, xerrors.New("authority is nil") - } - - points := make([]kyber.Point, 0, ca.Len()) - iter := ca.PublicKeyIterator() - for iter.HasNext() { - next := iter.GetNext() - pk, ok := next.(PublicKey) - if !ok { - return nil, xerrors.Errorf("invalid public key type: %T", next) - } - - points = append(points, pk.point) - } - - return newVerifier(points), nil -} - -// FromArray implements crypto.VerifierFactory. It returns a verifier that will -// verify the signatures collectively signed by all the signers associated with -// the public keys. -func (v verifierFactory) FromArray(publicKeys []crypto.PublicKey) (crypto.Verifier, error) { - points := make([]kyber.Point, len(publicKeys)) - for i, pubkey := range publicKeys { - pk, ok := pubkey.(PublicKey) - if !ok { - return nil, xerrors.Errorf("invalid public key type: %T", pubkey) - } - - points[i] = pk.point - } - - return newVerifier(points), nil -} - -// Signer is the adapter of a private key from the Kyber package for the BN256 -// elliptic curve. -// -// - implements crypto.AggregateSigner -// - implements encoding.BinaryMarshaler -type Signer struct { - public kyber.Point - private kyber.Scalar -} - -// NewSigner generates and returns a new random signer. -func NewSigner() Signer { - return Generate().(Signer) -} - -// NewSignerFromBytes restores a signer from a marshalling. -func NewSignerFromBytes(data []byte) (crypto.AggregateSigner, error) { - scalar := suite.Scalar() - err := scalar.UnmarshalBinary(data) - if err != nil { - return nil, xerrors.Errorf("while unmarshaling scalar: %v", err) - } - - pubkey := suite.Point().Mul(scalar, nil) - - signer := Signer{ - public: pubkey, - private: scalar, - } - - return signer, nil -} - -// Generate returns a new random BLS signer that supports aggregation. -func Generate() crypto.Signer { - kp := key.NewKeyPair(suite) - return Signer{ - private: kp.Private, - public: kp.Public, - } -} - -// GetVerifierFactory implements crypto.Signer. It returns the verifier factory -// for BLS signatures. -func (s Signer) GetVerifierFactory() crypto.VerifierFactory { - return verifierFactory{} -} - -// GetPublicKeyFactory implements crypto.Signer. It returns the public key -// factory for BLS signatures. -func (s Signer) GetPublicKeyFactory() crypto.PublicKeyFactory { - return publicKeyFactory{} -} - -// GetSignatureFactory implements crypto.Signer. It returns the signature -// factory for BLS signatures. -func (s Signer) GetSignatureFactory() crypto.SignatureFactory { - return signatureFactory{} -} - -// GetPublicKey implements crypto.Signer. It returns the public key of the -// signer that can be used to verify signatures. -func (s Signer) GetPublicKey() crypto.PublicKey { - return PublicKey{point: s.public} -} - -// Sign implements crypto.Signer. It signs the message in parameter and returns -// the signature, or an error if it cannot sign. -func (s Signer) Sign(msg []byte) (crypto.Signature, error) { - sig, err := bls.Sign(suite, s.private, msg) - if err != nil { - return nil, xerrors.Errorf("couldn't make bls signature: %v", err) - } - - return Signature{data: sig}, nil -} - -// Aggregate implements crypto.Signer. It aggregates the signatures into a -// single one that can be verifier with the aggregated public key associated. -func (s Signer) Aggregate(signatures ...crypto.Signature) (crypto.Signature, error) { - buffers := make([][]byte, len(signatures)) - for i, sig := range signatures { - buffers[i] = sig.(Signature).data - } - - agg, err := bls.AggregateSignatures(suite, buffers...) - if err != nil { - return nil, xerrors.Errorf("couldn't aggregate: %v", err) - } - - return Signature{data: agg}, nil -} - -// MarshalBinary implements encoding.BinaryMarshaler. It returns a binary -// representation of the signer. -func (s Signer) MarshalBinary() ([]byte, error) { - data, err := s.private.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("while marshaling scalar: %v", err) - } - - return data, nil -} diff --git a/dela/crypto/bls/bls_test.go b/dela/crypto/bls/bls_test.go deleted file mode 100644 index c3656a1..0000000 --- a/dela/crypto/bls/bls_test.go +++ /dev/null @@ -1,386 +0,0 @@ -package bls - -import ( - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" - "go.dedis.ch/kyber/v3" -) - -func init() { - RegisterPublicKeyFormat(fake.GoodFormat, fake.Format{Msg: PublicKey{}}) - RegisterPublicKeyFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterPublicKeyFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterSignatureFormat(fake.GoodFormat, fake.Format{Msg: Signature{}}) - RegisterSignatureFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterSignatureFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestPublicKey_New(t *testing.T) { - signer := Generate() - data, err := signer.GetPublicKey().MarshalBinary() - require.NoError(t, err) - - pk, err := NewPublicKey(data) - require.NoError(t, err) - require.True(t, signer.GetPublicKey().Equal(pk)) - - _, err = NewPublicKey(nil) - require.Error(t, err) -} - -func TestPublicKey_MarshalBinary(t *testing.T) { - signer := Generate() - - buffer, err := signer.GetPublicKey().MarshalBinary() - require.NoError(t, err) - require.NotEmpty(t, buffer) -} - -func TestPublicKey_Serialize(t *testing.T) { - pubkey := NewPublicKeyFromPoint(nil) - - data, err := pubkey.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = pubkey.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode public key")) -} - -func TestPublicKey_Verify(t *testing.T) { - msg := []byte("deadbeef") - signer := Generate() - sig, err := signer.Sign(msg) - require.NoError(t, err) - - err = signer.GetPublicKey().Verify(msg, sig) - require.NoError(t, err) - - err = signer.GetPublicKey().Verify([]byte{}, sig) - require.EqualError(t, err, "bls verify failed: bls: invalid signature") - - err = signer.GetPublicKey().Verify(msg, fake.Signature{}) - require.EqualError(t, err, "invalid signature type 'fake.Signature'") -} - -func TestPublicKey_Equal(t *testing.T) { - signerA := Generate() - signerB := Generate() - require.True(t, signerA.GetPublicKey().Equal(signerA.GetPublicKey())) - require.True(t, signerB.GetPublicKey().Equal(signerB.GetPublicKey())) - require.False(t, signerA.GetPublicKey().Equal(signerB.GetPublicKey())) - require.False(t, signerA.GetPublicKey().Equal(fake.PublicKey{})) -} - -func TestPublicKey_MarshalText(t *testing.T) { - signer := Generate() - text, err := signer.GetPublicKey().MarshalText() - require.NoError(t, err) - require.Regexp(t, "^bls:", string(text)) - - pk := PublicKey{point: badPoint{}} - _, err = pk.MarshalText() - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestPublicKey_GetPoint(t *testing.T) { - point := suite.Point() - pk := PublicKey{point: point} - - require.True(t, point.Equal(pk.GetPoint())) -} - -func TestPublicKey_String(t *testing.T) { - signer := Generate() - str := signer.GetPublicKey().(PublicKey).String() - require.Contains(t, str, "bls:") - - pk := PublicKey{point: badPoint{}} - str = pk.String() - require.Equal(t, "bls:malformed_point", str) -} - -func TestPublicKeyFactory_Deserialize(t *testing.T) { - factory := NewPublicKeyFactory() - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, PublicKey{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode public key")) -} - -func TestPublicKeyFactory_PublicKeyOf(t *testing.T) { - factory := NewPublicKeyFactory() - - pk, err := factory.PublicKeyOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, PublicKey{}, pk) - - _, err = factory.PublicKeyOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode public key")) - - _, err = factory.PublicKeyOf(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid public key of type 'fake.Message'") -} - -func TestPublicKeyFactory_FromBytes(t *testing.T) { - factory := NewPublicKeyFactory() - - point := suite.Point() - data, err := point.MarshalBinary() - require.NoError(t, err) - - pk, err := factory.FromBytes(data) - require.NoError(t, err) - require.True(t, pk.(PublicKey).point.Equal(point)) - - _, err = factory.FromBytes(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to unmarshal key: ") -} - -func TestSignature_MarshalBinary(t *testing.T) { - f := func(data []byte) bool { - sig := NewSignature(data) - buffer, err := sig.MarshalBinary() - require.NoError(t, err) - require.Equal(t, data, buffer) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestSignature_Serialize(t *testing.T) { - sig := Signature{} - - data, err := sig.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = sig.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode signature")) -} - -func TestSignature_Equal(t *testing.T) { - f := func(data []byte) bool { - sig := Signature{data: data} - require.True(t, sig.Equal(Signature{data: data})) - - buffer := append(append([]byte{}, data...), 0xaa) - require.False(t, sig.Equal(Signature{data: buffer})) - - require.False(t, sig.Equal(fake.Signature{})) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestSignature_String(t *testing.T) { - sig := Signature{data: []byte{1, 2, 3}} - require.Equal(t, "bls:010203", sig.String()) -} - -func TestSignatureFactory_Deserialize(t *testing.T) { - factory := NewSignatureFactory() - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, Signature{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode signature")) -} - -func TestSignatureFactory_SignatureOf(t *testing.T) { - factory := NewSignatureFactory() - - sig, err := factory.SignatureOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, Signature{}, sig) - - _, err = factory.SignatureOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode signature")) - - _, err = factory.SignatureOf(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid signature of type 'fake.Message'") -} - -func TestVerifier_Verify(t *testing.T) { - f := func(msg []byte) bool { - signer := Generate() - sig, err := signer.Sign(msg) - require.NoError(t, err) - - verifier := newVerifier( - []kyber.Point{signer.GetPublicKey().(PublicKey).point}, - ) - err = verifier.Verify(msg, sig) - require.NoError(t, err) - - err = verifier.Verify(append([]byte{1}, msg...), sig) - require.Error(t, err) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestVerifierFactory_FromAuthority(t *testing.T) { - factory := verifierFactory{} - - verifier, err := factory.FromAuthority(fake.NewAuthority(2, Generate)) - require.NoError(t, err) - require.IsType(t, blsVerifier{}, verifier) - require.Len(t, verifier.(blsVerifier).points, 2) - require.NotNil(t, verifier.(blsVerifier).points[0]) - require.NotNil(t, verifier.(blsVerifier).points[1]) - - _, err = factory.FromAuthority(nil) - require.EqualError(t, err, "authority is nil") - - _, err = factory.FromAuthority(fake.NewAuthority(2, fake.NewSigner)) - require.EqualError(t, err, "invalid public key type: fake.PublicKey") -} - -func TestVerifierFactory_FromArray(t *testing.T) { - factory := verifierFactory{} - - verifier, err := factory.FromArray([]crypto.PublicKey{PublicKey{}}) - require.NoError(t, err) - require.Len(t, verifier.(blsVerifier).points, 1) - - verifier, err = factory.FromArray(nil) - require.NoError(t, err) - require.Empty(t, verifier.(blsVerifier).points) - - _, err = factory.FromArray([]crypto.PublicKey{fake.PublicKey{}}) - require.EqualError(t, err, "invalid public key type: fake.PublicKey") -} - -func TestSigner_GetVerifierFactory(t *testing.T) { - signer := NewSigner() - - factory := signer.GetVerifierFactory() - require.NotNil(t, factory) - require.IsType(t, verifierFactory{}, factory) -} - -func TestSigner_GetPublicKeyFactory(t *testing.T) { - signer := Generate() - - factory := signer.GetPublicKeyFactory() - require.NotNil(t, factory) - require.IsType(t, publicKeyFactory{}, factory) -} - -func TestSigner_GetSignatureFactory(t *testing.T) { - signer := Generate() - - factory := signer.GetSignatureFactory() - require.NotNil(t, factory) - require.IsType(t, signatureFactory{}, factory) -} - -func TestSigner_Sign(t *testing.T) { - signer := NewSigner() - f := func(msg []byte) bool { - sig, err := signer.Sign(msg) - require.NoError(t, err) - - verifier, err := signer.GetVerifierFactory().FromArray( - []crypto.PublicKey{signer.GetPublicKey()}, - ) - require.NoError(t, err) - require.NoError(t, verifier.Verify(msg, sig)) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestSigner_Aggregate(t *testing.T) { - N := 3 - - f := func(msg []byte) bool { - signatures := make([]crypto.Signature, N) - pubkeys := make([]crypto.PublicKey, N) - for i := 0; i < N; i++ { - signer := Generate() - pubkeys[i] = signer.GetPublicKey() - sig, err := signer.Sign(msg) - require.NoError(t, err) - signatures[i] = sig - } - - signer := NewSigner() - agg, err := signer.Aggregate(signatures...) - require.NoError(t, err) - - verifier, err := signer.GetVerifierFactory().FromArray(pubkeys) - require.NoError(t, err) - err = verifier.Verify(msg, agg) - require.NoError(t, err) - - return agg != nil - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestSigner_MarshalBinary(t *testing.T) { - signer := NewSigner() - - sig, err := signer.Sign([]byte{1, 2, 3}) - require.NoError(t, err) - - data, err := signer.MarshalBinary() - require.NoError(t, err) - - next, err := NewSignerFromBytes(data) - require.NoError(t, err) - require.NoError(t, next.GetPublicKey().Verify([]byte{1, 2, 3}, sig)) - - signer.private = badScalar{} - _, err = signer.MarshalBinary() - require.EqualError(t, err, fake.Err("while marshaling scalar")) - - _, err = NewSignerFromBytes(nil) - require.EqualError(t, err, "while unmarshaling scalar: UnmarshalBinary: wrong size buffer") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badPoint struct { - kyber.Point -} - -func (p badPoint) MarshalBinary() ([]byte, error) { - return nil, fake.GetError() -} - -type badScalar struct { - kyber.Scalar -} - -func (s badScalar) MarshalBinary() ([]byte, error) { - return nil, fake.GetError() -} diff --git a/dela/crypto/bls/command/action.go b/dela/crypto/bls/command/action.go deleted file mode 100644 index a77c794..0000000 --- a/dela/crypto/bls/command/action.go +++ /dev/null @@ -1,118 +0,0 @@ -package command - -import ( - "encoding/base64" - "fmt" - "io" - "os" - - "go.dedis.ch/dela/crypto" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/crypto/bls" - "golang.org/x/xerrors" -) - -// action defines the different cli actions of the BLS commands. Defining -// functions and printer helps in testing the commands. -type action struct { - printer io.Writer - - genSigner func() ([]byte, error) - getPubKey func([]byte) (crypto.PublicKey, error) - - readFile func(filename string) ([]byte, error) - saveFile func(path string, force bool, data []byte) error -} - -func (a action) newSignerAction(flags cli.Flags) error { - data, err := a.genSigner() - if err != nil { - return xerrors.Errorf("failed to marshal signer: %v", err) - } - - switch flags.String("save") { - case "": - fmt.Fprintln(a.printer, string(data)) - default: - err := a.saveFile(flags.String("save"), flags.Bool("force"), data) - if err != nil { - return xerrors.Errorf("failed to save files: %v", err) - } - } - - return nil -} - -func (a action) loadSignerAction(flags cli.Flags) error { - data, err := a.readFile(flags.Path("path")) - if err != nil { - return xerrors.Errorf("failed to read data: %v", err) - } - - var out []byte - - switch flags.String("format") { - case "PUBKEY": - pubkey, err := a.getPubKey(data) - if err != nil { - return xerrors.Errorf("failed to get PUBKEY: %v", err) - } - - out, err = pubkey.MarshalText() - if err != nil { - return xerrors.Errorf("failed to marshal pubkey: %v", err) - } - - case "BASE64_PUBKEY": - pubkey, err := a.getPubKey(data) - if err != nil { - return xerrors.Errorf("failed to get PUBKEY: %v", err) - } - - buf, err := pubkey.MarshalBinary() - if err != nil { - return xerrors.Errorf("failed to marshal pubkey: %v", err) - } - - out = []byte(base64.StdEncoding.EncodeToString(buf)) - - case "BASE64": - out = []byte(base64.StdEncoding.EncodeToString(data)) - - default: - return xerrors.Errorf("unknown format '%s'", flags.String("format")) - } - - fmt.Fprintln(a.printer, string(out)) - - return nil -} - -func saveToFile(path string, force bool, data []byte) error { - if !force && fileExist(path) { - return xerrors.Errorf("file '%s' already exist, use --force if you "+ - "want to overwrite", path) - } - - err := os.WriteFile(path, data, os.ModePerm) - if err != nil { - return xerrors.Errorf("failed to write file: %v", err) - } - - return nil -} - -func fileExist(path string) bool { - _, err := os.Stat(path) - return !os.IsNotExist(err) -} - -func getPubkey(data []byte) (crypto.PublicKey, error) { - signer, err := bls.NewSignerFromBytes(data) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal signer: %v", err) - } - - return signer.GetPublicKey(), nil -} diff --git a/dela/crypto/bls/command/action_test.go b/dela/crypto/bls/command/action_test.go deleted file mode 100644 index a0a0bff..0000000 --- a/dela/crypto/bls/command/action_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package command - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli/node" - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestNewSignerAction(t *testing.T) { - action := action{ - printer: io.Discard, - genSigner: badGenSigner, - saveFile: fakeSaveFile, - getPubKey: getPubkey, - } - - set := node.FlagSet{} - err := action.newSignerAction(set) - require.EqualError(t, err, fake.Err("failed to marshal signer")) - - action.genSigner = bls.NewSigner().MarshalBinary - err = action.newSignerAction(set) - require.NoError(t, err) - - set["save"] = "/do/not/exist" - action.saveFile = badSaveFile - - err = action.newSignerAction(set) - require.EqualError(t, err, fake.Err("failed to save files")) -} - -func TestLoadSignerAction(t *testing.T) { - action := action{ - printer: io.Discard, - readFile: badReadFile, - } - - set := node.FlagSet{} - err := action.loadSignerAction(set) - require.EqualError(t, err, fake.Err("failed to read data")) - - action.readFile = fakeReadFile - err = action.loadSignerAction(set) - require.EqualError(t, err, "unknown format ''") - - set["format"] = "PUBKEY" - action.getPubKey = badGetPubKey - err = action.loadSignerAction(set) - require.EqualError(t, err, fake.Err("failed to get PUBKEY")) - - action.getPubKey = wrongGetPubKey - err = action.loadSignerAction(set) - require.EqualError(t, err, fake.Err("failed to marshal pubkey")) - - set["format"] = "BASE64_PUBKEY" - action.getPubKey = badGetPubKey - err = action.loadSignerAction(set) - require.EqualError(t, err, fake.Err("failed to get PUBKEY")) - - action.getPubKey = wrongGetPubKey - err = action.loadSignerAction(set) - require.EqualError(t, err, fake.Err("failed to marshal pubkey")) - - set["format"] = "BASE64_PUBKEY" - action.getPubKey = fakeGetPubKey - err = action.loadSignerAction(set) - require.NoError(t, err) - - set["format"] = "BASE64" - action.getPubKey = badGetPubKey - err = action.loadSignerAction(set) - require.NoError(t, err) -} - -func TestSaveToFile(t *testing.T) { - path, err := os.MkdirTemp("", "dela-test-") - require.NoError(t, err) - - defer os.RemoveAll(path) - - file := filepath.Join(path, "test") - err = saveToFile(file, false, []byte{1}) - require.NoError(t, err) - - res, err := os.ReadFile(file) - require.NoError(t, err) - require.Equal(t, []byte{1}, res) - - err = saveToFile(file, false, nil) - require.Regexp(t, "^file '.*' already exist, use --force if you want to overwrite$", err) - - err = saveToFile("/not/exist", true, nil) - require.Regexp(t, "^failed to write file:", err) - - err = saveToFile(file, true, []byte{2}) - require.NoError(t, err) - - res, err = os.ReadFile(file) - require.NoError(t, err) - require.Equal(t, []byte{2}, res) -} - -func TestGetPUBKEY_Happy(t *testing.T) { - buf, err := bls.NewSigner().MarshalBinary() - require.NoError(t, err) - - _, err = getPubkey(buf) - require.NoError(t, err) -} - -func TestGetPUBKEY_Error(t *testing.T) { - _, err := getPubkey(nil) - require.EqualError(t, err, "failed to unmarshal signer: while unmarshaling scalar: UnmarshalBinary: wrong size buffer") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func badGenSigner() ([]byte, error) { - return nil, fake.GetError() -} - -func badReadFile(path string) ([]byte, error) { - return nil, fake.GetError() -} - -func badSaveFile(path string, force bool, data []byte) error { - return fake.GetError() -} - -func fakeReadFile(path string) ([]byte, error) { - return nil, nil -} - -func fakeSaveFile(path string, force bool, data []byte) error { - return nil -} - -func badGetPubKey([]byte) (crypto.PublicKey, error) { - return nil, fake.GetError() -} - -func wrongGetPubKey([]byte) (crypto.PublicKey, error) { - return fake.NewBadPublicKey(), nil -} - -func fakeGetPubKey([]byte) (crypto.PublicKey, error) { - return bls.Generate().GetPublicKey(), nil -} diff --git a/dela/crypto/bls/command/command.go b/dela/crypto/bls/command/command.go deleted file mode 100644 index 15da38f..0000000 --- a/dela/crypto/bls/command/command.go +++ /dev/null @@ -1,57 +0,0 @@ -// Package command defines cli commands for the bls package. -package command - -import ( - "os" - - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/crypto/bls" -) - -// Initializer implements the BLS initializer for the crypto CLI. -// -// - implements cli.Initializer -type Initializer struct { -} - -// SetCommands implements cli.Initializer. -func (i Initializer) SetCommands(provider cli.Provider) { - action := action{ - printer: os.Stdout, - - genSigner: bls.NewSigner().MarshalBinary, - getPubKey: getPubkey, - readFile: os.ReadFile, - saveFile: saveToFile, - } - - cmd := provider.SetCommand("bls") - signer := cmd.SetSubCommand("signer") - - new := signer.SetSubCommand("new") - new.SetDescription("create a new bls signer") - new.SetFlags(cli.StringFlag{ - Name: "save", - Usage: "if provided, save the signer to that file", - Required: false, - }, cli.BoolFlag{ - Name: "force", - Usage: "in the case it saves the signer, will overwrite if needed", - Required: false, - }) - new.SetAction(action.newSignerAction) - - read := signer.SetSubCommand("read") - read.SetDescription("read a signer") - read.SetFlags(cli.StringFlag{ - Name: "path", - Usage: "path to the signer's file", - Required: true, - }, cli.StringFlag{ - Name: "format", - Usage: "output format: [PUBKEY | BASE64 | BASE64_PUBKEY]", - Value: "PUBKEY", - Required: false, - }) - read.SetAction(action.loadSignerAction) -} diff --git a/dela/crypto/bls/command/command_test.go b/dela/crypto/bls/command/command_test.go deleted file mode 100644 index 90d3b96..0000000 --- a/dela/crypto/bls/command/command_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package command - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/cli" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestSetCommands(t *testing.T) { - init := Initializer{} - - call := &fake.Call{} - provider := fakeBuilder{call: call} - init.SetCommands(provider) - - require.Equal(t, 10, call.Len()) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type fakeCommandBuilder struct { - call *fake.Call -} - -func (b fakeCommandBuilder) SetSubCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return b -} - -func (b fakeCommandBuilder) SetDescription(value string) { - b.call.Add(value) -} - -func (b fakeCommandBuilder) SetFlags(flags ...cli.Flag) { - b.call.Add(flags) -} - -func (b fakeCommandBuilder) SetAction(a cli.Action) { - b.call.Add(a) -} - -type fakeBuilder struct { - call *fake.Call -} - -func (b fakeBuilder) SetCommand(name string) cli.CommandBuilder { - b.call.Add(name) - return fakeCommandBuilder(b) -} diff --git a/dela/crypto/bls/example_test.go b/dela/crypto/bls/example_test.go deleted file mode 100644 index 924b3c2..0000000 --- a/dela/crypto/bls/example_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package bls - -import ( - "fmt" - - "go.dedis.ch/dela/crypto" -) - -func ExampleSigner_Sign() { - signerA := NewSigner() - signerB := NewSigner() - - publicKeys := []crypto.PublicKey{ - signerA.GetPublicKey(), - signerB.GetPublicKey(), - } - - message := []byte("42") - - signatureA, err := signerA.Sign(message) - if err != nil { - panic("signer A failed: " + err.Error()) - } - - signatureB, err := signerB.Sign(message) - if err != nil { - panic("signer B failed: " + err.Error()) - } - - aggregate, err := signerA.Aggregate(signatureA, signatureB) - if err != nil { - panic("aggregate failed: " + err.Error()) - } - - verifier, err := signerA.GetVerifierFactory().FromArray(publicKeys) - if err != nil { - panic("verifier failed: " + err.Error()) - } - - err = verifier.Verify(message, aggregate) - if err != nil { - panic("invalid signature: " + err.Error()) - } - - fmt.Println("Success") - - // Output: Success -} diff --git a/dela/crypto/bls/json/json.go b/dela/crypto/bls/json/json.go deleted file mode 100644 index 839d5ab..0000000 --- a/dela/crypto/bls/json/json.go +++ /dev/null @@ -1,113 +0,0 @@ -package json - -import ( - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/crypto/common/json" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - bls.RegisterPublicKeyFormat(serde.FormatJSON, pubkeyFormat{}) - bls.RegisterSignatureFormat(serde.FormatJSON, sigFormat{}) -} - -// PubkeyFormat is the engine to encode and decode BLS-BN256 public keys in JSON -// format. -// -// - implements serde.FormatEngine -type pubkeyFormat struct{} - -// Encode implements serde.FormatEngine. It serialized the public key message in -// JSON if appropriate, otherwise it returns an error. -func (f pubkeyFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - pubkey, ok := msg.(bls.PublicKey) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - buffer, err := pubkey.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal point: %v", err) - } - - m := json.PublicKey{ - Algorithm: json.Algorithm{Name: bls.Algorithm}, - Data: buffer, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the public key with JSON -// data if appropriate, otherwise it returns an error. -func (f pubkeyFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := json.PublicKey{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize data: %v", err) - } - - pubkey, err := bls.NewPublicKey(m.Data) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal point: %v", err) - } - - return pubkey, nil -} - -// SigFormat is the engine to encode and decode signature messages in JSON -// format. -// -// - implements serde.FormatEngine -type sigFormat struct{} - -// Encode implements serde.FormatEngine. It returns the serialized data of the -// signature message if appropriate, otherwise an error. -func (f sigFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - sig, ok := msg.(bls.Signature) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - buffer, err := sig.MarshalBinary() - assert(err) - - m := json.Signature{ - Algorithm: json.Algorithm{Name: bls.Algorithm}, - Data: buffer, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the signature with the -// JSON data if appropriate, otherwise it returns an error. -func (f sigFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := json.Signature{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize data: %v", err) - } - - return bls.NewSignature(m.Data), nil -} - -// Current implementation cannot return an error but it might change in the -// future therefore an assertion is made to detect if it changes. -func assert(err error) { - if err != nil { - panic("Implementation of the BLS signature is expected " + - "to return a nil when marshaling but an error has been found: " + err.Error()) - } -} diff --git a/dela/crypto/bls/json/json_test.go b/dela/crypto/bls/json/json_test.go deleted file mode 100644 index 7fd9900..0000000 --- a/dela/crypto/bls/json/json_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package json - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" - "go.dedis.ch/kyber/v3" -) - -func TestPubkeyFormat_Encode(t *testing.T) { - signer := bls.Generate() - format := pubkeyFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := format.Encode(ctx, signer.GetPublicKey()) - require.NoError(t, err) - require.Contains(t, string(data), fmt.Sprintf(`{"Name":"%s","Data":`, bls.Algorithm)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(ctx, bls.NewPublicKeyFromPoint(badPoint{})) - require.EqualError(t, err, fake.Err("couldn't marshal point")) - - _, err = format.Encode(fake.NewBadContext(), signer.GetPublicKey()) - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestPubkeyFormat_Decode(t *testing.T) { - signer := bls.Generate() - format := pubkeyFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := signer.GetPublicKey().Serialize(ctx) - require.NoError(t, err) - - pubkey, err := format.Decode(ctx, data) - require.NoError(t, err) - require.True(t, signer.GetPublicKey().Equal(pubkey.(bls.PublicKey))) - - _, err = format.Decode(ctx, []byte(`{"Data":[]}`)) - require.EqualError(t, err, - "couldn't unmarshal point: bn256.G2: not enough data") - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize data")) -} - -func TestSigFormat_Encode(t *testing.T) { - sig := bls.NewSignature([]byte("deadbeef")) - format := sigFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := format.Encode(ctx, sig) - require.NoError(t, err) - require.Contains(t, string(data), fmt.Sprintf(`{"Name":"%s","Data":`, bls.Algorithm)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), sig) - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestSigFormat_Decode(t *testing.T) { - format := sigFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - - sig, err := format.Decode(ctx, []byte(`{"Data":"QQ=="}`)) - require.NoError(t, err) - require.Equal(t, bls.NewSignature([]byte("A")), sig) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{"Data":"QQ=="}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize data")) -} - -func TestAssert(t *testing.T) { - defer func() { - r := recover() - require.Contains(t, r, fake.GetError().Error()) - }() - - assert(fake.GetError()) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badPoint struct { - kyber.Point -} - -func (p badPoint) MarshalBinary() ([]byte, error) { - return nil, fake.GetError() -} diff --git a/dela/crypto/common/common.go b/dela/crypto/common/common.go deleted file mode 100644 index 4d429fe..0000000 --- a/dela/crypto/common/common.go +++ /dev/null @@ -1,177 +0,0 @@ -// Package common implements the factories of the crypto primitives to allow the -// use of multiple algorithms over the same communication channel. -// -// Documentation Last Review: 05.10.2020 -package common - -import ( - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "golang.org/x/xerrors" -) - -var algFormats = registry.NewSimpleRegistry() - -// RegisterAlgorithmFormat registers the engine for the provided format. -func RegisterAlgorithmFormat(c serde.Format, f serde.FormatEngine) { - algFormats.Register(c, f) -} - -// Algorithm contains information about a signature algorithm. -// -// - implements serde.Message -type Algorithm struct { - name string -} - -// NewAlgorithm returns a new algorithm from the provided name. -func NewAlgorithm(name string) Algorithm { - return Algorithm{name: name} -} - -// GetName returns the name of the algorithm. -func (alg Algorithm) GetName() string { - return alg.name -} - -// Serialize implements serde.Message. It returns the serialized for the -// algorithm. -func (alg Algorithm) Serialize(ctx serde.Context) ([]byte, error) { - format := algFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, alg) - if err != nil { - return nil, xerrors.Errorf("couldn't encode algorithm: %v", err) - } - - return data, nil -} - -// PublicKeyFactory is a redefinition of the crypto public key factory to -// exclude some functions that are incompatible with the logic of a common -// factory which requires specific serialization. -type PublicKeyFactory interface { - serde.Factory - - // PublicKeyOf returns the public key associated to the data if appropriate, - // otherwise an error. - PublicKeyOf(serde.Context, []byte) (crypto.PublicKey, error) -} - -// PublicKeyFac is a public key factory for commonly known algorithms. -// -// - implements common.PublicKeyFactory -type PublicKeyFac struct { - factories map[string]crypto.PublicKeyFactory -} - -// NewPublicKeyFactory returns a new instance of the common public key factory. -func NewPublicKeyFactory() PublicKeyFac { - factory := PublicKeyFac{ - factories: make(map[string]crypto.PublicKeyFactory), - } - - factory.RegisterAlgorithm(bls.Algorithm, bls.NewPublicKeyFactory()) - - return factory -} - -// RegisterAlgorithm registers the factory for the algorithm. It will override -// an already existing key. -func (f PublicKeyFac) RegisterAlgorithm(algo string, factory crypto.PublicKeyFactory) { - f.factories[algo] = factory -} - -// Deserialize implements serde.Factory. It looks up the format and returns the -// public key of the data if appropriate, otherwise an error. -func (f PublicKeyFac) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := algFormats.Get(ctx.GetFormat()) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode algorithm: %v", err) - } - - alg, ok := m.(Algorithm) - if !ok { - return nil, xerrors.Errorf("invalid message of type '%T'", m) - } - - factory := f.factories[alg.name] - if factory == nil { - return nil, xerrors.Errorf("unknown algorithm '%s'", alg.name) - } - - return factory.PublicKeyOf(ctx, data) -} - -// PublicKeyOf implements crypto.PublicKeyFactory. It returns the public key of -// the data if appropriate, otherwise an error. -func (f PublicKeyFac) PublicKeyOf(ctx serde.Context, data []byte) (crypto.PublicKey, error) { - msg, err := f.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - return msg.(crypto.PublicKey), nil -} - -// SignatureFactory is a factory for commonly known algorithms. -// -// - implements crypto.SignatureFactory -type SignatureFactory struct { - factories map[string]crypto.SignatureFactory -} - -// NewSignatureFactory returns a new instance of the common signature factory. -// It registers the BLS algorithm by default. -func NewSignatureFactory() SignatureFactory { - factory := SignatureFactory{ - factories: make(map[string]crypto.SignatureFactory), - } - - factory.RegisterAlgorithm(bls.Algorithm, bls.NewSignatureFactory()) - - return factory -} - -// RegisterAlgorithm register the factory for the algorithm. -func (f SignatureFactory) RegisterAlgorithm(name string, factory crypto.SignatureFactory) { - f.factories[name] = factory -} - -// Deserialize implements serde.Factory. It returns the signature associated to -// the data if appropriate, otherwise it returns an error. -func (f SignatureFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - format := algFormats.Get(ctx.GetFormat()) - - m, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode algorithm: %v", err) - } - - alg, ok := m.(Algorithm) - if !ok { - return nil, xerrors.Errorf("invalid message of type '%T'", m) - } - - factory := f.factories[alg.name] - if factory == nil { - return nil, xerrors.Errorf("unknown algorithm '%s'", alg.name) - } - - return factory.SignatureOf(ctx, data) -} - -// SignatureOf implements crypto.SignatureFactory. It returns the signature -// associated to the data if appropriate, otherwise it returns an error. -func (f SignatureFactory) SignatureOf(ctx serde.Context, data []byte) (crypto.Signature, error) { - msg, err := f.Deserialize(ctx, data) - if err != nil { - return nil, err - } - - return msg.(crypto.Signature), nil -} diff --git a/dela/crypto/common/common_test.go b/dela/crypto/common/common_test.go deleted file mode 100644 index a7f7577..0000000 --- a/dela/crypto/common/common_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package common - -import ( - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -const testAlgorithm = "fake" - -func init() { - RegisterAlgorithmFormat(fake.GoodFormat, fake.Format{ - Msg: Algorithm{name: testAlgorithm}, - }) - RegisterAlgorithmFormat(serde.Format("BAD_TYPE"), fake.Format{Msg: fake.Message{}}) - RegisterAlgorithmFormat(fake.BadFormat, fake.NewBadFormat()) -} - -func TestAlgorithm_GetName(t *testing.T) { - f := func(name string) bool { - algo := NewAlgorithm(name) - - return name == algo.GetName() - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -func TestAlgorithm_Serialize(t *testing.T) { - algo := NewAlgorithm(testAlgorithm) - - data, err := algo.Serialize(fake.NewContext()) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), data) - - _, err = algo.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode algorithm")) -} - -func TestPublicKeyFactory_RegisterAlgorithm(t *testing.T) { - factory := NewPublicKeyFactory() - - // Check passive registrations. - require.Len(t, factory.factories, 1) - - factory.RegisterAlgorithm(testAlgorithm, fake.PublicKeyFactory{}) - require.Len(t, factory.factories, 2) -} - -func TestPublicKeyFactory_Deserialize(t *testing.T) { - factory := NewPublicKeyFactory() - factory.RegisterAlgorithm(testAlgorithm, fake.PublicKeyFactory{}) - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, fake.PublicKey{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode algorithm")) - - _, err = factory.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid message of type 'fake.Message'") - - factory = NewPublicKeyFactory() - _, err = factory.Deserialize(fake.NewContext(), nil) - require.EqualError(t, err, "unknown algorithm 'fake'") -} - -func TestPublicKeyFactory_PublicKeyOf(t *testing.T) { - factory := NewPublicKeyFactory() - factory.RegisterAlgorithm(testAlgorithm, fake.PublicKeyFactory{}) - - pk, err := factory.PublicKeyOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, fake.PublicKey{}, pk) - - _, err = factory.PublicKeyOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode algorithm")) -} - -func TestSignatureFactory_RegisterAlgorithm(t *testing.T) { - factory := NewSignatureFactory() - - require.Len(t, factory.factories, 1) - - factory.RegisterAlgorithm("fake", fake.SignatureFactory{}) - require.Len(t, factory.factories, 2) -} - -func TestSignatureFactory_Deserialize(t *testing.T) { - factory := NewSignatureFactory() - factory.RegisterAlgorithm(testAlgorithm, fake.SignatureFactory{}) - - msg, err := factory.Deserialize(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, fake.Signature{}, msg) - - _, err = factory.Deserialize(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode algorithm")) - - _, err = factory.Deserialize(fake.NewContextWithFormat(serde.Format("BAD_TYPE")), nil) - require.EqualError(t, err, "invalid message of type 'fake.Message'") - - factory = NewSignatureFactory() - _, err = factory.Deserialize(fake.NewContext(), nil) - require.EqualError(t, err, "unknown algorithm 'fake'") -} - -func TestSignatureFactory_SignatureOf(t *testing.T) { - factory := NewSignatureFactory() - factory.RegisterAlgorithm(testAlgorithm, fake.SignatureFactory{}) - - sig, err := factory.SignatureOf(fake.NewContext(), nil) - require.NoError(t, err) - require.Equal(t, fake.Signature{}, sig) - - _, err = factory.SignatureOf(fake.NewBadContext(), nil) - require.EqualError(t, err, fake.Err("couldn't decode algorithm")) -} diff --git a/dela/crypto/common/example_test.go b/dela/crypto/common/example_test.go deleted file mode 100644 index 1b63ad6..0000000 --- a/dela/crypto/common/example_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package common_test - -import ( - "fmt" - - "go.dedis.ch/dela/crypto/bls" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/crypto/ed25519" - "go.dedis.ch/dela/serde/json" -) - -func ExamplePublicKeyFactory_PublicKeyOf_bls() { - // BLS is already registered by default - factory := common.NewPublicKeyFactory() - - ctx := json.NewContext() - - message := []byte("42") - - signer := bls.NewSigner() - publicKey := signer.GetPublicKey() - - signature, err := signer.Sign(message) - if err != nil { - panic("signing failed: " + err.Error()) - } - - data, err := publicKey.Serialize(ctx) - if err != nil { - panic("serialization failed: " + err.Error()) - } - - // Transmit the data over a physical communication channel... - - result, err := factory.PublicKeyOf(ctx, data) - if err != nil { - panic("factory failed: " + err.Error()) - } - - err = result.Verify(message, signature) - if err != nil { - fmt.Println("public key is invalid") - } else { - fmt.Println("signature is verified") - } - - // Output: signature is verified -} - -func ExamplePublicKeyFactory_PublicKeyOf_ed25519() { - factory := common.NewPublicKeyFactory() - factory.RegisterAlgorithm(ed25519.Algorithm, ed25519.NewPublicKeyFactory()) - - ctx := json.NewContext() - - message := []byte("42") - - signer := ed25519.NewSigner() - publicKey := signer.GetPublicKey() - - signature, err := signer.Sign(message) - if err != nil { - panic("signature failed: " + err.Error()) - } - - data, err := publicKey.Serialize(ctx) - if err != nil { - panic("serialization failed: " + err.Error()) - } - - // Transmit the data over a physical communication channel... - - result, err := factory.PublicKeyOf(ctx, data) - if err != nil { - panic("factory failed: " + err.Error()) - } - - err = result.Verify(message, signature) - if err != nil { - fmt.Println("public key is invalid") - } else { - fmt.Println("signature is verified") - } - - // Output: signature is verified -} diff --git a/dela/crypto/common/json/json.go b/dela/crypto/common/json/json.go deleted file mode 100644 index 2e28243..0000000 --- a/dela/crypto/common/json/json.go +++ /dev/null @@ -1,70 +0,0 @@ -package json - -import ( - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/serde" - "golang.org/x/xerrors" -) - -func init() { - common.RegisterAlgorithmFormat(serde.FormatJSON, algoFormat{}) -} - -// Algorithm is a common JSON message to identify which algorithm is used in a -// message. -type Algorithm struct { - Name string -} - -// PublicKey is the common JSON message for a public key. It contains the -// algorithm and the data to deserialize. -type PublicKey struct { - Algorithm - Data []byte -} - -// Signature is the common JSON message for a signature. It contains the -// algorithm and the data to deserialize. -type Signature struct { - Algorithm - Data []byte -} - -// AlgoFormat is the engine to encode and decode algorithm data in JSON format. -// -// - implements serde.FormatEngine -type algoFormat struct{} - -// Encode implements serde.FormatEngine. It returns the JSON representation of -// an algorithm message. -func (f algoFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - algo, ok := msg.(common.Algorithm) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - m := Algorithm{ - Name: algo.GetName(), - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -// Decode implements serde.FormatEngine. It populates the algorithm message from -// the JSON data if appropriate, otherwise it returns an error. -func (f algoFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := Algorithm{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't deserialize algorithm: %v", err) - } - - alg := common.NewAlgorithm(m.Name) - - return alg, nil -} diff --git a/dela/crypto/common/json/json_test.go b/dela/crypto/common/json/json_test.go deleted file mode 100644 index 46c8de5..0000000 --- a/dela/crypto/common/json/json_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto/common" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" -) - -func TestAlgoFormat_Encode(t *testing.T) { - algo := common.NewAlgorithm("fake") - - format := algoFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - - data, err := format.Encode(ctx, algo) - require.NoError(t, err) - require.Equal(t, `{"Name":"fake"}`, string(data)) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") - - _, err = format.Encode(fake.NewBadContext(), algo) - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestFormat_Decode(t *testing.T) { - format := algoFormat{} - ctx := serde.NewContext(fake.ContextEngine{}) - - algo, err := format.Decode(ctx, []byte(`{"Name": "fake","Data":[]}`)) - require.NoError(t, err) - require.Equal(t, common.NewAlgorithm("fake"), algo) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't deserialize algorithm")) -} diff --git a/dela/crypto/ed25519/ed25519.go b/dela/crypto/ed25519/ed25519.go deleted file mode 100644 index ea56b6c..0000000 --- a/dela/crypto/ed25519/ed25519.go +++ /dev/null @@ -1,330 +0,0 @@ -// Package ed25519 implements the cryptographic primitives for the Edwards 25519 -// elliptic curve. -// -// The signatures are created using the Schnorr algorithm which allows the -// aggregation of multiple signatures and public keys. -// -// Related Papers: -// -// Efficient Identification and Signatures for Smart Cards (1989) -// https://link.springer.com/chapter/10.1007/0-387-34805-0_22 -// -// Documentation Last Review: 05.10.2020 -package ed25519 - -import ( - "bytes" - "fmt" - - "go.dedis.ch/dela/crypto" - "go.dedis.ch/dela/serde" - "go.dedis.ch/dela/serde/registry" - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" - "go.dedis.ch/kyber/v3/suites" - "go.dedis.ch/kyber/v3/util/key" - "golang.org/x/xerrors" -) - -const ( - // Algorithm is the name of the curve used for the schnorr signature. - Algorithm = "CURVE-ED25519" -) - -var ( - suite = suites.MustFind("Ed25519") - - pubkeyFormats = registry.NewSimpleRegistry() - - sigFormats = registry.NewSimpleRegistry() -) - -// RegisterPublicKeyFormat register the engine for the provided format. -func RegisterPublicKeyFormat(format serde.Format, engine serde.FormatEngine) { - pubkeyFormats.Register(format, engine) -} - -// RegisterSignatureFormat register the engine for the provided format. -func RegisterSignatureFormat(format serde.Format, engine serde.FormatEngine) { - sigFormats.Register(format, engine) -} - -// PublicKey is the public key adapter to the Kyber Ed25519 public key. -// -// - implements crypto.PublicKey -type PublicKey struct { - point kyber.Point -} - -// NewPublicKey returns a new public key from the data. -func NewPublicKey(data []byte) (PublicKey, error) { - point := suite.Point() - err := point.UnmarshalBinary(data) - if err != nil { - return PublicKey{}, xerrors.Errorf("couldn't unmarshal point: %v", err) - } - - pk := PublicKey{ - point: point, - } - - return pk, nil -} - -// NewPublicKeyFromPoint creates a new public key from an existing point. -func NewPublicKeyFromPoint(point kyber.Point) PublicKey { - return PublicKey{ - point: point, - } -} - -// MarshalBinary implements encoding.BinaryMarshaler. It produces a slice of -// bytes representing the public key. -func (pk PublicKey) MarshalBinary() ([]byte, error) { - return pk.point.MarshalBinary() -} - -// Serialize implements serde.Message. It returns the serialized data of the -// public key. -func (pk PublicKey) Serialize(ctx serde.Context) ([]byte, error) { - format := pubkeyFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, pk) - if err != nil { - return nil, xerrors.Errorf("couldn't encode public key: %v", err) - } - - return data, nil -} - -// Verify implements crypto.PublicKey. It returns nil if the signature matches -// the message for this public key. -func (pk PublicKey) Verify(msg []byte, sig crypto.Signature) error { - signature, ok := sig.(Signature) - if !ok { - return xerrors.Errorf("invalid signature type '%T'", sig) - } - - err := schnorr.Verify(suite, pk.point, msg, signature.data) - if err != nil { - return xerrors.Errorf("schnorr verify failed: %v", err) - } - - return nil -} - -// Equal implements crypto.PublicKey. It returns true if the other public key -// is the same. -func (pk PublicKey) Equal(other interface{}) bool { - pubkey, ok := other.(PublicKey) - if !ok { - return false - } - - return pubkey.point.Equal(pk.point) -} - -// MarshalText implements encoding.TextMarshaler. It returns a text -// representation of the public key. -func (pk PublicKey) MarshalText() ([]byte, error) { - buffer, err := pk.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return []byte(fmt.Sprintf("schnorr:%x", buffer)), nil -} - -// GetPoint returns the kyber.point. -func (pk PublicKey) GetPoint() kyber.Point { - return pk.point -} - -// String implements fmt.Stringer. It returns a string representation of the -// point. -func (pk PublicKey) String() string { - buffer, err := pk.MarshalText() - if err != nil { - return "schnorr:malformed_point" - } - - // Output only the prefix and 16 characters of the buffer in hexadecimal. - return string(buffer)[:8+16] -} - -// Signature is the adapter of the Kyber Schnorr signature. -// -// - implements crypto.Signature -type Signature struct { - data []byte -} - -// NewSignature returns a new signature from the data. -func NewSignature(data []byte) Signature { - return Signature{ - data: data, - } -} - -// MarshalBinary implements encoding.BinaryMarshaler. It returns a slice of -// bytes representing the signature. -func (sig Signature) MarshalBinary() ([]byte, error) { - return sig.data, nil -} - -// Serialize implements serde.Message. It returns the serialized data of the -// signature. -func (sig Signature) Serialize(ctx serde.Context) ([]byte, error) { - format := sigFormats.Get(ctx.GetFormat()) - - data, err := format.Encode(ctx, sig) - if err != nil { - return nil, xerrors.Errorf("couldn't encode signature: %v", err) - } - - return data, nil -} - -// Equal implements crypto.Signature. It returns true if both signatures are the -// same. -func (sig Signature) Equal(other crypto.Signature) bool { - otherSig, ok := other.(Signature) - if !ok { - return false - } - - return bytes.Equal(sig.data, otherSig.data) -} - -// publicKeyFactory is a factory to deserialize public keys for the Ed25519 -// curve. -// -// - implements crypto.PublicKeyFactory -// - implements serde.Factory -type publicKeyFactory struct{} - -// NewPublicKeyFactory returns a new instance of the factory. -func NewPublicKeyFactory() crypto.PublicKeyFactory { - return publicKeyFactory{} -} - -// Deserialize implements serde.Factory. It returns the public key deserialized -// if appropriate, otherwise an error. -func (f publicKeyFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.PublicKeyOf(ctx, data) -} - -// PublicKeyOf implements crypto.PublicKeyFactory. It returns the public key -// deserialized if appropriate, otherwise an error. -func (f publicKeyFactory) PublicKeyOf(ctx serde.Context, data []byte) (crypto.PublicKey, error) { - format := pubkeyFormats.Get(ctx.GetFormat()) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode public key: %v", err) - } - - pubkey, ok := msg.(PublicKey) - if !ok { - return nil, xerrors.Errorf("invalid public key of type '%T'", msg) - } - - return pubkey, nil -} - -// FromBytes implements crypto.PublicKeyFactory. It returns the public key -// unmarshaled from the bytes. -func (f publicKeyFactory) FromBytes(data []byte) (crypto.PublicKey, error) { - pubkey, err := NewPublicKey(data) - if err != nil { - return nil, xerrors.Errorf("failed to unmarshal the key: %v", err) - } - - return pubkey, nil -} - -// signatureFactory is a factory to deserialize signatures of the Ed25519 -// elliptic curve. -// -// - implements crypto.SignatureFactory -// - implements serde.Factory -type signatureFactory struct{} - -// NewSignatureFactory returns a new instance of the factory. -func NewSignatureFactory() crypto.SignatureFactory { - return signatureFactory{} -} - -// Deserialize implements serde.Factory. It returns the signature associated to -// the data if appropriate, otherwise an error. -func (f signatureFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { - return f.SignatureOf(ctx, data) -} - -// SignatureOf implements crypto.SignatureFactory. It returns the signature -// associated to the data if appropriate, otherwise an error. -func (f signatureFactory) SignatureOf(ctx serde.Context, data []byte) (crypto.Signature, error) { - format := sigFormats.Get(ctx.GetFormat()) - - msg, err := format.Decode(ctx, data) - if err != nil { - return nil, xerrors.Errorf("couldn't decode signature: %v", err) - } - - signature, ok := msg.(Signature) - if !ok { - return nil, xerrors.Errorf("invalid signature of type '%T'", msg) - } - - return signature, nil -} - -// Signer implements a signer that is creating Schnorr signatures using the -// private key of the Ed25519 elliptic curve. -// -// - implements crypto.Signer -type Signer struct { - keyPair *key.Pair -} - -// NewSigner returns a new random schnorr signer. -func NewSigner() crypto.Signer { - kp := key.NewKeyPair(suite) - return Signer{ - keyPair: kp, - } -} - -// GetPublicKeyFactory implements crypto.Signer. It returns the public key -// factory for schnorr signatures. -func (s Signer) GetPublicKeyFactory() crypto.PublicKeyFactory { - return publicKeyFactory{} -} - -// GetSignatureFactory implements crypto.Signer. It returns the signature -// factory for schnorr signatures. -func (s Signer) GetSignatureFactory() crypto.SignatureFactory { - return signatureFactory{} -} - -// GetPublicKey implements crypto.Signer. It returns the public key of the -// signer that can be used to verify signatures. -func (s Signer) GetPublicKey() crypto.PublicKey { - return PublicKey{point: s.keyPair.Public} -} - -// GetPrivateKey returns the signer's private key. -func (s Signer) GetPrivateKey() kyber.Scalar { - return s.keyPair.Private -} - -// Sign implements crypto.Signer. It signs the message in parameter and returns -// the signature, or an error if it cannot sign. -func (s Signer) Sign(msg []byte) (crypto.Signature, error) { - sig, err := schnorr.Sign(suite, s.keyPair.Private, msg) - if err != nil { - return nil, xerrors.Errorf("couldn't make schnorr signature: %v", err) - } - - return Signature{data: sig}, nil -} diff --git a/dela/crypto/ed25519/ed25519_test.go b/dela/crypto/ed25519/ed25519_test.go deleted file mode 100644 index 237f320..0000000 --- a/dela/crypto/ed25519/ed25519_test.go +++ /dev/null @@ -1,320 +0,0 @@ -package ed25519 - -import ( - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" - "go.dedis.ch/kyber/v3/util/key" -) - -func init() { - RegisterPublicKeyFormat(fake.GoodFormat, fake.Format{Msg: PublicKey{}}) - RegisterPublicKeyFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterPublicKeyFormat("BAD_POINT", fake.Format{Msg: fake.Message{}}) - - RegisterSignatureFormat(fake.GoodFormat, fake.Format{Msg: Signature{}}) - RegisterSignatureFormat(fake.BadFormat, fake.NewBadFormat()) - RegisterSignatureFormat("BAD_SIG", fake.Format{Msg: fake.Message{}}) -} - -func TestPublicKey_New(t *testing.T) { - point := suite.Point() - pointBuf, err := point.MarshalBinary() - require.NoError(t, err) - - pubKey, err := NewPublicKey(pointBuf) - require.NoError(t, err) - - require.True(t, pubKey.GetPoint().Equal(point)) - - _, err = NewPublicKey([]byte{}) - require.EqualError(t, err, "couldn't unmarshal point: invalid Ed25519 curve point") -} - -func TestPublicKey_NewFromPoint(t *testing.T) { - point := suite.Point() - pk := NewPublicKeyFromPoint(point) - require.True(t, pk.GetPoint().Equal(point)) -} - -func TestPublicKey_MarshalBinary(t *testing.T) { - point := suite.Point() - pointBuf, err := point.MarshalBinary() - require.NoError(t, err) - - pk := PublicKey{point: point} - pointBuf2, err := pk.MarshalBinary() - require.NoError(t, err) - - require.Equal(t, pointBuf, pointBuf2) -} - -func TestPublicKey_Serialize(t *testing.T) { - pk := PublicKey{} - ctx := fake.NewContext() - - pkBuf, err := pk.Serialize(ctx) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), pkBuf) - - _, err = pk.Serialize(fake.NewBadContext()) - require.EqualError(t, err, fake.Err("couldn't encode public key")) -} - -func TestPublicKey_Verify(t *testing.T) { - privKey := suite.Scalar().Pick(suite.RandomStream()) - pubKey := suite.Point().Mul(privKey, nil) - pk := PublicKey{point: pubKey} - - msg := []byte("hello") - signature, err := schnorr.Sign(suite, privKey, msg) - require.NoError(t, err) - - err = pk.Verify(msg, Signature{data: signature}) - require.NoError(t, err) - - err = pk.Verify(msg, fake.NewBadSignature()) - require.EqualError(t, err, "invalid signature type 'fake.Signature'") - - err = pk.Verify(msg, Signature{data: []byte{}}) - // the second error part depends on kyber implementation - require.Regexp(t, "^schnorr verify failed: ", err) -} - -func TestPublicKey_Equal(t *testing.T) { - point := suite.Point() - pk := PublicKey{point: point} - pk2 := PublicKey{point: point} - - require.True(t, pk.Equal(pk2)) - require.False(t, pk.Equal(fake.NewBadPublicKey())) - - point2 := suite.Point().Pick(suite.RandomStream()) - pk2 = PublicKey{point: point2} - - require.False(t, pk.Equal(pk2)) -} - -func TestPublicKey_MarshalText(t *testing.T) { - point := suite.Point() - pk := PublicKey{point: point} - - res, err := pk.MarshalText() - require.NoError(t, err) - require.Regexp(t, "^schnorr:", string(res)) - - pk.point = badPoint{} - _, err = pk.MarshalText() - require.EqualError(t, err, fake.Err("couldn't marshal")) -} - -func TestPublicKey_GetPoint(t *testing.T) { - point := suite.Point() - pk := PublicKey{point: point} - - require.True(t, point.Equal(pk.GetPoint())) -} - -func TestPublicKey_String(t *testing.T) { - point := suite.Point() - pk := PublicKey{point: point} - - res := pk.String() - require.Regexp(t, "^schnorr:[a-f0-9]{16}$", res) - - pk.point = badPoint{} - res = pk.String() - require.Equal(t, "schnorr:malformed_point", res) -} - -func TestSignature_New(t *testing.T) { - data := []byte("hello") - sig := NewSignature(data) - require.Equal(t, data, sig.data) -} - -func TestSignature_MarshalBinary(t *testing.T) { - data := []byte("hello") - sig := NewSignature(data) - - buf, err := sig.MarshalBinary() - require.NoError(t, err) - require.Equal(t, data, buf) -} - -func TestSignature_Serialize(t *testing.T) { - data := []byte("hello") - sig := NewSignature(data) - - ctx := fake.NewContext() - buf, err := sig.Serialize(ctx) - require.NoError(t, err) - require.Equal(t, fake.GetFakeFormatValue(), buf) - - ctx = fake.NewBadContext() - _, err = sig.Serialize(ctx) - require.EqualError(t, err, fake.Err("couldn't encode signature")) -} - -func TestSignature_Equal(t *testing.T) { - data := []byte("hello") - sig := NewSignature(data) - sig2 := NewSignature(data) - - require.True(t, sig.Equal(sig2)) - require.False(t, sig.Equal(fake.NewBadSignature())) - - data2 := []byte("world") - sig2 = NewSignature(data2) - - require.False(t, sig.Equal(sig2)) -} - -func TestPublicKeyFactory_New(t *testing.T) { - pkf := NewPublicKeyFactory() - require.IsType(t, publicKeyFactory{}, pkf) -} - -func TestPublicKeyFactory_Deserialize(t *testing.T) { - pkf := NewPublicKeyFactory() - ctx := fake.NewContext() - - msg, err := pkf.Deserialize(ctx, nil) - require.NoError(t, err) - require.IsType(t, PublicKey{}, msg) -} - -func TestPublicKeyFactory_PublicKeyOf(t *testing.T) { - pkf := NewPublicKeyFactory() - ctx := fake.NewContext() - - msg, err := pkf.PublicKeyOf(ctx, nil) - require.NoError(t, err) - require.IsType(t, PublicKey{}, msg) - - ctx = fake.NewBadContext() - _, err = pkf.PublicKeyOf(ctx, nil) - require.EqualError(t, err, fake.Err("couldn't decode public key")) - - ctx = fake.NewContextWithFormat("BAD_POINT") - _, err = pkf.PublicKeyOf(ctx, nil) - require.EqualError(t, err, "invalid public key of type 'fake.Message'") -} - -func TestPublicKeyFactory_FromBytes(t *testing.T) { - fac := NewPublicKeyFactory() - - point := suite.Point() - data, err := point.MarshalBinary() - require.NoError(t, err) - - pk, err := fac.FromBytes(data) - require.NoError(t, err) - require.True(t, pk.(PublicKey).point.Equal(point)) - - _, err = fac.FromBytes(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to unmarshal the key: ") -} - -func TestSignatureFactory_New(t *testing.T) { - sf := NewSignatureFactory() - require.IsType(t, signatureFactory{}, sf) -} - -func TestSignatureFactory_Deserialize(t *testing.T) { - sf := NewSignatureFactory() - ctx := fake.NewContext() - - msg, err := sf.Deserialize(ctx, nil) - require.NoError(t, err) - require.IsType(t, Signature{}, msg) -} - -func TestSignatureFactory_SignatureOf(t *testing.T) { - sf := NewSignatureFactory() - ctx := fake.NewContext() - - msg, err := sf.SignatureOf(ctx, nil) - require.NoError(t, err) - require.IsType(t, Signature{}, msg) - - ctx = fake.NewBadContext() - _, err = sf.SignatureOf(ctx, nil) - require.EqualError(t, err, fake.Err("couldn't decode signature")) - - ctx = fake.NewContextWithFormat("BAD_SIG") - _, err = sf.SignatureOf(ctx, nil) - require.EqualError(t, err, "invalid signature of type 'fake.Message'") -} - -func TestSigner_New(t *testing.T) { - signer := NewSigner() - require.IsType(t, Signer{}, signer) -} - -func TestSigner_GetPublicKeyFactory(t *testing.T) { - signer := NewSigner() - factory := signer.GetPublicKeyFactory() - require.IsType(t, publicKeyFactory{}, factory) -} - -func TestSigner_GetSignatureFactory(t *testing.T) { - signer := NewSigner() - factory := signer.GetSignatureFactory() - require.IsType(t, signatureFactory{}, factory) -} - -func TestSigner_GetPublicKey(t *testing.T) { - kp := key.NewKeyPair(suite) - signer := Signer{keyPair: kp} - - pk := NewPublicKeyFromPoint(kp.Public) - - pk2 := signer.GetPublicKey() - require.True(t, pk.Equal(pk2)) -} - -func TestSigner_GetPrivateKey(t *testing.T) { - kp := key.NewKeyPair(suite) - signer := Signer{keyPair: kp} - - secret := signer.GetPrivateKey() - require.True(t, secret.Equal(kp.Private)) -} - -func TestSigner_Sign(t *testing.T) { - kp := key.NewKeyPair(suite) - signer := Signer{keyPair: kp} - - f := func(msg []byte) bool { - signature, err := signer.Sign(msg) - require.NoError(t, err) - - signData, err := signature.MarshalBinary() - require.NoError(t, err) - - err = schnorr.Verify(suite, kp.Public, msg, signData) - require.NoError(t, err) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badPoint struct { - kyber.Point -} - -func (p badPoint) MarshalBinary() ([]byte, error) { - return nil, fake.GetError() -} diff --git a/dela/crypto/ed25519/example_test.go b/dela/crypto/ed25519/example_test.go deleted file mode 100644 index 5289bf9..0000000 --- a/dela/crypto/ed25519/example_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package ed25519 - -import "fmt" - -func ExampleSigner_Sign() { - signerA := NewSigner() - - message := []byte("42") - - signature, err := signerA.Sign(message) - if err != nil { - panic("signer failed: " + err.Error()) - } - - err = signerA.GetPublicKey().Verify(message, signature) - if err != nil { - panic("invalid signature: " + err.Error()) - } - - fmt.Println("Success") - - // Output: Success -} diff --git a/dela/crypto/ed25519/json/json.go b/dela/crypto/ed25519/json/json.go deleted file mode 100644 index 999e3fe..0000000 --- a/dela/crypto/ed25519/json/json.go +++ /dev/null @@ -1,90 +0,0 @@ -package json - -import ( - "go.dedis.ch/dela/crypto/common/json" - "go.dedis.ch/dela/crypto/ed25519" - "golang.org/x/xerrors" - - "go.dedis.ch/dela/serde" -) - -func init() { - ed25519.RegisterPublicKeyFormat(serde.FormatJSON, pubkeyFormat{}) - ed25519.RegisterSignatureFormat(serde.FormatJSON, sigFormat{}) -} - -type pubkeyFormat struct{} - -func (f pubkeyFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - pubkey, ok := msg.(ed25519.PublicKey) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - buffer, err := pubkey.MarshalBinary() - if err != nil { - return nil, xerrors.Errorf("couldn't marshal point: %v", err) - } - - m := json.PublicKey{ - Algorithm: json.Algorithm{Name: ed25519.Algorithm}, - Data: buffer, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -func (f pubkeyFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := json.PublicKey{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal public key: %v", err) - } - - pubkey, err := ed25519.NewPublicKey(m.Data) - if err != nil { - return nil, xerrors.Errorf("couldn't create public key: %v", err) - } - - return pubkey, nil -} - -type sigFormat struct{} - -func (f sigFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { - signature, ok := msg.(ed25519.Signature) - if !ok { - return nil, xerrors.Errorf("unsupported message of type '%T'", msg) - } - - data, _ := signature.MarshalBinary() - - m := json.Signature{ - Algorithm: json.Algorithm{Name: ed25519.Algorithm}, - Data: data, - } - - data, err := ctx.Marshal(m) - if err != nil { - return nil, xerrors.Errorf("couldn't marshal: %v", err) - } - - return data, nil -} - -func (f sigFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { - m := json.Signature{} - err := ctx.Unmarshal(data, &m) - if err != nil { - return nil, xerrors.Errorf("couldn't unmarshal signature: %v", err) - } - - signature := ed25519.NewSignature(m.Data) - - return signature, nil -} diff --git a/dela/crypto/ed25519/json/json_test.go b/dela/crypto/ed25519/json/json_test.go deleted file mode 100644 index 25324df..0000000 --- a/dela/crypto/ed25519/json/json_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package json - -import ( - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/crypto/ed25519" - "go.dedis.ch/dela/internal/testing/fake" - "go.dedis.ch/dela/serde" - "go.dedis.ch/kyber/v3" -) - -func TestPubKkeyFormat_Encode(t *testing.T) { - format := pubkeyFormat{} - signer := ed25519.NewSigner() - - msg := signer.GetPublicKey() - - ctx := serde.NewContext(fake.ContextEngine{}) - - data, err := format.Encode(ctx, msg) - require.NoError(t, err) - require.Regexp(t, `{"Name":"CURVE-ED25519","Data":"[^"]+"}`, string(data)) - - _, err = format.Encode(fake.NewBadContext(), msg) - require.EqualError(t, err, fake.Err("couldn't marshal")) - - _, err = format.Encode(fake.NewBadContext(), ed25519.NewPublicKeyFromPoint(badPoint{})) - require.EqualError(t, err, fake.Err("couldn't marshal point")) - - _, err = format.Encode(fake.NewBadContext(), fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") -} - -func TestPubKkeyFormat_Decode(t *testing.T) { - format := pubkeyFormat{} - signer := ed25519.NewSigner() - - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - data, err := signer.GetPublicKey().Serialize(ctx) - require.NoError(t, err) - - pubkey, err := format.Decode(ctx, data) - require.NoError(t, err) - require.True(t, signer.GetPublicKey().Equal(pubkey.(ed25519.PublicKey))) - - _, err = format.Decode(ctx, []byte(`{"Data":[]}`)) - require.EqualError(t, err, "couldn't create public key: couldn't unmarshal point: invalid Ed25519 curve point") - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't unmarshal public key")) -} - -func TestSigFormat_Encode(t *testing.T) { - format := sigFormat{} - ctx := fake.NewContext() - - signer := ed25519.NewSigner() - sign, err := signer.Sign([]byte("hello")) - require.NoError(t, err) - - data, err := format.Encode(ctx, sign) - require.NoError(t, err) - require.Regexp(t, `{"Name":"CURVE-ED25519","Data":"[^"]+"}`, string(data)) - - _, err = format.Encode(fake.NewBadContext(), sign) - require.EqualError(t, err, fake.Err("couldn't marshal")) - - _, err = format.Encode(ctx, fake.Message{}) - require.EqualError(t, err, "unsupported message of type 'fake.Message'") -} - -func TestSigFormat_Decode(t *testing.T) { - format := sigFormat{} - ctx := fake.NewContextWithFormat(serde.FormatJSON) - - signer := ed25519.NewSigner() - signatue, err := signer.Sign([]byte("hello")) - require.NoError(t, err) - - data, err := signatue.Serialize(ctx) - require.NoError(t, err) - - msg, err := format.Decode(ctx, data) - require.NoError(t, err) - require.True(t, signatue.Equal(msg.(ed25519.Signature))) - - _, err = format.Decode(fake.NewBadContext(), []byte(`{}`)) - require.EqualError(t, err, fake.Err("couldn't unmarshal signature")) -} - -// ----------------------------------------------------------------------------- -// Utility functions - -type badPoint struct { - kyber.Point -} - -func (p badPoint) MarshalBinary() ([]byte, error) { - return nil, fake.GetError() -} diff --git a/dela/crypto/hash.go b/dela/crypto/hash.go deleted file mode 100644 index 9d8940e..0000000 --- a/dela/crypto/hash.go +++ /dev/null @@ -1,25 +0,0 @@ -// -// Documentation Last Review: 05.10.2020 -// - -package crypto - -import ( - "crypto/sha256" - "hash" -) - -// Sha256Factory is a hash factory that is using SHA256. -// -// - implements crypto.HashFactory -type Sha256Factory struct{} - -// NewSha256Factory returns a new instance of the factory. -func NewSha256Factory() Sha256Factory { - return Sha256Factory{} -} - -// New implements crypto.HashFactory. It returns a new SHA256 instance. -func (f Sha256Factory) New() hash.Hash { - return sha256.New() -} diff --git a/dela/crypto/hash_test.go b/dela/crypto/hash_test.go deleted file mode 100644 index bf17ea1..0000000 --- a/dela/crypto/hash_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package crypto - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSha256Factory_New(t *testing.T) { - factory := NewSha256Factory() - require.NotNil(t, factory.New()) -} diff --git a/dela/crypto/loader/example_test.go b/dela/crypto/loader/example_test.go deleted file mode 100644 index a50f405..0000000 --- a/dela/crypto/loader/example_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package loader - -import ( - "fmt" - "os" - "path/filepath" -) - -func ExampleLoader_LoadOrCreate() { - dir, err := os.MkdirTemp(os.TempDir(), "example") - if err != nil { - panic("no folder: " + err.Error()) - } - - defer os.RemoveAll(dir) - - loader := NewFileLoader(filepath.Join(dir, "private.key")) - - data, err := loader.LoadOrCreate(exampleGenerator{}) - if err != nil { - panic("loading key failed: " + err.Error()) - } - - fmt.Println(string(data)) - - // Output: a marshaled private key -} - -// Example of dummy generator which generates a key and returns its -// serialization. -// -// - implements loader.Generator -type exampleGenerator struct{} - -// Generate implements loader.Generator. It returns a dummy byte slice for the -// example. -func (exampleGenerator) Generate() ([]byte, error) { - return []byte("a marshaled private key"), nil -} diff --git a/dela/crypto/loader/file.go b/dela/crypto/loader/file.go deleted file mode 100644 index 8bc26b8..0000000 --- a/dela/crypto/loader/file.go +++ /dev/null @@ -1,85 +0,0 @@ -// -// Documentation Last Review: 06.10.2020 -// - -package loader - -import ( - "io" - "os" - - "golang.org/x/xerrors" -) - -// FileLoader is loader that is storing the new keys to a file. -// -// - implements loader.Loader -type fileLoader struct { - path string - - openFn func(path string) (*os.File, error) - openFileFn func(path string, flags int, perms os.FileMode) (*os.File, error) - statFn func(path string) (os.FileInfo, error) -} - -// NewFileLoader creates a new loader that is using the file given in parameter. -func NewFileLoader(path string) Loader { - return fileLoader{ - path: path, - openFn: os.Open, - openFileFn: os.OpenFile, - statFn: os.Stat, - } -} - -// LoadOrCreate implements loader.Loader. It either loads the key from the file -// if it exists, or it generates a new one and stores it in the file. The file -// created has minimal read permission for the current user (0400). -func (l fileLoader) LoadOrCreate(g Generator) ([]byte, error) { - _, err := l.statFn(l.path) - if os.IsNotExist(err) { - data, err := g.Generate() - if err != nil { - return nil, xerrors.Errorf("generator failed: %v", err) - } - - file, err := l.openFileFn(l.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0400) - if err != nil { - return nil, xerrors.Errorf("while creating file: %v", err) - } - - defer file.Close() - - _, err = file.Write(data) - if err != nil { - return nil, xerrors.Errorf("while writing: %v", err) - } - - return data, nil - } - - data, err := l.Load() - if err != nil { - return nil, xerrors.Errorf("failed to load file: %v", err) - } - - return data, nil -} - -// Load implements loader.Loader. It loads the key from the file if it exists, -// otherwise it returns an error. -func (l fileLoader) Load() ([]byte, error) { - file, err := l.openFn(l.path) - if err != nil { - return nil, xerrors.Errorf("while opening file: %v", err) - } - - defer file.Close() - - data, err := io.ReadAll(file) - if err != nil { - return nil, xerrors.Errorf("while reading file: %v", err) - } - - return data, nil -} diff --git a/dela/crypto/loader/file_test.go b/dela/crypto/loader/file_test.go deleted file mode 100644 index 9e0c5dc..0000000 --- a/dela/crypto/loader/file_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package loader - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - "go.dedis.ch/dela/internal/testing/fake" -) - -func TestFileLoader_LoadOrCreate(t *testing.T) { - file, err := os.CreateTemp(os.TempDir(), "dela") - require.NoError(t, err) - - file.Close() - - defer os.Remove(file.Name()) - require.NoError(t, os.Remove(file.Name())) - - generator := fakeGenerator{ - calls: fake.NewCall(), - } - - loader := NewFileLoader(file.Name()).(fileLoader) - - // Generate.. - data, err := loader.LoadOrCreate(generator) - require.NoError(t, err) - require.Equal(t, []byte{1, 2, 3}, data) - require.Equal(t, 1, generator.calls.Len()) - - // Read from the file.. - data, err = loader.LoadOrCreate(generator) - require.NoError(t, err) - require.Equal(t, []byte{1, 2, 3}, data) - require.Equal(t, 1, generator.calls.Len()) -} - -func TestFileLoader_BadGenerator_LoadOrCreate(t *testing.T) { - loader := fileLoader{ - path: "", - statFn: statNotExists, - } - - _, err := loader.LoadOrCreate(fakeGenerator{err: fake.GetError()}) - require.EqualError(t, err, fake.Err("generator failed")) -} - -func TestFileLoader_FailCreateFile_LoadOrCreate(t *testing.T) { - loader := fileLoader{ - path: "", - statFn: statNotExists, - openFileFn: func(path string, flags int, perms os.FileMode) (*os.File, error) { - return nil, fake.GetError() - }, - } - - _, err := loader.LoadOrCreate(fakeGenerator{}) - require.EqualError(t, err, fake.Err("while creating file")) -} - -func TestFileLoader_FailWriteFile_LoadOrCreate(t *testing.T) { - loader := fileLoader{ - path: "", - statFn: statNotExists, - openFileFn: func(path string, flags int, perms os.FileMode) (*os.File, error) { - return os.NewFile(0, ""), nil - }, - } - - _, err := loader.LoadOrCreate(fakeGenerator{}) - require.Error(t, err) - require.Contains(t, err.Error(), "while writing: write : ") -} - -func TestFileLoader_FailOpenFile_LoadOrCreate(t *testing.T) { - loader := fileLoader{ - path: "", - statFn: statExists, - openFn: func(path string) (*os.File, error) { - return nil, fake.GetError() - }, - } - - _, err := loader.LoadOrCreate(fakeGenerator{}) - require.EqualError(t, err, fake.Err("failed to load file: while opening file")) -} - -func TestFileLoader_FailReadFile_LoadOrCreate(t *testing.T) { - loader := fileLoader{ - path: "", - statFn: statExists, - openFn: func(path string) (*os.File, error) { - return os.Open(os.TempDir()) - }, - } - - _, err := loader.LoadOrCreate(fakeGenerator{}) - require.Error(t, err) - require.Contains(t, err.Error(), "while reading file: ") -} - -// ----------------------------------------------------------------------------- -// Utility functions - -func statNotExists(path string) (os.FileInfo, error) { - return nil, os.ErrNotExist -} - -func statExists(path string) (os.FileInfo, error) { - return nil, nil -} - -type fakeGenerator struct { - calls *fake.Call - err error -} - -func (g fakeGenerator) Generate() ([]byte, error) { - g.calls.Add("Generate") - - return []byte{1, 2, 3}, g.err -} diff --git a/dela/crypto/loader/mod.go b/dela/crypto/loader/mod.go deleted file mode 100644 index 93f93a6..0000000 --- a/dela/crypto/loader/mod.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package loader defines an abstraction to store a private, or a public, key in -// a storage. -// -// When the key does not exist, it will generate a new one using a generator -// implemented by the caller and stores it for the next time. -// -// Documentation Last Review: 06.10.2020 -// -package loader - -// Generator is the interface to implement to generate a key. -type Generator interface { - Generate() ([]byte, error) -} - -// Loader is an abstraction to load a key from a storage. It allows for instance -// to load a private key from the disk, or generate it if it doesn't exist. -type Loader interface { - // LoadOrCreate tries to load the key and returns it if found, otherwise it - // generates a new one using the generator and stores it. - LoadOrCreate(Generator) ([]byte, error) - - // Load loads the key and returns an error if it doesn't find it. - Load() ([]byte, error) -} diff --git a/dela/crypto/mod.go b/dela/crypto/mod.go deleted file mode 100644 index ee31839..0000000 --- a/dela/crypto/mod.go +++ /dev/null @@ -1,161 +0,0 @@ -// Package crypto defines cryptographic primitives shared by the different -// modules of Dela. -// -// It defines the abstraction of a public and a private key and their -// combination where a private key can create a signature for a given message, -// while the public key can verify the association of those two elements. -// -// A signer is a unique entity that possesses both a public and an associated -// private key. It is assumed that the combination is a unique identity. This -// abstraction hides the logic of a private key. -// -// For the aggregation of those primitives, a verifier abstraction is defined to -// provide the primitives to verify using the aggregate instead of a single one. -// -// Documentation Last Review: 05.10.2020 -// -package crypto - -import ( - "encoding" - "hash" - - "go.dedis.ch/dela/mino" - "go.dedis.ch/dela/serde" -) - -// HashFactory is an interface to produce a hash digest. -type HashFactory interface { - New() hash.Hash -} - -// RandGenerator is an interface to generate random values with a fully seeded -// random source. -type RandGenerator interface { - // Read populates the buffer with random bytes up to a size that is - // returned alongside an error if something goes wrong. - Read([]byte) (int, error) -} - -// PublicKey is a public identity that can be used to verify a signature. -type PublicKey interface { - encoding.BinaryMarshaler - encoding.TextMarshaler - serde.Message - - // Verify returns nil if the signature matches the message, otherwise an - // error is returned. - Verify(msg []byte, signature Signature) error - - // Equal returns true when both objects are similar. - Equal(other interface{}) bool -} - -// PublicKeyFactory is a factory to decode public keys. -type PublicKeyFactory interface { - serde.Factory - - // PublicKeyOf populates the public key associated to the data if - // appropriate, otherwise it returns an error. - PublicKeyOf(ctx serde.Context, data []byte) (PublicKey, error) - - // FromBytes returns the public key associated to the data if appropriate, - // otherwise it returns an error. - FromBytes(data []byte) (PublicKey, error) -} - -// PublicKeyIterator is an iterator over the list of public keys of a -// collective authority. -type PublicKeyIterator interface { - // Seek moves the iterator to a specific index. - Seek(int) - - // HasNext returns true if a public key is available, false if the iterator - // is exhausted. - HasNext() bool - - // GetNext returns the next public key in case HasNext returns true, - // otherwise no assumption can be done. - GetNext() PublicKey -} - -// Signature is a verifiable element for a unique message. -type Signature interface { - encoding.BinaryMarshaler - serde.Message - - // Equal returns true if both objects are similar. - Equal(other Signature) bool -} - -// SignatureFactory is a factory to decode signatures. -type SignatureFactory interface { - serde.Factory - - // SignatureOf returns a signature associated with the data if appropriate, - // otherwise it returns an error. - SignatureOf(ctx serde.Context, data []byte) (Signature, error) -} - -// Verifier provides the primitive to verify a signature w.r.t. a message. -type Verifier interface { - // Verify returns nil if the signature matches the message for the public - // key internal to the verifier. - Verify(msg []byte, signature Signature) error -} - -// VerifierFactory provides the primitives to create a verifier. -type VerifierFactory interface { - // FromAuthority returns a verifier that will use the authority to generate - // a public key. - FromAuthority(ca CollectiveAuthority) (Verifier, error) - - // FromArray returns a verifier that will use the list of public keys to - // generate one. - FromArray(keys []PublicKey) (Verifier, error) -} - -// Signer provides the primitives to sign and verify signatures. -type Signer interface { - // GetPublicKeyFactory returns a factory that can deserialize public keys of - // the same type as the signer. - GetPublicKeyFactory() PublicKeyFactory - - // GetSignatureFactory returns a factory that can deserialize signatures of - // the same type as the signer. - GetSignatureFactory() SignatureFactory - - // GetPublicKey returns the public key of the signer. - GetPublicKey() PublicKey - - // Sign returns a signature that will match the message for the signer - // public key. - Sign(msg []byte) (Signature, error) -} - -// AggregateSigner offers the same primitives as the Signer interface but -// also includes a primitive to aggregate signatures into one. -type AggregateSigner interface { - Signer - - // GetVerifierFactory returns the factory that can deserialize verifiers of - // the same type as the signer. - GetVerifierFactory() VerifierFactory - - // Aggregate returns the aggregate signature of the ones in parameter. - Aggregate(signatures ...Signature) (Signature, error) -} - -// CollectiveAuthority is a set of participants with each of them being -// associated to a Mino address and a public key. -type CollectiveAuthority interface { - mino.Players - - // GetPublicKey returns the public key and its index of the corresponding - // address if any matches. An index < 0 means no correspondance found. - GetPublicKey(addr mino.Address) (PublicKey, int) - - // PublicKeyIterator creates a public key iterator that iterates over the - // list of public keys and is consistent with the address iterator. - PublicKeyIterator() PublicKeyIterator -} diff --git a/dela/crypto/rand.go b/dela/crypto/rand.go deleted file mode 100644 index 7493b80..0000000 --- a/dela/crypto/rand.go +++ /dev/null @@ -1,18 +0,0 @@ -// -// Documentation Last Review: 05.10.2020 -// - -package crypto - -import "crypto/rand" - -// CryptographicRandomGenerator is cryptographically secure random generator. -// -// - implements crypto.RandGenerator -type CryptographicRandomGenerator struct{} - -// Read implements crypto.RandGenerator. It fills the given buffer at its -// capacity as long as no error occurred. -func (crg CryptographicRandomGenerator) Read(buffer []byte) (int, error) { - return rand.Read(buffer) -} diff --git a/dela/crypto/rand_test.go b/dela/crypto/rand_test.go deleted file mode 100644 index 596ceac..0000000 --- a/dela/crypto/rand_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package crypto - -import ( - "testing" - "testing/quick" - - "github.com/stretchr/testify/require" -) - -func TestCryptographicRandomGenerator_Read(t *testing.T) { - rand := CryptographicRandomGenerator{} - - f := func(buffer []byte) bool { - n, err := rand.Read(buffer) - require.NoError(t, err) - require.Equal(t, len(buffer), n) - - return true - } - - err := quick.Check(f, nil) - require.NoError(t, err) -} diff --git a/dela/demo.sh b/dela/demo.sh deleted file mode 100755 index 2bfc1f4..0000000 --- a/dela/demo.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash - -set -e - -export TEMPDIR=$(mktemp -d /tmp/dkgcli.XXXXXXXXXXXXX) -rm_tempdir () { - rm -rf "$TEMPDIR" -} -trap rm_tempdir EXIT - -n=16 -t=9 - -for i in $(seq $n); do - tmux new-window -d "LLVL=info dkgcli --config $TEMPDIR/node$i start --listen tcp://127.0.0.1:$((2000+i)); read" -done - -sleep 3 - -# Exchange certificates -for i in $(seq 2 $n); do - dkgcli --config $TEMPDIR/node$i minogrpc join --address //127.0.0.1:2001 $(dkgcli --config $TEMPDIR/node1 minogrpc token) -done - -# Initialize DKG on each node. Do that in a 4th session. -for i in $(seq $n); do - dkgcli --config $TEMPDIR/node$i dkg listen -done - -# Do the setup in one of the node: -cmd=(dkgcli --config $TEMPDIR/node1 dkg setup --threshold $t) -for i in $(seq $n); do - cmd+=(--authority $(cat $TEMPDIR/node$i/dkgauthority)) -done -"${cmd[@]}" - -verbs=(buy sell) -animals=(penguin lion monkey cat dog kangaroo) -formats=(jpeg png gif) -coins=(poptokens byzcoins efranks) - -function pick { - shift $((RANDOM%$#)) - echo "$1" -} - - -echo ✅ generated committee pubkey: $(dkgcli --config $TEMPDIR/node1 dkg get-public-key) - -for ((i = 0; i < 12; i++)); do - sleep 1 - label=$(printf "%02x" $i) - echo ⌚ block $i decryption key: $(dkgcli --config $TEMPDIR/node1 dkg sign -message $label) - case $i in - ( 5 ) - msg="$(pick ${verbs[@]}) a $(pick ${animals[@]}) $(pick ${formats[@]}) in exchange for $(pick ${coins[@]})" - ct=$(dkgcli --config $TEMPDIR/node1 dkg encrypt -label 0a -message $(xxd -p -c0 <<<$msg)) - echo 🔐 encrypted message for block 10: $ct - ;; - ( 10 ) - echo 🔓 decrypted message: $(dkgcli --config $TEMPDIR/node1 dkg decrypt -label $label -ciphertext $ct) - echo 🔓 decrypted message: $(dkgcli --config $TEMPDIR/node1 dkg decrypt -label $label -ciphertext $ct | xxd -r -p) - ;; - esac -done - -echo ⚠️ NOT FINANCIAL ADVICE⚠️ - -read diff --git a/dela/dkg/ibe/ibe.go b/dela/dkg/ibe/ibe.go deleted file mode 100644 index 29887f1..0000000 --- a/dela/dkg/ibe/ibe.go +++ /dev/null @@ -1,113 +0,0 @@ -// This code is (c) by DEDIS/EPFL 2017 under the MPL v2 or later version. - -package ibe - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/sha512" - "errors" - "fmt" - - kyber "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/pairing" - "go.dedis.ch/kyber/v3/util/random" - - "golang.org/x/crypto/hkdf" -) - -// Based on https://github.com/drand/kyber/blob/master/encrypt/ibe/ibe.go -// with G1 and G2 flipped and AES-CTR instead of Blake2s - -type CiphertextCPA struct { - U kyber.Point - V []byte -} - -const pointMarshalledSize = 128 - -func (ct *CiphertextCPA) Serialize(suite pairing.Suite) ([]byte, error) { - marshalledU, err := ct.U.MarshalBinary() - if err != nil { - return nil, err - } - if len(marshalledU) != pointMarshalledSize { - return nil, fmt.Errorf("unexpected point marshalled size: %v", len(marshalledU)) - } - buf := make([]byte, 0, pointMarshalledSize+len(ct.V)) - buf = append(buf, marshalledU...) - buf = append(buf, ct.V...) - return buf, nil -} -func (ct *CiphertextCPA) Deserialize(suite pairing.Suite, data []byte) error { - marshalledU := data[:pointMarshalledSize] - U := suite.G2().Point() - U.UnmarshalBinary(marshalledU) - ct.U = U - ct.V = bytes.Clone(data[pointMarshalledSize:]) - return nil -} - -type hashablePoint interface { - Hash([]byte) kyber.Point -} - -func gtToStream(GidT kyber.Point) (cipher.Stream, error) { - seed, err := GidT.MarshalBinary() - if err != nil { - return nil, err - } - key := make([]byte, 32) - kdf := hkdf.New(sha512.New, seed, nil, nil) - kdf.Read(key) - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - iv := make([]byte, 16) //NOTE: this is allowed because the key is used only once - return cipher.NewCTR(block, iv), nil -} - -func DeriveEncryptionKeyOnG2(suite pairing.Suite, X kyber.Point, label []byte) (kyber.Point, error) { - hashable, ok := suite.G1().Point().(hashablePoint) - if !ok { - return nil, errors.New("point needs to implement hashablePoint") - } - P := suite.Pair(hashable.Hash(label), X) - return P, nil -} - -func EncryptCPAonG2(suite pairing.Suite, P kyber.Point, msg []byte) (*CiphertextCPA, error) { - r := suite.G2().Scalar().Pick(random.New()) - U := suite.G2().Point().Mul(r, suite.G2().Point().Base()) - xof, err := gtToStream(suite.GT().Point().Mul(r, P)) - if err != nil { - return nil, err - } - V := make([]byte, len(msg)) - xof.XORKeyStream(V, msg) - - return &CiphertextCPA{U, V}, nil -} - -func DecryptCPAonG2(suite pairing.Suite, decKey kyber.Point, ct *CiphertextCPA) ([]byte, error) { - xof, err := gtToStream(suite.Pair(decKey, ct.U)) - if err != nil { - return nil, err - } - msg := make([]byte, len(ct.V)) - xof.XORKeyStream(msg, ct.V) - return msg, nil -} - -func VerifyIdentityOnG2(suite pairing.Suite, pk, identity kyber.Point, label []byte) (bool, error) { - hashable, ok := suite.G1().Point().(hashablePoint) - if !ok { - return false, errors.New("point needs to implement hashablePoint") - } - - lhs := suite.Pair(identity, suite.G2().Point().Base()) - rhs := suite.Pair(hashable.Hash(label), pk) - return lhs.Equal(rhs), nil -} diff --git a/dela/dkg/pedersen_bn256/dkgcli/README.md b/dela/dkg/pedersen_bn256/dkgcli/README.md deleted file mode 100644 index c7268d4..0000000 --- a/dela/dkg/pedersen_bn256/dkgcli/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# DKGCLI - -DKGCLI is a CLI tool for using the DKG protocol. Here is a complete scenario: - -```sh -# Install the CLI -go install . - -# Run 3 nodes. Do that in 3 different sessions -LLVL=info dkgcli --config /tmp/node1 start --routing tree --listen tcp://127.0.0.1:2001 -LLVL=info dkgcli --config /tmp/node2 start --routing tree --listen tcp://127.0.0.1:2002 -LLVL=info dkgcli --config /tmp/node3 start --routing tree --listen tcp://127.0.0.1:2003 - -# Exchange certificates -dkgcli --config /tmp/node2 minogrpc join --address //127.0.0.1:2001 $(dkgcli --config /tmp/node1 minogrpc token) -dkgcli --config /tmp/node3 minogrpc join --address //127.0.0.1:2001 $(dkgcli --config /tmp/node1 minogrpc token) - -# Initialize DKG on each node. Do that in a 4th session. -dkgcli --config /tmp/node1 dkg listen -dkgcli --config /tmp/node2 dkg listen -dkgcli --config /tmp/node3 dkg listen - -# Do the setup in one of the node: -dkgcli --config /tmp/node1 dkg setup \ - --authority $(cat /tmp/node1/dkgauthority) \ - --authority $(cat /tmp/node2/dkgauthority) \ - --authority $(cat /tmp/node3/dkgauthority) - -# Encrypt a message: -dkgcli --config /tmp/node2 dkg encrypt --message deadbeef - -# Decrypt a message -dkgcli --config /tmp/node3 dkg decrypt --encrypted <...> \ No newline at end of file diff --git a/dela/dkg/pedersen_bn256/dkgcli/dockerfile b/dela/dkg/pedersen_bn256/dkgcli/dockerfile deleted file mode 100644 index ceee9ac..0000000 --- a/dela/dkg/pedersen_bn256/dkgcli/dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Defines a docker container to use dkgcli -# -# Build from the dela folder: -# docker build -t dela/dkg:latest -f dkg/pedersen/dkgcli/dockerfile . -# Run with: -# docker run --rm -e LLVL=info dela/dkg -# -FROM golang:1.14 AS build -ADD . /dela -WORKDIR /dela -RUN CGO_ENABLED=0 GOOS=linux go build -o dkgcli ./dkg/pedersen/dkgcli - -FROM alpine:latest -COPY --from=build /dela/dkgcli /usr/local/bin - -ENTRYPOINT [ "dkgcli" ] -CMD ["--config", "/config", "start", "--routing", "tree", "--listen", "tcp://0.0.0.0:2000"] \ No newline at end of file diff --git a/dela/docker-compose.yml b/dela/docker-compose.yml deleted file mode 100644 index a8f32cd..0000000 --- a/dela/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - demo: - build: . - volumes: - - ./:/app - stdin_open: true # docker run -i - tty: true # docker run -t - command: tmux new-session ./demo.sh diff --git a/dela/docs/.nojekyll b/dela/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/dela/docs/README.md b/dela/docs/README.md deleted file mode 100644 index 787566e..0000000 --- a/dela/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -To manually browse the doc start with [sidebar.md](sidebar.md). Otherwise you -can still go to -[https://dedis.github.io/dela/](https://dedis.github.io/dela/). \ No newline at end of file diff --git a/dela/docs/architecture.md b/dela/docs/architecture.md deleted file mode 100644 index 8d2c353..0000000 --- a/dela/docs/architecture.md +++ /dev/null @@ -1,37 +0,0 @@ -# Architecture - -Dela has been built with modularization and testability in mind. As such, the -system is made of multiple interoperable modules based on a set of well-defined -abstractions. - -## Stacks - -The stacks display the role of each module and its relation to others. Each -module can rely on zero or more of the modules below it. From the client's -perspective the stack is simpler since it doesn't need to know about the complex -ordering mechanism. - -![stacks](assets/stacks.png) - -## Execution flow - -The execution flow displays how the system uses the modules in order to fulfill -its goals. Since a client and a node have different goals, we display the -perspective from those two separately. - -![flow](assets/dela-flow.png) - -## CoSiPBFT flow - -CoSiPBFT is the actual implementation of the ordering module. This is the core -part that orchestrates the addition of new blocks based on Collective -Signatures. This diagrams helps to understand how the leader and its followers -work together to verify and include new blocks. - -![cosi flow](assets/cosipbft.png) - -## Package dependencies - -Here is a simplified diagram of package dependencies, for reference: - -![package dep](assets/packages.png) \ No newline at end of file diff --git a/dela/docs/assets/cosipbft.png b/dela/docs/assets/cosipbft.png deleted file mode 100644 index 11afbdeff54909e126045e97f5596d46e37921c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117751 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�@tw%apl$rp}S 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 z

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 diff --git a/dela/docs/assets/infograph.png b/dela/docs/assets/infograph.png deleted file mode 100644 index 97dc9072af101e595eeb229da790c5330944dd08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 672948 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