feat(*): init project

This commit is contained in:
Aiden X 2020-03-29 21:32:01 +08:00
parent eca7a1caef
commit d5b4071447
216 changed files with 16307 additions and 1 deletions

23
.gitignore vendored Normal file
View File

@ -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

201
LICENSE Normal file
View File

@ -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.

73
Makefile Normal file
View File

@ -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

View File

@ -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) [Whitepaper](https://upload.hyperchain.cn/bitxhub_whitepaper.pdf) | [白皮书](https://upload.hyperchain.cn/BitXHub%E7%99%BD%E7%9A%AE%E4%B9%A6.pdf)
## License

40
api/gateway/gateway.go Normal file
View File

@ -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))
}

46
api/grpc/account.go Normal file
View File

@ -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
}

83
api/grpc/block.go Normal file
View File

@ -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
}

67
api/grpc/broker.go Normal file
View File

@ -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
}

17
api/grpc/chain.go Normal file
View File

@ -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
}

18
api/grpc/network.go Normal file
View File

@ -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
}

14
api/grpc/receipt.go Normal file
View File

@ -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)
}

167
api/grpc/subscribe.go Normal file
View File

@ -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
}

106
api/grpc/transaction.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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),
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

65
cmd/bitxhub/config.go Normal file
View File

@ -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
}

47
cmd/bitxhub/init.go Executable file
View File

@ -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)
}

169
cmd/bitxhub/key.go Executable file
View File

@ -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
}

43
cmd/bitxhub/main.go Normal file
View File

@ -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)
}
}

142
cmd/bitxhub/start.go Executable file
View File

@ -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)
}
}()
}

17
cmd/bitxhub/version.go Normal file
View File

@ -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
}

1
config/api Normal file
View File

@ -0,0 +1 @@
http://localhost:9091/v1/

41
config/bitxhub.toml Executable file
View File

@ -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"
]

18
config/network.toml Normal file
View File

@ -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

11
config/order.toml Normal file
View File

@ -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.

BIN
docs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

59
go.mod Normal file
View File

@ -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

926
go.sum Normal file
View File

@ -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=

189
internal/app/bitxhub.go Normal file
View File

@ -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
}
}
}

53
internal/app/feedhub.go Normal file
View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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()
}

22
internal/coreapi/chain.go Normal file
View File

@ -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
}

View File

@ -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)
}

15
internal/coreapi/feed.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}

143
internal/executor/executor.go Executable file
View File

@ -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)
}

View File

@ -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(),
}
}

323
internal/executor/handle.go Executable file
View File

@ -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
}

5
internal/executor/testdata/key.json vendored Executable file
View File

@ -0,0 +1,5 @@
{
"address": "0xba30d0dd7876318da4515826a1f8bee8cefc9061",
"private_key": "3592c476850c4007385fe4084039d29bcc160652f5a41e83bb89ebdd1432ece4746545ffbdb7269a9df8aad2a6e33f97",
"encrypted": true
}

View File

@ -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
}

153
internal/ledger/account.go Normal file
View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

17
internal/ledger/key.go Normal file
View File

@ -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))...)
}

View File

@ -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"))
}

148
internal/ledger/ledger.go Normal file
View File

@ -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()
}

View File

@ -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())
}

View File

@ -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())
}

102
internal/ledger/types.go Normal file
View File

@ -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)
}

View File

@ -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]
}

19
internal/model/events/events.go Executable file
View File

@ -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
}

29
internal/model/model.go Normal file
View File

@ -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)
}

View File

@ -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)
}

18
internal/plugins/Makefile Normal file
View File

@ -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>)
raft:
@mkdir -p build
$(GO) build --buildmode=plugin -o build/raft.so order/etcdraft/*.go
## make raft: build plugin (make plugin type= <solo>)
solo:
@mkdir -p build
$(GO) build --buildmode=plugin -o build/solo.so order/solo/*.go
## make plugins: build plugins (make plugin type=<raft> and <solo>)
plugins: raft solo

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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")
)

View File

@ -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;
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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...)
}

58
internal/repo/cert.go Normal file
View File

@ -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
}

190
internal/repo/config.go Executable file
View File

@ -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
}

View File

@ -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)
}
}

39
internal/repo/init.go Normal file
View File

@ -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))
}

116
internal/repo/key.go Normal file
View File

@ -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
}

View File

@ -0,0 +1 @@
package repo

78
internal/repo/network.go Normal file
View File

@ -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
}

65
internal/repo/repo.go Executable file
View File

@ -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
}

View File

@ -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")
}

1
internal/repo/testdata/api vendored Normal file
View File

@ -0,0 +1 @@
http://localhost:9091/v1/

36
internal/repo/testdata/bitxhub.toml vendored Executable file
View File

@ -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"
]

View File

@ -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-----

16
internal/repo/testdata/certs/ca.cert vendored Normal file
View File

@ -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-----

17
internal/repo/testdata/certs/node.cert vendored Normal file
View File

@ -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-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKnfYqPIMB1JHPCEA8P5qX/9sJR92LKtiB8i3xAnnfz/oAoGCCqGSM49
AwEHoUQDQgAET5MTfp/rudlnnVlTqFWaW9mSVkW/JY2SWTVkvgDIFJk4OWkAFNcW
qThuDBVHkbo09DplyDGN7MPq6KnBvA6MZg==
-----END EC PRIVATE KEY-----

Some files were not shown because too many files have changed in this diff Show More