diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd366c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.idea +.DS_Store +.vscode +a_main-packr.go +a_repo-packr.go + +bin/* +datastore +node_modules +dump +build +build_solo +build.tar.gz +bitxhub/bitxhub +cover.out +coverage.out +cover.html +pid +scripts/prepare +docs/.vuepress/dist +logs +mock_*/ +.fabric-samples \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad410e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0b199d --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ + +SHELL := /bin/bash +CURRENT_PATH = $(shell pwd) +APP_NAME = bitxhub +APP_VERSION = 0.4.8 + +# build with verison infos +VERSION_DIR = github.com/meshplus/${APP_NAME} +BUILD_DATE = $(shell date +%FT%T) +GIT_COMMIT = $(shell git log --pretty=format:'%h' -n 1) +GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD) + +LDFLAGS += -X "${VERSION_DIR}.BuildDate=${BUILD_DATE}" +LDFLAGS += -X "${VERSION_DIR}.CurrentCommit=${GIT_COMMIT}" +LDFLAGS += -X "${VERSION_DIR}.CurrentBranch=${GIT_BRANCH}" +LDFLAGS += -X "${VERSION_DIR}.CurrentVersion=${APP_VERSION}" + +GO = GO111MODULE=on go +TEST_PKGS := $(shell $(GO) list ./... | grep -v 'mock_*' | grep -v 'tester') + +RED=\033[0;31m +GREEN=\033[0;32m +BLUE=\033[0;34m +NC=\033[0m + +help: Makefile + @printf "${BLUE}Choose a command run:${NC}\n" + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + +## make prepare: Preparation before development +prepare: + @cd scripts && sh prepare.sh + +## make test: Run go unittest +test: + go generate ./... + @$(GO) test ${TEST_PKGS} -race -count=1 + +## make test-coverage: Test project with cover +test-coverage: + @$(GO) test -coverprofile cover.out ${TEST_PKGS} + $(GO) tool cover -html=cover.out -o cover.html + +## make tester: Run integration test +tester: + cd tester && $(GO) test -v -run TestTester + +## make install: Go install the project +install: + cd internal/repo && packr + $(GO) install -ldflags '${LDFLAGS}' ./cmd/${APP_NAME} + @printf "${GREEN}Build bitxhub successfully!${NC}\n" + +## make build-linux: Go build linux executable file +build-linux: + cd scripts && sh cross_compile.sh linux-amd64 ${CURRENT_PATH} + +## make docs-build: Build vuepress docs +docs-build: + cd docs && sudo vuepress build + +## make linter: Run golanci-lint +linter: + golangci-lint run --enable-all \ + -D lll -D gochecknoglobals -D maligned -D funlen \ + --skip-dirs-use-default \ + --skip-dirs internal/plugins + +## make cluster: Run cluster including 4 nodes +cluster: + cd scripts && sh cluster.sh 4 + +.PHONY: tester diff --git a/README.md b/README.md index d84f2e2..e57b122 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -# BitXHub +![BitXHub-Logo](https://raw.githubusercontent.com/meshplus/bitxhub/master/docs/logo.png) + +BitXHub is committed to building a scalable, robust, and pluggable inter-blockchain +reference implementation, that can provide reliable technical support for the formation +of a blockchain internet and intercommunication of value islands. + + +## Dependencies + +This project uses [golang](https://golang.org/), [tmux](https://github.com/tmux/tmux/wiki). Go check them out if you don't have them locally installed. + +This project also depends on [packr](https://github.com/gobuffalo/packr/), [golangci-lint](github.com/golangci/golangci-lint), [gomock](github.com/golang/mock) and [mockgen](github.com/golang/mock), Installing them by follow command: + +```bash +bash scripts/prepare.sh +``` + + +## Documentation + +If you want to run bitxhub in cluster mode, please use the follow command: + +```bash +make cluster +``` + +## Deploy +BitXHub needs link dynamic library. User should download the [libwasmer_runtime_c_api.so](https://github.com/wasmerio/wasmer/releases/download/0.11.0/libwasmer_runtime_c_api.so) file and set the LD_LIBRARY_PATH to ensure BitXHub run correctly. + +```bash +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:《your_lib_path》 +``` [Whitepaper](https://upload.hyperchain.cn/bitxhub_whitepaper.pdf) | [白皮书](https://upload.hyperchain.cn/BitXHub%E7%99%BD%E7%9A%AE%E4%B9%A6.pdf) + + +## License \ No newline at end of file diff --git a/api/gateway/gateway.go b/api/gateway/gateway.go new file mode 100644 index 0000000..83ca429 --- /dev/null +++ b/api/gateway/gateway.go @@ -0,0 +1,40 @@ +package gateway + +import ( + "context" + "fmt" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/rs/cors" + "github.com/tmc/grpc-websocket-proxy/wsproxy" + "google.golang.org/grpc" +) + +func Start(config *repo.Config) error { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, + &runtime.JSONPb{OrigName: true, EmitDefaults: true}, + ), + ) + + handler := cors.New(cors.Options{ + AllowedOrigins: config.AllowedOrigins, + }).Handler(mux) + + opts := []grpc.DialOption{grpc.WithInsecure()} + + endpoint := fmt.Sprintf("localhost:%d", config.Port.Grpc) + err := pb.RegisterChainBrokerHandlerFromEndpoint(ctx, mux, endpoint, opts) + if err != nil { + return err + } + + return http.ListenAndServe(fmt.Sprintf(":%d", config.Port.Gateway), wsproxy.WebsocketProxy(handler)) +} diff --git a/api/grpc/account.go b/api/grpc/account.go new file mode 100644 index 0000000..3a06d33 --- /dev/null +++ b/api/grpc/account.go @@ -0,0 +1,46 @@ +package grpc + +import ( + "context" + "encoding/json" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +type Account struct { + Type string `json:"type"` + Balance uint64 `json:"balance"` + ContractCount uint64 `json:"contract_count"` + CodeHash types.Hash `json:"code_hash"` +} + +func (cbs *ChainBrokerService) GetAccountBalance(ctx context.Context, req *pb.Address) (*pb.Response, error) { + addr := types.String2Address(req.Address) + + account := cbs.api.Account().GetAccount(addr) + + hash := types.Bytes2Hash(account.CodeHash) + + typ := "normal" + + if account.CodeHash != nil { + typ = "contract" + } + + ret := &Account{ + Type: typ, + Balance: account.Balance, + ContractCount: account.Nonce, + CodeHash: hash, + } + + data, err := json.Marshal(ret) + if err != nil { + return nil, err + } + + return &pb.Response{ + Data: data, + }, nil +} diff --git a/api/grpc/block.go b/api/grpc/block.go new file mode 100644 index 0000000..16fc614 --- /dev/null +++ b/api/grpc/block.go @@ -0,0 +1,83 @@ +package grpc + +import ( + "context" + + "github.com/meshplus/bitxhub-model/pb" +) + +func (cbs *ChainBrokerService) SyncMerkleWrapper(req *pb.SyncMerkleWrapperRequest, server pb.ChainBroker_SyncMerkleWrapperServer) error { + c, err := cbs.api.Broker().AddPier(req.AppchainId) + if err != nil { + return err + } + + for { + select { + case <-cbs.ctx.Done(): + break + case bw, ok := <-c: + if !ok { + return nil + } + + bs, err := bw.Marshal() + if err != nil { + cbs.api.Broker().RemovePier(req.AppchainId) + break + } + + if err := server.Send(&pb.Response{ + Data: bs, + }); err != nil { + cbs.api.Broker().RemovePier(req.AppchainId) + break + } + } + } +} + +func (cbs *ChainBrokerService) GetMerkleWrapper(req *pb.GetMerkleWrapperRequest, server pb.ChainBroker_GetMerkleWrapperServer) error { + ch := make(chan *pb.MerkleWrapper, req.End-req.Begin+1) + if err := cbs.api.Broker().GetMerkleWrapper(req.Pid, req.Begin, req.End, ch); err != nil { + return err + } + + for { + select { + case <-cbs.ctx.Done(): + break + case w := <-ch: + data, err := w.Marshal() + if err != nil { + return err + } + + if err := server.Send(&pb.Response{ + Data: data, + }); err != nil { + return err + } + + if w.BlockHeader.Number == req.End { + return nil + } + + } + } +} + +func (cbs *ChainBrokerService) GetBlock(ctx context.Context, req *pb.GetBlockRequest) (*pb.Block, error) { + return cbs.api.Broker().GetBlock(req.Type.String(), req.Value) +} + +func (cbs *ChainBrokerService) GetBlocks(ctx context.Context, req *pb.GetBlocksRequest) (*pb.GetBlocksResponse, error) { + blocks, err := cbs.api.Broker().GetBlocks(req.Offset, req.Length) + if err != nil { + return nil, err + } + + return &pb.GetBlocksResponse{ + Blocks: blocks, + }, nil +} diff --git a/api/grpc/broker.go b/api/grpc/broker.go new file mode 100644 index 0000000..5429409 --- /dev/null +++ b/api/grpc/broker.go @@ -0,0 +1,67 @@ +package grpc + +import ( + "context" + "fmt" + "net" + + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/loggers" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" +) + +type ChainBrokerService struct { + config *repo.Config + api api.CoreAPI + server *grpc.Server + logger logrus.FieldLogger + + ctx context.Context + cancel context.CancelFunc +} + +func NewChainBrokerService(api api.CoreAPI, config *repo.Config) (*ChainBrokerService, error) { + ctx, cancel := context.WithCancel(context.Background()) + + return &ChainBrokerService{ + logger: loggers.Logger(loggers.API), + config: config, + api: api, + server: grpc.NewServer(grpc.MaxConcurrentStreams(1000)), + ctx: ctx, + cancel: cancel, + }, nil +} + +func (cbs *ChainBrokerService) Start() error { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", cbs.config.Port.Grpc)) + if err != nil { + return err + } + + pb.RegisterChainBrokerServer(cbs.server, cbs) + + cbs.logger.WithFields(logrus.Fields{ + "port": cbs.config.Port.Grpc, + }).Info("GRPC service started") + + go func() { + err := cbs.server.Serve(lis) + if err != nil { + cbs.logger.Error(err) + } + }() + + return nil +} + +func (cbs *ChainBrokerService) Stop() error { + cbs.cancel() + + cbs.logger.Info("GRPC service stopped") + + return nil +} diff --git a/api/grpc/chain.go b/api/grpc/chain.go new file mode 100644 index 0000000..841eb9d --- /dev/null +++ b/api/grpc/chain.go @@ -0,0 +1,17 @@ +package grpc + +import ( + "context" + + "github.com/meshplus/bitxhub-model/pb" +) + +func (cbs *ChainBrokerService) GetChainMeta(ctx context.Context, req *pb.Request) (*pb.ChainMeta, error) { + return cbs.api.Chain().Meta() +} + +func (cbs *ChainBrokerService) GetChainStatus(ctx context.Context, req *pb.Request) (*pb.Response, error) { + return &pb.Response{ + Data: []byte(cbs.api.Chain().Status()), + }, nil +} diff --git a/api/grpc/network.go b/api/grpc/network.go new file mode 100644 index 0000000..2b1b168 --- /dev/null +++ b/api/grpc/network.go @@ -0,0 +1,18 @@ +package grpc + +import ( + "context" + + "github.com/meshplus/bitxhub-model/pb" +) + +func (cbs *ChainBrokerService) GetNetworkMeta(ctx context.Context, req *pb.Request) (*pb.Response, error) { + data, err := cbs.api.Network().PeerInfo() + if err != nil { + return nil, err + } + + return &pb.Response{ + Data: data, + }, nil +} diff --git a/api/grpc/receipt.go b/api/grpc/receipt.go new file mode 100644 index 0000000..eb52971 --- /dev/null +++ b/api/grpc/receipt.go @@ -0,0 +1,14 @@ +package grpc + +import ( + "context" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +func (cbs *ChainBrokerService) GetReceipt(ctx context.Context, req *pb.TransactionHashMsg) (*pb.Receipt, error) { + hash := types.String2Hash(req.TxHash) + + return cbs.api.Broker().GetReceipt(hash) +} diff --git a/api/grpc/subscribe.go b/api/grpc/subscribe.go new file mode 100644 index 0000000..6fcb75f --- /dev/null +++ b/api/grpc/subscribe.go @@ -0,0 +1,167 @@ +package grpc + +import ( + "encoding/json" + "fmt" + + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/model/events" +) + +type InterchainStatus struct { + From string `json:"from"` + To string `json:"to"` + Hash string `json:"hash"` +} + +// Subscribe implements the interface for client to Subscribe the certain type of event +// arose in bitxhub. This request will establish a websocket conn with client. +func (cbs *ChainBrokerService) Subscribe(req *pb.SubscriptionRequest, server pb.ChainBroker_SubscribeServer) error { + switch req.Type.String() { + case pb.SubscriptionRequest_INTERCHAIN_TX.String(): + return cbs.handleInterchainTxSubscription(server) + case pb.SubscriptionRequest_BLOCK.String(): + return cbs.handleNewBlockSubscription(server) + case pb.SubscriptionRequest_BLOCK_HEADER.String(): + return cbs.handleBlockHeaderSubscription(server) + } + + return nil +} + +func (cbs *ChainBrokerService) handleNewBlockSubscription(server pb.ChainBroker_SubscribeServer) error { + blockCh := make(chan events.NewBlockEvent) + sub := cbs.api.Feed().SubscribeNewBlockEvent(blockCh) + defer sub.Unsubscribe() + + for ev := range blockCh { + block := ev.Block + data, err := block.Marshal() + if err != nil { + return err + } + + if err := server.Send(&pb.Response{ + Data: data, + }); err != nil { + return err + } + } + + return nil +} + +func (cbs *ChainBrokerService) handleBlockHeaderSubscription(server pb.ChainBroker_SubscribeServer) error { + blockCh := make(chan events.NewBlockEvent) + sub := cbs.api.Feed().SubscribeNewBlockEvent(blockCh) + defer sub.Unsubscribe() + + for ev := range blockCh { + header := ev.Block.BlockHeader + data, err := header.Marshal() + if err != nil { + return err + } + + if err := server.Send(&pb.Response{ + Data: data, + }); err != nil { + return err + } + } + + return nil +} + +func (cbs *ChainBrokerService) handleInterchainTxSubscription(server pb.ChainBroker_SubscribeServer) error { + blockCh := make(chan events.NewBlockEvent) + sub := cbs.api.Feed().SubscribeNewBlockEvent(blockCh) + defer sub.Unsubscribe() + + for { + select { + case ev := <-blockCh: + block := ev.Block + interStatus, err := cbs.interStatus(block) + if err != nil { + cbs.logger.Fatal(err) + return fmt.Errorf("wrap interchain tx status error") + } + if interStatus == nil { + continue + } + data, err := json.Marshal(interStatus) + if err != nil { + cbs.logger.Fatalf("Marshal new block event: %s", err.Error()) + return fmt.Errorf("marshal interchain tx status failed") + } + if err := server.Send(&pb.Response{ + Data: data, + }); err != nil { + cbs.logger.Warnf("Send new interchain tx event failed %s", err.Error()) + return fmt.Errorf("send new interchain tx event failed") + } + case <-server.Context().Done(): + return nil + } + } +} + +type interchainEvent struct { + InterchainTx []*InterchainStatus `json:"interchain_tx"` + InterchainReceipt []*InterchainStatus `json:"interchain_receipt"` + InterchainConfirm []*InterchainStatus `json:"interchain_confirm"` + InterchainTxCount uint64 `json:"interchain_tx_count"` + BlockHeight uint64 `json:"block_height"` +} + +func (cbs *ChainBrokerService) interStatus(block *pb.Block) (*interchainEvent, error) { + if block.BlockHeader.InterchainIndex == nil { + return nil, nil + } + interchainIndexM := make(map[string][]uint64) + err := json.Unmarshal(block.BlockHeader.InterchainIndex, &interchainIndexM) + if err != nil { + return nil, fmt.Errorf("unmarshal interchainIndex: %w", err) + } + + // empty interchain tx + if len(interchainIndexM) == 0 { + return nil, nil + } + + meta, err := cbs.api.Chain().Meta() + if err != nil { + return nil, err + } + + ev := &interchainEvent{ + InterchainTx: make([]*InterchainStatus, 0), + InterchainReceipt: make([]*InterchainStatus, 0), + InterchainTxCount: meta.InterchainTxCount, + BlockHeight: block.BlockHeader.Number, + } + txs := block.Transactions + + for _, indices := range interchainIndexM { + for _, idx := range indices { + ibtp, err := txs[idx].GetIBTP() + if err != nil { + return nil, err + } + + status := &InterchainStatus{ + From: ibtp.From, + To: ibtp.To, + Hash: ibtp.ID(), + } + switch ibtp.Type { + case pb.IBTP_INTERCHAIN: + ev.InterchainTx = append(ev.InterchainTx, status) + case pb.IBTP_RECEIPT: + ev.InterchainReceipt = append(ev.InterchainReceipt, status) + } + } + } + return ev, nil +} diff --git a/api/grpc/transaction.go b/api/grpc/transaction.go new file mode 100644 index 0000000..5bef154 --- /dev/null +++ b/api/grpc/transaction.go @@ -0,0 +1,106 @@ +package grpc + +import ( + "context" + "fmt" + "time" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +// SendTransaction handles transaction sent by the client. +// If the transaction is valid, it will return the transaction hash. +func (cbs *ChainBrokerService) SendTransaction(ctx context.Context, tx *pb.SendTransactionRequest) (*pb.TransactionHashMsg, error) { + if !cbs.api.Broker().OrderReady() { + return nil, fmt.Errorf("the system is temporarily unavailable") + } + + if err := cbs.checkTransaction(tx); err != nil { + return nil, err + } + + hash, err := cbs.sendTransaction(tx) + if err != nil { + return nil, err + } + + return &pb.TransactionHashMsg{TxHash: hash}, nil +} + +func (cbs *ChainBrokerService) checkTransaction(tx *pb.SendTransactionRequest) error { + if tx.Data == nil { + return fmt.Errorf("tx data can't be empty") + } + + if tx.Data.Type == pb.TransactionData_NORMAL && tx.Data.Amount == 0 { + return fmt.Errorf("amount can't be 0 in transfer tx") + } + + emptyAddress := types.Address{}.Hex() + if tx.From.Hex() == emptyAddress { + return fmt.Errorf("from can't be empty") + } + + if tx.From == tx.To { + return fmt.Errorf("from can`t be the same as to") + } + + if tx.To.Hex() == emptyAddress && len(tx.Data.Payload) == 0 { + return fmt.Errorf("can't deploy empty contract") + } + + if tx.Timestamp < time.Now().UnixNano()-10*time.Minute.Nanoseconds() || + tx.Timestamp > time.Now().UnixNano()+10*time.Minute.Nanoseconds() { + return fmt.Errorf("timestamp is illegal") + } + + if tx.Nonce <= 0 { + return fmt.Errorf("nonce is illegal") + } + + if len(tx.Signature) == 0 { + return fmt.Errorf("signature can't be empty") + } + + return nil +} + +func (cbs *ChainBrokerService) sendTransaction(req *pb.SendTransactionRequest) (string, error) { + tx := &pb.Transaction{ + Version: req.Version, + From: req.From, + To: req.To, + Timestamp: req.Timestamp, + Data: req.Data, + Nonce: req.Nonce, + Signature: req.Signature, + Extra: req.Extra, + } + + tx.TransactionHash = tx.Hash() + err := cbs.api.Broker().HandleTransaction(tx) + if err != nil { + return "", err + } + + return tx.TransactionHash.Hex(), nil +} + +func (cbs *ChainBrokerService) GetTransaction(ctx context.Context, req *pb.TransactionHashMsg) (*pb.GetTransactionResponse, error) { + hash := types.String2Hash(req.TxHash) + tx, err := cbs.api.Broker().GetTransaction(hash) + if err != nil { + return nil, err + } + + meta, err := cbs.api.Broker().GetTransactionMeta(hash) + if err != nil { + return nil, err + } + + return &pb.GetTransactionResponse{ + Tx: tx, + TxMeta: meta, + }, nil +} diff --git a/cmd/bitxhub/client/account.go b/cmd/bitxhub/client/account.go new file mode 100644 index 0000000..62ac6c1 --- /dev/null +++ b/cmd/bitxhub/client/account.go @@ -0,0 +1,40 @@ +package client + +import ( + "fmt" + + "github.com/urfave/cli" +) + +func accountCMD() cli.Command { + return cli.Command{ + Name: "account", + Usage: "Query account information", + Action: getAccount, + } +} + +func getAccount(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("lack of account address") + } + + // get block by height + url, err := getURL(ctx, "account_balance/"+ctx.Args().Get(0)) + if err != nil { + return err + } + data, err := httpGet(url) + if err != nil { + return err + } + + ret, err := parseResponse(data) + if err != nil { + return fmt.Errorf("wrong response: %w", err) + } + + fmt.Println(ret) + + return nil +} diff --git a/cmd/bitxhub/client/appchain.go b/cmd/bitxhub/client/appchain.go new file mode 100644 index 0000000..c2c9ba4 --- /dev/null +++ b/cmd/bitxhub/client/appchain.go @@ -0,0 +1,186 @@ +package client + +import ( + "encoding/json" + "fmt" + "io/ioutil" + + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/urfave/cli" +) + +func appchainCMD() cli.Command { + return cli.Command{ + Name: "appchain", + Usage: "Command about appchain", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "key", + Usage: "Specific key.json path", + Required: true, + }, + }, + Subcommands: []cli.Command{ + { + Name: "register", + Usage: "Register appchain in bitxhub", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "name", + Usage: "Specific appchain name", + Required: true, + }, + cli.StringFlag{ + Name: "type", + Usage: "Specific appchain type", + Required: true, + }, + cli.StringFlag{ + Name: "desc", + Usage: "Specific appchain description", + Required: true, + }, + cli.StringFlag{ + Name: "version", + Usage: "Specific appchain version", + Required: true, + }, + cli.StringFlag{ + Name: "validators", + Usage: "Specific appchain validators path", + Required: true, + }, + }, + Action: registerAppchain, + }, + { + Name: "audit", + Usage: "Audit appchain in bitxhub", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Specific appchain id", + Required: true, + }, + }, + Action: auditAppchain, + }, + { + Name: "get", + Usage: "Get appchain info", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Specific appchain id", + Required: true, + }, + }, + Action: getAppchain, + }, + }, + } +} + +func registerAppchain(ctx *cli.Context) error { + keyPath := ctx.GlobalString("key") + grpcAddr := ctx.GlobalString("grpc") + name := ctx.String("name") + typ := ctx.String("type") + desc := ctx.String("desc") + version := ctx.String("version") + validatorsPath := ctx.String("validators") + + data, err := ioutil.ReadFile(validatorsPath) + if err != nil { + return fmt.Errorf("read validators file: %w", err) + } + + client, err := loadClient(keyPath, grpcAddr) + if err != nil { + return fmt.Errorf("load client: %w", err) + } + + receipt, err := client.InvokeBVMContract( + rpcx.InterchainContractAddr, + "Register", rpcx.String(string(data)), + rpcx.Int32(1), + rpcx.String(typ), + rpcx.String(name), + rpcx.String(desc), + rpcx.String(version), + ) + if err != nil { + return fmt.Errorf("invoke bvm contract: %w", err) + } + + if !receipt.IsSuccess() { + return fmt.Errorf("invoke register: %s", receipt.Ret) + } + + appchain := &rpcx.Appchain{} + if err := json.Unmarshal(receipt.Ret, appchain); err != nil { + return err + } + + fmt.Printf("appchain register successfully, id is %s\n", appchain.ID) + + return nil +} + +func auditAppchain(ctx *cli.Context) error { + keyPath := ctx.GlobalString("key") + grpcAddr := ctx.GlobalString("grpc") + id := ctx.String("id") + + client, err := loadClient(keyPath, grpcAddr) + if err != nil { + return fmt.Errorf("load client: %w", err) + } + + receipt, err := client.InvokeBVMContract( + rpcx.InterchainContractAddr, + "Audit", + rpcx.String(id), + rpcx.Int32(1), + rpcx.String("Audit passed"), + ) + + if err != nil { + return err + } + + if !receipt.IsSuccess() { + return fmt.Errorf("invoke audit: %s", receipt.Ret) + } + + fmt.Printf("audit appchain %s successfully\n", id) + + return nil +} + +func getAppchain(ctx *cli.Context) error { + keyPath := ctx.GlobalString("key") + grpcAddr := ctx.GlobalString("grpc") + + client, err := loadClient(keyPath, grpcAddr) + if err != nil { + return fmt.Errorf("load client: %w", err) + } + + receipt, err := client.InvokeBVMContract( + rpcx.InterchainContractAddr, + "Appchain", + ) + + if err != nil { + return err + } + + if !receipt.IsSuccess() { + return fmt.Errorf("get appchain: %s", receipt.Ret) + } + + fmt.Println(string(receipt.Ret)) + + return nil +} diff --git a/cmd/bitxhub/client/block.go b/cmd/bitxhub/client/block.go new file mode 100644 index 0000000..1c9ae00 --- /dev/null +++ b/cmd/bitxhub/client/block.go @@ -0,0 +1,65 @@ +package client + +import ( + "fmt" + + "github.com/spf13/cast" + "github.com/urfave/cli" +) + +func blockCMD() cli.Command { + return cli.Command{ + Name: "block", + Usage: "Query block", + Action: getBlock, + } +} + +func getBlock(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("please input block height or block hash") + } + + input := ctx.Args().Get(0) + + height, err := cast.ToUint64E(input) + if err != nil { + return getBlockByHash(ctx, input) + } + + if err := getBlockByHeight(ctx, height); err != nil { + return err + } + + return nil +} + +func getBlockByHeight(ctx *cli.Context, height uint64) error { + url, err := getURL(ctx, fmt.Sprintf("block?type=0&value=%d", height)) + if err != nil { + return err + } + data, err := httpGet(url) + if err != nil { + return err + } + + fmt.Println(string(data)) + + return nil +} + +func getBlockByHash(ctx *cli.Context, hash string) error { + url, err := getURL(ctx, fmt.Sprintf("block?type=1&value=%s", hash)) + if err != nil { + return err + } + data, err := httpGet(url) + if err != nil { + return err + } + + fmt.Println(string(data)) + + return nil +} diff --git a/cmd/bitxhub/client/chain.go b/cmd/bitxhub/client/chain.go new file mode 100644 index 0000000..8e4e1fd --- /dev/null +++ b/cmd/bitxhub/client/chain.go @@ -0,0 +1,64 @@ +package client + +import ( + "fmt" + + "github.com/urfave/cli" +) + +func chainCMD() cli.Command { + return cli.Command{ + Name: "chain", + Usage: "Query bitxhub chain info", + Subcommands: []cli.Command{ + { + Name: "meta", + Usage: "Query bitxhub chain meta", + Action: getChainMeta, + }, + { + Name: "status", + Usage: "Query bitxhub chain status", + Action: getChainStatus, + }, + }, + } +} + +func getChainMeta(ctx *cli.Context) error { + url, err := getURL(ctx, "chain_meta") + if err != nil { + return err + } + + data, err := httpGet(url) + if err != nil { + return fmt.Errorf("http get: %w", err) + } + + fmt.Println(string(data)) + + return nil +} + +func getChainStatus(ctx *cli.Context) error { + url, err := getURL(ctx, "chain_status") + if err != nil { + return err + } + + data, err := httpGet(url) + if err != nil { + return fmt.Errorf("http get: %w", err) + } + + ret, err := parseResponse(data) + if err != nil { + return err + } + + fmt.Println(ret) + + return nil + +} diff --git a/cmd/bitxhub/client/client.go b/cmd/bitxhub/client/client.go new file mode 100644 index 0000000..4bad959 --- /dev/null +++ b/cmd/bitxhub/client/client.go @@ -0,0 +1,35 @@ +package client + +import "github.com/urfave/cli" + +var clientCMD = cli.Command{ + Name: "client", + Usage: "BitXHub client command", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "gateway", + Usage: "Specific gateway address", + Value: "localhost:9091", + }, + cli.StringFlag{ + Name: "grpc", + Usage: "Specific grpc address", + Value: "localhost:60011", + }, + }, + Subcommands: cli.Commands{ + accountCMD(), + appchainCMD(), + chainCMD(), + blockCMD(), + networkCMD(), + receiptCMD(), + ruleCMD(), + txCMD(), + interchainCMD(), + }, +} + +func LoadClientCMD() cli.Command { + return clientCMD +} diff --git a/cmd/bitxhub/client/helper.go b/cmd/bitxhub/client/helper.go new file mode 100644 index 0000000..7702007 --- /dev/null +++ b/cmd/bitxhub/client/helper.go @@ -0,0 +1,23 @@ +package client + +import ( + "github.com/meshplus/bitxhub-kit/key" + rpcx "github.com/meshplus/go-bitxhub-client" +) + +func loadClient(keyPath, grpcAddr string) (rpcx.Client, error) { + key, err := key.LoadKey(keyPath) + if err != nil { + return nil, err + } + + privateKey, err := key.GetPrivateKey("bitxhub") + if err != nil { + return nil, err + } + + return rpcx.New( + rpcx.WithAddrs([]string{grpcAddr}), + rpcx.WithPrivateKey(privateKey), + ) +} diff --git a/cmd/bitxhub/client/http.go b/cmd/bitxhub/client/http.go new file mode 100644 index 0000000..5a34caf --- /dev/null +++ b/cmd/bitxhub/client/http.go @@ -0,0 +1,69 @@ +package client + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/meshplus/bitxhub/internal/repo" + "github.com/urfave/cli" +) + +func httpGet(url string) ([]byte, error) { + /* #nosec */ + resp, err := http.Get(url) + if err != nil { + return nil, err + } + + c, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = resp.Body.Close() + if err != nil { + return nil, err + } + + return c, nil +} + +func httpPost(url string, data []byte) ([]byte, error) { + buffer := bytes.NewBuffer(data) + + /* #nosec */ + resp, err := http.Post(url, "application/json", buffer) + if err != nil { + return nil, err + } + c, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = resp.Body.Close() + if err != nil { + return nil, err + } + + return c, nil +} + +func getURL(ctx *cli.Context, path string) (string, error) { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return "", err + } + + api, err := repo.GetAPI(repoRoot) + if err != nil { + return "", fmt.Errorf("get api file: %w", err) + } + + api = strings.TrimSpace(api) + + return api + path, nil +} diff --git a/cmd/bitxhub/client/interchain.go b/cmd/bitxhub/client/interchain.go new file mode 100644 index 0000000..068e508 --- /dev/null +++ b/cmd/bitxhub/client/interchain.go @@ -0,0 +1,71 @@ +package client + +import ( + "fmt" + + "github.com/meshplus/bitxhub-kit/types" + + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/urfave/cli" +) + +func interchainCMD() cli.Command { + return cli.Command{ + Name: "interchain", + Usage: "Query interchain info", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "key", + Usage: "Specific key.json path", + Required: true, + }, + }, + Subcommands: []cli.Command{ + { + Name: "ibtp", + Usage: "Query ibtp by id", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Specific ibtp id", + Required: true, + }, + }, + Action: getIBTP, + }, + }, + } +} + +func getIBTP(ctx *cli.Context) error { + keyPath := ctx.GlobalString("key") + grpcAddr := ctx.GlobalString("grpc") + id := ctx.String("id") + + client, err := loadClient(keyPath, grpcAddr) + if err != nil { + return fmt.Errorf("laod client: %w", err) + } + + receipt, err := client.InvokeBVMContract( + rpcx.InterchainContractAddr, + "GetIBTPByID", + rpcx.String(id), + ) + if err != nil { + return err + } + + hash := types.Bytes2Hash(receipt.Ret) + + fmt.Printf("Tx hash: %s\n", hash.Hex()) + + response, err := client.GetTransaction(hash.Hex()) + if err != nil { + return err + } + + fmt.Println(response) + + return nil +} diff --git a/cmd/bitxhub/client/network.go b/cmd/bitxhub/client/network.go new file mode 100644 index 0000000..8bb75fc --- /dev/null +++ b/cmd/bitxhub/client/network.go @@ -0,0 +1,36 @@ +package client + +import ( + "fmt" + + "github.com/urfave/cli" +) + +func networkCMD() cli.Command { + return cli.Command{ + Name: "network", + Usage: "Query network info from node", + Action: network, + } +} + +func network(ctx *cli.Context) error { + url, err := getURL(ctx, "network") + if err != nil { + return err + } + + data, err := httpGet(url) + if err != nil { + return fmt.Errorf("http get: %w", err) + } + + ret, err := parseResponse(data) + if err != nil { + return fmt.Errorf("wrong response: %w", err) + } + + fmt.Println(ret) + + return nil +} diff --git a/cmd/bitxhub/client/receipt.go b/cmd/bitxhub/client/receipt.go new file mode 100644 index 0000000..ed8cd6c --- /dev/null +++ b/cmd/bitxhub/client/receipt.go @@ -0,0 +1,35 @@ +package client + +import ( + "fmt" + + "github.com/urfave/cli" +) + +func receiptCMD() cli.Command { + return cli.Command{ + Name: "receipt", + Usage: "Query receipt", + Action: getReceipt, + } +} + +func getReceipt(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("please input transaction hash") + } + + url, err := getURL(ctx, "receipt/"+ctx.Args().Get(0)) + if err != nil { + return err + } + + data, err := httpGet(url) + if err != nil { + return err + } + + fmt.Println(string(data)) + + return nil +} diff --git a/cmd/bitxhub/client/result.go b/cmd/bitxhub/client/result.go new file mode 100644 index 0000000..e1a0f3f --- /dev/null +++ b/cmd/bitxhub/client/result.go @@ -0,0 +1,19 @@ +package client + +import ( + "encoding/base64" + "fmt" + + "github.com/tidwall/gjson" +) + +func parseResponse(data []byte) (string, error) { + res := gjson.Get(string(data), "data") + + ret, err := base64.StdEncoding.DecodeString(res.String()) + if err != nil { + return "", fmt.Errorf("wrong data: %w", err) + } + + return string(ret), nil +} diff --git a/cmd/bitxhub/client/rule.go b/cmd/bitxhub/client/rule.go new file mode 100644 index 0000000..bbb2852 --- /dev/null +++ b/cmd/bitxhub/client/rule.go @@ -0,0 +1,81 @@ +package client + +import ( + "fmt" + "io/ioutil" + + "github.com/tidwall/gjson" + + "github.com/meshplus/bitxhub/internal/constant" + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/urfave/cli" +) + +func ruleCMD() cli.Command { + return cli.Command{ + Name: "rule", + Usage: "Command about rule", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "key", + Usage: "Specific key.json path", + Required: true, + }, + }, + Subcommands: cli.Commands{ + { + Name: "deploy", + Usage: "Deploy validation rule", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "path", + Usage: "Specific rule path", + Required: true, + }, + }, + Action: deployRule, + }, + }, + } +} + +func deployRule(ctx *cli.Context) error { + keyPath := ctx.GlobalString("key") + grpcAddr := ctx.GlobalString("grpc") + rulePath := ctx.String("path") + + contract, err := ioutil.ReadFile(rulePath) + if err != nil { + return err + } + + client, err := loadClient(keyPath, grpcAddr) + if err != nil { + return fmt.Errorf("load client: %w", err) + } + + data, err := ioutil.ReadFile(keyPath) + if err != nil { + return err + } + + address := gjson.Get(string(data), "address") + + contractAddr, err := client.DeployContract(contract) + if err != nil { + return fmt.Errorf("deploy rule: %w", err) + } + + _, err = client.InvokeBVMContract( + constant.RuleManagerContractAddr.Address(), + "RegisterRule", + rpcx.String(address.String()), + rpcx.String(contractAddr.String())) + if err != nil { + return fmt.Errorf("register rule") + } + + fmt.Println("Deploy rule to bitxhub successfully") + + return nil +} diff --git a/cmd/bitxhub/client/transaction.go b/cmd/bitxhub/client/transaction.go new file mode 100644 index 0000000..b6fb4a7 --- /dev/null +++ b/cmd/bitxhub/client/transaction.go @@ -0,0 +1,142 @@ +package client + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/urfave/cli" +) + +func txCMD() cli.Command { + return cli.Command{ + Name: "tx", + Usage: "Transaction manipulation", + Subcommands: []cli.Command{ + { + Name: "get", + Usage: "Query transaction", + Action: getTransaction, + }, + { + Name: "send", + Usage: "Send transaction", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "key", + Usage: "Private key path", + }, + cli.StringFlag{ + Name: "to", + Usage: "Target address", + }, + cli.Uint64Flag{ + Name: "amount", + Usage: "Transfer amount", + }, + cli.Uint64Flag{ + Name: "type", + Usage: "Transaction type", + }, + }, + Action: sendTransaction, + }, + }, + } + +} + +func getTransaction(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("please input transaction hash") + } + + hash := ctx.Args().Get(0) + + url, err := getURL(ctx, "transaction/"+hash) + if err != nil { + return err + } + + data, err := httpGet(url) + if err != nil { + return err + } + + fmt.Println(string(data)) + + return nil +} + +func sendTransaction(ctx *cli.Context) error { + toString := ctx.String("to") + amount := ctx.Uint64("amount") + txType := ctx.Uint64("type") + keyPath := ctx.String("key") + + if keyPath == "" { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return err + } + + keyPath = repo.GetKeyPath(repoRoot) + } + + key, err := repo.LoadKey(keyPath) + if err != nil { + return fmt.Errorf("wrong key: %w", err) + } + + from, err := key.PrivKey.PublicKey().Address() + if err != nil { + return fmt.Errorf("wrong private key: %w", err) + } + + to := types.String2Address(toString) + + req := pb.SendTransactionRequest{ + From: from, + To: to, + Timestamp: time.Now().UnixNano(), + Data: &pb.TransactionData{ + Type: pb.TransactionData_Type(txType), + Amount: amount, + }, + Signature: nil, + } + + tx := &pb.Transaction{ + From: from, + To: to, + Timestamp: req.Timestamp, + Nonce: req.Nonce, + Data: req.Data, + } + + if err := tx.Sign(key.PrivKey); err != nil { + return err + } + + reqData, err := json.Marshal(req) + if err != nil { + return err + } + + url, err := getURL(ctx, "transaction") + if err != nil { + return err + } + + resp, err := httpPost(url, reqData) + if err != nil { + return err + } + + fmt.Println(string(resp)) + + return nil +} diff --git a/cmd/bitxhub/config.go b/cmd/bitxhub/config.go new file mode 100644 index 0000000..f81fc05 --- /dev/null +++ b/cmd/bitxhub/config.go @@ -0,0 +1,65 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/hokaccha/go-prettyjson" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/urfave/cli" +) + +func configCMD() cli.Command { + return cli.Command{ + Name: "config", + Usage: "Operate bitxhub config", + Action: showConfig, + Flags: []cli.Flag{}, + } +} + +func showConfig(ctx *cli.Context) error { + repoRoot := ctx.GlobalString("repo") + var err error + if len(repoRoot) == 0 { + if repoRoot, err = repo.PathRoot(); err != nil { + return err + } + } + + cfg, err := repo.UnmarshalConfig(repoRoot) + if err != nil { + return err + } + + if ctx.NArg() == 0 { + s, err := prettyjson.Marshal(cfg) + if err != nil { + return err + } + + fmt.Println(string(s)) + + return nil + } + m := make(map[string]interface{}) + data, err := cfg.Bytes() + if err != nil { + return err + } + + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + v := m[ctx.Args()[0]] + + s, err := prettyjson.Marshal(v) + if err != nil { + return err + } + + fmt.Println(string(s)) + + return nil +} diff --git a/cmd/bitxhub/init.go b/cmd/bitxhub/init.go new file mode 100755 index 0000000..f869a3e --- /dev/null +++ b/cmd/bitxhub/init.go @@ -0,0 +1,47 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "github.com/meshplus/bitxhub/internal/repo" + "github.com/urfave/cli" +) + +func initCMD() cli.Command { + return cli.Command{ + Name: "init", + Usage: "Initialize BitXHub local configuration", + Action: initialize, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "config", + Value: "", + Usage: "BitXHub config repo path", + }, + }, + } +} + +func initialize(ctx *cli.Context) error { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return err + } + + fmt.Printf("initializing bitxhub at %s\n", repoRoot) + + if repo.Initialized(repoRoot) { + fmt.Println("bitxhub configuration file already exists") + fmt.Println("reinitializing would overwrite your configuration, Y/N?") + input := bufio.NewScanner(os.Stdin) + input.Scan() + if input.Text() == "Y" || input.Text() == "y" { + return repo.Initialize(repoRoot) + } + return nil + } + + return repo.Initialize(repoRoot) +} diff --git a/cmd/bitxhub/key.go b/cmd/bitxhub/key.go new file mode 100755 index 0000000..19a78ac --- /dev/null +++ b/cmd/bitxhub/key.go @@ -0,0 +1,169 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/meshplus/bitxhub-kit/key" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-kit/fileutil" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/cert" + "github.com/urfave/cli" +) + +func keyCMD() cli.Command { + return cli.Command{ + Name: "key", + Usage: "Create and show key information", + Subcommands: []cli.Command{ + { + Name: "gen", + Usage: "Create new key file from private key", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "save,s", + Usage: "save key into repo", + }, + cli.StringFlag{ + Name: "priv", + Usage: "private key path", + Required: true, + }, + }, + Action: generateKey, + }, + { + Name: "show", + Usage: "Show key from cert", + Action: showKey, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "path", + Usage: "Node Path", + }, + }, + }, + { + Name: "pid", + Usage: "Show pid from cert", + Action: getPid, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "path", + Usage: "Private Key Path", + }, + }, + }, + }, + } +} + +func generateKey(ctx *cli.Context) error { + privPath := ctx.String("priv") + + data, err := ioutil.ReadFile(privPath) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + stdPriv, err := cert.ParsePrivateKey(data) + if err != nil { + return err + } + + privKey := &ecdsa.PrivateKey{K: stdPriv} + + act, err := key.NewWithPrivateKey(privKey, "bitxhub") + if err != nil { + return fmt.Errorf("create account error: %s", err) + } + + out, err := json.Marshal(act) + if err != nil { + return err + } + + if ctx.Bool("save") { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return err + } + + keyPath := filepath.Join(repoRoot, repo.KeyName) + ok := fileutil.Exist(keyPath) + if ok { + fmt.Println("Key file already exists") + fmt.Println("Recreate would overwrite your key, Y/N?") + input := bufio.NewScanner(os.Stdin) + input.Scan() + if input.Text() == "Y" || input.Text() == "y" { + err := ioutil.WriteFile(keyPath, out, os.ModePerm) + if err != nil { + return fmt.Errorf("write key file: %w", err) + } + } + + return nil + } + + err = ioutil.WriteFile(keyPath, out, os.ModePerm) + if err != nil { + return fmt.Errorf("write key file: %w", err) + } + } else { + fmt.Println(string(out)) + } + + return nil +} + +func showKey(ctx *cli.Context) error { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return err + } + + keyPath := filepath.Join(repoRoot, repo.KeyName) + data, err := ioutil.ReadFile(keyPath) + if err != nil { + return err + } + + fmt.Println(string(data)) + + return nil +} + +func getPid(ctx *cli.Context) error { + privPath := ctx.String("path") + + data, err := ioutil.ReadFile(privPath) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + stdPriv, err := cert.ParsePrivateKey(data) + if err != nil { + return err + } + + _, pk, err := crypto.KeyPairFromStdKey(stdPriv) + if err != nil { + return err + } + + pid, err := peer.IDFromPublicKey(pk) + if err != nil { + return err + } + + fmt.Println(pid) + + return nil +} diff --git a/cmd/bitxhub/main.go b/cmd/bitxhub/main.go new file mode 100644 index 0000000..874914c --- /dev/null +++ b/cmd/bitxhub/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/meshplus/bitxhub/cmd/bitxhub/client" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "BitXHub" + app.Usage = "A leading inter-blockchain platform" + app.Compiled = time.Now() + + cli.VersionPrinter = func(c *cli.Context) { + printVersion() + } + + // global flags + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "repo", + Usage: "BitXHub storage repo path", + }, + } + + app.Commands = []cli.Command{ + configCMD(), + initCMD(), + startCMD(), + keyCMD(), + versionCMD(), + client.LoadClientCMD(), + } + + err := app.Run(os.Args) + if err != nil { + fmt.Println(err) + } +} diff --git a/cmd/bitxhub/start.go b/cmd/bitxhub/start.go new file mode 100755 index 0000000..e9da113 --- /dev/null +++ b/cmd/bitxhub/start.go @@ -0,0 +1,142 @@ +package main + +import ( + "fmt" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + "path/filepath" + "sync" + "syscall" + "time" + + "github.com/meshplus/bitxhub" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub/api/gateway" + "github.com/meshplus/bitxhub/api/grpc" + "github.com/meshplus/bitxhub/internal/app" + "github.com/meshplus/bitxhub/internal/coreapi" + "github.com/meshplus/bitxhub/internal/loggers" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/urfave/cli" +) + +var logger = log.NewWithModule("cmd") + +func startCMD() cli.Command { + return cli.Command{ + Name: "start", + Usage: "Start a long-running start process", + Action: start, + } +} + +func start(ctx *cli.Context) error { + repoRoot, err := repo.PathRootWithDefault(ctx.GlobalString("repo")) + if err != nil { + return fmt.Errorf("get repo path: %w", err) + } + + repo, err := repo.Load(repoRoot) + if err != nil { + return fmt.Errorf("repo load: %w", err) + } + + err = log.Initialize( + log.WithReportCaller(repo.Config.Log.ReportCaller), + log.WithPersist(true), + log.WithFilePath(filepath.Join(repoRoot, repo.Config.Log.Dir)), + log.WithFileName(repo.Config.Log.Filename), + log.WithMaxSize(2*1024*1024), + log.WithMaxAge(24*time.Hour), + log.WithRotationTime(24*time.Hour), + ) + if err != nil { + return fmt.Errorf("log initialize: %w", err) + } + + loggers.Initialize(repo.Config) + + if repo.Config.PProf.Enable { + runPProf(repo.Config.Port.PProf) + } + + printVersion() + + bxh, err := app.NewBitXHub(repo) + if err != nil { + return err + } + + // coreapi + api, err := coreapi.New(bxh) + if err != nil { + return err + } + + // start grpc service + b, err := grpc.NewChainBrokerService(api, repo.Config) + if err != nil { + return err + } + + if err := b.Start(); err != nil { + return err + } + + go func() { + logger.WithField("port", repo.Config.Port.Gateway).Info("Gateway service started") + err := gateway.Start(repo.Config) + if err != nil { + fmt.Println(err) + } + }() + + var wg sync.WaitGroup + wg.Add(1) + handleShutdown(bxh, &wg) + + if err := bxh.Start(); err != nil { + return err + } + + wg.Wait() + + return nil +} + +func printVersion() { + fmt.Printf("BitXHub version: %s-%s-%s\n", bitxhub.CurrentVersion, bitxhub.CurrentBranch, bitxhub.CurrentCommit) + fmt.Printf("App build date: %s\n", bitxhub.BuildDate) + fmt.Printf("System version: %s\n", bitxhub.Platform) + fmt.Printf("Golang version: %s\n", bitxhub.GoVersion) + fmt.Println() +} + +func handleShutdown(node *app.BitXHub, wg *sync.WaitGroup) { + var stop = make(chan os.Signal) + signal.Notify(stop, syscall.SIGTERM) + signal.Notify(stop, syscall.SIGINT) + + go func() { + <-stop + fmt.Println("received interrupt signal, shutting down...") + if err := node.Stop(); err != nil { + panic(err) + } + wg.Done() + os.Exit(0) + }() +} + +func runPProf(port int64) { + go func() { + addr := fmt.Sprintf(":%d", port) + logger.WithField("port", port).Info("Start pprof") + err := http.ListenAndServe(addr, nil) + if err != nil { + fmt.Println(err) + } + }() +} diff --git a/cmd/bitxhub/version.go b/cmd/bitxhub/version.go new file mode 100644 index 0000000..e502803 --- /dev/null +++ b/cmd/bitxhub/version.go @@ -0,0 +1,17 @@ +package main + +import "github.com/urfave/cli" + +func versionCMD() cli.Command { + return cli.Command{ + Name: "version", + Usage: "BitXHub version", + Action: version, + } +} + +func version(ctx *cli.Context) error { + printVersion() + + return nil +} diff --git a/config/api b/config/api new file mode 100644 index 0000000..fc69c8b --- /dev/null +++ b/config/api @@ -0,0 +1 @@ +http://localhost:9091/v1/ \ No newline at end of file diff --git a/config/bitxhub.toml b/config/bitxhub.toml new file mode 100755 index 0000000..63bfa4b --- /dev/null +++ b/config/bitxhub.toml @@ -0,0 +1,41 @@ +title = "BitXHub configuration file" + +solo = false + +[port] + grpc = 60011 + gateway = 9091 + pprof = 53121 + +[pprof] + enable = true + +[gateway] + allowed_origins = ["*"] + +[log] + level = "debug" + dir = "logs" + filename = "bitxhub.log" + report_caller = true + [log.module] + p2p = "debug" + consensus = "debug" + executor = "debug" + router = "info" + api = "info" + coreapi = "info" + +[cert] + verify = true + +[order] + plugin = "plugins/raft.so" + +[genesis] + addresses = [ + "0xe6f8c9cf6e38bd506fae93b73ee5e80cc8f73667", + "0x8374bb1e41d4a4bb4ac465e74caa37d242825efc", + "0x759801eab44c9a9bbc3e09cb7f1f85ac57298708", + "0xf2d66e2c27e93ff083ee3999acb678a36bb349bb" + ] diff --git a/config/network.toml b/config/network.toml new file mode 100644 index 0000000..ca6e8d2 --- /dev/null +++ b/config/network.toml @@ -0,0 +1,18 @@ +id = 1 # self id +N = 4 # the number of cluster nodes + +[[nodes]] +addr = "/ip4/127.0.0.1/tcp/4001/p2p/QmZZFk1Tj6p25ecz98SpyHxb6joDPxR3wVPptDwuc8fue1" +id = 1 + +[[nodes]] +addr = "/ip4/127.0.0.1/tcp/4002/p2p/QmNRgD6djYJERNpDpHqRn3mxjJ9SYiiGWzExNSy4sEmSNL" +id = 2 + +[[nodes]] +addr = "/ip4/127.0.0.1/tcp/4003/p2p/QmXmyw2usKApP6UyK3cHEJ1XvxxSa8kM2M3Q1T6fhdifs5" +id = 3 + +[[nodes]] +addr = "/ip4/127.0.0.1/tcp/4004/p2p/QmY21wH1M694j1JFEvwegyJz8h2VpaSeeqcwt2vUpxsFPt" +id = 4 diff --git a/config/order.toml b/config/order.toml new file mode 100644 index 0000000..b2b0f15 --- /dev/null +++ b/config/order.toml @@ -0,0 +1,11 @@ +[raft] +election_tick = 10 # ElectionTick is the number of Node.Tick invocations that must pass between elections.(s) +heartbeat_tick = 1 # HeartbeatTick is the number of Node.Tick invocations that must pass between heartbeats.(s) +max_size_per_msg = 1048576 # 1024*1024, MaxSizePerMsg limits the max size of each append message. +max_inflight_msgs = 500 # MaxInflightMsgs limits the max number of in-flight append messages during optimistic replication phase. +check_quorum = false # Leader steps down when quorum is not active for an electionTimeout. +pre_vote = true # PreVote prevents reconnected node from disturbing network. +disable_proposal_forwarding = true # This prevents blocks from being accidentally proposed by followers. + [raft.tx_pool] + pack_size = 500 # Maximum number of transaction packages. + block_tick = "500ms" # Block packaging time period. diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000..215f661 Binary files /dev/null and b/docs/logo.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e4d483c --- /dev/null +++ b/go.mod @@ -0,0 +1,59 @@ +module github.com/meshplus/bitxhub + +require ( + github.com/Knetic/govaluate v3.0.0+incompatible // indirect + github.com/Rican7/retry v0.1.0 + github.com/aristanetworks/goarista v0.0.0-20200310212843-2da4c1f5881b // indirect + github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a + github.com/coreos/etcd v3.3.18+incompatible + github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/ethereum/go-ethereum v1.9.7 + github.com/fatih/color v1.7.0 // indirect + github.com/gobuffalo/envy v1.9.0 // indirect + github.com/gobuffalo/packd v1.0.0 + github.com/gobuffalo/packr v1.30.1 + github.com/gogo/protobuf v1.3.1 + github.com/golang/mock v1.4.3 + github.com/golang/protobuf v1.3.3 + github.com/grpc-ecosystem/grpc-gateway v1.13.0 + github.com/hashicorp/go-version v1.2.0 // indirect + github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e + github.com/hyperledger/fabric v2.0.1+incompatible + github.com/hyperledger/fabric-amcl v0.0.0-20200128223036-d1aa2665426a // indirect + github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b + github.com/libp2p/go-libp2p v0.5.0 + github.com/libp2p/go-libp2p-core v0.3.0 + github.com/magiconair/properties v1.8.1 + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect + github.com/meshplus/bitxhub-kit v0.0.0-20200329124935-585edb85ca63 + github.com/meshplus/bitxhub-model v0.0.0-20200329125320-a37501c7a13e + github.com/meshplus/go-bitxhub-client v0.0.0-20200329130007-318d6f4c1087 + github.com/miekg/pkcs11 v1.0.3 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/multiformats/go-multiaddr v0.2.0 + github.com/pkg/errors v0.9.1 + github.com/rogpeppe/go-internal v1.5.2 // indirect + github.com/rs/cors v1.7.0 + github.com/sirupsen/logrus v1.4.2 + github.com/spf13/cast v1.3.0 + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.4.0 + github.com/stretchr/testify v1.4.0 + github.com/sykesm/zap-logfmt v0.0.3 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 + github.com/tendermint/iavl v0.12.4 + github.com/tendermint/tm-db v0.1.1 + github.com/tidwall/gjson v1.3.5 + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 + github.com/urfave/cli v1.22.1 + github.com/wasmerio/go-ext-wasm v0.3.1 + github.com/willf/bitset v1.1.10 // indirect + github.com/willf/bloom v2.0.3+incompatible + github.com/wonderivan/logger v1.0.0 + golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88 // indirect + google.golang.org/grpc v1.27.1 +) + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..73b155e --- /dev/null +++ b/go.sum @@ -0,0 +1,926 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Rican7/retry v0.1.0 h1:FqK94z34ly8Baa6K+G8Mmza9rYWTKOJk+yckIBB5qVk= +github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= +github.com/Shopify/sarama v1.26.1 h1:3jnfWKD7gVwbB1KSy/lE0szA9duPuSFLViK0o/d3DgA= +github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= +github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= +github.com/aristanetworks/goarista v0.0.0-20200310212843-2da4c1f5881b h1:VBFuX8nQQ57A6OGYGOLugx/Sc488F0nNIdTtcmNq9qE= +github.com/aristanetworks/goarista v0.0.0-20200310212843-2da4c1f5881b/go.mod h1:QZe5Yh80Hp1b6JxQdpfSEEe8X7hTyTEZSosSrFf/oJE= +github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 h1:qkOC5Gd33k54tobS36cXdAzJbeHaduLtnLQQwNoIi78= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3 h1:A/EVblehb75cUgXA5njHPn0kLAsykn6mJGz7rnmW5W0= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cbergoon/merkletree v0.2.0 h1:Bttqr3OuoiZEo4ed1L7fTasHka9II+BF9fhBfbNEEoQ= +github.com/cbergoon/merkletree v0.2.0/go.mod h1:5c15eckUgiucMGDOCanvalj/yJnD+KAZj1qyJtRW5aM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a h1:kTv7wPomOuRf17BKQKO5Y6GrKsYC52XHrjf26H6FdQU= +github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.18+incompatible h1:Zz1aXgDrFFi1nadh58tA9ktt06cmPTwNNP3dXwIq1lE= +github.com/coreos/etcd v3.3.18+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/go-ethereum v1.9.7 h1:p4O+z0MGzB7xxngHbplcYNloxkFwGkeComhkzWnq0ig= +github.com/ethereum/go-ethereum v1.9.7/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= +github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= +github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.13.0 h1:sBDQoHXrOlfPobnKw69FIKa1wg9qsLLvvQ/Y19WtFgI= +github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyFSs7UnsU= +github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= +github.com/gxed/hashland/murmur3 v0.0.1 h1:SheiaIt0sda5K+8FLz952/1iWS9zrnKsEJaOJu4ZbSc= +github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e h1:0aewS5NTyxftZHSnFaJmWE5oCCrj4DyEXkAiMa1iZJM= +github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/hyperledger/fabric v2.0.1+incompatible h1:7W+yG0gLKTC7NLcWPT3vfpnaseztPpH9wXGfAW7yvBs= +github.com/hyperledger/fabric v2.0.1+incompatible/go.mod h1:tGFAOCT696D3rG0Vofd2dyWYLySHlh0aQjf7Q1HAju0= +github.com/hyperledger/fabric-amcl v0.0.0-20200128223036-d1aa2665426a h1:HgdNn3UYz8PdcZrLEk0IsSU4LRHp7yY2rgjIKcSiJaA= +github.com/hyperledger/fabric-amcl v0.0.0-20200128223036-d1aa2665426a/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= +github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b h1:rZ3Vro68vStzLYfcSrQlprjjCf5UmFk7QjKGgHL8IQg= +github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ipfs/go-cid v0.0.1 h1:GBjWPktLnNyX0JiQCNFpUuUSoMw5KMyqrsejHYlILBE= +github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.4 h1:UlfXKrZx1DjZoBhQHmNHLC1fK1dUJDN20Y28A7s+gJ8= +github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= +github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.0 h1:TOxI04l8CmO4zGtesENhzm4PwkFwJXY3rKiYaaMf9fI= +github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.1 h1:F4k0TkTAZGLFzBOrVKDAvch6JZtuN4NHkfdcEZL50aI= +github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= +github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= +github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.1.0 h1:OsCuIIh1LMTk4WIQ1UJH7e3j01qlOP+KWVhNS6lBDZY= +github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= +github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50= +github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= +github.com/ipfs/go-log v0.0.1 h1:9XTUN/rW64BCG1YhPK9Hoy3q8nr4gOmHHBpgFdfw6Lc= +github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= +github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA= +github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec h1:DQqZhhDvrTrEQ3Qod5yfavcA064e53xlQ+xajiorXgM= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= +github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc= +github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= +github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8 h1:bspPhN+oKYFk5fcGNuQzp6IGzYQSenLEgH3s6jkXrWw= +github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= +github.com/jbenet/goprocess v0.1.3 h1:YKyIEECS/XvcfHtBzxtjBBbWK+MbvA6dG8ASiqwvr10= +github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.1 h1:a/QY0o9S6wCi0XhxaMX/QmusicNUqCqFugR6WKPOSoQ= +github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible h1:eXEwY0f2h6mcobdAxm4VRSWds4tqmlLdUqxu8ybiEEA= +github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.0 h1:wZIfTHGdu7TeGu318uLJwuQvTMt9UpRyS+XV2Rc4wo4= +github.com/lestrrat-go/strftime v1.0.0/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= +github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88= +github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= +github.com/libp2p/go-buffer-pool v0.0.1 h1:9Rrn/H46cXjaA2HQ5Y8lyhOS1NhTkZ4yuEs2r3Eechg= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= +github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= +github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-conn-security-multistream v0.1.0 h1:aqGmto+ttL/uJgX0JtQI0tD21CIEy5eYd1Hlp0juHY0= +github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= +github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ= +github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= +github.com/libp2p/go-flow-metrics v0.0.1 h1:0gxuFd2GuK7IIP5pKljLwps6TvcuYgvG7Atqi3INF5s= +github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= +github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= +github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-libp2p v0.5.0 h1:/nnb5mc2TK6TwknECsWIkfCwMTHv0AXbvzxlnVivfeg= +github.com/libp2p/go-libp2p v0.5.0/go.mod h1:Os7a5Z3B+ErF4v7zgIJ7nBHNu2LYt8ZMLkTQUB3G/wA= +github.com/libp2p/go-libp2p-autonat v0.1.1 h1:WLBZcIRsjZlWdAZj9CiBSvU2wQXoUOiS1Zk1tM7DTJI= +github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= +github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= +github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk= +github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= +github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8= +github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= +github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= +github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= +github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= +github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= +github.com/libp2p/go-libp2p-core v0.2.4 h1:Et6ykkTwI6PU44tr8qUF9k43vP0aduMNniShAbUJJw8= +github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= +github.com/libp2p/go-libp2p-core v0.3.0 h1:F7PqduvrztDtFsAa/bcheQ3azmNo+Nq7m8hQY5GiUW8= +github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= +github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ= +github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.2.0 h1:1p3YSOq7VsgaL+xVHPi8XAmtGyas6D2J6rWBEfz/aiY= +github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= +github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= +github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= +github.com/libp2p/go-libp2p-mplex v0.2.1 h1:E1xaJBQnbSiTHGI1gaBKmKhu1TUKkErKJnE8iGvirYI= +github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= +github.com/libp2p/go-libp2p-nat v0.0.5 h1:/mH8pXFVKleflDL1YwqMg27W9GD8kjEx7NY0P6eGc98= +github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= +github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-peer v0.2.0 h1:EQ8kMjaCUwt/Y5uLgjT8iY2qg0mGUT0N1zUjer50DsY= +github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= +github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= +github.com/libp2p/go-libp2p-peerstore v0.1.3 h1:wMgajt1uM2tMiqf4M+4qWKVyyFc8SfA+84VV9glZq1M= +github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.1.4 h1:d23fvq5oYMJ/lkkbO4oTwBp/JP+I/1m5gZJobNXCE/k= +github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= +github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= +github.com/libp2p/go-libp2p-secio v0.2.0 h1:ywzZBsWEEz2KNTn5RtzauEDq5RFEefPsttXYwAWqHng= +github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= +github.com/libp2p/go-libp2p-secio v0.2.1 h1:eNWbJTdyPA7NxhP7J3c5lT97DC5d+u+IldkgCYFTPVA= +github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= +github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= +github.com/libp2p/go-libp2p-swarm v0.2.2 h1:T4hUpgEs2r371PweU3DuH7EOmBIdTBCwWs+FLcgx3bQ= +github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= +github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.1.0 h1:WaFRj/t3HdMZGNZqnU2pS7pDRBmMeoDx7/HDNpeyT9U= +github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5W5jAOPNmU= +github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1 h1:PZMS9lhjK9VytzMCW3tWHAXtKXmlURSc3ZdvwEcKCzw= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= +github.com/libp2p/go-libp2p-yamux v0.2.0 h1:TSPZ5cMMz/wdoYsye/wU1TE4G3LDGMoeEN0xgnCKU/I= +github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.1 h1:Q3XYNiKCC2vIxrvUJL+Jg1kiyeEaIDNKLjgEjo3VQdI= +github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= +github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= +github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= +github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= +github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= +github.com/libp2p/go-mplex v0.1.0 h1:/nBTy5+1yRyY82YaO6HXQRnO5IAGsXTjEJaR3LdTPc0= +github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= +github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA= +github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-nat v0.0.4 h1:KbizNnq8YIf7+Hn7+VFL/xE0eDrkPru2zIO9NMwL8UQ= +github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= +github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= +github.com/libp2p/go-openssl v0.0.3 h1:wjlG7HvQkt4Fq4cfH33Ivpwp0omaElYEi9z26qaIkIk= +github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg= +github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= +github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= +github.com/libp2p/go-reuseport-transport v0.0.2 h1:WglMwyXyBu61CMkjCCtnmqNqnjib0GIEjMiHTwR/KN4= +github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= +github.com/libp2p/go-stream-muxer v0.0.1 h1:Ce6e2Pyu+b5MC1k3eeFtAax0pW4gc6MosYSLV05UeLw= +github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= +github.com/libp2p/go-stream-muxer-multistream v0.2.0 h1:714bRJ4Zy9mdhyTLJ+ZKiROmAFwUHpeRidG+q7LTQOg= +github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= +github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= +github.com/libp2p/go-tcp-transport v0.1.1 h1:yGlqURmqgNA2fvzjSgZNlHcsd/IulAnKM8Ncu+vlqnw= +github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= +github.com/libp2p/go-ws-transport v0.2.0 h1:MJCw2OrPA9+76YNRvdo1wMnSOxb9Bivj6sVFY1Xrj6w= +github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= +github.com/libp2p/go-yamux v1.2.2 h1:s6J6o7+ajoQMjHe7BEnq+EynOj5D2EoG8CuQgL3F2vg= +github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.2.3 h1:xX8A36vpXb59frIzWFdEgptLMsOANMFq2K7fPRlunYI= +github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/meshplus/bitxhub-kit v0.0.0-20200329124935-585edb85ca63 h1:WDsTW19L/BEaMukcTV2VQXinXhP2n3Y5nyTKeNPC5BI= +github.com/meshplus/bitxhub-kit v0.0.0-20200329124935-585edb85ca63/go.mod h1:ra/AhOkPvpElI+wXrB9G6DjdcrdxFU3vMwA5MYKr9D0= +github.com/meshplus/bitxhub-model v0.0.0-20200329125320-a37501c7a13e h1:066IcST8gbjy4mlAYbxAi0QZ26rMZ0Wl67g3Q04+7Os= +github.com/meshplus/bitxhub-model v0.0.0-20200329125320-a37501c7a13e/go.mod h1:mJ6ucVyePaAojmQ8Y1NAS4PoZzCNxC/Q6EHC18HyMvw= +github.com/meshplus/go-bitxhub-client v0.0.0-20200329130007-318d6f4c1087 h1:j2O1vvQi6qc6+Q8db5u8GJPHY1ddDIdpjWKK9HFAOhM= +github.com/meshplus/go-bitxhub-client v0.0.0-20200329130007-318d6f4c1087/go.mod h1:u/rFcS6BbMDpgDlC0C8b8oLPKWdBDoOlt6OuACqJVc0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16 h1:5W7KhL8HVF3XCFOweFD3BNESdnO8ewyYTFT2R+/b8FQ= +github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78= +github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-multiaddr v0.0.1 h1:/QUV3VBMDI6pi6xfiw7lr6xhDWWvQKn9udPn68kLSdY= +github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.1 h1:rVAztJYMhCQ7vEFr8FvxW3mS+HF2eY/oPbOMeS0ZDnE= +github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= +github.com/multiformats/go-multiaddr v0.2.0 h1:lR52sFwcTCuQb6bTfnXF6zA2XfyYvyd+5a9qECv/J90= +github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.2 h1:/Bbsgsy3R6e3jf2qBahzNHzww6usYaZ0NhNH3sqdFS8= +github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA= +github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= +github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= +github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= +github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multiaddr-net v0.0.1 h1:76O59E3FavvHqNg7jvzWzsPSW5JSi/ek0E4eiDVbg9g= +github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= +github.com/multiformats/go-multiaddr-net v0.1.0 h1:ZepO8Ezwovd+7b5XPPDhQhayk1yt0AJpzQBpq9fejx4= +github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.1 h1:jFFKUuXTXv+3ARyHZi3XUqQO+YWMKgBdhEvuGRfnL6s= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= +github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= +github.com/multiformats/go-multihash v0.0.1 h1:HHwN1K12I+XllBCrqKnhX949Orn4oawPkegHMu2vDqQ= +github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= +github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= +github.com/multiformats/go-multihash v0.0.8 h1:wrYcW5yxSi3dU07n5jnuS5PrNwyHy0zRHGVoUugWvXg= +github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multihash v0.0.10 h1:lMoNbh2Ssd9PUF74Nz008KGzGPlfeV6wH3rit5IIGCM= +github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= +github.com/multiformats/go-multistream v0.1.0 h1:UpO6jrsjqs46mqAK3n6wKRYFhugss9ArzbyUzU+4wkQ= +github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= +github.com/multiformats/go-varint v0.0.1 h1:TR/0rdQtnNxuN2IhiB639xC3tWM4IUi7DkTBVTdGW/M= +github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= +github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= +github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8= +github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.10 h1:QJQN3jYQhkamO4mhfUWqdDH2asK7ONOI9MTWjyAxNKM= +github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0 h1:g0fH8RicVgNl+zVZDCDfbdWxAWoAEJyI7I3TZYXFiig= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/sykesm/zap-logfmt v0.0.3 h1:3Wrhf7+I9JEUD8B6KPtDAr9j2jrS0/EPLy7GCE1t/+U= +github.com/sykesm/zap-logfmt v0.0.3/go.mod h1:AuBd9xQjAe3URrWT1BBDk2v2onAZHkZkWRMiYZXiZWA= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= +github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= +github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= +github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= +github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8= +github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o= +github.com/tendermint/tendermint v0.32.1 h1:J8ddXMbCmG6GZjdCl/N1wgdXDU9uO91J2Y5CA9xYfGo= +github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU= +github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0= +github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= +github.com/tidwall/gjson v1.3.5 h1:2oW9FBNu8qt9jy5URgrzsVx/T/KSn3qn/smJQ0crlDQ= +github.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= +github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= +github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc h1:9lDbC6Rz4bwmou+oE6Dt4Cb2BGMur5eR/GYptkKUVHo= +github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= +github.com/whyrusleeping/go-logging v0.0.1 h1:fwpzlmT0kRC/Fmd0MdmGgJG/CXIZ6gFq46FQZjprUcc= +github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= +github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA= +github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= +github.com/wonderivan/logger v1.0.0 h1:Z6Nz+3SNcizolx3ARH11axdD4DXjFpb2J+ziGUVlv/U= +github.com/wonderivan/logger v1.0.0/go.mod h1:NObMfQ3WOLKfYEZuGeZQfuQfSPE5+QNgRddVMzsAT/k= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= +go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b h1:+/WWzjwW6gidDJnMKWLKLX1gxn7irUTF1fLpQovfQ5M= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190227160552-c95aed5357e7 h1:C2F/nMkR/9sfUTpvR3QrjBuTdvMUC/cFajkphs1YLQo= +golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 h1:4dVFTC832rPn4pomLSz1vA+are2+dU19w1H8OngV7nc= +golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219092855-153ac476189d h1:Z0Ahzd7HltpJtjAHHxX8QFP3j1yYgiuvjbjRzDj/KH0= +golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88 h1:LNVdAhESTW4gWDhYvciNcGoS9CEcxRiUKE9kSgw+X3s= +golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c h1:KfpJVdWhuRqNk4XVXzjXf2KAV4TBEP77SYdFGjeGuIE= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 h1:jB9+PJSvu5tBfmJHy/OVapFdjDF3WvpkqRhxqrmzoEU= +google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.0 h1:ItERT+UbGdX+s4u+nQNlVM/Q7cbmf7icKfvzbWqVtq0= +google.golang.org/grpc v1.25.0/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= +gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/app/bitxhub.go b/internal/app/bitxhub.go new file mode 100644 index 0000000..71ec87b --- /dev/null +++ b/internal/app/bitxhub.go @@ -0,0 +1,189 @@ +package app + +import ( + "context" + "fmt" + "time" + + "github.com/meshplus/bitxhub/pkg/order" + + "github.com/common-nighthawk/go-figure" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub/internal/executor" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/ledger/genesis" + "github.com/meshplus/bitxhub/internal/loggers" + orderplg "github.com/meshplus/bitxhub/internal/plugins" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/internal/router" + "github.com/meshplus/bitxhub/internal/storages" + "github.com/meshplus/bitxhub/pkg/peermgr" + "github.com/sirupsen/logrus" +) + +type BitXHub struct { + Ledger ledger.Ledger + Executor executor.Executor + Router router.Router + Order order.Order + PeerMgr peermgr.PeerManager + + repo *repo.Repo + logger logrus.FieldLogger + + ctx context.Context + cancel context.CancelFunc +} + +func NewBitXHub(rep *repo.Repo) (*BitXHub, error) { + repoRoot := rep.Config.RepoRoot + logger := loggers.Logger(loggers.App) + + if err := storages.Initialize(repoRoot); err != nil { + return nil, fmt.Errorf("storages initialize: %w", err) + } + + bcStorage, err := storages.Get(storages.BlockChain) + if err != nil { + return nil, fmt.Errorf("create blockchain storage: %w", err) + } + + // 0. load ledger + ldg, err := ledger.New(repoRoot, bcStorage, loggers.Logger(loggers.Executor)) + if err != nil { + return nil, fmt.Errorf("create ledger: %w", err) + } + + if ldg.GetChainMeta().Height == 0 { + if err := genesis.Initialize(rep.Config, ldg); err != nil { + return nil, err + } + logger.Info("Initialize genesis") + } + + // 1. create executor + exec, err := executor.New(ldg, loggers.Logger(loggers.Executor)) + if err != nil { + return nil, fmt.Errorf("create BlockExecutor: %w", err) + } + + chainMeta := ldg.GetChainMeta() + + peerMgr, err := peermgr.New(rep, loggers.Logger(loggers.P2P), ldg) + if err != nil { + return nil, fmt.Errorf("create peer manager: %w", err) + } + + m := make(map[uint64]types.Address) + + if !rep.Config.Solo { + for i, node := range rep.NetworkConfig.Nodes { + m[node.ID] = types.String2Address(rep.Config.Genesis.Addresses[i]) + } + } + order, err := orderplg.New( + order.WithRepoRoot(repoRoot), + order.WithStoragePath(repo.GetStoragePath(repoRoot, "order")), + order.WithPluginPath(rep.Config.Plugin), + order.WithNodes(m), + order.WithID(rep.NetworkConfig.ID), + order.WithPeerManager(peerMgr), + order.WithPrivKey(nil), + order.WithLogger(loggers.Logger(loggers.Order)), + order.WithApplied(chainMeta.Height), + order.WithDigest(chainMeta.BlockHash.Hex()), + order.WithGetChainMetaFunc(ldg.GetChainMeta), + order.WithGetTransactionFunc(ldg.GetTransaction), + ) + if err != nil { + return nil, err + } + + r, err := router.New(loggers.Logger(loggers.Router), rep, ldg, peerMgr, order.Quorum()) + if err != nil { + return nil, fmt.Errorf("create InterchainRouter: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + return &BitXHub{ + repo: rep, + logger: logger, + Ledger: ldg, + Executor: exec, + Router: r, + Order: order, + PeerMgr: peerMgr, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (bxh *BitXHub) Start() error { + if !bxh.repo.Config.Solo { + if err := bxh.PeerMgr.Start(); err != nil { + return fmt.Errorf("peer manager start: %w", err) + } + } + + if err := bxh.Order.Start(); err != nil { + return fmt.Errorf("order start: %w", err) + } + + if err := bxh.Executor.Start(); err != nil { + return fmt.Errorf("executor start: %w", err) + } + + if err := bxh.Router.Start(); err != nil { + return fmt.Errorf("router start: %w", err) + } + + bxh.start() + + bxh.printLogo() + + return nil +} + +func (bxh *BitXHub) Stop() error { + if err := bxh.Executor.Stop(); err != nil { + return fmt.Errorf("executor stop: %w", err) + } + + if err := bxh.Router.Stop(); err != nil { + return fmt.Errorf("InterchainRouter stop: %w", err) + } + + if !bxh.repo.Config.Solo { + if err := bxh.PeerMgr.Stop(); err != nil { + return fmt.Errorf("network stop: %w", err) + } + } + + bxh.Order.Stop() + + bxh.cancel() + + bxh.logger.Info("Bitxhub stopped") + + return nil +} + +func (bxh *BitXHub) printLogo() { + for { + time.Sleep(100 * time.Millisecond) + if bxh.Order.Ready() { + bxh.logger.WithFields(logrus.Fields{ + "plugin_path": bxh.repo.Config.Order.Plugin, + }).Info("Order is ready") + fmt.Println() + fmt.Println("=======================================================") + fig := figure.NewFigure("BitXHub", "slant", true) + fig.Print() + fmt.Println() + fmt.Println("=======================================================") + fmt.Println() + return + } + } +} diff --git a/internal/app/feedhub.go b/internal/app/feedhub.go new file mode 100644 index 0000000..fddf511 --- /dev/null +++ b/internal/app/feedhub.go @@ -0,0 +1,53 @@ +package app + +import ( + "context" + + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/sirupsen/logrus" +) + +func (bxh *BitXHub) start() { + go bxh.listenEvent() + + go func() { + for { + select { + case block := <-bxh.Order.Commit(): + bxh.logger.WithFields(logrus.Fields{ + "height": block.BlockHeader.Number, + "count": len(block.Transactions), + }).Info("Generate block") + bxh.Executor.ExecuteBlock(block) + case <-bxh.ctx.Done(): + return + } + } + }() +} + +func (bxh *BitXHub) listenEvent() { + blockCh := make(chan events.NewBlockEvent) + orderMsgCh := make(chan events.OrderMessageEvent) + blockSub := bxh.Executor.SubscribeBlockEvent(blockCh) + orderMsgSub := bxh.PeerMgr.SubscribeOrderMessage(orderMsgCh) + + defer blockSub.Unsubscribe() + defer orderMsgSub.Unsubscribe() + + for { + select { + case ev := <-blockCh: + go bxh.Order.ReportState(ev.Block.BlockHeader.Number, ev.Block.BlockHash) + go bxh.Router.PutBlock(ev.Block) + case ev := <-orderMsgCh: + go func() { + if err := bxh.Order.Step(context.Background(), ev.Data); err != nil { + bxh.logger.Error(err) + } + }() + case <-bxh.ctx.Done(): + return + } + } +} diff --git a/internal/constant/constant.go b/internal/constant/constant.go new file mode 100644 index 0000000..8b934bf --- /dev/null +++ b/internal/constant/constant.go @@ -0,0 +1,20 @@ +package constant + +import "github.com/meshplus/bitxhub-kit/types" + +type BoltContractAddress string + +const ( + InterchainContractAddr BoltContractAddress = "0x000000000000000000000000000000000000000a" + StoreContractAddr BoltContractAddress = "0x000000000000000000000000000000000000000b" + RuleManagerContractAddr BoltContractAddress = "0x000000000000000000000000000000000000000c" + RoleContractAddr BoltContractAddress = "0x000000000000000000000000000000000000000d" +) + +func (addr BoltContractAddress) Address() types.Address { + return types.String2Address(string(addr)) +} + +func (addr BoltContractAddress) String() string { + return string(addr) +} diff --git a/internal/coreapi/account.go b/internal/coreapi/account.go new file mode 100644 index 0000000..b05a1b2 --- /dev/null +++ b/internal/coreapi/account.go @@ -0,0 +1,15 @@ +package coreapi + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/ledger" +) + +type AccountAPI CoreAPI + +var _ api.AccountAPI = (*AccountAPI)(nil) + +func (api *AccountAPI) GetAccount(addr types.Address) *ledger.Account { + return api.bxh.Ledger.GetAccount(addr) +} diff --git a/internal/coreapi/api/api.go b/internal/coreapi/api/api.go new file mode 100644 index 0000000..0ea9eda --- /dev/null +++ b/internal/coreapi/api/api.go @@ -0,0 +1,55 @@ +package api + +import ( + "github.com/ethereum/go-ethereum/event" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/model/events" +) + +//go:generate mockgen -destination mock_api/mock_api.go -package mock_api -source api.go +type CoreAPI interface { + Broker() BrokerAPI + Network() NetworkAPI + Chain() ChainAPI + Feed() FeedAPI + Account() AccountAPI +} + +type BrokerAPI interface { + HandleTransaction(tx *pb.Transaction) error + GetTransaction(types.Hash) (*pb.Transaction, error) + GetTransactionMeta(types.Hash) (*pb.TransactionMeta, error) + GetReceipt(types.Hash) (*pb.Receipt, error) + GetBlock(mode string, key string) (*pb.Block, error) + GetBlocks(offset uint64, length uint64) ([]*pb.Block, error) + + // AddPier + AddPier(pid string) (chan *pb.MerkleWrapper, error) + + // RemovePier + RemovePier(pid string) + + GetMerkleWrapper(pid string, begin, end uint64, ch chan<- *pb.MerkleWrapper) error + + // OrderReady + OrderReady() bool +} + +type NetworkAPI interface { + PeerInfo() ([]byte, error) +} + +type ChainAPI interface { + Status() string + Meta() (*pb.ChainMeta, error) +} + +type FeedAPI interface { + SubscribeNewBlockEvent(chan<- events.NewBlockEvent) event.Subscription +} + +type AccountAPI interface { + GetAccount(addr types.Address) *ledger.Account +} diff --git a/internal/coreapi/broker.go b/internal/coreapi/broker.go new file mode 100644 index 0000000..d065f96 --- /dev/null +++ b/internal/coreapi/broker.go @@ -0,0 +1,88 @@ +package coreapi + +import ( + "fmt" + "strconv" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/sirupsen/logrus" +) + +type BrokerAPI CoreAPI + +var _ api.BrokerAPI = (*BrokerAPI)(nil) + +func (b *BrokerAPI) HandleTransaction(tx *pb.Transaction) error { + b.logger.WithFields(logrus.Fields{ + "hash": tx.TransactionHash.String(), + }).Debugf("receive tx") + + go func() { + if err := b.bxh.Order.Prepare(tx); err != nil { + b.logger.Error(err) + } + }() + + return nil +} + +func (b *BrokerAPI) GetTransaction(hash types.Hash) (*pb.Transaction, error) { + return b.bxh.Ledger.GetTransaction(hash) +} + +func (b *BrokerAPI) GetTransactionMeta(hash types.Hash) (*pb.TransactionMeta, error) { + return b.bxh.Ledger.GetTransactionMeta(hash) +} + +func (b *BrokerAPI) GetReceipt(hash types.Hash) (*pb.Receipt, error) { + return b.bxh.Ledger.GetReceipt(hash) +} + +func (b *BrokerAPI) AddPier(key string) (chan *pb.MerkleWrapper, error) { + return b.bxh.Router.AddPier(key) +} + +func (b *BrokerAPI) GetMerkleWrapper(pid string, begin, end uint64, ch chan<- *pb.MerkleWrapper) error { + return b.bxh.Router.GetMerkleWrapper(pid, begin, end, ch) +} + +func (b *BrokerAPI) GetBlock(mode string, value string) (*pb.Block, error) { + switch mode { + case "HEIGHT": + height, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return nil, fmt.Errorf("wrong block number: %s", value) + } + return b.bxh.Ledger.GetBlock(height) + case "HASH": + return b.bxh.Ledger.GetBlockByHash(types.String2Hash(value)) + default: + return nil, fmt.Errorf("wrong args about getting block: %s", mode) + } +} + +func (b *BrokerAPI) GetBlocks(offset uint64, length uint64) ([]*pb.Block, error) { + meta := b.bxh.Ledger.GetChainMeta() + + var blocks []*pb.Block + for i := meta.Height - offset; i > 0 && length > 0; i-- { + length-- + b, err := b.GetBlock("HEIGHT", strconv.Itoa(int(i))) + if err != nil { + continue + } + blocks = append(blocks, b) + } + + return blocks, nil +} + +func (b *BrokerAPI) RemovePier(key string) { + b.bxh.Router.RemovePier(key) +} + +func (b *BrokerAPI) OrderReady() bool { + return b.bxh.Order.Ready() +} diff --git a/internal/coreapi/chain.go b/internal/coreapi/chain.go new file mode 100644 index 0000000..b83f15e --- /dev/null +++ b/internal/coreapi/chain.go @@ -0,0 +1,22 @@ +package coreapi + +import ( + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/coreapi/api" +) + +type ChainAPI CoreAPI + +var _ api.ChainAPI = (*ChainAPI)(nil) + +func (api *ChainAPI) Status() string { + if api.bxh.Order.Ready() { + return "normal" + } + + return "abnormal" +} + +func (api *ChainAPI) Meta() (*pb.ChainMeta, error) { + return api.bxh.Ledger.GetChainMeta(), nil +} diff --git a/internal/coreapi/coreapi.go b/internal/coreapi/coreapi.go new file mode 100644 index 0000000..76e5990 --- /dev/null +++ b/internal/coreapi/coreapi.go @@ -0,0 +1,42 @@ +package coreapi + +import ( + "github.com/meshplus/bitxhub/internal/app" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/loggers" + "github.com/sirupsen/logrus" +) + +var _ api.CoreAPI = (*CoreAPI)(nil) + +type CoreAPI struct { + bxh *app.BitXHub + logger logrus.FieldLogger +} + +func New(bxh *app.BitXHub) (*CoreAPI, error) { + return &CoreAPI{ + bxh: bxh, + logger: loggers.Logger(loggers.CoreAPI), + }, nil +} + +func (api *CoreAPI) Account() api.AccountAPI { + return (*AccountAPI)(api) +} + +func (api *CoreAPI) Broker() api.BrokerAPI { + return (*BrokerAPI)(api) +} + +func (api *CoreAPI) Network() api.NetworkAPI { + return (*NetworkAPI)(api) +} + +func (api *CoreAPI) Chain() api.ChainAPI { + return (*ChainAPI)(api) +} + +func (api *CoreAPI) Feed() api.FeedAPI { + return (*FeedAPI)(api) +} diff --git a/internal/coreapi/feed.go b/internal/coreapi/feed.go new file mode 100644 index 0000000..58a1edf --- /dev/null +++ b/internal/coreapi/feed.go @@ -0,0 +1,15 @@ +package coreapi + +import ( + "github.com/ethereum/go-ethereum/event" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/model/events" +) + +type FeedAPI CoreAPI + +var _ api.FeedAPI = (*FeedAPI)(nil) + +func (api *FeedAPI) SubscribeNewBlockEvent(ch chan<- events.NewBlockEvent) event.Subscription { + return api.bxh.Executor.SubscribeBlockEvent(ch) +} diff --git a/internal/coreapi/network.go b/internal/coreapi/network.go new file mode 100644 index 0000000..fe6f681 --- /dev/null +++ b/internal/coreapi/network.go @@ -0,0 +1,23 @@ +package coreapi + +import ( + "encoding/json" + + "github.com/meshplus/bitxhub/internal/coreapi/api" +) + +type NetworkAPI CoreAPI + +var _ api.NetworkAPI = (*NetworkAPI)(nil) + +// PeerInfo collects the peers' info in p2p network. +func (network *NetworkAPI) PeerInfo() ([]byte, error) { + peerInfo := network.bxh.PeerMgr.Peers() + + data, err := json.Marshal(peerInfo) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/internal/executor/context.go b/internal/executor/context.go new file mode 100644 index 0000000..eb34ad0 --- /dev/null +++ b/internal/executor/context.go @@ -0,0 +1,9 @@ +package executor + +func (exec *BlockExecutor) isDemandNumber(num uint64) bool { + return exec.currentHeight+1 == num +} + +func (exec *BlockExecutor) getDemandNumber() uint64 { + return exec.currentHeight + 1 +} diff --git a/internal/executor/contracts/interchain.go b/internal/executor/contracts/interchain.go new file mode 100755 index 0000000..3cf516f --- /dev/null +++ b/internal/executor/contracts/interchain.go @@ -0,0 +1,358 @@ +package contracts + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/meshplus/bitxhub/internal/constant" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/vm/boltvm" + "github.com/sirupsen/logrus" +) + +const ( + prefix = "appchain-" + + registered = 0 + approved = 1 +) + +type Interchain struct { + boltvm.Stub +} + +type appchain struct { + ID string `json:"id"` + Name string `json:"name"` + Validators string `json:"validators"` + ConsensusType int32 `json:"consensus_type"` + // 0 => registered, 1 => approved, -1 => rejected + Status int32 `json:"status"` + ChainType string `json:"chain_type"` + Desc string `json:"desc"` + Version string `json:"version"` + InterchainCounter map[string]uint64 `json:"interchain_counter,omitempty"` + ReceiptCounter map[string]uint64 `json:"receipt_counter,omitempty"` + SourceReceiptCounter map[string]uint64 `json:"source_receipt_counter,omitempty"` +} + +type auditRecord struct { + Appchain *appchain `json:"appchain"` + IsApproved bool `json:"is_approved"` + Desc string `json:"desc"` +} + +func (chain *appchain) UnmarshalJSON(data []byte) error { + type alias appchain + t := &alias{} + if err := json.Unmarshal(data, t); err != nil { + return err + } + + if t.InterchainCounter == nil { + t.InterchainCounter = make(map[string]uint64) + } + + if t.ReceiptCounter == nil { + t.ReceiptCounter = make(map[string]uint64) + } + + if t.SourceReceiptCounter == nil { + t.SourceReceiptCounter = make(map[string]uint64) + } + + *chain = appchain(*t) + return nil +} + +// Register appchain manager registers appchain info caller is the appchain +// manager address return appchain id and error +func (x *Interchain) Register(validators string, consensusType int32, chainType, name, desc, version string) *boltvm.Response { + chain := &appchain{ + ID: x.Caller(), + Name: name, + Validators: validators, + ConsensusType: consensusType, + ChainType: chainType, + Desc: desc, + Version: version, + } + + ok := x.Has(x.appchainKey(x.Caller())) + if ok { + x.Stub.Logger().WithFields(logrus.Fields{ + "id": x.Caller(), + }).Debug("Appchain has registered") + x.GetObject(x.appchainKey(x.Caller()), chain) + } else { + // logger.Info(x.Caller()) + x.SetObject(x.appchainKey(x.Caller()), chain) + x.Logger().WithFields(logrus.Fields{ + "id": x.Caller(), + }).Info("Appchain register successfully") + } + body, err := json.Marshal(chain) + if err != nil { + return boltvm.Error(err.Error()) + } + + return boltvm.Success(body) +} + +func (x *Interchain) UpdateAppchain(validators string, consensusType int32, chainType, name, desc, version string) *boltvm.Response { + ok := x.Has(x.appchainKey(x.Caller())) + if !ok { + return boltvm.Error("register appchain firstly") + } + + chain := &appchain{} + x.GetObject(x.appchainKey(x.Caller()), chain) + + if chain.Status == registered { + return boltvm.Error("this appchain is being audited") + } + + chain = &appchain{ + ID: x.Caller(), + Name: name, + Validators: validators, + ConsensusType: consensusType, + ChainType: chainType, + Desc: desc, + Version: version, + } + + x.SetObject(x.appchainKey(x.Caller()), chain) + + return boltvm.Success(nil) +} + +// Audit bitxhub manager audit appchain register info +// caller is the bitxhub manager address +// proposer is the appchain manager address +func (x *Interchain) Audit(proposer string, isApproved int32, desc string) *boltvm.Response { + ret := x.CrossInvoke(constant.RoleContractAddr.String(), "IsAdmin", pb.String(x.Caller())) + is, err := strconv.ParseBool(string(ret.Result)) + if err != nil { + return boltvm.Error(fmt.Errorf("judge caller type: %w", err).Error()) + } + + if !is { + return boltvm.Error("caller is not an admin account") + } + + chain := &appchain{} + ok := x.GetObject(x.appchainKey(proposer), chain) + if !ok { + return boltvm.Error(fmt.Errorf("this appchain does not exist").Error()) + } + + chain.Status = isApproved + + record := &auditRecord{ + Appchain: chain, + IsApproved: isApproved == approved, + Desc: desc, + } + + var records []*auditRecord + x.GetObject(x.auditRecordKey(proposer), &records) + records = append(records, record) + + x.SetObject(x.auditRecordKey(proposer), records) + x.SetObject(x.appchainKey(proposer), chain) + + return boltvm.Success([]byte(fmt.Sprintf("audit %s successfully", proposer))) +} + +func (x *Interchain) FetchAuditRecords(id string) *boltvm.Response { + var records []*auditRecord + x.GetObject(x.auditRecordKey(id), &records) + + body, err := json.Marshal(records) + if err != nil { + return boltvm.Error(err.Error()) + } + + return boltvm.Success(body) +} + +// CountApprovedAppchains counts all approved appchains +func (x *Interchain) CountApprovedAppchains() *boltvm.Response { + ok, value := x.Query(prefix) + if !ok { + return boltvm.Success([]byte("0")) + } + + count := 0 + for _, v := range value { + a := &appchain{} + if err := json.Unmarshal(v, a); err != nil { + return boltvm.Error(fmt.Sprintf("unmarshal json error: %v", err)) + } + if a.Status == approved { + count++ + } + } + + return boltvm.Success([]byte(strconv.Itoa(count))) +} + +// CountAppchains counts all appchains including approved, rejected or registered +func (x *Interchain) CountAppchains() *boltvm.Response { + ok, value := x.Query(prefix) + if !ok { + return boltvm.Success([]byte("0")) + } + + return boltvm.Success([]byte(strconv.Itoa(len(value)))) +} + +// Appchains returns all appchains +func (x *Interchain) Appchains() *boltvm.Response { + ok, value := x.Query(prefix) + if !ok { + return boltvm.Success(nil) + } + + ret := make([]*appchain, 0) + for _, data := range value { + chain := &appchain{} + if err := json.Unmarshal(data, chain); err != nil { + return boltvm.Error(err.Error()) + } + ret = append(ret, chain) + } + + data, err := json.Marshal(ret) + if err != nil { + return boltvm.Error(err.Error()) + } + + return boltvm.Success(data) +} + +func (x *Interchain) DeleteAppchain(cid string) *boltvm.Response { + x.Delete(prefix + cid) + x.Logger().Infof("delete appchain:%s", cid) + + return boltvm.Success(nil) +} + +func (x *Interchain) Appchain() *boltvm.Response { + ok, data := x.Get(x.appchainKey(x.Caller())) + if !ok { + return boltvm.Error(fmt.Errorf("this appchain does not exist").Error()) + } + + return boltvm.Success(data) +} + +func (x *Interchain) HandleIBTP(data []byte) *boltvm.Response { + ok := x.Has(x.appchainKey(x.Caller())) + if !ok { + return boltvm.Error("this appchain does not exist") + } + + ibtp := &pb.IBTP{} + if err := ibtp.Unmarshal(data); err != nil { + return boltvm.Error(err.Error()) + } + + if ibtp.To == "" { + return boltvm.Error("target appchain is empty") + } + + ok = x.Has(x.appchainKey(ibtp.To)) + if !ok { + return boltvm.Error("target appchain does not exist") + } + + app := &appchain{} + x.GetObject(x.appchainKey(ibtp.From), &app) + + // get validation rule contract address + res := x.CrossInvoke(constant.RuleManagerContractAddr.String(), "GetRuleAddress", pb.String(ibtp.From)) + if !res.Ok { + return boltvm.Error("this appchain don't register rule") + } + + // handle validation + isValid, err := x.Validator().Verify(string(res.Result), ibtp.From, ibtp.Proof, app.Validators) + if err != nil { + return boltvm.Error(err.Error()) + } + + if !isValid { + return boltvm.Error("invalid interchain transaction") + } + + switch ibtp.Type { + case pb.IBTP_INTERCHAIN: + if ibtp.From != x.Caller() { + return boltvm.Error("ibtp from != caller") + } + + idx := app.InterchainCounter[ibtp.To] + if idx+1 != ibtp.Index { + return boltvm.Error(fmt.Sprintf("wrong index, required %d, but %d", idx+1, ibtp.Index)) + } + + app.InterchainCounter[ibtp.To]++ + x.SetObject(x.appchainKey(ibtp.From), app) + x.SetObject(x.indexMapKey(ibtp.ID()), x.GetTxHash()) + m := make(map[string]uint64) + m[ibtp.To] = x.GetTxIndex() + x.PostInterchainEvent(m) + case pb.IBTP_RECEIPT: + if ibtp.To != x.Caller() { + return boltvm.Error("ibtp from != caller") + } + + idx := app.ReceiptCounter[ibtp.To] + if idx+1 != ibtp.Index { + if app.SourceReceiptCounter[ibtp.To]+1 != ibtp.Index { + return boltvm.Error(fmt.Sprintf("wrong receipt index, required %d, but %d", idx+1, ibtp.Index)) + } + } + + app.ReceiptCounter[ibtp.To] = ibtp.Index + x.SetObject(x.appchainKey(ibtp.From), app) + m := make(map[string]uint64) + m[ibtp.From] = x.GetTxIndex() + + ac := &appchain{} + x.GetObject(x.appchainKey(ibtp.To), &ac) + ac.SourceReceiptCounter[ibtp.From] = ibtp.Index + x.SetObject(x.appchainKey(ibtp.To), ac) + + x.PostInterchainEvent(m) + } + + return boltvm.Success(nil) +} + +func (x *Interchain) GetIBTPByID(id string) *boltvm.Response { + var hash types.Hash + exist := x.GetObject(x.indexMapKey(id), &hash) + if !exist { + return boltvm.Error("this id is not existed") + } + + return boltvm.Success(hash.Bytes()) +} + +func (x *Interchain) appchainKey(id string) string { + return prefix + id +} + +func (x *Interchain) auditRecordKey(id string) string { + return "audit-record-" + id +} + +func (x *Interchain) indexMapKey(id string) string { + return fmt.Sprintf("index-tx-%s", id) +} diff --git a/internal/executor/contracts/role.go b/internal/executor/contracts/role.go new file mode 100644 index 0000000..36f5001 --- /dev/null +++ b/internal/executor/contracts/role.go @@ -0,0 +1,71 @@ +package contracts + +import ( + "encoding/json" + "strconv" + + "github.com/meshplus/bitxhub/internal/constant" + + "github.com/meshplus/bitxhub/pkg/vm/boltvm" +) + +const ( + adminRolesKey = "admin-roles" +) + +type Role struct { + boltvm.Stub +} + +func (r *Role) GetRole() *boltvm.Response { + var addrs []string + r.GetObject(adminRolesKey, &addrs) + + for _, addr := range addrs { + if addr == r.Caller() { + return boltvm.Success([]byte("admin")) + } + } + + res := r.CrossInvoke(constant.InterchainContractAddr.String(), "Appchain") + if !res.Ok { + return boltvm.Success([]byte("none")) + } + + return boltvm.Success([]byte("appchain_admin")) +} + +func (r *Role) IsAdmin(address string) *boltvm.Response { + var addrs []string + r.GetObject(adminRolesKey, &addrs) + + for _, addr := range addrs { + if addr == address { + return boltvm.Success([]byte(strconv.FormatBool(true))) + } + } + + return boltvm.Success([]byte(strconv.FormatBool(false))) +} + +func (r *Role) GetAdminRoles() *boltvm.Response { + var addrs []string + r.GetObject(adminRolesKey, &addrs) + + ret, err := json.Marshal(addrs) + if err != nil { + return boltvm.Error(err.Error()) + } + + return boltvm.Success(ret) +} + +func (r *Role) SetAdminRoles(addrs string) *boltvm.Response { + as := make([]string, 0) + if err := json.Unmarshal([]byte(addrs), &as); err != nil { + return boltvm.Error(err.Error()) + } + + r.SetObject(adminRolesKey, as) + return boltvm.Success(nil) +} diff --git a/internal/executor/contracts/rule_manager.go b/internal/executor/contracts/rule_manager.go new file mode 100644 index 0000000..2044e3c --- /dev/null +++ b/internal/executor/contracts/rule_manager.go @@ -0,0 +1,80 @@ +package contracts + +import ( + "fmt" + + "github.com/meshplus/bitxhub/pkg/vm/boltvm" +) + +const ( + rulePrefix = "rule-" +) + +// RuleManager is the contract manage validation rules +type RuleManager struct { + boltvm.Stub +} + +type rule struct { + Address string `json:"address"` + Status int32 `json:"status"` // 0 => registered, 1 => approved, -1 => rejected +} + +type ruleRecord struct { + Rule *rule `json:"rule"` + IsApproved bool `json:"is_approved"` + Desc string `json:"desc"` +} + +// SetRule can map the validation rule address with the chain id +func (r *RuleManager) RegisterRule(id string, address string) *boltvm.Response { + rl := &rule{ + Address: address, + Status: 0, + } + r.SetObject(r.ruleKey(id), rl) + return boltvm.Success(nil) +} + +func (r *RuleManager) GetRuleAddress(id string) *boltvm.Response { + rl := &rule{} + + ok := r.GetObject(r.ruleKey(id), rl) + if ok { + return boltvm.Success([]byte(rl.Address)) + } + + return boltvm.Error("") +} + +func (r *RuleManager) Audit(id string, isApproved int32, desc string) *boltvm.Response { + rl := &rule{} + ok := r.GetObject(r.ruleKey(id), rl) + if !ok { + return boltvm.Error(fmt.Errorf("this rule does not exist").Error()) + } + rl.Status = isApproved + + record := &ruleRecord{ + Rule: rl, + IsApproved: isApproved == approved, + Desc: desc, + } + + var records []*ruleRecord + r.GetObject(r.ruleRecordKey(id), &records) + records = append(records, record) + + r.SetObject(r.ruleRecordKey(id), records) + r.SetObject(r.ruleKey(id), rl) + + return boltvm.Success(nil) +} + +func (r *RuleManager) ruleKey(id string) string { + return rulePrefix + id +} + +func (r *RuleManager) ruleRecordKey(id string) string { + return "audit-record-" + id +} diff --git a/internal/executor/contracts/store.go b/internal/executor/contracts/store.go new file mode 100644 index 0000000..50c0571 --- /dev/null +++ b/internal/executor/contracts/store.go @@ -0,0 +1,25 @@ +package contracts + +import ( + "github.com/meshplus/bitxhub/pkg/vm/boltvm" +) + +type Store struct { + boltvm.Stub +} + +func (s *Store) Set(key string, value string) *boltvm.Response { + s.SetObject(key, value) + + return boltvm.Success(nil) +} + +func (s *Store) Get(key string) *boltvm.Response { + var v string + ok := s.Stub.GetObject(key, &v) + if !ok { + return boltvm.Error("there is not exist key") + } + + return boltvm.Success([]byte(v)) +} diff --git a/internal/executor/executor.go b/internal/executor/executor.go new file mode 100755 index 0000000..e7c22ae --- /dev/null +++ b/internal/executor/executor.go @@ -0,0 +1,143 @@ +package executor + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/event" + "github.com/meshplus/bitxhub-kit/cache" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/constant" + "github.com/meshplus/bitxhub/internal/executor/contracts" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/meshplus/bitxhub/internal/validator" + "github.com/meshplus/bitxhub/pkg/vm/boltvm" + "github.com/sirupsen/logrus" +) + +const blockChanNumber = 1024 + +var _ Executor = (*BlockExecutor)(nil) + +// BlockExecutor executes block from order +type BlockExecutor struct { + ledger ledger.Ledger + logger logrus.FieldLogger + blockC chan *pb.Block + pendingBlockQ *cache.Cache + interchainCounter map[string][]uint64 + validator validator.Validator + currentHeight uint64 + currentBlockHash types.Hash + + blockFeed event.Feed + + ctx context.Context + cancel context.CancelFunc +} + +// New creates executor instance +func New(ledger ledger.Ledger, logger logrus.FieldLogger) (*BlockExecutor, error) { + pendingBlockQ, err := cache.NewCache() + if err != nil { + return nil, fmt.Errorf("create cache: %w", err) + } + + vlt := validator.NewWasmValidator(ledger, logger) + + registerBoltContracts() + + ctx, cancel := context.WithCancel(context.Background()) + + return &BlockExecutor{ + ledger: ledger, + logger: logger, + interchainCounter: make(map[string][]uint64), + ctx: ctx, + cancel: cancel, + blockC: make(chan *pb.Block, blockChanNumber), + pendingBlockQ: pendingBlockQ, + validator: vlt, + currentHeight: ledger.GetChainMeta().Height, + currentBlockHash: ledger.GetChainMeta().BlockHash, + }, nil +} + +// Start starts executor +func (exec *BlockExecutor) Start() error { + go exec.listenExecuteEvent() + + exec.logger.WithFields(logrus.Fields{ + "height": exec.currentHeight, + "hash": exec.currentBlockHash.ShortString(), + }).Infof("Executor started") + + return nil +} + +// Stop stops executor +func (exec *BlockExecutor) Stop() error { + exec.cancel() + + exec.logger.Info("Executor stopped") + + return nil +} + +// ExecuteBlock executes block from order +func (exec *BlockExecutor) ExecuteBlock(block *pb.Block) { + exec.blockC <- block +} + +func (exec *BlockExecutor) SyncExecuteBlock(block *pb.Block) { + exec.handleExecuteEvent(block) +} + +// SubscribeBlockEvent registers a subscription of NewBlockEvent. +func (exec *BlockExecutor) SubscribeBlockEvent(ch chan<- events.NewBlockEvent) event.Subscription { + return exec.blockFeed.Subscribe(ch) +} + +func (exec *BlockExecutor) listenExecuteEvent() { + for { + select { + case block := <-exec.blockC: + exec.handleExecuteEvent(block) + case <-exec.ctx.Done(): + return + } + } +} + +func registerBoltContracts() { + boltContracts := []*boltvm.BoltContract{ + { + Enabled: true, + Name: "appchain manager contract", + Address: constant.InterchainContractAddr.String(), + Contract: &contracts.Interchain{}, + }, + { + Enabled: true, + Name: "store service", + Address: constant.StoreContractAddr.String(), + Contract: &contracts.Store{}, + }, + { + Enabled: true, + Name: "rule manager service", + Address: constant.RuleManagerContractAddr.String(), + Contract: &contracts.RuleManager{}, + }, + { + Enabled: true, + Name: "role manager service", + Address: constant.RoleContractAddr.String(), + Contract: &contracts.Role{}, + }, + } + + boltvm.Register(boltContracts) +} diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go new file mode 100644 index 0000000..1d91907 --- /dev/null +++ b/internal/executor/executor_test.go @@ -0,0 +1,285 @@ +package executor + +import ( + "encoding/json" + "io/ioutil" + "math/rand" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/meshplus/bitxhub-kit/crypto" + + "github.com/meshplus/bitxhub-kit/key" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/stretchr/testify/require" + + "github.com/gogo/protobuf/proto" + "github.com/golang/mock/gomock" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger/mock_ledger" + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/stretchr/testify/assert" +) + +const ( + keyPassword = "bitxhub" + from = "0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997" +) + +func TestBlockExecutor_ExecuteBlock(t *testing.T) { + mockCtl := gomock.NewController(t) + mockLedger := mock_ledger.NewMockLedger(mockCtl) + + // mock data for ledger + chainMeta := &pb.ChainMeta{ + Height: 1, + BlockHash: types.String2Hash(from), + } + + evs := make([]*pb.Event, 0) + m := make(map[string]uint64) + m[from] = 3 + data, err := json.Marshal(m) + assert.Nil(t, err) + ev := &pb.Event{ + TxHash: types.String2Hash(from), + Data: data, + Interchain: true, + } + evs = append(evs, ev) + mockLedger.EXPECT().GetChainMeta().Return(chainMeta).AnyTimes() + mockLedger.EXPECT().Events(gomock.Any()).Return(evs).AnyTimes() + mockLedger.EXPECT().Commit().Return(types.String2Hash(from), nil).AnyTimes() + mockLedger.EXPECT().Clear().AnyTimes() + mockLedger.EXPECT().GetState(gomock.Any(), gomock.Any()).Return(true, []byte("10")).AnyTimes() + mockLedger.EXPECT().SetState(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockLedger.EXPECT().GetBalance(gomock.Any()).Return(uint64(10)).AnyTimes() + mockLedger.EXPECT().SetBalance(gomock.Any(), gomock.Any()).AnyTimes() + mockLedger.EXPECT().SetNonce(gomock.Any(), gomock.Any()).AnyTimes() + mockLedger.EXPECT().GetNonce(gomock.Any()).Return(uint64(0)).AnyTimes() + mockLedger.EXPECT().SetCode(gomock.Any(), gomock.Any()).AnyTimes() + mockLedger.EXPECT().GetCode(gomock.Any()).Return([]byte("10")).AnyTimes() + mockLedger.EXPECT().PersistExecutionResult(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + logger := log.NewWithModule("executor") + + exec, err := New(mockLedger, logger) + assert.Nil(t, err) + + // mock data for block + var txs []*pb.Transaction + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + assert.Nil(t, err) + pubKey := privKey.PublicKey() + + // set tx of TransactionData_BVM type + ibtp1 := mockIBTP(t, 1, pb.IBTP_INTERCHAIN) + BVMData := mockTxData(t, pb.TransactionData_INVOKE, pb.TransactionData_BVM, ibtp1) + BVMTx := mockTx(BVMData) + txs = append(txs, BVMTx) + // set tx of TransactionData_XVM type + ibtp2 := mockIBTP(t, 2, pb.IBTP_INTERCHAIN) + XVMData := mockTxData(t, pb.TransactionData_INVOKE, pb.TransactionData_XVM, ibtp2) + XVMTx := mockTx(XVMData) + txs = append(txs, XVMTx) + // set tx of TransactionData_NORMAL type + ibtp3 := mockIBTP(t, 3, pb.IBTP_INTERCHAIN) + NormalData := mockTxData(t, pb.TransactionData_NORMAL, pb.TransactionData_XVM, ibtp3) + NormalTx := mockTx(NormalData) + txs = append(txs, NormalTx) + + // set signature for txs + for _, tx := range txs { + sig, err := privKey.Sign(tx.SignHash().Bytes()) + assert.Nil(t, err) + tx.Signature = sig + tx.From, err = pubKey.Address() + assert.Nil(t, err) + } + // set invalid signature tx + invalidTx := mockTx(nil) + invalidTx.Signature = []byte("invalid") + invalidTx.From = types.String2Address(from) + txs = append(txs, invalidTx) + + assert.Nil(t, exec.Start()) + + // send blocks to executor + block2 := mockBlock(uint64(2), txs) + block4 := mockBlock(uint64(4), txs) + block3 := mockBlock(uint64(3), txs) + block6 := mockBlock(uint64(6), txs) + exec.ExecuteBlock(block2) + exec.ExecuteBlock(block4) + exec.ExecuteBlock(block6) + exec.ExecuteBlock(block3) + + done := make(chan bool) + ch := make(chan events.NewBlockEvent) + blockSub := exec.SubscribeBlockEvent(ch) + defer blockSub.Unsubscribe() + + // count received block to end test + var wg sync.WaitGroup + wg.Add(3) + go listenBlock(&wg, done, ch) + + wg.Wait() + done <- true + assert.Nil(t, exec.Stop()) + assert.Equal(t, 2, exec.pendingBlockQ.Len()) + assert.Equal(t, uint64(5), exec.getDemandNumber()) +} + +func listenBlock(wg *sync.WaitGroup, done chan bool, blockCh chan events.NewBlockEvent) { + for { + select { + case <-blockCh: + wg.Done() + case <-done: + return + } + } +} + +func mockBlock(blockNumber uint64, txs []*pb.Transaction) *pb.Block { + header := &pb.BlockHeader{ + Number: blockNumber, + Timestamp: time.Now().UnixNano(), + } + return &pb.Block{ + BlockHeader: header, + Transactions: txs, + } +} + +func mockTx(data *pb.TransactionData) *pb.Transaction { + return &pb.Transaction{ + Data: data, + Nonce: rand.Int63(), + } +} + +func TestBlockExecutor_ExecuteBlock_Transfer(t *testing.T) { + repoRoot, err := ioutil.TempDir("", "executor") + require.Nil(t, err) + + blockchainStorage, err := leveldb.New(filepath.Join(repoRoot, "storage")) + require.Nil(t, err) + + ledger, err := ledger.New(repoRoot, blockchainStorage, log.NewWithModule("ledger")) + require.Nil(t, err) + + _, from := loadAdminKey(t) + + ledger.SetBalance(from, 100000000) + _, err = ledger.Commit() + require.Nil(t, err) + err = ledger.PersistExecutionResult(mockBlock(1, nil), nil) + require.Nil(t, err) + + executor, err := New(ledger, log.NewWithModule("executor")) + require.Nil(t, err) + err = executor.Start() + require.Nil(t, err) + + var txs []*pb.Transaction + txs = append(txs, mockTransferTx(t)) + txs = append(txs, mockTransferTx(t)) + txs = append(txs, mockTransferTx(t)) + executor.ExecuteBlock(mockBlock(2, txs)) + require.Nil(t, err) + + ch := make(chan events.NewBlockEvent) + sub := executor.SubscribeBlockEvent(ch) + defer sub.Unsubscribe() + + block := <-ch + require.EqualValues(t, 2, block.Block.Height()) + require.EqualValues(t, 99999997, ledger.GetBalance(from)) +} + +func mockTransferTx(t *testing.T) *pb.Transaction { + privKey, from := loadAdminKey(t) + to := randAddress(t) + + tx := &pb.Transaction{ + From: from, + To: to, + Timestamp: time.Now().UnixNano(), + Data: &pb.TransactionData{ + Type: pb.TransactionData_NORMAL, + Amount: 1, + }, + Nonce: rand.Int63(), + } + + err := tx.Sign(privKey) + require.Nil(t, err) + tx.TransactionHash = tx.Hash() + + return tx +} + +func loadAdminKey(t *testing.T) (crypto.PrivateKey, types.Address) { + k, err := key.LoadKey(filepath.Join("testdata", "key.json")) + require.Nil(t, err) + + privKey, err := k.GetPrivateKey(keyPassword) + require.Nil(t, err) + + from, err := privKey.PublicKey().Address() + require.Nil(t, err) + + return privKey, from +} + +func randAddress(t *testing.T) types.Address { + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + require.Nil(t, err) + address, err := privKey.PublicKey().Address() + require.Nil(t, err) + + return address +} + +func mockTxData(t *testing.T, dataType pb.TransactionData_Type, vmType pb.TransactionData_VMType, ibtp proto.Marshaler) *pb.TransactionData { + ib, err := ibtp.Marshal() + assert.Nil(t, err) + + tmpIP := &pb.InvokePayload{ + Method: "set", + Args: []*pb.Arg{{Value: ib}}, + } + pd, err := tmpIP.Marshal() + assert.Nil(t, err) + + return &pb.TransactionData{ + VmType: vmType, + Type: dataType, + Amount: 10, + Payload: pd, + } +} + +func mockIBTP(t *testing.T, index uint64, typ pb.IBTP_Type) *pb.IBTP { + ibtppd, err := json.Marshal(pb.Payload{ + FID: from, + TID: from, + Func: "set", + }) + assert.Nil(t, err) + return &pb.IBTP{ + From: from, + To: from, + Payload: ibtppd, + Index: index, + Type: typ, + Timestamp: time.Now().UnixNano(), + } +} diff --git a/internal/executor/handle.go b/internal/executor/handle.go new file mode 100755 index 0000000..c0ed40c --- /dev/null +++ b/internal/executor/handle.go @@ -0,0 +1,323 @@ +package executor + +import ( + "encoding/json" + "fmt" + "sort" + "sync" + "time" + + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/merkle/merkletree" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/meshplus/bitxhub/pkg/vm" + "github.com/meshplus/bitxhub/pkg/vm/boltvm" + "github.com/meshplus/bitxhub/pkg/vm/wasm" + "github.com/sirupsen/logrus" +) + +func (exec *BlockExecutor) handleExecuteEvent(block *pb.Block) { + if !exec.isDemandNumber(block.BlockHeader.Number) { + exec.addPendingExecuteEvent(block) + return + } + exec.processExecuteEvent(block) + exec.handlePendingExecuteEvent() +} + +func (exec *BlockExecutor) addPendingExecuteEvent(block *pb.Block) { + exec.logger.WithFields(logrus.Fields{ + "received": block.BlockHeader.Number, + "required": exec.currentHeight + 1, + }).Warnf("Save wrong block into cache") + + exec.pendingBlockQ.Add(block.BlockHeader.Number, block) +} + +func (exec *BlockExecutor) fetchPendingExecuteEvent(num uint64) *pb.Block { + res, ok := exec.pendingBlockQ.Get(num) + if !ok { + return nil + } + + return res.(*pb.Block) +} + +func (exec *BlockExecutor) processExecuteEvent(block *pb.Block) { + exec.logger.WithFields(logrus.Fields{ + "height": block.BlockHeader.Number, + "count": len(block.Transactions), + }).Infof("Execute block") + + validTxs, invalidReceipts := exec.verifySign(block) + receipts := exec.applyTransactions(validTxs) + + root, receiptRoot, err := exec.calcMerkleRoots(block.Transactions, append(receipts, invalidReceipts...)) + if err != nil { + panic(err) + } + + block.BlockHeader.TxRoot = root + block.BlockHeader.ReceiptRoot = receiptRoot + block.BlockHeader.ParentHash = exec.currentBlockHash + idx, err := json.Marshal(exec.interchainCounter) + if err != nil { + panic(err) + } + + block.BlockHeader.InterchainIndex = idx + hash, err := exec.ledger.Commit() + if err != nil { + panic(err) + } + block.BlockHeader.StateRoot = hash + block.BlockHash = block.Hash() + + exec.logger.WithFields(logrus.Fields{ + "tx_root": block.BlockHeader.TxRoot.ShortString(), + "receipt_root": block.BlockHeader.ReceiptRoot.ShortString(), + "state_root": block.BlockHeader.StateRoot.ShortString(), + }).Debug("block meta") + + // persist execution result + receipts = append(receipts, invalidReceipts...) + if err := exec.ledger.PersistExecutionResult(block, receipts); err != nil { + panic(err) + } + + exec.logger.WithFields(logrus.Fields{ + "height": block.BlockHeader.Number, + "hash": block.BlockHash.ShortString(), + "count": len(block.Transactions), + }).Info("Persist block") + + exec.postBlockEvent(block) + exec.clear() + + exec.currentHeight = block.BlockHeader.Number + exec.currentBlockHash = block.BlockHash +} + +func (exec *BlockExecutor) verifySign(block *pb.Block) ([]*pb.Transaction, []*pb.Receipt) { + if block.BlockHeader.Number == 1 { + return block.Transactions, nil + } + + txs := block.Transactions + + var ( + wg sync.WaitGroup + receipts []*pb.Receipt + mutex sync.Mutex + index []int + ) + + receiptsM := make(map[int]*pb.Receipt) + + wg.Add(len(txs)) + for i, tx := range txs { + go func(i int, tx *pb.Transaction) { + defer wg.Done() + ok, _ := asym.Verify(asym.ECDSASecp256r1, tx.Signature, tx.SignHash().Bytes(), tx.From) + mutex.Lock() + defer mutex.Unlock() + if !ok { + receiptsM[i] = &pb.Receipt{ + Version: tx.Version, + TxHash: tx.TransactionHash, + Ret: []byte("invalid signature"), + Status: pb.Receipt_FAILED, + } + + index = append(index, i) + } + }(i, tx) + + } + + wg.Wait() + + if len(index) > 0 { + sort.Ints(index) + count := 0 + for _, idx := range index { + receipts = append(receipts, receiptsM[idx]) + idx -= count + txs = append(txs[:idx], txs[idx+1:]...) + count++ + + } + } + + return txs, receipts +} + +func (exec *BlockExecutor) applyTransactions(txs []*pb.Transaction) []*pb.Receipt { + current := time.Now() + receipts := make([]*pb.Receipt, 0, len(txs)) + + for i, tx := range txs { + receipt := &pb.Receipt{ + Version: tx.Version, + TxHash: tx.TransactionHash, + } + + ret, err := exec.applyTransaction(i, tx) + if err != nil { + receipt.Status = pb.Receipt_FAILED + receipt.Ret = []byte(err.Error()) + } else { + receipt.Status = pb.Receipt_SUCCESS + receipt.Ret = ret + } + + events := exec.ledger.Events(tx.TransactionHash.Hex()) + if len(events) != 0 { + receipt.Events = events + for _, ev := range events { + if ev.Interchain { + m := make(map[string]uint64) + err := json.Unmarshal(ev.Data, &m) + if err != nil { + panic(err) + } + + for k, v := range m { + exec.interchainCounter[k] = append(exec.interchainCounter[k], v) + } + } + } + } + + receipts = append(receipts, receipt) + } + + exec.logger.WithFields(logrus.Fields{ + "time": time.Since(current), + "count": len(txs), + }).Debug("Apply transactions elapsed") + + return receipts +} + +func (exec *BlockExecutor) postBlockEvent(block *pb.Block) { + go exec.blockFeed.Send(events.NewBlockEvent{Block: block}) + +} + +func (exec *BlockExecutor) handlePendingExecuteEvent() { + if exec.pendingBlockQ.Len() > 0 { + for exec.pendingBlockQ.Contains(exec.getDemandNumber()) { + block := exec.fetchPendingExecuteEvent(exec.getDemandNumber()) + exec.processExecuteEvent(block) + } + } +} + +func (exec *BlockExecutor) applyTransaction(i int, tx *pb.Transaction) ([]byte, error) { + if tx.Data == nil { + return nil, fmt.Errorf("empty transaction data") + } + + switch tx.Data.Type { + case pb.TransactionData_NORMAL: + err := exec.transfer(tx.From, tx.To, tx.Data.Amount) + return nil, err + default: + var instance vm.VM + switch tx.Data.VmType { + case pb.TransactionData_BVM: + ctx := vm.NewContext(tx, uint64(i), tx.Data, exec.ledger, exec.logger) + instance = boltvm.New(ctx, exec.validator) + case pb.TransactionData_XVM: + ctx := vm.NewContext(tx, uint64(i), tx.Data, exec.ledger, exec.logger) + var err error + instance, err = wasm.New(ctx) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("wrong vm type") + } + + return instance.Run(tx.Data.Payload) + } +} + +func (exec *BlockExecutor) calcMerkleRoot(txs []*pb.Transaction) (types.Hash, error) { + if len(txs) == 0 { + return types.Hash{}, nil + } + + hashes := make([]interface{}, 0, len(txs)) + for _, tx := range txs { + hashes = append(hashes, pb.TransactionHash(tx.TransactionHash.Bytes())) + } + tree := merkletree.NewMerkleTree() + err := tree.InitMerkleTree(hashes) + if err != nil { + return types.Hash{}, err + } + + return types.Bytes2Hash(tree.GetMerkleRoot()), nil +} + +func (exec *BlockExecutor) calcReceiptMerkleRoot(receipts []*pb.Receipt) (types.Hash, error) { + if len(receipts) == 0 { + return types.Hash{}, nil + } + + hashes := make([]interface{}, 0, len(receipts)) + for _, receipt := range receipts { + hashes = append(hashes, pb.TransactionHash(receipt.Hash().Bytes())) + } + tree := merkletree.NewMerkleTree() + err := tree.InitMerkleTree(hashes) + if err != nil { + return types.Hash{}, err + } + + return types.Bytes2Hash(tree.GetMerkleRoot()), nil +} + +func (exec *BlockExecutor) clear() { + exec.interchainCounter = make(map[string][]uint64) + exec.ledger.Clear() +} + +func (exec *BlockExecutor) transfer(from, to types.Address, value uint64) error { + if value == 0 { + return nil + } + + fv := exec.ledger.GetBalance(from) + if fv < value { + return fmt.Errorf("not sufficient funds for %s", from.Hex()) + } + + tv := exec.ledger.GetBalance(to) + + exec.ledger.SetBalance(from, fv-value) + exec.ledger.SetBalance(to, tv+value) + + return nil +} + +func (exec *BlockExecutor) calcMerkleRoots(txs []*pb.Transaction, receipts []*pb.Receipt) (types.Hash, types.Hash, error) { + current := time.Now() + root, err := exec.calcMerkleRoot(txs) + if err != nil { + return types.Hash{}, types.Hash{}, err + } + + receiptRoot, err := exec.calcReceiptMerkleRoot(receipts) + if err != nil { + return types.Hash{}, types.Hash{}, err + } + + exec.logger.WithField("time", time.Since(current)).Debug("calculate merkle roots") + + return root, receiptRoot, nil +} diff --git a/internal/executor/testdata/key.json b/internal/executor/testdata/key.json new file mode 100755 index 0000000..8356c90 --- /dev/null +++ b/internal/executor/testdata/key.json @@ -0,0 +1,5 @@ +{ + "address": "0xba30d0dd7876318da4515826a1f8bee8cefc9061", + "private_key": "3592c476850c4007385fe4084039d29bcc160652f5a41e83bb89ebdd1432ece4746545ffbdb7269a9df8aad2a6e33f97", + "encrypted": true +} \ No newline at end of file diff --git a/internal/executor/types.go b/internal/executor/types.go new file mode 100644 index 0000000..eeb3d05 --- /dev/null +++ b/internal/executor/types.go @@ -0,0 +1,21 @@ +package executor + +import ( + "github.com/ethereum/go-ethereum/event" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/model/events" +) + +type Executor interface { + // Start + Start() error + + // Stop + Stop() error + + // ExecutorBlock + ExecuteBlock(*pb.Block) + + // SubscribeBlockEvent + SubscribeBlockEvent(chan<- events.NewBlockEvent) event.Subscription +} diff --git a/internal/ledger/account.go b/internal/ledger/account.go new file mode 100644 index 0000000..74b1a43 --- /dev/null +++ b/internal/ledger/account.go @@ -0,0 +1,153 @@ +package ledger + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/tendermint/iavl" + db "github.com/tendermint/tm-db" +) + +type Account struct { + Nonce uint64 `json:"nonce"` + Balance uint64 `json:"balance"` + Version int64 `json:"version"` + StateRoot types.Hash `json:"merkle_root"` + CodeHash []byte `json:"code_hash"` + + tree *iavl.MutableTree + code []byte +} + +func newAccount(l *ChainLedger, ldb db.DB, addr types.Address) *Account { + account := &Account{} + _, data := l.tree.Get(compositeKey(accountKey, addr.Hex())) + if data != nil { + if err := account.Unmarshal(data); err != nil { + panic(err) + } + } + + p := []byte(fmt.Sprintf("account-%s", addr.Hex())) + tree := iavl.NewMutableTree(db.NewPrefixDB(ldb, p), defaultIAVLCacheSize) + _, err := tree.Load() + if err != nil { + panic(err) + } + account.tree = tree + + return account +} + +// GetState Get state from the tree +func (o *Account) GetState(key []byte) (bool, []byte) { + _, v := o.tree.Get(key) + + return v != nil, v +} + +// SetState Set account state +func (o *Account) SetState(key []byte, value []byte) { + o.tree.Set(key, value) +} + +// SetCodeAndHash Set the contract code and hash +func (o *Account) SetCodeAndHash(code []byte) { + ret := sha256.Sum256(code) + o.CodeHash = ret[:] + o.SetState(o.CodeHash, code) + o.code = code +} + +// Code return the contract code +func (o *Account) Code() []byte { + if o.code != nil { + return o.code + } + + if bytes.Equal(o.CodeHash, nil) { + return nil + } + + ok, code := o.GetState(o.CodeHash) + if !ok { + return nil + } + o.code = code + + return code +} + +// SetNonce Set the nonce which indicates the contract number +func (o *Account) SetNonce(nonce uint64) { + o.Nonce = nonce +} + +// GetNonce Get the nonce from user account +func (o *Account) GetNonce() uint64 { + return o.Nonce +} + +// SetStateRoot Set the state root hash +func (o *Account) SetStateRoot(hash types.Hash) { + o.StateRoot = hash +} + +// Query Query the value using key +func (o *Account) Query(prefix string) (bool, [][]byte) { + var ret [][]byte + begin, end := bytesPrefix([]byte(prefix)) + o.tree.IterateRange(begin, end, false, func(key []byte, value []byte) bool { + ret = append(ret, value) + return false + }) + + return len(ret) != 0, ret +} + +// Commit Commit the result +func (o *Account) Commit() (types.Hash, error) { + hash, ver, err := o.tree.SaveVersion() + if err != nil { + return types.Hash{}, err + } + + o.Version = ver + + return types.Bytes2Hash(hash), nil +} + +// Marshal Marshal the account into byte +func (o *Account) Marshal() ([]byte, error) { + obj := &Account{ + Nonce: o.Nonce, + Balance: o.Balance, + CodeHash: o.CodeHash, + Version: o.Version, + StateRoot: o.StateRoot, + } + + return json.Marshal(obj) +} + +// Unmarshal Unmarshal the account byte into structure +func (o *Account) Unmarshal(data []byte) error { + return json.Unmarshal(data, o) +} + +func bytesPrefix(prefix []byte) ([]byte, []byte) { + var limit []byte + for i := len(prefix) - 1; i >= 0; i-- { + c := prefix[i] + if c < 0xff { + limit = make([]byte, i+1) + copy(limit, prefix) + limit[i] = c + 1 + break + } + } + return prefix, limit +} diff --git a/internal/ledger/account_test.go b/internal/ledger/account_test.go new file mode 100644 index 0000000..c7724ec --- /dev/null +++ b/internal/ledger/account_test.go @@ -0,0 +1,85 @@ +package ledger + +import ( + "io/ioutil" + "testing" + + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/stretchr/testify/require" + + "github.com/meshplus/bitxhub-kit/bytesutil" + "github.com/meshplus/bitxhub-kit/hexutil" + "github.com/meshplus/bitxhub-kit/types" + "github.com/stretchr/testify/assert" + db "github.com/tendermint/tm-db" +) + +func TestAccount_GetState(t *testing.T) { + _, err := asym.GenerateKey(asym.ECDSASecp256r1) + assert.Nil(t, err) + repoRoot, err := ioutil.TempDir("", "ledger_commit") + assert.Nil(t, err) + blockStorage, err := leveldb.New(repoRoot) + assert.Nil(t, err) + ledger, err := New(repoRoot, blockStorage, log.NewWithModule("ChainLedger")) + assert.Nil(t, err) + + dir, err := ioutil.TempDir("", "") + require.Nil(t, err) + ldb, err := db.NewGoLevelDB("ledger", dir) + assert.Nil(t, err) + + h := hexutil.Encode(bytesutil.LeftPadBytes([]byte{11}, 20)) + addr := types.String2Address(h) + account := newAccount(ledger, ldb, addr) + + account.SetState([]byte("a"), []byte("b")) + ok, v := account.GetState([]byte("a")) + assert.True(t, ok) + assert.Equal(t, []byte("b"), v) + + // save into db + hash, err := account.Commit() + assert.Nil(t, err) + assert.Equal(t, "0x07babb4c717e4c854558e806f6c0c82344009b234f1733b638c14730f625e8d1", hash.Hex()) + + // recreate account + account2 := newAccount(ledger, ldb, addr) + ok2, v2 := account2.GetState([]byte("a")) + assert.True(t, ok2) + assert.Equal(t, []byte("b"), v2) +} + +func TestAccount_Commit(t *testing.T) { + _, err := asym.GenerateKey(asym.ECDSASecp256r1) + assert.Nil(t, err) + repoRoot, err := ioutil.TempDir("", "ledger_commit") + assert.Nil(t, err) + blockStorage, err := leveldb.New(repoRoot) + assert.Nil(t, err) + ledger, err := New(repoRoot, blockStorage, log.NewWithModule("ChainLedger")) + assert.Nil(t, err) + + dir, err := ioutil.TempDir("", "") + require.Nil(t, err) + ldb, err := db.NewGoLevelDB("ledger", dir) + assert.Nil(t, err) + + h := hexutil.Encode(bytesutil.LeftPadBytes([]byte{11}, 20)) + addr := types.String2Address(h) + account := newAccount(ledger, ldb, addr) + + account.SetState([]byte("alice"), []byte("bob")) + + // save into db + hash, err := account.Commit() + assert.Nil(t, err) + assert.Equal(t, "0x73f56a5593a5ab27d7db1c91bd1c78d26ebd3c5a235a226428f57d4abaa49fab", hash.Hex()) + + account.SetState([]byte("a"), []byte("b")) + hash, err = account.Commit() + assert.Nil(t, err) + assert.Equal(t, "0x8ebbcd9523cf21e5b284325542f8e7dcf588f3cddd2e94342a2acdbfb9dd3358", hash.Hex()) +} diff --git a/internal/ledger/blockchain.go b/internal/ledger/blockchain.go new file mode 100644 index 0000000..7518f04 --- /dev/null +++ b/internal/ledger/blockchain.go @@ -0,0 +1,273 @@ +package ledger + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/meshplus/bitxhub/pkg/storage" + + "github.com/gogo/protobuf/proto" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +// PutBlock put block into store +func (l *ChainLedger) PutBlock(height uint64, block *pb.Block) error { + data, err := block.Marshal() + if err != nil { + return err + } + + return l.blockchainStore.Put(compositeKey(blockKey, height), data) +} + +// GetBlock get block with height +func (l *ChainLedger) GetBlock(height uint64) (*pb.Block, error) { + data, err := l.blockchainStore.Get(compositeKey(blockKey, height)) + if err != nil { + return nil, err + } + + block := &pb.Block{} + if err = block.Unmarshal(data); err != nil { + return nil, err + } + + return block, nil +} + +// GetBlockSign get the signature of block +func (l *ChainLedger) GetBlockSign(height uint64) ([]byte, error) { + block, err := l.GetBlock(height) + if err != nil { + return nil, err + } + + return block.Signature, nil +} + +// GetBlockByHash get the block using block hash +func (l *ChainLedger) GetBlockByHash(hash types.Hash) (*pb.Block, error) { + data, err := l.blockchainStore.Get(compositeKey(blockHashKey, hash.Hex())) + if err != nil { + return nil, err + } + + height, err := strconv.Atoi(string(data)) + if err != nil { + return nil, fmt.Errorf("wrong height, %w", err) + } + + v, err := l.blockchainStore.Get(compositeKey(blockKey, height)) + if err != nil { + return nil, fmt.Errorf("get block: %w", err) + } + + block := &pb.Block{} + if err := block.Unmarshal(v); err != nil { + return nil, fmt.Errorf("unmarshal block: %w", err) + } + + return block, nil +} + +// GetTransaction get the transaction using transaction hash +func (l *ChainLedger) GetTransaction(hash types.Hash) (*pb.Transaction, error) { + v, err := l.blockchainStore.Get(compositeKey(transactionKey, hash.Hex())) + if err != nil { + return nil, err + } + tx := &pb.Transaction{} + err = proto.Unmarshal(v, tx) + if err != nil { + return nil, err + } + + return tx, nil +} + +// GetTransactionMeta get the transaction meta data +func (l *ChainLedger) GetTransactionMeta(hash types.Hash) (*pb.TransactionMeta, error) { + data, err := l.blockchainStore.Get(compositeKey(transactionMetaKey, hash.Hex())) + if err != nil { + return nil, err + } + + meta := &pb.TransactionMeta{} + if err := meta.Unmarshal(data); err != nil { + return nil, err + } + + return meta, nil +} + +// GetReceipt get the transaction receipt +func (l *ChainLedger) GetReceipt(hash types.Hash) (*pb.Receipt, error) { + data, err := l.blockchainStore.Get(compositeKey(receiptKey, hash.Hex())) + if err != nil { + return nil, err + } + + r := &pb.Receipt{} + if err := r.Unmarshal(data); err != nil { + return nil, err + } + + return r, nil +} + +// PersistExecutionResult persist the execution result +func (l *ChainLedger) PersistExecutionResult(block *pb.Block, receipts []*pb.Receipt) error { + current := time.Now() + + if block == nil { + return fmt.Errorf("empty block data") + } + + batcher := l.blockchainStore.NewBatch() + + if err := l.persistReceipts(batcher, receipts); err != nil { + return err + } + + if err := l.persistTransactions(batcher, block); err != nil { + return err + } + + if err := l.persistBlock(batcher, block); err != nil { + return err + } + + // update chain meta in cache + count, err := getInterchainTxCount(block.BlockHeader) + if err != nil { + return err + } + + meta := &pb.ChainMeta{ + Height: block.BlockHeader.Number, + BlockHash: block.BlockHash, + InterchainTxCount: count + l.chainMeta.InterchainTxCount, + } + + if err := l.persistChainMeta(batcher, meta); err != nil { + return err + } + + if err := batcher.Commit(); err != nil { + return err + } + + l.UpdateChainMeta(meta) + + l.logger.WithField("time", time.Since(current)).Debug("persist execution result elapsed") + + return nil +} + +// UpdateChainMeta update the chain meta data +func (l *ChainLedger) UpdateChainMeta(meta *pb.ChainMeta) { + l.chainMutex.Lock() + defer l.chainMutex.Unlock() + l.chainMeta.Height = meta.Height + l.chainMeta.BlockHash = meta.BlockHash + l.chainMeta.InterchainTxCount = meta.InterchainTxCount +} + +// GetChainMeta get chain meta data +func (l *ChainLedger) GetChainMeta() *pb.ChainMeta { + l.chainMutex.RLock() + defer l.chainMutex.RUnlock() + + return &pb.ChainMeta{ + Height: l.chainMeta.Height, + BlockHash: l.chainMeta.BlockHash, + InterchainTxCount: l.chainMeta.InterchainTxCount, + } +} + +func getInterchainTxCount(header *pb.BlockHeader) (uint64, error) { + if header.InterchainIndex == nil { + return 0, nil + } + + txCount := make(map[string][]uint64) + err := json.Unmarshal(header.InterchainIndex, &txCount) + if err != nil { + return 0, fmt.Errorf("get interchain tx count: %w", err) + } + + var ret uint64 + for _, v := range txCount { + ret += uint64(len(v)) + } + + return ret, nil +} + +func (l *ChainLedger) persistReceipts(batcher storage.Batch, receipts []*pb.Receipt) error { + for _, receipt := range receipts { + data, err := receipt.Marshal() + if err != nil { + return err + } + + batcher.Put(compositeKey(receiptKey, receipt.TxHash.Hex()), data) + } + + return nil +} + +func (l *ChainLedger) persistTransactions(batcher storage.Batch, block *pb.Block) error { + for i, tx := range block.Transactions { + body, err := tx.Marshal() + if err != nil { + return err + } + + batcher.Put(compositeKey(transactionKey, tx.TransactionHash.Hex()), body) + + meta := &pb.TransactionMeta{ + BlockHeight: block.BlockHeader.Number, + BlockHash: block.BlockHash.Bytes(), + Index: uint64(i), + } + + bs, err := meta.Marshal() + if err != nil { + return fmt.Errorf("marshal tx meta error: %s", err) + } + + batcher.Put(compositeKey(transactionMetaKey, tx.TransactionHash.Hex()), bs) + } + + return nil +} + +func (l *ChainLedger) persistBlock(batcher storage.Batch, block *pb.Block) error { + bs, err := block.Marshal() + if err != nil { + return err + } + + height := block.BlockHeader.Number + batcher.Put(compositeKey(blockKey, height), bs) + + hash := block.BlockHash.Hex() + batcher.Put(compositeKey(blockHashKey, hash), []byte(fmt.Sprintf("%d", height))) + + return nil +} + +func (l *ChainLedger) persistChainMeta(batcher storage.Batch, meta *pb.ChainMeta) error { + data, err := meta.Marshal() + if err != nil { + return err + } + + batcher.Put([]byte(chainMetaKey), data) + + return nil +} diff --git a/internal/ledger/chain_meta.go b/internal/ledger/chain_meta.go new file mode 100644 index 0000000..d5611a9 --- /dev/null +++ b/internal/ledger/chain_meta.go @@ -0,0 +1,33 @@ +package ledger + +import ( + "fmt" + + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/storage" +) + +var ( + chainKey = []byte("chain-meta") +) + +func loadChainMeta(store storage.Storage) (*pb.ChainMeta, error) { + ok, err := store.Has(chainKey) + if err != nil { + return nil, fmt.Errorf("judge chain meta: %w", err) + } + + chain := &pb.ChainMeta{} + if ok { + body, err := store.Get(chainKey) + if err != nil { + return nil, fmt.Errorf("get chain meta: %w", err) + } + + if err := chain.Unmarshal(body); err != nil { + return nil, fmt.Errorf("unmarshal chain meta: %w", err) + } + } + + return chain, nil +} diff --git a/internal/ledger/genesis/genesis.go b/internal/ledger/genesis/genesis.go new file mode 100644 index 0000000..2f97d93 --- /dev/null +++ b/internal/ledger/genesis/genesis.go @@ -0,0 +1,46 @@ +package genesis + +import ( + "encoding/json" + + "github.com/meshplus/bitxhub-model/pb" + + "github.com/meshplus/bitxhub-kit/bytesutil" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/repo" +) + +var ( + roleAddr = types.Bytes2Address(bytesutil.LeftPadBytes([]byte{13}, 20)) +) + +// Initialize initialize block +func Initialize(config *repo.Config, ledger ledger.Ledger) error { + for _, addr := range config.Addresses { + ledger.SetBalance(types.String2Address(addr), 100000000) + } + + body, err := json.Marshal(config.Genesis.Addresses) + if err != nil { + return err + } + + ledger.SetState(roleAddr, []byte("admin-roles"), body) + + hash, err := ledger.Commit() + if err != nil { + return err + } + + block := &pb.Block{ + BlockHeader: &pb.BlockHeader{ + Number: 1, + StateRoot: hash, + }, + } + + block.BlockHash = block.Hash() + + return ledger.PersistExecutionResult(block, nil) +} diff --git a/internal/ledger/key.go b/internal/ledger/key.go new file mode 100644 index 0000000..d5c2789 --- /dev/null +++ b/internal/ledger/key.go @@ -0,0 +1,17 @@ +package ledger + +import "fmt" + +const ( + ledgerTreePrefix = "ChainLedger" + blockKey = "block-" + blockHashKey = "block-hash-" + receiptKey = "receipt-" + transactionKey = "tx-" + transactionMetaKey = "tx-meta-" + chainMetaKey = "chain-meta" +) + +func compositeKey(prefix string, value interface{}) []byte { + return append([]byte(prefix), []byte(fmt.Sprintf("%v", value))...) +} diff --git a/internal/ledger/key_test.go b/internal/ledger/key_test.go new file mode 100644 index 0000000..cc756a3 --- /dev/null +++ b/internal/ledger/key_test.go @@ -0,0 +1,13 @@ +package ledger + +import ( + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestCompositeKey(t *testing.T) { + assert.Equal(t, compositeKey(blockKey, 1), []byte("block-1")) + assert.Equal(t, compositeKey(blockHashKey, "0x112233"), []byte("block-hash-0x112233")) + assert.Equal(t, compositeKey(transactionKey, "0x112233"), []byte("tx-0x112233")) +} diff --git a/internal/ledger/ledger.go b/internal/ledger/ledger.go new file mode 100644 index 0000000..ea1d46d --- /dev/null +++ b/internal/ledger/ledger.go @@ -0,0 +1,148 @@ +package ledger + +import ( + "bytes" + "fmt" + "sync" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/sirupsen/logrus" + "github.com/tendermint/iavl" + db "github.com/tendermint/tm-db" + "github.com/wonderivan/logger" +) + +var _ Ledger = (*ChainLedger)(nil) + +var ( + ErrorRollbackTohigherNumber = fmt.Errorf("rollback to higher blockchain height") +) + +const ( + defaultIAVLCacheSize = 10000 +) + +type ChainLedger struct { + logger logrus.FieldLogger + blockchainStore storage.Storage + ldb db.DB + tree *iavl.MutableTree + height uint64 + events map[string][]*pb.Event + accounts map[string]*Account + modifiedAccount map[string]bool + + chainMutex sync.RWMutex + chainMeta *pb.ChainMeta +} + +// New create a new ledger instance +func New(repoRoot string, blockchainStore storage.Storage, logger logrus.FieldLogger) (*ChainLedger, error) { + ldb, err := db.NewGoLevelDB("ledger", repo.GetStoragePath(repoRoot)) + if err != nil { + return nil, fmt.Errorf("create tm-leveldb: %w", err) + } + + chainMeta, err := loadChainMeta(blockchainStore) + if err != nil { + return nil, fmt.Errorf("load chain meta: %w", err) + } + + tree := iavl.NewMutableTree(db.NewPrefixDB(ldb, []byte(ledgerTreePrefix)), defaultIAVLCacheSize) + height, err := tree.LoadVersionForOverwriting(int64(chainMeta.Height)) + if err != nil { + return nil, fmt.Errorf("load state tree: %w", err) + } + + if uint64(height) < chainMeta.Height { + // TODO(xcc): how to handle this case + panic("state tree height is less than blockchain height") + } + + return &ChainLedger{ + logger: logger, + chainMeta: chainMeta, + blockchainStore: blockchainStore, + ldb: ldb, + tree: tree, + events: make(map[string][]*pb.Event, 10), + accounts: make(map[string]*Account), + modifiedAccount: make(map[string]bool), + }, nil +} + +// Rollback rollback to history version +func (l *ChainLedger) Rollback(height uint64) error { + if l.chainMeta.Height <= height { + return ErrorRollbackTohigherNumber + } + + block, err := l.GetBlock(height) + if err != nil { + return err + } + + count, err := getInterchainTxCount(block.BlockHeader) + if err != nil { + return err + } + + l.UpdateChainMeta(&pb.ChainMeta{ + Height: height, + BlockHash: block.BlockHash, + InterchainTxCount: count, + }) + + // clean cache account + l.Clear() + + _, err = l.tree.LoadVersionForOverwriting(int64(height)) + if err != nil { + return err + } + + begin, end := bytesPrefix([]byte(accountKey)) + l.tree.IterateRange(begin, end, false, func(key []byte, value []byte) bool { + arr := bytes.Split(key, []byte("-")) + if len(arr) != 2 { + logger.Info("wrong account key") + } + + a := newAccount(l, l.ldb, types.String2Address(string(arr[1]))) + if err := a.Unmarshal(value); err != nil { + logger.Error(err) + } + + if _, err := a.tree.LoadVersionForOverwriting(a.Version); err != nil { + logger.Error(err) + } + + _, err = a.Commit() + if err != nil { + logger.Error(err) + } + + return false + }) + + return nil +} + +// AddEvent add ledger event +func (l *ChainLedger) AddEvent(event *pb.Event) { + hash := event.TxHash.Hex() + l.events[hash] = append(l.events[hash], event) +} + +// Events return ledger events +func (l *ChainLedger) Events(txHash string) []*pb.Event { + return l.events[txHash] +} + +// Close close the ledger instance +func (l *ChainLedger) Close() { + l.ldb.Close() +} diff --git a/internal/ledger/ledger_test.go b/internal/ledger/ledger_test.go new file mode 100644 index 0000000..78e2e74 --- /dev/null +++ b/internal/ledger/ledger_test.go @@ -0,0 +1,122 @@ +package ledger + +import ( + "io/ioutil" + "testing" + + "github.com/meshplus/bitxhub-kit/bytesutil" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/stretchr/testify/assert" +) + +func TestLedger_Commit(t *testing.T) { + repoRoot, err := ioutil.TempDir("", "ledger_commit") + assert.Nil(t, err) + blockStorage, err := leveldb.New(repoRoot) + assert.Nil(t, err) + ledger, err := New(repoRoot, blockStorage, log.NewWithModule("executor")) + assert.Nil(t, err) + + // create an account + account := types.Bytes2Address(bytesutil.LeftPadBytes([]byte{100}, 20)) + + ledger.SetState(account, []byte("a"), []byte("b")) + hash, err := ledger.Commit() + assert.Nil(t, err) + assert.Equal(t, uint64(1), ledger.Version()) + assert.Equal(t, "0x711ba7e0fbb4011960870c9c98fdf930809a384243f580aae3b5d9d0d3f19f50", hash.Hex()) + + hash, err = ledger.Commit() + assert.Nil(t, err) + assert.Equal(t, uint64(2), ledger.Version()) + assert.Equal(t, "0x711ba7e0fbb4011960870c9c98fdf930809a384243f580aae3b5d9d0d3f19f50", hash.Hex()) + + ledger.SetState(account, []byte("a"), []byte("3")) + ledger.SetState(account, []byte("a"), []byte("2")) + hash, err = ledger.Commit() + assert.Nil(t, err) + assert.Equal(t, uint64(3), ledger.Version()) + assert.Equal(t, "0x102f75930e478956e0cc1ae4f79d24723d893bdf40e65186b6bb109c6f17131e", hash.Hex()) + + ledger.Close() + + // load ChainLedger from db + ldg, err := New(repoRoot, blockStorage, log.NewWithModule("executor")) + assert.Nil(t, err) + + ok, value := ldg.GetState(account, []byte("a")) + assert.True(t, ok) + assert.Equal(t, []byte("2"), value) + ver := ldg.Version() + assert.Equal(t, uint64(3), ver) + assert.Equal(t, "0x102f75930e478956e0cc1ae4f79d24723d893bdf40e65186b6bb109c6f17131e", hash.Hex()) +} + +func TestLedger_Rollback(t *testing.T) { + repoRoot, err := ioutil.TempDir("", "ledger_rollback") + assert.Nil(t, err) + blockStorage, err := leveldb.New(repoRoot) + assert.Nil(t, err) + ledger, err := New(repoRoot, blockStorage, log.NewWithModule("executor")) + assert.Nil(t, err) + + // create an account + account := types.Bytes2Address(bytesutil.LeftPadBytes([]byte{100}, 20)) + + ledger.SetState(account, []byte("a"), []byte("b")) + _, err = ledger.Commit() + assert.Nil(t, err) + ledger.SetState(account, []byte("a"), []byte("c")) + _, err = ledger.Commit() + assert.Nil(t, err) + ledger.SetState(account, []byte("a"), []byte("d")) + _, err = ledger.Commit() + assert.Nil(t, err) + + block1 := &pb.Block{BlockHeader: &pb.BlockHeader{Number: 1}} + block1.BlockHash = block1.Hash() + block2 := &pb.Block{BlockHeader: &pb.BlockHeader{Number: 2}} + block2.BlockHash = block2.Hash() + block3 := &pb.Block{BlockHeader: &pb.BlockHeader{Number: 3}} + block3.BlockHash = block3.Hash() + err = ledger.PutBlock(1, block1) + assert.Nil(t, err) + err = ledger.PutBlock(2, block2) + assert.Nil(t, err) + err = ledger.PutBlock(3, block3) + assert.Nil(t, err) + + ledger.UpdateChainMeta(&pb.ChainMeta{ + Height: 3, + BlockHash: types.Hash{}, + }) + + err = ledger.Rollback(1) + assert.Nil(t, err) + assert.Equal(t, uint64(1), ledger.Version()) + err = ledger.Rollback(2) + assert.Equal(t, ErrorRollbackTohigherNumber, err) + + ok, value := ledger.GetState(account, []byte("a")) + assert.True(t, ok) + assert.Equal(t, []byte("b"), value) + + ledger.SetState(account, []byte("a"), []byte("c")) + _, err = ledger.Commit() + assert.Nil(t, err) + ledger.SetState(account, []byte("a"), []byte("d")) + _, err = ledger.Commit() + assert.Nil(t, err) + err = ledger.PutBlock(2, block2) + assert.Nil(t, err) + err = ledger.PutBlock(3, block3) + assert.Nil(t, err) + + ok, value = ledger.GetState(account, []byte("a")) + assert.True(t, ok) + assert.Equal(t, []byte("d"), value) + assert.Equal(t, uint64(3), ledger.Version()) +} diff --git a/internal/ledger/state_accessor.go b/internal/ledger/state_accessor.go new file mode 100644 index 0000000..6e535ee --- /dev/null +++ b/internal/ledger/state_accessor.go @@ -0,0 +1,169 @@ +package ledger + +import ( + "sort" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +var _ Ledger = (*ChainLedger)(nil) + +const ( + accountKey = "account-" +) + +// GetOrCreateAccount get the account, if not exist, create a new account +func (l *ChainLedger) GetOrCreateAccount(addr types.Address) *Account { + h := addr.Hex() + value, ok := l.accounts[h] + if ok { + return value + } + + obj := newAccount(l, l.ldb, addr) + + l.accounts[h] = obj + + return obj +} + +// GetAccount get account info using account address +func (l *ChainLedger) GetAccount(addr types.Address) *Account { + h := addr.Hex() + value, ok := l.accounts[h] + if ok { + return value + } + + account := &Account{} + _, data := l.tree.Get(compositeKey(accountKey, addr.Hex())) + if data != nil { + if err := account.Unmarshal(data); err != nil { + panic(err) + } + } + + return account +} + +// GetBalanec get account balance using account address +func (l *ChainLedger) GetBalance(addr types.Address) uint64 { + account := l.GetOrCreateAccount(addr) + + return account.Balance +} + +// SetBalance set account balance +func (l *ChainLedger) SetBalance(addr types.Address, value uint64) { + h := addr.Hex() + account := l.GetOrCreateAccount(addr) + account.Balance = value + + l.accounts[h] = account + l.modifiedAccount[h] = true +} + +// GetState get account state value using account address and key +func (l *ChainLedger) GetState(addr types.Address, key []byte) (bool, []byte) { + account := l.GetOrCreateAccount(addr) + + return account.GetState(key) +} + +// SetState set account state value using account address and key +func (l *ChainLedger) SetState(addr types.Address, key []byte, v []byte) { + h := addr.Hex() + account := l.GetOrCreateAccount(addr) + account.SetState(key, v) + + l.accounts[h] = account + l.modifiedAccount[h] = true +} + +// SetCode set contract code +func (l *ChainLedger) SetCode(addr types.Address, code []byte) { + h := addr.Hex() + account := l.GetOrCreateAccount(addr) + account.SetCodeAndHash(code) + + l.accounts[h] = account + l.modifiedAccount[h] = true +} + +// GetCode get contract code +func (l *ChainLedger) GetCode(addr types.Address) []byte { + account := l.GetOrCreateAccount(addr) + return account.Code() +} + +// GetNonce get account nonce +func (l *ChainLedger) GetNonce(addr types.Address) uint64 { + account := l.GetOrCreateAccount(addr) + return account.GetNonce() +} + +// SetNonce set account nonce +func (l *ChainLedger) SetNonce(addr types.Address, nonce uint64) { + h := addr.Hex() + account := l.GetOrCreateAccount(addr) + account.SetNonce(nonce) + + l.accounts[h] = account + l.modifiedAccount[h] = true +} + +// QueryByPrefix query value using key +func (l *ChainLedger) QueryByPrefix(addr types.Address, prefix string) (bool, [][]byte) { + account := l.GetOrCreateAccount(addr) + return account.Query(prefix) +} + +func (l *ChainLedger) Clear() { + l.events = make(map[string][]*pb.Event, 10) + l.accounts = make(map[string]*Account) + l.modifiedAccount = make(map[string]bool) +} + +// Commit commit the state +func (l *ChainLedger) Commit() (types.Hash, error) { + sk := make([]string, 0, len(l.modifiedAccount)) + for id := range l.modifiedAccount { + sk = append(sk, id) + } + + sort.Strings(sk) + + for _, id := range sk { + obj := l.accounts[id] + hash, err := obj.Commit() + if err != nil { + return types.Hash{}, err + } + + obj.SetStateRoot(hash) + + data, err := obj.Marshal() + if err != nil { + return types.Hash{}, err + } + + l.tree.Set(compositeKey(accountKey, id), data) + } + + hash, height, err := l.tree.SaveVersion() + if err != nil { + return types.Hash{}, err + } + + l.height = uint64(height) + + l.Clear() + + return types.Bytes2Hash(hash), nil +} + +// Version returns the current version +func (l *ChainLedger) Version() uint64 { + return uint64(l.tree.Version()) +} diff --git a/internal/ledger/types.go b/internal/ledger/types.go new file mode 100644 index 0000000..782979d --- /dev/null +++ b/internal/ledger/types.go @@ -0,0 +1,102 @@ +package ledger + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +//go:generate mockgen -destination mock_ledger/mock_ledger.go -package mock_ledger -source types.go +type Ledger interface { + BlockchainLedger + StateAccessor + + // AddEvent + AddEvent(*pb.Event) + + // Events + Events(txHash string) []*pb.Event + + // Rollback + Rollback(height uint64) error + + // Close release resource + Close() +} + +// StateAccessor manipulates the state data +type StateAccessor interface { + // GetOrCreateAccount + GetOrCreateAccount(types.Address) *Account + + // GetAccount + GetAccount(types.Address) *Account + + // GetBalance + GetBalance(types.Address) uint64 + + // SetBalance + SetBalance(types.Address, uint64) + + // GetState + GetState(types.Address, []byte) (bool, []byte) + + // SetState + SetState(types.Address, []byte, []byte) + + // SetCode + SetCode(types.Address, []byte) + + // GetCode + GetCode(types.Address) []byte + + // SetNonce + SetNonce(types.Address, uint64) + + // GetNonce + GetNonce(types.Address) uint64 + + // QueryByPrefix + QueryByPrefix(address types.Address, prefix string) (bool, [][]byte) + + // Commit commits the state data + Commit() (types.Hash, error) + + // Version + Version() uint64 + + // Clear + Clear() +} + +// BlockchainLedger handles block, transaction and receipt data. +type BlockchainLedger interface { + // PutBlock put block into store + PutBlock(height uint64, block *pb.Block) error + + // GetBlock get block with height + GetBlock(height uint64) (*pb.Block, error) + + // GetBlockSign get the signature of block + GetBlockSign(height uint64) ([]byte, error) + + // GetBlockByHash get the block using block hash + GetBlockByHash(hash types.Hash) (*pb.Block, error) + + // GetTransaction get the transaction using transaction hash + GetTransaction(hash types.Hash) (*pb.Transaction, error) + + // GetTransactionMeta get the transaction meta data + GetTransactionMeta(hash types.Hash) (*pb.TransactionMeta, error) + + // GetReceipt get the transaction receipt + GetReceipt(hash types.Hash) (*pb.Receipt, error) + + // PersistExecutionResult persist the execution result + PersistExecutionResult(block *pb.Block, receipts []*pb.Receipt) error + + // GetChainMeta get chain meta data + GetChainMeta() *pb.ChainMeta + + // UpdateChainMeta update the chain meta data + UpdateChainMeta(*pb.ChainMeta) +} diff --git a/internal/loggers/loggers.go b/internal/loggers/loggers.go new file mode 100644 index 0000000..54b6957 --- /dev/null +++ b/internal/loggers/loggers.go @@ -0,0 +1,47 @@ +package loggers + +import ( + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/sirupsen/logrus" +) + +const ( + P2P = "p2p" + Order = "order" + Executor = "executor" + Router = "router" + App = "app" + API = "api" + CoreAPI = "coreapi" +) + +var w *loggerWrapper + +type loggerWrapper struct { + loggers map[string]*logrus.Entry +} + +func Initialize(config *repo.Config) { + m := make(map[string]*logrus.Entry) + m[P2P] = log.NewWithModule(P2P) + m[P2P].Logger.SetLevel(log.ParseLevel(config.Log.Module.P2P)) + m[Order] = log.NewWithModule(Order) + m[Order].Logger.SetLevel(log.ParseLevel(config.Log.Module.Consensus)) + m[Executor] = log.NewWithModule(Executor) + m[Executor].Logger.SetLevel(log.ParseLevel(config.Log.Module.Executor)) + m[Router] = log.NewWithModule(Router) + m[Router].Logger.SetLevel(log.ParseLevel(config.Log.Module.Router)) + m[App] = log.NewWithModule(App) + m[App].Logger.SetLevel(log.ParseLevel(config.Log.Level)) + m[API] = log.NewWithModule(API) + m[API].Logger.SetLevel(log.ParseLevel(config.Log.Module.API)) + m[CoreAPI] = log.NewWithModule(CoreAPI) + m[CoreAPI].Logger.SetLevel(log.ParseLevel(config.Log.Module.CoreAPI)) + + w = &loggerWrapper{loggers: m} +} + +func Logger(name string) logrus.FieldLogger { + return w.loggers[name] +} diff --git a/internal/model/events/events.go b/internal/model/events/events.go new file mode 100755 index 0000000..bd919eb --- /dev/null +++ b/internal/model/events/events.go @@ -0,0 +1,19 @@ +package events + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +type NewBlockEvent struct { + Block *pb.Block +} + +type CheckpointEvent struct { + Index uint64 + Digest types.Hash +} + +type OrderMessageEvent struct { + Data []byte +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..e44bb5d --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,29 @@ +package model + +import "encoding/json" + +type MerkleWrapperSign struct { + Address string `json:"address"` + Signature []byte `json:"signature"` +} + +func (m *MerkleWrapperSign) Marshal() ([]byte, error) { + return json.Marshal(m) +} + +func (m *MerkleWrapperSign) Unmarshal(data []byte) error { + return json.Unmarshal(data, m) +} + +type CertsMessage struct { + AgencyCert []byte + NodeCert []byte +} + +func (c *CertsMessage) Marshal() ([]byte, error) { + return json.Marshal(c) +} + +func (c *CertsMessage) Unmarshal(data []byte) error { + return json.Unmarshal(data, c) +} diff --git a/internal/model/model_test.go b/internal/model/model_test.go new file mode 100644 index 0000000..19c0cec --- /dev/null +++ b/internal/model/model_test.go @@ -0,0 +1,25 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMerkleWrapperSign_Unmarshal(t *testing.T) { + m := MerkleWrapperSign{ + Address: "0xba30d0dd7876318da451582", + Signature: []byte("123456"), + } + + data, err := m.Marshal() + require.Nil(t, err) + require.EqualValues(t, + `{"address":"0xba30d0dd7876318da451582","signature":"MTIzNDU2"}`, + string(data)) + + s := &MerkleWrapperSign{} + err = s.Unmarshal(data) + require.Nil(t, err) + require.EqualValues(t, "0xba30d0dd7876318da451582", s.Address) +} diff --git a/internal/plugins/Makefile b/internal/plugins/Makefile new file mode 100644 index 0000000..8b5ae37 --- /dev/null +++ b/internal/plugins/Makefile @@ -0,0 +1,18 @@ +GO = GO111MODULE=on go + +help: Makefile + @echo "Choose a command run:" + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + +## make raft: build plugin (make plugin type= ) +raft: + @mkdir -p build + $(GO) build --buildmode=plugin -o build/raft.so order/etcdraft/*.go + +## make raft: build plugin (make plugin type= ) +solo: + @mkdir -p build + $(GO) build --buildmode=plugin -o build/solo.so order/solo/*.go + +## make plugins: build plugins (make plugin type= and ) +plugins: raft solo \ No newline at end of file diff --git a/internal/plugins/order/etcdraft/config.go b/internal/plugins/order/etcdraft/config.go new file mode 100644 index 0000000..09d5b38 --- /dev/null +++ b/internal/plugins/order/etcdraft/config.go @@ -0,0 +1,108 @@ +package main + +import ( + "path/filepath" + "time" + + "github.com/coreos/etcd/raft" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +type RAFTConfig struct { + RAFT RAFT +} + +type TxPoolConfig struct { + PackSize int `mapstructure:"pack_size"` + BlockTick time.Duration `mapstructure:"block_tick"` +} + +type RAFT struct { + ElectionTick int `mapstructure:"election_tick"` + HeartbeatTick int `mapstructure:"heartbeat_tick"` + MaxSizePerMsg uint64 `mapstructure:"max_size_per_msg"` + MaxInflightMsgs int `mapstructure:"max_inflight_msgs"` + CheckQuorum bool `mapstructure:"check_quorum"` + PreVote bool `mapstructure:"pre_vote"` + DisableProposalForwarding bool `mapstructure:"disable_proposal_forwarding"` + TxPoolConfig TxPoolConfig `mapstructure:"tx_pool"` +} + +func defaultRaftConfig() raft.Config { + return raft.Config{ + ElectionTick: 10, //ElectionTick is the number of Node.Tick invocations that must pass between elections.(s) + HeartbeatTick: 1, //HeartbeatTick is the number of Node.Tick invocations that must pass between heartbeats.(s) + MaxSizePerMsg: 1024 * 1024, //1024*1024, MaxSizePerMsg limits the max size of each append message. + MaxInflightMsgs: 500, //MaxInflightMsgs limits the max number of in-flight append messages during optimistic replication phase. + PreVote: true, // PreVote prevents reconnected node from disturbing network. + CheckQuorum: false, // Leader steps down when quorum is not active for an electionTimeout. + DisableProposalForwarding: true, // This prevents blocks from being accidentally proposed by followers + } +} + +func defaultTxPoolConfig() TxPoolConfig { + return TxPoolConfig{ + PackSize: 500, //Maximum number of transaction packages. + BlockTick: 500 * time.Millisecond, //Block packaging time period. + } +} + +func generateRaftConfig(id uint64, repoRoot string, logger logrus.FieldLogger, ram MemoryStorage) (*raft.Config, error) { + readConfig, err := readConfig(repoRoot) + if err != nil { + return &raft.Config{}, nil + } + defaultConfig := defaultRaftConfig() + defaultConfig.ID = id + defaultConfig.Storage = ram + defaultConfig.Logger = logger + if readConfig.RAFT.ElectionTick > 0 { + defaultConfig.ElectionTick = readConfig.RAFT.ElectionTick + } + if readConfig.RAFT.HeartbeatTick > 0 { + defaultConfig.HeartbeatTick = readConfig.RAFT.HeartbeatTick + } + if readConfig.RAFT.MaxSizePerMsg > 0 { + defaultConfig.MaxSizePerMsg = readConfig.RAFT.MaxSizePerMsg + } + if readConfig.RAFT.MaxInflightMsgs > 0 { + defaultConfig.MaxInflightMsgs = readConfig.RAFT.MaxInflightMsgs + } + defaultConfig.PreVote = readConfig.RAFT.PreVote + defaultConfig.CheckQuorum = readConfig.RAFT.CheckQuorum + defaultConfig.DisableProposalForwarding = readConfig.RAFT.DisableProposalForwarding + return &defaultConfig, nil +} + +func generateTxPoolConfig(repoRoot string) (*TxPoolConfig, error) { + readConfig, err := readConfig(repoRoot) + if err != nil { + return &TxPoolConfig{}, nil + } + defaultTxPoolConfig := defaultTxPoolConfig() + if readConfig.RAFT.TxPoolConfig.BlockTick > 0 { + defaultTxPoolConfig.BlockTick = readConfig.RAFT.TxPoolConfig.BlockTick + } + if readConfig.RAFT.TxPoolConfig.PackSize > 0 { + defaultTxPoolConfig.PackSize = readConfig.RAFT.TxPoolConfig.PackSize + } + return &defaultTxPoolConfig, nil +} + +func readConfig(repoRoot string) (*RAFTConfig, error) { + v := viper.New() + v.SetConfigFile(filepath.Join(repoRoot, "order.toml")) + v.SetConfigType("toml") + if err := v.ReadInConfig(); err != nil { + return nil, err + } + + config := &RAFTConfig{} + + if err := v.Unmarshal(config); err != nil { + return nil, err + } + + return config, nil +} diff --git a/internal/plugins/order/etcdraft/node.go b/internal/plugins/order/etcdraft/node.go new file mode 100644 index 0000000..04f3709 --- /dev/null +++ b/internal/plugins/order/etcdraft/node.go @@ -0,0 +1,574 @@ +package main + +import ( + "context" + "encoding/binary" + "fmt" + "path/filepath" + "sync" + "time" + + "github.com/coreos/etcd/raft" + "github.com/coreos/etcd/raft/raftpb" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + raftproto "github.com/meshplus/bitxhub/internal/plugins/order/etcdraft/proto" + "github.com/meshplus/bitxhub/internal/plugins/order/etcdraft/txpool" + "github.com/meshplus/bitxhub/pkg/order" + "github.com/meshplus/bitxhub/pkg/peermgr" + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/sirupsen/logrus" +) + +var defaultSnapshotCount uint64 = 10000 + +type Node struct { + id uint64 // raft id + node raft.Node // raft node + peerMgr peermgr.PeerManager // network manager + peers []raft.Peer // raft peers + + proposeC chan *raftproto.Ready // proposed ready, input channel + confChangeC <-chan raftpb.ConfChange // proposed cluster config changes + confState raftpb.ConfState // raft requires ConfState to be persisted within snapshot + commitC chan *pb.Block // the hash commit channel + errorC chan<- error // errors from raft session + + raftStorage *RaftStorage // the raft backend storage system + tp *txpool.TxPool // transaction pool + storage storage.Storage // db + + repoRoot string //project path + logger logrus.FieldLogger //logger + + blockAppliedIndex sync.Map // mapping of block height and apply index in raft log + appliedIndex uint64 // current apply index in raft log + snapCount uint64 // snapshot count + snapshotIndex uint64 // current snapshot apply index in raft log + lastIndex uint64 // last apply index in raft log + + readyPool *sync.Pool // ready pool, avoiding memory growth fast + readyCache sync.Map //ready cache + ctx context.Context // context + haltC chan struct{} // exit signal +} + +// NewNode new raft node +func NewNode(opts ...order.Option) (order.Order, error) { + config, err := order.GenerateConfig(opts...) + if err != nil { + return nil, fmt.Errorf("generate config: %w", err) + } + + repoRoot := config.RepoRoot + + // raft storage directory + walDir := filepath.Join(config.StoragePath, "wal") + snapDir := filepath.Join(config.StoragePath, "snap") + dbDir := filepath.Join(config.StoragePath, "state") + raftStorage, dbStorage, err := CreateStorage(config.Logger, walDir, snapDir, dbDir, raft.NewMemoryStorage()) + if err != nil { + return nil, err + } + + //generate raft peers + peers, err := GenerateRaftPeers(config) + if err != nil { + return nil, fmt.Errorf("generate raft peers: %w", err) + } + + //generate txpool config + tpc, err := generateTxPoolConfig(repoRoot) + if err != nil { + return nil, fmt.Errorf("generate raft txpool config: %w", err) + } + txPool, proposeC := txpool.New(config, dbStorage, tpc.PackSize, tpc.BlockTick) + + readyPool := &sync.Pool{New: func() interface{} { + return new(raftproto.Ready) + }} + return &Node{ + id: config.ID, + proposeC: proposeC, + confChangeC: make(chan raftpb.ConfChange), + commitC: make(chan *pb.Block, 1024), + errorC: make(chan<- error), + tp: txPool, + repoRoot: repoRoot, + snapCount: defaultSnapshotCount, + peerMgr: config.PeerMgr, + peers: peers, + logger: config.Logger, + storage: dbStorage, + raftStorage: raftStorage, + readyPool: readyPool, + ctx: context.Background(), + }, nil +} + +//Start or restart raft node +func (n *Node) Start() error { + n.blockAppliedIndex.Store(n.tp.GetHeight(), n.loadAppliedIndex()) + rc, err := generateRaftConfig(n.id, n.repoRoot, n.logger, n.raftStorage.ram) + if err != nil { + return fmt.Errorf("generate raft config: %w", err) + } + if restart { + n.node = raft.RestartNode(rc) + } else { + n.node = raft.StartNode(rc, n.peers) + } + + go n.run() + n.logger.Info("Consensus module started") + + return nil +} + +//Stop the raft node +func (n *Node) Stop() { + n.tp.CheckExecute(false) + n.node.Stop() + n.logger.Infof("Consensus stopped") +} + +//Add the transaction into txpool and broadcast it to other nodes +func (n *Node) Prepare(tx *pb.Transaction) error { + if err := n.tp.AddPendingTx(tx); err != nil { + return err + } + if err := n.tp.Broadcast(tx); err != nil { + return err + } + + return nil +} + +func (n *Node) Commit() chan *pb.Block { + return n.commitC +} + +func (n *Node) ReportState(height uint64, hash types.Hash) { + appliedIndex, ok := n.blockAppliedIndex.Load(height) + if !ok { + n.logger.Errorf("can not found appliedIndex:", height) + return + } + //block already persisted, record the apply index in db + n.writeAppliedIndex(appliedIndex.(uint64)) + n.blockAppliedIndex.Delete(height) + + n.tp.BuildReqLookUp() //store bloom filter + + ready, ok := n.readyCache.Load(height) + if !ok { + n.logger.Errorf("can not found ready:", height) + return + } + // remove redundant tx + n.tp.RemoveTxs(ready.(*raftproto.Ready).TxHashes, n.IsLeader()) + n.readyCache.Delete(height) + + if height%10 == 0 { + n.logger.WithFields(logrus.Fields{ + "height": height, + "hash": hash.ShortString(), + }).Info("Report checkpoint") + } +} + +func (n *Node) Quorum() uint64 { + return uint64(len(n.peers)/2 + 1) +} + +func (n *Node) Step(ctx context.Context, msg []byte) error { + rm := &raftproto.RaftMessage{} + if err := rm.Unmarshal(msg); err != nil { + return err + } + switch rm.Type { + case raftproto.RaftMessage_CONSENSUS: + msg := &raftpb.Message{} + if err := msg.Unmarshal(rm.Data); err != nil { + return err + } + return n.node.Step(ctx, *msg) + case raftproto.RaftMessage_GET_TX: + hash := types.Hash{} + if err := hash.Unmarshal(rm.Data); err != nil { + return err + } + tx, ok := n.tp.GetTx(hash, true) + if !ok { + return nil + } + v, err := tx.Marshal() + if err != nil { + return err + } + txAck := &raftproto.RaftMessage{ + Type: raftproto.RaftMessage_GET_TX_ACK, + Data: v, + } + txAckData, err := txAck.Marshal() + if err != nil { + return err + } + m := &pb.Message{ + Type: pb.Message_CONSENSUS, + Data: txAckData, + } + return n.peerMgr.Send(rm.FromId, m) + case raftproto.RaftMessage_GET_TX_ACK: + fallthrough + case raftproto.RaftMessage_BROADCAST_TX: + tx := &pb.Transaction{} + if err := tx.Unmarshal(rm.Data); err != nil { + return err + } + return n.tp.AddPendingTx(tx) + default: + return fmt.Errorf("unexpected raft message received") + } +} + +func (n *Node) IsLeader() bool { + return n.node.Status().SoftState.Lead == n.id +} + +func (n *Node) Ready() bool { + return n.node.Status().SoftState.Lead != 0 +} + +// main work loop +func (n *Node) run() { + snap, err := n.raftStorage.ram.Snapshot() + if err != nil { + n.logger.Panic(err) + } + n.confState = snap.Metadata.ConfState + n.snapshotIndex = snap.Metadata.Index + n.appliedIndex = snap.Metadata.Index + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + // handle input request + go func() { + // + // TODO: does it matter that this will restart from 0 whenever we restart a cluster? + // + confChangeCount := uint64(0) + + for n.proposeC != nil && n.confChangeC != nil { + select { + case ready, ok := <-n.proposeC: + if !ok { + n.proposeC = nil + } else { + data, err := ready.Marshal() + if err != nil { + n.logger.Panic(err) + } + n.tp.BatchStore(ready.TxHashes) + if err := n.node.Propose(n.ctx, data); err != nil { + n.logger.Panic("Failed to propose block [%d] to raft: %s", ready.Height, err) + } + } + case cc, ok := <-n.confChangeC: + if !ok { + n.confChangeC = nil + } else { + confChangeCount++ + cc.ID = confChangeCount + if err := n.node.ProposeConfChange(n.ctx, cc); err != nil { + n.logger.Panic("Failed to propose configuration update to Raft node: %s", err) + } + } + case <-n.ctx.Done(): + return + } + } + + }() + + // handle messages from raft state machine + for { + select { + case <-ticker.C: + n.node.Tick() + // when the node is first ready it gives us entries to commit and messages + // to immediately publish + case rd := <-n.node.Ready(): + // 1: Write HardState, Entries, and Snapshot to persistent storage if they + // are not empty. + if err := n.raftStorage.Store(rd.Entries, rd.HardState, rd.Snapshot); err != nil { + n.logger.Fatalf("failed to persist etcd/raft data: %s", err) + } + + // 2: Apply Snapshot (if any) and CommittedEntries to the state machine. + if len(rd.CommittedEntries) != 0 || rd.SoftState != nil { + n.tp.CheckExecute(n.IsLeader()) + if ok := n.publishEntries(n.entriesToApply(rd.CommittedEntries)); !ok { + n.Stop() + return + } + } + // 3: Send all Messages to the nodes named in the To field. + n.send(rd.Messages) + + n.maybeTriggerSnapshot() + + // 4: Call Node.Advance() to signal readiness for the next batch of + // updates. + n.node.Advance() + case <-n.ctx.Done(): + n.Stop() + } + } +} + +// send raft consensus message +func (n *Node) send(messages []raftpb.Message) { + for _, msg := range messages { + if msg.To == 0 { + continue + } + status := raft.SnapshotFinish + + data, err := (&msg).Marshal() + if err != nil { + n.logger.Error(err) + continue + } + + rm := &raftproto.RaftMessage{ + Type: raftproto.RaftMessage_CONSENSUS, + Data: data, + } + rmData, err := rm.Marshal() + if err != nil { + n.logger.Error(err) + continue + } + p2pMsg := &pb.Message{ + Type: pb.Message_CONSENSUS, + Data: rmData, + } + + err = n.peerMgr.Send(msg.To, p2pMsg) + if err != nil { + n.logger.WithFields(logrus.Fields{ + "mgs_to": msg.To, + }).Debugln("message consensus error") + n.node.ReportUnreachable(msg.To) + status = raft.SnapshotFailure + } + + if msg.Type == raftpb.MsgSnap { + n.node.ReportSnapshot(msg.To, status) + } + } +} + +func (n *Node) publishEntries(ents []raftpb.Entry) bool { + for i := range ents { + switch ents[i].Type { + case raftpb.EntryNormal: + if len(ents[i].Data) == 0 { + // ignore empty messages + break + } + + ready := n.readyPool.Get().(*raftproto.Ready) + if err := ready.Unmarshal(ents[i].Data); err != nil { + n.logger.Error(err) + continue + } + // This can happen: + // + // if (1) we crashed after applying this block to the chain, but + // before writing appliedIndex to LDB. + // or (2) we crashed in a scenario where we applied further than + // raft *durably persisted* its committed index (see + // https://github.com/coreos/etcd/pull/7899). In this + // scenario, when the node comes back up, we will re-apply + // a few entries. + if n.getBlockAppliedIndex() >= ents[i].Index { + // after commit, update appliedIndex + n.appliedIndex = ents[i].Index + continue + } + n.mint(ready) + n.blockAppliedIndex.Store(ready.Height, ents[i].Index) + case raftpb.EntryConfChange: + var cc raftpb.ConfChange + if err := cc.Unmarshal(ents[i].Data); err != nil { + continue + } + n.confState = *n.node.ApplyConfChange(cc) + switch cc.Type { + case raftpb.ConfChangeAddNode: + //if len(cc.Context) > 0 { + // _ := types.Bytes2Address(cc.Context) + //} + case raftpb.ConfChangeRemoveNode: + //if cc.NodeID == n.id { + // n.logger.Infoln("I've been removed from the cluster! Shutting down.") + // continue + //} + } + } + + // after commit, update appliedIndex + n.appliedIndex = ents[i].Index + + // special nil commit to signal replay has finished + if ents[i].Index == n.lastIndex { + select { + case n.commitC <- nil: + case <-n.haltC: + return false + } + } + } + return true +} + +//mint the block +func (n *Node) mint(ready *raftproto.Ready) { + loseTxs := make([]types.Hash, 0) + txs := make([]*pb.Transaction, 0, len(ready.TxHashes)) + for _, hash := range ready.TxHashes { + _, ok := n.tp.GetTx(hash, false) + if !ok { + loseTxs = append(loseTxs, hash) + } + } + + //handler missing tx + if len(loseTxs) != 0 { + var wg sync.WaitGroup + wg.Add(len(loseTxs)) + for _, hash := range loseTxs { + go func(hash types.Hash) { + defer wg.Done() + n.tp.FetchTx(hash) + }(hash) + } + wg.Wait() + } + + for _, hash := range ready.TxHashes { + tx, _ := n.tp.GetTx(hash, false) + txs = append(txs, tx) + } + //follower node update the block height + if !n.IsLeader() { + n.tp.UpdateHeight() + } + block := &pb.Block{ + BlockHeader: &pb.BlockHeader{ + Version: []byte("1.0.0"), + Number: ready.Height, + Timestamp: time.Now().UnixNano(), + }, + Transactions: txs, + } + + n.logger.WithFields(logrus.Fields{ + "txpool_size": n.tp.PoolSize(), + }).Debugln("current tx pool size") + n.readyCache.Store(ready.Height, ready) + n.commitC <- block +} + +//Determine whether the current apply index is normal +func (n *Node) entriesToApply(allEntries []raftpb.Entry) (entriesToApply []raftpb.Entry) { + if len(allEntries) == 0 { + return + } + firstIdx := allEntries[0].Index + if firstIdx > n.appliedIndex+1 { + n.logger.Fatalf("first index of committed entry[%d] should <= progress.appliedIndex[%d]+1", firstIdx, n.appliedIndex) + } + if n.appliedIndex-firstIdx+1 < uint64(len(allEntries)) { + entriesToApply = allEntries[n.appliedIndex-firstIdx+1:] + } + return entriesToApply +} + +//Determines whether the current apply index triggers a snapshot +func (n *Node) maybeTriggerSnapshot() { + if n.appliedIndex-n.snapshotIndex <= n.snapCount { + return + } + data := n.raftStorage.Snapshot().Data + n.logger.Infof("Start snapshot [applied index: %d | last snapshot index: %d]", n.appliedIndex, n.snapshotIndex) + snap, err := n.raftStorage.ram.CreateSnapshot(n.appliedIndex, &n.confState, data) + if err != nil { + panic(err) + } + if err := n.raftStorage.saveSnap(snap); err != nil { + panic(err) + } + + compactIndex := uint64(1) + if n.appliedIndex > n.raftStorage.SnapshotCatchUpEntries { + compactIndex = n.appliedIndex - n.raftStorage.SnapshotCatchUpEntries + } + if err := n.raftStorage.ram.Compact(compactIndex); err != nil { + panic(err) + } + + n.logger.Infof("compacted log at index %d", compactIndex) + n.snapshotIndex = n.appliedIndex +} + +func GenerateRaftPeers(config *order.Config) ([]raft.Peer, error) { + nodes := config.Nodes + peers := make([]raft.Peer, 0, len(nodes)) + for id, node := range nodes { + peers = append(peers, raft.Peer{ID: id, Context: node.Bytes()}) + } + return peers, nil +} + +//Get the raft apply index of the highest block +func (n *Node) getBlockAppliedIndex() uint64 { + height := uint64(0) + n.blockAppliedIndex.Range( + func(key, value interface{}) bool { + k := key.(uint64) + if k > height { + height = k + } + return true + }) + appliedIndex, ok := n.blockAppliedIndex.Load(height) + if !ok { + return 0 + } + return appliedIndex.(uint64) +} + +//Load the lastAppliedIndex of +func (n *Node) loadAppliedIndex() uint64 { + dat, err := n.storage.Get(appliedDbKey) + var lastAppliedIndex uint64 + if err != nil { + lastAppliedIndex = 0 + } else { + lastAppliedIndex = binary.LittleEndian.Uint64(dat) + } + + return lastAppliedIndex +} + +//Write the lastAppliedIndex +func (n *Node) writeAppliedIndex(index uint64) { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, index) + if err := n.storage.Put(appliedDbKey, buf); err != nil { + n.logger.Errorf("persisted the latest applied index: %s", err) + } +} diff --git a/internal/plugins/order/etcdraft/node_test.go b/internal/plugins/order/etcdraft/node_test.go new file mode 100644 index 0000000..c775f28 --- /dev/null +++ b/internal/plugins/order/etcdraft/node_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/order" + "github.com/meshplus/bitxhub/pkg/peermgr/mock_peermgr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const to = "0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997" + +func TestNode_Start(t *testing.T) { + repoRoot, err := ioutil.TempDir("", "node") + assert.Nil(t, err) + defer os.RemoveAll(repoRoot) + + var ID uint64 = 1 + nodes := make(map[uint64]types.Address) + nodes[ID] = types.String2Address("") + + fileData, err := ioutil.ReadFile("../../../../config/order.toml") + require.Nil(t, err) + err = ioutil.WriteFile(filepath.Join(repoRoot, "order.toml"), fileData, 0644) + require.Nil(t, err) + + mockCtl := gomock.NewController(t) + mockPeermgr := mock_peermgr.NewMockPeerManager(mockCtl) + peers := make(map[uint64]*peer.AddrInfo) + mockPeermgr.EXPECT().Peers().Return(peers).AnyTimes() + + order, err := NewNode( + order.WithRepoRoot(repoRoot), + order.WithID(ID), + order.WithNodes(nodes), + order.WithPeerManager(mockPeermgr), + order.WithStoragePath(repo.GetStoragePath(repoRoot, "order")), + order.WithLogger(log.NewWithModule("consensus")), + order.WithApplied(1), + ) + require.Nil(t, err) + + err = order.Start() + require.Nil(t, err) + + for { + time.Sleep(200 * time.Millisecond) + if order.Ready() { + break + } + } + tx := generateTx() + err = order.Prepare(tx) + require.Nil(t, err) + + block := <-order.Commit() + require.Equal(t, uint64(2), block.BlockHeader.Number) + require.Equal(t, 1, len(block.Transactions)) + + order.Stop() + err = order.Start() + require.Nil(t, err) + for { + time.Sleep(200 * time.Millisecond) + if order.Ready() { + break + } + } + tx1 := generateTx() + err = order.Prepare(tx1) + require.Nil(t, err) + + block1 := <-order.Commit() + require.Equal(t, uint64(2), block1.BlockHeader.Number) + require.Equal(t, 1, len(block.Transactions)) + order.Stop() +} + +func generateTx() *pb.Transaction { + privKey, _ := asym.GenerateKey(asym.ECDSASecp256r1) + + from, _ := privKey.PublicKey().Address() + + tx := &pb.Transaction{ + From: from, + To: types.String2Address(to), + Data: &pb.TransactionData{ + Amount: 10, + }, + Timestamp: time.Now().UnixNano(), + Nonce: rand.Int63(), + } + _ = tx.Sign(privKey) + tx.TransactionHash = tx.Hash() + return tx +} diff --git a/internal/plugins/order/etcdraft/proto/message.pb.go b/internal/plugins/order/etcdraft/proto/message.pb.go new file mode 100644 index 0000000..4045d86 --- /dev/null +++ b/internal/plugins/order/etcdraft/proto/message.pb.go @@ -0,0 +1,668 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: message.proto + +package proto + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_meshplus_bitxhub_kit_types "github.com/meshplus/bitxhub-kit/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type RaftMessage_Type int32 + +const ( + RaftMessage_CONSENSUS RaftMessage_Type = 0 + RaftMessage_BROADCAST_TX RaftMessage_Type = 1 + RaftMessage_GET_TX RaftMessage_Type = 2 + RaftMessage_GET_TX_ACK RaftMessage_Type = 3 +) + +var RaftMessage_Type_name = map[int32]string{ + 0: "CONSENSUS", + 1: "BROADCAST_TX", + 2: "GET_TX", + 3: "GET_TX_ACK", +} + +var RaftMessage_Type_value = map[string]int32{ + "CONSENSUS": 0, + "BROADCAST_TX": 1, + "GET_TX": 2, + "GET_TX_ACK": 3, +} + +func (x RaftMessage_Type) String() string { + return proto.EnumName(RaftMessage_Type_name, int32(x)) +} + +func (RaftMessage_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_33c57e4bae7b9afd, []int{0, 0} +} + +type RaftMessage struct { + Type RaftMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=proto.RaftMessage_Type" json:"type,omitempty"` + FromId uint64 `protobuf:"varint,2,opt,name=fromId,proto3" json:"fromId,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RaftMessage) Reset() { *m = RaftMessage{} } +func (m *RaftMessage) String() string { return proto.CompactTextString(m) } +func (*RaftMessage) ProtoMessage() {} +func (*RaftMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_33c57e4bae7b9afd, []int{0} +} +func (m *RaftMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RaftMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RaftMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RaftMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_RaftMessage.Merge(m, src) +} +func (m *RaftMessage) XXX_Size() int { + return m.Size() +} +func (m *RaftMessage) XXX_DiscardUnknown() { + xxx_messageInfo_RaftMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_RaftMessage proto.InternalMessageInfo + +func (m *RaftMessage) GetType() RaftMessage_Type { + if m != nil { + return m.Type + } + return RaftMessage_CONSENSUS +} + +func (m *RaftMessage) GetFromId() uint64 { + if m != nil { + return m.FromId + } + return 0 +} + +func (m *RaftMessage) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +type Ready struct { + TxHashes []github_com_meshplus_bitxhub_kit_types.Hash `protobuf:"bytes,1,rep,name=txHashes,proto3,customtype=github.com/meshplus/bitxhub-kit/types.Hash" json:"txHashes"` + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Ready) Reset() { *m = Ready{} } +func (m *Ready) String() string { return proto.CompactTextString(m) } +func (*Ready) ProtoMessage() {} +func (*Ready) Descriptor() ([]byte, []int) { + return fileDescriptor_33c57e4bae7b9afd, []int{1} +} +func (m *Ready) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Ready) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Ready.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Ready) XXX_Merge(src proto.Message) { + xxx_messageInfo_Ready.Merge(m, src) +} +func (m *Ready) XXX_Size() int { + return m.Size() +} +func (m *Ready) XXX_DiscardUnknown() { + xxx_messageInfo_Ready.DiscardUnknown(m) +} + +var xxx_messageInfo_Ready proto.InternalMessageInfo + +func (m *Ready) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func init() { + proto.RegisterEnum("proto.RaftMessage_Type", RaftMessage_Type_name, RaftMessage_Type_value) + proto.RegisterType((*RaftMessage)(nil), "proto.RaftMessage") + proto.RegisterType((*Ready)(nil), "proto.Ready") +} + +func init() { proto.RegisterFile("message.proto", fileDescriptor_33c57e4bae7b9afd) } + +var fileDescriptor_33c57e4bae7b9afd = []byte{ + // 306 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x41, 0x4b, 0xc3, 0x30, + 0x1c, 0xc5, 0x97, 0xad, 0x1b, 0xfa, 0xb7, 0x1b, 0x25, 0x07, 0x2d, 0x1e, 0xb6, 0xb2, 0x53, 0x51, + 0xd6, 0xc2, 0xfc, 0x04, 0x5b, 0x1d, 0x2a, 0xe2, 0x06, 0xe9, 0x04, 0x6f, 0x23, 0x75, 0x59, 0x53, + 0xb4, 0xa4, 0x2c, 0x29, 0xac, 0x9f, 0xc9, 0x2f, 0xb2, 0xa3, 0x67, 0x0f, 0x43, 0xfa, 0x49, 0xa4, + 0xa9, 0xca, 0x4e, 0x79, 0xbf, 0xf0, 0xde, 0x23, 0x2f, 0xd0, 0x4d, 0x99, 0x94, 0x34, 0x66, 0x5e, + 0xb6, 0x15, 0x4a, 0xe0, 0xb6, 0x3e, 0x2e, 0x47, 0x71, 0xa2, 0x78, 0x1e, 0x79, 0xaf, 0x22, 0xf5, + 0x63, 0x11, 0x0b, 0x5f, 0x5f, 0x47, 0xf9, 0x46, 0x93, 0x06, 0xad, 0xea, 0xd4, 0xf0, 0x03, 0xc1, + 0x19, 0xa1, 0x1b, 0xf5, 0x54, 0x77, 0xe1, 0x6b, 0x30, 0x54, 0x91, 0x31, 0x1b, 0x39, 0xc8, 0xed, + 0x8d, 0x2f, 0x6a, 0x97, 0x77, 0xe4, 0xf0, 0x96, 0x45, 0xc6, 0x88, 0x36, 0xe1, 0x73, 0xe8, 0x6c, + 0xb6, 0x22, 0x7d, 0x58, 0xdb, 0x4d, 0x07, 0xb9, 0x06, 0xf9, 0x25, 0x8c, 0xc1, 0x58, 0x53, 0x45, + 0xed, 0x96, 0x83, 0x5c, 0x93, 0x68, 0x3d, 0x0c, 0xc0, 0xa8, 0x92, 0xb8, 0x0b, 0xa7, 0xc1, 0x62, + 0x1e, 0xce, 0xe6, 0xe1, 0x73, 0x68, 0x35, 0xb0, 0x05, 0xe6, 0x94, 0x2c, 0x26, 0xb7, 0xc1, 0x24, + 0x5c, 0xae, 0x96, 0x2f, 0x16, 0xc2, 0x00, 0x9d, 0xbb, 0x99, 0xd6, 0x4d, 0xdc, 0x03, 0xa8, 0xf5, + 0x6a, 0x12, 0x3c, 0x5a, 0xad, 0xa1, 0x80, 0x36, 0x61, 0x74, 0x5d, 0xe0, 0x39, 0x9c, 0xa8, 0xdd, + 0x3d, 0x95, 0x9c, 0x49, 0x1b, 0x39, 0x2d, 0xd7, 0x9c, 0x8e, 0xf7, 0x87, 0x41, 0xe3, 0xeb, 0x30, + 0xb8, 0x3a, 0xda, 0x9f, 0x32, 0xc9, 0xb3, 0xf7, 0x5c, 0xfa, 0x51, 0xa2, 0x76, 0x3c, 0x8f, 0x46, + 0x6f, 0x89, 0xf2, 0xab, 0x97, 0x4b, 0xaf, 0xca, 0x92, 0xff, 0x8e, 0x6a, 0x09, 0x67, 0x49, 0xcc, + 0xd5, 0xdf, 0x92, 0x9a, 0xa6, 0xe6, 0xbe, 0xec, 0xa3, 0xcf, 0xb2, 0x8f, 0xbe, 0xcb, 0x3e, 0x8a, + 0x3a, 0xfa, 0x37, 0x6e, 0x7e, 0x02, 0x00, 0x00, 0xff, 0xff, 0x7c, 0xb0, 0xe7, 0x45, 0x7a, 0x01, + 0x00, 0x00, +} + +func (m *RaftMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RaftMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RaftMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintMessage(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + } + if m.FromId != 0 { + i = encodeVarintMessage(dAtA, i, uint64(m.FromId)) + i-- + dAtA[i] = 0x10 + } + if m.Type != 0 { + i = encodeVarintMessage(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Ready) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Ready) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Ready) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Height != 0 { + i = encodeVarintMessage(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if len(m.TxHashes) > 0 { + for iNdEx := len(m.TxHashes) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.TxHashes[iNdEx].Size() + i -= size + if _, err := m.TxHashes[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintMessage(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintMessage(dAtA []byte, offset int, v uint64) int { + offset -= sovMessage(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RaftMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovMessage(uint64(m.Type)) + } + if m.FromId != 0 { + n += 1 + sovMessage(uint64(m.FromId)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovMessage(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Ready) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.TxHashes) > 0 { + for _, e := range m.TxHashes { + l = e.Size() + n += 1 + l + sovMessage(uint64(l)) + } + } + if m.Height != 0 { + n += 1 + sovMessage(uint64(m.Height)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovMessage(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMessage(x uint64) (n int) { + return sovMessage(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RaftMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RaftMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RaftMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= RaftMessage_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FromId", wireType) + } + m.FromId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FromId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMessage + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMessage + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMessage(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMessage + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMessage + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Ready) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Ready: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Ready: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxHashes", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthMessage + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthMessage + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_meshplus_bitxhub_kit_types.Hash + m.TxHashes = append(m.TxHashes, v) + if err := m.TxHashes[len(m.TxHashes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMessage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipMessage(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthMessage + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthMessage + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMessage(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMessage + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMessage + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMessage + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthMessage + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMessage + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMessage + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMessage = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMessage = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMessage = fmt.Errorf("proto: unexpected end of group") +) diff --git a/internal/plugins/order/etcdraft/proto/message.proto b/internal/plugins/order/etcdraft/proto/message.proto new file mode 100644 index 0000000..5c22d98 --- /dev/null +++ b/internal/plugins/order/etcdraft/proto/message.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package proto; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +message RaftMessage { + enum Type { + CONSENSUS = 0; + BROADCAST_TX = 1; + GET_TX = 2; + GET_TX_ACK = 3; + } + Type type = 1; + uint64 fromId = 2; + bytes data = 3; +} + +message Ready { + repeated bytes txHashes = 1 [(gogoproto.customtype) = "github.com/meshplus/bitxhub-kit/types.Hash", (gogoproto.nullable) = false]; + uint64 height = 2; +} \ No newline at end of file diff --git a/internal/plugins/order/etcdraft/storage.go b/internal/plugins/order/etcdraft/storage.go new file mode 100644 index 0000000..311103c --- /dev/null +++ b/internal/plugins/order/etcdraft/storage.go @@ -0,0 +1,456 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/coreos/etcd/pkg/fileutil" + "github.com/coreos/etcd/raft" + "github.com/coreos/etcd/raft/raftpb" + "github.com/coreos/etcd/snap" + "github.com/coreos/etcd/wal" + "github.com/coreos/etcd/wal/walpb" + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/pkg/errors" +) + +// MaxSnapshotFiles defines max number of etcd/raft snapshot files to retain +// on filesystem. Snapshot files are read from newest to oldest, until first +// intact file is found. The more snapshot files we keep around, the more we +// mitigate the impact of a corrupted snapshots. This is exported for testing +// purpose. This MUST be greater equal than 1. +var MaxSnapshotFiles = 5 + +var restart = false + +var appliedDbKey = []byte("applied") + +// MemoryStorage is currently backed by etcd/raft.MemoryStorage. This interface is +// defined to expose dependencies of fsm so that it may be swapped in the +// future. TODO(jay) Add other necessary methods to this interface once we need +// them in implementation, e.g. ApplySnapshot. +type MemoryStorage interface { + raft.Storage + Append(entries []raftpb.Entry) error + SetHardState(st raftpb.HardState) error + CreateSnapshot(i uint64, cs *raftpb.ConfState, data []byte) (raftpb.Snapshot, error) + Compact(compactIndex uint64) error + ApplySnapshot(snap raftpb.Snapshot) error +} + +// RaftStorage encapsulates storages needed for etcd/raft data, i.e. memory, wal +type RaftStorage struct { + SnapshotCatchUpEntries uint64 + + walDir string + snapDir string + + lg raft.Logger + ram MemoryStorage + wal *wal.WAL + snap *snap.Snapshotter + db storage.Storage // Persistent storage for last-applied raft index + // a queue that keeps track of indices of snapshots on disk + snapshotIndex []uint64 +} + +// CreateStorage attempts to create a storage to persist etcd/raft data. +// If data presents in specified disk, they are loaded to reconstruct storage state. +func CreateStorage( + lg raft.Logger, + walDir string, + snapDir string, + dbDir string, + ram MemoryStorage, +) (*RaftStorage, storage.Storage, error) { + + sn, err := createSnapshotter(snapDir) + if err != nil { + return nil, nil, err + } + + snapshot, err := sn.Load() + if err != nil { + if err == snap.ErrNoSnapshot { + lg.Debugf("No snapshot found at %s", snapDir) + } else { + return nil, nil, errors.Errorf("failed to load snapshot: %s", err) + } + } else { + // snapshot found + lg.Debugf("Loaded snapshot at Term %d and Index %d, Nodes: %+v", + snapshot.Metadata.Term, snapshot.Metadata.Index, snapshot.Metadata.ConfState.Nodes) + } + + w, st, ents, err := createOrReadWAL(lg, walDir, snapshot) + if err != nil { + return nil, nil, errors.Errorf("failed to create or read WAL: %s", err) + } + + if snapshot != nil { + lg.Debugf("Applying snapshot to raft MemoryStorage") + if err := ram.ApplySnapshot(*snapshot); err != nil { + return nil, nil, errors.Errorf("Failed to apply snapshot to memory: %s", err) + } + } + + lg.Debugf("Setting HardState to {Term: %d, Commit: %d}", st.Term, st.Commit) + // MemoryStorage.SetHardState always returns nil + if err := ram.SetHardState(st); err != nil { + panic(err) + } + + lg.Debugf("Appending %d entries to memory storage", len(ents)) + // MemoryStorage.Append always return nil + if err := ram.Append(ents); err != nil { + panic(err) + } + + db, err := leveldb.New(dbDir) + if err != nil { + return nil, nil, errors.Errorf("Failed to new leveldb: %s", err) + } + return &RaftStorage{ + lg: lg, + ram: ram, + wal: w, + snap: sn, + walDir: walDir, + snapDir: snapDir, + db: db, + SnapshotCatchUpEntries: 4, + snapshotIndex: ListSnapshots(lg, snapDir), + }, db, nil +} + +// ListSnapshots returns a list of RaftIndex of snapshots stored on disk. +// If a file is corrupted, rename the file. +func ListSnapshots(logger raft.Logger, snapDir string) []uint64 { + dir, err := os.Open(snapDir) + if err != nil { + logger.Errorf("Failed to open snapshot directory %s: %s", snapDir, err) + return nil + } + defer dir.Close() + + filenames, err := dir.Readdirnames(-1) + if err != nil { + logger.Errorf("Failed to read snapshot files: %s", err) + return nil + } + + snapfiles := []string{} + for i := range filenames { + if strings.HasSuffix(filenames[i], ".snap") { + snapfiles = append(snapfiles, filenames[i]) + } + } + sort.Strings(snapfiles) + + var snapshots []uint64 + for _, snapfile := range snapfiles { + fpath := filepath.Join(snapDir, snapfile) + s, err := snap.Read(fpath) + if err != nil { + logger.Errorf("Snapshot file %s is corrupted: %s", fpath, err) + + broken := fpath + ".broken" + if err = os.Rename(fpath, broken); err != nil { + logger.Errorf("Failed to rename corrupted snapshot file %s to %s: %s", fpath, broken, err) + } else { + logger.Debugf("Renaming corrupted snapshot file %s to %s", fpath, broken) + } + + continue + } + + snapshots = append(snapshots, s.Metadata.Index) + } + + return snapshots +} + +func createSnapshotter(snapDir string) (*snap.Snapshotter, error) { + if !fileutil.Exist(snapDir) { + if err := os.MkdirAll(snapDir, os.ModePerm); err != nil { + return nil, errors.Errorf("failed to mkdir '%s' for snapshot: %s", snapDir, err) + } + } + return snap.New(snapDir), nil +} + +func createOrReadWAL(lg raft.Logger, walDir string, snapshot *raftpb.Snapshot) (w *wal.WAL, st raftpb.HardState, ents []raftpb.Entry, err error) { + if !wal.Exist(walDir) { + lg.Infof("No WAL data found, creating new WAL at path '%s'", walDir) + // TODO(jay_guo) add metadata to be persisted with wal once we need it. + // use case could be data dump and restore on a new node. + w, err := wal.Create(walDir, nil) + if err == os.ErrExist { + lg.Fatalf("programming error, we've just checked that WAL does not exist") + } + + if err != nil { + return nil, st, nil, errors.Errorf("failed to initialize WAL: %s", err) + } + + if err = w.Close(); err != nil { + return nil, st, nil, errors.Errorf("failed to close the WAL just created: %s", err) + } + } else { + restart = true + lg.Infof("Found WAL data at path '%s', replaying it", walDir) + } + + walsnap := walpb.Snapshot{} + if snapshot != nil { + walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term + } + + lg.Debugf("Loading WAL at Term %d and Index %d", walsnap.Term, walsnap.Index) + + var repaired bool + for { + if w, err = wal.Open(walDir, walsnap); err != nil { + return nil, st, nil, errors.Errorf("failed to open WAL: %s", err) + } + + if _, st, ents, err = w.ReadAll(); err != nil { + lg.Warningf("Failed to read WAL: %s", err) + + if errc := w.Close(); errc != nil { + return nil, st, nil, errors.Errorf("failed to close erroneous WAL: %s", errc) + } + + // only repair UnexpectedEOF and only repair once + if repaired || err != io.ErrUnexpectedEOF { + return nil, st, nil, errors.Errorf("failed to read WAL and cannot repair: %s", err) + } + + if !wal.Repair(walDir) { + return nil, st, nil, errors.Errorf("failed to repair WAL: %s", err) + } + + repaired = true + // next loop should be able to open WAL and return + continue + } + + // successfully opened WAL and read all entries, break + break + } + + return w, st, ents, nil +} + +// Snapshot returns the latest snapshot stored in memory +func (rs *RaftStorage) Snapshot() raftpb.Snapshot { + sn, _ := rs.ram.Snapshot() // Snapshot always returns nil error + return sn +} + +// Store persists etcd/raft data +func (rs *RaftStorage) Store(entries []raftpb.Entry, hardstate raftpb.HardState, snapshot raftpb.Snapshot) error { + if err := rs.wal.Save(hardstate, entries); err != nil { + return err + } + + if !raft.IsEmptySnap(snapshot) { + if err := rs.saveSnap(snapshot); err != nil { + return err + } + + if err := rs.ram.ApplySnapshot(snapshot); err != nil { + if err == raft.ErrSnapOutOfDate { + rs.lg.Warningf("Attempted to apply out-of-date snapshot at Term %d and Index %d", + snapshot.Metadata.Term, snapshot.Metadata.Index) + } else { + rs.lg.Fatalf("Unexpected programming error: %s", err) + } + } + } + + if err := rs.ram.Append(entries); err != nil { + return err + } + + return nil +} + +func (rs *RaftStorage) saveSnap(snap raftpb.Snapshot) error { + rs.lg.Infof("Persisting snapshot (term: %d, index: %d) to WAL and disk", snap.Metadata.Term, snap.Metadata.Index) + + // must save the snapshot index to the WAL before saving the + // snapshot to maintain the invariant that we only Open the + // wal at previously-saved snapshot indexes. + walsnap := walpb.Snapshot{ + Index: snap.Metadata.Index, + Term: snap.Metadata.Term, + } + + if err := rs.wal.SaveSnapshot(walsnap); err != nil { + return errors.Errorf("failed to save snapshot to WAL: %s", err) + } + + if err := rs.snap.SaveSnap(snap); err != nil { + return errors.Errorf("failed to save snapshot to disk: %s", err) + } + + rs.lg.Debugf("Releasing lock to wal files prior to %d", snap.Metadata.Index) + if err := rs.wal.ReleaseLockTo(snap.Metadata.Index); err != nil { + return err + } + + return nil +} + +// TakeSnapshot takes a snapshot at index i from MemoryStorage, and persists it to wal and disk. +func (rs *RaftStorage) TakeSnapshot(i uint64, cs raftpb.ConfState, data []byte) error { + rs.lg.Debugf("Creating snapshot at index %d from MemoryStorage", i) + snap, err := rs.ram.CreateSnapshot(i, &cs, data) + if err != nil { + return errors.Errorf("failed to create snapshot from MemoryStorage: %s", err) + } + + if err = rs.saveSnap(snap); err != nil { + return err + } + + rs.snapshotIndex = append(rs.snapshotIndex, snap.Metadata.Index) + + // Keep some entries in memory for slow followers to catchup + if i > rs.SnapshotCatchUpEntries { + compacti := i - rs.SnapshotCatchUpEntries + rs.lg.Debugf("Purging in-memory raft entries prior to %d", compacti) + if err = rs.ram.Compact(compacti); err != nil { + if err == raft.ErrCompacted { + rs.lg.Warningf("Raft entries prior to %d are already purged", compacti) + } else { + rs.lg.Fatalf("Failed to purge raft entries: %s", err) + } + } + } + + rs.lg.Infof("Snapshot is taken at index %d", i) + + rs.gc() + return nil +} + +// gc collects etcd/raft garbage files, namely wal and snapshot files +func (rs *RaftStorage) gc() { + if len(rs.snapshotIndex) < MaxSnapshotFiles { + rs.lg.Debugf("Snapshots on disk (%d) < limit (%d), no need to purge wal/snapshot", + len(rs.snapshotIndex), MaxSnapshotFiles) + return + } + + rs.snapshotIndex = rs.snapshotIndex[len(rs.snapshotIndex)-MaxSnapshotFiles:] + + rs.purgeWAL() + rs.purgeSnap() +} + +func (rs *RaftStorage) purgeWAL() { + retain := rs.snapshotIndex[0] + + walFiles, err := fileutil.ReadDir(rs.walDir) + if err != nil { + rs.lg.Errorf("Failed to read WAL directory %s: %s", rs.walDir, err) + } + + var files []string + for _, f := range walFiles { + if !strings.HasSuffix(f, ".wal") { + continue + } + + var seq, index uint64 + fmt.Sscanf(f, "%016x-%016x.wal", &seq, &index) + if index >= retain { + break + } + + files = append(files, filepath.Join(rs.walDir, f)) + } + + if len(files) <= 1 { + // we need to keep one wal segment with index smaller than snapshot. + // see comment on wal.ReleaseLockTo for the more details. + return + } + + rs.purge(files[:len(files)-1]) +} + +func (rs *RaftStorage) purgeSnap() { + snapFiles, err := fileutil.ReadDir(rs.snapDir) + if err != nil { + rs.lg.Errorf("Failed to read Snapshot directory %s: %s", rs.snapDir, err) + } + + var files []string + for _, f := range snapFiles { + if !strings.HasSuffix(f, ".snap") { + if strings.HasPrefix(f, ".broken") { + rs.lg.Warningf("Found broken snapshot file %s, it can be removed manually", f) + } + + continue + } + + files = append(files, filepath.Join(rs.snapDir, f)) + } + + l := len(files) + if l <= MaxSnapshotFiles { + return + } + + rs.purge(files[:l-MaxSnapshotFiles]) // retain last MaxSnapshotFiles snapshot files +} + +func (rs *RaftStorage) purge(files []string) { + for _, file := range files { + l, err := fileutil.TryLockFile(file, os.O_WRONLY, fileutil.PrivateFileMode) + if err != nil { + rs.lg.Debugf("Failed to lock %s, abort purging", file) + break + } + + if err = os.Remove(file); err != nil { + rs.lg.Errorf("Failed to remove %s: %s", file, err) + } else { + rs.lg.Debugf("Purged file %s", file) + } + + if err = l.Close(); err != nil { + rs.lg.Errorf("Failed to close file lock %s: %s", l.Name(), err) + } + } +} + +// ApplySnapshot applies snapshot to local memory storage +func (rs *RaftStorage) ApplySnapshot(snap raftpb.Snapshot) { + if err := rs.ram.ApplySnapshot(snap); err != nil { + if err == raft.ErrSnapOutOfDate { + rs.lg.Warningf("Attempted to apply out-of-date snapshot at Term %d and Index %d", + snap.Metadata.Term, snap.Metadata.Index) + } else { + rs.lg.Fatalf("Unexpected programming error: %s", err) + } + } +} + +// Close closes storage +func (rs *RaftStorage) Close() error { + if err := rs.wal.Close(); err != nil { + return err + } + + return nil +} diff --git a/internal/plugins/order/etcdraft/txpool/txpool.go b/internal/plugins/order/etcdraft/txpool/txpool.go new file mode 100644 index 0000000..82d3aa9 --- /dev/null +++ b/internal/plugins/order/etcdraft/txpool/txpool.go @@ -0,0 +1,417 @@ +package txpool + +import ( + "container/list" + "context" + "fmt" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/meshplus/bitxhub/pkg/order" + + "github.com/Rican7/retry" + "github.com/Rican7/retry/strategy" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + raftproto "github.com/meshplus/bitxhub/internal/plugins/order/etcdraft/proto" + "github.com/meshplus/bitxhub/pkg/peermgr" + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/sirupsen/logrus" +) + +type TxPool struct { + sync.RWMutex + nodeId uint64 //node id + height uint64 //current block height + pendingTxs *list.List //pending tx pool + presenceTxs sync.Map //tx cache + readyC chan *raftproto.Ready + peerMgr peermgr.PeerManager //network manager + logger logrus.FieldLogger //logger + reqLookUp *order.ReqLookUp // bloom filter + storage storage.Storage // storage pending tx + getTransactionFunc func(hash types.Hash) (*pb.Transaction, error) + isExecuting bool //only raft leader can execute + packSize int //maximum number of transaction packages + blockTick time.Duration //block packed period + + ctx context.Context + cancel context.CancelFunc +} + +//New txpool +func New(config *order.Config, storage storage.Storage, packSize int, blockTick time.Duration) (*TxPool, chan *raftproto.Ready) { + readyC := make(chan *raftproto.Ready) + reqLookUp, err := order.NewReqLookUp(storage, config.Logger) + if err != nil { + return nil, nil + } + ctx, cancel := context.WithCancel(context.Background()) + return &TxPool{ + nodeId: config.ID, + peerMgr: config.PeerMgr, + logger: config.Logger, + readyC: readyC, + height: config.Applied, + pendingTxs: list.New(), + reqLookUp: reqLookUp, + storage: storage, + packSize: packSize, + blockTick: blockTick, + getTransactionFunc: config.GetTransactionFunc, + ctx: ctx, + cancel: cancel, + }, readyC +} + +//Add pending transaction into txpool +func (tp *TxPool) AddPendingTx(tx *pb.Transaction) error { + hash := tx.TransactionHash + if e := tp.get(hash); e != nil { + return nil + } + //look up by bloom filter + if ok := tp.reqLookUp.LookUp(hash.Bytes()); ok { + //find the tx again by ledger if hash in bloom filter + if tx, _ := tp.getTransactionFunc(hash); tx != nil { + return nil + } + } + //add pending tx + tp.pushBack(hash, tx) + return nil +} + +//Current txpool's size +func (tp *TxPool) PoolSize() int { + tp.RLock() + defer tp.RUnlock() + return tp.pendingTxs.Len() +} + +//Remove stored transactions +func (tp *TxPool) RemoveTxs(hashes []types.Hash, isLeader bool) { + if isLeader { + tp.BatchDelete(hashes) + } + for _, hash := range hashes { + if !isLeader { + if e := tp.get(hash); e != nil { + tp.Lock() + tp.pendingTxs.Remove(e) + tp.Unlock() + } + } + tp.presenceTxs.Delete(hash) + } +} + +//Store the bloom filter +func (tp *TxPool) BuildReqLookUp() { + if err := tp.reqLookUp.Build(); err != nil { + tp.logger.Errorf("bloom filter persistence error:", err) + } +} + +//Check the txpool status, only leader node can run Execute() +func (tp *TxPool) CheckExecute(isLeader bool) { + if isLeader { + if !tp.isExecuting { + go tp.execute() + } + } else { + if tp.isExecuting { + tp.cancel() + } + } +} + +// Schedule to collect txs to the ready channel +func (tp *TxPool) execute() { + tp.isExecuting = true + tp.pendingTxs.Init() + tp.presenceTxs = sync.Map{} + ticker := time.NewTicker(tp.blockTick) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + ready := tp.ready() + if ready == nil { + continue + } + tp.logger.WithFields(logrus.Fields{ + "height": ready.Height, + }).Debugln("block will be generated") + tp.readyC <- ready + case <-tp.ctx.Done(): + tp.isExecuting = false + tp.logger.Infoln("Done txpool execute") + return + } + } + +} + +func (tp *TxPool) ready() *raftproto.Ready { + tp.Lock() + defer tp.Unlock() + l := tp.pendingTxs.Len() + if l == 0 { + return nil + } + + var size int + if l > tp.packSize { + size = tp.packSize + } else { + size = l + } + hashes := make([]types.Hash, 0, size) + for i := 0; i < size; i++ { + front := tp.pendingTxs.Front() + tx := front.Value.(*pb.Transaction) + hashes = append(hashes, tx.TransactionHash) + tp.pendingTxs.Remove(front) + } + height := tp.UpdateHeight() + return &raftproto.Ready{ + TxHashes: hashes, + Height: height, + } +} + +//Add the block height +func (tp *TxPool) UpdateHeight() uint64 { + return atomic.AddUint64(&tp.height, 1) +} + +//Get current block height +func (tp *TxPool) GetHeight() uint64 { + return atomic.LoadUint64(&tp.height) +} + +//Get the transaction by txpool or ledger +func (tp *TxPool) GetTx(hash types.Hash, findByStore bool) (*pb.Transaction, bool) { + if e := tp.get(hash); e != nil { + return e.Value.(*pb.Transaction), true + } + if findByStore { + // find by txpool store + tx, ok := tp.load(hash) + if ok { + return tx, true + } + // find by ledger + tx, err := tp.getTransactionFunc(hash) + if err != nil { + return nil, false + } + return tx, true + } + return nil, false +} + +//Broadcast the new transaction to other nodes +func (tp *TxPool) Broadcast(tx *pb.Transaction) error { + data, err := tx.Marshal() + if err != nil { + return err + } + rm := &raftproto.RaftMessage{ + Type: raftproto.RaftMessage_BROADCAST_TX, + Data: data, + } + cmData, err := rm.Marshal() + if err != nil { + return err + } + msg := &pb.Message{ + Type: pb.Message_CONSENSUS, + Data: cmData, + } + + for id := range tp.peerMgr.Peers() { + if id == tp.nodeId { + continue + } + if err := tp.peerMgr.Send(id, msg); err != nil { + tp.logger.Debugln("send transaction error:", err) + continue + } + } + return nil +} + +// Fetch tx by local txpool or network +func (tp *TxPool) FetchTx(hash types.Hash) *pb.Transaction { + if tx, ok := tp.GetTx(hash, false); ok { + return tx + } + raftMessage := &raftproto.RaftMessage{ + Type: raftproto.RaftMessage_GET_TX, + FromId: tp.nodeId, + Data: hash.Bytes(), + } + rmData, err := raftMessage.Marshal() + if err != nil { + return nil + } + m := &pb.Message{ + Type: pb.Message_CONSENSUS, + Data: rmData, + } + + asyncGet := func() (tx *pb.Transaction, err error) { + for id := range tp.peerMgr.Peers() { + if id == tp.nodeId { + continue + } + if tx, ok := tp.GetTx(hash, false); ok { + return tx, nil + } + if err := tp.peerMgr.Send(id, m); err != nil { + return nil, err + } + } + return nil, fmt.Errorf("can't get transaction: %s", hash.String()) + } + + var tx *pb.Transaction + if err := retry.Retry(func(attempt uint) (err error) { + tx, err = asyncGet() + if err != nil { + //retry times > 2 + if attempt > 2 { + tp.logger.Debugln(err) + } + return err + } + return nil + }, strategy.Wait(200*time.Millisecond)); err != nil { + tp.logger.Errorln(err) + } + return tx +} + +// Fetch tx by local txpool or network +func (tp *TxPool) FetchBlock(height uint64) (*pb.Block, error) { + get := func(height uint64) (block *pb.Block, err error) { + for id := range tp.peerMgr.Peers() { + block, err = tp.getBlock(id, int(height)) + if err != nil { + continue + } + return block, nil + } + return nil, fmt.Errorf("can't get block: %d", height) + } + + var block *pb.Block + if err := retry.Retry(func(attempt uint) (err error) { + block, err = get(height) + if err != nil { + tp.logger.Debugln(err) + return err + } + return nil + }, strategy.Wait(200*time.Millisecond), strategy.Limit(1)); err != nil { + return nil, err + } + return block, nil +} + +//Get block by network +func (tp *TxPool) getBlock(id uint64, i int) (*pb.Block, error) { + m := &pb.Message{ + Type: pb.Message_GET_BLOCK, + Data: []byte(strconv.Itoa(i)), + } + + res, err := tp.peerMgr.SyncSend(id, m) + if err != nil { + return nil, err + } + + block := &pb.Block{} + if err := block.Unmarshal(res.Data); err != nil { + return nil, err + } + + return block, nil +} + +func (tp *TxPool) get(key types.Hash) *list.Element { + e, ok := tp.presenceTxs.Load(key) + if ok { + return e.(*list.Element) + } + return nil +} + +func (tp *TxPool) pushBack(key types.Hash, value interface{}) *list.Element { + tp.Lock() + defer tp.Unlock() + e := tp.pendingTxs.PushBack(value) + tp.presenceTxs.Store(key, e) + return e +} + +var transactionKey = []byte("tx-") + +func compositeKey(prefix []byte, value interface{}) []byte { + return append(prefix, []byte(fmt.Sprintf("%v", value))...) +} +func (tp *TxPool) store(tx *pb.Transaction) { + txKey := compositeKey(transactionKey, tx.TransactionHash.Bytes()) + txData, _ := tx.Marshal() + if err := tp.storage.Put(txKey, txData); err != nil { + tp.logger.Error("store tx error:", err) + } +} +func (tp *TxPool) load(hash types.Hash) (*pb.Transaction, bool) { + txKey := compositeKey(transactionKey, hash.Bytes()) + txData, err := tp.storage.Get(txKey) + if err != nil { + return nil, false + } + var tx pb.Transaction + if err := tx.Unmarshal(txData); err != nil { + tp.logger.Error(err) + return nil, false + } + return &tx, true +} + +//batch store txs +func (tp *TxPool) BatchStore(hashes []types.Hash) { + batch := tp.storage.NewBatch() + for _, hash := range hashes { + e := tp.get(hash) + if e == nil { + continue + } + tx := e.Value.(*pb.Transaction) + txKey := compositeKey(transactionKey, hash.Bytes()) + txData, _ := tx.Marshal() + batch.Put(txKey, txData) + } + if err := batch.Commit(); err != nil { + tp.logger.Fatalf("storage batch tx error:", err) + } +} + +//batch delete txs +func (tp *TxPool) BatchDelete(hashes []types.Hash) { + batch := tp.storage.NewBatch() + for _, hash := range hashes { + txKey := compositeKey(transactionKey, hash.Bytes()) + batch.Delete(txKey) + } + if err := batch.Commit(); err != nil { + tp.logger.Fatalf("storage batch tx error:", err) + } +} diff --git a/internal/plugins/order/solo/node.go b/internal/plugins/order/solo/node.go new file mode 100644 index 0000000..18a5837 --- /dev/null +++ b/internal/plugins/order/solo/node.go @@ -0,0 +1,161 @@ +package main + +import ( + "container/list" + "context" + "fmt" + "sync" + "time" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/order" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/sirupsen/logrus" +) + +type Node struct { + sync.RWMutex + height uint64 // current block height + pendingTxs *list.List //pending tx pool + commitC chan *pb.Block //block channel + logger logrus.FieldLogger //logger + reqLookUp *order.ReqLookUp //bloom filter + getTransactionFunc func(hash types.Hash) (*pb.Transaction, error) + + packSize int //maximum number of transaction packages + blockTick time.Duration //block packed period + + ctx context.Context + cancel context.CancelFunc +} + +func (n *Node) Start() error { + go n.execute() + return nil +} + +func (n *Node) Stop() { + n.cancel() +} + +func (n *Node) Prepare(tx *pb.Transaction) error { + hash := tx.TransactionHash + if ok := n.reqLookUp.LookUp(hash.Bytes()); ok { + if tx, _ := n.getTransactionFunc(hash); tx != nil { + return nil + } + } + n.pushBack(tx) + return nil +} + +func (n *Node) Commit() chan *pb.Block { + return n.commitC +} + +func (n *Node) Step(ctx context.Context, msg []byte) error { + return nil +} + +func (n *Node) Ready() bool { + return true +} + +func (n *Node) ReportState(height uint64, hash types.Hash) { + if err := n.reqLookUp.Build(); err != nil { + n.logger.Errorf("bloom filter persistence error:", err) + } + + if height%10 == 0 { + n.logger.WithFields(logrus.Fields{ + "height": height, + "hash": hash.ShortString(), + }).Info("Report checkpoint") + } +} + +func (n *Node) Quorum() uint64 { + return 1 +} + +func NewNode(opts ...order.Option) (order.Order, error) { + config, err := order.GenerateConfig(opts...) + if err != nil { + return nil, fmt.Errorf("generate config: %w", err) + } + storage, err := leveldb.New(config.StoragePath) + if err != nil { + return nil, fmt.Errorf("new leveldb: %w", err) + } + reqLookUp, err := order.NewReqLookUp(storage, config.Logger) + if err != nil { + return nil, fmt.Errorf("new bloom filter: %w", err) + } + ctx, cancel := context.WithCancel(context.Background()) + return &Node{ + height: config.Applied, + pendingTxs: list.New(), + commitC: make(chan *pb.Block, 1024), + packSize: 500, + blockTick: 500 * time.Millisecond, + reqLookUp: reqLookUp, + getTransactionFunc: config.GetTransactionFunc, + logger: config.Logger, + ctx: ctx, + cancel: cancel, + }, nil +} + +// Schedule to collect txs to the ready channel +func (n *Node) execute() { + ticker := time.NewTicker(n.blockTick) + defer ticker.Stop() + for { + select { + case <-ticker.C: + n.Lock() + l := n.pendingTxs.Len() + if l == 0 { + n.Unlock() + continue + } + + var size int + if l > n.packSize { + size = n.packSize + } else { + size = l + } + txs := make([]*pb.Transaction, 0, size) + for i := 0; i < size; i++ { + front := n.pendingTxs.Front() + tx := front.Value.(*pb.Transaction) + txs = append(txs, tx) + n.pendingTxs.Remove(front) + } + n.height++ + n.Unlock() + + block := &pb.Block{ + BlockHeader: &pb.BlockHeader{ + Version: []byte("1.0.0"), + Number: n.height, + Timestamp: time.Now().UnixNano(), + }, + Transactions: txs, + } + n.commitC <- block + case <-n.ctx.Done(): + n.logger.Infoln("Done txpool execute") + return + } + } + +} + +func (n *Node) pushBack(value interface{}) *list.Element { + n.Lock() + defer n.Unlock() + return n.pendingTxs.PushBack(value) +} diff --git a/internal/plugins/order/solo/node_test.go b/internal/plugins/order/solo/node_test.go new file mode 100644 index 0000000..5ff2e35 --- /dev/null +++ b/internal/plugins/order/solo/node_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/order" + "github.com/stretchr/testify/require" +) + +const to = "0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997" + +func TestNode_Start(t *testing.T) { + repoRoot, err := ioutil.TempDir("", "node") + defer os.RemoveAll(repoRoot) + assert.Nil(t, err) + order, err := NewNode( + order.WithRepoRoot(repoRoot), + order.WithStoragePath(repo.GetStoragePath(repoRoot, "order")), + order.WithLogger(log.NewWithModule("consensus")), + order.WithApplied(1), + ) + require.Nil(t, err) + + err = order.Start() + require.Nil(t, err) + + privKey, err := asym.GenerateKey(asym.ECDSASecp256r1) + require.Nil(t, err) + + from, err := privKey.PublicKey().Address() + require.Nil(t, err) + + tx := &pb.Transaction{ + From: from, + To: types.String2Address(to), + Data: &pb.TransactionData{ + Amount: 10, + }, + Timestamp: time.Now().UnixNano(), + Nonce: rand.Int63(), + } + err = tx.Sign(privKey) + require.Nil(t, err) + + for { + time.Sleep(200 * time.Millisecond) + if order.Ready() { + break + } + } + + err = order.Prepare(tx) + require.Nil(t, err) + + block := <-order.Commit() + require.Equal(t, uint64(2), block.BlockHeader.Number) + require.Equal(t, 1, len(block.Transactions)) + + order.Stop() +} diff --git a/internal/plugins/plugins.go b/internal/plugins/plugins.go new file mode 100644 index 0000000..fa5661b --- /dev/null +++ b/internal/plugins/plugins.go @@ -0,0 +1,38 @@ +package orderplg + +import ( + "fmt" + "path/filepath" + "plugin" + + "github.com/meshplus/bitxhub/pkg/order" +) + +//Load order plugin +func New(opts ...order.Option) (order.Order, error) { + config, err := order.GenerateConfig(opts...) + if err != nil { + return nil, err + } + pluginPath := config.PluginPath + + if !filepath.IsAbs(pluginPath) { + pluginPath = filepath.Join(config.RepoRoot, pluginPath) + } + + p, err := plugin.Open(pluginPath) + if err != nil { + return nil, fmt.Errorf("plugin open error: %s", err) + } + + m, err := p.Lookup("NewNode") + if err != nil { + return nil, fmt.Errorf("plugin lookup error: %s", err) + } + + NewNode, ok := m.(func(...order.Option) (order.Order, error)) + if !ok { + return nil, fmt.Errorf("assert NewOrder error") + } + return NewNode(opts...) +} diff --git a/internal/repo/cert.go b/internal/repo/cert.go new file mode 100644 index 0000000..88eb3fa --- /dev/null +++ b/internal/repo/cert.go @@ -0,0 +1,58 @@ +package repo + +import ( + "crypto/x509" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/meshplus/bitxhub/pkg/cert" +) + +type Certs struct { + NodeCertData []byte + AgencyCertData []byte + CACertData []byte + NodeCert *x509.Certificate + AgencyCert *x509.Certificate + CACert *x509.Certificate +} + +func loadCerts(repoRoot string) (*Certs, error) { + nodeCert, nodeCertData, err := loadCert(filepath.Join(repoRoot, "certs/node.cert")) + if err != nil { + return nil, fmt.Errorf("load node cert: %w", err) + } + + agencyCert, agencyCertData, err := loadCert(filepath.Join(repoRoot, "certs/agency.cert")) + if err != nil { + return nil, fmt.Errorf("load agency cert: %w", err) + } + caCert, caCertData, err := loadCert(filepath.Join(repoRoot, "certs/ca.cert")) + if err != nil { + return nil, fmt.Errorf("load ca cert: %w", err) + } + + return &Certs{ + NodeCertData: nodeCertData, + AgencyCertData: agencyCertData, + CACertData: caCertData, + NodeCert: nodeCert, + AgencyCert: agencyCert, + CACert: caCert, + }, nil +} + +func loadCert(certPath string) (*x509.Certificate, []byte, error) { + data, err := ioutil.ReadFile(certPath) + if err != nil { + return nil, nil, fmt.Errorf("read cert: %w", err) + } + + cert, err := cert.ParseCert(data) + if err != nil { + return nil, nil, fmt.Errorf("parse cert: %w", err) + } + + return cert, data, nil +} diff --git a/internal/repo/config.go b/internal/repo/config.go new file mode 100755 index 0000000..400c39b --- /dev/null +++ b/internal/repo/config.go @@ -0,0 +1,190 @@ +package repo + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "time" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +) + +const ( + // defaultPathName is the default config dir name + defaultPathName = ".bitxhub" + // defaultPathRoot is the path to the default config dir location. + defaultPathRoot = "~/" + defaultPathName + // envDir is the environment variable used to change the path root. + envDir = "BITXHUB_PATH" + // Config name + configName = "bitxhub.toml" + // key name + KeyName = "key.json" + // API name + APIName = "api" +) + +type Config struct { + RepoRoot string `json:"repo_root"` + Title string `json:"tile"` + Solo bool `json:"solo"` + Port `json:"port"` + PProf `json:"pprof"` + Gateway `json:"gateway"` + Log `json:"log"` + Cert `json:"cert"` + Genesis `json:"genesis"` + Txpool `json:"txpool"` + Order `json:"order"` +} + +type Port struct { + Grpc int64 `toml:"grpc" json:"grpc"` + Gateway int64 `toml:"gateway" json:"gateway"` + PProf int64 `toml:"pprof" json:"pprof"` +} + +type PProf struct { + Enable bool +} + +type Gateway struct { + AllowedOrigins []string `mapstructure:"allowed_origins"` +} + +type Log struct { + Level string `toml:"level" json:"level"` + Dir string `toml:"dir" json:"dir"` + Filename string `toml:"filename" json:"filename"` + ReportCaller bool `mapstructure:"report_caller" json:"report_caller"` + Module LogModule `toml:"module" json:"module"` +} + +type LogModule struct { + P2P string `toml:"p2p" json:"p2p"` + Consensus string `toml:"consensus" json:"consensus"` + Executor string `toml:"executor" json:"executor"` + Router string `toml:"router" json:"router"` + API string `toml:"api" json:"api"` + CoreAPI string `mapstructure:"coreapi" toml:"coreapi" json:"coreapi"` +} + +type Genesis struct { + Addresses []string `json:"addresses" toml:"addresses"` +} + +type Cert struct { + Verify bool `toml:"verify" json:"verify"` +} + +type Txpool struct { + BatchSize int `mapstructure:"batch_size" json:"batch_size"` + BatchTimeout time.Duration `mapstructure:"batch_timeout" json:"batch_timeout"` +} + +type Order struct { + Plugin string `toml:"plugin" json:"plugin"` +} + +func (c *Config) Bytes() ([]byte, error) { + ret, err := json.Marshal(c) + if err != nil { + return nil, err + } + + return ret, nil +} + +func DefaultConfig() (*Config, error) { + return &Config{ + Title: "BitXHub configuration file", + Solo: false, + Port: Port{ + Grpc: 60011, + Gateway: 9091, + PProf: 53121, + }, + PProf: PProf{Enable: false}, + Gateway: Gateway{AllowedOrigins: []string{"*"}}, + Log: Log{ + Level: "info", + Dir: "logs", + Filename: "bitxhub.log", + Module: LogModule{ + P2P: "info", + Consensus: "debug", + Executor: "info", + Router: "info", + API: "info", + CoreAPI: "info", + }, + }, + Cert: Cert{Verify: true}, + Txpool: Txpool{ + BatchSize: 500, + BatchTimeout: 500 * time.Millisecond, + }, + Order: Order{ + Plugin: "plugins/raft.so", + }, + }, nil +} + +func UnmarshalConfig(repoRoot string) (*Config, error) { + viper.SetConfigFile(filepath.Join(repoRoot, configName)) + viper.SetConfigType("toml") + viper.AutomaticEnv() + viper.SetEnvPrefix("BITXHUB") + replacer := strings.NewReplacer(".", "_") + viper.SetEnvKeyReplacer(replacer) + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + config, err := DefaultConfig() + if err != nil { + return nil, err + } + + if err := viper.Unmarshal(config); err != nil { + return nil, err + } + + config.RepoRoot = repoRoot + + return config, nil +} + +func ReadConfig(path string, config interface{}) error { + v := viper.New() + v.SetConfigFile(path) + v.SetConfigType("toml") + if err := v.ReadInConfig(); err != nil { + return err + } + + if err := v.Unmarshal(config); err != nil { + return err + } + + return nil +} + +func PathRoot() (string, error) { + dir := os.Getenv(envDir) + var err error + if len(dir) == 0 { + dir, err = homedir.Expand(defaultPathRoot) + } + return dir, err +} + +func PathRootWithDefault(path string) (string, error) { + if len(path) == 0 { + return PathRoot() + } + + return path, nil +} diff --git a/internal/repo/config_test.go b/internal/repo/config_test.go new file mode 100644 index 0000000..bffaa97 --- /dev/null +++ b/internal/repo/config_test.go @@ -0,0 +1,22 @@ +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadConfig(t *testing.T) { + path := "../../config/network.toml" + cfg := &NetworkConfig{} + err := ReadConfig(path, cfg) + assert.Nil(t, err) + + assert.True(t, 1 == cfg.ID) + assert.True(t, 4 == cfg.N) + assert.True(t, 4 == len(cfg.Nodes)) + + for i, node := range cfg.Nodes { + assert.True(t, uint64(i+1) == node.ID) + } +} diff --git a/internal/repo/init.go b/internal/repo/init.go new file mode 100644 index 0000000..6175793 --- /dev/null +++ b/internal/repo/init.go @@ -0,0 +1,39 @@ +package repo + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/gobuffalo/packd" + "github.com/gobuffalo/packr" + "github.com/meshplus/bitxhub-kit/fileutil" +) + +const ( + packPath = "../../config" +) + +func Initialize(repoRoot string) error { + box := packr.NewBox(packPath) + if err := box.Walk(func(s string, file packd.File) error { + p := filepath.Join(repoRoot, s) + dir := filepath.Dir(p) + if _, err := os.Stat(dir); os.IsNotExist(err) { + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + } + + return ioutil.WriteFile(p, []byte(file.String()), 0644) + }); err != nil { + return err + } + + return nil +} + +func Initialized(repoRoot string) bool { + return fileutil.Exist(filepath.Join(repoRoot, configName)) +} diff --git a/internal/repo/key.go b/internal/repo/key.go new file mode 100644 index 0000000..7b2f3cf --- /dev/null +++ b/internal/repo/key.go @@ -0,0 +1,116 @@ +package repo + +import ( + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/libp2p/go-libp2p-core/crypto" + crypto2 "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-kit/fileutil" + "github.com/meshplus/bitxhub-kit/key" + "github.com/meshplus/bitxhub/pkg/cert" + "github.com/tidwall/gjson" +) + +type Key struct { + PID string `json:"pid"` + Address string `json:"address"` + PrivKey crypto2.PrivateKey `json:"priv_key"` + Libp2pPrivKey crypto.PrivKey +} + +func LoadKey(path string) (*Key, error) { + keyPath := filepath.Join(path) + data, err := ioutil.ReadFile(keyPath) + if err != nil { + return nil, err + } + + pid := gjson.GetBytes(data, "pid") + address := gjson.GetBytes(data, "address") + privKeyString := gjson.GetBytes(data, "priv_key") + + fmt.Println(string(data)) + fmt.Println(address) + libp2pPrivKeyData, err := crypto.ConfigDecodeKey(privKeyString.String()) + if err != nil { + return nil, err + } + + libp2pPrivKey, err := crypto.UnmarshalPrivateKey(libp2pPrivKeyData) + if err != nil { + return nil, err + } + + raw, err := libp2pPrivKey.Raw() + if err != nil { + return nil, err + } + + privKey, err := x509.ParseECPrivateKey(raw) + if err != nil { + return nil, err + } + + return &Key{ + PID: pid.String(), + Address: address.String(), + PrivKey: &ecdsa.PrivateKey{K: privKey}, + Libp2pPrivKey: libp2pPrivKey, + }, nil +} + +func loadPrivKey(repoRoot string) (*Key, error) { + data, err := ioutil.ReadFile(filepath.Join(repoRoot, "certs/node.priv")) + if err != nil { + return nil, err + } + + stdPriv, err := cert.ParsePrivateKey(data) + if err != nil { + return nil, err + } + + privKey := &ecdsa.PrivateKey{K: stdPriv} + + address, err := privKey.PublicKey().Address() + if err != nil { + return nil, err + } + + libp2pPrivKey, _, err := crypto.ECDSAKeyPairFromKey(stdPriv) + if err != nil { + return nil, err + } + + pid := gjson.Get(string(data), "pid").String() + + keyPath := filepath.Join(repoRoot, KeyName) + + if !fileutil.Exist(keyPath) { + k, err := key.NewWithPrivateKey(privKey, "bitxhub") + if err != nil { + return nil, err + } + + data, err := k.Pretty() + if err != nil { + return nil, err + } + + if err := ioutil.WriteFile(keyPath, []byte(data), os.ModePerm); err != nil { + return nil, err + } + } + + return &Key{ + PID: pid, + Address: address.Hex(), + PrivKey: privKey, + Libp2pPrivKey: libp2pPrivKey, + }, nil +} diff --git a/internal/repo/key_test.go b/internal/repo/key_test.go new file mode 100644 index 0000000..e0281bf --- /dev/null +++ b/internal/repo/key_test.go @@ -0,0 +1 @@ +package repo diff --git a/internal/repo/network.go b/internal/repo/network.go new file mode 100644 index 0000000..ae08397 --- /dev/null +++ b/internal/repo/network.go @@ -0,0 +1,78 @@ +package repo + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/libp2p/go-libp2p-core/peer" + ma "github.com/multiformats/go-multiaddr" +) + +type NetworkConfig struct { + ID uint64 + N uint64 + LocalAddr string + Nodes []*NetworkNodes + OtherNodes map[uint64]*peer.AddrInfo +} + +type NetworkNodes struct { + ID uint64 + Addr string +} + +// AddrToPeerInfo transfer addr to PeerInfo +// addr example: "/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64" +func AddrToPeerInfo(multiAddr string) (*peer.AddrInfo, error) { + maddr, err := ma.NewMultiaddr(multiAddr) + if err != nil { + return nil, err + } + + return peer.AddrInfoFromP2pAddr(maddr) +} + +func loadNetworkConfig(repoRoot string) (*NetworkConfig, error) { + networkConfig := &NetworkConfig{} + if err := ReadConfig(filepath.Join(repoRoot, "network.toml"), networkConfig); err != nil { + return nil, err + } + + if uint64(len(networkConfig.Nodes)) != networkConfig.N { + return nil, fmt.Errorf("wrong nodes number") + } + + for _, node := range networkConfig.Nodes { + if node.ID == networkConfig.ID { + networkConfig.LocalAddr = node.Addr + } + } + + if networkConfig.LocalAddr == "" { + return nil, fmt.Errorf("lack of local address") + } + + idx := strings.LastIndex(networkConfig.LocalAddr, "/p2p/") + if idx == -1 { + return nil, fmt.Errorf("pid is not existed in bootstrap") + } + + networkConfig.LocalAddr = networkConfig.LocalAddr[:idx] + + nodes := networkConfig.Nodes + m := make(map[uint64]*peer.AddrInfo) + for _, node := range nodes { + if node.ID != networkConfig.ID { + addr, err := AddrToPeerInfo(node.Addr) + if err != nil { + return nil, fmt.Errorf("wrong network addr: %w", err) + } + m[node.ID] = addr + } + } + + networkConfig.OtherNodes = m + + return networkConfig, nil +} diff --git a/internal/repo/repo.go b/internal/repo/repo.go new file mode 100755 index 0000000..5ea8e5d --- /dev/null +++ b/internal/repo/repo.go @@ -0,0 +1,65 @@ +package repo + +import ( + "fmt" + "io/ioutil" + "path/filepath" +) + +type Repo struct { + Config *Config + NetworkConfig *NetworkConfig + Key *Key + Certs *Certs +} + +func Load(repoRoot string) (*Repo, error) { + config, err := UnmarshalConfig(repoRoot) + if err != nil { + return nil, err + } + + networkConfig, err := loadNetworkConfig(repoRoot) + if err != nil { + return nil, fmt.Errorf("load network config: %w", err) + } + + certs, err := loadCerts(repoRoot) + if err != nil { + return nil, err + } + + key, err := loadPrivKey(repoRoot) + if err != nil { + return nil, fmt.Errorf("load private key: %w", err) + } + + return &Repo{ + Config: config, + NetworkConfig: networkConfig, + Key: key, + Certs: certs, + }, nil +} + +func GetAPI(repoRoot string) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(repoRoot, APIName)) + if err != nil { + return "", err + } + + return string(data), nil +} + +func GetKeyPath(repoRoot string) string { + return filepath.Join(repoRoot, KeyName) +} + +func GetStoragePath(repoRoot string, subPath ...string) string { + p := filepath.Join(repoRoot, "storage") + for _, s := range subPath { + p = filepath.Join(p, s) + } + + return p +} diff --git a/internal/repo/repo_test.go b/internal/repo/repo_test.go new file mode 100644 index 0000000..7d48d2c --- /dev/null +++ b/internal/repo/repo_test.go @@ -0,0 +1,14 @@ +package repo + +import ( + "testing" + + "github.com/magiconair/properties/assert" +) + +func TestGetStoragePath(t *testing.T) { + p := GetStoragePath("/data", "order") + assert.Equal(t, p, "/data/storage/order") + p = GetStoragePath("/data") + assert.Equal(t, p, "/data/storage") +} diff --git a/internal/repo/testdata/api b/internal/repo/testdata/api new file mode 100644 index 0000000..fc69c8b --- /dev/null +++ b/internal/repo/testdata/api @@ -0,0 +1 @@ +http://localhost:9091/v1/ \ No newline at end of file diff --git a/internal/repo/testdata/bitxhub.toml b/internal/repo/testdata/bitxhub.toml new file mode 100755 index 0000000..fc54189 --- /dev/null +++ b/internal/repo/testdata/bitxhub.toml @@ -0,0 +1,36 @@ +title = "BitXHub configuration file" + +solo = false + +[port] + grpc = 60011 + gateway = 9091 + pprof = 53121 + +[pprof] + enable = false + +[gateway] + allowed_origins = ["*"] + +[log] + level = "info" + dir = "logs" + filename = "bitxhub.log" + [log.module] + p2p = "info" + consensus = "info" + executor = "info" + router = "info" + api = "info" + +[cert] + verify = true + +[order] + plugin = "plugins/raft.so" + +[genesis] + addresses = [ + "0x64734c73a4cd00d7b3d1768946447ae9ebf240af" + ] diff --git a/internal/repo/testdata/certs/agency.cert b/internal/repo/testdata/certs/agency.cert new file mode 100644 index 0000000..8fdadad --- /dev/null +++ b/internal/repo/testdata/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClTCCAjygAwIBAgIDCpnMMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE3MDE1MjIxWhgPMjA3MDAyMDQwMTUyMjFaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQWNGFeLaXUE7yX2Kd2 +0wU1c+IBVvD3KZLvFwFWdCBFHw2bE7/duotcHjaVFpWhRVWrTDd3nMWlj914Am8X +Qhdho18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCD21xMA5rLIkyzkYHy2oe70SHWkZviYg0gil6f0 +TdkzlzAKBggqhkjOPQQDAgNHADBEAiB3xpXHUJ1BlMULSl9xCFDI8BIwd/Mm7hHV +oNVr2zyeBAIgMGqpZC582WJzsbmYUMqLVchvrrVLN2LPfUxY5ncDJwY= +-----END CERTIFICATE----- diff --git a/internal/repo/testdata/certs/ca.cert b/internal/repo/testdata/certs/ca.cert new file mode 100644 index 0000000..f197523 --- /dev/null +++ b/internal/repo/testdata/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDCzDiMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE3MDE1MjIxWhgPMjA3MDAyMDQwMTUyMjFaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATxM8DMzuGg4v2ekuUB +CM+ZsY4zPzsAeLp612fKoBpI2iONJvcM5eLCaHv5cfNkee6EFNOjc38npHhaCXDe +x9Sqo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCC+ZNFmUeGwNRwJjdUGele2fSjlS4+Nr388sj0C +naSIFDAKBggqhkjOPQQDAgNJADBGAiEAmI3DI3dyCEGvsNBOLM3CCtZ3aPGI4EBj +lwtZofbtzxUCIQCaNdgdO/x6kA7I4wMtRchiSmUQ0VRiHF1mXUVYsMeQGA== +-----END CERTIFICATE----- diff --git a/internal/repo/testdata/certs/node.cert b/internal/repo/testdata/certs/node.cert new file mode 100644 index 0000000..3d1aaa2 --- /dev/null +++ b/internal/repo/testdata/certs/node.cert @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwjCCAmigAwIBAgIDCWorMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE3MDE1MjIyWhgPMjA3MDAyMDQwMTUyMjJaMIGeMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEDAO +BgNVBAoTB0FnZW5jeTExEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHho +dWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARPkxN+n+u52WedWVOoVZpb +2ZJWRb8ljZJZNWS+AMgUmTg5aQAU1xapOG4MFUeRujT0OmXIMY3sw+roqcG8Doxm +o4GNMIGKMA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB +/wQFMAMBAf8wKQYDVR0OBCIEIP7jTLDWDuuEwaiZOMxv/4lpNkYJEnI/yduG4lcJ +AGZzMCsGA1UdIwQkMCKAIPbXEwDmssiTLORgfLah7vRIdaRm+JiDSCKXp/RN2TOX +MAoGCCqGSM49BAMCA0gAMEUCIE1CfVzBrANGHI2vlKZJAJQ4ccnnNn//0EVs/do6 +iWIHAiEAzNx/cvkrtVxX1E6JGEEj1H+/aQep1KmtgEZHPKv/O50= +-----END CERTIFICATE----- diff --git a/internal/repo/testdata/certs/node.priv b/internal/repo/testdata/certs/node.priv new file mode 100644 index 0000000..0ded716 --- /dev/null +++ b/internal/repo/testdata/certs/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKnfYqPIMB1JHPCEA8P5qX/9sJR92LKtiB8i3xAnnfz/oAoGCCqGSM49 +AwEHoUQDQgAET5MTfp/rudlnnVlTqFWaW9mSVkW/JY2SWTVkvgDIFJk4OWkAFNcW +qThuDBVHkbo09DplyDGN7MPq6KnBvA6MZg== +-----END EC PRIVATE KEY----- diff --git a/internal/repo/testdata/key b/internal/repo/testdata/key new file mode 100755 index 0000000..10eb995 --- /dev/null +++ b/internal/repo/testdata/key @@ -0,0 +1,5 @@ +{ + "pid": "QmUxPDgpWTVrFByfqhfbiULJKoRPBjgVVXhhdtByfZPi5C", + "address": "0x486e02ee11994af271a0df12111a654a47bce6dc", + "priv_key": "CAMSeTB3AgEBBCBNF9f5ktxONu1pqprIFLV6/7F27gyjvycYPq6zaqplvqAKBggqhkjOPQMBB6FEA0IABPY2wPn+T8+bDcIuyKRfcO4E7UujG2lT+UGuSXH1exdOjVdixno9J8PT7glJfpcTJ+ayomb6UqT4lkfCewZ70ZI=" +} \ No newline at end of file diff --git a/internal/repo/testdata/network.toml b/internal/repo/testdata/network.toml new file mode 100644 index 0000000..efea830 --- /dev/null +++ b/internal/repo/testdata/network.toml @@ -0,0 +1,18 @@ +id = 1 # self id +N = 4 # the number of cluster nodes + +[[nodes]] +id = 1 +addr = "/ip4/127.0.0.1/tcp/4001/p2p/QmYkQxwC3cj8HqxAnsgM1cSK5yPjkL8UySc21b1AhVF2Wf" + +[[nodes]] +id = 2 +addr = "/ip4/127.0.0.1/tcp/4002/p2p/QmfHtWy4uqu3JvUUDpAq7oHXkpq5qkuY1Ffb6zngqXtpMi" + +[[nodes]] +id = 3 +addr = "/ip4/127.0.0.1/tcp/4003/p2p/QmcpUhepA4tgkVvzW6WnxHCCk163CTLSiKatQeVSF8ro9z" + +[[nodes]] +id = 4 +addr = "/ip4/127.0.0.1/tcp/4004/p2p/QmT3ghgJFboqYYM9B6p6ehJFGHoEqpaRCJBByWCCYgHHZt" diff --git a/internal/repo/testdata/order.toml b/internal/repo/testdata/order.toml new file mode 100644 index 0000000..b2b0f15 --- /dev/null +++ b/internal/repo/testdata/order.toml @@ -0,0 +1,11 @@ +[raft] +election_tick = 10 # ElectionTick is the number of Node.Tick invocations that must pass between elections.(s) +heartbeat_tick = 1 # HeartbeatTick is the number of Node.Tick invocations that must pass between heartbeats.(s) +max_size_per_msg = 1048576 # 1024*1024, MaxSizePerMsg limits the max size of each append message. +max_inflight_msgs = 500 # MaxInflightMsgs limits the max number of in-flight append messages during optimistic replication phase. +check_quorum = false # Leader steps down when quorum is not active for an electionTimeout. +pre_vote = true # PreVote prevents reconnected node from disturbing network. +disable_proposal_forwarding = true # This prevents blocks from being accidentally proposed by followers. + [raft.tx_pool] + pack_size = 500 # Maximum number of transaction packages. + block_tick = "500ms" # Block packaging time period. diff --git a/internal/router/interchain.go b/internal/router/interchain.go new file mode 100644 index 0000000..b315fa5 --- /dev/null +++ b/internal/router/interchain.go @@ -0,0 +1,180 @@ +package router + +import ( + "context" + "encoding/json" + "fmt" + "sync" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/peermgr" + "github.com/sirupsen/logrus" +) + +var _ Router = (*InterchainRouter)(nil) + +const blockChanNumber = 1024 + +type InterchainRouter struct { + logger logrus.FieldLogger + repo *repo.Repo + piers sync.Map + count uint64 + ledger ledger.Ledger + peerMgr peermgr.PeerManager + quorum uint64 + + ctx context.Context + cancel context.CancelFunc +} + +func New(logger logrus.FieldLogger, repo *repo.Repo, ledger ledger.Ledger, peerMgr peermgr.PeerManager, quorum uint64) (*InterchainRouter, error) { + ctx, cancel := context.WithCancel(context.Background()) + + return &InterchainRouter{ + logger: logger, + ledger: ledger, + peerMgr: peerMgr, + quorum: quorum, + repo: repo, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (router *InterchainRouter) Start() error { + router.logger.Infof("router module started") + + return nil +} + +func (router *InterchainRouter) Stop() error { + router.cancel() + + router.logger.Infof("router module stopped") + + return nil +} + +func (router *InterchainRouter) AddPier(key string) (chan *pb.MerkleWrapper, error) { + c := make(chan *pb.MerkleWrapper, blockChanNumber) + router.piers.Store(key, c) + router.count++ + router.logger.WithFields(logrus.Fields{ + "id": key, + }).Infof("Add pier") + + return c, nil +} + +func (router *InterchainRouter) RemovePier(key string) { + router.piers.Delete(key) + router.count-- +} + +func (router *InterchainRouter) PutBlock(block *pb.Block) { + if router.count == 0 { + return + } + + signed, err := router.fetchSigns(block.BlockHeader.Number) + if err != nil { + router.logger.Errorf("fetch signs: %w", err) + } + + ret := router.classify(block) + + router.piers.Range(func(k, value interface{}) bool { + key := k.(string) + w := value.(chan *pb.MerkleWrapper) + _, ok := ret[key] + if ok { + ret[key].Signatures = signed + w <- ret[key] + return true + } + + w <- &pb.MerkleWrapper{ + BlockHeader: block.BlockHeader, + BlockHash: block.BlockHash, + Signatures: signed, + } + + return true + }) +} + +func (router *InterchainRouter) GetMerkleWrapper(pid string, begin, end uint64, ch chan<- *pb.MerkleWrapper) error { + for i := begin; i <= end; i++ { + block, err := router.ledger.GetBlock(i) + if err != nil { + return fmt.Errorf("get block: %w", err) + } + + signed, err := router.fetchSigns(i) + if err != nil { + return fmt.Errorf("fetch signs: %w", err) + } + + ret := router.classify(block) + if ret[pid] != nil { + ret[pid].Signatures = signed + ch <- ret[pid] + continue + } + + ch <- &pb.MerkleWrapper{ + BlockHeader: block.BlockHeader, + BlockHash: block.BlockHash, + Signatures: signed, + } + } + + return nil +} + +func (router *InterchainRouter) fetchSigns(height uint64) (map[string][]byte, error) { + // TODO(xcc): fetch block sign from other nodes + return nil, nil +} + +func (router *InterchainRouter) classify(block *pb.Block) map[string]*pb.MerkleWrapper { + hashes := make([]types.Hash, 0, len(block.Transactions)) + for _, tx := range block.Transactions { + hashes = append(hashes, tx.TransactionHash) + } + + if block.BlockHeader.InterchainIndex == nil { + return make(map[string]*pb.MerkleWrapper) + } + idx := make(map[string][]uint64) + m := make(map[string][]*pb.Transaction) + err := json.Unmarshal(block.BlockHeader.InterchainIndex, &idx) + if err != nil { + panic(err) + } + + for k, vs := range idx { + var txs []*pb.Transaction + for _, i := range vs { + txs = append(txs, block.Transactions[i]) + } + m[k] = txs + } + + target := make(map[string]*pb.MerkleWrapper) + for dest, txs := range m { + wrapper := &pb.MerkleWrapper{ + BlockHeader: block.BlockHeader, + TransactionHashes: hashes, + Transactions: txs, + BlockHash: block.BlockHash, + } + target[dest] = wrapper + } + + return target +} diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..572c313 --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,23 @@ +package router + +import "github.com/meshplus/bitxhub-model/pb" + +type Router interface { + // Start starts the router module + Start() error + + // Stop + Stop() error + + // PutBlock + PutBlock(*pb.Block) + + // AddPier + AddPier(id string) (chan *pb.MerkleWrapper, error) + + // RemovePier + RemovePier(id string) + + // GetMerkleWrapper + GetMerkleWrapper(pid string, begin, end uint64, ch chan<- *pb.MerkleWrapper) error +} diff --git a/internal/storages/storages.go b/internal/storages/storages.go new file mode 100644 index 0000000..92633c5 --- /dev/null +++ b/internal/storages/storages.go @@ -0,0 +1,41 @@ +package storages + +import ( + "fmt" + + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" +) + +const ( + BlockChain = "blockchain" +) + +var s = &wrapper{ + storages: make(map[string]storage.Storage), +} + +type wrapper struct { + storages map[string]storage.Storage +} + +func Initialize(repoRoot string) error { + bcStorage, err := leveldb.New(repo.GetStoragePath(repoRoot, BlockChain)) + if err != nil { + return fmt.Errorf("create blockchain storage: %w", err) + } + + s.storages[BlockChain] = bcStorage + + return nil +} + +func Get(name string) (storage.Storage, error) { + strg, ok := s.storages[name] + if !ok { + return nil, fmt.Errorf("wrong storage name") + } + + return strg, nil +} diff --git a/internal/validator/validator.go b/internal/validator/validator.go new file mode 100644 index 0000000..5b63f15 --- /dev/null +++ b/internal/validator/validator.go @@ -0,0 +1,5 @@ +package validator + +type Validator interface { + Verify(address, from string, proof []byte, validators string) (bool, error) +} diff --git a/internal/validator/wasm_validator.go b/internal/validator/wasm_validator.go new file mode 100755 index 0000000..1467b8f --- /dev/null +++ b/internal/validator/wasm_validator.go @@ -0,0 +1,93 @@ +package validator + +import ( + "strconv" + + "github.com/gogo/protobuf/proto" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/pkg/vm" + "github.com/meshplus/bitxhub/pkg/vm/wasm" + "github.com/sirupsen/logrus" +) + +// Validator is the instance that can use wasm to verify transaction validity +type WasmValidator struct { + wasm *wasm.Wasm + tx *pb.Transaction + ledger ledger.Ledger + logger logrus.FieldLogger +} + +// New a validator instance +func NewWasmValidator(ledger ledger.Ledger, logger logrus.FieldLogger) *WasmValidator { + return &WasmValidator{ + ledger: ledger, + logger: logger, + } +} + +// Verify will check whether the transaction info is valid +func (vlt *WasmValidator) Verify(address, from string, proof []byte, validators string) (bool, error) { + err := vlt.initRule(address, from, proof, validators) + if err != nil { + return false, err + } + + ret, err := vlt.wasm.Run(vlt.tx.Data.Payload) + if err != nil { + return false, err + } + + result, err := strconv.Atoi(string(ret)) + if err != nil { + return false, err + } + + if result == 0 { + return false, nil + } + + return true, nil +} + +// InitRule can import a specific rule for validator to verify the transaction +func (vlt *WasmValidator) initRule(address, from string, proof []byte, validators string) error { + err := vlt.setTransaction(address, from, proof, validators) + if err != nil { + return err + } + + wasmCtx := vm.NewContext(vlt.tx, 0, vlt.tx.Data, vlt.ledger, vlt.logger) + wasm, err := wasm.New(wasmCtx) + if err != nil { + return err + } + vlt.wasm = wasm + + return nil +} + +func (vlt *WasmValidator) setTransaction(address, from string, proof []byte, validators string) error { + payload := &pb.InvokePayload{ + Method: "start_verify", + Args: []*pb.Arg{ + {Type: pb.Arg_Bytes, Value: proof}, + {Type: pb.Arg_Bytes, Value: []byte(validators)}, + }, + } + input, _ := proto.Marshal(payload) + + txData := &pb.TransactionData{ + Type: pb.TransactionData_INVOKE, + Payload: input, + } + + vlt.tx = &pb.Transaction{ + From: types.String2Address(from), + To: types.String2Address(address), + Data: txData, + } + return nil +} diff --git a/pkg/cert/cert.go b/pkg/cert/cert.go new file mode 100644 index 0000000..5bc3b62 --- /dev/null +++ b/pkg/cert/cert.go @@ -0,0 +1,47 @@ +package cert + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "fmt" + "time" +) + +func VerifySign(subCert *x509.Certificate, caCert *x509.Certificate) error { + if err := subCert.CheckSignatureFrom(caCert); err != nil { + return fmt.Errorf("check sign: %w", err) + } + + if subCert.NotBefore.After(time.Now()) || subCert.NotAfter.Before(time.Now()) { + return fmt.Errorf("cert expired") + } + + return nil +} + +func ParsePrivateKey(data []byte) (*ecdsa.PrivateKey, error) { + if data == nil { + return nil, fmt.Errorf("empty data") + } + + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("empty block") + } + + return x509.ParseECPrivateKey(block.Bytes) +} + +func ParseCert(data []byte) (*x509.Certificate, error) { + if data == nil { + return nil, fmt.Errorf("empty data") + } + + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("empty block") + } + + return x509.ParseCertificate(block.Bytes) +} diff --git a/pkg/cert/cert_test.go b/pkg/cert/cert_test.go new file mode 100644 index 0000000..900eeea --- /dev/null +++ b/pkg/cert/cert_test.go @@ -0,0 +1,40 @@ +package cert + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func TestParsePrivateKey(t *testing.T) { + data, err := ioutil.ReadFile(filepath.Join("testdata", "ca.priv")) + assert.Nil(t, err) + privKey, err := ParsePrivateKey(data) + assert.Nil(t, err) + assert.NotNil(t, privKey) +} + +func TestVerifySign(t *testing.T) { + data, err := ioutil.ReadFile(filepath.Join("testdata", "ca.cert")) + require.Nil(t, err) + caCert, err := ParseCert(data) + require.Nil(t, err) + + subData, err := ioutil.ReadFile(filepath.Join("testdata", "agency.cert")) + require.Nil(t, err) + subCert, err := ParseCert(subData) + require.Nil(t, err) + err = VerifySign(subCert, caCert) + require.Nil(t, err) + + nodeData, err := ioutil.ReadFile(filepath.Join("testdata", "node.cert")) + require.Nil(t, err) + nodeCert, err := ParseCert(nodeData) + require.Nil(t, err) + err = VerifySign(nodeCert, subCert) + require.Nil(t, err) +} diff --git a/pkg/cert/testdata/agency.cert b/pkg/cert/testdata/agency.cert new file mode 100644 index 0000000..a9c0f7e --- /dev/null +++ b/pkg/cert/testdata/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICljCCAjygAwIBAgIDDM4TMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE2MTA1OTEyWhgPMjA3MDAyMDMxMDU5MTJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARzSt8E8u1OUpi8Xtjn +ZtYfNCzr49+MkvPpTkMNR6eeVu0Ojmg+1B3N5ACJ4IBdwyB98i2nJdEGWXnB8tQe +EjeQo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCD4D+j//rrAmy9ONm16YVs2ME5F7V77r4jPUwhk +BrzMNjAKBggqhkjOPQQDAgNIADBFAiB9EztiHPJCR27NAua9ym6o438y8rbgDxWk +xgAUhD/kGQIhAPaDmeySLJ36nfhSB/1DB7UwH6Ft4zbNXlwXSF7WFDZc +-----END CERTIFICATE----- diff --git a/pkg/cert/testdata/agency.priv b/pkg/cert/testdata/agency.priv new file mode 100644 index 0000000..7b162be --- /dev/null +++ b/pkg/cert/testdata/agency.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIK5aL/4xchh+S4V61eNe5pd39Q2Y6LQGUkF2DxZSrh1LoAoGCCqGSM49 +AwEHoUQDQgAEc0rfBPLtTlKYvF7Y52bWHzQs6+PfjJLz6U5DDUennlbtDo5oPtQd +zeQAieCAXcMgffItpyXRBll5wfLUHhI3kA== +-----END EC PRIVATE KEY----- diff --git a/pkg/cert/testdata/ca.cert b/pkg/cert/testdata/ca.cert new file mode 100644 index 0000000..6e1219f --- /dev/null +++ b/pkg/cert/testdata/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICljCCAjygAwIBAgIDBw66MAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE2MTA1ODU2WhgPMjA3MDAyMDMxMDU4NTZaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASaN7fk29koRjieizGU +keMuKsgjKV/ETyyncf2tUKChie9xp50ZwNyWeNw1RkZwaq4VSgc3XO4PK1brMi/I +ryrvo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCC1Q6sMZ1x2MoCnta2NNa1j/fXX9Ro67ifzzsju +12EJ0TAKBggqhkjOPQQDAgNIADBFAiEAkuuL1IuOwsMPSGztyz2LWLMQWGGGtom9 +H3/P5l+mfmMCID7rXvAaGLPy+E2o0mbeT5+/xe8Rz2WWbW1WKS512eWV +-----END CERTIFICATE----- diff --git a/pkg/cert/testdata/ca.priv b/pkg/cert/testdata/ca.priv new file mode 100644 index 0000000..f8967f3 --- /dev/null +++ b/pkg/cert/testdata/ca.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEICm0b8dBl2nUDWN/tWomSiEWHFrInXdCMSr4So1XJH16oAoGCCqGSM49 +AwEHoUQDQgAEmje35NvZKEY4nosxlJHjLirIIylfxE8sp3H9rVCgoYnvcaedGcDc +lnjcNUZGcGquFUoHN1zuDytW6zIvyK8q7w== +-----END EC PRIVATE KEY----- diff --git a/pkg/cert/testdata/node.cert b/pkg/cert/testdata/node.cert new file mode 100644 index 0000000..d75d031 --- /dev/null +++ b/pkg/cert/testdata/node.cert @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwTCCAmegAwIBAgIDDiTiMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMjE2MTA1OTIwWhgPMjA3MDAyMDMxMDU5MjBaMIGdMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjETMBEGA1UEAxMKYml0eGh1 +Yi5jbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHdBYo//anaFE6mWJ1OPSB7C +vRxUVaU5v/wnerThPBNJZK5xYBP4Ss2Pdu9dia+MZEgfbADJgKXYl27tNrvyxeej +gY0wgYowDgYDVR0PAQH/BAQDAgGmMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/ +BAUwAwEB/zApBgNVHQ4EIgQgSfHyXM1Dz06eSwdHAEfntUOZtkXMQ6c+naNwHQ8s +McwwKwYDVR0jBCQwIoAg+A/o//66wJsvTjZtemFbNjBORe1e+6+Iz1MIZAa8zDYw +CgYIKoZIzj0EAwIDSAAwRQIgSIEexoEAMc0Mp0jbhT0hzY4ejCDArxqiWRXcMynM +YiQCIQDBZhgf9db9jgE/NUV4BQjK4SX+yz1zWrlgcF67calOJw== +-----END CERTIFICATE----- diff --git a/pkg/cert/testdata/node.priv b/pkg/cert/testdata/node.priv new file mode 100644 index 0000000..e4b320a --- /dev/null +++ b/pkg/cert/testdata/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILrm8HS7M1yMOKRYPhNCbZM1AYI0vcGb8NaEPzy0K8UEoAoGCCqGSM49 +AwEHoUQDQgAEd0Fij/9qdoUTqZYnU49IHsK9HFRVpTm//Cd6tOE8E0lkrnFgE/hK +zY92712Jr4xkSB9sAMmApdiXbu02u/LF5w== +-----END EC PRIVATE KEY----- diff --git a/pkg/network/Makefile b/pkg/network/Makefile new file mode 100644 index 0000000..e9ff86b --- /dev/null +++ b/pkg/network/Makefile @@ -0,0 +1,14 @@ +GO = GO111MODULE=on go + +help: Makefile + @echo "Choose a command run:" + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + +## make pb: build network message protobuf +proto: + cd proto && protoc -I=. \ + -I${GOPATH}/src \ + -I${GOPATH}/src/github.com/gogo/protobuf/protobuf \ + --gogofast_out=:. network.proto + +.PHONY: proto diff --git a/pkg/network/network.go b/pkg/network/network.go new file mode 100755 index 0000000..f55ae31 --- /dev/null +++ b/pkg/network/network.go @@ -0,0 +1,54 @@ +package network + +import ( + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/meshplus/bitxhub/pkg/network/proto" +) + +// ID represents peer id +type ID interface{} + +type ConnectCallback func(ID) error + +type IDStore interface { + Add(ID, *peer.AddrInfo) + Remove(ID) + Addr(ID) *peer.AddrInfo + Addrs() map[ID]*peer.AddrInfo +} + +type Network interface { + // Start start the network service. + Start() error + + // Stop stop the network service. + Stop() error + + // Connect connects peer by ID. + Connect(ID) error + + // Disconnect peer with id + Disconnect(ID) error + + // SetConnectionCallback Sets the callback after connecting + SetConnectCallback(ConnectCallback) + + // Send message to peer with peer info. + Send(ID, *proto.Message) error + + // Send message using existed stream + SendWithStream(s network.Stream, msg *proto.Message) error + + // Sync Send message + SyncSend(ID, *proto.Message) (*proto.Message, error) + + // Broadcast message to all node + Broadcast([]ID, *proto.Message) error + + // Receive message from the channel + Receive() <-chan *MessageStream + + // IDStore + IDStore() IDStore +} diff --git a/pkg/network/p2p/config.go b/pkg/network/p2p/config.go new file mode 100644 index 0000000..55eed02 --- /dev/null +++ b/pkg/network/p2p/config.go @@ -0,0 +1,76 @@ +package p2p + +import ( + "fmt" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/meshplus/bitxhub-kit/log" + net "github.com/meshplus/bitxhub/pkg/network" + "github.com/sirupsen/logrus" +) + +type Config struct { + localAddr string + privKey crypto.PrivKey + protocolID protocol.ID + logger logrus.FieldLogger + idStore net.IDStore +} + +type Option func(*Config) + +func WithPrivateKey(privKey crypto.PrivKey) Option { + return func(config *Config) { + config.privKey = privKey + } +} + +func WithLocalAddr(addr string) Option { + return func(config *Config) { + config.localAddr = addr + } +} + +func WithProtocolID(id protocol.ID) Option { + return func(config *Config) { + config.protocolID = id + } +} + +func WithLogger(logger logrus.FieldLogger) Option { + return func(config *Config) { + config.logger = logger + } +} + +func WithIDStore(store net.IDStore) Option { + return func(config *Config) { + config.idStore = store + } +} + +func checkConfig(config *Config) error { + if config.logger == nil { + config.logger = log.NewWithModule("p2p") + } + + if config.localAddr == "" { + return fmt.Errorf("empty local address") + } + + return nil +} + +func generateConfig(opts ...Option) (*Config, error) { + conf := &Config{} + for _, opt := range opts { + opt(conf) + } + + if err := checkConfig(conf); err != nil { + return nil, fmt.Errorf("create p2p: %w", err) + } + + return conf, nil +} diff --git a/pkg/network/p2p/handle.go b/pkg/network/p2p/handle.go new file mode 100644 index 0000000..6ede699 --- /dev/null +++ b/pkg/network/p2p/handle.go @@ -0,0 +1,79 @@ +package p2p + +import ( + "context" + "fmt" + "io" + "time" + + ggio "github.com/gogo/protobuf/io" + "github.com/libp2p/go-libp2p-core/network" + net "github.com/meshplus/bitxhub/pkg/network" + "github.com/meshplus/bitxhub/pkg/network/proto" +) + +// handle newly connected stream +func (p2p *P2P) handleNewStream(s network.Stream) { + if err := s.SetReadDeadline(time.Time{}); err != nil { + p2p.logger.WithField("error", err).Error("Set stream read deadline") + return + } + + reader := ggio.NewDelimitedReader(s, network.MessageSizeMax) + for { + msg := &proto.Message{} + if err := reader.ReadMsg(msg); err != nil { + if err != io.EOF { + if err := s.Reset(); err != nil { + p2p.logger.WithField("error", err).Error("Reset stream") + } + } + return + } + + p2p.recvQ <- &net.MessageStream{ + Message: msg, + Stream: s, + } + } +} + +// waitMsg wait the incoming messages within time duration. +func waitMsg(stream io.Reader, timeout time.Duration) *proto.Message { + reader := ggio.NewDelimitedReader(stream, network.MessageSizeMax) + rs := make(chan *proto.Message) + go func() { + msg := &proto.Message{} + if err := reader.ReadMsg(msg); err == nil { + rs <- msg + } else { + rs <- nil + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + + select { + case r := <-rs: + cancel() + return r + case <-ctx.Done(): + cancel() + return nil + } +} + +func (p2p *P2P) send(s network.Stream, message *proto.Message) error { + deadline := time.Now().Add(sendTimeout) + + if err := s.SetWriteDeadline(deadline); err != nil { + return fmt.Errorf("set deadline: %w", err) + } + + writer := ggio.NewDelimitedWriter(s) + if err := writer.WriteMsg(message); err != nil { + return fmt.Errorf("write msg: %w", err) + } + + return nil +} diff --git a/pkg/network/p2p/id_store.go b/pkg/network/p2p/id_store.go new file mode 100644 index 0000000..3e2f2f9 --- /dev/null +++ b/pkg/network/p2p/id_store.go @@ -0,0 +1,68 @@ +package p2p + +import ( + "sync" + + "github.com/libp2p/go-libp2p-core/peer" + net "github.com/meshplus/bitxhub/pkg/network" +) + +type IDStore struct { + ids map[net.ID]*peer.AddrInfo + sync.RWMutex +} + +func NewIDStore() *IDStore { + return &IDStore{ + ids: make(map[net.ID]*peer.AddrInfo), + } +} + +func (store *IDStore) Add(id net.ID, addr *peer.AddrInfo) { + store.Lock() + defer store.Unlock() + store.ids[id] = addr +} + +func (store *IDStore) Remove(id net.ID) { + store.Lock() + defer store.Unlock() + delete(store.ids, id) +} + +func (store *IDStore) Addr(id net.ID) *peer.AddrInfo { + store.RLock() + defer store.RUnlock() + return store.ids[id] +} + +func (store *IDStore) Addrs() map[net.ID]*peer.AddrInfo { + ret := make(map[net.ID]*peer.AddrInfo) + for id, addr := range store.ids { + ret[id] = addr + } + + return ret +} + +type NullIDStore struct { +} + +func NewNullIDStore() *NullIDStore { + return &NullIDStore{} +} + +func (null *NullIDStore) Add(net.ID, *peer.AddrInfo) { +} + +func (null *NullIDStore) Remove(net.ID) { +} + +func (null *NullIDStore) Addr(id net.ID) *peer.AddrInfo { + return id.(*peer.AddrInfo) +} + +// Addrs always returns 0 +func (null *NullIDStore) Addrs() map[net.ID]*peer.AddrInfo { + return nil +} diff --git a/pkg/network/p2p/p2p.go b/pkg/network/p2p/p2p.go new file mode 100755 index 0000000..d2f8f2a --- /dev/null +++ b/pkg/network/p2p/p2p.go @@ -0,0 +1,208 @@ +package p2p + +import ( + "context" + "fmt" + "time" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + net "github.com/meshplus/bitxhub/pkg/network" + "github.com/meshplus/bitxhub/pkg/network/proto" + ma "github.com/multiformats/go-multiaddr" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var _ net.Network = (*P2P)(nil) + +var ErrorPeerNotFound = errors.New("peer not found") + +var ( + connectTimeout = 10 * time.Second + sendTimeout = 5 * time.Second + waitTimeout = 5 * time.Second +) + +type P2P struct { + config *Config + host host.Host // manage all connections + recvQ chan *net.MessageStream + streamMng *streamMgr + connectCallback net.ConnectCallback + logger logrus.FieldLogger + idStore net.IDStore + + ctx context.Context + cancel context.CancelFunc +} + +func New(opts ...Option) (net.Network, error) { + config, err := generateConfig(opts...) + if err != nil { + return nil, fmt.Errorf("generate config: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + h, err := libp2p.New(ctx, + libp2p.Identity(config.privKey), + libp2p.ListenAddrStrings(config.localAddr)) + if err != nil { + cancel() + return nil, fmt.Errorf("create libp2p: %w", err) + } + + p2p := &P2P{ + config: config, + host: h, + idStore: config.idStore, + recvQ: make(chan *net.MessageStream), + streamMng: newStreamMng(ctx, h, config.protocolID), + logger: config.logger, + ctx: ctx, + cancel: cancel, + } + + if config.idStore == nil { + p2p.idStore = NewIDStore() + } + + return p2p, nil +} + +// Start start the network service. +func (p2p *P2P) Start() error { + p2p.host.SetStreamHandler(p2p.config.protocolID, p2p.handleNewStream) + + return nil +} + +// Connect peer. +func (p2p *P2P) Connect(id net.ID) error { + ctx, cancel := context.WithTimeout(p2p.ctx, connectTimeout) + defer cancel() + + pid := p2p.idStore.Addr(id) + if pid == nil { + return ErrorPeerNotFound + } + + if err := p2p.host.Connect(ctx, *pid); err != nil { + return err + } + + p2p.host.Peerstore().AddAddrs(pid.ID, pid.Addrs, peerstore.PermanentAddrTTL) + + if p2p.connectCallback != nil { + if err := p2p.connectCallback(id); err != nil { + return err + } + } + + return nil +} + +func (p2p *P2P) SetConnectCallback(callback net.ConnectCallback) { + p2p.connectCallback = callback +} + +// Send message to peer with specific id. +func (p2p *P2P) Send(id net.ID, msg *proto.Message) error { + addr := p2p.idStore.Addr(id) + if addr == nil { + return ErrorPeerNotFound + } + + s, err := p2p.streamMng.get(addr.ID) + if err != nil { + return fmt.Errorf("get stream: %w", err) + } + + if err := p2p.send(s, msg); err != nil { + p2p.streamMng.remove(addr.ID) + return err + } + + return nil +} + +func (p2p *P2P) SendWithStream(s network.Stream, msg *proto.Message) error { + return p2p.send(s, msg) +} + +func (p2p *P2P) SyncSend(id net.ID, msg *proto.Message) (*proto.Message, error) { + addr := p2p.idStore.Addr(id) + if addr == nil { + return nil, ErrorPeerNotFound + } + + s, err := p2p.streamMng.get(addr.ID) + if err != nil { + return nil, fmt.Errorf("get stream: %w", err) + } + + if err := p2p.send(s, msg); err != nil { + p2p.streamMng.remove(addr.ID) + return nil, err + } + + recvMsg := waitMsg(s, waitTimeout) + if recvMsg == nil { + return nil, fmt.Errorf("sync send msg to node%d timeout", id) + } + + return recvMsg, nil +} + +func (p2p *P2P) Broadcast(ids []net.ID, msg *proto.Message) error { + for _, id := range ids { + if err := p2p.Send(id, msg); err != nil { + p2p.logger.WithFields(logrus.Fields{ + "error": err, + "id": id, + }).Error("Send message") + continue + } + } + + return nil +} + +func (p2p *P2P) Receive() <-chan *net.MessageStream { + return p2p.recvQ +} + +// Stop stop the network service. +func (p2p *P2P) Stop() error { + p2p.cancel() + + return p2p.host.Close() +} + +// AddrToPeerInfo transfer addr to PeerInfo +// addr example: "/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64" +func AddrToPeerInfo(multiAddr string) (*peer.AddrInfo, error) { + maddr, err := ma.NewMultiaddr(multiAddr) + if err != nil { + return nil, err + } + + return peer.AddrInfoFromP2pAddr(maddr) +} + +func (p2p *P2P) Disconnect(id net.ID) error { + addr := p2p.idStore.Addr(id) + if addr == nil { + return ErrorPeerNotFound + } + + return p2p.host.Network().ClosePeer(addr.ID) +} + +func (p2p *P2P) IDStore() net.IDStore { + return p2p.idStore +} diff --git a/pkg/network/p2p/p2p_test.go b/pkg/network/p2p/p2p_test.go new file mode 100644 index 0000000..3390b52 --- /dev/null +++ b/pkg/network/p2p/p2p_test.go @@ -0,0 +1,155 @@ +package p2p + +import ( + "context" + "crypto/rand" + "fmt" + "testing" + "time" + + net "github.com/meshplus/bitxhub/pkg/network" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/stretchr/testify/assert" +) + +const ( + protocolID protocol.ID = "/test/1.0.0" // magic protocol +) + +func TestP2P_Connect(t *testing.T) { + p1, addr1 := generateNetwork(t, 6001, nil) + p2, addr2 := generateNetwork(t, 6002, nil) + + p1.IDStore().Add(2, addr2) + p2.IDStore().Add(1, addr1) + + err := p1.Connect(2) + assert.Nil(t, err) + err = p2.Connect(1) + assert.Nil(t, err) + + assert.EqualValues(t, 1, len(p1.IDStore().Addrs())) + assert.EqualValues(t, 1, len(p2.IDStore().Addrs())) +} + +func TestP2p_ConnectWithNullIDStore(t *testing.T) { + p1, addr1 := generateNetwork(t, 6003, NewNullIDStore()) + p2, addr2 := generateNetwork(t, 6004, NewNullIDStore()) + + err := p1.Connect(addr2) + assert.Nil(t, err) + err = p2.Connect(addr1) + assert.Nil(t, err) + + assert.EqualValues(t, 0, len(p1.IDStore().Addrs())) + assert.EqualValues(t, 0, len(p2.IDStore().Addrs())) +} + +func TestP2P_Send(t *testing.T) { + p1, addr1 := generateNetwork(t, 6005, nil) + p2, addr2 := generateNetwork(t, 6006, nil) + + p1.IDStore().Add(2, addr2) + p2.IDStore().Add(1, addr1) + + err := p1.Start() + assert.Nil(t, err) + err = p2.Start() + assert.Nil(t, err) + + err = p1.Connect(2) + assert.Nil(t, err) + err = p2.Connect(1) + assert.Nil(t, err) + + msg := []byte("hello") + err = p1.Send(2, net.Message(msg)) + assert.Nil(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + ch := p2.Receive() + for { + select { + case c := <-ch: + assert.EqualValues(t, msg, c.Message.Data) + return + case <-ctx.Done(): + assert.Error(t, fmt.Errorf("timeout")) + return + } + } +} + +func TestP2p_MultiSend(t *testing.T) { + p1, addr1 := generateNetwork(t, 6007, nil) + p2, addr2 := generateNetwork(t, 6008, nil) + + p1.IDStore().Add(2, addr2) + p2.IDStore().Add(1, addr1) + + err := p1.Start() + assert.Nil(t, err) + err = p2.Start() + assert.Nil(t, err) + + err = p1.Connect(2) + assert.Nil(t, err) + err = p2.Connect(1) + assert.Nil(t, err) + + N := 50 + msg := []byte("hello") + ch := p2.Receive() + + go func() { + for i := 0; i < N; i++ { + time.Sleep(200 * time.Microsecond) + err = p1.Send(2, net.Message(msg)) + assert.Nil(t, err) + } + + }() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + count := 0 + for { + select { + case c := <-ch: + assert.EqualValues(t, msg, c.Message.Data) + case <-ctx.Done(): + assert.Error(t, fmt.Errorf("timeout")) + } + count++ + if count == N { + return + } + } +} + +func generateNetwork(t *testing.T, port int, store net.IDStore) (net.Network, *peer.AddrInfo) { + privKey, pubKey, err := crypto.GenerateECDSAKeyPair(rand.Reader) + assert.Nil(t, err) + + pid1, err := peer.IDFromPublicKey(pubKey) + assert.Nil(t, err) + addr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port) + maddr := fmt.Sprintf("%s/p2p/%s", addr, pid1) + p2p, err := New( + WithLocalAddr(addr), + WithPrivateKey(privKey), + WithProtocolID(protocolID), + WithIDStore(store), + ) + assert.Nil(t, err) + + info, err := AddrToPeerInfo(maddr) + assert.Nil(t, err) + + return p2p, info +} diff --git a/pkg/network/p2p/stream_manager.go b/pkg/network/p2p/stream_manager.go new file mode 100644 index 0000000..50cfc28 --- /dev/null +++ b/pkg/network/p2p/stream_manager.go @@ -0,0 +1,65 @@ +package p2p + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/libp2p/go-libp2p-core/network" +) + +var ( + newStreamTimeout = 5 * time.Second +) + +type streamMgr struct { + ctx context.Context + protocolID protocol.ID + host host.Host + + streams map[peer.ID]network.Stream + sync.RWMutex +} + +func newStreamMng(ctx context.Context, host host.Host, protocolID protocol.ID) *streamMgr { + return &streamMgr{ + ctx: ctx, + protocolID: protocolID, + host: host, + streams: make(map[peer.ID]network.Stream), + } +} + +func (mng *streamMgr) get(pid peer.ID) (network.Stream, error) { + mng.Lock() + defer mng.Unlock() + + s := mng.streams[pid] + if s != nil { + return s, nil + } + + ctx, cancel := context.WithTimeout(mng.ctx, newStreamTimeout) + defer cancel() + + s, err := mng.host.NewStream(ctx, pid, mng.protocolID) + if err != nil { + return nil, fmt.Errorf("new stream: %w", err) + } + + mng.streams[pid] = s + + return s, nil +} + +func (mng *streamMgr) remove(pid peer.ID) { + mng.Lock() + defer mng.Unlock() + delete(mng.streams, pid) +} diff --git a/pkg/network/proto/network.pb.go b/pkg/network/proto/network.pb.go new file mode 100644 index 0000000..ef5127d --- /dev/null +++ b/pkg/network/proto/network.pb.go @@ -0,0 +1,378 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: network.proto + +package proto + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type Message struct { + Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` + Version string `protobuf:"bytes,2,opt,name=Version,proto3" json:"Version,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{0} +} +func (m *Message) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Message.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Message) XXX_Merge(src proto.Message) { + xxx_messageInfo_Message.Merge(m, src) +} +func (m *Message) XXX_Size() int { + return m.Size() +} +func (m *Message) XXX_DiscardUnknown() { + xxx_messageInfo_Message.DiscardUnknown(m) +} + +var xxx_messageInfo_Message proto.InternalMessageInfo + +func (m *Message) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *Message) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func init() { + proto.RegisterType((*Message)(nil), "proto.Message") +} + +func init() { proto.RegisterFile("network.proto", fileDescriptor_8571034d60397816) } + +var fileDescriptor_8571034d60397816 = []byte{ + // 110 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcd, 0x4b, 0x2d, 0x29, + 0xcf, 0x2f, 0xca, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0xe6, 0x5c, + 0xec, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x42, 0x42, 0x5c, 0x2c, 0x2e, 0x89, 0x25, 0x89, + 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x60, 0xb6, 0x90, 0x04, 0x17, 0x7b, 0x58, 0x6a, 0x51, + 0x71, 0x66, 0x7e, 0x9e, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x8c, 0xeb, 0xc4, 0x73, 0xe2, + 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x26, 0xb1, 0x81, 0x4d, 0x33, + 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xf4, 0xb2, 0xbf, 0x15, 0x65, 0x00, 0x00, 0x00, +} + +func (m *Message) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Message) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Message) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintNetwork(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintNetwork(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintNetwork(dAtA []byte, offset int, v uint64) int { + offset -= sovNetwork(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Message) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovNetwork(uint64(l)) + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovNetwork(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovNetwork(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozNetwork(x uint64) (n int) { + return sovNetwork(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetwork + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetwork + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetwork + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthNetwork + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetwork + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthNetwork + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthNetwork + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetwork(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetwork + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthNetwork + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipNetwork(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetwork + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetwork + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetwork + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthNetwork + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupNetwork + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthNetwork + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthNetwork = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowNetwork = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupNetwork = fmt.Errorf("proto: unexpected end of group") +) diff --git a/pkg/network/proto/network.proto b/pkg/network/proto/network.proto new file mode 100644 index 0000000..f1c97a3 --- /dev/null +++ b/pkg/network/proto/network.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package proto; + +message Message { + bytes Data = 1; + string Version = 2; +} \ No newline at end of file diff --git a/pkg/network/types.go b/pkg/network/types.go new file mode 100644 index 0000000..c445e2d --- /dev/null +++ b/pkg/network/types.go @@ -0,0 +1,21 @@ +package network + +import ( + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/meshplus/bitxhub/pkg/network/proto" +) + +type OnConnectCallback func(*peer.AddrInfo, ID) + +type MessageStream struct { + Message *proto.Message + Stream network.Stream +} + +func Message(data []byte) *proto.Message { + return &proto.Message{ + Data: data, + Version: "1.0", + } +} diff --git a/pkg/order/config.go b/pkg/order/config.go new file mode 100644 index 0000000..08591eb --- /dev/null +++ b/pkg/order/config.go @@ -0,0 +1,120 @@ +package order + +import ( + "fmt" + + "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/peermgr" + "github.com/sirupsen/logrus" +) + +type Config struct { + ID uint64 + RepoRoot string + StoragePath string + PluginPath string + PeerMgr peermgr.PeerManager + PrivKey crypto.PrivateKey + Logger logrus.FieldLogger + Nodes map[uint64]types.Address + Applied uint64 + Digest string + GetTransactionFunc func(hash types.Hash) (*pb.Transaction, error) + GetChainMetaFunc func() *pb.ChainMeta +} +type Option func(*Config) + +func WithID(id uint64) Option { + return func(config *Config) { + config.ID = id + } +} + +func WithRepoRoot(path string) Option { + return func(config *Config) { + config.RepoRoot = path + } +} + +func WithStoragePath(path string) Option { + return func(config *Config) { + config.StoragePath = path + } +} + +func WithPluginPath(path string) Option { + return func(config *Config) { + config.PluginPath = path + } +} + +func WithPeerManager(peerMgr peermgr.PeerManager) Option { + return func(config *Config) { + config.PeerMgr = peerMgr + } +} + +func WithPrivKey(privKey crypto.PrivateKey) Option { + return func(config *Config) { + config.PrivKey = privKey + } +} + +func WithLogger(logger logrus.FieldLogger) Option { + return func(config *Config) { + config.Logger = logger + } +} + +func WithNodes(nodes map[uint64]types.Address) Option { + return func(config *Config) { + config.Nodes = nodes + } +} + +func WithApplied(height uint64) Option { + return func(config *Config) { + config.Applied = height + } +} + +func WithDigest(digest string) Option { + return func(config *Config) { + config.Digest = digest + } +} + +func WithGetChainMetaFunc(f func() *pb.ChainMeta) Option { + return func(config *Config) { + config.GetChainMetaFunc = f + } +} + +func WithGetTransactionFunc(f func(hash types.Hash) (*pb.Transaction, error)) Option { + return func(config *Config) { + config.GetTransactionFunc = f + } +} + +func checkConfig(config *Config) error { + if config.Logger == nil { + return fmt.Errorf("logger is nil") + } + + return nil +} + +func GenerateConfig(opts ...Option) (*Config, error) { + config := &Config{} + for _, opt := range opts { + opt(config) + } + + if err := checkConfig(config); err != nil { + return nil, fmt.Errorf("create p2p: %w", err) + } + + return config, nil +} diff --git a/pkg/order/filter.go b/pkg/order/filter.go new file mode 100644 index 0000000..da9c6e2 --- /dev/null +++ b/pkg/order/filter.go @@ -0,0 +1,62 @@ +package order + +import ( + "bytes" + + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/sirupsen/logrus" + "github.com/willf/bloom" +) + +const ( + filterDbKey = "bloom_filter" + m = 10000000 //bits + k = 4 //calc hash times +) + +type ReqLookUp struct { + filter *bloom.BloomFilter + storage storage.Storage + buffer bytes.Buffer + logger logrus.FieldLogger //logger +} + +func NewReqLookUp(storage storage.Storage, logger logrus.FieldLogger) (*ReqLookUp, error) { + filter := bloom.New(m, k) + filterDB, _ := storage.Get([]byte(filterDbKey)) + if filterDB != nil { + var b bytes.Buffer + if _, err := b.Write(filterDB); err != nil { + return nil, err + } + if _, err := filter.ReadFrom(&b); err != nil { + return nil, err + } + } + return &ReqLookUp{ + filter: filter, + storage: storage, + logger: logger, + }, nil + +} + +func (r *ReqLookUp) Add(key []byte) { + r.filter.Add(key) +} + +func (r *ReqLookUp) LookUp(key []byte) bool { + return r.filter.TestAndAdd(key) +} + +func (r *ReqLookUp) Build() error { + if _, err := r.filter.WriteTo(&r.buffer); err != nil { + return err + } + //r.logger.Debugln("bloom filter bytes length:", r.buffer.Len()) + if err := r.storage.Put([]byte(filterDbKey), r.buffer.Bytes()); err != nil { + return err + } + r.buffer.Reset() + return nil +} diff --git a/pkg/order/filter_test.go b/pkg/order/filter_test.go new file mode 100644 index 0000000..19c2580 --- /dev/null +++ b/pkg/order/filter_test.go @@ -0,0 +1,22 @@ +package order + +import ( + "testing" + + "github.com/meshplus/bitxhub-kit/log" + + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/stretchr/testify/require" +) + +func TestReqLookUp_Add(t *testing.T) { + storage, err := leveldb.New("./build") + require.Nil(t, err) + r, err := NewReqLookUp(storage, log.NewWithModule("bloom_filter")) + require.Nil(t, err) + r.Add([]byte("abcd")) + require.Nil(t, err) + err = r.Build() + require.Nil(t, err) + require.True(t, r.LookUp([]byte("abcd"))) +} diff --git a/pkg/order/order.go b/pkg/order/order.go new file mode 100644 index 0000000..79cc38b --- /dev/null +++ b/pkg/order/order.go @@ -0,0 +1,35 @@ +package order + +import ( + "context" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" +) + +//go:generate mockgen -destination mock_order/mock_order.go -package mock_order -source order.go +type Order interface { + // Start the order service. + Start() error + + // Stop means frees the resources which were allocated for this service. + Stop() + + // Prepare means send transaction to the consensus engine + Prepare(tx *pb.Transaction) error + + // Commit recv blocks form Order and commit it by order + Commit() chan *pb.Block + + // Step send msg to the consensus engine + Step(ctx context.Context, msg []byte) error + + // Ready means whether order has finished electing leader + Ready() bool + + // ReportState means block was persisted and report it to the consensus engine + ReportState(height uint64, hash types.Hash) + + // Quorum means minimum number of nodes in the cluster that can work + Quorum() uint64 +} diff --git a/pkg/peermgr/handle.go b/pkg/peermgr/handle.go new file mode 100644 index 0000000..5687929 --- /dev/null +++ b/pkg/peermgr/handle.go @@ -0,0 +1,146 @@ +package peermgr + +import ( + "crypto/x509" + "fmt" + "strconv" + + "github.com/meshplus/bitxhub/internal/model" + + "github.com/libp2p/go-libp2p-core/network" + network2 "github.com/libp2p/go-libp2p-core/network" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/meshplus/bitxhub/pkg/cert" + proto2 "github.com/meshplus/bitxhub/pkg/network/proto" +) + +func (swarm *Swarm) handleMessage(s network2.Stream, msg *proto2.Message) error { + m := &pb.Message{} + if err := m.Unmarshal(msg.Data); err != nil { + return err + } + + switch m.Type { + case pb.Message_GET_BLOCK: + return swarm.handleGetBlockPack(s, m) + case pb.Message_FETCH_CERT: + return swarm.handleFetchCertMessage(s) + case pb.Message_CONSENSUS: + go swarm.orderMessageFeed.Send(events.OrderMessageEvent{Data: m.Data}) + case pb.Message_FETCH_BLOCK_SIGN: + swarm.handleFetchBlockSignMessage(s, m.Data) + default: + swarm.logger.WithField("module", "p2p").Errorf("can't handle msg[type: %v]", m.Type) + } + + return nil +} + +func (swarm *Swarm) handleGetBlockPack(s network.Stream, msg *pb.Message) error { + num, err := strconv.Atoi(string(msg.Data)) + if err != nil { + return err + } + + block, err := swarm.ledger.GetBlock(uint64(num)) + if err != nil { + return err + } + + v, err := block.Marshal() + if err != nil { + return err + } + + m := &pb.Message{ + Type: pb.Message_GET_BLOCK_ACK, + Data: v, + } + + if err := swarm.SendWithStream(s, m); err != nil { + return err + } + + return nil +} + +func (swarm *Swarm) handleFetchCertMessage(s network.Stream) error { + certs := &model.CertsMessage{ + AgencyCert: swarm.repo.Certs.AgencyCertData, + NodeCert: swarm.repo.Certs.NodeCertData, + } + + data, err := certs.Marshal() + if err != nil { + return fmt.Errorf("marshal certs: %w", err) + } + + msg := &pb.Message{ + Type: pb.Message_FETCH_CERT, + Data: data, + } + + err = swarm.SendWithStream(s, msg) + if err != nil { + return fmt.Errorf("send msg: %w", err) + } + + return nil +} + +func verifyCerts(nodeCert *x509.Certificate, agencyCert *x509.Certificate, caCert *x509.Certificate) error { + if err := cert.VerifySign(agencyCert, caCert); err != nil { + return fmt.Errorf("verify agency cert: %w", err) + } + + if err := cert.VerifySign(nodeCert, agencyCert); err != nil { + return fmt.Errorf("verify node cert: %w", err) + } + + return nil +} + +func (swarm *Swarm) handleFetchBlockSignMessage(s network2.Stream, data []byte) { + handle := func(data []byte) ([]byte, error) { + height, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return nil, fmt.Errorf("parse height: %w", err) + } + + swarm.logger.WithField("height", height).Debug("Handle fetching block sign message") + + signed, err := swarm.ledger.GetBlockSign(height) + if err != nil { + return nil, fmt.Errorf("get block sign: %w", err) + } + + return signed, nil + } + + signed, err := handle(data) + if err != nil { + swarm.logger.Errorf("handle fetch-block-sign: %s", err) + return + } + + m := model.MerkleWrapperSign{ + Address: swarm.repo.Key.Address, + Signature: signed, + } + + body, err := m.Marshal() + if err != nil { + swarm.logger.Errorf("marshal merkle wrapper sign: %s", err) + return + } + + msg := &pb.Message{ + Type: pb.Message_FETCH_BLOCK_SIGN_ACK, + Data: body, + } + + if err := swarm.SendWithStream(s, msg); err != nil { + swarm.logger.Errorf("send block sign back: %s", err) + } +} diff --git a/pkg/peermgr/peermgr.go b/pkg/peermgr/peermgr.go new file mode 100644 index 0000000..188ac98 --- /dev/null +++ b/pkg/peermgr/peermgr.go @@ -0,0 +1,39 @@ +package peermgr + +import ( + "github.com/ethereum/go-ethereum/event" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/model/events" +) + +//go:generate mockgen -destination mock_peermgr/mock_peermgr.go -package mock_peermgr -source peermgr.go +type PeerManager interface { + // Start + Start() error + + // Stop + Stop() error + + // Send message to peer with peer info. + Send(uint64, *pb.Message) error + + // Send message using existed stream + SendWithStream(network.Stream, *pb.Message) error + + // Sync Send message + SyncSend(uint64, *pb.Message) (*pb.Message, error) + + // Broadcast message to all node + Broadcast(*pb.Message) error + + // Peers + Peers() map[uint64]*peer.AddrInfo + + // OtherPeers + OtherPeers() map[uint64]*peer.AddrInfo + + // SubscribeOrderMessage + SubscribeOrderMessage(ch chan<- events.OrderMessageEvent) event.Subscription +} diff --git a/pkg/peermgr/swarm.go b/pkg/peermgr/swarm.go new file mode 100644 index 0000000..ea5c7b5 --- /dev/null +++ b/pkg/peermgr/swarm.go @@ -0,0 +1,266 @@ +package peermgr + +import ( + "context" + "fmt" + "time" + + "github.com/Rican7/retry" + "github.com/Rican7/retry/strategy" + "github.com/ethereum/go-ethereum/event" + network2 "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/internal/model" + "github.com/meshplus/bitxhub/internal/model/events" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/meshplus/bitxhub/pkg/cert" + "github.com/meshplus/bitxhub/pkg/network" + "github.com/meshplus/bitxhub/pkg/network/p2p" + "github.com/sirupsen/logrus" +) + +const ( + protocolID protocol.ID = "/B1txHu6/1.0.0" // magic protocol +) + +type Swarm struct { + repo *repo.Repo + p2p network.Network + logger logrus.FieldLogger + peers map[uint64]*peer.AddrInfo + connectedPeers map[uint64]*peer.AddrInfo + ledger ledger.Ledger + + orderMessageFeed event.Feed + + ctx context.Context + cancel context.CancelFunc +} + +func New(repo *repo.Repo, logger logrus.FieldLogger, ledger ledger.Ledger) (*Swarm, error) { + p2p, err := p2p.New( + p2p.WithLocalAddr(repo.NetworkConfig.LocalAddr), + p2p.WithPrivateKey(repo.Key.Libp2pPrivKey), + p2p.WithProtocolID(protocolID), + p2p.WithLogger(logger), + ) + + if err != nil { + return nil, fmt.Errorf("create p2p: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + return &Swarm{ + repo: repo, + p2p: p2p, + logger: logger, + ledger: ledger, + peers: repo.NetworkConfig.OtherNodes, + connectedPeers: make(map[uint64]*peer.AddrInfo, len(repo.NetworkConfig.OtherNodes)), + ctx: ctx, + cancel: cancel, + }, nil +} + +func (swarm *Swarm) Start() error { + if err := swarm.p2p.Start(); err != nil { + return err + } + + go swarm.receiveMessage() + + for id, addr := range swarm.peers { + swarm.p2p.IDStore().Add(id, addr) + go func(id uint64, addr *peer.AddrInfo) { + if err := retry.Retry(func(attempt uint) error { + if err := swarm.p2p.Connect(id); err != nil { + swarm.logger.WithFields(logrus.Fields{ + "node": id, + "error": err, + }).Error("Connect failed") + return err + } + + if err := swarm.verifyCert(id); err != nil { + if attempt != 0 && attempt%5 == 0 { + swarm.logger.WithFields(logrus.Fields{ + "node": id, + "error": err, + }).Error("Verify cert") + } + + return err + } + + swarm.logger.WithFields(logrus.Fields{ + "node": id, + }).Info("Connect successfully") + + swarm.connectedPeers[id] = addr + + return nil + }, + strategy.Wait(1*time.Second), + ); err != nil { + swarm.logger.Error(err) + } + }(id, addr) + } + + return nil +} + +func (swarm *Swarm) Stop() error { + swarm.cancel() + + return nil +} + +func (swarm *Swarm) Send(id uint64, msg *pb.Message) error { + if err := swarm.checkID(id); err != nil { + return fmt.Errorf("p2p send: %w", err) + } + + data, err := msg.Marshal() + if err != nil { + return err + } + + m := network.Message(data) + + return swarm.p2p.Send(id, m) +} + +func (swarm *Swarm) SendWithStream(s network2.Stream, msg *pb.Message) error { + data, err := msg.Marshal() + if err != nil { + return err + } + + m := network.Message(data) + + return swarm.p2p.SendWithStream(s, m) +} + +func (swarm *Swarm) SyncSend(id uint64, msg *pb.Message) (*pb.Message, error) { + if err := swarm.checkID(id); err != nil { + return nil, fmt.Errorf("check id: %w", err) + } + + data, err := msg.Marshal() + if err != nil { + return nil, err + } + + ret, err := swarm.p2p.SyncSend(id, network.Message(data)) + if err != nil { + return nil, fmt.Errorf("sync send: %w", err) + } + + m := &pb.Message{} + if err := m.Unmarshal(ret.Data); err != nil { + return nil, err + } + + return m, nil +} + +func (swarm *Swarm) Broadcast(msg *pb.Message) error { + ids := make([]network.ID, 0, len(swarm.peers)) + for id := range swarm.peers { + ids = append(ids, id) + } + + data, err := msg.Marshal() + if err != nil { + return err + } + + m := network.Message(data) + + return swarm.p2p.Broadcast(ids, m) +} + +func (swarm *Swarm) Peers() map[uint64]*peer.AddrInfo { + m := make(map[uint64]*peer.AddrInfo) + for id, addr := range swarm.peers { + m[id] = addr + } + + return m +} + +func (swarm *Swarm) OtherPeers() map[uint64]*peer.AddrInfo { + m := swarm.Peers() + delete(m, swarm.repo.NetworkConfig.ID) + + return m +} + +func (swarm *Swarm) SubscribeOrderMessage(ch chan<- events.OrderMessageEvent) event.Subscription { + return swarm.orderMessageFeed.Subscribe(ch) +} + +func (swarm *Swarm) verifyCert(id uint64) error { + msg := &pb.Message{ + Type: pb.Message_FETCH_CERT, + } + + ret, err := swarm.SyncSend(id, msg) + if err != nil { + return fmt.Errorf("sync send: %w", err) + } + + certs := &model.CertsMessage{} + if err := certs.Unmarshal(ret.Data); err != nil { + return fmt.Errorf("unmarshal certs: %w", err) + } + + nodeCert, err := cert.ParseCert(certs.NodeCert) + if err != nil { + return fmt.Errorf("parse node cert: %w", err) + } + + agencyCert, err := cert.ParseCert(certs.AgencyCert) + if err != nil { + return fmt.Errorf("parse agency cert: %w", err) + } + + if err := verifyCerts(nodeCert, agencyCert, swarm.repo.Certs.CACert); err != nil { + return fmt.Errorf("verify certs: %w", err) + } + + err = swarm.p2p.Disconnect(id) + if err != nil { + return fmt.Errorf("disconnect peer: %w", err) + } + + return nil +} + +func (swarm *Swarm) checkID(id uint64) error { + if swarm.peers[id] == nil { + return fmt.Errorf("wrong id: %d", id) + } + + return nil +} + +func (swarm *Swarm) receiveMessage() { + for { + select { + case m := <-swarm.p2p.Receive(): + go func() { + if err := swarm.handleMessage(m.Stream, m.Message); err != nil { + swarm.logger.WithField("error", err).Error("Handle message") + } + }() + case <-swarm.ctx.Done(): + return + } + } +} diff --git a/pkg/storage/leveldb/iterator.go b/pkg/storage/leveldb/iterator.go new file mode 100644 index 0000000..994b1c5 --- /dev/null +++ b/pkg/storage/leveldb/iterator.go @@ -0,0 +1,27 @@ +package leveldb + +import "github.com/syndtr/goleveldb/leveldb/iterator" + +type iter struct { + iter iterator.Iterator +} + +func (it *iter) Prev() bool { + return it.iter.Prev() +} + +func (it *iter) Seek(key []byte) bool { + return it.iter.Seek(key) +} + +func (it *iter) Next() bool { + return it.iter.Next() +} + +func (it *iter) Key() []byte { + return it.iter.Key() +} + +func (it *iter) Value() []byte { + return it.iter.Value() +} diff --git a/pkg/storage/leveldb/leveldb.go b/pkg/storage/leveldb/leveldb.go new file mode 100644 index 0000000..78e794e --- /dev/null +++ b/pkg/storage/leveldb/leveldb.go @@ -0,0 +1,82 @@ +package leveldb + +import ( + "github.com/meshplus/bitxhub/pkg/storage" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/util" +) + +type ldb struct { + db *leveldb.DB +} + +func New(path string) (storage.Storage, error) { + db, err := leveldb.OpenFile(path, nil) + if err != nil { + return nil, err + } + + return &ldb{ + db: db, + }, nil +} + +func (l *ldb) Put(key, value []byte) error { + return l.db.Put(key, value, nil) +} + +func (l *ldb) Delete(key []byte) error { + return l.db.Delete(key, nil) +} + +func (l *ldb) Get(key []byte) (value []byte, err error) { + return l.db.Get(key, nil) +} + +func (l *ldb) Has(key []byte) (exists bool, err error) { + return l.db.Has(key, nil) +} + +func (l *ldb) Iterator(start, end []byte) storage.Iterator { + rg := &util.Range{ + Start: start, + Limit: end, + } + it := l.db.NewIterator(rg, nil) + + return &iter{iter: it} +} + +func (l *ldb) Prefix(prefix []byte) storage.Iterator { + rg := util.BytesPrefix(prefix) + + return &iter{iter: l.db.NewIterator(rg, nil)} +} + +func (l *ldb) NewBatch() storage.Batch { + return &ldbBatch{ + ldb: l.db, + batch: &leveldb.Batch{}, + } +} + +func (l *ldb) Close() error { + return l.db.Close() +} + +type ldbBatch struct { + ldb *leveldb.DB + batch *leveldb.Batch +} + +func (l *ldbBatch) Put(key, value []byte) { + l.batch.Put(key, value) +} + +func (l *ldbBatch) Delete(key []byte) { + l.batch.Delete(key) +} + +func (l *ldbBatch) Commit() error { + return l.ldb.Write(l.batch, nil) +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go new file mode 100644 index 0000000..ce7bcb3 --- /dev/null +++ b/pkg/storage/storage.go @@ -0,0 +1,61 @@ +package storage + +type Storage interface { + Write + + // Get retrieves the object `value` named by `key`. + // Get will return ErrNotFound if the key is not mapped to a value. + Get(key []byte) (value []byte, err error) + + // Has returns whether the `key` is mapped to a `value`. + Has(key []byte) (exists bool, err error) + + // Iterator iterates over a DB's key/value pairs in key order. + Iterator(start, end []byte) Iterator + + // QueryByPrefix iterates over a DB's key/value pairs in key order including prefix. + Prefix(prefix []byte) Iterator + + NewBatch() Batch + + Close() error +} + +// Write is the write-side of the storage interface. +type Write interface { + // Put stores the object `value` named by `key`. + Put(key, value []byte) error + + // Delete removes the value for given `key`. If the key is not in the + // datastore, this method returns no error. + Delete(key []byte) error +} + +type Iterator interface { + // Next moves the iterator to the next key/value pair. + // It returns false if the iterator is exhausted. + Next() bool + + // Prev moves the iterator to the previous key/value pair. + // It returns false if the iterator is exhausted. + Prev() bool + + // Seek moves the iterator to the first key/value pair whose key is greater + // than or equal to the given key. + // It returns whether such pair exist. + // + // It is safe to modify the contents of the argument after Seek returns. + Seek(key []byte) bool + + // Key returns the key of the current key/value pair, or nil if done. + Key() []byte + + // Value returns the value of the current key/value pair, or nil if done. + Value() []byte +} + +type Batch interface { + Put(key, value []byte) + Delete(key []byte) + Commit() error +} diff --git a/pkg/vm/boltvm/bolt_stub.go b/pkg/vm/boltvm/bolt_stub.go new file mode 100755 index 0000000..16cccd3 --- /dev/null +++ b/pkg/vm/boltvm/bolt_stub.go @@ -0,0 +1,136 @@ +package boltvm + +import ( + "encoding/json" + + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/validator" + "github.com/meshplus/bitxhub/pkg/vm" + "github.com/sirupsen/logrus" +) + +var _ Stub = (*BoltStubImpl)(nil) + +type BoltStubImpl struct { + bvm *BoltVM + ctx *vm.Context + vlt validator.Validator +} + +func (b *BoltStubImpl) Caller() string { + return b.ctx.Caller.Hex() +} + +func (b *BoltStubImpl) Callee() string { + return b.ctx.Callee.Hex() +} + +func (b *BoltStubImpl) Logger() logrus.FieldLogger { + return b.ctx.Logger +} + +// GetTxHash returns the transaction hash +func (b *BoltStubImpl) GetTxHash() types.Hash { + hash := b.ctx.TransactionHash + return hash +} + +func (b *BoltStubImpl) GetTxIndex() uint64 { + return b.ctx.TransactionIndex +} + +func (b *BoltStubImpl) Has(key string) bool { + exist, _ := b.ctx.Ledger.GetState(b.ctx.Callee, []byte(key)) + return exist +} + +func (b *BoltStubImpl) Get(key string) (bool, []byte) { + return b.ctx.Ledger.GetState(b.ctx.Callee, []byte(key)) +} + +func (b *BoltStubImpl) Delete(key string) { + +} + +func (b *BoltStubImpl) GetObject(key string, ret interface{}) bool { + ok, data := b.Get(key) + if !ok { + return ok + } + + err := json.Unmarshal(data, ret) + return err == nil +} + +func (b *BoltStubImpl) Set(key string, value []byte) { + b.ctx.Ledger.SetState(b.ctx.Callee, []byte(key), value) +} + +func (b *BoltStubImpl) SetObject(key string, value interface{}) { + data, err := json.Marshal(value) + if err != nil { + panic(err) + } + + b.Set(key, data) +} + +func (b *BoltStubImpl) Query(prefix string) (bool, [][]byte) { + return b.ctx.Ledger.QueryByPrefix(b.ctx.Callee, prefix) +} + +func (b *BoltStubImpl) PostEvent(event interface{}) { + b.postEvent(false, event) +} + +func (b *BoltStubImpl) PostInterchainEvent(event interface{}) { + b.postEvent(true, event) +} + +func (b *BoltStubImpl) postEvent(interchain bool, event interface{}) { + data, err := json.Marshal(event) + if err != nil { + panic(err) + } + + b.ctx.Ledger.AddEvent(&pb.Event{ + Interchain: interchain, + Data: data, + TxHash: b.GetTxHash(), + }) +} + +func (b *BoltStubImpl) CrossInvoke(address, method string, args ...*pb.Arg) *Response { + addr := types.String2Address(address) + + payload := &pb.InvokePayload{ + Method: method, + Args: args, + } + + ctx := &vm.Context{ + Caller: b.bvm.ctx.Caller, + Callee: addr, + Ledger: b.bvm.ctx.Ledger, + TransactionIndex: b.bvm.ctx.TransactionIndex, + TransactionHash: b.bvm.ctx.TransactionHash, + Logger: b.bvm.ctx.Logger, + } + + data, err := payload.Marshal() + if err != nil { + return Error(err.Error()) + } + bvm := New(ctx, b.vlt) + ret, err := bvm.Run(data) + if err != nil { + return Error(err.Error()) + } + + return Success(ret) +} + +func (b *BoltStubImpl) Validator() validator.Validator { + return b.vlt +} diff --git a/pkg/vm/boltvm/boltvm.go b/pkg/vm/boltvm/boltvm.go new file mode 100755 index 0000000..3d0eafc --- /dev/null +++ b/pkg/vm/boltvm/boltvm.go @@ -0,0 +1,104 @@ +package boltvm + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/validator" + "github.com/meshplus/bitxhub/pkg/vm" +) + +var _ vm.VM = (*BoltVM)(nil) + +type BoltVM struct { + ctx *vm.Context + vlt validator.Validator +} + +// New creates a blot vm object +func New(ctx *vm.Context, vlt validator.Validator) *BoltVM { + return &BoltVM{ + ctx: ctx, + vlt: vlt, + } +} + +func (bvm *BoltVM) Run(input []byte) (ret []byte, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + }() + + payload := &pb.InvokePayload{} + if err := payload.Unmarshal(input); err != nil { + return nil, fmt.Errorf("unmarshal invoke payload: %w", err) + } + + contract, err := GetBoltContract(bvm.ctx.Callee.Hex()) + if err != nil { + return nil, fmt.Errorf("get bolt contract: %w", err) + } + + rc := reflect.ValueOf(contract) + stubField := rc.Elem().Field(0) + stub := &BoltStubImpl{ + bvm: bvm, + ctx: bvm.ctx, + vlt: bvm.vlt, + } + + if stubField.CanSet() { + stubField.Set(reflect.ValueOf(stub)) + } else { + return nil, fmt.Errorf("stub filed can`t set") + } + + // judge whether method is valid + m := rc.MethodByName(payload.Method) + if !m.IsValid() { + return nil, fmt.Errorf("not such method `%s`", payload.Method) + } + + fnArgs, err := parseArgs(payload.Args) + if err != nil { + return nil, fmt.Errorf("parse args: %w", err) + } + + res := m.Call(fnArgs)[0].Interface().(*Response) + if !res.Ok { + return nil, fmt.Errorf("call error: %s", res.Result) + } + + return res.Result, err +} + +func parseArgs(in []*pb.Arg) ([]reflect.Value, error) { + args := make([]reflect.Value, len(in)) + for i := 0; i < len(in); i++ { + switch in[i].Type { + case pb.Arg_I32: + ret, err := strconv.Atoi(string(in[i].Value)) + if err != nil { + return nil, err + } + + args[i] = reflect.ValueOf(int32(ret)) + case pb.Arg_U64: + ret, err := strconv.ParseUint(string(in[i].Value), 10, 64) + if err != nil { + return nil, err + } + args[i] = reflect.ValueOf(ret) + case pb.Arg_String: + args[i] = reflect.ValueOf(string(in[i].Value)) + case pb.Arg_Bytes: + args[i] = reflect.ValueOf(in[i].Value) + default: + args[i] = reflect.ValueOf(string(in[i].Value)) + } + } + return args, nil +} diff --git a/pkg/vm/boltvm/context.go b/pkg/vm/boltvm/context.go new file mode 100644 index 0000000..c9878d1 --- /dev/null +++ b/pkg/vm/boltvm/context.go @@ -0,0 +1,48 @@ +package boltvm + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/sirupsen/logrus" +) + +type Context struct { + caller types.Address + callee types.Address + ledger ledger.Ledger + transactionIndex uint64 + transactionHash types.Hash + logger logrus.FieldLogger +} + +func NewContext(tx *pb.Transaction, txIndex uint64, data *pb.TransactionData, ledger ledger.Ledger, logger logrus.FieldLogger) *Context { + return &Context{ + caller: tx.From, + callee: tx.To, + ledger: ledger, + transactionIndex: txIndex, + transactionHash: tx.TransactionHash, + logger: logger, + } +} + +func (ctx *Context) Caller() string { + return ctx.caller.Hex() +} + +func (ctx *Context) Callee() string { + return ctx.callee.Hex() +} + +func (ctx *Context) TransactionIndex() uint64 { + return ctx.transactionIndex +} + +func (ctx *Context) TransactionHash() types.Hash { + return ctx.transactionHash +} + +func (ctx *Context) Logger() logrus.FieldLogger { + return ctx.logger +} diff --git a/pkg/vm/boltvm/register.go b/pkg/vm/boltvm/register.go new file mode 100644 index 0000000..c0327cf --- /dev/null +++ b/pkg/vm/boltvm/register.go @@ -0,0 +1,38 @@ +package boltvm + +import ( + "fmt" +) + +type BoltContract struct { + // enable/disable bolt contract + Enabled bool + // contract name + Name string + // contract address + Address string + // Contract is contract object + Contract Contract +} + +var boltRegister map[string]Contract + +// register contract +func Register(contracts []*BoltContract) { + boltRegister = make(map[string]Contract) + for _, c := range contracts { + if _, ok := boltRegister[c.Address]; ok { + panic("duplicate bolt contract address") + } else { + boltRegister[c.Address] = c.Contract + } + } +} + +func GetBoltContract(address string) (contract Contract, err error) { + var ok bool + if contract, ok = boltRegister[address]; !ok { + return nil, fmt.Errorf("the address %v is not a bolt contract", address) + } + return contract, nil +} diff --git a/pkg/vm/boltvm/response.go b/pkg/vm/boltvm/response.go new file mode 100644 index 0000000..45c425e --- /dev/null +++ b/pkg/vm/boltvm/response.go @@ -0,0 +1,23 @@ +package boltvm + +type Response struct { + Ok bool + Result []byte +} + +// Result returns normal result +func Success(data []byte) *Response { + return &Response{ + Ok: true, + Result: data, + } +} + +// Error returns error result that will cause +// vm call error, and this transaction will be invalid +func Error(msg string) *Response { + return &Response{ + Ok: false, + Result: []byte(msg), + } +} diff --git a/pkg/vm/boltvm/stub.go b/pkg/vm/boltvm/stub.go new file mode 100644 index 0000000..9f8203b --- /dev/null +++ b/pkg/vm/boltvm/stub.go @@ -0,0 +1,45 @@ +package boltvm + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/validator" + "github.com/sirupsen/logrus" +) + +type Contract interface{} + +type Stub interface { + // Caller + Caller() string + // Callee + Callee() string + // Logger + Logger() logrus.FieldLogger + // GetTxHash returns the transaction hash + GetTxHash() types.Hash + // GetTxIndex returns the transaction index in the block + GetTxIndex() uint64 + // Has judges key + Has(key string) bool + // Get gets value from datastore by key + Get(key string) (bool, []byte) + // GetObject + GetObject(key string, ret interface{}) bool + // Set sets k-v + Set(key string, value []byte) + // SetObject sets k with object v, v will be marshaled using json + SetObject(key string, value interface{}) + // Delete deletes k-v + Delete(key string) + // QueryByPrefix queries object by prefix + Query(prefix string) (bool, [][]byte) + // PostEvent posts event to external + PostEvent(interface{}) + // PostInterchainEvent posts interchain event to external + PostInterchainEvent(interface{}) + // Validator returns the instance of validator + Validator() validator.Validator + // CrossInvoke cross contract invoke + CrossInvoke(address, method string, args ...*pb.Arg) *Response +} diff --git a/pkg/vm/context.go b/pkg/vm/context.go new file mode 100644 index 0000000..3168042 --- /dev/null +++ b/pkg/vm/context.go @@ -0,0 +1,34 @@ +package vm + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/sirupsen/logrus" +) + +// Context represents the context of wasm +type Context struct { + Caller types.Address + Callee types.Address + Ledger ledger.Ledger + TransactionIndex uint64 + TransactionHash types.Hash + TransactionData *pb.TransactionData + Nonce int64 + Logger logrus.FieldLogger +} + +// NewContext creates a context of wasm instance +func NewContext(tx *pb.Transaction, txIndex uint64, data *pb.TransactionData, ledger ledger.Ledger, logger logrus.FieldLogger) *Context { + return &Context{ + Caller: tx.From, + Callee: tx.To, + Ledger: ledger, + TransactionIndex: txIndex, + TransactionHash: tx.TransactionHash, + TransactionData: data, + Nonce: tx.Nonce, + Logger: logger, + } +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go new file mode 100755 index 0000000..e21f936 --- /dev/null +++ b/pkg/vm/vm.go @@ -0,0 +1,9 @@ +package vm + +// VM is the basic interface for an implementation of the VM. +type VM interface { + // Run should execute the given contract with the given input + // and return the contract execution return bytes or an error if it + // failed. + Run(input []byte) ([]byte, error) +} diff --git a/pkg/vm/wasm/context.go b/pkg/vm/wasm/context.go new file mode 100755 index 0000000..e6d4b58 --- /dev/null +++ b/pkg/vm/wasm/context.go @@ -0,0 +1,45 @@ +package wasm + +import ( + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/sirupsen/logrus" +) + +// Context represents the context of wasm +type Context struct { + caller types.Address + callee types.Address + ledger ledger.Ledger + transactionData *pb.TransactionData + nonce int64 + logger logrus.FieldLogger +} + +// NewContext creates a context of wasm instance +func NewContext(tx *pb.Transaction, data *pb.TransactionData, ledger ledger.Ledger, logger logrus.FieldLogger) *Context { + return &Context{ + caller: tx.From, + callee: tx.To, + ledger: ledger, + transactionData: data, + nonce: tx.Nonce, + logger: logger, + } +} + +// Caller returns the tx caller address +func (ctx *Context) Caller() string { + return ctx.caller.Hex() +} + +// Callee returns the tx callee address +func (ctx *Context) Callee() string { + return ctx.callee.Hex() +} + +// Logger returns the log instance +func (ctx *Context) Logger() logrus.FieldLogger { + return ctx.logger +} diff --git a/pkg/vm/wasm/runtime.go b/pkg/vm/wasm/runtime.go new file mode 100644 index 0000000..fd0e170 --- /dev/null +++ b/pkg/vm/wasm/runtime.go @@ -0,0 +1,49 @@ +package wasm + +// SetString set the string type arg for wasm +func (w *Wasm) SetString(str string) (int32, error) { + alloc := w.Instance.Exports["allocate"] + lengthOfStr := len(str) + + allocResult, err := alloc(lengthOfStr) + if err != nil { + return 0, err + } + inputPointer := allocResult.ToI32() + + memory := w.Instance.Memory.Data()[inputPointer:] + + var i int + for i = 0; i < lengthOfStr; i++ { + memory[i] = str[i] + } + + memory[i] = 0 + w.argMap[int(inputPointer)] = len(str) + + return inputPointer, nil +} + +// SetBytes set bytes type arg for wasm +func (w *Wasm) SetBytes(b []byte) (int32, error) { + alloc := w.Instance.Exports["allocate"] + lengthOfBytes := len(b) + + allocResult, err := alloc(lengthOfBytes) + if err != nil { + return 0, err + } + inputPointer := allocResult.ToI32() + + memory := w.Instance.Memory.Data()[inputPointer:] + + var i int + for i = 0; i < lengthOfBytes; i++ { + memory[i] = b[i] + } + + memory[i] = 0 + w.argMap[int(inputPointer)] = len(b) + + return inputPointer, nil +} diff --git a/pkg/vm/wasm/testdata/conf b/pkg/vm/wasm/testdata/conf new file mode 100644 index 0000000..12eb8b9 --- /dev/null +++ b/pkg/vm/wasm/testdata/conf @@ -0,0 +1 @@ +config:"\n\007Org1MSP\022\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\nB\016\n\004SHA2\022\006SHA256J\347\006-----BEGIN CERTIFICATE-----\nMIICVzCCAf2gAwIBAgIQf3G6fCLqBl6of6Mne0kgrjAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIy\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEnOg10EXRyXMIdXulFZ8A/C74TP93sPlv6VGeuJL45BE0KDfduZTlAFSq\ni8JzyWuYoeyMmXNgn4MVKf8dUNrGCKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCB5+011QxrNtHNxk53Hlc0+6dJwUYB9zjTCSnBlSRRuCTAKBggqhkjOPQQD\nAgNIADBFAiEA0iXoxQ2QYwB58E/OlH67EXixxy+y8MWn3nHqQxcFbj8CIDiuiBYn\n6BPE4Vni3TtITEUoKr1Zk9FEArFqd4jZckyY\n-----END CERTIFICATE-----\nZ\264\033\010\001\022\352\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\006client\032\350\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\004peer\"\351\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\005admin*\353\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\007orderer" \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/conf1 b/pkg/vm/wasm/testdata/conf1 new file mode 100644 index 0000000..12eb8b9 --- /dev/null +++ b/pkg/vm/wasm/testdata/conf1 @@ -0,0 +1 @@ +config:"\n\007Org1MSP\022\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\nB\016\n\004SHA2\022\006SHA256J\347\006-----BEGIN CERTIFICATE-----\nMIICVzCCAf2gAwIBAgIQf3G6fCLqBl6of6Mne0kgrjAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIy\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEnOg10EXRyXMIdXulFZ8A/C74TP93sPlv6VGeuJL45BE0KDfduZTlAFSq\ni8JzyWuYoeyMmXNgn4MVKf8dUNrGCKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCB5+011QxrNtHNxk53Hlc0+6dJwUYB9zjTCSnBlSRRuCTAKBggqhkjOPQQD\nAgNIADBFAiEA0iXoxQ2QYwB58E/OlH67EXixxy+y8MWn3nHqQxcFbj8CIDiuiBYn\n6BPE4Vni3TtITEUoKr1Zk9FEArFqd4jZckyY\n-----END CERTIFICATE-----\nZ\264\033\010\001\022\352\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\006client\032\350\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\004peer\"\351\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\005admin*\353\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\nFtMeiClbcFbF2skGXXmw+/LhvTS9\n-----END CERTIFICATE-----\n\022\007orderer" \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/conf2 b/pkg/vm/wasm/testdata/conf2 new file mode 100644 index 0000000..e6047fd --- /dev/null +++ b/pkg/vm/wasm/testdata/conf2 @@ -0,0 +1 @@ +config:"\n\007Org2MSP\022\337\006-----BEGIN CERTIFICATE-----\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\n-----END CERTIFICATE-----\nB\016\n\004SHA2\022\006SHA256J\347\006-----BEGIN CERTIFICATE-----\nMIICVzCCAf6gAwIBAgIRALeI2n3d7i8NYA0k5uYSudcwCgYIKoZIzj0EAwIwdjEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs\nc2NhLm9yZzIuZXhhbXBsZS5jb20wHhcNMjAwMjA1MDgyMjAwWhcNMzAwMjAyMDgy\nMjAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE\nBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEfMB0G\nA1UEAxMWdGxzY2Eub3JnMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABAqomq221xDTSYx9u+X6QRdTvEbLUT0aUHf35tIEpxghbcYlCQJjNH7h\n25ORrmZn+2d/drhzrgcgmHNw+uSjBiWjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV\nHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV\nHQ4EIgQgVlPMVnzAIjgWLk+hbqH4mqiABZRJY6XqXnzGaxiPo5gwCgYIKoZIzj0E\nAwIDRwAwRAIgGv1DhSuU8PNcopHgMIWbY6SMxPOVp7x5zGL6n7RHK58CIBtPepLU\nEBf+fYV6i/JXaaolTvdLvlStkoTR6RuuGzV6\n-----END CERTIFICATE-----\nZ\264\033\010\001\022\352\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\n-----END CERTIFICATE-----\n\022\006client\032\350\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\n-----END CERTIFICATE-----\n\022\004peer\"\351\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\n-----END CERTIFICATE-----\n\022\005admin*\353\006\n\337\006-----BEGIN CERTIFICATE-----\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\n-----END CERTIFICATE-----\n\022\007orderer" \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/conf3 b/pkg/vm/wasm/testdata/conf3 new file mode 100644 index 0000000..45fb301 --- /dev/null +++ b/pkg/vm/wasm/testdata/conf3 @@ -0,0 +1 @@ +config:"\n\nOrdererMSP\022\307\006-----BEGIN CERTIFICATE-----\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\n/kY=\n-----END CERTIFICATE-----\nB\016\n\004SHA2\022\006SHA256J\317\006-----BEGIN CERTIFICATE-----\nMIICQzCCAemgAwIBAgIQAT9ufXTKHsaf3FePfHQIazAKBggqhkjOPQQDAjBsMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEUMBIGA1UEChMLZXhhbXBsZS5jb20xGjAYBgNVBAMTEXRsc2NhLmV4\nYW1wbGUuY29tMB4XDTIwMDIwNTA4MjIwMFoXDTMwMDIwMjA4MjIwMFowbDELMAkG\nA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFu\nY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRowGAYDVQQDExF0bHNjYS5leGFt\ncGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNa1DY9sQJSm5qlKDDAP\nZHG2uVnYb4UIDonhssuRmfgl2h22Syx/WE703yMDxdaX4GYSs7pPuszyj3F9HCcS\nBNijbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYB\nBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgKP262WZIDcasrdgPeL4F\ncs1ehed8aJByB1p6HOUwmp8wCgYIKoZIzj0EAwIDSAAwRQIhAMl0+SAt/xlyqNiE\ngiZK01ho31vBVWvDt1VWsINca/ytAiAQ1qDK2Eb/pleni56Fw/6Ee1mBRR/Sh3H7\nSNbe1oR5KA==\n-----END CERTIFICATE-----\nZ\324\032\010\001\022\322\006\n\307\006-----BEGIN CERTIFICATE-----\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\n/kY=\n-----END CERTIFICATE-----\n\022\006client\032\320\006\n\307\006-----BEGIN CERTIFICATE-----\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\n/kY=\n-----END CERTIFICATE-----\n\022\004peer\"\321\006\n\307\006-----BEGIN CERTIFICATE-----\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\n/kY=\n-----END CERTIFICATE-----\n\022\005admin*\323\006\n\307\006-----BEGIN CERTIFICATE-----\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\n/kY=\n-----END CERTIFICATE-----\n\022\007orderer" \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/contract_hpc.wasm b/pkg/vm/wasm/testdata/contract_hpc.wasm new file mode 100644 index 0000000..ae90784 Binary files /dev/null and b/pkg/vm/wasm/testdata/contract_hpc.wasm differ diff --git a/pkg/vm/wasm/testdata/fab_test b/pkg/vm/wasm/testdata/fab_test new file mode 100644 index 0000000..27184d7 --- /dev/null +++ b/pkg/vm/wasm/testdata/fab_test @@ -0,0 +1,37 @@ + +V +T +R  +Broker-001@ + pollingEvent +0{"0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997":7} + + d)]= ےA+At;TI +;_ +B$ + +Broker-001 + + +OutterMeta +lscc + + +Broker-001b[]" +Broker-001v6 + +Org2MSP-----BEGIN CERTIFICATE----- +MIICKDCCAc+gAwIBAgIRAIvRdkwS+++KkoPliLaqSF0wCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzIuZXhhbXBsZS5jb20wHhcNMTkwODI3MDgwOTAwWhcNMjkwODI0MDgwOTAw +WjBqMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzENMAsGA1UECxMEcGVlcjEfMB0GA1UEAxMWcGVlcjEub3Jn +Mi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL10V0Smz2FO +HR82njo9H0HNyNWt/JUKWMt8Olx425u5y2kqs5RjAMRdyz3U3N0Tve1znDbCjoDL +WtrXW0WdKSSjTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud +IwQkMCKAIG4CB58dq07RqnQt9GKmMABv6hWRK0oDO4FBHFtpDlXUMAoGCCqGSM49 +BAMCA0cAMEQCIFuh8p+nbtjQEZEFg03BN58//9VRsukQXj0xP1eHnrD4AiBwI1jq +L6FMy96mi64g37R0i/I+T4MC5p2mzZIHvRJ8Rg== +-----END CERTIFICATE----- +F0D 1$nz+'90xnTAa!Λ : '`M7F4-6׌n1a \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/fab_test1 b/pkg/vm/wasm/testdata/fab_test1 new file mode 100644 index 0000000..c143ffa Binary files /dev/null and b/pkg/vm/wasm/testdata/fab_test1 differ diff --git a/pkg/vm/wasm/testdata/fabric_policy.wasm b/pkg/vm/wasm/testdata/fabric_policy.wasm new file mode 100644 index 0000000..6f9394b Binary files /dev/null and b/pkg/vm/wasm/testdata/fabric_policy.wasm differ diff --git a/pkg/vm/wasm/testdata/proof b/pkg/vm/wasm/testdata/proof new file mode 100755 index 0000000..2efe327 --- /dev/null +++ b/pkg/vm/wasm/testdata/proof @@ -0,0 +1,34 @@ + +$ +" + Broker + pollingEvent +{} + + 72 !ris"3s +|` +BrokerV + + +OutterMeta +> +4out-msg-0xd50ec14376374a6ef9396f813cd9c25b15241907-1 +lscc + +Broker[{"index":1,"to":"0xd50ec14376374a6ef9396f813cd9c25b15241907","fid":"mychannel-Transfer","tid":"0x9f03a1fb549899d669d49d0fc6e6c86e467b1277","func":"charge","args":"Alice,13","callback":""}]" Brokerv2 + +Org2MSP-----BEGIN CERTIFICATE----- +MIICKTCCAc+gAwIBAgIRAIBO31aZaSZoEYSy2AJuhJcwCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzIuZXhhbXBsZS5jb20wHhcNMjAwMjA1MDgyMjAwWhcNMzAwMjAyMDgyMjAw +WjBqMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzENMAsGA1UECxMEcGVlcjEfMB0GA1UEAxMWcGVlcjEub3Jn +Mi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABG3jszFPTbGm +dAYg2BxmHMTDKfQReNw3p9ttMK130qF5lQo5zLBG8Sa3viOCLnvjjg6A/P+yKnwv +isI/jEVE8T2jTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud +IwQkMCKAIMVL+daK7nMGr2/AQIXTSPFkdd3UiPVDkWtkh5ujnalEMAoGCCqGSM49 +BAMCA0gAMEUCIQDMYOQiYeMiQZTxlRkj/3/jjYvwwdCcX5AWuFmraiHkugIgFkX/ +6uiTSD0lz8P+wwlLf24cIABq2aZyi8q4gj0YfwA= +-----END CERTIFICATE----- +F0D 4ClI$J+eٴd!P?-I/ [R&s:z7y (eBdAG \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/validation_test.wasm b/pkg/vm/wasm/testdata/validation_test.wasm new file mode 100644 index 0000000..9204f21 Binary files /dev/null and b/pkg/vm/wasm/testdata/validation_test.wasm differ diff --git a/pkg/vm/wasm/testdata/validator b/pkg/vm/wasm/testdata/validator new file mode 100755 index 0000000..014ef44 --- /dev/null +++ b/pkg/vm/wasm/testdata/validator @@ -0,0 +1 @@ +{"conf_byte":["config:\"\\n\\007Org1MSP\\022\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\347\\006-----BEGIN CERTIFICATE-----\\nMIICVzCCAf2gAwIBAgIQf3G6fCLqBl6of6Mne0kgrjAKBggqhkjOPQQDAjB2MQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIy\\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\\nAQcDQgAEnOg10EXRyXMIdXulFZ8A/C74TP93sPlv6VGeuJL45BE0KDfduZTlAFSq\\ni8JzyWuYoeyMmXNgn4MVKf8dUNrGCKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\\nDgQiBCB5+011QxrNtHNxk53Hlc0+6dJwUYB9zjTCSnBlSRRuCTAKBggqhkjOPQQD\\nAgNIADBFAiEA0iXoxQ2QYwB58E/OlH67EXixxy+y8MWn3nHqQxcFbj8CIDiuiBYn\\n6BPE4Vni3TtITEUoKr1Zk9FEArFqd4jZckyY\\n-----END CERTIFICATE-----\\nZ\\264\\033\\010\\001\\022\\352\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\350\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\351\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\353\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" ","config:\"\\n\\007Org2MSP\\022\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\347\\006-----BEGIN CERTIFICATE-----\\nMIICVzCCAf6gAwIBAgIRALeI2n3d7i8NYA0k5uYSudcwCgYIKoZIzj0EAwIwdjEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs\\nc2NhLm9yZzIuZXhhbXBsZS5jb20wHhcNMjAwMjA1MDgyMjAwWhcNMzAwMjAyMDgy\\nMjAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE\\nBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEfMB0G\\nA1UEAxMWdGxzY2Eub3JnMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\\nAwEHA0IABAqomq221xDTSYx9u+X6QRdTvEbLUT0aUHf35tIEpxghbcYlCQJjNH7h\\n25ORrmZn+2d/drhzrgcgmHNw+uSjBiWjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV\\nHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV\\nHQ4EIgQgVlPMVnzAIjgWLk+hbqH4mqiABZRJY6XqXnzGaxiPo5gwCgYIKoZIzj0E\\nAwIDRwAwRAIgGv1DhSuU8PNcopHgMIWbY6SMxPOVp7x5zGL6n7RHK58CIBtPepLU\\nEBf+fYV6i/JXaaolTvdLvlStkoTR6RuuGzV6\\n-----END CERTIFICATE-----\\nZ\\264\\033\\010\\001\\022\\352\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\350\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\351\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\353\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" ","config:\"\\n\\nOrdererMSP\\022\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\317\\006-----BEGIN CERTIFICATE-----\\nMIICQzCCAemgAwIBAgIQAT9ufXTKHsaf3FePfHQIazAKBggqhkjOPQQDAjBsMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEUMBIGA1UEChMLZXhhbXBsZS5jb20xGjAYBgNVBAMTEXRsc2NhLmV4\\nYW1wbGUuY29tMB4XDTIwMDIwNTA4MjIwMFoXDTMwMDIwMjA4MjIwMFowbDELMAkG\\nA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFu\\nY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRowGAYDVQQDExF0bHNjYS5leGFt\\ncGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNa1DY9sQJSm5qlKDDAP\\nZHG2uVnYb4UIDonhssuRmfgl2h22Syx/WE703yMDxdaX4GYSs7pPuszyj3F9HCcS\\nBNijbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYB\\nBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgKP262WZIDcasrdgPeL4F\\ncs1ehed8aJByB1p6HOUwmp8wCgYIKoZIzj0EAwIDSAAwRQIhAMl0+SAt/xlyqNiE\\ngiZK01ho31vBVWvDt1VWsINca/ytAiAQ1qDK2Eb/pleni56Fw/6Ee1mBRR/Sh3H7\\nSNbe1oR5KA==\\n-----END CERTIFICATE-----\\nZ\\324\\032\\010\\001\\022\\322\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\320\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\321\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\323\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" "],"policy":"\u0012\u0010\u0012\u000e\u0008\u0001\u0012\u0002\u0008\u0000\u0012\u0002\u0008\u0001\u0012\u0002\u0008\u0002\u001a\u000e\u0012\u000c\n\nOrdererMSP\u001a\u000b\u0012\t\n\u0007Org1MSP\u001a\u000b\u0012\t\n\u0007Org2MSP","cid":"Broker"} \ No newline at end of file diff --git a/pkg/vm/wasm/testdata/wasm_test.wasm b/pkg/vm/wasm/testdata/wasm_test.wasm new file mode 100755 index 0000000..ed2cbac Binary files /dev/null and b/pkg/vm/wasm/testdata/wasm_test.wasm differ diff --git a/pkg/vm/wasm/wasm.go b/pkg/vm/wasm/wasm.go new file mode 100755 index 0000000..60f9bfd --- /dev/null +++ b/pkg/vm/wasm/wasm.go @@ -0,0 +1,215 @@ +package wasm + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "strconv" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/gogo/protobuf/proto" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/pkg/vm" + "github.com/meshplus/bitxhub/pkg/vm/wasm/wasmlib" + "github.com/wasmerio/go-ext-wasm/wasmer" +) + +var ( + errorLackOfMethod = fmt.Errorf("wasm execute: lack of method name") +) + +var _ vm.VM = (*Wasm)(nil) + +var instances = make(map[string]wasmer.Instance) + +func getInstance(code []byte) (wasmer.Instance, error) { + ret := sha256.Sum256(code) + v, ok := instances[string(ret[:])] + if ok { + return v, nil + } + + imports, err := wasmlib.New() + if err != nil { + return wasmer.Instance{}, err + } + + instance, err := wasmer.NewInstanceWithImports(code, imports) + if err != nil { + return wasmer.Instance{}, err + } + + instances[string(ret[:])] = instance + + return instance, nil +} + +// Wasm represents the wasm vm in BitXHub +type Wasm struct { + // contract context + ctx *vm.Context + + // wasm instance + Instance wasmer.Instance + + argMap map[int]int +} + +// Contract represents the smart contract structure used in the wasm vm +type Contract struct { + // contract byte + Code []byte + + // contract hash + Hash types.Hash +} + +// New creates a wasm vm instance +func New(ctx *vm.Context) (*Wasm, error) { + wasm := &Wasm{ + ctx: ctx, + } + + if ctx.Callee == (types.Address{}) { + return wasm, nil + } + + contractByte := ctx.Ledger.GetCode(ctx.Callee) + + if contractByte == nil { + return nil, fmt.Errorf("this contract address does not exist") + } + + contract := &Contract{} + if err := json.Unmarshal(contractByte, contract); err != nil { + return wasm, fmt.Errorf("contract byte not correct") + } + + if len(contract.Code) == 0 { + return wasm, fmt.Errorf("contract byte is empty") + } + + instance, err := getInstance(contract.Code) + if err != nil { + return nil, err + } + + wasm.Instance = instance + wasm.argMap = make(map[int]int) + + return wasm, nil +} + +// Run let the wasm vm excute or deploy the smart contract which depends on whether the callee is empty +func (w *Wasm) Run(input []byte) (ret []byte, err error) { + if w.ctx.Callee == (types.Address{}) { + return w.deploy() + } + + return w.execute(input) +} + +func (w *Wasm) deploy() ([]byte, error) { + if len(w.ctx.TransactionData.Payload) == 0 { + return nil, fmt.Errorf("contract cannot be empty") + } + contractNonce := w.ctx.Ledger.GetNonce(w.ctx.Caller) + + contractAddr := createAddress(w.ctx.Caller, contractNonce) + wasmStruct := &Contract{ + Code: w.ctx.TransactionData.Payload, + Hash: types.Bytes2Hash(w.ctx.TransactionData.Payload), + } + wasmByte, err := json.Marshal(wasmStruct) + if err != nil { + return nil, err + } + w.ctx.Ledger.SetCode(contractAddr, wasmByte) + + w.ctx.Ledger.SetNonce(w.ctx.Caller, contractNonce+1) + + return contractAddr.Bytes(), nil +} + +func (w *Wasm) execute(input []byte) ([]byte, error) { + payload := &pb.InvokePayload{} + if err := proto.Unmarshal(input, payload); err != nil { + return nil, err + } + + if payload.Method == "" { + return nil, errorLackOfMethod + } + + methodName, ok := w.Instance.Exports[payload.Method] + if !ok { + return nil, fmt.Errorf("wrong rule contract") + } + slice := make([]interface{}, len(payload.Args)) + for i := range slice { + arg := payload.Args[i] + switch arg.Type { + case pb.Arg_I32: + temp, err := strconv.Atoi(string(arg.Value)) + if err != nil { + return nil, err + } + slice[i] = temp + case pb.Arg_I64: + temp, err := strconv.ParseInt(string(arg.Value), 10, 64) + if err != nil { + return nil, err + } + slice[i] = temp + case pb.Arg_F32: + temp, err := strconv.ParseFloat(string(arg.Value), 32) + if err != nil { + return nil, err + } + slice[i] = temp + case pb.Arg_F64: + temp, err := strconv.ParseFloat(string(arg.Value), 64) + if err != nil { + return nil, err + } + slice[i] = temp + case pb.Arg_String: + inputPointer, err := w.SetString(string(arg.Value)) + if err != nil { + return nil, err + } + slice[i] = inputPointer + case pb.Arg_Bytes: + inputPointer, err := w.SetBytes(arg.Value) + if err != nil { + return nil, err + } + slice[i] = inputPointer + case pb.Arg_Bool: + inputPointer, err := strconv.Atoi(string(arg.Value)) + if err != nil { + return nil, err + } + slice[i] = inputPointer + default: + return nil, fmt.Errorf("input type not support") + } + } + + w.Instance.SetContextData(w.argMap) + + result, err := methodName(slice...) + if err != nil { + return nil, err + } + + return []byte(result.String()), err +} + +func createAddress(b types.Address, nonce uint64) types.Address { + data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) + hashBytes := sha256.Sum256(data) + + return types.Bytes2Address(hashBytes[12:]) +} diff --git a/pkg/vm/wasm/wasm_test.go b/pkg/vm/wasm/wasm_test.go new file mode 100755 index 0000000..7e15d92 --- /dev/null +++ b/pkg/vm/wasm/wasm_test.go @@ -0,0 +1,327 @@ +package wasm + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-kit/log" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + "github.com/meshplus/bitxhub/internal/ledger" + "github.com/meshplus/bitxhub/pkg/storage/leveldb" + "github.com/meshplus/bitxhub/pkg/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const cert1 = `-----BEGIN CERTIFICATE----- +MIICKDCCAc+gAwIBAgIRAIvRdkwS+++KkoPliLaqSF0wCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzIuZXhhbXBsZS5jb20wHhcNMTkwODI3MDgwOTAwWhcNMjkwODI0MDgwOTAw +WjBqMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzENMAsGA1UECxMEcGVlcjEfMB0GA1UEAxMWcGVlcjEub3Jn +Mi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL10V0Smz2FO +HR82njo9H0HNyNWt/JUKWMt8Olx425u5y2kqs5RjAMRdyz3U3N0Tve1znDbCjoDL +WtrXW0WdKSSjTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud +IwQkMCKAIG4CB58dq07RqnQt9GKmMABv6hWRK0oDO4FBHFtpDlXUMAoGCCqGSM49 +BAMCA0cAMEQCIFuh8p+nbtjQEZEFg03BN58//9VRsukQXj0xP1eHnrD4AiBwI1jq +L6FMy96mi64g37R0i/I+T4MC5p2mzZIHvRJ8Rg== +-----END CERTIFICATE-----` + +func initCreateContext(t *testing.T, name string) *vm.Context { + privKey, err := asym.GenerateKey(asym.ECDSASecp256r1) + assert.Nil(t, err) + dir := filepath.Join(os.TempDir(), "wasm", name) + + bytes, err := ioutil.ReadFile("./testdata/wasm_test.wasm") + assert.Nil(t, err) + + data := &pb.TransactionData{ + Payload: bytes, + } + + caller, err := privKey.PublicKey().Address() + assert.Nil(t, err) + + store, err := leveldb.New(filepath.Join(dir, "wasm")) + assert.Nil(t, err) + ldg, err := ledger.New(dir, store, log.NewWithModule("executor")) + assert.Nil(t, err) + + return &vm.Context{ + Caller: caller, + TransactionData: data, + Ledger: ldg, + } +} + +func initValidationContext(t *testing.T, name string) *vm.Context { + dir := filepath.Join(os.TempDir(), "validation", name) + + bytes, err := ioutil.ReadFile("./testdata/validation_test.wasm") + require.Nil(t, err) + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + require.Nil(t, err) + + data := &pb.TransactionData{ + Payload: bytes, + } + + caller, err := privKey.PublicKey().Address() + require.Nil(t, err) + + store, err := leveldb.New(filepath.Join(dir, "validation")) + assert.Nil(t, err) + ldg, err := ledger.New(dir, store, log.NewWithModule("executor")) + require.Nil(t, err) + + return &vm.Context{ + Caller: caller, + TransactionData: data, + Ledger: ldg, + } +} + +func initFabricContext(t *testing.T, name string) *vm.Context { + dir := filepath.Join(os.TempDir(), "fabric_policy", name) + + bytes, err := ioutil.ReadFile("./testdata/fabric_policy.wasm") + require.Nil(t, err) + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + require.Nil(t, err) + + data := &pb.TransactionData{ + Payload: bytes, + } + + caller, err := privKey.PublicKey().Address() + require.Nil(t, err) + + store, err := leveldb.New(filepath.Join(dir, "validation_farbic")) + assert.Nil(t, err) + ldg, err := ledger.New(dir, store, log.NewWithModule("executor")) + require.Nil(t, err) + + return &vm.Context{ + Caller: caller, + TransactionData: data, + Ledger: ldg, + } +} + +func TestDeploy(t *testing.T) { + ctx := initCreateContext(t, "create") + wasm, err := New(ctx) + require.Nil(t, err) + + _, err = wasm.deploy() + require.Nil(t, err) +} + +func TestExecute(t *testing.T) { + ctx := initCreateContext(t, "execute") + wasm, err := New(ctx) + require.Nil(t, err) + + ret, err := wasm.deploy() + require.Nil(t, err) + + invokePayload := &pb.InvokePayload{ + Method: "a", + Args: []*pb.Arg{ + {Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 1))}, + {Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 2))}, + }, + } + payload, err := invokePayload.Marshal() + require.Nil(t, err) + data := &pb.TransactionData{ + Payload: payload, + } + ctx1 := &vm.Context{ + Caller: ctx.Caller, + Callee: types.Bytes2Address(ret), + TransactionData: data, + Ledger: ctx.Ledger, + } + wasm1, err := New(ctx1) + require.Nil(t, err) + + result, err := wasm1.Run(payload) + require.Nil(t, err) + require.Equal(t, "336", string(result)) +} + +func TestWasm_RunFabValidation(t *testing.T) { + ctx := initFabricContext(t, "execute") + wasm, err := New(ctx) + require.Nil(t, err) + + ret, err := wasm.deploy() + require.Nil(t, err) + + proof, err := ioutil.ReadFile("./testdata/proof") + require.Nil(t, err) + validator, err := ioutil.ReadFile("./testdata/validator") + require.Nil(t, err) + invokePayload := &pb.InvokePayload{ + Method: "start_verify", + Args: []*pb.Arg{ + {Type: pb.Arg_Bytes, Value: proof}, + {Type: pb.Arg_Bytes, Value: validator}, + }, + } + payload, err := invokePayload.Marshal() + require.Nil(t, err) + data := &pb.TransactionData{ + Payload: payload, + } + ctx1 := &vm.Context{ + Caller: ctx.Caller, + Callee: types.Bytes2Address(ret), + TransactionData: data, + Ledger: ctx.Ledger, + } + wasm1, err := New(ctx1) + require.Nil(t, err) + + result, err := wasm1.Run(payload) + require.Nil(t, err) + require.Equal(t, "1", string(result)) +} + +func BenchmarkRunFabValidation(b *testing.B) { + dir := filepath.Join(os.TempDir(), "bmark", "execute") + + bytes, err := ioutil.ReadFile("./testdata/fabric_policy.wasm") + require.Nil(b, err) + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + require.Nil(b, err) + + data := &pb.TransactionData{ + Payload: bytes, + } + + caller, err := privKey.PublicKey().Address() + require.Nil(b, err) + + store, err := leveldb.New(filepath.Join(dir, "111")) + assert.Nil(b, err) + ldg, err := ledger.New(dir, store, log.NewWithModule("executor")) + require.Nil(b, err) + ctx := &vm.Context{ + Caller: caller, + TransactionData: data, + Ledger: ldg, + } + wasm, err := New(ctx) + require.Nil(b, err) + + ret, err := wasm.deploy() + require.Nil(b, err) + + proof, err := ioutil.ReadFile("./testdata/proof") + require.Nil(b, err) + validator, err := ioutil.ReadFile("./testdata/validator") + require.Nil(b, err) + invokePayload := &pb.InvokePayload{ + Method: "start_verify", + Args: []*pb.Arg{ + {Type: pb.Arg_Bytes, Value: proof}, + {Type: pb.Arg_Bytes, Value: validator}, + }, + } + payload, err := invokePayload.Marshal() + require.Nil(b, err) + ctx1 := &vm.Context{ + Caller: ctx.Caller, + Callee: types.Bytes2Address(ret), + TransactionData: data, + Ledger: ctx.Ledger, + } + for i := 0; i < b.N; i++ { + wasm1, err := New(ctx1) + require.Nil(b, err) + + result, err := wasm1.Run(payload) + require.Nil(b, err) + require.Equal(b, "1", string(result)) + } + ctx.Ledger.Close() + store.Close() +} + +func TestWasm_RunValidation(t *testing.T) { + ctx := initValidationContext(t, "execute") + wasm, err := New(ctx) + require.Nil(t, err) + + ret, err := wasm.deploy() + require.Nil(t, err) + + bytes, err := ioutil.ReadFile("./testdata/fab_test") + require.Nil(t, err) + invokePayload := &pb.InvokePayload{ + Method: "start_verify", + Args: []*pb.Arg{ + {Type: pb.Arg_Bytes, Value: bytes}, + {Type: pb.Arg_Bytes, Value: []byte(cert1)}, + }, + } + payload, err := invokePayload.Marshal() + require.Nil(t, err) + data := &pb.TransactionData{ + Payload: payload, + } + ctx1 := &vm.Context{ + Caller: ctx.Caller, + Callee: types.Bytes2Address(ret), + TransactionData: data, + Ledger: ctx.Ledger, + } + wasm1, err := New(ctx1) + require.Nil(t, err) + + result, err := wasm1.Run(payload) + require.Nil(t, err) + require.Equal(t, "1", string(result)) +} + +func TestWasm_RunWithoutMethod(t *testing.T) { + ctx := initCreateContext(t, "execute_without_method") + wasm, err := New(ctx) + require.Nil(t, err) + + ret, err := wasm.deploy() + require.Nil(t, err) + + pl := &pb.InvokePayload{ + // Method: "", + Args: []*pb.Arg{ + {Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 1))}, + {Type: pb.Arg_I32, Value: []byte(fmt.Sprintf("%d", 2))}, + }, + } + payload, err := pl.Marshal() + require.Nil(t, err) + data := &pb.TransactionData{ + Payload: payload, + } + ctx1 := &vm.Context{ + Caller: ctx.Caller, + Callee: types.Bytes2Address(ret), + TransactionData: data, + Ledger: ctx.Ledger, + } + wasm1, err := New(ctx1) + require.Nil(t, err) + + _, err = wasm1.Run(payload) + assert.Equal(t, errorLackOfMethod, err) +} diff --git a/pkg/vm/wasm/wasmlib/ecdsa.go b/pkg/vm/wasm/wasmlib/ecdsa.go new file mode 100755 index 0000000..5ebbcaf --- /dev/null +++ b/pkg/vm/wasm/wasmlib/ecdsa.go @@ -0,0 +1,168 @@ +package wasmlib + +// #include +// +// extern int32_t ecdsa_verify(void *context, long long sig_ptr, long long digest_ptr, long long pubkey_ptr, int32_t opt); +import "C" +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "math/big" + "unsafe" + + "github.com/meshplus/bitxhub-kit/crypto/asym/secp256k1" + "github.com/wasmerio/go-ext-wasm/wasmer" +) + +type AlgorithmOption string + +const ( + // Secp256k1 secp256k1 algorithm + Secp256k1 AlgorithmOption = "Secp256k1" + // Secp256r1 secp256r1 algorithm + Secp256r1 AlgorithmOption = "Secp256r1" +) + +type PrivateKey struct { + K *ecdsa.PrivateKey +} + +// PublicKey ECDSA public key. +// never new(PublicKey), use NewPublicKey() +type PublicKey struct { + k *ecdsa.PublicKey +} + +type ECDSASignature struct { + R, S *big.Int +} + +//export ecdsa_verify +func ecdsa_verify(context unsafe.Pointer, sig_ptr int64, digest_ptr int64, pubkey_ptr int64, opt int32) int32 { + ctx := wasmer.IntoInstanceContext(context) + data := ctx.Data().(map[int]int) + memory := ctx.Memory() + signature := memory.Data()[sig_ptr : sig_ptr+70] + digest := memory.Data()[digest_ptr : digest_ptr+32] + pubkey := memory.Data()[pubkey_ptr : pubkey_ptr+int64(data[int(pubkey_ptr)])] + pemCert, _ := pem.Decode(pubkey) + var cert *x509.Certificate + cert, err := x509.ParseCertificate(pemCert.Bytes) + if err != nil { + return 0 + } + pk := cert.PublicKey + r, s, err := unmarshalECDSASignature(signature) + if err != nil { + return 0 + } + isValid := ecdsa.Verify(pk.(*ecdsa.PublicKey), digest, r, s) + + if isValid { + return 1 + } else { + return 0 + } +} + +func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) { + sig := new(ECDSASignature) + _, err := asn1.Unmarshal(raw, sig) + if err != nil { + return nil, nil, fmt.Errorf("failed unmashalling signature [%s]", err) + } + + // Validate sig + if sig.R == nil { + return nil, nil, fmt.Errorf("invalid signature, r must be different from nil") + } + if sig.S == nil { + return nil, nil, fmt.Errorf("invalid signature, s must be different from nil") + } + + if sig.R.Sign() != 1 { + return nil, nil, fmt.Errorf("invalid signature, r must be larger than zero") + } + if sig.S.Sign() != 1 { + return nil, nil, fmt.Errorf("invalid signature, s must be larger than zero") + } + + return sig.R, sig.S, nil +} + +func (im *Imports) importECDSA() { + var err error + im.imports, err = im.imports.Append("ecdsa_verify", ecdsa_verify, C.ecdsa_verify) + if err != nil { + return + } +} + +// Bytes returns a serialized, storable representation of this key +func (priv *PrivateKey) Bytes() ([]byte, error) { + if priv.K == nil { + return nil, fmt.Errorf("ECDSAPrivateKey.K is nil, please invoke FromBytes()") + } + r := make([]byte, 32) + a := priv.K.D.Bytes() + copy(r[32-len(a):], a) + return r, nil +} + +func (pub *PublicKey) Bytes() ([]byte, error) { + x := pub.k.X.Bytes() + y := pub.k.Y.Bytes() + return bytes.Join( + [][]byte{{0x04}, + make([]byte, 32-len(x)), x, // padding to 32 bytes + make([]byte, 32-len(y)), y, + }, nil), nil +} + +func UnmarshalPrivateKey(data []byte, opt AlgorithmOption) (crypto.PrivateKey, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty private key data") + } + key := &PrivateKey{K: new(ecdsa.PrivateKey)} + key.K.D = big.NewInt(0) + key.K.D.SetBytes(data) + switch opt { + case Secp256k1: + key.K.Curve = secp256k1.S256() + case Secp256r1: + key.K.Curve = elliptic.P256() + default: + return nil, fmt.Errorf("unsupported algorithm option") + } + + key.K.PublicKey.X, key.K.PublicKey.Y = key.K.Curve.ScalarBaseMult(data) + + return key, nil +} + +func UnmarshalPublicKey(data []byte, opt AlgorithmOption) (crypto.PublicKey, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty public key data") + } + key := &PublicKey{k: new(ecdsa.PublicKey)} + key.k.X = big.NewInt(0) + key.k.Y = big.NewInt(0) + if len(data) != 65 { + return nil, fmt.Errorf("public key data length is not 65") + } + key.k.X.SetBytes(data[1:33]) + key.k.Y.SetBytes(data[33:]) + switch opt { + case Secp256k1: + key.k.Curve = secp256k1.S256() + case Secp256r1: + key.k.Curve = elliptic.P256() + } + return key, nil +} diff --git a/pkg/vm/wasm/wasmlib/import.go b/pkg/vm/wasm/wasmlib/import.go new file mode 100644 index 0000000..9a1f37a --- /dev/null +++ b/pkg/vm/wasm/wasmlib/import.go @@ -0,0 +1,19 @@ +package wasmlib + +import ( + "github.com/wasmerio/go-ext-wasm/wasmer" +) + +type Imports struct { + imports *wasmer.Imports +} + +func New() (*wasmer.Imports, error) { + imports := &Imports{ + imports: wasmer.NewImports(), + } + imports.importECDSA() + imports.importFabricV13() + + return imports.imports, nil +} diff --git a/pkg/vm/wasm/wasmlib/policy.go b/pkg/vm/wasm/wasmlib/policy.go new file mode 100644 index 0000000..a6560b1 --- /dev/null +++ b/pkg/vm/wasm/wasmlib/policy.go @@ -0,0 +1,220 @@ +package wasmlib + +// #include +// +// extern int32_t fabric_validate_v13(void *context, long long proof_ptr, long long validator_ptr); +import "C" +import ( + "encoding/json" + "fmt" + "unsafe" + + "github.com/gogo/protobuf/proto" + mb "github.com/hyperledger/fabric-protos-go/msp" + "github.com/hyperledger/fabric-protos-go/peer" + "github.com/hyperledger/fabric/bccsp/factory" + "github.com/hyperledger/fabric/common/cauthdsl" + "github.com/hyperledger/fabric/msp" + "github.com/hyperledger/fabric/protoutil" + "github.com/wasmerio/go-ext-wasm/wasmer" +) + +//export fabric_validate_v13 +func fabric_validate_v13(context unsafe.Pointer, proof_ptr int64, validator_ptr int64) int32 { + ctx := wasmer.IntoInstanceContext(context) + data := ctx.Data().(map[int]int) + memory := ctx.Memory() + proof := memory.Data()[proof_ptr : proof_ptr+int64(data[int(proof_ptr)])] + validator := memory.Data()[validator_ptr : validator_ptr+int64(data[int(validator_ptr)])] + vInfo, err := UnmarshalValidatorInfo(validator) + if err != nil { + return 0 + } + err = ValidateV14(proof, []byte(vInfo.Policy), vInfo.ConfByte, vInfo.Cid) + if err != nil { + return 0 + } + + return 1 +} + +type valiadationArtifacts struct { + rwset []byte + prp []byte + endorsements []*peer.Endorsement + cap *peer.ChaincodeActionPayload +} + +type validatorInfo struct { + ConfByte []string `json:"conf_byte"` + Policy string `json:"policy"` + Cid string `json:"cid"` +} + +func GetPolicyEnvelope(policy string) ([]byte, error) { + policyEnv, err := cauthdsl.FromString(policy) + if err != nil { + return nil, err + } + policyBytes, err := proto.Marshal(policyEnv) + if err != nil { + return nil, err + } + return policyBytes, nil +} + +func UnmarshalValidatorInfo(validatorBytes []byte) (*validatorInfo, error) { + vInfo := &validatorInfo{} + if err := json.Unmarshal(validatorBytes, vInfo); err != nil { + return nil, err + } + return vInfo, nil +} + +func extractValidationArtifacts(proof []byte) (*valiadationArtifacts, error) { + cap, err := protoutil.UnmarshalChaincodeActionPayload(proof) + if err != nil { + return nil, err + } + + pRespPayload, err := protoutil.UnmarshalProposalResponsePayload(cap.Action.ProposalResponsePayload) + if err != nil { + err = fmt.Errorf("GetProposalResponsePayload error %s", err) + return nil, err + } + if pRespPayload.Extension == nil { + err = fmt.Errorf("nil pRespPayload.Extension") + return nil, err + } + respPayload, err := protoutil.UnmarshalChaincodeAction(pRespPayload.Extension) + if err != nil { + err = fmt.Errorf("GetChaincodeAction error %s", err) + return nil, err + } + + return &valiadationArtifacts{ + rwset: respPayload.Results, + prp: cap.Action.ProposalResponsePayload, + endorsements: cap.Action.Endorsements, + cap: cap, + }, nil +} + +func ValidateV14(proof, policyBytes []byte, confByte []string, cid string) error { + // Get the validation artifacts that help validate the chaincodeID and policy + artifact, err := extractValidationArtifacts(proof) + if err != nil { + return err + } + + err = ValidateChainCodeID(artifact.prp, cid) + if err != nil { + return err + } + signatureSet := GetSignatureSet(artifact) + pe, err := NewPolicyEvaluator(confByte) + if err != nil { + return err + } + + return pe.Evaluate(policyBytes, signatureSet) +} + +func ValidateChainCodeID(prp []byte, name string) error { + payload := &peer.ProposalResponsePayload{} + if err := proto.Unmarshal(prp, payload); err != nil { + return err + } + chaincodeAct := &peer.ChaincodeAction{} + if err := proto.Unmarshal(payload.Extension, chaincodeAct); err != nil { + return err + } + if name != chaincodeAct.ChaincodeId.Name { + return fmt.Errorf("chaincode id does not match") + } + + return nil +} + +type PolicyEvaluator struct { + msp.IdentityDeserializer +} + +func NewPolicyEvaluator(confBytes []string) (*PolicyEvaluator, error) { + mspList := make([]msp.MSP, len(confBytes)) + for i, confByte := range confBytes { + tempBccsp, err := msp.New( + &msp.BCCSPNewOpts{NewBaseOpts: msp.NewBaseOpts{Version: msp.MSPv1_3}}, + factory.GetDefault(), + ) + if err != nil { + return nil, err + } + conf := &mb.MSPConfig{} + if err := proto.UnmarshalText(confByte, conf); err != nil { + return nil, err + } + err = tempBccsp.Setup(conf) + if err != nil { + return nil, err + } + mspList[i] = tempBccsp + } + + manager := msp.NewMSPManager() + err := manager.Setup(mspList) + if err != nil { + return nil, err + } + deserializer := &dynamicDeserializer{mspm: manager} + pe := &PolicyEvaluator{IdentityDeserializer: deserializer} + + return pe, nil +} + +func (id *PolicyEvaluator) Evaluate(policyBytes []byte, signatureSet []*protoutil.SignedData) error { + pp := cauthdsl.NewPolicyProvider(id.IdentityDeserializer) + policy, _, err := pp.NewPolicy(policyBytes) + if err != nil { + return err + } + return policy.EvaluateSignedData(signatureSet) +} + +func GetSignatureSet(artifact *valiadationArtifacts) []*protoutil.SignedData { + signatureSet := []*protoutil.SignedData{} + for _, endorsement := range artifact.endorsements { + data := make([]byte, len(artifact.prp)+len(endorsement.Endorser)) + copy(data, artifact.prp) + copy(data[len(artifact.prp):], endorsement.Endorser) + + signatureSet = append(signatureSet, &protoutil.SignedData{ + // set the data that is signed; concatenation of proposal response bytes and endorser ID + Data: data, + // set the identity that signs the message: it's the endorser + Identity: endorsement.Endorser, + // set the signature + Signature: endorsement.Signature}) + } + return signatureSet +} + +type dynamicDeserializer struct { + mspm msp.MSPManager +} + +func (ds *dynamicDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { + return ds.mspm.DeserializeIdentity(serializedIdentity) +} + +func (ds *dynamicDeserializer) IsWellFormed(identity *mb.SerializedIdentity) error { + return ds.mspm.IsWellFormed(identity) +} + +func (im *Imports) importFabricV13() { + var err error + im.imports, err = im.imports.Append("fabric_validate_v13", fabric_validate_v13, C.fabric_validate_v13) + if err != nil { + return + } +} diff --git a/pkg/vm/wasm/wasmlib/policy_test.go b/pkg/vm/wasm/wasmlib/policy_test.go new file mode 100644 index 0000000..22fd8e7 --- /dev/null +++ b/pkg/vm/wasm/wasmlib/policy_test.go @@ -0,0 +1,62 @@ +package wasmlib + +import ( + "io/ioutil" + "testing" + + "github.com/golang/protobuf/proto" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/hyperledger/fabric/protoutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetPolicyEnvelope(t *testing.T) { + _, err := GetPolicyEnvelope("OR(AND('A.member', 'B.member'), OR('C.admin', 'D.member'))") + assert.NoError(t, err) +} + +func TestPayloadUnmarshal(t *testing.T) { + bytes, err := ioutil.ReadFile("../testdata/proof") + require.Nil(t, err) + cap, err := protoutil.UnmarshalChaincodeActionPayload(bytes) + + require.Nil(t, err) + + prp := cap.Action.ProposalResponsePayload + signatureSet := []*protoutil.SignedData{} + for _, endorsement := range cap.Action.Endorsements { + data := make([]byte, len(prp)+len(endorsement.Endorser)) + copy(data, prp) + copy(data[len(prp):], endorsement.Endorser) + + signatureSet = append(signatureSet, &protoutil.SignedData{ + // set the data that is signed; concatenation of proposal response bytes and endorser ID + Data: data, + // set the identity that signs the message: it's the endorser + Identity: endorsement.Endorser, + // set the signature + Signature: endorsement.Signature}) + } + sId := &m.SerializedIdentity{} + err = proto.Unmarshal(signatureSet[0].Identity, sId) + require.Nil(t, err) +} + +func TestUnmarshalValidatorInfo(t *testing.T) { + vBytes, err := ioutil.ReadFile("../testdata/validator") + require.Nil(t, err) + _, err = UnmarshalValidatorInfo(vBytes) + require.Nil(t, err) +} + +func TestValidateV14(t *testing.T) { + vBytes, err := ioutil.ReadFile("../testdata/validator") + require.Nil(t, err) + proof, err := ioutil.ReadFile("../testdata/proof") + require.Nil(t, err) + v, err := UnmarshalValidatorInfo(vBytes) + require.Nil(t, err) + err = ValidateV14(proof, []byte(v.Policy), v.ConfByte, v.Cid) + require.Nil(t, err) +} diff --git a/scripts/boot_bitxhub.sh b/scripts/boot_bitxhub.sh new file mode 100644 index 0000000..bba6d3b --- /dev/null +++ b/scripts/boot_bitxhub.sh @@ -0,0 +1,37 @@ +CURRENT_PATH=$(pwd) +BUILD_PATH=${CURRENT_PATH}/build +N=$1 + +function splitWindow() { + tmux splitw -v -p 50 + tmux splitw -h -p 50 + tmux selectp -t 0 + tmux splitw -h -p 50 +} + +function start() { + cd ${CURRENT_PATH} + rm -rf build + tar -xf build.tar.gz + pkill -9 bitxhub + tmux kill-session -t bitxhub + tmux new -d -s bitxhub + + cd build + for ((i=0;i or ) + cd "${PROJECT_PATH}"/internal/plugins + make raft + + for ((i = 1; i < N + 1; i = i + 1)); do + mkdir -p "${BUILD_PATH}"/node${i} + cp -rf "${PROJECT_PATH}"/internal/plugins/build "${BUILD_PATH}"/node${i}/plugins + done +} + +build_config +build_plugins diff --git a/scripts/cert.sh b/scripts/cert.sh new file mode 100644 index 0000000..f901134 --- /dev/null +++ b/scripts/cert.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build +CERT_PATH=${CURRENT_PATH}/cert +N=$1 + +mkdir -p "${CERT_PATH}" +cd "${CERT_PATH}" + +## Generate ca private key and cert +premo cert ca + +for ((i = 1; i < N + 1; i = i + 1)); do + mkdir -p "${CERT_PATH}"/node${i} + cd "${CERT_PATH}"/node${i} + premo cert issue --name agency --priv ../ca.priv --cert ../ca.cert --org=Hyperchain + premo cert issue --name node --priv ./agency.priv --cert ./agency.cert --org=Agency${i} +done diff --git a/scripts/certs/agency.cert b/scripts/certs/agency.cert new file mode 100644 index 0000000..46ec460 --- /dev/null +++ b/scripts/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjegAwIBAgIDCFlzMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGaMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1 +YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDtMPr3mXU6kqFlAC9QDD+IofJJW +phhBiNVqgKjVwuAnYdhONPtxHKK6wWBV5pwT2rLKQUOZjdIULNd+yjTGVy6jYTBf +MA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMB +Af8wKwYDVR0jBCQwIoAgkq80RhqcgxYaYUzvK00siYzAYcEN0BdgUmnqZ5Fa9rIw +CgYIKoZIzj0EAwIDSAAwRQIhAIBVe6GWbBNAchWrU6jTXWR7BFCcI5uN2hGsmb2b +H7rxAiB6j/dmP16cBHj6FpeqeF48GMqxEY8cGOlGD1m556Db4A== +-----END CERTIFICATE----- diff --git a/scripts/certs/agency.priv b/scripts/certs/agency.priv new file mode 100644 index 0000000..ff5c1a0 --- /dev/null +++ b/scripts/certs/agency.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDNMmrlEZb7f34zCNZ1lXeDWtDQfJGGaQCdEpQja785CoAoGCCqGSM49 +AwEHoUQDQgAEO0w+veZdTqSoWUAL1AMP4ih8klamGEGI1WqAqNXC4Cdh2E40+3Ec +orrBYFXmnBPasspBQ5mN0hQs137KNMZXLg== +-----END EC PRIVATE KEY----- diff --git a/scripts/certs/ca.cert b/scripts/certs/ca.cert new file mode 100644 index 0000000..b63fbe9 --- /dev/null +++ b/scripts/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDAZBcMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSk9sbU3nPwrFBokQC +Hnw6KfX4TJleYLxTCgQ+JNprzXgSuqRNZcuRAUW9ZjXuV8UFdEvk+R9dwiCCIJtr +wQRBo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCCSrzRGGpyDFhphTO8rTSyJjMBhwQ3QF2BSaepn +kVr2sjAKBggqhkjOPQQDAgNJADBGAiEAyW+stMmrW/ADCcEoSCW+WFp8+R+b3xC7 +TXkeulinQ0kCIQDbk0a04PxAIPP8TPHPeekqpVmNzcnAhjh73vzVxycD3Q== +-----END CERTIFICATE----- diff --git a/scripts/certs/ca.priv b/scripts/certs/ca.priv new file mode 100644 index 0000000..68b1980 --- /dev/null +++ b/scripts/certs/ca.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIN/zAUW7izN9tvYYYHL/NLPwYvACXE9Lz5YLSgASBMLtoAoGCCqGSM49 +AwEHoUQDQgAEUpPbG1N5z8KxQaJEAh58Oin1+EyZXmC8UwoEPiTaa814ErqkTWXL +kQFFvWY17lfFBXRL5PkfXcIggiCba8EEQQ== +-----END EC PRIVATE KEY----- diff --git a/scripts/certs/node1/certs/agency.cert b/scripts/certs/node1/certs/agency.cert new file mode 100644 index 0000000..46ec460 --- /dev/null +++ b/scripts/certs/node1/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjegAwIBAgIDCFlzMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGaMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1 +YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDtMPr3mXU6kqFlAC9QDD+IofJJW +phhBiNVqgKjVwuAnYdhONPtxHKK6wWBV5pwT2rLKQUOZjdIULNd+yjTGVy6jYTBf +MA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMB +Af8wKwYDVR0jBCQwIoAgkq80RhqcgxYaYUzvK00siYzAYcEN0BdgUmnqZ5Fa9rIw +CgYIKoZIzj0EAwIDSAAwRQIhAIBVe6GWbBNAchWrU6jTXWR7BFCcI5uN2hGsmb2b +H7rxAiB6j/dmP16cBHj6FpeqeF48GMqxEY8cGOlGD1m556Db4A== +-----END CERTIFICATE----- diff --git a/scripts/certs/node1/certs/ca.cert b/scripts/certs/node1/certs/ca.cert new file mode 100644 index 0000000..b63fbe9 --- /dev/null +++ b/scripts/certs/node1/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDAZBcMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSk9sbU3nPwrFBokQC +Hnw6KfX4TJleYLxTCgQ+JNprzXgSuqRNZcuRAUW9ZjXuV8UFdEvk+R9dwiCCIJtr +wQRBo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCCSrzRGGpyDFhphTO8rTSyJjMBhwQ3QF2BSaepn +kVr2sjAKBggqhkjOPQQDAgNJADBGAiEAyW+stMmrW/ADCcEoSCW+WFp8+R+b3xC7 +TXkeulinQ0kCIQDbk0a04PxAIPP8TPHPeekqpVmNzcnAhjh73vzVxycD3Q== +-----END CERTIFICATE----- diff --git a/scripts/certs/node1/certs/node.cert b/scripts/certs/node1/certs/node.cert new file mode 100644 index 0000000..72cc0e7 --- /dev/null +++ b/scripts/certs/node1/certs/node.cert @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWjCCAf+gAwIBAgIDC4rPMAoGCCqGSM49BAMCMIGaMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzANBgNVBAoT +BkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1YjAgFw0y +MDAzMDQwODQ0MjRaGA8yMDcwMDIyMDA4NDQyNFowgZkxCzAJBgNVBAYTAkNOMREw +DwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMISGFuZ1pob3UxHzANBgNVBAkTBnN0 +cmVldDAOBgNVBAkTB2FkZHJlc3MxDzANBgNVBBETBjMyNDAwMDEOMAwGA1UEChMF +Tm9kZTExEDAOBgNVBAsTB0JpdFhIdWIxEDAOBgNVBAMTB0JpdFhIdWIwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQi8bygCNVfLigWljvEOCr8gRlpTpfIEGoP+HL8 +4BwKcSX1n0hZC3oTfv7fMMGxE7lVjZ26C5q54vJAliceljbPozEwLzAOBgNVHQ8B +Af8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAMBgNVHRMBAf8EAjAAMAoGCCqGSM49 +BAMCA0kAMEYCIQDrq69L8mxZReND60P6tXDW5AnR4sSS/yPTsmh31D6+EgIhAKh/ +WpVWOgI8b/ltCWpufb3TUM3lEQrsXP4W28Uk7q1j +-----END CERTIFICATE----- diff --git a/scripts/certs/node1/certs/node.priv b/scripts/certs/node1/certs/node.priv new file mode 100644 index 0000000..d208575 --- /dev/null +++ b/scripts/certs/node1/certs/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFYN9+xvnRzlNX6Cob3trD3HlcvuYZUYRPrhYU+Yh2ADoAoGCCqGSM49 +AwEHoUQDQgAEIvG8oAjVXy4oFpY7xDgq/IEZaU6XyBBqD/hy/OAcCnEl9Z9IWQt6 +E37+3zDBsRO5VY2duguaueLyQJYnHpY2zw== +-----END EC PRIVATE KEY----- diff --git a/scripts/certs/node2/certs/agency.cert b/scripts/certs/node2/certs/agency.cert new file mode 100644 index 0000000..46ec460 --- /dev/null +++ b/scripts/certs/node2/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjegAwIBAgIDCFlzMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGaMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1 +YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDtMPr3mXU6kqFlAC9QDD+IofJJW +phhBiNVqgKjVwuAnYdhONPtxHKK6wWBV5pwT2rLKQUOZjdIULNd+yjTGVy6jYTBf +MA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMB +Af8wKwYDVR0jBCQwIoAgkq80RhqcgxYaYUzvK00siYzAYcEN0BdgUmnqZ5Fa9rIw +CgYIKoZIzj0EAwIDSAAwRQIhAIBVe6GWbBNAchWrU6jTXWR7BFCcI5uN2hGsmb2b +H7rxAiB6j/dmP16cBHj6FpeqeF48GMqxEY8cGOlGD1m556Db4A== +-----END CERTIFICATE----- diff --git a/scripts/certs/node2/certs/ca.cert b/scripts/certs/node2/certs/ca.cert new file mode 100644 index 0000000..b63fbe9 --- /dev/null +++ b/scripts/certs/node2/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDAZBcMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSk9sbU3nPwrFBokQC +Hnw6KfX4TJleYLxTCgQ+JNprzXgSuqRNZcuRAUW9ZjXuV8UFdEvk+R9dwiCCIJtr +wQRBo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCCSrzRGGpyDFhphTO8rTSyJjMBhwQ3QF2BSaepn +kVr2sjAKBggqhkjOPQQDAgNJADBGAiEAyW+stMmrW/ADCcEoSCW+WFp8+R+b3xC7 +TXkeulinQ0kCIQDbk0a04PxAIPP8TPHPeekqpVmNzcnAhjh73vzVxycD3Q== +-----END CERTIFICATE----- diff --git a/scripts/certs/node2/certs/node.cert b/scripts/certs/node2/certs/node.cert new file mode 100644 index 0000000..f2400ae --- /dev/null +++ b/scripts/certs/node2/certs/node.cert @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWTCCAf+gAwIBAgIDB5GsMAoGCCqGSM49BAMCMIGaMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzANBgNVBAoT +BkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1YjAgFw0y +MDAzMDQwODQ0MjRaGA8yMDcwMDIyMDA4NDQyNFowgZkxCzAJBgNVBAYTAkNOMREw +DwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMISGFuZ1pob3UxHzANBgNVBAkTBnN0 +cmVldDAOBgNVBAkTB2FkZHJlc3MxDzANBgNVBBETBjMyNDAwMDEOMAwGA1UEChMF +Tm9kZTIxEDAOBgNVBAsTB0JpdFhIdWIxEDAOBgNVBAMTB0JpdFhIdWIwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARNhmawbnCyMOZmKAsFOPIOGSJo0/l9RGCFUU+a +Cd6/apiwrwWD4RGKWGHwjFAXSRHpYUzuwXwyQrB4FtJZ/1xoozEwLzAOBgNVHQ8B +Af8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAMBgNVHRMBAf8EAjAAMAoGCCqGSM49 +BAMCA0gAMEUCIQDSF6I7+I23UmoNxERZ7PHvW4dsIFTHaCLUNl2pG9uiHAIgB898 +yCHKASfneRgUxz5KZ1kvtcrnqC73muAxWz0TMEE= +-----END CERTIFICATE----- diff --git a/scripts/certs/node2/certs/node.priv b/scripts/certs/node2/certs/node.priv new file mode 100644 index 0000000..4e5abff --- /dev/null +++ b/scripts/certs/node2/certs/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEP1ckhwUxWqpJqnKitIC/u8eWAsR/4I+aLJDesKB9XOoAoGCCqGSM49 +AwEHoUQDQgAETYZmsG5wsjDmZigLBTjyDhkiaNP5fURghVFPmgnev2qYsK8Fg+ER +ilhh8IxQF0kR6WFM7sF8MkKweBbSWf9caA== +-----END EC PRIVATE KEY----- diff --git a/scripts/certs/node3/certs/agency.cert b/scripts/certs/node3/certs/agency.cert new file mode 100644 index 0000000..46ec460 --- /dev/null +++ b/scripts/certs/node3/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjegAwIBAgIDCFlzMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGaMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1 +YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDtMPr3mXU6kqFlAC9QDD+IofJJW +phhBiNVqgKjVwuAnYdhONPtxHKK6wWBV5pwT2rLKQUOZjdIULNd+yjTGVy6jYTBf +MA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMB +Af8wKwYDVR0jBCQwIoAgkq80RhqcgxYaYUzvK00siYzAYcEN0BdgUmnqZ5Fa9rIw +CgYIKoZIzj0EAwIDSAAwRQIhAIBVe6GWbBNAchWrU6jTXWR7BFCcI5uN2hGsmb2b +H7rxAiB6j/dmP16cBHj6FpeqeF48GMqxEY8cGOlGD1m556Db4A== +-----END CERTIFICATE----- diff --git a/scripts/certs/node3/certs/ca.cert b/scripts/certs/node3/certs/ca.cert new file mode 100644 index 0000000..b63fbe9 --- /dev/null +++ b/scripts/certs/node3/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDAZBcMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSk9sbU3nPwrFBokQC +Hnw6KfX4TJleYLxTCgQ+JNprzXgSuqRNZcuRAUW9ZjXuV8UFdEvk+R9dwiCCIJtr +wQRBo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCCSrzRGGpyDFhphTO8rTSyJjMBhwQ3QF2BSaepn +kVr2sjAKBggqhkjOPQQDAgNJADBGAiEAyW+stMmrW/ADCcEoSCW+WFp8+R+b3xC7 +TXkeulinQ0kCIQDbk0a04PxAIPP8TPHPeekqpVmNzcnAhjh73vzVxycD3Q== +-----END CERTIFICATE----- diff --git a/scripts/certs/node3/certs/node.cert b/scripts/certs/node3/certs/node.cert new file mode 100644 index 0000000..25414c5 --- /dev/null +++ b/scripts/certs/node3/certs/node.cert @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWDCCAf+gAwIBAgIDAv8gMAoGCCqGSM49BAMCMIGaMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzANBgNVBAoT +BkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1YjAgFw0y +MDAzMDQwODQ0MjRaGA8yMDcwMDIyMDA4NDQyNFowgZkxCzAJBgNVBAYTAkNOMREw +DwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMISGFuZ1pob3UxHzANBgNVBAkTBnN0 +cmVldDAOBgNVBAkTB2FkZHJlc3MxDzANBgNVBBETBjMyNDAwMDEOMAwGA1UEChMF +Tm9kZTMxEDAOBgNVBAsTB0JpdFhIdWIxEDAOBgNVBAMTB0JpdFhIdWIwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARuVl3rBHNHE6214smPAzYe3zVwzM6FDuIwNePf +5n+hcafOvy1VtaaWaQzYFgYqHN3+aGSG/LeYdF0omY7NxOu3ozEwLzAOBgNVHQ8B +Af8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAMBgNVHRMBAf8EAjAAMAoGCCqGSM49 +BAMCA0cAMEQCIDLIYh5ivZaag+wpW+dIuH0fn/qbF6LOzWqAMVp3w5iyAiA7goaw +Kzc9FkMNtBbWATkEHrTK83MgW4gj0K0OFmtM2w== +-----END CERTIFICATE----- diff --git a/scripts/certs/node3/certs/node.priv b/scripts/certs/node3/certs/node.priv new file mode 100644 index 0000000..8f976d1 --- /dev/null +++ b/scripts/certs/node3/certs/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIPp2yQwm9q6goxwC+FtMjEMTqlmqPszl36E9HaFw/kIyoAoGCCqGSM49 +AwEHoUQDQgAEblZd6wRzRxOtteLJjwM2Ht81cMzOhQ7iMDXj3+Z/oXGnzr8tVbWm +lmkM2BYGKhzd/mhkhvy3mHRdKJmOzcTrtw== +-----END EC PRIVATE KEY----- diff --git a/scripts/certs/node4/certs/agency.cert b/scripts/certs/node4/certs/agency.cert new file mode 100644 index 0000000..46ec460 --- /dev/null +++ b/scripts/certs/node4/certs/agency.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkTCCAjegAwIBAgIDCFlzMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGaMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzAN +BgNVBAoTBkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1 +YjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDtMPr3mXU6kqFlAC9QDD+IofJJW +phhBiNVqgKjVwuAnYdhONPtxHKK6wWBV5pwT2rLKQUOZjdIULNd+yjTGVy6jYTBf +MA4GA1UdDwEB/wQEAwIBpjAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMB +Af8wKwYDVR0jBCQwIoAgkq80RhqcgxYaYUzvK00siYzAYcEN0BdgUmnqZ5Fa9rIw +CgYIKoZIzj0EAwIDSAAwRQIhAIBVe6GWbBNAchWrU6jTXWR7BFCcI5uN2hGsmb2b +H7rxAiB6j/dmP16cBHj6FpeqeF48GMqxEY8cGOlGD1m556Db4A== +-----END CERTIFICATE----- diff --git a/scripts/certs/node4/certs/ca.cert b/scripts/certs/node4/certs/ca.cert new file mode 100644 index 0000000..b63fbe9 --- /dev/null +++ b/scripts/certs/node4/certs/ca.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIDAZBcMAoGCCqGSM49BAMCMIGhMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzARBgNVBAoT +Ckh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJpdHhodWIu +Y24wIBcNMjAwMzA0MDg0NDIyWhgPMjA3MDAyMjAwODQ0MjJaMIGhMQswCQYDVQQG +EwJDTjERMA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYD +VQQJEwZzdHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxEzAR +BgNVBAoTCkh5cGVyY2hhaW4xEDAOBgNVBAsTB0JpdFhIdWIxEzARBgNVBAMTCmJp +dHhodWIuY24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSk9sbU3nPwrFBokQC +Hnw6KfX4TJleYLxTCgQ+JNprzXgSuqRNZcuRAUW9ZjXuV8UFdEvk+R9dwiCCIJtr +wQRBo18wXTAOBgNVHQ8BAf8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMB +Af8EBTADAQH/MCkGA1UdDgQiBCCSrzRGGpyDFhphTO8rTSyJjMBhwQ3QF2BSaepn +kVr2sjAKBggqhkjOPQQDAgNJADBGAiEAyW+stMmrW/ADCcEoSCW+WFp8+R+b3xC7 +TXkeulinQ0kCIQDbk0a04PxAIPP8TPHPeekqpVmNzcnAhjh73vzVxycD3Q== +-----END CERTIFICATE----- diff --git a/scripts/certs/node4/certs/node.cert b/scripts/certs/node4/certs/node.cert new file mode 100644 index 0000000..94705fd --- /dev/null +++ b/scripts/certs/node4/certs/node.cert @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWDCCAf+gAwIBAgIDCkQ5MAoGCCqGSM49BAMCMIGaMQswCQYDVQQGEwJDTjER +MA8GA1UECBMIWmhlSmlhbmcxETAPBgNVBAcTCEhhbmdaaG91MR8wDQYDVQQJEwZz +dHJlZXQwDgYDVQQJEwdhZGRyZXNzMQ8wDQYDVQQREwYzMjQwMDAxDzANBgNVBAoT +BkFnZW5jeTEQMA4GA1UECxMHQml0WEh1YjEQMA4GA1UEAxMHQml0WEh1YjAgFw0y +MDAzMDQwODQ0MjRaGA8yMDcwMDIyMDA4NDQyNFowgZkxCzAJBgNVBAYTAkNOMREw +DwYDVQQIEwhaaGVKaWFuZzERMA8GA1UEBxMISGFuZ1pob3UxHzANBgNVBAkTBnN0 +cmVldDAOBgNVBAkTB2FkZHJlc3MxDzANBgNVBBETBjMyNDAwMDEOMAwGA1UEChMF +Tm9kZTQxEDAOBgNVBAsTB0JpdFhIdWIxEDAOBgNVBAMTB0JpdFhIdWIwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARuDsfGfnzbqIZLSENOGJ72BfZW1NeKN8TsD/RD +j4Trb3kbyBmY7qntTvNR7HuDmFkCVcRFR16IBPV+E7VtulxZozEwLzAOBgNVHQ8B +Af8EBAMCAaYwDwYDVR0lBAgwBgYEVR0lADAMBgNVHRMBAf8EAjAAMAoGCCqGSM49 +BAMCA0cAMEQCIG96uffPzrEeYm688/GhV9d1gqaUTmQ1b4YYJo9GiNtiAiAVwBMc +oz12grMgErh3sHb1DsbwWcz1aL1PDynk40HrvQ== +-----END CERTIFICATE----- diff --git a/scripts/certs/node4/certs/node.priv b/scripts/certs/node4/certs/node.priv new file mode 100644 index 0000000..5dc2cf2 --- /dev/null +++ b/scripts/certs/node4/certs/node.priv @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDDlbCWaYt24ANXbRep+t7Keq0Zu5GckeDECSfEASTq2oAoGCCqGSM49 +AwEHoUQDQgAEbg7Hxn5826iGS0hDThie9gX2VtTXijfE7A/0Q4+E6295G8gZmO6p +7U7zUex7g5hZAlXERUdeiAT1fhO1bbpcWQ== +-----END EC PRIVATE KEY----- diff --git a/scripts/cluster.sh b/scripts/cluster.sh new file mode 100755 index 0000000..bbd5b66 --- /dev/null +++ b/scripts/cluster.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -e + +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build +N=$1 + +function prepare() { + bash build.sh "${N}" +} + +function compile() { + cd "${PROJECT_PATH}" + make install +} + +function splitWindow() { + tmux splitw -v -p 50 + tmux splitw -h -p 50 + tmux selectp -t 0 + tmux splitw -h -p 50 +} + +function start() { + #osascript ${PROJECT_PATH}/scripts/split.scpt ${N} ${DUMP_PATH}/cluster/node + tmux new -d -s bitxhub || (tmux kill-session -t bitxhub && tmux new -d -s bitxhub) + + for ((i = 0; i < N / 4; i = i + 1)); do + splitWindow + tmux new-window + done + splitWindow + for ((i = 0; i < N; i = i + 1)); do + tmux selectw -t $(($i / 4)) + tmux selectp -t $(($i % 4)) + tmux send-keys "ulimit -n 20000" C-m + tmux send-keys "bitxhub --repo=${BUILD_PATH}/node$(($i + 1)) start" C-m + done + tmux selectw -t 0 + tmux attach-session -t bitxhub +} + +function clear_config() { + for ((i = 1; i < N + 1; i = i + 1)); do + rm -rf ~/bitxhub${i} + done +} + +prepare +compile +start diff --git a/scripts/config.sh b/scripts/config.sh new file mode 100644 index 0000000..9dc4816 --- /dev/null +++ b/scripts/config.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +set -e + +source x.sh + +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build +CONFIG_PATH=${PROJECT_PATH}/config +N=$1 + +function prepare() { + rm -rf "${BUILD_PATH}" + mkdir "${BUILD_PATH}" + mkdir "${BUILD_PATH}"/certs +} + +function generate_certs() { + for ((i = 1; i < N + 1; i = i + 1)); do + if [[ $i -le 4 ]]; then + cp -rf "${CURRENT_PATH}"/certs/node${i} "${BUILD_PATH}"/certs + else + certs_path=${BUILD_PATH}/certs/node${i}/certs + mkdir -p "${certs_path}" + cp "${CURRENT_PATH}"/certs/ca.cert "${CURRENT_PATH}"/certs/agency.cert "${certs_path}" + premo cert priv --name node --target "${certs_path}" + premo cert csr --key "${certs_path}"/node.priv --org Node${i} --target "${certs_path}" + premo cert issue --csr "${certs_path}"/node.csr \ + --key "${CURRENT_PATH}"/certs/ca.priv \ + --cert "${CURRENT_PATH}"/certs/ca.cert \ + --target "${certs_path}" + + premo key convert --path "${certs_path}"/node.priv --target "${BUILD_PATH}"/certs/node${i} + fi + done +} + +# Generate config +function generate() { + for ((i = 1; i < N + 1; i = i + 1)); do + root=${BUILD_PATH}/node${i} + mkdir -p "${root}" + cp -rf "${BUILD_PATH}"/certs/node${i}/* "${root}" + cp -rf "${CONFIG_PATH}"/* "${root}" + + echo "#!/usr/bin/env bash" >"${root}"/start.sh + echo "./bitxhub --root \$(pwd)" start >>"${root}"/start.sh + + bitxhubConfig=${root}/bitxhub.toml + networkConfig=${root}/network.toml + x_replace "s/60011/6001${i}/g" "${bitxhubConfig}" + x_replace "s/60011/6001${i}/g" "${bitxhubConfig}" + x_replace "s/9091/909${i}/g" "${bitxhubConfig}" + x_replace "s/53121/5312${i}/g" "${bitxhubConfig}" + x_replace "s/9091/909${i}/g" "${root}"/api + x_replace "1s/1/${i}/" "${networkConfig}" + done +} + +print_green "Generating $1 nodes configuration..." +prepare +generate_certs +generate diff --git a/scripts/cross_compile.sh b/scripts/cross_compile.sh new file mode 100755 index 0000000..55ddc71 --- /dev/null +++ b/scripts/cross_compile.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +PROJECT_PATH=$(dirname "$(pwd)") +BIN_PATH=${PROJECT_PATH}/bin +set -e + +source x.sh + +# $1 is arch, $2 is source code path +case $1 in +linux-amd64) + print_blue "Compile for linux/amd64" + if [ -z "$(docker image inspect golang:1.13)" ]; then + docker pull golang:1.13 + else + print_blue "golang:1.13 image already exist" + fi + + if [ "$(docker container ls -a | grep -c bitxhub_linux)" -ge 1 ];then + print_blue "golang:1.13 container already exist" + rm -f "${BIN_PATH}"/bitxhub_linux-amd64 + docker restart bitxhub_linux + docker logs bitxhub_linux -f --tail "0" + else + docker run --name bitxhub_linux -t \ + -v $2:/code/bitxhub \ + -v ~/.ssh:/root/.ssh \ + -v ~/.gitconfig:/root/.gitconfig \ + -v $GOPATH/pkg/mod:$GOPATH/pkg/mod \ + golang:1.13 \ + /bin/bash -c "go env -w GO111MODULE=on && + go env -w GOPROXY=https://goproxy.cn,direct && + go get -u github.com/gobuffalo/packr/packr && + cd /code/bitxhub && + make install && + cd internal/plugins && + make raft && + mkdir -p /code/bitxhub/bin && + cp /go/bin/bitxhub /code/bitxhub/bin/bitxhub_linux-amd64" + fi + ;; +*) + print_red "Other architectures are not supported yet" + ;; +esac diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..493b1d7 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,87 @@ +set -e + +source x.sh + +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build + +USERNAME=hyperchain +SERVER_BUILD_PATH="/home/hyperchain/bitxhub" +N=$1 +IP=$2 +RECOMPILE=$3 + +if [ "$#" -ne 3 ]; then + echo "Illegal number of parameters" + exit +fi + +while getopts "h?n:r:" opt; do + case "$opt" in + h | \?) + help + exit 0 + ;; + n) + N=$OPTARG + ;; + r) + RECOMPILE=$OPTARG + ;; + esac +done + +function build() { + bash config.sh "$N" +} + +function compile() { + if [[ $RECOMPILE == true ]]; then + cd "${PROJECT_PATH}" + make build-linux + else + echo "Do not need compile" + fi +} + +function prepare() { + cd "${CURRENT_PATH}" + cp ../bin/bitxhub_linux-amd64 "${BUILD_PATH}"/bitxhub + cp ../internal/plugins/build/*.so "${BUILD_PATH}"/ + tar cf build.tar.gz build +} + +function deploy() { + cd "${CURRENT_PATH}" + #ssh-copy-id -i ~/.ssh/id_rsa.pub hyperchain@${IP} + scp build.tar.gz ${USERNAME}@"${IP}":${SERVER_BUILD_PATH} + scp boot_bitxhub.sh ${USERNAME}@"${IP}":${SERVER_BUILD_PATH} + + ssh -t ${USERNAME}@"${IP}" ' + cd '${SERVER_BUILD_PATH}' + bash boot_bitxhub.sh 4 + tmux attach-session -t bitxhub +' +} + +# help prompt message +function help() { + echo "deploy.sh helper:" + echo " -h, --help: show the help for this bash script" + echo " -n, --number: bitxhub node to be started" + echo " -r, --recompile: need compile or not" + echo "---------------------------------------------------" + echo "Example for deploy 4 node in server:" + echo "./deploy.sh -n 4 -r 1" +} + +print_blue "1. Generate config" +build + +print_blue "2. Compile bitxhub" +compile +prepare + +print_blue "3. Deploy bitxhub" +deploy diff --git a/scripts/integration.sh b/scripts/integration.sh new file mode 100644 index 0000000..f5eb4e9 --- /dev/null +++ b/scripts/integration.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +# integration test script +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build +N=$1 + +sh build.sh "$N" + +cd "${PROJECT_PATH}" +make install + +bitxhub version +for ((i = 1; i < N + 1; i = i + 1)); do + echo "Start node${i}" + nohup bitxhub --repo="${BUILD_PATH}"/node${i} start & +done diff --git a/scripts/prepare.sh b/scripts/prepare.sh new file mode 100644 index 0000000..0943318 --- /dev/null +++ b/scripts/prepare.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +BLUE='\033[0;34m' +NC='\033[0m' + +function print_blue() { + printf "${BLUE}%s${NC}\n" "$1" +} + +print_blue "1. Install packr" +if ! type packr >/dev/null 2>&1; then + go get -u github.com/gobuffalo/packr/packr +fi + +print_blue "2. Install golangci-lint" +if ! type golanci-lint >/dev/null 2>&1; then + go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.18.0 +fi + +print_blue "3. Install go mock tool" +if ! type gomock >/dev/null 2>&1; then + go get github.com/golang/mock/gomock +fi +if ! type mockgen >/dev/null 2>&1; then + go get github.com/golang/mock/mockgen +fi + +print_blue "4. Check tmux" +if ! type tmux >/dev/null 2>&1; then + print_red "Please Install tmux" +else + echo "tmux is already installed" +fi diff --git a/scripts/quick_start/.gitignore b/scripts/quick_start/.gitignore new file mode 100644 index 0000000..9d84eab --- /dev/null +++ b/scripts/quick_start/.gitignore @@ -0,0 +1,2 @@ + +fabric-samples diff --git a/scripts/quick_start/README.md b/scripts/quick_start/README.md new file mode 100644 index 0000000..fa4f3e6 --- /dev/null +++ b/scripts/quick_start/README.md @@ -0,0 +1,49 @@ +# Quick start + +## Fabric first network + +Download script: + +```bash +wget https://github.com/meshplus/bitxhub/raw/master/scripts/quick_start/ffn.sh +``` + +```bash +./ffn.sh up // build up fabric first network +./ffn.sh down // clear up fabric first network +./ffn.sh restart // restart fabric first network +``` + +`./ffn.sh up` will generate `crypto-config`. + +## Chaincode + +Download script: + +```bash +wget https://github.com/meshplus/bitxhub/raw/master/scripts/quick_start/chaincode.sh +``` + +```bash +./chaincode.sh install // Install broker, transfer and data_swapper chaincode +./chaincode.sh upgrade +./chaincode.sh init // Init broker chaincode +./chaincode.sh get_balance // Query Alice balance +./chaincode.sh get_data // Query the value of 'path' +./chaincode.sh interchain_transfer +./chaincode.sh interchain_gt +``` + +## Fabric pier +Download script: + +```bash +wget https://github.com/meshplus/bitxhub/raw/master/scripts/quick_start/fabric_pir.sh +``` + +```bash +./fabric_pier.sh start +./fabric_pier.sh restart +./fabric_pier.sh id +``` + diff --git a/scripts/quick_start/chaincode.sh b/scripts/quick_start/chaincode.sh new file mode 100755 index 0000000..d468dc8 --- /dev/null +++ b/scripts/quick_start/chaincode.sh @@ -0,0 +1,332 @@ +#!/usr/bin/env bash + +set -e + +CURRENT_PATH=$(pwd) +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +# The sed commend with system judging +# Examples: +# sed -i 's/a/b/g' bob.txt => x_replace 's/a/b/g' bob.txt +function x_replace() { + system=$(uname) + + if [ "${system}" = "Linux" ]; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +function print_blue() { + printf "${BLUE}%s${NC}\n" "$1" +} + +function print_red() { + printf "${RED}%s${NC}\n" "$1" +} + +function printHelp() { + print_blue "Usage: " + echo " chaincode.sh " + echo " - one of 'up', 'down', 'restart'" + echo " - 'install ' - install broker, transfer and data_swapper chaincode" + echo " - 'upgrade ' - upgrade broker, transfer and data_swapper chaincode" + echo " - 'init ' - init broker" + echo " - 'get_balance ' - get Alice balance from transfer chaincode" + echo " - 'get_data ' - get path value from data_swapper chaincode" + echo " - 'interchain_transfer ' - interchain transfer" + echo " - 'interchain_get ' - interchain get data" + echo " chaincode.sh -h (print this message)" +} + +function prepare() { + if ! type fabric-cli >/dev/null 2>&1; then + print_blue "===> Install fabric-cli" + go get github.com/securekey/fabric-examples/fabric-cli/cmd/fabric-cli + fi + + if [ ! -d contracts ]; then + print_blue "===> Download chaincode" + wget https://github.com/meshplus/bitxhub/raw/master/scripts/quick_start/contracts.zip + unzip -q contracts.zip + rm contracts.zip + fi + + if [ ! -f config-template.yaml ]; then + print_blue "===> Download config-template.yaml" + wget https://raw.githubusercontent.com/meshplus/bitxhub/master/scripts/quick_start/config-template.yaml + fi + rm -rf "${CURRENT_PATH}"/config.yaml + cp "${CURRENT_PATH}"/config-template.yaml "${CURRENT_PATH}"/config.yaml + + if [ ! -d crypto-config ]; then + print_red "===> Please provide the 'crypto-config'" + exit 1 + fi +} + +function installChaincode() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + print_blue "===> Install chaincode at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + print_blue "===> 1. Deploying broker, transfer and data_swapper chaincode" + fabric-cli chaincode install --gopath ./contracts --ccp broker --ccid broker --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode instantiate --ccp broker --ccid broker --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode install --gopath ./contracts --ccp transfer --ccid transfer --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode instantiate --ccp transfer --ccid transfer --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode install --gopath ./contracts --ccp data_swapper --ccid data_swapper --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode instantiate --ccp data_swapper --ccid data_swapper --config ./config.yaml --orgid org2 --user Admin --cid mychannel + + print_blue "===> 2. Set Alice 10000 amout in transfer chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=transfer \ + --args='{"Func":"setBalance","Args":["Alice", "10000"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 3. Set (key: path, value: ${CURRENT_PATH}) in data_swapper chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=data_swapper \ + --args='{"Func":"set","Args":["path", "'"${CURRENT_PATH}"'"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 4. Register transfer and data_swapper chaincode to broker chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=transfer \ + --args='{"Func":"register"}' --user Admin --orgid org2 --payload --config ./config.yaml + fabric-cli chaincode invoke --cid mychannel --ccid=data_swapper \ + --args='{"Func":"register"}' --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 6. Audit transfer and data_swapper chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=broker \ + --args='{"Func":"audit", "Args":["mychannel", "transfer", "1"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + fabric-cli chaincode invoke --cid mychannel --ccid=broker \ + --args='{"Func":"audit", "Args":["mychannel", "data_swapper", "1"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + +} + +function upgradeChaincode() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + CHAINCODE_VERSION=v1 + if [ $2 ]; then + CHAINCODE_VERSION=$2 + fi + + print_blue "Upgrade chaincode at $FABRIC_IP" + print_blue "Upgrade to version: $CHAINCODE_VERSION" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + print_blue "===> 1. Deploying broker, transfer and data_swapper chaincode" + fabric-cli chaincode install --gopath ./contracts --ccp broker --ccid broker \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode upgrade --ccp broker --ccid broker \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + + fabric-cli chaincode install --gopath ./contracts --ccp transfer --ccid transfer \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode upgrade --ccp transfer --ccid transfer \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + + fabric-cli chaincode install --gopath ./contracts --ccp data_swapper --ccid data_swapper \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + fabric-cli chaincode upgrade --ccp data_swapper --ccid data_swapper \ + --v $CHAINCODE_VERSION \ + --config ./config.yaml --orgid org2 --user Admin --cid mychannel + + print_blue "===> 2. Set Alice 10000 amout in transfer chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=transfer \ + --args='{"Func":"setBalance","Args":["Alice", "10000"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 3. Set (key: path, value: ${CURRENT_PATH}) in data_swapper chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=data_swapper \ + --args='{"Func":"set","Args":["path", "'"${CURRENT_PATH}"'"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 4. Register transfer and data_swapper chaincode to broker chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=transfer \ + --args='{"Func":"register"}' --user Admin --orgid org2 --payload --config ./config.yaml + fabric-cli chaincode invoke --cid mychannel --ccid=data_swapper \ + --args='{"Func":"register"}' --user Admin --orgid org2 --payload --config ./config.yaml + + print_blue "===> 6. Audit transfer and data_swapper chaincode" + fabric-cli chaincode invoke --cid mychannel --ccid=broker \ + --args='{"Func":"audit", "Args":["mychannel", "transfer", "1"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml + fabric-cli chaincode invoke --cid mychannel --ccid=broker \ + --args='{"Func":"audit", "Args":["mychannel", "data_swapper", "1"]}' \ + --user Admin --orgid org2 --payload --config ./config.yaml +} + +function initBroker() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + print_blue "===> Init broker chaincode at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + fabric-cli chaincode invoke --cid mychannel --ccid=broker \ + --args='{"Func":"initialize"}' \ + --user Admin --orgid org2 --payload --config ./config.yaml +} + +function getBalance() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + print_blue "===> Query Alice balance at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + fabric-cli chaincode invoke --ccid=transfer \ + --args '{"Func":"getBalance","Args":["Alice"]}' \ + --config ./config.yaml --payload \ + --orgid=org2 --user=Admin --cid=mychannel +} + +function getData() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + print_blue "===> Query data at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + fabric-cli chaincode invoke --ccid=data_swapper \ + --args '{"Func":"get","Args":["path"]}' \ + --config ./config.yaml --payload \ + --orgid=org2 --user=Admin --cid=mychannel +} + +function interchainTransfer() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + if [ ! $2 ]; then + echo "Please input target appchain" + exit 1 + fi + + TARGET_APPCHAIN_ID=$2 + + print_blue "===> Invoke at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + echo "===> Alice transfer token from one chain to another chain" + echo "===> Target appchain id: $TARGET_APPCHAIN_ID" + + fabric-cli chaincode invoke --ccid transfer \ + --args '{"Func":"transfer","Args":["'"${TARGET_APPCHAIN_ID}"'", "mychannel&transfer", "Alice","Alice","1"]}' \ + --config ./config.yaml --payload \ + --orgid=org2 --user=Admin --cid=mychannel +} + +function interchainGet() { + prepare + + FABRIC_IP=localhost + if [ $1 ]; then + FABRIC_IP=$1 + fi + + if [ ! $2 ]; then + echo "Please input target appchain" + exit 1 + fi + + TARGET_APPCHAIN_ID=$2 + + print_blue "===> Invoke at $FABRIC_IP" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${CURRENT_PATH} + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + echo "===> Get path value from other appchain" + echo "===> Target appchain id: $TARGET_APPCHAIN_ID" + + fabric-cli chaincode invoke --ccid data_swapper \ + --args '{"Func":"get","Args":["'"${TARGET_APPCHAIN_ID}"'", "mychannel&data_swapper", "path"]}' \ + --config ./config.yaml --payload \ + --orgid=org2 --user=Admin --cid=mychannel +} + +MODE=$1 + +if [ "$MODE" == "install" ]; then + shift + installChaincode $1 +elif [ "$MODE" == "upgrade" ]; then + shift + upgradeChaincode $1 $2 +elif [ "$MODE" == "init" ]; then + shift + initBroker $1 +elif [ "$MODE" == "get_balance" ]; then + shift + getBalance $1 +elif [ "$MODE" == "get_data" ]; then + shift + getData $1 +elif [ "$MODE" == "interchain_transfer" ]; then + shift + interchainTransfer $1 $2 +elif [ "$MODE" == "interchain_get" ]; then + shift + interchainGet $1 $2 +else + printHelp + exit 1 +fi diff --git a/scripts/quick_start/config-template.yaml b/scripts/quick_start/config-template.yaml new file mode 100644 index 0000000..c01f956 --- /dev/null +++ b/scripts/quick_start/config-template.yaml @@ -0,0 +1,328 @@ +version: 1.0.0 + +client: + + # Which organization does this application instance belong to? The value must be the name of an org + # defined under "organizations" + organization: org2 + + logging: + level: info + + + # Root of the MSP directories with keys and certs. + cryptoconfig: + path: ${CONFIG_PATH}/crypto-config + + # Some SDKs support pluggable KV stores, the properties under "credentialStore" + # are implementation specific + credentialStore: + # [Optional]. Used by user store. Not needed if all credentials are embedded in configuration + # and enrollments are performed elswhere. + path: "/tmp/state-store" + + # [Optional]. Specific to the CryptoSuite implementation used by GO SDK. Software-based implementations + # requiring a key store. PKCS#11 based implementations does not. + cryptoStore: + # Specific to the underlying KeyValueStore that backs the crypto key store. + path: /tmp/msp + + # BCCSP config for the client. Used by GO SDK. + BCCSP: + security: + enabled: true + default: + provider: "SW" + hashAlgorithm: "SHA2" + softVerify: true + level: 256 + + tlsCerts: + #[Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false + systemCertPool: true + + #[Optional]. Client key and cert for TLS handshake with peers and orderers + client: + key: + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/tls/server.key + cert: + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/tls/server.crt + +# +# [Optional]. But most apps would have this section so that channel objects can be constructed +# based on the content below. If an app is creating channels, then it likely will not need this +# section. +# +channels: + + #[Required if _default not defined; Optional if _default defined]. + # name of the channel + mychannel: + + # list of orderers designated by the application to use for transactions on this + # channel. This list can be a result of access control ("FBI" can only access "ordererA"), or + # operational decisions to share loads from applications among the orderers. The values must + # be "names" of orgs defined under "organizations/p unable to load config backend: loading config feers" + # deprecated: not recommended, to override any orderer configuration items, entity matchers should be used. + # orderers: + # - orderer.citizens.com + + #[Required if _default peers not defined; Optional if _default peers defined]. + # list of peers from participating orgs + peers: + peer1.org2.example.com: + endorsingPeer: true + chaincodeQuery: true + ledgerQuery: true + eventSource: true + # peer0.org2.example.com: + # endorsingPeer: true + # chaincodeQuery: true + # ledgerQuery: true + # eventSource: true +# +# list of participating organizations in this network +# +organizations: + org1: + mspid: Org1MSP + + # This org's MSP store (absolute path or relative to client.cryptoconfig) + cryptoPath: peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + + peers: + - peer0.org1.example.com + - peer1.org1.example.com + + # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fab based + # network. Typically certificates provisioning is done in a separate process outside of the + # runtime network. CA is a special certificate authority that provides a REST APIs for + # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for + # CA servers. + # certificateAuthorities: + # - ca.fbi.citizens.com + + # the profile will contain public information about organizations other than the one it belongs to. + # These are necessary information to make transaction lifecycles work, including MSP IDs and + # peers with a public URL to send transaction proposals. The file will not contain private + # information reserved for members of the organization, such as admin key and certificate, + # ca registrar enroll ID and secret, etc. + + org2: + mspid: Org2MSP + + # This org's MSP store (absolute path or relative to client.cryptoconfig) + cryptoPath: peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp + + peers: + - peer0.org2.example.com + - peer1.org2.example.com + +# +# List of orderers to send transaction and channel create/update requests to. For the time +# being only one orderer is needed. If more than one is defined, which one get used by the +# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. +# +orderers: + orderer.example.com: + url: grpcs://localhost:7050 + + # these are standard properties defined by the gRPC library + # they will be passed in as-is to gRPC client constructor + grpcOptions: + ssl-target-name-override: orderer.example.com + # These parameters should be set in coordination with the keepalive policy on the server, + # as incompatible settings can result in closing of connection. + # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs + allow-insecure: false + + tlsCACerts: + # Certificate location absolute path + path: ${CONFIG_PATH}/crypto-config/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem + +# +# List of peers to send various requests to, including endorsement, query +# and event listener registration. +# +peers: + peer0.org1.example.com: + # this URL is used to send endorsement and query requests + url: grpcs://localhost:7051 + eventUrl: grpcs://localhost:7053 + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + # These parameters should be set in coordination with the keepalive policy on the server, + # as incompatible settings can result in closing of connection. + # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs + allow-insecure: false + + tlsCACerts: + # Certificate location absolute path + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem + + peer1.org1.example.com: + # this URL is used to send endorsement and query requests + url: grpcs://localhost:8051 + eventUrl: grpcs://localhost:8053 + grpcOptions: + ssl-target-name-override: peer1.org1.example.com + # These parameters should be set in coordination with the keepalive policy on the server, + # as incompatible settings can result in closing of connection. + # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs + allow-insecure: false + + tlsCACerts: + # Certificate location absolute path + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem + + peer0.org2.example.com: + # this URL is used to send endorsement and query requests + url: grpcs://localhost:9051 + eventUrl: grpcs://localhost:9053 + grpcOptions: + ssl-target-name-override: peer0.org2.example.com + # These parameters should be set in coordination with the keepalive policy on the server, + # as incompatible settings can result in closing of connection. + # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs + allow-insecure: false + + tlsCACerts: + # Certificate location absolute path + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem + + peer1.org2.example.com: + # this URL is used to send endorsement and query requests + url: grpcs://localhost:10051 + eventUrl: grpcs://localhost:10053 + grpcOptions: + ssl-target-name-override: peer1.org2.example.com + # These parameters should be set in coordination with the keepalive policy on the server, + # as incompatible settings can result in closing of connection. + # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled + keep-alive-time: 0s + keep-alive-timeout: 20s + keep-alive-permit: false + fail-fast: false + # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs + allow-insecure: false + + tlsCACerts: + # Certificate location absolute path + path: ${CONFIG_PATH}/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem + + # + # CA is a special kind of Certificate Authority provided by Hyperledger Fab which allows + # certificate management to be done via REST APIs. Application may choose to use a standard + # Certificate Authority instead of CA, in which case this section would not be specified. + # + # certificateAuthorities: + # ca.org1.example.com: + # url: https://ca.org1.example.com:7053 + # tlsCACerts: + # # Comma-Separated list of paths + # path: /home/shaojie/config_v1/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem + # # Client key and cert for SSL handshake with Fab CA + # client: + # key: + # path: /home/shaojie/config_v1/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.key + # cert: + # path: /home/shaojie/config_v1/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.crt + + # # CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is + # # needed to enroll and invoke new users. + # registrar: + # enrollId: admin + # enrollSecret: adminpw + # # [Optional] The optional name of the CA. + # caName: ca.org1.example.com + + + # EntityMatchers enable substitution of network hostnames with static configurations + # so that properties can be mapped. Regex can be used for this purpose + # UrlSubstitutionExp can be empty which means the same network hostname will be used + # UrlSubstitutionExp can be given same as mapped peer url, so that mapped peer url can be used + # UrlSubstitutionExp can have golang regex matchers like 1.local.example.2:3 for pattern + # like peer0.fbi.citizens.com:1234 which converts peer0.fbi.citizens.com to peer0.FBI.local.citizens.com:1234 + # sslTargetOverrideUrlSubstitutionExp follow in the same lines as + # SubstitutionExp for the fields gprcOptions.ssl-target-name-override respectively +# In any case mappedHost's config will be used, so mapped host cannot be empty, if entityMatchers are used +#entityMatchers: +entityMatchers: + peer: + - pattern: (\w*)peer0.org1.example.com:(\w*) + urlSubstitutionExp: grpcs://localhost:7051 + eventUrlSubstitutionExp: grpcs://localhost:7053 + sslTargetOverrideUrlSubstitutionExp: peer0.org1.example.com + mappedHost: peer0.org1.example.com + + + - pattern: (\w*)peer1.org1.example.com:(\w*) + urlSubstitutionExp: grpcs://localhost:8051 + eventUrlSubstitutionExp: grpcs://localhost:8053 + sslTargetOverrideUrlSubstitutionExp: peer1.org1.example.com + mappedHost: peer1.org1.example.com + + - pattern: (\w*)peer0.org2.example.com:(\w*) + urlSubstitutionExp: grpcs://localhost:9051 + eventUrlSubstitutionExp: grpcs://localhost:9053 + sslTargetOverrideUrlSubstitutionExp: peer0.org2.example.com + mappedHost: peer0.org2.example.com + + - pattern: (\w*)peer1.org2.example.com:(\w*) + urlSubstitutionExp: grpcs://localhost:10051 + eventUrlSubstitutionExp: grpcs://localhost:10053 + sslTargetOverrideUrlSubstitutionExp: peer1.org2.example.com + mappedHost: peer1.org2.example.com + + # orderer: + # - pattern: (\w+).example.(\w+) + # urlSubstitutionExp: orderer.citizens.com:7050 + # sslTargetOverrideUrlSubstitutionExp: orderer.citizens.com + # mappedHost: orderer.citizens.com + # + # - pattern: (\w+).example2.(\w+) + # urlSubstitutionExp: localhost:7050 + # sslTargetOverrideUrlSubstitutionExp: localhost + # mappedHost: orderer.citizens.com + # + # - pattern: (\w+).example3.(\w+) + # urlSubstitutionExp: + # sslTargetOverrideUrlSubstitutionExp: + # mappedHost: orderer.citizens.com + # + # - pattern: (\w+).example4.(\w+):(\d+) + # urlSubstitutionExp: 1.example.2:3 + # sslTargetOverrideUrlSubstitutionExp: 1.example.2 + # mappedHost: orderer.citizens.com + # + # certificateAuthority: + # - pattern: (\w+).fbi.citizens.com.(\w+) + # urlSubstitutionExp: + # mappedHost: ca.fbi.citizens.com + # + #entityMatchers: + orderer: + - pattern: (\w*)orderer.example.com(\w*) + urlSubstitutionExp: grpcs://localhost:7050 + sslTargetOverrideUrlSubstitutionExp: orderer.example.com + mappedHost: orderer.example.com diff --git a/scripts/quick_start/contracts.zip b/scripts/quick_start/contracts.zip new file mode 100644 index 0000000..5dc6b62 Binary files /dev/null and b/scripts/quick_start/contracts.zip differ diff --git a/scripts/quick_start/fabric-rule.wasm b/scripts/quick_start/fabric-rule.wasm new file mode 100644 index 0000000..cafc923 Binary files /dev/null and b/scripts/quick_start/fabric-rule.wasm differ diff --git a/scripts/quick_start/fabric_pier.sh b/scripts/quick_start/fabric_pier.sh new file mode 100755 index 0000000..a378cf4 --- /dev/null +++ b/scripts/quick_start/fabric_pier.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash + +set -e + +CURRENT_PATH=$(pwd) +PIER_ROOT=${CURRENT_PATH}/.pier +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +# The sed commend with system judging +# Examples: +# sed -i 's/a/b/g' bob.txt => x_replace 's/a/b/g' bob.txt +function x_replace() { + system=$(uname) + + if [ "${system}" = "Linux" ]; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +function print_blue() { + printf "${BLUE}%s${NC}\n" "$1" +} + +function print_red() { + printf "${RED}%s${NC}\n" "$1" +} + +function printHelp() { + print_blue "Usage: " + echo " fabric_pier.sh " + echo " - one of 'start', 'restart'" + echo " - 'start ' - bring up the fabric pier" + echo " - 'restart ' - restart the fabric pier" + echo " - 'id' - print pier id" + echo " fabric_pier.sh -h (print this message)" +} + +function prepare() { + cd "${CURRENT_PATH}" + if [ ! -d pier ]; then + print_blue "===> Clone pier" + git clone git@git.hyperchain.cn:dmlab/pier.git + fi + + print_blue "===> Compile pier" + cd pier + make install + + cd "${CURRENT_PATH}" + if [ ! -d pier-client-fabric ]; then + print_blue "===> Clone pier-client-fabric" + git clone git@git.hyperchain.cn:dmlab/pier-client-fabric.git + fi + + print_blue "===> Compile pier-client-fabric" + cd pier-client-fabric + make fabric1.4 + + cd "${CURRENT_PATH}" + if [ ! -d crypto-config ]; then + print_red "===> Please provide the 'crypto-config'" + exit 1 + fi + + if [ ! -f config-template.yaml ]; then + print_blue "===> Download config-template.yaml" + wget https://raw.githubusercontent.com/meshplus/bitxhub/master/scripts/quick_start/config-template.yaml + fi + rm -rf "${CURRENT_PATH}"/config.yaml + cp "${CURRENT_PATH}"/config-template.yaml "${CURRENT_PATH}"/config.yaml + + if [ ! -f fabric-rule.wasm ]; then + print_blue "===> Download fabric-rule.wasm" + wget https://raw.githubusercontent.com/meshplus/bitxhub/master/scripts/quick_start/fabric-rule.wasm + fi +} + +function start() { + prepare + + pier --repo="${PIER_ROOT}" init + mkdir -p "${PIER_ROOT}"/plugins + cp "${CURRENT_PATH}"/pier-client-fabric/build/fabric-client-1.4.so "${PIER_ROOT}"/plugins/ + cp -rf "${CURRENT_PATH}"/pier-client-fabric/config "${PIER_ROOT}"/fabric + cp -rf "${CURRENT_PATH}"/crypto-config "${PIER_ROOT}"/fabric/ + cp -rf "${CURRENT_PATH}"/config.yaml "${PIER_ROOT}"/fabric/ + cp "${PIER_ROOT}"/fabric/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/msp/signcerts/peer1.org2.example.com-cert.pem \ + "${PIER_ROOT}"/fabric/fabric.validators + + BITXHUB_ADDR="localhost:60011" + FABRIC_IP=localhost + PPROF_PORT=44555 + + if [ $1 ]; then + BITXHUB_ADDR=$1 + fi + + if [ $2 ]; then + FABRIC_IP=$2 + fi + + if [ $3 ]; then + PPROF_PORT=$3 + fi + + x_replace "s/44555/${PPROF_PORT}/g" "${PIER_ROOT}"/pier.toml + x_replace "s/localhost:60011/${BITXHUB_ADDR}/g" "${PIER_ROOT}"/pier.toml + x_replace "s/localhost/${FABRIC_IP}/g" "${PIER_ROOT}"/fabric/fabric.toml + + print_blue "===> bitxhub_addr: $BITXHUB_ADDR, fabric_ip: $FABRIC_IP, pprof: $PPROF_PORT" + + print_blue "===> Register pier to bitxhub" + pier --repo "${PIER_ROOT}" appchain register \ + --name chainA \ + --type fabric \ + --desc chainA-description \ + --version 1.4.3 \ + --validators "${PIER_ROOT}"/fabric/fabric.validators + + print_blue "===> Deploy rule in bitxhub" + pier --repo "${PIER_ROOT}" rule deploy --path "${CURRENT_PATH}"/fabric-rule.wasm + + print_blue "===> Start pier" + cd "${CURRENT_PATH}" + export CONFIG_PATH=${PIER_ROOT}/fabric + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + + pier --repo "${PIER_ROOT}" start +} + +function restart() { + prepare + + BITXHUB_ADDR="localhost:60011" + FABRIC_IP=localhost + PPROF_PORT=44555 + + if [ $1 ]; then + BITXHUB_ADDR=$1 + fi + + if [ $2 ]; then + FABRIC_IP=$2 + fi + + if [ $3 ]; then + PPROF_PORT=$3 + fi + + print_blue "===> bitxhub_addr: $BITXHUB_ADDR, fabric_ip: $FABRIC_IP, pprof: $PPROF_PORT" + + cd "${CURRENT_PATH}" + export CONFIG_PATH=${PIER_ROOT}/fabric + x_replace "s/localhost/${FABRIC_IP}/g" config.yaml + pier --repo "${PIER_ROOT}" start +} + +function printID() { + if [ ! -d $PIER_ROOT ]; then + print_red "Please start pier firstly" + exit 1 + fi + + pier --repo "${PIER_ROOT}" id +} + +MODE=$1 + +if [ "$MODE" == "start" ]; then + shift + start $1 $2 $3 +elif [ "$MODE" == "restart" ]; then + shift + restart $1 $2 $3 +elif [ "$MODE" == "id" ]; then + printID +else + printHelp + exit 1 +fi diff --git a/scripts/quick_start/ffn.sh b/scripts/quick_start/ffn.sh new file mode 100755 index 0000000..536490b --- /dev/null +++ b/scripts/quick_start/ffn.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -e + +VERSION=1.0 +CURRENT_PATH=$(pwd) +FABRIC_SAMPLE_PATH=${CURRENT_PATH}/fabric-samples +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +# The sed commend with system judging +# Examples: +# sed -i 's/a/b/g' bob.txt => x_replace 's/a/b/g' bob.txt +function x_replace() { + system=$(uname) + + if [ "${system}" = "Linux" ]; then + sed -i "$@" + else + sed -i '' "$@" + fi +} + +function print_blue() { + printf "${BLUE}%s${NC}\n" "$1" +} + +function printHelp() { + print_blue "Usage: " + echo " ffn.sh " + echo " - one of 'up', 'down', 'restart'" + echo " - 'up' - bring up the fabric first network" + echo " - 'down' - clear the fabric first network" + echo " - 'restart' - restart the fabric first network" + echo " ffn.sh -h (print this message)" +} + +function prepare() { + if [ ! -d "${FABRIC_SAMPLE_PATH}"/bin ]; then + print_blue "===> Download the necessary dependencies" + curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/master/scripts/bootstrap.sh | bash -s -- 1.4.3 1.4.3 0.4.18 + fi +} + +function networkUp() { + prepare + cd "${FABRIC_SAMPLE_PATH}"/first-network + ./byfn.sh generate + ./byfn.sh up -n + cp -rf "${FABRIC_SAMPLE_PATH}"/first-network/crypto-config "${CURRENT_PATH}" +} + +function networkDown() { + prepare + cd "${FABRIC_SAMPLE_PATH}"/first-network + ./byfn.sh down +} + +function networkRestart() { + prepare + cd "${FABRIC_SAMPLE_PATH}"/first-network + ./byfn.sh restart -n +} + +print_blue "===> Script version: $VERSION" + +MODE=$1 + +if [ "$MODE" == "up" ]; then + networkUp +elif [ "$MODE" == "down" ]; then + networkDown +elif [ "$MODE" == "restart" ]; then + networkRestart +else + printHelp + exit 1 +fi diff --git a/scripts/solo.sh b/scripts/solo.sh new file mode 100644 index 0000000..673f59c --- /dev/null +++ b/scripts/solo.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e + +CURRENT_PATH=$(pwd) +PROJECT_PATH=$(dirname "${CURRENT_PATH}") +BUILD_PATH=${CURRENT_PATH}/build_solo + +function prepare() { + rm -rf "${BUILD_PATH}" + mkdir -p "${BUILD_PATH}" +} + +function config() { + mkdir -p ${BUILD_PATH}/certs + cp -r "${CURRENT_PATH}"/certs/node1/certs/* "${BUILD_PATH}"/certs +} + +function compile() { + cd "${PROJECT_PATH}" + make install + + ## build plugin + cd "${PROJECT_PATH}"/internal/plugins + make solo +} + +function start() { + bitxhub --repo="${BUILD_PATH}" init + mkdir -p "${BUILD_PATH}"/plugins + cp "${PROJECT_PATH}"/internal/plugins/build/solo.so "${BUILD_PATH}"/plugins/ + bitxhub --repo="${BUILD_PATH}" start +} + +prepare +config +compile +start diff --git a/scripts/x.sh b/scripts/x.sh new file mode 100644 index 0000000..2c6bc67 --- /dev/null +++ b/scripts/x.sh @@ -0,0 +1,29 @@ +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +function print_blue() { + printf "${BLUE}%s${NC}\n" "$1" +} + +function print_green() { + printf "${GREEN}%s${NC}\n" "$1" +} + +function print_red() { + printf "${RED}%s${NC}\n" "$1" +} + +# The sed commend with system judging +# Examples: +# sed -i 's/a/b/g' bob.txt => x_replace 's/a/b/g' bob.txt +function x_replace() { + system=$(uname) + + if [ "${system}" = "Linux" ]; then + sed -i "$@" + else + sed -i '' "$@" + fi +} diff --git a/tester/case001_api_test.go b/tester/case001_api_test.go new file mode 100644 index 0000000..10a31d7 --- /dev/null +++ b/tester/case001_api_test.go @@ -0,0 +1,127 @@ +package tester + +import ( + "math/rand" + "testing" + "time" + + "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/crypto/asym" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + rpcx "github.com/meshplus/go-bitxhub-client" + + "github.com/stretchr/testify/suite" +) + +type API struct { + suite.Suite + client rpcx.Client + privKey crypto.PrivateKey + from types.Address +} + +func (suite *API) SetupSuite() { + privKey, err := asym.GenerateKey(asym.ECDSASecp256r1) + suite.Assert().Nil(err) + + client, err := rpcx.New( + rpcx.WithPrivateKey(privKey), + rpcx.WithAddrs(grpcAddresses()), + ) + suite.Assert().Nil(err) + + from, err := privKey.PublicKey().Address() + suite.Assert().Nil(err) + + suite.client = client + suite.privKey = privKey + suite.from = from +} + +func (suite *API) TestSendTransaction() { + pl := &pb.InvokePayload{ + Method: "Set", + Args: []*pb.Arg{ + pb.String("key"), + pb.String("value"), + }, + } + + data, err := pl.Marshal() + suite.Assert().Nil(err) + + tx := &pb.Transaction{ + From: suite.from, + To: rpcx.StoreContractAddr, + Timestamp: time.Now().UnixNano(), + Data: &pb.TransactionData{ + Type: pb.TransactionData_INVOKE, + VmType: pb.TransactionData_BVM, + Payload: data, + }, + Nonce: rand.Int63(), + } + err = tx.Sign(suite.privKey) + suite.Nil(err) + hash, err := suite.client.SendTransaction(tx) + suite.Nil(err) + suite.NotEmpty(hash) +} + +func (suite *API) TestSendWrongTransaction() { + tx := &pb.Transaction{ + To: suite.from, + TransactionHash: types.Hash{}, + Nonce: rand.Int63(), + } + + _, err := suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "tx data can't be empty") + + tx.Data = &pb.TransactionData{} + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "amount can't be 0 in transfer tx") + + tx.Data = &pb.TransactionData{Amount: 10} + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "from can't be empty") + + tx.From = suite.from + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "from can`t be the same as to") + + tx.To = rpcx.StoreContractAddr + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "timestamp is illegal") + + tx.Nonce = -100 + tx.Timestamp = time.Now().UnixNano() + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "nonce is illegal") + + tx.Nonce = rand.Int63() + + _, err = suite.client.SendTransaction(tx) + suite.Require().NotNil(err) + suite.Contains(err.Error(), "signature can't be empty") + + err = tx.Sign(suite.privKey) + suite.Require().Nil(err) + hash, err := suite.client.SendTransaction(tx) + suite.Require().Nil(err) + suite.Assert().NotEmpty(hash) +} + +func (suite *API) TestGetBlockByHash() { +} + +func TestAPI(t *testing.T) { + suite.Run(t, &API{}) +} diff --git a/tester/case002_appchain_test.go b/tester/case002_appchain_test.go new file mode 100755 index 0000000..c6985da --- /dev/null +++ b/tester/case002_appchain_test.go @@ -0,0 +1,98 @@ +package tester + +import ( + "strconv" + "testing" + + "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-model/pb" + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" +) + +type RegisterAppchain struct { + suite.Suite + privKey crypto.PrivateKey + client rpcx.Client +} + +func (suite *RegisterAppchain) SetupSuite() { + var err error + suite.privKey, err = ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + + suite.client, err = rpcx.New( + rpcx.WithPrivateKey(suite.privKey), + rpcx.WithAddrs(grpcAddresses()), + ) + suite.Assert().Nil(err) +} + +// Appchain registers in bitxhub +func (suite *RegisterAppchain) TestRegisterAppchain() { + suite.client.SetPrivateKey(suite.privKey) + args := []*pb.Arg{ + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("税务链"), + rpcx.String("趣链税务链"), + rpcx.String("1.8"), + } + ret, err := suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", args...) + suite.Assert().Nil(err) + suite.Assert().Equal("hyperchain", gjson.Get(string(ret.Ret), "chain_type").String()) +} + +func (suite *RegisterAppchain) TestFetchAppchains() { + k1, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + k2, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + + suite.client.SetPrivateKey(k1) + args := []*pb.Arg{ + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("税务链"), + rpcx.String("趣链税务链"), + rpcx.String("1.8"), + } + _, err = suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", args...) + suite.Assert().Nil(err) + + suite.client.SetPrivateKey(k2) + args = []*pb.Arg{ + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("fabric"), + rpcx.String("政务链"), + rpcx.String("fabric政务"), + rpcx.String("1.4"), + } + _, err = suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", args...) + + suite.Assert().Nil(err) + receipt, err := suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "Appchains") + suite.Assert().Nil(err) + + rec, err := suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "CountAppchains") + suite.Assert().Nil(err) + num, err := strconv.Atoi(string(rec.Ret)) + suite.Assert().Nil(err) + result := gjson.Parse(string(receipt.Ret)) + suite.Assert().EqualValues(num, len(result.Array())) + + r, err := suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "CountApprovedAppchains") + suite.Assert().Nil(err) + num, err = strconv.Atoi(string(r.Ret)) + suite.Assert().Nil(err) + suite.Assert().EqualValues(0, num) +} + +func TestRegisterAppchain(t *testing.T) { + suite.Run(t, &RegisterAppchain{}) +} diff --git a/tester/case003_interchain_test.go b/tester/case003_interchain_test.go new file mode 100644 index 0000000..422d783 --- /dev/null +++ b/tester/case003_interchain_test.go @@ -0,0 +1,192 @@ +package tester + +import ( + "io/ioutil" + "testing" + "time" + + "github.com/meshplus/bitxhub/internal/constant" + + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-model/pb" + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/stretchr/testify/suite" +) + +type Interchain struct { + suite.Suite +} + +func (suite *Interchain) SetupSuite() { +} + +func (suite *Interchain) TestHandleIBTP() { + k1, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + k2, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + f, err := k1.PublicKey().Address() + suite.Require().Nil(err) + t, err := k2.PublicKey().Address() + suite.Require().Nil(err) + + c1, err := rpcx.New( + rpcx.WithPrivateKey(k1), + rpcx.WithAddrs([]string{ + "localhost:60011", + "localhost:60012", + "localhost:60013", + "localhost:60014", + }), + ) + suite.Assert().Nil(err) + + c2, err := rpcx.New( + rpcx.WithPrivateKey(k2), + rpcx.WithAddrs([]string{ + "localhost:60011", + "localhost:60012", + "localhost:60013", + "localhost:60014", + }), + ) + suite.Assert().Nil(err) + + _, err = c1.InvokeBVMContract(constant.InterchainContractAddr.Address(), "Register", + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("婚姻链"), + rpcx.String("趣链婚姻链"), + rpcx.String("1.8"), + ) + suite.Assert().Nil(err) + _, err = c2.InvokeBVMContract(constant.InterchainContractAddr.Address(), "Register", + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("fabric"), + rpcx.String("税务链"), + rpcx.String("fabric婚姻链"), + rpcx.String("1.4"), + ) + suite.Assert().Nil(err) + + // register rule + _, err = c1.InvokeBVMContract(constant.RuleManagerContractAddr.Address(), "RegisterRule", rpcx.String(f.Hex()), rpcx.String("")) + suite.Assert().Nil(err) + + ib := &pb.IBTP{From: f.Hex(), To: t.Hex(), Index: 1, Timestamp: time.Now().UnixNano()} + data, err := ib.Marshal() + suite.Require().Nil(err) + _, err = c1.InvokeBVMContract(rpcx.InterchainContractAddr, "HandleIBTP", rpcx.Bytes(data)) + suite.Require().Nil(err) +} + +func (suite *Interchain) TestGetIBTPByID() { + k1, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + k2, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + f, err := k1.PublicKey().Address() + suite.Require().Nil(err) + t, err := k2.PublicKey().Address() + suite.Require().Nil(err) + + c1, err := rpcx.New( + rpcx.WithPrivateKey(k1), + rpcx.WithAddrs([]string{ + "localhost:60011", + }), + ) + suite.Assert().Nil(err) + + c2, err := rpcx.New( + rpcx.WithPrivateKey(k2), + rpcx.WithAddrs([]string{ + "localhost:60011", + }), + ) + suite.Assert().Nil(err) + + confByte, err := ioutil.ReadFile("./test_data/validator") + suite.Assert().Nil(err) + _, err = c1.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", + rpcx.String(string(confByte)), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("婚姻链"), + rpcx.String("趣链婚姻链"), + rpcx.String("1.8"), + ) + suite.Assert().Nil(err) + _, err = c2.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("fabric"), + rpcx.String("税务链"), + rpcx.String("fabric税务链"), + rpcx.String("1.8"), + ) + suite.Assert().Nil(err) + + contractByte, err := ioutil.ReadFile("./test_data/fabric_policy.wasm") + suite.Assert().Nil(err) + addr, err := c1.DeployContract(contractByte) + suite.Assert().Nil(err) + // register rule + _, err = c1.InvokeBVMContract(constant.RuleManagerContractAddr.Address(), "RegisterRule", rpcx.String(f.Hex()), rpcx.String(addr.Hex())) + suite.Assert().Nil(err) + + proof, err := ioutil.ReadFile("./test_data/proof") + suite.Assert().Nil(err) + ib := &pb.IBTP{From: f.Hex(), To: t.Hex(), Index: 1, Timestamp: time.Now().UnixNano(), Proof: proof} + data, err := ib.Marshal() + suite.Assert().Nil(err) + _, err = c1.InvokeBVMContract(rpcx.InterchainContractAddr, "HandleIBTP", rpcx.Bytes(data)) + suite.Assert().Nil(err) + + ib.Index = 2 + data, err = ib.Marshal() + suite.Assert().Nil(err) + _, err = c1.InvokeBVMContract(rpcx.InterchainContractAddr, "HandleIBTP", rpcx.Bytes(data)) + suite.Assert().Nil(err) + + ib.Index = 3 + data, err = ib.Marshal() + suite.Assert().Nil(err) + _, err = c1.InvokeBVMContract(rpcx.InterchainContractAddr, "HandleIBTP", rpcx.Bytes(data)) + suite.Assert().Nil(err) + + ib.Index = 2 + ret, err := c1.InvokeBVMContract(rpcx.InterchainContractAddr, "GetIBTPByID", rpcx.String(ib.ID())) + suite.Assert().Nil(err) + suite.Assert().Equal(ret.Status.String(), "SUCCESS") +} + +func (suite *Interchain) TestAudit() { + k, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Require().Nil(err) + + c, err := rpcx.New( + rpcx.WithPrivateKey(k), + rpcx.WithAddrs([]string{ + "localhost:60011", + "localhost:60012", + "localhost:60013", + "localhost:60014", + }), + ) + suite.Require().Nil(err) + + ret, err := c.InvokeBVMContract(rpcx.InterchainContractAddr, "Audit", + rpcx.String("0x123"), + rpcx.Int32(1), + rpcx.String("通过"), + ) + suite.Require().Nil(err) + suite.Contains(string(ret.Ret), "caller is not an admin account") +} + +func TestInterchain(t *testing.T) { + suite.Run(t, &Interchain{}) +} diff --git a/tester/case004_role_test.go b/tester/case004_role_test.go new file mode 100644 index 0000000..75a3f88 --- /dev/null +++ b/tester/case004_role_test.go @@ -0,0 +1,85 @@ +package tester + +import ( + "strconv" + + "github.com/meshplus/bitxhub/internal/constant" + + "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" +) + +type Role struct { + suite.Suite + privKey crypto.PrivateKey + client rpcx.Client +} + +func (suite *Role) SetupSuite() { + var err error + suite.privKey, err = ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + + suite.client, err = rpcx.New( + rpcx.WithPrivateKey(suite.privKey), + rpcx.WithAddrs([]string{ + "localhost:60011", + "localhost:60012", + "localhost:60013", + "localhost:60014", + }), + ) + suite.Require().Nil(err) +} + +func (suite *Role) TestGetRole() { + _, err := suite.client.InvokeBVMContract(constant.InterchainContractAddr.Address(), "Register", + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("婚姻链"), + rpcx.String("趣链婚姻链"), + rpcx.String("1.8"), + ) + suite.Assert().Nil(err) + + receipt, err := suite.client.InvokeBVMContract(constant.RoleContractAddr.Address(), "GetRole") + suite.Require().Nil(err) + suite.Equal("appchain_admin", string(receipt.Ret)) + + k, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Require().Nil(err) + + suite.client.SetPrivateKey(k) + r, err := suite.client.InvokeBVMContract(constant.RoleContractAddr.Address(), "GetRole") + suite.Assert().Nil(err) + suite.Equal("none", string(r.Ret)) +} + +func (suite *Role) TestGetAdminRoles() { + k, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Require().Nil(err) + + suite.client.SetPrivateKey(k) + r, err := suite.client.InvokeBVMContract(constant.RoleContractAddr.Address(), "GetAdminRoles") + suite.Assert().Nil(err) + ret := gjson.ParseBytes(r.Ret) + suite.EqualValues(4, len(ret.Array())) +} + +func (suite *Role) TestIsAdmin() { + k, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Require().Nil(err) + from, err := k.PublicKey().Address() + suite.Require().Nil(err) + + suite.client.SetPrivateKey(k) + r, err := suite.client.InvokeBVMContract(constant.RoleContractAddr.Address(), "IsAdmin", rpcx.String(from.Hex())) + suite.Assert().Nil(err) + ret, err := strconv.ParseBool(string(r.Ret)) + suite.Assert().Nil(err) + suite.EqualValues(false, ret) +} diff --git a/tester/case005_gateway_test.go b/tester/case005_gateway_test.go new file mode 100644 index 0000000..d533d42 --- /dev/null +++ b/tester/case005_gateway_test.go @@ -0,0 +1,47 @@ +package tester + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" +) + +const ( + host = "http://localhost:9091/v1/" +) + +type Gateway struct { + suite.Suite +} + +func (suite *Gateway) TestGetBlock() { + data := httpGet(suite.Suite, "block?type=0&value=1") + height := gjson.Get(data, "block_header.number").Int() + suite.Assert().EqualValues(1, height, data) +} + +func (suite *Gateway) TestGetBlockByHash() { + data := httpGet(suite.Suite, "block?type=0&value=1") + hash := gjson.Get(data, "block_hash").String() + m := httpGet(suite.Suite, "block?type=1&value="+hash) + suite.Assert().EqualValues(1, gjson.Get(m, "block_header.number").Int(), m) + suite.Assert().Equal(hash, gjson.Get(m, "block_hash").String(), m) +} + +func TestGateway(t *testing.T) { + suite.Run(t, &Gateway{}) +} + +func httpGet(suite suite.Suite, path string) string { + resp, err := http.Get(host + path) + suite.Assert().Nil(err) + c, err := ioutil.ReadAll(resp.Body) + suite.Assert().Nil(err) + err = resp.Body.Close() + suite.Assert().Nil(err) + + return string(c) +} diff --git a/tester/case006_pier_test.go b/tester/case006_pier_test.go new file mode 100644 index 0000000..9e3d327 --- /dev/null +++ b/tester/case006_pier_test.go @@ -0,0 +1,88 @@ +package tester + +import ( + "context" + "fmt" + "testing" + + "github.com/meshplus/bitxhub-kit/crypto" + "github.com/meshplus/bitxhub-kit/crypto/asym/ecdsa" + "github.com/meshplus/bitxhub-kit/key" + "github.com/meshplus/bitxhub-kit/types" + "github.com/meshplus/bitxhub-model/pb" + rpcx "github.com/meshplus/go-bitxhub-client" + "github.com/stretchr/testify/suite" +) + +type Pier struct { + suite.Suite + privKey crypto.PrivateKey + address types.Address + client rpcx.Client // bitxhub admin +} + +func (suite *Pier) SetupSuite() { + key, err := key.LoadKey("./test_data/key") + suite.Require().Nil(err) + + privKey, err := key.GetPrivateKey("bitxhub") + suite.Require().Nil(err) + + address, err := privKey.PublicKey().Address() + suite.Require().Nil(err) + + client, err := rpcx.New( + rpcx.WithPrivateKey(privKey), + rpcx.WithAddrs(grpcAddresses()), + ) + suite.Require().Nil(err) + + suite.privKey = privKey + suite.address = address + suite.client = client +} + +func (suite *Pier) TestSyncMerkleWrapper() { + privKey, err := ecdsa.GenerateKey(ecdsa.Secp256r1) + suite.Assert().Nil(err) + + address, err := privKey.PublicKey().Address() + suite.Require().Nil(err) + + client, err := rpcx.New( + rpcx.WithPrivateKey(privKey), + rpcx.WithAddrs(grpcAddresses()), + ) + suite.Require().Nil(err) + + args := []*pb.Arg{ + rpcx.String(""), + rpcx.Int32(0), + rpcx.String("hyperchain"), + rpcx.String("税务链"), + rpcx.String("趣链税务链"), + rpcx.String("1.8"), + } + + ret, err := client.InvokeBVMContract(rpcx.InterchainContractAddr, "Register", args...) + suite.Require().Nil(err) + suite.Require().EqualValues("SUCCESS", ret.Status.String()) + + go func() { + w, err := client.SyncMerkleWrapper(context.Background(), address.Hex(), 1) + suite.Require().Nil(err) + fmt.Println(<-w) + }() + + res, err := suite.client.InvokeBVMContract(rpcx.InterchainContractAddr, "Audit", + rpcx.String(address.Hex()), + rpcx.Int32(1), + rpcx.String("pass"), + ) + suite.Require().Nil(err) + suite.Require().EqualValues("SUCCESS", res.Status.String()) +} + +func TestPier(t *testing.T) { + suite.Run(t, &Pier{}) +} diff --git a/tester/test_data/fabric_policy.wasm b/tester/test_data/fabric_policy.wasm new file mode 100644 index 0000000..6f9394b Binary files /dev/null and b/tester/test_data/fabric_policy.wasm differ diff --git a/tester/test_data/key b/tester/test_data/key new file mode 100755 index 0000000..3f7b3e6 --- /dev/null +++ b/tester/test_data/key @@ -0,0 +1 @@ +{"address":"0xba30d0dd7876318da4515826a1f8bee8cefc9061","private_key":"35378a746979e54690ddbb08ee8c678f22a45c7affa7565650efb5137827d5dfa611a95336d28c6f408ff7879989044d","version":"1.0","encrypted":true} \ No newline at end of file diff --git a/tester/test_data/proof b/tester/test_data/proof new file mode 100755 index 0000000..2efe327 --- /dev/null +++ b/tester/test_data/proof @@ -0,0 +1,34 @@ + +$ +" + Broker + pollingEvent +{} + + 72 !ris"3s +|` +BrokerV + + +OutterMeta +> +4out-msg-0xd50ec14376374a6ef9396f813cd9c25b15241907-1 +lscc + +Broker[{"index":1,"to":"0xd50ec14376374a6ef9396f813cd9c25b15241907","fid":"mychannel-Transfer","tid":"0x9f03a1fb549899d669d49d0fc6e6c86e467b1277","func":"charge","args":"Alice,13","callback":""}]" Brokerv2 + +Org2MSP-----BEGIN CERTIFICATE----- +MIICKTCCAc+gAwIBAgIRAIBO31aZaSZoEYSy2AJuhJcwCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzIuZXhhbXBsZS5jb20wHhcNMjAwMjA1MDgyMjAwWhcNMzAwMjAyMDgyMjAw +WjBqMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzENMAsGA1UECxMEcGVlcjEfMB0GA1UEAxMWcGVlcjEub3Jn +Mi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABG3jszFPTbGm +dAYg2BxmHMTDKfQReNw3p9ttMK130qF5lQo5zLBG8Sa3viOCLnvjjg6A/P+yKnwv +isI/jEVE8T2jTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1Ud +IwQkMCKAIMVL+daK7nMGr2/AQIXTSPFkdd3UiPVDkWtkh5ujnalEMAoGCCqGSM49 +BAMCA0gAMEUCIQDMYOQiYeMiQZTxlRkj/3/jjYvwwdCcX5AWuFmraiHkugIgFkX/ +6uiTSD0lz8P+wwlLf24cIABq2aZyi8q4gj0YfwA= +-----END CERTIFICATE----- +F0D 4ClI$J+eٴd!P?-I/ [R&s:z7y (eBdAG \ No newline at end of file diff --git a/tester/test_data/validator b/tester/test_data/validator new file mode 100755 index 0000000..014ef44 --- /dev/null +++ b/tester/test_data/validator @@ -0,0 +1 @@ +{"conf_byte":["config:\"\\n\\007Org1MSP\\022\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\347\\006-----BEGIN CERTIFICATE-----\\nMIICVzCCAf2gAwIBAgIQf3G6fCLqBl6of6Mne0kgrjAKBggqhkjOPQQDAjB2MQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIy\\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\\nAQcDQgAEnOg10EXRyXMIdXulFZ8A/C74TP93sPlv6VGeuJL45BE0KDfduZTlAFSq\\ni8JzyWuYoeyMmXNgn4MVKf8dUNrGCKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\\nDgQiBCB5+011QxrNtHNxk53Hlc0+6dJwUYB9zjTCSnBlSRRuCTAKBggqhkjOPQQD\\nAgNIADBFAiEA0iXoxQ2QYwB58E/OlH67EXixxy+y8MWn3nHqQxcFbj8CIDiuiBYn\\n6BPE4Vni3TtITEUoKr1Zk9FEArFqd4jZckyY\\n-----END CERTIFICATE-----\\nZ\\264\\033\\010\\001\\022\\352\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\350\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\351\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\353\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUTCCAfegAwIBAgIQRjOCgYVRiKNLAKMZ7gH6iTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nAYk87opDv3F1nDFPIzvwdTejNAys5uss9qs3C9BVmzSvdCLpwSvxJ5FgVBMQUyT5\\neWhgycO1dJkWLyccA9emWKNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCCD\\n2RLxnfds2FQ0CARP+j58qmhujdp0rV/JaAx4O6RQhjAKBggqhkjOPQQDAgNIADBF\\nAiEArPP2HJ+fpJ5/B6kpA30zsYsFaxqqC5yGS1hcNUSwGkgCIAvOFv7CUXTrd0qn\\nFtMeiClbcFbF2skGXXmw+/LhvTS9\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" ","config:\"\\n\\007Org2MSP\\022\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\347\\006-----BEGIN CERTIFICATE-----\\nMIICVzCCAf6gAwIBAgIRALeI2n3d7i8NYA0k5uYSudcwCgYIKoZIzj0EAwIwdjEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs\\nc2NhLm9yZzIuZXhhbXBsZS5jb20wHhcNMjAwMjA1MDgyMjAwWhcNMzAwMjAyMDgy\\nMjAwWjB2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE\\nBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEfMB0G\\nA1UEAxMWdGxzY2Eub3JnMi5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49\\nAwEHA0IABAqomq221xDTSYx9u+X6QRdTvEbLUT0aUHf35tIEpxghbcYlCQJjNH7h\\n25ORrmZn+2d/drhzrgcgmHNw+uSjBiWjbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNV\\nHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNV\\nHQ4EIgQgVlPMVnzAIjgWLk+hbqH4mqiABZRJY6XqXnzGaxiPo5gwCgYIKoZIzj0E\\nAwIDRwAwRAIgGv1DhSuU8PNcopHgMIWbY6SMxPOVp7x5zGL6n7RHK58CIBtPepLU\\nEBf+fYV6i/JXaaolTvdLvlStkoTR6RuuGzV6\\n-----END CERTIFICATE-----\\nZ\\264\\033\\010\\001\\022\\352\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\350\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\351\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\353\\006\\n\\337\\006-----BEGIN CERTIFICATE-----\\nMIICUDCCAfegAwIBAgIQC1pZAjSfaiv0KlgBkVcF/DAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMi5leGFtcGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcyLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcyLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\n3PR79jvxLfXetqoL0BT3DzMdmGQmvo6eLLTK8e0qBpyDqmy2vQWn+BhFH74TVIde\\neJ4OvGS6F7uQGWDn9wVBxaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDF\\nS/nWiu5zBq9vwECF00jxZHXd1Ij1Q5FrZIebo52pRDAKBggqhkjOPQQDAgNHADBE\\nAiBg9ebmCyWjfw6SAE6Ke+Itp6o+TvmIOwdSrpY/D9uOgwIgBY0chdl3VfhmOizW\\nzw+3Rij9yNAoaRvE1OhP5uErO1g=\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" ","config:\"\\n\\nOrdererMSP\\022\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\nB\\016\\n\\004SHA2\\022\\006SHA256J\\317\\006-----BEGIN CERTIFICATE-----\\nMIICQzCCAemgAwIBAgIQAT9ufXTKHsaf3FePfHQIazAKBggqhkjOPQQDAjBsMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEUMBIGA1UEChMLZXhhbXBsZS5jb20xGjAYBgNVBAMTEXRsc2NhLmV4\\nYW1wbGUuY29tMB4XDTIwMDIwNTA4MjIwMFoXDTMwMDIwMjA4MjIwMFowbDELMAkG\\nA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFu\\nY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRowGAYDVQQDExF0bHNjYS5leGFt\\ncGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNa1DY9sQJSm5qlKDDAP\\nZHG2uVnYb4UIDonhssuRmfgl2h22Syx/WE703yMDxdaX4GYSs7pPuszyj3F9HCcS\\nBNijbTBrMA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYB\\nBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgKP262WZIDcasrdgPeL4F\\ncs1ehed8aJByB1p6HOUwmp8wCgYIKoZIzj0EAwIDSAAwRQIhAMl0+SAt/xlyqNiE\\ngiZK01ho31vBVWvDt1VWsINca/ytAiAQ1qDK2Eb/pleni56Fw/6Ee1mBRR/Sh3H7\\nSNbe1oR5KA==\\n-----END CERTIFICATE-----\\nZ\\324\\032\\010\\001\\022\\322\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\006client\\032\\320\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\004peer\\\"\\321\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\005admin*\\323\\006\\n\\307\\006-----BEGIN CERTIFICATE-----\\nMIICPjCCAeSgAwIBAgIRANzgptpH5rKbTAqTnDY+RdwwCgYIKoZIzj0EAwIwaTEL\\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\\ncmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt\\ncGxlLmNvbTAeFw0yMDAyMDUwODIyMDBaFw0zMDAyMDIwODIyMDBaMGkxCzAJBgNV\\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp\\nc2NvMRQwEgYDVQQKEwtleGFtcGxlLmNvbTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5j\\nb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASBzwYcOSXpBzwCDlYMrJAd01V7\\nTQHbGkCnFOhvSG3rklHy1/X3ku1DljA970wi05FUgyME8+uBRokz6KRU/Cefo20w\\nazAOBgNVHQ8BAf8EBAMCAaYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\\nMA8GA1UdEwEB/wQFMAMBAf8wKQYDVR0OBCIEIOsMje7jvZcX05nRhnE817Ni+CeC\\nwlhNnFZVTx4G/lx8MAoGCCqGSM49BAMCA0gAMEUCIQD0w0Yk++NxSsnYrubDzxPA\\nA1pjyywNiDV9Si8EFu52+wIgQ3vuhPLOoaVElb0CeTGcMp2LLaJOtwA6EwwL23UQ\\n/kY=\\n-----END CERTIFICATE-----\\n\\022\\007orderer\" "],"policy":"\u0012\u0010\u0012\u000e\u0008\u0001\u0012\u0002\u0008\u0000\u0012\u0002\u0008\u0001\u0012\u0002\u0008\u0002\u001a\u000e\u0012\u000c\n\nOrdererMSP\u001a\u000b\u0012\t\n\u0007Org1MSP\u001a\u000b\u0012\t\n\u0007Org2MSP","cid":"Broker"} \ No newline at end of file diff --git a/tester/tester_test.go b/tester/tester_test.go new file mode 100644 index 0000000..57f0e84 --- /dev/null +++ b/tester/tester_test.go @@ -0,0 +1,70 @@ +package tester + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "net/http" + "testing" + "time" + + "github.com/Rican7/retry" + "github.com/Rican7/retry/strategy" + "github.com/stretchr/testify/suite" + "github.com/tidwall/gjson" +) + +func TestTester(t *testing.T) { + err := retry.Retry(func(attempt uint) error { + resp, err := http.Get(host + "chain_status") + if err != nil { + fmt.Println(err) + return err + } + + c, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + return err + } + + if err := resp.Body.Close(); err != nil { + return err + } + + res := gjson.Get(string(c), "data") + + ret, err := base64.StdEncoding.DecodeString(res.String()) + if err != nil { + fmt.Println(err) + return err + } + + if string(ret) != "normal" { + fmt.Println("abnormal") + return fmt.Errorf("abnormal") + } + + return nil + }, strategy.Wait(1*time.Second), strategy.Limit(60)) + + if err != nil { + log.Fatal(err) + } + + suite.Run(t, &API{}) + suite.Run(t, &RegisterAppchain{}) + suite.Run(t, &Interchain{}) + suite.Run(t, &Role{}) + suite.Run(t, &Gateway{}) +} + +func grpcAddresses() []string { + return []string{ + "localhost:60011", + "localhost:60012", + "localhost:60013", + "localhost:60014", + } +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..963310a --- /dev/null +++ b/version.go @@ -0,0 +1,21 @@ +package bitxhub + +import ( + "fmt" + "runtime" +) + +var ( + // CurrentCommit current git commit hash + CurrentCommit = "" + // CurrentBranch current git branch + CurrentBranch = "" + // CurrentVersion current project version + CurrentVersion = "0.0.0" + // BuildDate compile date + BuildDate = "" + // GoVersion system go version + GoVersion = runtime.Version() + // Platform info + Platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) +)