diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..5ac863b890861a98141a3b0b263e386c7f5b7be7 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,92 @@ +# Some resuable sections, note the top-level keys 'defaults' and 'tag_filters' +# have no special meaning, they just exist so I can alias them and import them +# in later blocks +defaults: &defaults + working_directory: /go/src/github.com/hyperledger/burrow + docker: + - image: circleci/golang:1.8.1 + +tag_filters: &tags_filters + tags: + only: /^v[0-9]+\.[0-9]+\.[0-9]+$/ + +version: 2 +jobs: + checkout_code: + <<: *defaults + steps: + - checkout + - run: go get github.com/Masterminds/glide + - run: glide install + + # Just persist the entire working dir (burrow checkout) + - persist_to_workspace: + root: . + paths: + - . + test: + <<: *defaults + steps: + - attach_workspace: + at: . + - run: make test + + test_integration: + <<: *defaults + steps: + - attach_workspace: + at: . + - run: make test_integration + + release: + <<: *defaults + steps: + # restore checkout + - attach_workspace: + at: . + # This allows us to perform our docker builds + - setup_remote_docker: + version: 17.06.1-ce + - run: docker login -u $DOCKER_USER -p $DOCKER_PASS quay.io + # build docker image and tag the image with the version, date, and commit hash + - run: make build_docker_db + - run: docker push quay.io/monax/db + + +workflows: + version: 2 + + test_and_release: + jobs: + - checkout_code: + # Rather annoyingly we need this boilerplate on all transitive + # dependencies if we want the deploy job to build against a version + # tag. + # Also note jobs build against all branches by default + filters: + <<: *tags_filters + - test: + requires: + - checkout_code + filters: + <<: *tags_filters + + - test_integration: + requires: + - checkout_code + filters: + <<: *tags_filters + + - release: + requires: + - test + - test_integration + filters: + <<: *tags_filters + branches: + # Although we seem to exclude the master branch below, since + # matching on tags is independent we will still build tags that + # happen to point to a commit on master + # We push dev pre-release images for every commit on develop + only: develop + diff --git a/.dockerignore b/.dockerignore index 7ab5de766b88dfef5e626f418ea883cbe3374bc5..9a1e1647f0dc47d7ebfe254c583d272b863513af 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,14 @@ +.github .git +.gitignore .project run.sh -build +build_tool.sh +Makefile Vagrantfile +Dockerfile +CHANGELOG.md README.md -circle.yml -api.md +.circleci +docs vendor \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 035777600c2676816fab86762e4ac067f2b2f2f1..6a78b5febf846c4d6823bc4a8b07e9784de5a677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,19 @@ # burrow changelog +## v0.17.0 +This is a service release with some significant ethereum/solidity compatibility improvements and new logging features. It includes: + +- [Upgrade to use Tendermint v0.9.2](https://github.com/hyperledger/burrow/pull/595) +- [Implemented dynamic memory](https://github.com/hyperledger/burrow/pull/607) assumed by the EVM bytecode produce by solidity, fixing various issues. +- Logging sinks and configuration - providing a flexible mechanism for configuring log flows and outputs see [logging section in readme](https://github.com/hyperledger/burrow#logging). Various other logging enhancements. +- Fix event unsubscription +- Remove module-specific versioning +- Rename suicide to selfdestruct +- SNative tweaks + +Known issues: + +- SELFDESTRUCT opcode causes a panic when an account is removed. A [fix](https://github.com/hyperledger/burrow/pull/605) was produced but was [reverted](https://github.com/hyperledger/burrow/pull/636) pending investigation of a possible regression. + ## v0.16.3 This release adds an stop-gap fix to the `Transact` method so that it never transfers value with the `CallTx` is generates. diff --git a/Dockerfile b/Dockerfile index e75eb594b4a6ce7fc19ecddc03bc1a585071de97..033acdbb92b2a466c2261bebbd89331cdbdd2c8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,38 @@ -FROM quay.io/monax/build:0.16 +# We use a multistage build to avoid bloating our deployment image with build dependencies +FROM golang:1.9.0-alpine3.6 as builder MAINTAINER Monax <support@monax.io> -ENV TARGET burrow -ENV REPO $GOPATH/src/github.com/hyperledger/$TARGET +RUN apk add --no-cache --update git +RUN go get github.com/Masterminds/glide -ADD ./glide.yaml $REPO/ -ADD ./glide.lock $REPO/ +ENV REPO $GOPATH/src/github.com/hyperledger/burrow +COPY . $REPO WORKDIR $REPO RUN glide install -COPY . $REPO/. -RUN cd $REPO/cmd/$TARGET && \ - go build --ldflags '-extldflags "-static"' -o $INSTALL_BASE/$TARGET +# Build purely static binaries +RUN go build --ldflags '-extldflags "-static"' -o bin/burrow ./cmd/burrow +RUN go build --ldflags '-extldflags "-static"' -o bin/burrow-client ./client/cmd/burrow-client -# build customizations start here -RUN cd $REPO/client/cmd/burrow-client && \ - go build --ldflags '-extldflags "-static"' -o $INSTALL_BASE/burrow-client +# This will be our base container image +FROM alpine:3.6 + +# There does not appear to be a way to share environment variables between stages +ENV REPO /go/src/github.com/hyperledger/burrow + +ENV USER monax +ENV MONAX_PATH /home/$USER/.monax +RUN addgroup -g 101 -S $USER && adduser -S -D -u 1000 $USER $USER +VOLUME $MONAX_PATH +WORKDIR $MONAX_PATH +USER $USER:$USER + +# Copy binaries built in previous stage +COPY --from=builder $REPO/bin/* /usr/local/bin/ + +# Expose ports for 1337:burrow API; 46656:tendermint-peer; 46657:tendermint-rpc +EXPOSE 1337 +EXPOSE 46656 +EXPOSE 46657 + +CMD [ "burrow", "serve" ] diff --git a/Dockerfile.deploy b/Dockerfile.deploy deleted file mode 100644 index 8df703505fa669f198dcd8e4675718d5bd1480c7..0000000000000000000000000000000000000000 --- a/Dockerfile.deploy +++ /dev/null @@ -1,23 +0,0 @@ -FROM quay.io/monax/base:0.16 -MAINTAINER Monax <support@monax.io> - -ENV TARGET burrow - - -# Get the binary from the artefact in pwd/target/docker -COPY ./target/docker/"$TARGET".dockerartefact $INSTALL_BASE/$TARGET -RUN chmod +x --recursive $INSTALL_BASE - -# Finalize -RUN chown --recursive $USER:$USER /home/$USER -VOLUME $MONAX_PATH -WORKDIR $MONAX_PATH -USER $USER - -# runtime customization start here -# Expose ports for 1337:burrow API; 46656:tendermint-peer; 46657:tendermint-rpc -EXPOSE 1337 -EXPOSE 46656 -EXPOSE 46657 - -CMD [ "burrow", "serve" ] diff --git a/Makefile b/Makefile index 06b6400be4523665c4c994f11443c4eaf7299a65..91b8dfc77c9d444fd19a8451e53cc025b19b8af4 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ SHELL := /bin/bash REPO := $(shell pwd) GOFILES_NOVENDOR := $(shell find ${REPO} -type f -name '*.go' -not -path "${REPO}/vendor/*") PACKAGES_NOVENDOR := $(shell go list github.com/hyperledger/burrow/... | grep -v /vendor/) -VERSION := $(shell cat ${REPO}/version/version.go | tail -n 1 | cut -d \ -f 4 | tr -d '"') +VERSION := $(shell go run ./util/version/cmd/main.go) VERSION_MIN := $(shell echo ${VERSION} | cut -d . -f 1-2) COMMIT_SHA := $(shell echo `git rev-parse --short --verify HEAD`) @@ -21,6 +21,10 @@ DOCKER_NAMESPACE := quay.io/monax greet: @echo "Hi! I'm the marmot that will help you with burrow v${VERSION}" +.PHONY: version +version: + @echo "${VERSION}" + ### Formatting, linting and vetting # check the code for style standards; currently enforces go formatting. @@ -31,6 +35,11 @@ check: @gofmt -l -d ${GOFILES_NOVENDOR} @gofmt -l ${GOFILES_NOVENDOR} | read && echo && echo "Your marmot has found a problem with the formatting style of the code." 1>&2 && exit 1 || true +# Just fix it +.PHONY: fix +fix: + @goimports -l -w ${GOFILES_NOVENDOR} + # fmt runs gofmt -w on the code, modifying any files that do not match # the style guide. .PHONY: fmt @@ -54,6 +63,12 @@ vet: @echo "Running go vet." @go vet ${PACKAGES_NOVENDOR} +# run the megacheck tool for code compliance +.PHONY: megacheck +megacheck: + @go get honnef.co/go/tools/cmd/megacheck + @for pkg in ${PACKAGES_NOVENDOR}; do megacheck "$$pkg"; done + ### Dependency management for github.com/hyperledger/burrow # erase vendor wipes the full vendor directory @@ -67,12 +82,6 @@ install_vendor: go get github.com/Masterminds/glide glide install -# hell runs utility tool hell to selectively update glide dependencies -.PHONY: hell -hell: - go build -o ${REPO}/target/hell ./util/hell/cmd/hell/main.go - ./target/hell $(filter-out $@,$(MAKECMDGOALS)) - # Dumps Solidity interface contracts for SNatives .PHONY: snatives snatives: @@ -112,8 +121,12 @@ build_race_client: # test burrow .PHONY: test -test: build - @go test ${PACKAGES_NOVENDOR} -tags integration +test: + @go test ${PACKAGES_NOVENDOR} + +.PHONY: test_integration +test_integration: + @go test ./rpc/tendermint/test -tags integration # test burrow with checks for race conditions .PHONY: test_race @@ -125,23 +138,7 @@ test_race: build_race # build docker image for burrow .PHONY: build_docker_db build_docker_db: check - @mkdir -p ${REPO}/target/docker - docker build -t ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} ${REPO} - docker run --rm --entrypoint cat ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} /usr/local/bin/burrow > ${REPO}/target/docker/burrow.dockerartefact - docker run --rm --entrypoint cat ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} /usr/local/bin/burrow-client > ${REPO}/target/docker/burrow-client.dockerartefact - docker build -t ${DOCKER_NAMESPACE}/db:${VERSION} -f Dockerfile.deploy ${REPO} - - @rm ${REPO}/target/docker/burrow.dockerartefact - @rm ${REPO}/target/docker/burrow-client.dockerartefact - docker rmi ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} - -### Test docker images for github.com/hyperledger/burrow - -# test docker image for burrow -.PHONY: test_docker_db -test_docker_db: check - docker build -t ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} ${REPO} - docker run ${DOCKER_NAMESPACE}/db:build-${COMMIT_SHA} glide nv | xargs go test -tags integration + @./build_tool.sh ### Clean up diff --git a/README.md b/README.md index c6d75accaeddf20eff237b8b853f3830fe54bce6..f5746bc543779c51780aea177be226de94e0f3d1 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,46 @@ Once the server has started, it will begin syncing up with the network. At that A commented template config will be written as part of the `monax chains make` [process](https://monax.io/docs/getting-started) and can be edited prior to the `monax chains start` [process](https://monax.io/docs/getting-started). +### Logging +Logging is highly configurable through the `config.toml` `[logging]` section. Each log line is a list of key-value pairs that flows from the root sink through possible child sinks. Each sink can have an output, a transform, and sinks that it outputs to. Below is a more involved example of than the one appearing in the default generated config of what you can configure: + +```toml +# This is a top level config section within the main Burrow config +[logging] + # All log lines are sent to the root sink from all sources + [logging.root_sink] + # We define two child sinks that each receive all log lines + [[logging.root_sink.sinks]] + # We send all output to stderr + [logging.root_sink.sinks.output] + output_type = "stderr" + + [[logging.root_sink.sinks]] + # But for the second sink we define a transform that filters log lines from Tendermint's p2p module + [logging.root_sink.sinks.transform] + transform_type = "filter" + filter_mode = "exclude_when_all_match" + + [[logging.root_sink.sinks.transform.predicates]] + key_regex = "module" + value_regex = "p2p" + + [[logging.root_sink.sinks.transform.predicates]] + key_regex = "captured_logging_source" + value_regex = "tendermint_log15" + + # The child sinks of this filter transform sink are syslog and file and will omit log lines originating from p2p + [[logging.root_sink.sinks.sinks]] + [logging.root_sink.sinks.sinks.output] + output_type = "syslog" + url = "" + tag = "Burrow-network" + + [[logging.root_sink.sinks.sinks]] + [logging.root_sink.sinks.sinks.output] + output_type = "file" + path = "/var/log/burrow-network.log" +``` ## Contribute We welcome all contributions and have submitted the code base to the Hyperledger project governance during incubation phase. As an integral part of this effort we want to invite new contributors, not just to maintain but also to steer the future direction of the code in an active and open process. diff --git a/blockchain/filter.go b/blockchain/filter.go index c61f77fb1c7803ef3ef03f3e51f5cea4616804ec..d0780f90e8773bae11db67b6d7580a8d1e8fc44b 100644 --- a/blockchain/filter.go +++ b/blockchain/filter.go @@ -70,7 +70,7 @@ func FilterBlocks(blockchain blockchain_types.Blockchain, // Optimization. Break any height filters out. Messy but makes sure we don't // fetch more blocks then necessary. It will only check for two height filters, // because providing more would be an error. - if filterData == nil || len(filterData) == 0 { + if len(filterData) == 0 { minHeight = 0 maxHeight = height } else { @@ -178,29 +178,24 @@ func getHeightMinMax(fda []*event.FilterData, height int) (int, int, []*event.Fi } min = val max = val - break case "<": mx := val - 1 if mx > min && mx < max { max = mx } - break case "<=": if val > min && val < max { max = val } - break case ">": mn := val + 1 if mn < max && mn > min { min = mn } - break case ">=": if val < max && val > min { min = val } - break default: return 0, 0, nil, fmt.Errorf("Operator not supported") } @@ -214,10 +209,3 @@ func getHeightMinMax(fda []*event.FilterData, height int) (int, int, []*event.Fi } return min, max, fda, nil } - -func min(x, y int) int { - if x > y { - return y - } - return x -} diff --git a/build_tool.sh b/build_tool.sh new file mode 100755 index 0000000000000000000000000000000000000000..d9e2570e14d42d221ff46865409c60406996f404 --- /dev/null +++ b/build_tool.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# ---------------------------------------------------------- +# PURPOSE + +# This is the build script for the Monax stack. It will +# build the tool into docker containers in a reliable and +# predictable manner. + +# ---------------------------------------------------------- +# REQUIREMENTS +# +# docker, go, make, and git installed locally + +# ---------------------------------------------------------- +# USAGE + +# build_tool.sh [version tag] + +# ---------------------------------------------------------- + +set -e + +IMAGE=${IMAGE:-"quay.io/monax/db"} +VERSION_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" + +version=$(go run ./util/version/cmd/main.go) +tag=$(git tag --points-at HEAD) + +if [[ ${tag} =~ ${VERSION_REGEX} ]] ; then + # Only label a build as a release version when the commit is tagged + echo "Building release version (tagged $tag)..." + # Fail noisily when trying to build a release version that does not match code tag + if [[ ! ${tag} = "v$version" ]]; then + echo "Build failure: version tag $tag does not match version/version.go version $version" + exit 1 + fi +else + date=$(date +"%Y%m%d") + commit=$(git rev-parse --short HEAD) + version="$version-dev-$date-$commit" + echo "Building non-release version $version..." +fi + +if [[ "$1" ]] ; then + # If argument provided, use it as the version tag + echo "Overriding detected version $version and tagging image as $1" + version="$1" +fi + +docker build -t ${IMAGE}:${version} . + diff --git a/circle.yml b/circle.yml deleted file mode 100644 index a29094caaa338788f315162c7517d382f3e74b78..0000000000000000000000000000000000000000 --- a/circle.yml +++ /dev/null @@ -1,73 +0,0 @@ -machine: - environment: - GOPATH: $HOME/.go_workspace - REPO: ${GOPATH}/src/github.com/hyperledger/burrow - GO15VENDOREXPERIMENT: 1 - pre: - - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0 - services: - - docker - post: - - git config --global user.email "billings@monax.io" - - git config --global user.name "Billings the Bot" - - rm -rf ${GOPATH%%:*}/src/github.com/hyperledger - - mkdir -p ${GOPATH%%:*}/src/github.com/hyperledger - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME} ${GOPATH%%:*}/src/github.com/hyperledger/. - -dependencies: - override: - - sudo apt-get update && sudo apt-get install -y libgmp3-dev - - sudo apt-get install jq curl && go get github.com/Masterminds/glide - -test: - pre: - - cd $REPO && glide install - # Test the build target for burrow - - echo "Build target burrow..." && cd $REPO && go install ./cmd/burrow && burrow --help - # Test the build target for burrow-client - - echo "Build target burrow-client..." && cd $REPO && go install ./client/cmd/burrow-client && burrow-client --help - override: - # We only wish to test our packages not vendored ones - - echo "Running unit tests..." && cd $REPO && glide novendor | xargs go test -tags integration - # - echo "Running integration tests..." && cd $REPO && "tests/circle_test.sh" # | tee $CIRCLE_ARTIFACTS/output.log; test ${PIPESTATUS[0]} -eq 0" - -deployment: - release-0.12: - branch: release-0.12 - commands: - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io - # build docker image and tag the image with the version - - tests/build_tool.sh - - docker push quay.io/monax/db - release-0.16: - branch: release-0.16 - commands: - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io - # build docker image and tag the image with the version - - tests/build_tool.sh - - docker push quay.io/monax/db - develop: - branch: develop - commands: - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io - # build docker image and tag the image with the version - - tests/build_tool.sh - - docker push quay.io/monax/db - master: - branch: master - commands: - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io - # build docker image and tag the image with ':latest' - # builds on master are considered immutable so we do not push the version - # tag to allow for hotfixes - - tests/build_tool.sh latest - - docker push quay.io/monax/db - tagged-releases: - tag: /v[0-9]+(\.[0-9]+)*/ - commands: - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io - # build docker image and tag the image with the version; - # once the commit is tagged the docker image for this version tag is - # considered immutable. - - tests/build_tool.sh - - docker push quay.io/monax/db diff --git a/client/cmd/burrow-client.go b/client/cmd/burrow-client.go index 788d7fd16b727cf0eb9b2878486c4c32494114a3..77f746a38f4cee038f141e265f1c9ebbeacb0857 100644 --- a/client/cmd/burrow-client.go +++ b/client/cmd/burrow-client.go @@ -17,7 +17,6 @@ package commands import ( "os" "strconv" - "strings" "github.com/spf13/cobra" @@ -100,11 +99,3 @@ func setDefaultString(envVar, def string) string { } return def } - -func setDefaultStringSlice(envVar string, def []string) []string { - env := os.Getenv(envVar) - if env != "" { - return strings.Split(env, ",") - } - return def -} diff --git a/client/methods/helpers.go b/client/methods/helpers.go index 15b0a4fee09ac67b9f63d5c6e9a3d844de154cac..2e8bc6fcaddb11eac66c55f8992039a9beec8feb 100644 --- a/client/methods/helpers.go +++ b/client/methods/helpers.go @@ -20,10 +20,10 @@ import ( "github.com/hyperledger/burrow/definitions" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" ) -func unpackSignAndBroadcast(result *rpc.TxResult, logger loggers.InfoTraceLogger) { +func unpackSignAndBroadcast(result *rpc.TxResult, logger logging_types.InfoTraceLogger) { if result == nil { // if we don't provide --sign or --broadcast return @@ -45,10 +45,17 @@ func unpackSignAndBroadcast(result *rpc.TxResult, logger loggers.InfoTraceLogger logging.InfoMsg(logger, "SignAndBroadcast result") } -func loggerFromClientDo(do *definitions.ClientDo, scope string) (loggers.InfoTraceLogger, error) { +func loggerFromClientDo(do *definitions.ClientDo, scope string) (logging_types.InfoTraceLogger, error) { lc, err := core.LoadLoggingConfigFromClientDo(do) if err != nil { return nil, err } - return logging.WithScope(lifecycle.NewLoggerFromLoggingConfig(lc), scope), nil + logger, err := lifecycle.NewLoggerFromLoggingConfig(lc) + if err != nil { + return nil, err + } + logger = logging.WithScope(logger, scope) + lifecycle.CaptureStdlibLogOutput(logger) + lifecycle.CaptureTendermintLog15Output(logger) + return logger, nil } diff --git a/client/mock/client_mock.go b/client/mock/client_mock.go index 08ba2641c625a6925d41d3981e2b65148c7dd422..c6a0d12b8bb5f95a9bf98e1c1b5c28a1c53e02af 100644 --- a/client/mock/client_mock.go +++ b/client/mock/client_mock.go @@ -22,6 +22,7 @@ import ( consensus_types "github.com/hyperledger/burrow/consensus/types" core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/txs" ) @@ -40,9 +41,9 @@ func NewMockNodeClient() *MockNodeClient { func (mock *MockNodeClient) Broadcast(transaction txs.Tx) (*txs.Receipt, error) { // make zero transaction receipt txReceipt := &txs.Receipt{ - TxHash: make([]byte, 20, 20), + TxHash: make([]byte, 20), CreatesContract: 0, - ContractAddr: make([]byte, 20, 20), + ContractAddr: make([]byte, 20), } return txReceipt, nil } @@ -54,7 +55,7 @@ func (mock *MockNodeClient) DeriveWebsocketClient() (nodeWsClient NodeWebsocketC func (mock *MockNodeClient) GetAccount(address []byte) (*acc.Account, error) { // make zero account var zero [32]byte - copyAddressBytes := make([]byte, len(address), len(address)) + copyAddressBytes := make([]byte, len(address)) copy(copyAddressBytes, address) account := &acc.Account{ Address: copyAddressBytes, @@ -115,6 +116,6 @@ func (mock *MockNodeClient) ListValidators() (blockHeight int, bondedValidators, return 0, nil, nil, nil } -func (mock *MockNodeClient) Logger() loggers.InfoTraceLogger { +func (mock *MockNodeClient) Logger() logging_types.InfoTraceLogger { return loggers.NewNoopInfoTraceLogger() } diff --git a/client/node_client.go b/client/node_client.go index 775e29fda8eeb33ae83868cf22436166e94b6de7..a7d49a14568f92c5d19cf07e28ba93f921da2b2b 100644 --- a/client/node_client.go +++ b/client/node_client.go @@ -24,7 +24,7 @@ import ( consensus_types "github.com/hyperledger/burrow/consensus/types" core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" tendermint_client "github.com/hyperledger/burrow/rpc/tendermint/client" tendermint_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" "github.com/hyperledger/burrow/txs" @@ -46,7 +46,7 @@ type NodeClient interface { ListValidators() (blockHeight int, bondedValidators, unbondingValidators []consensus_types.Validator, err error) // Logging context for this NodeClient - Logger() loggers.InfoTraceLogger + Logger() logging_types.InfoTraceLogger } type NodeWebsocketClient interface { @@ -64,12 +64,12 @@ var _ NodeClient = (*burrowNodeClient)(nil) // burrow-client is a simple struct exposing the client rpc methods type burrowNodeClient struct { broadcastRPC string - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // BurrowKeyClient.New returns a new monax-keys client for provided rpc location // Monax-keys connects over http request-responses -func NewBurrowNodeClient(rpcString string, logger loggers.InfoTraceLogger) *burrowNodeClient { +func NewBurrowNodeClient(rpcString string, logger logging_types.InfoTraceLogger) *burrowNodeClient { return &burrowNodeClient{ broadcastRPC: rpcString, logger: logging.WithScope(logger, "BurrowNodeClient"), @@ -87,7 +87,7 @@ func init() { // broadcast to blockchain node func (burrowNodeClient *burrowNodeClient) Broadcast(tx txs.Tx) (*txs.Receipt, error) { - client := rpcclient.NewClientURI(burrowNodeClient.broadcastRPC) + client := rpcclient.NewURIClient(burrowNodeClient.broadcastRPC) receipt, err := tendermint_client.BroadcastTx(client, tx) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (burrowNodeClient *burrowNodeClient) DeriveWebsocketClient() (nodeWsClient // Status returns the ChainId (GenesisHash), validator's PublicKey, latest block hash // the block height and the latest block time. func (burrowNodeClient *burrowNodeClient) Status() (GenesisHash []byte, ValidatorPublicKey []byte, LatestBlockHash []byte, LatestBlockHeight int, LatestBlockTime int64, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) res, err := tendermint_client.Status(client) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get status: %s", @@ -152,7 +152,7 @@ func (burrowNodeClient *burrowNodeClient) Status() (GenesisHash []byte, Validato } func (burrowNodeClient *burrowNodeClient) ChainId() (ChainName, ChainId string, GenesisHash []byte, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) chainIdResult, err := tendermint_client.ChainId(client) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get chain id: %s", @@ -170,11 +170,11 @@ func (burrowNodeClient *burrowNodeClient) ChainId() (ChainName, ChainId string, // QueryContract executes the contract code at address with the given data // NOTE: there is no check on the caller; func (burrowNodeClient *burrowNodeClient) QueryContract(callerAddress, calleeAddress, data []byte) (ret []byte, gasUsed int64, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) callResult, err := tendermint_client.Call(client, callerAddress, calleeAddress, data) if err != nil { - err = fmt.Errorf("Error connnecting to node (%s) to query contract at (%X) with data (%X)", - burrowNodeClient.broadcastRPC, calleeAddress, data, err.Error()) + err = fmt.Errorf("Error (%v) connnecting to node (%s) to query contract at (%X) with data (%X)", + err.Error(), burrowNodeClient.broadcastRPC, calleeAddress, data) return nil, int64(0), err } return callResult.Return, callResult.GasUsed, nil @@ -182,7 +182,7 @@ func (burrowNodeClient *burrowNodeClient) QueryContract(callerAddress, calleeAdd // QueryContractCode executes the contract code at address with the given data but with provided code func (burrowNodeClient *burrowNodeClient) QueryContractCode(address, code, data []byte) (ret []byte, gasUsed int64, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) // TODO: [ben] Call and CallCode have an inconsistent signature; it makes sense for both to only // have a single address that is the contract to query. callResult, err := tendermint_client.CallCode(client, address, code, data) @@ -196,7 +196,7 @@ func (burrowNodeClient *burrowNodeClient) QueryContractCode(address, code, data // GetAccount returns a copy of the account func (burrowNodeClient *burrowNodeClient) GetAccount(address []byte) (*acc.Account, error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) account, err := tendermint_client.GetAccount(client, address) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to fetch account (%X): %s", @@ -213,7 +213,7 @@ func (burrowNodeClient *burrowNodeClient) GetAccount(address []byte) (*acc.Accou // DumpStorage returns the full storage for an account. func (burrowNodeClient *burrowNodeClient) DumpStorage(address []byte) (storage *core_types.Storage, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) resultStorage, err := tendermint_client.DumpStorage(client, address) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get storage for account (%X): %s", @@ -231,7 +231,7 @@ func (burrowNodeClient *burrowNodeClient) DumpStorage(address []byte) (storage * // Name registry func (burrowNodeClient *burrowNodeClient) GetName(name string) (owner []byte, data string, expirationBlock int, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) entryResult, err := tendermint_client.GetName(client, name) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get name registrar entry for name (%s)", @@ -249,7 +249,7 @@ func (burrowNodeClient *burrowNodeClient) GetName(name string) (owner []byte, da func (burrowNodeClient *burrowNodeClient) ListValidators() (blockHeight int, bondedValidators []consensus_types.Validator, unbondingValidators []consensus_types.Validator, err error) { - client := rpcclient.NewClientJSONRPC(burrowNodeClient.broadcastRPC) + client := rpcclient.NewJSONRPCClient(burrowNodeClient.broadcastRPC) validatorsResult, err := tendermint_client.ListValidators(client) if err != nil { err = fmt.Errorf("Error connecting to node (%s) to get validators", @@ -263,6 +263,6 @@ func (burrowNodeClient *burrowNodeClient) ListValidators() (blockHeight int, return } -func (burrowNodeClient *burrowNodeClient) Logger() loggers.InfoTraceLogger { +func (burrowNodeClient *burrowNodeClient) Logger() logging_types.InfoTraceLogger { return burrowNodeClient.logger } diff --git a/client/rpc/client_test.go b/client/rpc/client_test.go index a805b99bc8f2040f2cda854554cf132a6adbabb5..603fe793fe2f7e32e9c355f8c5150f797428283c 100644 --- a/client/rpc/client_test.go +++ b/client/rpc/client_test.go @@ -117,7 +117,7 @@ func testName(t *testing.T, // set data dataString := fmt.Sprintf("%X", "We are DOUG.") // set name - nameString := fmt.Sprintf("%s", "DOUG") + nameString := "DOUG" _, err := Name(nodeClient, keyClient, publicKeyString, addressString, amountString, nonceString, feeString, nameString, dataString) diff --git a/client/websocket_client.go b/client/websocket_client.go index 2c84ecba54dcc629d1439e8359bae948d60ebb08..428f78fd66498efb40cdf380c5f71bf0f0eb1870 100644 --- a/client/websocket_client.go +++ b/client/websocket_client.go @@ -19,11 +19,12 @@ import ( "fmt" "time" + logging_types "github.com/hyperledger/burrow/logging/types" "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + tendermint_client "github.com/hyperledger/burrow/rpc/tendermint/client" ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" "github.com/hyperledger/burrow/txs" ) @@ -46,18 +47,20 @@ var _ NodeWebsocketClient = (*burrowNodeWebsocketClient)(nil) type burrowNodeWebsocketClient struct { // TODO: assert no memory leak on closing with open websocket tendermintWebsocket *rpcclient.WSClient - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // Subscribe to an eventid -func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) Subscribe(eventid string) error { +func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) Subscribe(eventId string) error { // TODO we can in the background listen to the subscription id and remember it to ease unsubscribing later. - return burrowNodeWebsocketClient.tendermintWebsocket.Subscribe(eventid) + return tendermint_client.Subscribe(burrowNodeWebsocketClient.tendermintWebsocket, + eventId) } // Unsubscribe from an eventid func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) Unsubscribe(subscriptionId string) error { - return burrowNodeWebsocketClient.tendermintWebsocket.Unsubscribe(subscriptionId) + return tendermint_client.Unsubscribe(burrowNodeWebsocketClient.tendermintWebsocket, + subscriptionId) } // Returns a channel that will receive a confirmation with a result or the exception that @@ -73,10 +76,10 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( var latestBlockHash []byte eid := txs.EventStringAccInput(inputAddr) - if err := burrowNodeWebsocketClient.tendermintWebsocket.Subscribe(eid); err != nil { + if err := burrowNodeWebsocketClient.Subscribe(eid); err != nil { return nil, fmt.Errorf("Error subscribing to AccInput event (%s): %v", eid, err) } - if err := burrowNodeWebsocketClient.tendermintWebsocket.Subscribe(txs.EventStringNewBlock()); err != nil { + if err := burrowNodeWebsocketClient.Subscribe(txs.EventStringNewBlock()); err != nil { return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err) } // Read the incoming events @@ -163,7 +166,7 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( confirmationChannel <- Confirmation{ BlockHash: latestBlockHash, Event: &data, - Exception: fmt.Errorf("Transaction confirmed with exception:", data.Exception), + Exception: fmt.Errorf("Transaction confirmed with exception: %v", data.Exception), Error: nil, } return @@ -192,7 +195,6 @@ func (burrowNodeWebsocketClient *burrowNodeWebsocketClient) WaitForConfirmation( Exception: nil, Error: fmt.Errorf("timed out waiting for event"), } - return }() return confirmationChannel, nil } diff --git a/cmd/burrow.go b/cmd/burrow.go index bfa3b264a22ecd1d1d2c30e9c57c49c6cd81d658..a29f3e1fee9ace78eedd4f81eeed42f086834d3b 100644 --- a/cmd/burrow.go +++ b/cmd/burrow.go @@ -17,7 +17,6 @@ package commands import ( "os" "strconv" - "strings" "github.com/spf13/cobra" @@ -92,11 +91,3 @@ func setDefaultString(envVar, def string) string { } return def } - -func setDefaultStringSlice(envVar string, def []string) []string { - env := os.Getenv(envVar) - if env != "" { - return strings.Split(env, ",") - } - return def -} diff --git a/cmd/serve.go b/cmd/serve.go index a0c65d4b56948b5ba632c20d683cc11878c7e5d0..7f951f3bfe0247fc8aa2890cbd0d8dd82f41e7d2 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -28,6 +28,7 @@ import ( vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" "github.com/hyperledger/burrow/util" + "github.com/hyperledger/burrow/config" "github.com/spf13/cobra" ) @@ -95,6 +96,11 @@ func NewCoreFromDo(do *definitions.Do) (*core.Core, error) { do.GenesisFile = path.Join(do.WorkDir, do.Config.GetString("chain.genesis_file")) + err := config.AssertConfigCompatibleWithRuntime(do.Config) + if err != nil { + return nil, err + } + if do.Config.GetString("chain.genesis_file") == "" { return nil, fmt.Errorf("The config value chain.genesis_file is empty, " + "but should be set to the location of the genesis.json file.") @@ -109,8 +115,12 @@ func NewCoreFromDo(do *definitions.Do) (*core.Core, error) { return nil, fmt.Errorf("Failed to load logging config: %s", err) } + logger, err := lifecycle.NewLoggerFromLoggingConfig(loggerConfig) + if err != nil { + return nil, fmt.Errorf("Failed to build logger from logging config: %s", err) + } // Create a root logger to pass through to dependencies - logger := logging.WithScope(lifecycle.NewLoggerFromLoggingConfig(loggerConfig), "Serve") + logger = logging.WithScope(logger, "Serve") // Capture all logging from tendermint/tendermint and tendermint/go-* // dependencies lifecycle.CaptureTendermintLog15Output(logger) @@ -143,8 +153,8 @@ func NewCoreFromDo(do *definitions.Do) (*core.Core, error) { } logging.Msg(logger, "Modules configured", - "consensusModule", consensusConfig.Version, - "applicationManager", managerConfig.Version) + "consensusModule", consensusConfig.Name, + "applicationManager", managerConfig.Name) return core.NewCore(do.ChainId, consensusConfig, managerConfig, logger) } @@ -171,7 +181,7 @@ func ServeRunner(do *definitions.Do) func(*cobra.Command, []string) { } if !do.DisableRpc { - serverConfig, err := core.LoadServerConfig(do) + serverConfig, err := core.LoadServerConfigFromDo(do) if err != nil { util.Fatalf("Failed to load server configuration: %s.", err) } @@ -188,6 +198,8 @@ func ServeRunner(do *definitions.Do) func(*cobra.Command, []string) { util.Fatalf("Failed to start Tendermint gateway") } <-serverProcess.StopEventChannel() + // Attempt graceful shutdown + newCore.Stop() } else { signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) diff --git a/config/config.go b/config/config.go index 098d1420015e8193beb771f5b9cedaabd81aed8b..24753b65ffa8d0dd9e88e4a42262198e979fc406 100644 --- a/config/config.go +++ b/config/config.go @@ -18,6 +18,10 @@ import ( "bytes" "fmt" "text/template" + + lconfig "github.com/hyperledger/burrow/logging/config" + "github.com/hyperledger/burrow/version" + "github.com/spf13/viper" ) type ConfigServiceGeneral struct { @@ -37,8 +41,6 @@ type ConfigChainGeneral struct { type ConfigChainModule struct { Name string - MajorVersion uint8 - MinorVersion uint8 ModuleRelativeRoot string } @@ -87,22 +89,23 @@ func GetConfigurationFileBytes(chainId, moniker, seeds string, chainImageName st ExportedPorts: exportedPortsString, ContainerEntrypoint: containerEntrypoint, } + + // We want to encode in the config file which Burrow version generated the config + burrowVersion := version.GetBurrowVersion() burrowChain := &ConfigChainGeneral{ AssertChainId: chainId, - BurrowMajorVersion: uint8(0), - BurrowMinorVersion: uint8(16), + BurrowMajorVersion: burrowVersion.MajorVersion, + BurrowMinorVersion: burrowVersion.MinorVersion, GenesisRelativePath: "genesis.json", } + chainConsensusModule := &ConfigChainModule{ Name: "tendermint", - MajorVersion: uint8(0), - MinorVersion: uint8(8), ModuleRelativeRoot: "tendermint", } + chainApplicationManagerModule := &ConfigChainModule{ Name: "burrowmint", - MajorVersion: uint8(0), - MinorVersion: uint8(16), ModuleRelativeRoot: "burrowmint", } tendermintModule := &ConfigTendermint{ @@ -170,9 +173,25 @@ func GetConfigurationFileBytes(chainId, moniker, seeds string, chainImageName st // write static section burrowmint buffer.WriteString(sectionBurrowMint) + buffer.WriteString(sectionLoggingHeader) + buffer.WriteString(lconfig.DefaultNodeLoggingConfig().RootTOMLString()) + return buffer.Bytes(), nil } +func AssertConfigCompatibleWithRuntime(conf *viper.Viper) error { + burrowVersion := version.GetBurrowVersion() + majorVersion := uint8(conf.GetInt(fmt.Sprintf("chain.%s", majorVersionKey))) + minorVersion := uint8(conf.GetInt(fmt.Sprintf("chain.%s", majorVersionKey))) + if burrowVersion.MajorVersion != majorVersion || + burrowVersion.MinorVersion != minorVersion { + fmt.Errorf("Runtime Burrow version %s is not compatible with "+ + "configuration file version: major=%s, minor=%s", + burrowVersion.GetVersionString(), majorVersion, minorVersion) + } + return nil +} + func GetExampleConfigFileBytes() ([]byte, error) { return GetConfigurationFileBytes( "simplechain", diff --git a/config/config_test.go b/config/config_test.go index 67da505fd3d9346eac7fb575cf747d9bebcfb598..a49d808491b63518d227fc4aa854e7695372df27 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -15,19 +15,14 @@ package config import ( - "bytes" "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) func TestGeneratedConfigIsUsable(t *testing.T) { bs, err := GetExampleConfigFileBytes() assert.NoError(t, err, "Should be able to create example config") - buf := bytes.NewBuffer(bs) - conf := viper.New() - viper.SetConfigType("toml") - err = conf.ReadConfig(buf) + _, err = ReadViperConfig(bs) assert.NoError(t, err, "Should be able to read example config into Viper") } diff --git a/config/module.go b/config/module.go index da0218c5ce47ea9b71f2b9d2b6a52883cb711a5d..8b8eb9456837096b4bc4bd91c39b06817bd3a8b9 100644 --- a/config/module.go +++ b/config/module.go @@ -23,7 +23,6 @@ import ( type ModuleConfig struct { Module string Name string - Version string WorkDir string DataDir string RootDir string diff --git a/config/templates.go b/config/templates.go index ef376b78a62522420253fac5a6b3c67f1a3b9eba..8a4e15fb8111f1ea1531d8b946d42b46bf76a51d 100644 --- a/config/templates.go +++ b/config/templates.go @@ -14,6 +14,8 @@ package config +import "fmt" + const headerCopyright = `# Copyright 2017 Monax Industries Limited # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,20 +58,22 @@ const sectionServiceDependencies = `[dependencies] services = [ "keys" ] ` +const majorVersionKey = "major_version" +const minorVersionKey = "minor_version" -const sectionChainGeneral = `[chain] +var sectionChainGeneral string = fmt.Sprintf(`[chain] # ChainId is a human-readable name to identify the chain. # This must correspond to the chain_id defined in the genesis file # and the assertion here provides a safe-guard on misconfiguring chains. assert_chain_id = "{{.AssertChainId}}" # semantic major and minor version -major_version = {{.BurrowMajorVersion}} -minor_version = {{.BurrowMinorVersion}} +%s = {{.BurrowMajorVersion}} +%s = {{.BurrowMinorVersion}} # genesis file, relative path is to burrow working directory genesis_file = "{{.GenesisRelativePath}}" -` +`, majorVersionKey, minorVersionKey) const separatorChainConsensus = ` ################################################################################ @@ -85,10 +89,6 @@ const sectionChainConsensus = ` [chain.consensus] # this will define the peer-to-peer consensus network; # accepted values are ("noops", "abci",) "tendermint" name = "{{.Name}}" - # version is the major and minor semantic version; - # the version will be asserted on - major_version = {{.MajorVersion}} - minor_version = {{.MinorVersion}} # relative path to consensus' module root folder relative_root = "{{.ModuleRelativeRoot}}" @@ -107,10 +107,6 @@ const sectionChainApplicationManager = ` [chain.manager] # application manager name defines the module to use for handling # the transactions. Supported names are "burrowmint" name = "{{.Name}}" - # version is the major and minor semantic version; - # the version will be asserted on - major_version = {{.MajorVersion}} - minor_version = {{.MinorVersion}} # relative path to application manager root folder relative_root = "{{.ModuleRelativeRoot}}" @@ -162,11 +158,6 @@ const sectionServers = `[servers] rpc_local_address = "0.0.0.0:46657" endpoint = "/websocket" - [servers.logging] - console_log_level = "info" - file_log_level = "warn" - log_file = "" - ` const separatorModules = ` @@ -185,7 +176,6 @@ const sectionTendermint = ` ################################################################################ ## ## Tendermint -## version 0.8 ## ## in-process execution of Tendermint consensus engine ## @@ -234,14 +224,18 @@ private_validator_file = "priv_validator.json" # genesis_file = "./data/tendermint/genesis.json" # skip_upnp = false # addrbook_file = "./data/tendermint/addrbook.json" + # addrbook_strict = true + # pex_reactor = false # priv_validator_file = "./data/tendermint/priv_validator.json" # db_dir = "./data/tendermint/data" # prof_laddr = "" # revision_file = "./data/tendermint/revision" - # cswal = "./data/tendermint/data/cswal" - # cswal_light = false + # cs_wal_file = "./data/tendermint/data/cs.wal/wal" + # cs_wal_light = false + # filter_peers = false # block_size = 10000 + # block_part_size = 65536 # disable_data_hash = false # timeout_propose = 3000 # timeout_propose_delta = 500 @@ -250,9 +244,11 @@ private_validator_file = "priv_validator.json" # timeout_precommit = 1000 # timeout_precommit_delta = 500 # timeout_commit = 1000 + # skip_timeout_commit = false # mempool_recheck = true # mempool_recheck_empty = true # mempool_broadcast = true + # mempool_wal_dir = "./data/tendermint/data/mempool.wal" [tendermint.configuration.p2p] # Switch config keys @@ -280,7 +276,6 @@ const sectionBurrowMint = ` ################################################################################ ## ## Burrow-Mint -## version 0.16 ## ## The original Ethereum virtual machine with IAVL merkle trees ## and tendermint/go-wire encoding @@ -296,3 +291,35 @@ db_backend = "leveldb" tendermint_host = "0.0.0.0:46657" ` + +// TODO: [Silas]: before next logging release (finalising this stuff and adding +// presets) add full documentation of transforms, outputs, and their available +// configuration options. +const sectionLoggingHeader = ` +################################################################################ +## +## System-wide logging configuration +## +## Log messages are sent to one of two 'channels': info or trace +## +## They are delivered on a single non-blocking stream to a 'root sink'. +## +## A sink may optionally define any of a 'transform', an 'output', and a list of +## downstream sinks. Log messages flow through a sink by first having that +## sink's transform applied and then writing to its output and downstream sinks. +## In this way the log messages can output can be finely controlled and sent to +## a multiple different outputs having been filtered, modified, augmented, or +## labelled. This can be used to give more relevant output or to drive external +## systems. +## +## +################################################################################ + +## A minimal logging config for multi-line, colourised terminal output would be: +# +# [logging] +# [logging.root_sink] +# [logging.root_sink.output] +# output_type = "stderr" +# format = "terminal" +` diff --git a/config/viper.go b/config/viper.go index cefd2f2a8f5ec3b0abc89dd83f0dafa0f6b60951..c59799a80210f0623a54eb674de0971307383290 100644 --- a/config/viper.go +++ b/config/viper.go @@ -17,13 +17,15 @@ package config import ( "fmt" + "bytes" + "github.com/spf13/viper" ) // Safely get the subtree from a viper config, returning an error if it could not // be obtained for any reason. func ViperSubConfig(conf *viper.Viper, configSubtreePath string) (subConfig *viper.Viper, err error) { - // Viper internally panics if `moduleName` contains an unallowed + // Viper internally panics if `moduleName` contains an disallowed // character (eg, a dash). defer func() { if r := recover(); r != nil { @@ -42,3 +44,15 @@ func ViperSubConfig(conf *viper.Viper, configSubtreePath string) (subConfig *vip } return subConfig, err } + +// Read in TOML Viper config from bytes +func ReadViperConfig(configBytes []byte) (*viper.Viper, error) { + buf := bytes.NewBuffer(configBytes) + conf := viper.New() + conf.SetConfigType("toml") + err := conf.ReadConfig(buf) + if err != nil { + return nil, err + } + return conf, nil +} diff --git a/consensus/config.go b/consensus/config.go deleted file mode 100644 index 1c197b6fc45d75ee50a378fa8f8bc8604b67d0f9..0000000000000000000000000000000000000000 --- a/consensus/config.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package consensus - -import ( - // noops "github.com/hyperledger/burrow/consensus/noops" - tendermint "github.com/hyperledger/burrow/consensus/tendermint" -) - -//------------------------------------------------------------------------------ -// Helper functions - -func AssertValidConsensusModule(name, minorVersionString string) bool { - switch name { - case "noops": - // noops should not have any external interfaces that can change - // over iterations - return true - case "tendermint": - return minorVersionString == tendermint.GetTendermintVersion().GetMinorVersionString() - case "bigchaindb": - // TODO: [ben] implement BigchainDB as consensus engine - return false - } - return false -} diff --git a/consensus/consensus.go b/consensus/consensus.go index f94dffb2ef64dc6907257c796dc0844b4861232f..d821f551476146abdedcf461a591c4e3c5605cb0 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -24,10 +24,18 @@ import ( func LoadConsensusEngineInPipe(moduleConfig *config.ModuleConfig, pipe definitions.Pipe) error { + + // Check interface-level compatibility + if !pipe.GetApplication().CompatibleConsensus(&tendermint.Tendermint{}) { + return fmt.Errorf("Manager Application %s it no compatible with "+ + "%s consensus", moduleConfig.Name, pipe.GetApplication()) + } + switch moduleConfig.Name { case "tendermint": + tmint, err := tendermint.NewTendermint(moduleConfig, pipe.GetApplication(), - pipe.Logger().With()) + pipe.Logger()) if err != nil { return fmt.Errorf("Failed to load Tendermint node: %v", err) } diff --git a/consensus/tendermint/config.go b/consensus/tendermint/config.go index 0ea0a1d546cbcb8e261de919fa03be5c3637ab66..b73a0b4f71ac424ba14b83320f3e8681015fd4c6 100644 --- a/consensus/tendermint/config.go +++ b/consensus/tendermint/config.go @@ -77,7 +77,7 @@ func (tmintConfig *TendermintConfig) AssertTendermintDefaults(chainId, workDir, tmintConfig.SetDefault("rpc_laddr", "") tmintConfig.SetDefault("prof_laddr", "") tmintConfig.SetDefault("revision_file", path.Join(workDir, "revision")) - tmintConfig.SetDefault("cs_wal_dir", path.Join(dataDir, "cs.wal")) + tmintConfig.SetDefault("cs_wal_file", path.Join(dataDir, "cs.wal/wal")) tmintConfig.SetDefault("cs_wal_light", false) tmintConfig.SetDefault("filter_peers", false) diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go index c11a7c508cd1c1817eefc99652d1323df2b21bf4..a35337cc3c4ac88f7c52934f0a51036723d4f625 100644 --- a/consensus/tendermint/tendermint.go +++ b/consensus/tendermint/tendermint.go @@ -32,10 +32,12 @@ import ( config "github.com/hyperledger/burrow/config" manager_types "github.com/hyperledger/burrow/manager/types" // files "github.com/hyperledger/burrow/files" + "errors" + blockchain_types "github.com/hyperledger/burrow/blockchain/types" consensus_types "github.com/hyperledger/burrow/consensus/types" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/txs" "github.com/tendermint/go-wire" ) @@ -44,7 +46,7 @@ type Tendermint struct { tmintNode *node.Node tmintConfig *TendermintConfig chainId string - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // Compiler checks to ensure Tendermint successfully implements @@ -54,12 +56,7 @@ var _ blockchain_types.Blockchain = (*Tendermint)(nil) func NewTendermint(moduleConfig *config.ModuleConfig, application manager_types.Application, - logger loggers.InfoTraceLogger) (*Tendermint, error) { - // re-assert proper configuration for module - if moduleConfig.Version != GetTendermintVersion().GetMinorVersionString() { - return nil, fmt.Errorf("Version string %s did not match %s", - moduleConfig.Version, GetTendermintVersion().GetMinorVersionString()) - } + logger logging_types.InfoTraceLogger) (*Tendermint, error) { // loading the module has ensured the working and data directory // for tendermint have been created, but the config files needs // to be written in tendermint's root directory. @@ -122,15 +119,16 @@ func NewTendermint(moduleConfig *config.ModuleConfig, newNode := node.NewNode(tmintConfig, privateValidator, proxy.NewLocalClientCreator(application)) - listener := p2p.NewDefaultListener("tcp", tmintConfig.GetString("node_laddr"), - tmintConfig.GetBool("skip_upnp")) - - newNode.AddListener(listener) // TODO: [ben] delay starting the node to a different function, to hand // control over events to Core - if err := newNode.Start(); err != nil { + if started, err := newNode.Start(); !started { newNode.Stop() - return nil, fmt.Errorf("Failed to start Tendermint consensus node: %v", err) + if err != nil { + return nil, fmt.Errorf("Failed to start Tendermint consensus node: %v", err) + } + return nil, errors.New("Failed to start Tendermint consensus node, " + + "probably because it is already started, see logs") + } logging.InfoMsg(logger, "Tendermint consensus node started", "nodeAddress", tmintConfig.GetString("node_laddr"), @@ -269,6 +267,11 @@ func (tendermint *Tendermint) PeerConsensusStates() map[string]string { return peerConsensusStates } +// Allow for graceful shutdown of node. Returns whether the node was stopped. +func (tendermint *Tendermint) Stop() bool { + return tendermint.tmintNode.Stop() +} + //------------------------------------------------------------------------------ // Helper functions diff --git a/consensus/tendermint/version.go b/consensus/tendermint/version.go index 7a20ac5c1b5b7bc037d6591ea82164f9b9f3e9a8..58f690db7dbace4b8ebb5852457d7fdb26c867c8 100644 --- a/consensus/tendermint/version.go +++ b/consensus/tendermint/version.go @@ -25,12 +25,6 @@ import ( const ( // Client identifier to advertise over the network tendermintClientIdentifier = "tendermint" - // Major version component of the current release - tendermintVersionMajorConst uint8 = 0 - // Minor version component of the current release - tendermintVersionMinorConst uint8 = 8 - // Patch version component of the current release - tendermintVersionPatchConst uint8 = 0 ) var ( @@ -54,7 +48,7 @@ func GetTendermintVersion() *version.VersionIdentifier { func getTendermintMajorVersionFromSource() (uint8, error) { majorVersionUint, err := strconv.ParseUint(tendermint_version.Maj, 10, 8) if err != nil { - return tendermintVersionMajorConst, err + return 0, err } return uint8(majorVersionUint), nil } @@ -62,7 +56,7 @@ func getTendermintMajorVersionFromSource() (uint8, error) { func getTendermintMinorVersionFromSource() (uint8, error) { minorVersionUint, err := strconv.ParseUint(tendermint_version.Min, 10, 8) if err != nil { - return tendermintVersionMinorConst, err + return 0, err } return uint8(minorVersionUint), nil } @@ -70,7 +64,7 @@ func getTendermintMinorVersionFromSource() (uint8, error) { func getTendermintPatchVersionFromSource() (uint8, error) { patchVersionUint, err := strconv.ParseUint(tendermint_version.Fix, 10, 8) if err != nil { - return tendermintVersionPatchConst, err + return 0, err } return uint8(patchVersionUint), nil } diff --git a/consensus/tendermint/version_test.go b/consensus/tendermint/version_test.go index bfbbe3e762bde36304cf59379b04c94d13cff4ae..330c77425a6a281581882bda8424620873a84773 100644 --- a/consensus/tendermint/version_test.go +++ b/consensus/tendermint/version_test.go @@ -14,28 +14,7 @@ package tendermint -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMinorVersionTendermintEqual(t *testing.T) { - // assert explicitly on major and minor version number - assert.Equal(t, tendermintVersionMajorConst, tendermintVersionMajor, - fmt.Sprintf("Major version number for Tendermint consensus is not %v as expected: %v", - tendermintVersionMajorConst, tendermintVersionMajor)) - assert.Equal(t, tendermintVersionMinorConst, tendermintVersionMinor, - fmt.Sprintf("Minor version number for Tendermint consensus is not %v as expected: %v", - tendermintVersionMinorConst, tendermintVersionMinor)) - // assert patch number can not regress - if tendermintVersionPatchConst > tendermintVersionPatch { - t.Errorf("Patch version has regressed for Tendermint consensus: expected minimally %v, got %v", - tendermintVersionPatchConst, tendermintVersionPatch) - t.Fail() - } -} +import "testing" func TestSemanticVersioningTendermint(t *testing.T) { // assert that reading the semantic version from Tendermint vendored source diff --git a/consensus/types/consensus_engine.go b/consensus/types/consensus_engine.go index 26754db313609f0b5c30cf7ba6e8b622f18733e1..b1677d7a53fe8ef14181738ae17f93ff59748d22 100644 --- a/consensus/types/consensus_engine.go +++ b/consensus/types/consensus_engine.go @@ -48,4 +48,7 @@ type ConsensusEngine interface { // TODO: Consider creating a real type for PeerRoundState, but at the looks // quite coupled to tendermint PeerConsensusStates() map[string]string + + // Allow for graceful shutdown of node. Returns whether the node was stopped. + Stop() bool } diff --git a/core/config.go b/core/config.go index 30d21034f725508b5252a8993db218e314b536f4..bddb21b8491f69ed719230ceba412aa2c34aa028 100644 --- a/core/config.go +++ b/core/config.go @@ -24,13 +24,10 @@ import ( "path" "github.com/hyperledger/burrow/config" - "github.com/hyperledger/burrow/consensus" "github.com/hyperledger/burrow/definitions" - "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/manager" + lconfig "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/server" "github.com/hyperledger/burrow/util" - "github.com/hyperledger/burrow/version" "github.com/spf13/viper" ) @@ -54,14 +51,6 @@ func loadModuleConfigFromDo(do *definitions.Do, module string) (*config.ModuleCo func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir, genesisFile, chainId, module string) (*config.ModuleConfig, error) { moduleName := conf.GetString("chain." + module + ".name") - majorVersion := conf.GetInt("chain." + module + ".major_version") - minorVersion := conf.GetInt("chain." + module + ".minor_version") - minorVersionString := version.MakeMinorVersionString(moduleName, majorVersion, - minorVersion, 0) - if !assertValidModule(module, moduleName, minorVersionString) { - return nil, fmt.Errorf("Error reading config: %s module %s (%s) is not supported by %s", - module, moduleName, minorVersionString, version.GetVersionString()) - } // set up the directory structure for the module inside the data directory workDir := path.Join(rootDataDir, conf.GetString("chain."+module+ ".relative_root")) @@ -88,7 +77,6 @@ func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir, return &config.ModuleConfig{ Module: module, Name: moduleName, - Version: minorVersionString, WorkDir: workDir, DataDir: dataDir, RootDir: rootWorkDir, // burrow's working directory @@ -98,10 +86,15 @@ func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir, }, nil } -// LoadServerModuleConfig wraps specifically for the servers run by core -func LoadServerConfig(do *definitions.Do) (*server.ServerConfig, error) { +// Load the ServerConfig from commandline Do object +func LoadServerConfigFromDo(do *definitions.Do) (*server.ServerConfig, error) { // load configuration subtree for servers - subConfig, err := config.ViperSubConfig(do.Config, "servers") + return LoadServerConfig(do.ChainId, do.Config) +} + +// Load the ServerConfig from root Viper config, fixing the ChainId +func LoadServerConfig(chainId string, rootConfig *viper.Viper) (*server.ServerConfig, error) { + subConfig, err := config.ViperSubConfig(rootConfig, "servers") if err != nil { return nil, err } @@ -109,30 +102,19 @@ func LoadServerConfig(do *definitions.Do) (*server.ServerConfig, error) { if err != nil { return nil, err } - serverConfig.ChainId = do.ChainId + serverConfig.ChainId = chainId return serverConfig, err } -func LoadLoggingConfigFromDo(do *definitions.Do) (*logging.LoggingConfig, error) { - //subConfig, err := SubConfig(conf, "logging") - loggingConfig := &logging.LoggingConfig{} - return loggingConfig, nil +func LoadLoggingConfigFromDo(do *definitions.Do) (*lconfig.LoggingConfig, error) { + if !do.Config.IsSet("logging") { + return nil, nil + } + loggingConfigMap := do.Config.GetStringMap("logging") + return lconfig.LoggingConfigFromMap(loggingConfigMap) } -func LoadLoggingConfigFromClientDo(do *definitions.ClientDo) (*logging.LoggingConfig, error) { - loggingConfig := &logging.LoggingConfig{} +func LoadLoggingConfigFromClientDo(do *definitions.ClientDo) (*lconfig.LoggingConfig, error) { + loggingConfig := lconfig.DefaultClientLoggingConfig() return loggingConfig, nil } - -//------------------------------------------------------------------------------ -// Helper functions - -func assertValidModule(module, name, minorVersionString string) bool { - switch module { - case "consensus": - return consensus.AssertValidConsensusModule(name, minorVersionString) - case "manager": - return manager.AssertValidApplicationManagerModule(name, minorVersionString) - } - return false -} diff --git a/core/config_test.go b/core/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ef552d2b0581d48538e6119a2b5615474fe05de4 --- /dev/null +++ b/core/config_test.go @@ -0,0 +1,25 @@ +package core + +import ( + "testing" + + "github.com/hyperledger/burrow/config" + "github.com/hyperledger/burrow/definitions" + lconfig "github.com/hyperledger/burrow/logging/config" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestLoadLoggingConfigFromDo(t *testing.T) { + do := new(definitions.Do) + do.Config = viper.New() + lc, err := LoadLoggingConfigFromDo(do) + assert.NoError(t, err) + assert.Nil(t, lc, "Should get nil logging config when [logging] not set") + cnf, err := config.ReadViperConfig(([]byte)(lconfig.DefaultNodeLoggingConfig().RootTOMLString())) + assert.NoError(t, err) + do.Config = cnf + lc, err = LoadLoggingConfigFromDo(do) + assert.NoError(t, err) + assert.EqualValues(t, lconfig.DefaultNodeLoggingConfig(), lc) +} diff --git a/core/core.go b/core/core.go index 6e6189829fb949314a8e2cbb90eff27691eff64c..3727a823203850afe0881b950ebb41b6a10ec253 100644 --- a/core/core.go +++ b/core/core.go @@ -30,7 +30,7 @@ import ( // rpc_tendermint is carried over from burrowv0.11 and before on port 46657 "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" rpc_tendermint "github.com/hyperledger/burrow/rpc/tendermint/core" "github.com/hyperledger/burrow/server" ) @@ -41,20 +41,20 @@ type Core struct { evsw events.EventSwitch pipe definitions.Pipe tendermintPipe definitions.TendermintPipe + logger logging_types.InfoTraceLogger } func NewCore(chainId string, consensusConfig *config.ModuleConfig, managerConfig *config.ModuleConfig, - logger loggers.InfoTraceLogger) (*Core, error) { + logger logging_types.InfoTraceLogger) (*Core, error) { // start new event switch, TODO: [ben] replace with burrow/event evsw := events.NewEventSwitch() evsw.Start() logger = logging.WithScope(logger, "Core") // start a new application pipe that will load an application manager - pipe, err := manager.NewApplicationPipe(managerConfig, evsw, logger, - consensusConfig.Version) + pipe, err := manager.NewApplicationPipe(managerConfig, evsw, logger) if err != nil { return nil, fmt.Errorf("Failed to load application pipe: %v", err) } @@ -66,13 +66,14 @@ func NewCore(chainId string, tendermintPipe, err := pipe.GetTendermintPipe() if err != nil { logging.TraceMsg(logger, "Tendermint gateway not supported by manager", - "manager-version", managerConfig.Version) + "manager", managerConfig.Name) } return &Core{ chainId: chainId, evsw: evsw, pipe: pipe, tendermintPipe: tendermintPipe, + logger: logger, }, nil } @@ -99,9 +100,9 @@ func (core *Core) NewGatewayV0(config *server.ServerConfig) (*server.ServeProces jsonServer := rpc_v0.NewJsonRpcServer(tmjs) restServer := rpc_v0.NewRestServer(codec, core.pipe, eventSubscriptions) wsServer := server.NewWebSocketServer(config.WebSocket.MaxWebSocketSessions, - tmwss) + tmwss, core.logger) // Create a server process. - proc, err := server.NewServeProcess(config, jsonServer, restServer, wsServer) + proc, err := server.NewServeProcess(config, core.logger, jsonServer, restServer, wsServer) if err != nil { return nil, fmt.Errorf("Failed to load gateway: %v", err) } @@ -117,3 +118,8 @@ func (core *Core) NewGatewayTendermint(config *server.ServerConfig) ( return rpc_tendermint.NewTendermintWebsocketServer(config, core.tendermintPipe, core.evsw) } + +// Stop the core allowing for a graceful shutdown of component in order. +func (core *Core) Stop() bool { + return core.pipe.GetConsensusEngine().Stop() +} diff --git a/definitions/pipe.go b/definitions/pipe.go index 7c1fddd9ef501dc11d7a89bf4eee661121ef99b3..286a0623a000e792e37e32c595f5296cdf02ced2 100644 --- a/definitions/pipe.go +++ b/definitions/pipe.go @@ -29,7 +29,7 @@ import ( core_types "github.com/hyperledger/burrow/core/types" types "github.com/hyperledger/burrow/core/types" event "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" manager_types "github.com/hyperledger/burrow/manager/types" "github.com/hyperledger/burrow/txs" ) @@ -42,7 +42,7 @@ type Pipe interface { Transactor() Transactor // Hash of Genesis state GenesisHash() []byte - Logger() loggers.InfoTraceLogger + Logger() logging_types.InfoTraceLogger // NOTE: [ben] added to Pipe interface on 0.12 refactor GetApplication() manager_types.Application SetConsensusEngine(consensusEngine consensus_types.ConsensusEngine) error diff --git a/definitions/tendermint_pipe.go b/definitions/tendermint_pipe.go index 600bfbd19232bf078a3c21d560403046f962abca..faf78788a7f74d1efad0ae81df90b3c2cc520617 100644 --- a/definitions/tendermint_pipe.go +++ b/definitions/tendermint_pipe.go @@ -31,7 +31,7 @@ type TendermintPipe interface { // Subscribe attempts to subscribe the listener identified by listenerId to // the event named event. The Event result is written to rpcResponseWriter // which must be non-blocking - Subscribe(event string, + Subscribe(eventId string, rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) Unsubscribe(subscriptionId string) (*rpc_tm_types.ResultUnsubscribe, error) diff --git a/event/event_cache.go b/event/event_cache.go index 99dc4f3fa98e65f9de8c9cc6fc8f98ea9632912f..9342564bc51d26c80a10dbf2fa099b81e5eb167c 100644 --- a/event/event_cache.go +++ b/event/event_cache.go @@ -59,7 +59,7 @@ func (this *EventCache) poll() []interface{} { // Catches events that callers subscribe to and adds them to an array ready to be polled. type EventSubscriptions struct { - mtx *sync.Mutex + mtx *sync.RWMutex eventEmitter EventEmitter subs map[string]*EventCache reap bool @@ -67,7 +67,7 @@ type EventSubscriptions struct { func NewEventSubscriptions(eventEmitter EventEmitter) *EventSubscriptions { es := &EventSubscriptions{ - mtx: &sync.Mutex{}, + mtx: &sync.RWMutex{}, eventEmitter: eventEmitter, subs: make(map[string]*EventCache), reap: true, @@ -121,6 +121,8 @@ func (this *EventSubscriptions) Add(eventId string) (string, error) { } func (this *EventSubscriptions) Poll(subId string) ([]interface{}, error) { + this.mtx.RLock() + defer this.mtx.RUnlock() sub, ok := this.subs[subId] if !ok { return nil, fmt.Errorf("Subscription not active. ID: " + subId) diff --git a/event/event_cache_test.go b/event/event_cache_test.go index bd1954f6a79359bba214e7c7c366b4f91a0fa73a..1d271b9cc6499b11edd45ac9f619486cbc3b8a35 100644 --- a/event/event_cache_test.go +++ b/event/event_cache_test.go @@ -27,14 +27,13 @@ import ( "github.com/stretchr/testify/assert" ) -var mockInterval = 40 * time.Millisecond +var mockInterval = 20 * time.Millisecond type mockSub struct { - subId string - eventId string - f func(txs.EventData) - shutdown bool - sdChan chan struct{} + subId string + eventId string + f func(txs.EventData) + sdChan chan struct{} } type mockEventData struct { @@ -46,7 +45,7 @@ func (eventData mockEventData) AssertIsEventData() {} // A mock event func newMockSub(subId, eventId string, f func(txs.EventData)) mockSub { - return mockSub{subId, eventId, f, false, make(chan struct{})} + return mockSub{subId, eventId, f, make(chan struct{})} } type mockEventEmitter struct { @@ -67,33 +66,30 @@ func (this *mockEventEmitter) Subscribe(subId, eventId string, callback func(txs this.subs[subId] = me this.mutex.Unlock() - go func() { - <-me.sdChan - me.shutdown = true - }() go func() { for { - if !me.shutdown { - me.f(mockEventData{subId, eventId}) - } else { + select { + case <-me.sdChan: this.mutex.Lock() delete(this.subs, subId) this.mutex.Unlock() return + case <-time.After(mockInterval): + me.f(mockEventData{subId, eventId}) } - time.Sleep(mockInterval) } }() return nil } func (this *mockEventEmitter) Unsubscribe(subId string) error { + this.mutex.Lock() sub, ok := this.subs[subId] + this.mutex.Unlock() if !ok { return nil } - sub.shutdown = true - delete(this.subs, subId) + sub.sdChan <- struct{}{} return nil } diff --git a/event/events.go b/event/events.go index a198dc51a490b9450fe7b6716a00cdd1981a12b6..f93b1b643eb56d75af899995f5b732fa964532d1 100644 --- a/event/events.go +++ b/event/events.go @@ -22,7 +22,7 @@ import ( "fmt" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/txs" go_events "github.com/tendermint/go-events" tm_types "github.com/tendermint/tendermint/types" @@ -42,7 +42,7 @@ type EventEmitter interface { Unsubscribe(subId string) error } -func NewEvents(eventSwitch go_events.EventSwitch, logger loggers.InfoTraceLogger) *events { +func NewEvents(eventSwitch go_events.EventSwitch, logger logging_types.InfoTraceLogger) *events { return &events{eventSwitch: eventSwitch, logger: logging.WithScope(logger, "Events")} } @@ -56,7 +56,7 @@ func Multiplex(events ...EventEmitter) *multiplexedEvents { // The events struct has methods for working with events. type events struct { eventSwitch go_events.EventSwitch - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // Subscribe to an event. diff --git a/event/events_test.go b/event/events_test.go index a4dcf84cd2bedd1b0f01b079833661f0e385764c..9f373f46ee8d30a5c08cdd63a41e9d7c7f02fc95 100644 --- a/event/events_test.go +++ b/event/events_test.go @@ -53,16 +53,21 @@ func TestMultiplexedEvents(t *testing.T) { mutex2.Unlock() }) - time.Sleep(mockInterval) + time.Sleep(2 * mockInterval) - allEventData := make(map[txs.EventData]int) - for k, v := range eventData1 { - allEventData[k] = v - } - for k, v := range eventData2 { - allEventData[k] = v - } + err := emitter12.Unsubscribe("Sub12") + assert.NoError(t, err) + err = emitter1.Unsubscribe("Sub2") + assert.NoError(t, err) + err = emitter2.Unsubscribe("Sub2") + assert.NoError(t, err) + mutex1.Lock() + defer mutex1.Unlock() + mutex2.Lock() + defer mutex2.Unlock() + mutex12.Lock() + defer mutex12.Unlock() assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub1", "Event1"}: 1}, eventData1) assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub2", "Event2"}: 1}, @@ -70,5 +75,4 @@ func TestMultiplexedEvents(t *testing.T) { assert.Equal(t, map[txs.EventData]int{mockEventData{"Sub12", "Event12"}: 1}, eventData12) - assert.NotEmpty(t, allEventData, "Some events should have been published") } diff --git a/event/filters.go b/event/filters.go index ea04e989f45a8c61dd46370bebcaac02a86fc727..0a1c732437835d76bf056bd362a213c930a435a7 100644 --- a/event/filters.go +++ b/event/filters.go @@ -107,7 +107,7 @@ func (this *FilterFactory) RegisterFilterPool(fieldName string, pool *sync.Pool) // only if all the sub-filters matches. func (this *FilterFactory) NewFilter(fdArr []*FilterData) (Filter, error) { - if fdArr == nil || len(fdArr) == 0 { + if len(fdArr) == 0 { return &MatchAllFilter{}, nil } if len(fdArr) == 1 { diff --git a/files/files.go b/files/files.go index a11c8a62f145ba42813c5086713b9241f9212e21..2ab8b5152489d1fc82632b3636a0df56b30a98dc 100644 --- a/files/files.go +++ b/files/files.go @@ -17,7 +17,6 @@ package files import ( "fmt" - "io" "io/ioutil" "os" ) @@ -62,7 +61,6 @@ func WriteFile(fileName string, data []byte, perm os.FileMode) error { return err } if err2 == nil && n < len(data) { - err2 = io.ErrShortWrite return err } diff --git a/files/log.go b/files/log.go deleted file mode 100644 index b07f421feff655f01a8762505ab1d5ff31a09391..0000000000000000000000000000000000000000 --- a/files/log.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package files - -import ( - "github.com/tendermint/log15" -) - -var log = log15.New("module", "server/files") diff --git a/genesis/maker.go b/genesis/maker.go index f30ef3cbd23762ea269475e7ba38d8e328f2903d..48f09c563e0cd49ad786be7e235091d82946e436 100644 --- a/genesis/maker.go +++ b/genesis/maker.go @@ -43,25 +43,21 @@ func NewGenesisValidator(amount int64, name string, unbondToAddress []byte, // convert the key bytes into a typed fixed size byte array var typedPublicKeyBytes []byte switch keyType { - case "ed25519": + case "ed25519": // ed25519 has type byte 0x01 // TODO: [ben] functionality and checks need to be inherit in the type if len(publicKeyBytes) != PublicKeyEd25519ByteLength { - return nil, fmt.Errorf("Invalid length provided for ed25519 public key (len %v)", - len(publicKeyBytes)) + return nil, fmt.Errorf("Invalid length provided for ed25519 public key (%v bytes provided but expected %v bytes)", + len(publicKeyBytes), PublicKeyEd25519ByteLength) } - // ed25519 has type byte 0x01 - typedPublicKeyBytes = make([]byte, PublicKeyEd25519ByteLength+1) // prepend type byte to public key - typedPublicKeyBytes = append([]byte{crypto.PubKeyTypeEd25519}, publicKeyBytes...) - case "secp256k1": + typedPublicKeyBytes = append([]byte{crypto.TypeEd25519}, publicKeyBytes...) + case "secp256k1": // secp256k1 has type byte 0x02 if len(publicKeyBytes) != PublicKeySecp256k1ByteLength { - return nil, fmt.Errorf("Invalid length provided for secp256k1 public key (len %v)", - len(publicKeyBytes)) + return nil, fmt.Errorf("Invalid length provided for secp256k1 public key (%v bytes provided but expected %v bytes)", + len(publicKeyBytes), PublicKeySecp256k1ByteLength) } - // secp256k1 has type byte 0x02 - typedPublicKeyBytes = make([]byte, PublicKeySecp256k1ByteLength+1) // prepend type byte to public key - typedPublicKeyBytes = append([]byte{crypto.PubKeyTypeSecp256k1}, publicKeyBytes...) + typedPublicKeyBytes = append([]byte{crypto.TypeSecp256k1}, publicKeyBytes...) default: return nil, fmt.Errorf("Unsupported key type (%s)", keyType) } diff --git a/glide.lock b/glide.lock index 10bcdcad4c5ac8966a9786a9952e5f7194b471b1..b6a1515c7f0a3fe2c0b89866218c9eb0d31be930 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,12 @@ -hash: 310aa7c7435ad7dd1c3eb6772a42065b5f506e38e195107bdbfb1584833add9a -updated: 2017-02-21T01:43:41.814044634Z +hash: 4ad3a252504d2e316e39e6987952988b84692479a44ef29aeb1063add4c129c7 +updated: 2017-04-27T11:38:47.830251548+01:00 imports: - name: github.com/Azure/go-ansiterm version: 388960b655244e76e24c75f48631564eaefade62 subpackages: - winterm - name: github.com/btcsuite/btcd - version: 153dca5c1e4b5d1ea1523592495e5bedfa503391 + version: 4b348c1d33373d672edd83fc576892d0e46686d2 subpackages: - btcec - name: github.com/btcsuite/fastsha256 @@ -20,15 +20,17 @@ imports: - name: github.com/bugsnag/panicwrap version: d6191e27ad06236eaad65d79e49a08b03b9f8029 - name: github.com/BurntSushi/toml - version: 99064174e013895bbd9b025c31100bd1d9b590ca + version: b26d9c308763d68093482582cea63d69be07a0f0 - name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + subpackages: + - spew - name: github.com/eapache/channels version: 47238d5aae8c0fefd518ef2bee46290909cf8263 - name: github.com/eapache/queue version: 44cc805cf13205b55f69e14bcb69867d1ae92f98 - name: github.com/ebuchman/fail-test - version: c1eddaa09da2b4017351245b0d43234955276798 + version: 95f809107225be108efcf10a3509e4ea6ceef3c4 - name: github.com/fsnotify/fsnotify version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8 - name: github.com/gin-gonic/gin @@ -37,7 +39,7 @@ imports: - binding - render - name: github.com/go-kit/kit - version: f66b0e13579bfc5a48b9e2a94b1209c107ea1f41 + version: a9ca6725cbbea455e61c6bc8a1ed28e81eb3493b subpackages: - log - name: github.com/go-logfmt/logfmt @@ -45,15 +47,18 @@ imports: - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/gogo/protobuf - version: f9114dace7bd920b32f943b3c73fafbcbab2bf31 + version: 100ba4e885062801d56799d78530b73b178a78f3 - name: github.com/golang/protobuf - version: 8ee79997227bf9b34611aee7946ae64735e6fd93 + version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef subpackages: - proto + - ptypes/any - name: github.com/golang/snappy - version: d9eb7a3d35ec988b8585d4a0068e462c27d28380 + version: 553a641470496b2327abcac10b36396bd98e45c9 - name: github.com/gorilla/websocket - version: 17634340a83afe0cab595e40fbc63f6ffa1d8915 + version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13 +- name: github.com/Graylog2/go-gelf + version: 5bfd5bbbfe86489017c268bf86539fcec7e28d8e - name: github.com/hashicorp/hcl version: da486364306ed66c218be9b7953e19173447c18b subpackages: @@ -77,40 +82,38 @@ imports: version: c265cfa48dda6474e208715ca93e987829f572f8 - name: github.com/manucorporat/sse version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d -- name: github.com/Masterminds/glide - version: 869001d1571ce5f03fd0078bff188b9d3f272807 - name: github.com/mattn/go-colorable - version: d228849504861217f796da67fae4f6e347643f15 + version: ded68f7a9561c023e790de24279db7ebf473ea80 - name: github.com/mattn/go-isatty - version: 30a891c33c7cde7b02a981314b4228ec99380cca + version: fc9e8d8ef48496124e79ae0df75490096eccf6fe - name: github.com/mitchellh/mapstructure - version: d2dd0262208475919e1a362f675cfc0e7c10e905 + version: db1efb556f84b25a0a13a04aad883943538ad2e0 - name: github.com/naoina/toml version: 751171607256bb66e64c9f0220c00662420c38e9 +- name: github.com/pkg/errors + version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/pmezard/go-difflib version: d8ed2627bdf02c080bf22230dbb337003b7aba2d - subpackages: - - difflib - name: github.com/Sirupsen/logrus version: d26492970760ca5d33129d2d799e34be5c4782eb - name: github.com/spf13/cast version: 27b586b42e29bec072fe7379259cc719e1289da6 - name: github.com/spf13/cobra - version: bc81c21bd0d8be5ba2d6630a505d79d4467566e7 + version: 10f6b9d7e1631a54ad07c5c0fb71c28a1abfd3c2 - name: github.com/spf13/jwalterweatherman version: 33c24e77fb80341fe7130ee7c594256ff08ccc46 - name: github.com/spf13/pflag - version: 25f8b5b07aece3207895bf19f7ab517eb3b22a40 + version: 2300d0f8576fe575f71aaa5b9bbe4e1b0dc2eb51 - name: github.com/spf13/viper version: c1ccc378a054ea8d4e38d8c67f6938d4760b53dd - name: github.com/streadway/simpleuuid version: 6617b501e485b77e61b98cd533aefff9e258b5a7 - name: github.com/stretchr/testify - version: d77da356e56a7428ad25149ca77381849a6a5232 + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert - name: github.com/syndtr/goleveldb - version: 23851d93a2292dcc56e71a18ec9e0624d84a0f65 + version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 subpackages: - leveldb - leveldb/cache @@ -125,12 +128,11 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: 699d45bc678865b004b90213bf88a950f420973b + version: 56e13d87f4e3ec1ea756957d6b23caa6ebcf0998 subpackages: - client - example/counter - example/dummy - - example/nil - server - types - name: github.com/tendermint/ed25519 @@ -141,21 +143,23 @@ imports: - name: github.com/tendermint/flowcontrol version: 84d9671090430e8ec80e35b339907e0579b999eb - name: github.com/tendermint/go-autofile - version: 0416e0aa9c68205aa44844096f9f151ada9d0405 + version: 48b17de82914e1ec2f134ce823ba426337d2c518 - name: github.com/tendermint/go-clist version: 3baa390bbaf7634251c42ad69a8682e7e3990552 - name: github.com/tendermint/go-common - version: e289af53b6bf6af28da129d9ef64389a4cf7987f + version: f9e3db037330c8a8d61d3966de8473eaf01154fa subpackages: - test - name: github.com/tendermint/go-config - version: e64b424499acd0eb9856b88e10c0dff41628c0d6 + version: 620dcbbd7d587cf3599dedbf329b64311b0c307a - name: github.com/tendermint/go-crypto - version: 4b11d62bdb324027ea01554e5767b71174680ba0 + version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da +- name: github.com/tendermint/go-data + version: e7fcc6d081ec8518912fcdc103188275f83a3ee5 - name: github.com/tendermint/go-db - version: 72f6dacd22a686cdf7fcd60286503e3aceda77ba + version: 9643f60bc2578693844aacf380a7c32e4c029fee - name: github.com/tendermint/go-events - version: fddee66d90305fccb6f6d84d16c34fa65ea5b7f6 + version: f8ffbfb2be3483e9e7927495590a727f51c0c11f - name: github.com/tendermint/go-flowrate version: a20c98e61957faa93b4014fbd902f20ab9317a6a subpackages: @@ -163,25 +167,31 @@ imports: - name: github.com/tendermint/go-logger version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 - name: github.com/tendermint/go-merkle - version: 7a86b4486f2cd84ac885c5bbc609fdee2905f5d1 + version: 714d4d04557fd068a7c2a1748241ce8428015a96 - name: github.com/tendermint/go-p2p - version: 3d98f675f30dc4796546b8b890f895926152fa8d + version: e8f33a47846708269d373f9c8080613d6c4f66b2 subpackages: - upnp - name: github.com/tendermint/go-rpc - version: fcea0cda21f64889be00a0f4b6d13266b1a76ee7 + version: 2c8df0ee6b60d8ac33662df13a4e358c679e02bf subpackages: - client - server - types - name: github.com/tendermint/go-wire - version: 2f3b7aafe21c80b19b6ee3210ecb3e3d07c7a471 + version: c1c9a57ab8038448ddea1714c0698f8051e5748c - name: github.com/tendermint/log15 version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6 subpackages: - term +- name: github.com/tendermint/merkleeyes + version: 9fb76efa5aebe773a598f97e68e75fe53d520e70 + subpackages: + - app + - client + - testutil - name: github.com/tendermint/tendermint - version: 764091dfbb035f1b28da4b067526e04c6a849966 + version: f6e28c497510dcd266353649a45d6f962fd5001c subpackages: - blockchain - consensus @@ -196,7 +206,7 @@ imports: - name: github.com/tommy351/gin-cors version: dc91dec6313ae4db53481bf3b29cf6b94bf80357 - name: golang.org/x/crypto - version: 7c6cc321c680f03b9ef0764448e780704f486b51 + version: 96846453c37f0876340a66a47f3f75b1f3a6cd2d subpackages: - curve25519 - nacl/box @@ -207,7 +217,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: 60c41d1de8da134c05b7b40154a9a82bf5b7edb9 + version: c8c74377599bd978aee1cf3b9b63a8634051cec2 subpackages: - context - http2 @@ -215,27 +225,33 @@ imports: - idna - internal/timeseries - name: golang.org/x/sys - version: d75a52659825e75fff6158388dddc6a5b04f9ba5 + version: ea9bcade75cb975a0b9738936568ab388b845617 subpackages: - unix - name: golang.org/x/text - version: 44f4f658a783b0cee41fe0a23b8fc91d9c120558 + version: 19e3104b43db45fca0303f489a9536087b184802 subpackages: - secure/bidirule - transform - unicode/bidi - unicode/norm +- name: google.golang.org/genproto + version: 411e09b969b1170a9f0c467558eb4c4c110d9c77 + subpackages: + - googleapis/rpc/status - name: google.golang.org/grpc - version: 50955793b0183f9de69bd78e2ec251cf20aab121 + version: 6914ab1e338c92da4218a23d27fcd03d0ad78d46 subpackages: - codes - credentials - grpclog - internal + - keepalive - metadata - naming - peer - stats + - status - tap - transport - name: gopkg.in/fatih/set.v0 diff --git a/glide.yaml b/glide.yaml index aa29fb5b21bd58c975f388d8d711a9f5ccf932f4..53641e17c0b73ea1ce027b553ec2b716cf17fc79 100644 --- a/glide.yaml +++ b/glide.yaml @@ -6,8 +6,6 @@ import: - package: github.com/gorilla/websocket - package: github.com/naoina/toml - package: github.com/stretchr/testify -- package: github.com/tendermint/tendermint - version: ~0.8.0 - package: github.com/tendermint/ed25519 - package: github.com/tommy351/gin-cors - package: golang.org/x/crypto @@ -19,7 +17,7 @@ import: subpackages: - http2 - package: github.com/go-kit/kit - version: ^0.3.0 + version: ~0.5.0 - package: github.com/eapache/channels version: ~1.1.0 - package: github.com/go-logfmt/logfmt @@ -30,5 +28,6 @@ import: - package: github.com/Sirupsen/logrus version: ^0.11.0 - package: github.com/streadway/simpleuuid -- package: github.com/Masterminds/glide - version: ~0.12.3 +- package: github.com/Graylog2/go-gelf +- package: github.com/tendermint/tendermint + version: ~0.9.2 diff --git a/keys/key_client.go b/keys/key_client.go index 184ca2f8caae811ead93681e8657e78f9a552d4f..99046e5e316960de264d56b3041ec9491e136236 100644 --- a/keys/key_client.go +++ b/keys/key_client.go @@ -19,7 +19,7 @@ import ( "fmt" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" ) type KeyClient interface { @@ -36,12 +36,12 @@ var _ KeyClient = (*monaxKeyClient)(nil) type monaxKeyClient struct { rpcString string - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // monaxKeyClient.New returns a new monax-keys client for provided rpc location // Monax-keys connects over http request-responses -func NewBurrowKeyClient(rpcString string, logger loggers.InfoTraceLogger) *monaxKeyClient { +func NewBurrowKeyClient(rpcString string, logger logging_types.InfoTraceLogger) *monaxKeyClient { return &monaxKeyClient{ rpcString: rpcString, logger: logging.WithScope(logger, "BurrowKeyClient"), diff --git a/keys/key_client_util.go b/keys/key_client_util.go index 6046945aac9489122366999eb14abcad47fa4eb0..c0677f993cb841526a5a3527b028daffe9aef4f0 100644 --- a/keys/key_client_util.go +++ b/keys/key_client_util.go @@ -22,7 +22,7 @@ import ( "net/http" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" ) // Monax-Keys server connects over http request-response structures @@ -32,7 +32,7 @@ type HTTPResponse struct { Error string } -func RequestResponse(addr, method string, args map[string]string, logger loggers.InfoTraceLogger) (string, error) { +func RequestResponse(addr, method string, args map[string]string, logger logging_types.InfoTraceLogger) (string, error) { body, err := json.Marshal(args) if err != nil { return "", err diff --git a/logging/adapters/stdlib/capture.go b/logging/adapters/stdlib/capture.go index ceeaaf9a6304cd266541f329e4cc22efedc56d39..599a19a70aa36bc2107029ef12df1075055726c3 100644 --- a/logging/adapters/stdlib/capture.go +++ b/logging/adapters/stdlib/capture.go @@ -19,22 +19,22 @@ import ( "log" kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging/types" ) func Capture(stdLibLogger log.Logger, - logger loggers.InfoTraceLogger) io.Writer { + logger types.InfoTraceLogger) io.Writer { adapter := newAdapter(logger) stdLibLogger.SetOutput(adapter) return adapter } -func CaptureRootLogger(logger loggers.InfoTraceLogger) io.Writer { +func CaptureRootLogger(logger types.InfoTraceLogger) io.Writer { adapter := newAdapter(logger) log.SetOutput(adapter) return adapter } -func newAdapter(logger loggers.InfoTraceLogger) io.Writer { +func newAdapter(logger types.InfoTraceLogger) io.Writer { return kitlog.NewStdlibAdapter(logger) } diff --git a/logging/adapters/tendermint_log15/capture.go b/logging/adapters/tendermint_log15/capture.go index f29ca76cd96e2169775480f9309d683f0663e26c..1ad8550fa8335842338ad3c4e4a29999db288279 100644 --- a/logging/adapters/tendermint_log15/capture.go +++ b/logging/adapters/tendermint_log15/capture.go @@ -12,16 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package adapters +package tendermint_log15 import ( + "time" + kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/loggers" + "github.com/go-stack/stack" + "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" + . "github.com/hyperledger/burrow/util/slice" "github.com/tendermint/log15" ) type infoTraceLoggerAsLog15Handler struct { - logger loggers.InfoTraceLogger + logger types.InfoTraceLogger } var _ log15.Handler = (*infoTraceLoggerAsLog15Handler)(nil) @@ -54,8 +59,66 @@ func Log15HandlerAsKitLogger(handler log15.Handler) kitlog.Logger { } } -func InfoTraceLoggerAsLog15Handler(logger loggers.InfoTraceLogger) log15.Handler { +func InfoTraceLoggerAsLog15Handler(logger types.InfoTraceLogger) log15.Handler { return &infoTraceLoggerAsLog15Handler{ logger: logger, } } + +// Convert a go-kit log line (i.e. keyvals... interface{}) into a log15 record +// This allows us to use log15 output handlers +func LogLineToRecord(keyvals ...interface{}) *log15.Record { + vals, ctx := structure.ValuesAndContext(keyvals, structure.TimeKey, + structure.MessageKey, structure.CallerKey, structure.LevelKey) + + // Mapping of log line to Record is on a best effort basis + theTime, _ := vals[structure.TimeKey].(time.Time) + call, _ := vals[structure.CallerKey].(stack.Call) + level, _ := vals[structure.LevelKey].(string) + message, _ := vals[structure.MessageKey].(string) + + return &log15.Record{ + Time: theTime, + Lvl: Log15LvlFromString(level), + Msg: message, + Call: call, + Ctx: ctx, + KeyNames: log15.RecordKeyNames{ + Time: structure.TimeKey, + Msg: structure.MessageKey, + Lvl: structure.LevelKey, + }} +} + +// Convert a log15 record to a go-kit log line (i.e. keyvals... interface{}) +// This allows us to capture output from dependencies using log15 +func RecordToLogLine(record *log15.Record) []interface{} { + return Concat( + Slice( + structure.CallerKey, record.Call, + structure.LevelKey, record.Lvl.String(), + ), + record.Ctx, + Slice( + structure.MessageKey, record.Msg, + )) +} + +// Collapse our weak notion of leveling and log15's into a log15.Lvl +func Log15LvlFromString(level string) log15.Lvl { + if level == "" { + return log15.LvlDebug + } + switch level { + case types.InfoLevelName: + return log15.LvlInfo + case types.TraceLevelName: + return log15.LvlDebug + default: + lvl, err := log15.LvlFromString(level) + if err == nil { + return lvl + } + return log15.LvlDebug + } +} diff --git a/logging/adapters/tendermint_log15/convert.go b/logging/adapters/tendermint_log15/convert.go deleted file mode 100644 index a9a8e69a39eb85b2bb7eccafd7806accb95ac78a..0000000000000000000000000000000000000000 --- a/logging/adapters/tendermint_log15/convert.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package adapters - -import ( - "time" - - "github.com/go-stack/stack" - "github.com/hyperledger/burrow/logging/loggers" - "github.com/hyperledger/burrow/logging/structure" - . "github.com/hyperledger/burrow/util/slice" - "github.com/tendermint/log15" -) - -// Convert a go-kit log line (i.e. keyvals... interface{}) into a log15 record -// This allows us to use log15 output handlers -func LogLineToRecord(keyvals ...interface{}) *log15.Record { - vals, ctx := structure.ValuesAndContext(keyvals, structure.TimeKey, - structure.MessageKey, structure.CallerKey, structure.LevelKey) - - // Mapping of log line to Record is on a best effort basis - theTime, _ := vals[structure.TimeKey].(time.Time) - call, _ := vals[structure.CallerKey].(stack.Call) - level, _ := vals[structure.LevelKey].(string) - message, _ := vals[structure.MessageKey].(string) - - return &log15.Record{ - Time: theTime, - Lvl: Log15LvlFromString(level), - Msg: message, - Call: call, - Ctx: ctx, - KeyNames: log15.RecordKeyNames{ - Time: structure.TimeKey, - Msg: structure.MessageKey, - Lvl: structure.LevelKey, - }} -} - -// Convert a log15 record to a go-kit log line (i.e. keyvals... interface{}) -// This allows us to capture output from dependencies using log15 -func RecordToLogLine(record *log15.Record) []interface{} { - return Concat( - Slice( - structure.TimeKey, record.Time, - structure.CallerKey, record.Call, - structure.LevelKey, record.Lvl.String(), - ), - record.Ctx, - Slice( - structure.MessageKey, record.Msg, - )) -} - -// Collapse our weak notion of leveling and log15's into a log15.Lvl -func Log15LvlFromString(level string) log15.Lvl { - if level == "" { - return log15.LvlDebug - } - switch level { - case loggers.InfoLevelName: - return log15.LvlInfo - case loggers.TraceLevelName: - return log15.LvlDebug - default: - lvl, err := log15.LvlFromString(level) - if err == nil { - return lvl - } - return log15.LvlDebug - } -} diff --git a/logging/config.go b/logging/config.go deleted file mode 100644 index 5123b93695f9ee60ecb38e50ef58ad040fd6780c..0000000000000000000000000000000000000000 --- a/logging/config.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package logging - -type ( - SinkConfig struct { - Channels []string - } - - LoggingConfig struct { - Sinks []SinkConfig - } -) diff --git a/logging/config/config.go b/logging/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..f620f816a5cad755db7db81266f0204919faef48 --- /dev/null +++ b/logging/config/config.go @@ -0,0 +1,77 @@ +package config + +import ( + "bytes" + "fmt" + + "github.com/hyperledger/burrow/logging/structure" + + "github.com/BurntSushi/toml" +) + +type LoggingConfig struct { + RootSink *SinkConfig `toml:"root_sink,omitempty"` +} + +// For encoding a top-level '[logging]' TOML table +type LoggingConfigWrapper struct { + Logging *LoggingConfig `toml:"logging"` +} + +func DefaultNodeLoggingConfig() *LoggingConfig { + return &LoggingConfig{ + RootSink: Sink().SetOutput(StderrOutput()), + } +} + +func DefaultClientLoggingConfig() *LoggingConfig { + return &LoggingConfig{ + // No output + RootSink: Sink(). + SetTransform(FilterTransform(ExcludeWhenAnyMatches, + structure.CapturedLoggingSourceKey, "")). + SetOutput(StderrOutput()), + } +} + +// Returns the TOML for a top-level logging config wrapped with [logging] +func (lc *LoggingConfig) RootTOMLString() string { + return tomlString(LoggingConfigWrapper{lc}) +} + +func (lc *LoggingConfig) TOMLString() string { + return tomlString(lc) +} + +func LoggingConfigFromMap(loggingRootMap map[string]interface{}) (*LoggingConfig, error) { + lc := new(LoggingConfig) + buf := new(bytes.Buffer) + enc := toml.NewEncoder(buf) + // TODO: [Silas] consider using strongly typed config/struct mapping everywhere + // (!! unfortunately the way we are using viper + // to pass around a untyped bag of config means that we don't get keys mapped + // according to their metadata `toml:"Name"` tags. So we are re-encoding to toml + // and then decoding into the strongly type struct as a work-around) + // Encode the map back to TOML + err := enc.Encode(loggingRootMap) + if err != nil { + return nil, err + } + // Decode into struct into the LoggingConfig struct + _, err = toml.Decode(buf.String(), lc) + if err != nil { + return nil, err + } + return lc, nil +} + +func tomlString(v interface{}) string { + buf := new(bytes.Buffer) + encoder := toml.NewEncoder(buf) + err := encoder.Encode(v) + if err != nil { + // Seems like a reasonable compromise to make the string function clean + return fmt.Sprintf("Error encoding TOML: %s", err) + } + return buf.String() +} diff --git a/logging/config/config_test.go b/logging/config/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..de53dd3a73ae256dffd3367ff10ae39e30346571 --- /dev/null +++ b/logging/config/config_test.go @@ -0,0 +1,42 @@ +package config + +import ( + "strings" + "testing" + + "github.com/BurntSushi/toml" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +var complexConfig *LoggingConfig = &LoggingConfig{ + RootSink: Sink(). + SetTransform(LabelTransform(false, "Info", "Trace")). + AddSinks( + Sink(). + SetOutput(StdoutOutput()). + SetTransform(FilterTransform(ExcludeWhenAnyMatches, + "Foo", "Bars")). + AddSinks( + Sink(). + SetOutput(RemoteSyslogOutput("Eris-db", "tcp://example.com:6514")), + Sink(). + SetOutput(StdoutOutput()), + ), + ), +} + +func TestLoggingConfig_String(t *testing.T) { + lc := new(LoggingConfig) + toml.Decode(complexConfig.TOMLString(), lc) + assert.Equal(t, complexConfig, lc) +} + +func TestReadViperConfig(t *testing.T) { + conf := viper.New() + conf.SetConfigType("toml") + conf.ReadConfig(strings.NewReader(complexConfig.TOMLString())) + lc, err := LoggingConfigFromMap(conf.AllSettings()) + assert.NoError(t, err) + assert.Equal(t, complexConfig, lc) +} diff --git a/logging/config/filter.go b/logging/config/filter.go new file mode 100644 index 0000000000000000000000000000000000000000..1ddf35ff6a1339776f6793c63dc2a7aea19b5a4a --- /dev/null +++ b/logging/config/filter.go @@ -0,0 +1,107 @@ +package config + +import ( + "fmt" + + "regexp" + + "github.com/hyperledger/burrow/common/math/integral" +) + +func BuildFilterPredicate(filterConfig *FilterConfig) (func([]interface{}) bool, error) { + predicate, err := BuildKeyValuesPredicate(filterConfig.Predicates, + filterConfig.FilterMode.MatchAll()) + if err != nil { + return nil, err + } + include := filterConfig.FilterMode.Include() + return func(keyvals []interface{}) bool { + // XOR the predicate with include. If include is true then negate the match. + return predicate(keyvals) != include + }, nil +} + +func BuildKeyValuesPredicate(kvpConfigs []*KeyValuePredicateConfig, + matchAll bool) (func([]interface{}) bool, error) { + length := len(kvpConfigs) + keyRegexes := make([]*regexp.Regexp, length) + valueRegexes := make([]*regexp.Regexp, length) + + // Compile all KV regexes + for i, kvpConfig := range kvpConfigs { + // Store a nil regex to indicate no key match should occur + if kvpConfig.KeyRegex != "" { + keyRegex, err := regexp.Compile(kvpConfig.KeyRegex) + if err != nil { + return nil, err + } + keyRegexes[i] = keyRegex + } + // Store a nil regex to indicate no value match should occur + if kvpConfig.ValueRegex != "" { + valueRegex, err := regexp.Compile(kvpConfig.ValueRegex) + if err != nil { + return nil, err + } + valueRegexes[i] = valueRegex + } + } + + return func(keyvals []interface{}) bool { + return matchLogLine(keyvals, keyRegexes, valueRegexes, matchAll) + }, nil +} + +// matchLogLine tries to match a log line by trying to match each key value pair with each pair of key value regexes +// if matchAll is true then matchLogLine returns true iff every key value regexes finds a match or the line or regexes +// are empty +func matchLogLine(keyvals []interface{}, keyRegexes, valueRegexes []*regexp.Regexp, matchAll bool) bool { + all := matchAll + // We should be passed an aligned list of keyRegexes and valueRegexes, but since we can't error here we'll guard + // against a failure of the caller to pass valid arguments + length := integral.MinInt(len(keyRegexes), len(valueRegexes)) + for i := 0; i < length; i++ { + matched := findMatchInLogLine(keyvals, keyRegexes[i], valueRegexes[i]) + if matchAll { + all = all && matched + } else if matched { + return true + } + } + return all +} + +func findMatchInLogLine(keyvals []interface{}, keyRegex, valueRegex *regexp.Regexp) bool { + for i := 0; i < 2*(len(keyvals)/2); i += 2 { + key := convertToString(keyvals[i]) + val := convertToString(keyvals[i+1]) + if key == nil && val == nil { + continue + } + // At least one of key or val could be converted from string, only match on either if the conversion worked + // Try to match on both key and value, falling back to a positive match if either or both or not supplied + if match(keyRegex, key) && match(valueRegex, val) { + return true + } + } + return false +} + +func match(regex *regexp.Regexp, text *string) bool { + // Always match on a nil regex (indicating no constraint on text), + // and otherwise never match on nil text (indicating a non-string convertible type) + return regex == nil || (text != nil && regex.MatchString(*text)) +} + +func convertToString(value interface{}) *string { + // We have the option of returning nil here to indicate a conversion was + // not possible/does not make sense. Although we are not opting to use it + // currently + switch v := value.(type) { + case string: + return &v + default: + s := fmt.Sprintf("%v", v) + return &s + } +} diff --git a/logging/config/filter_test.go b/logging/config/filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3c5edf5ad4df8c54e999c33792c70e10f4a99c62 --- /dev/null +++ b/logging/config/filter_test.go @@ -0,0 +1,119 @@ +package config + +import ( + "testing" + + . "github.com/hyperledger/burrow/util/slice" + "github.com/stretchr/testify/assert" +) + +func TestBuildKeyValuesPredicateMatchAll(t *testing.T) { + conf := []*KeyValuePredicateConfig{ + { + KeyRegex: "Foo", + ValueRegex: "bar", + }, + } + kvp, err := BuildKeyValuesPredicate(conf, true) + assert.NoError(t, err) + assert.True(t, kvp(Slice("Foo", "bar", "Bosh", "Bish"))) +} + +func TestBuildKeyValuesPredicateMatchAny(t *testing.T) { + conf := []*KeyValuePredicateConfig{ + { + KeyRegex: "Bosh", + ValueRegex: "Bish", + }, + } + kvp, err := BuildKeyValuesPredicate(conf, false) + assert.NoError(t, err) + assert.True(t, kvp(Slice("Foo", "bar", "Bosh", "Bish"))) +} + +func TestExcludeAllFilterPredicate(t *testing.T) { + fc := &FilterConfig{ + FilterMode: ExcludeWhenAllMatch, + Predicates: []*KeyValuePredicateConfig{ + { + KeyRegex: "Bosh", + ValueRegex: "Bish", + }, + { + KeyRegex: "Bosh", + ValueRegex: "Bash", + }, + }, + } + fp, err := BuildFilterPredicate(fc) + assert.NoError(t, err) + assert.False(t, fp(Slice("Bosh", "Bash", "Shoes", 42))) + assert.True(t, fp(Slice("Bosh", "Bash", "Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) + assert.False(t, fp(Slice("Food", 0.2, "Shoes", 42))) + +} +func TestExcludeAnyFilterPredicate(t *testing.T) { + fc := &FilterConfig{ + FilterMode: ExcludeWhenAnyMatches, + Predicates: []*KeyValuePredicateConfig{ + { + KeyRegex: "Bosh", + ValueRegex: "Bish", + }, + { + KeyRegex: "Bosh", + ValueRegex: "Bash", + }, + }, + } + fp, err := BuildFilterPredicate(fc) + assert.NoError(t, err) + assert.False(t, fp(Slice("Foo", "bar", "Shoes", 42))) + assert.True(t, fp(Slice("Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) + assert.True(t, fp(Slice("Food", 0.2, "Shoes", 42, "Bosh", "Bish"))) + +} + +func TestIncludeAllFilterPredicate(t *testing.T) { + fc := &FilterConfig{ + FilterMode: IncludeWhenAllMatch, + Predicates: []*KeyValuePredicateConfig{ + { + KeyRegex: "Bosh", + ValueRegex: "Bish", + }, + { + KeyRegex: "Planks", + ValueRegex: "^0.2$", + }, + }, + } + fp, err := BuildFilterPredicate(fc) + assert.NoError(t, err) + assert.True(t, fp(Slice("Foo", "bar", "Shoes", 42))) + // Don't filter, it has all the required key values + assert.False(t, fp(Slice("Foo", "bar", "Planks", 0.2, "Shoes", 42, "Bosh", "Bish"))) + assert.True(t, fp(Slice("Food", 0.2, "Shoes", 42))) +} + +func TestIncludeAnyFilterPredicate(t *testing.T) { + fc := &FilterConfig{ + FilterMode: IncludeWhenAnyMatches, + Predicates: []*KeyValuePredicateConfig{ + { + KeyRegex: "Bosh", + ValueRegex: "Bish", + }, + { + KeyRegex: "^Shoes$", + ValueRegex: "42", + }, + }, + } + fp, err := BuildFilterPredicate(fc) + assert.NoError(t, err) + assert.False(t, fp(Slice("Foo", "bar", "Shoes", 3427))) + assert.False(t, fp(Slice("Foo", "bar", "Shoes", 42, "Bosh", "Bish"))) + assert.False(t, fp(Slice("Food", 0.2, "Shoes", 42))) + +} diff --git a/logging/config/sinks.go b/logging/config/sinks.go new file mode 100644 index 0000000000000000000000000000000000000000..fbea38d84a6a7c692a2ae27db56b5d2d482b44ac --- /dev/null +++ b/logging/config/sinks.go @@ -0,0 +1,386 @@ +package config + +import ( + "fmt" + "os" + + "net/url" + + "github.com/eapache/channels" + kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/logging/structure" +) + +// This file contains definitions for a configurable output graph for the +// logging system. + +type outputType string +type transformType string +type filterMode string + +const ( + // OutputType + NoOutput outputType = "" + Graylog outputType = "graylog" + Syslog outputType = "syslog" + File outputType = "file" + Stdout outputType = "stdout" + Stderr outputType = "stderr" + + // TransformType + NoTransform transformType = "" + // Filter log lines + Filter transformType = "filter" + // Remove key-val pairs from each log line + Prune transformType = "prune" + // Add key value pairs to each log line + Label transformType = "label" + Capture transformType = "capture" + // TODO [Silas]: add 'flush on exit' transform which flushes the buffer of + // CaptureLogger to its OutputLogger a non-passthrough capture when an exit + // signal is detected or some other exceptional thing happens + + IncludeWhenAllMatch filterMode = "include_when_all_match" + IncludeWhenAnyMatches filterMode = "include_when_any_matches" + ExcludeWhenAllMatch filterMode = "exclude_when_all_match" + ExcludeWhenAnyMatches filterMode = "exclude_when_any_matches" +) + +// Only include log lines matching the filter so negate the predicate in filter +func (mode filterMode) Include() bool { + switch mode { + case IncludeWhenAllMatch, IncludeWhenAnyMatches: + return true + default: + return false + } +} + +// The predicate should only evaluate true if all the key value predicates match +func (mode filterMode) MatchAll() bool { + switch mode { + case IncludeWhenAllMatch, ExcludeWhenAllMatch: + return true + default: + return false + } +} + +// Exclude log lines that match the predicate +func (mode filterMode) Exclude() bool { + return !mode.Include() +} + +// The predicate should evaluate true if at least one of the key value predicates matches +func (mode filterMode) MatchAny() bool { + return !mode.MatchAny() +} + +// Sink configuration types +type ( + // Outputs + GraylogConfig struct { + } + + SyslogConfig struct { + Url string `toml:"url"` + Tag string `toml:"tag"` + } + + FileConfig struct { + Path string `toml:"path"` + } + + OutputConfig struct { + OutputType outputType `toml:"output_type"` + Format string `toml:"format,omitempty"` + *GraylogConfig + *FileConfig + *SyslogConfig + } + + // Transforms + LabelConfig struct { + Labels map[string]string `toml:"labels"` + Prefix bool `toml:"prefix"` + } + + PruneConfig struct { + Keys []string `toml:"keys"` + } + + CaptureConfig struct { + Name string `toml:"name"` + BufferCap int `toml:"buffer_cap"` + Passthrough bool `toml:"passthrough"` + } + + // Generates true if KeyRegex matches a log line key and ValueRegex matches that key's value. + // If ValueRegex is empty then returns true if any key matches + // If KeyRegex is empty then returns true if any value matches + KeyValuePredicateConfig struct { + KeyRegex string `toml:"key_regex"` + ValueRegex string `toml:"value_regex"` + } + + // Filter types + FilterConfig struct { + FilterMode filterMode `toml:"filter_mode"` + // Predicates to match a log line against using FilterMode + Predicates []*KeyValuePredicateConfig `toml:"predicates"` + } + + TransformConfig struct { + TransformType transformType `toml:"transform_type"` + *LabelConfig + *PruneConfig + *CaptureConfig + *FilterConfig + } + + // Sink + // A Sink describes a logger that logs to zero or one output and logs to zero or more child sinks. + // before transmitting its log it applies zero or one transforms to the stream of log lines. + // by chaining together many Sinks arbitrary transforms to and multi + SinkConfig struct { + Transform *TransformConfig `toml:"transform"` + Sinks []*SinkConfig `toml:"sinks"` + Output *OutputConfig `toml:"output"` + } +) + +// Builders +func Sink() *SinkConfig { + return &SinkConfig{} +} + +func (sinkConfig *SinkConfig) AddSinks(sinks ...*SinkConfig) *SinkConfig { + sinkConfig.Sinks = append(sinkConfig.Sinks, sinks...) + return sinkConfig +} + +func (sinkConfig *SinkConfig) SetTransform(transform *TransformConfig) *SinkConfig { + sinkConfig.Transform = transform + return sinkConfig +} + +func (sinkConfig *SinkConfig) SetOutput(output *OutputConfig) *SinkConfig { + sinkConfig.Output = output + return sinkConfig +} + +func (outputConfig *OutputConfig) SetFormat(format string) *OutputConfig { + outputConfig.Format = format + return outputConfig +} + +func StdoutOutput() *OutputConfig { + return &OutputConfig{ + OutputType: Stdout, + } +} + +func StderrOutput() *OutputConfig { + return &OutputConfig{ + OutputType: Stderr, + } +} + +func SyslogOutput(tag string) *OutputConfig { + return RemoteSyslogOutput(tag, "") +} + +func FileOutput(path string) *OutputConfig { + return &OutputConfig{ + OutputType: File, + FileConfig: &FileConfig{ + Path: path, + }, + } +} + +func RemoteSyslogOutput(tag, remoteUrl string) *OutputConfig { + return &OutputConfig{ + OutputType: Syslog, + SyslogConfig: &SyslogConfig{ + Url: remoteUrl, + Tag: tag, + }, + } +} + +func CaptureTransform(name string, bufferCap int, passthrough bool) *TransformConfig { + return &TransformConfig{ + TransformType: Capture, + CaptureConfig: &CaptureConfig{ + Name: name, + BufferCap: bufferCap, + Passthrough: passthrough, + }, + } +} + +func LabelTransform(prefix bool, labelKeyvals ...string) *TransformConfig { + length := len(labelKeyvals) / 2 + labels := make(map[string]string, length) + for i := 0; i < 2*length; i += 2 { + labels[labelKeyvals[i]] = labelKeyvals[i+1] + } + return &TransformConfig{ + TransformType: Label, + LabelConfig: &LabelConfig{ + Prefix: prefix, + Labels: labels, + }, + } +} + +func PruneTransform(keys ...string) *TransformConfig { + return &TransformConfig{ + TransformType: Prune, + PruneConfig: &PruneConfig{ + Keys: keys, + }, + } +} + +func FilterTransform(fmode filterMode, keyvalueRegexes ...string) *TransformConfig { + length := len(keyvalueRegexes) / 2 + predicates := make([]*KeyValuePredicateConfig, length) + for i := 0; i < length; i++ { + kv := i * 2 + predicates[i] = &KeyValuePredicateConfig{ + KeyRegex: keyvalueRegexes[kv], + ValueRegex: keyvalueRegexes[kv+1], + } + } + return &TransformConfig{ + TransformType: Filter, + FilterConfig: &FilterConfig{ + FilterMode: fmode, + Predicates: predicates, + }, + } +} + +// Logger formation +func (sinkConfig *SinkConfig) BuildLogger() (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { + return BuildLoggerFromSinkConfig(sinkConfig, make(map[string]*loggers.CaptureLogger)) +} + +func BuildLoggerFromSinkConfig(sinkConfig *SinkConfig, + captures map[string]*loggers.CaptureLogger) (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { + if sinkConfig == nil { + return kitlog.NewNopLogger(), captures, nil + } + numSinks := len(sinkConfig.Sinks) + outputLoggers := make([]kitlog.Logger, numSinks, numSinks+1) + // We need a depth-first post-order over the output loggers so we'll keep + // recurring into children sinks we reach a terminal sink (with no children) + for i, sc := range sinkConfig.Sinks { + l, captures, err := BuildLoggerFromSinkConfig(sc, captures) + if err != nil { + return nil, captures, err + } + outputLoggers[i] = l + } + + // Grab the outputs after we have terminated any children sinks above + if sinkConfig.Output != nil && sinkConfig.Output.OutputType != NoOutput { + l, err := BuildOutputLogger(sinkConfig.Output) + if err != nil { + return nil, captures, err + } + outputLoggers = append(outputLoggers, l) + } + + outputLogger := loggers.NewMultipleOutputLogger(outputLoggers...) + + if sinkConfig.Transform != nil && sinkConfig.Transform.TransformType != NoTransform { + return BuildTransformLogger(sinkConfig.Transform, captures, outputLogger) + } + return outputLogger, captures, nil +} + +func BuildOutputLogger(outputConfig *OutputConfig) (kitlog.Logger, error) { + switch outputConfig.OutputType { + case NoOutput: + return kitlog.NewNopLogger(), nil + //case Graylog: + case Syslog: + urlString := outputConfig.SyslogConfig.Url + if urlString != "" { + remoteUrl, err := url.Parse(urlString) + if err != nil { + return nil, fmt.Errorf("Error parsing remote syslog URL: %s, "+ + "error: %s", + urlString, err) + } + return loggers.NewRemoteSyslogLogger(remoteUrl, + outputConfig.SyslogConfig.Tag, outputConfig.Format) + } + return loggers.NewSyslogLogger(outputConfig.SyslogConfig.Tag, + outputConfig.Format) + case Stdout: + return loggers.NewStreamLogger(os.Stdout, outputConfig.Format), nil + case Stderr: + return loggers.NewStreamLogger(os.Stderr, outputConfig.Format), nil + case File: + return loggers.NewFileLogger(outputConfig.FileConfig.Path, outputConfig.Format) + default: + return nil, fmt.Errorf("Could not build logger for output: '%s'", + outputConfig.OutputType) + } +} + +func BuildTransformLogger(transformConfig *TransformConfig, + captures map[string]*loggers.CaptureLogger, + outputLogger kitlog.Logger) (kitlog.Logger, map[string]*loggers.CaptureLogger, error) { + switch transformConfig.TransformType { + case NoTransform: + return outputLogger, captures, nil + case Label: + keyvals := make([]interface{}, 0, len(transformConfig.Labels)*2) + for k, v := range transformConfig.LabelConfig.Labels { + keyvals = append(keyvals, k, v) + } + if transformConfig.LabelConfig.Prefix { + return kitlog.WithPrefix(outputLogger, keyvals...), captures, nil + } else { + return kitlog.With(outputLogger, keyvals...), captures, nil + } + case Prune: + keys := make([]interface{}, len(transformConfig.PruneConfig.Keys)) + for i, k := range transformConfig.PruneConfig.Keys { + keys[i] = k + } + return kitlog.LoggerFunc(func(keyvals ...interface{}) error { + return outputLogger.Log(structure.RemoveKeys(keyvals, keys...)...) + }), captures, nil + + case Capture: + name := transformConfig.CaptureConfig.Name + if _, ok := captures[name]; ok { + return nil, captures, fmt.Errorf("Could not register new logging capture since name '%s' already "+ + "registered", name) + } + // Create a capture logger according to configuration (it may tee the output) + // or capture it to be flushed later + captureLogger := loggers.NewCaptureLogger(outputLogger, + channels.BufferCap(transformConfig.CaptureConfig.BufferCap), + transformConfig.CaptureConfig.Passthrough) + // Register the capture + captures[name] = captureLogger + // Pass it upstream to be logged to + return captureLogger, captures, nil + case Filter: + predicate, err := BuildFilterPredicate(transformConfig.FilterConfig) + if err != nil { + return nil, captures, fmt.Errorf("Could not build filter predicate: '%s'", err) + } + return loggers.NewFilterLogger(outputLogger, predicate), captures, nil + default: + return nil, captures, fmt.Errorf("Could not build logger for transform: '%s'", transformConfig.TransformType) + } +} diff --git a/logging/config/sinks_test.go b/logging/config/sinks_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fbe035bb410310ba7b3b13e993ff924973f4121a --- /dev/null +++ b/logging/config/sinks_test.go @@ -0,0 +1,117 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildLoggerFromSinkConfig(t *testing.T) { + sinkConfig := Sink(). + AddSinks( + Sink(). + AddSinks( + Sink(). + AddSinks( + Sink(). + SetTransform(CaptureTransform("cap", 100, true)). + SetOutput(StderrOutput()). + AddSinks( + Sink(). + SetTransform(LabelTransform(true, "Label", "A Label!")). + SetOutput(StdoutOutput()))))) + + logger, captures, err := sinkConfig.BuildLogger() + logger.Log("Foo", "Bar") + assert.NoError(t, err) + assert.Equal(t, logLines("Foo", "Bar"), + captures["cap"].BufferLogger().FlushLogLines()) +} + +func TestFilterSinks(t *testing.T) { + sinkConfig := Sink(). + SetOutput(StderrOutput()). + AddSinks( + Sink(). + SetTransform(FilterTransform(IncludeWhenAnyMatches, + "Foo", "Bar", + "Rough", "Trade", + )). + AddSinks( + Sink(). + SetTransform(CaptureTransform("Included", 100, true)). + AddSinks( + Sink(). + SetTransform(FilterTransform(ExcludeWhenAllMatch, + "Foo", "Baz", + "Index", "00$")). + AddSinks( + Sink(). + SetTransform(CaptureTransform("Excluded", 100, false)), + ), + ), + ), + ) + + logger, captures, err := sinkConfig.BuildLogger() + assert.NoError(t, err, "Should be able to build filter logger") + included := captures["Included"] + excluded := captures["Excluded"] + + // Included by both filters + ll := logLines("Foo", "Bar") + logger.Log(ll[0]...) + assert.Equal(t, logLines("Foo", "Bar"), + included.BufferLogger().FlushLogLines()) + assert.Equal(t, logLines("Foo", "Bar"), + excluded.BufferLogger().FlushLogLines()) + + // Included by first filter and excluded by second + ll = logLines("Foo", "Bar", "Foo", "Baz", "Index", "1000") + logger.Log(ll[0]...) + assert.Equal(t, ll, included.BufferLogger().FlushLogLines()) + assert.Equal(t, logLines(), excluded.BufferLogger().FlushLogLines()) + + // Included by first filter and not excluded by second despite matching one + // predicate + ll = logLines("Rough", "Trade", "Index", "1000") + logger.Log(ll[0]...) + assert.Equal(t, ll, included.BufferLogger().FlushLogLines()) + assert.Equal(t, ll, excluded.BufferLogger().FlushLogLines()) +} + +func TestPruneTransform(t *testing.T) { + sinkConfig := Sink(). + SetTransform(PruneTransform("Trace")). + AddSinks(Sink(). + SetTransform(CaptureTransform("cap", 100, false))) + + logger, captures, err := sinkConfig.BuildLogger() + assert.NoError(t, err) + logger.Log("msg", "Hello with a trace", + "Trace", []string{"logger:32, state:23"}) + logger.Log("msg", "Goodbye with a trace", + "Trace", []string{"logger:32, state:14"}) + assert.Equal(t, logLines("msg", "Hello with a trace", "", + "msg", "Goodbye with a trace"), + captures["cap"].FlushLogLines()) +} + +// Takes a variadic argument of log lines as a list of key value pairs delimited +// by the empty string +func logLines(keyvals ...string) [][]interface{} { + llines := make([][]interface{}, 0) + line := make([]interface{}, 0) + for _, kv := range keyvals { + if kv == "" { + llines = append(llines, line) + line = make([]interface{}, 0) + } else { + line = append(line, kv) + } + } + if len(line) > 0 { + llines = append(llines, line) + } + return llines +} diff --git a/logging/convention.go b/logging/convention.go index fdd09f7fadba0f022582905621a5d8672a46fbc5..d470451a9670a480bf47f9d36f6a4b900f5124c2 100644 --- a/logging/convention.go +++ b/logging/convention.go @@ -16,8 +16,8 @@ package logging import ( kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/util/slice" ) @@ -25,23 +25,13 @@ import ( // to centralise and establish logging conventions on top of in with the base // logging interface -// Record structured Info log line with a message and conventional keys -func InfoMsgVals(logger loggers.InfoTraceLogger, message string, vals ...interface{}) { - MsgVals(kitlog.LoggerFunc(logger.Info), message, vals...) -} - -// Record structured Trace log line with a message and conventional keys -func TraceMsgVals(logger loggers.InfoTraceLogger, message string, vals ...interface{}) { - MsgVals(kitlog.LoggerFunc(logger.Trace), message, vals...) -} - // Record structured Info log line with a message -func InfoMsg(logger loggers.InfoTraceLogger, message string, keyvals ...interface{}) { +func InfoMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) { Msg(kitlog.LoggerFunc(logger.Info), message, keyvals...) } // Record structured Trace log line with a message -func TraceMsg(logger loggers.InfoTraceLogger, message string, keyvals ...interface{}) { +func TraceMsg(logger types.InfoTraceLogger, message string, keyvals ...interface{}) { Msg(kitlog.LoggerFunc(logger.Trace), message, keyvals...) } @@ -49,7 +39,7 @@ func TraceMsg(logger loggers.InfoTraceLogger, message string, keyvals ...interfa // Like With the logging scope is append only but can be used to provide parenthetical scopes by hanging on to the // parent scope and using once the scope has been exited. The scope mechanism does is agnostic to the type of scope // so can be used to identify certain segments of the call stack, a lexical scope, or any other nested scope. -func WithScope(logger loggers.InfoTraceLogger, scopeName string) loggers.InfoTraceLogger { +func WithScope(logger types.InfoTraceLogger, scopeName string) types.InfoTraceLogger { // InfoTraceLogger will collapse successive (ScopeKey, scopeName) pairs into a vector in the order which they appear return logger.With(structure.ScopeKey, scopeName) } @@ -59,14 +49,3 @@ func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error { prepended := slice.CopyPrepend(keyvals, structure.MessageKey, message) return logger.Log(prepended...) } - -// Record a structured log line with a message and conventional keys -func MsgVals(logger kitlog.Logger, message string, vals ...interface{}) error { - keyvals := make([]interface{}, len(vals)*2) - for i := 0; i < len(vals); i++ { - kv := i * 2 - keyvals[kv] = structure.KeyFromValue(vals[i]) - keyvals[kv+1] = vals[i] - } - return Msg(logger, message, keyvals) -} diff --git a/logging/errors/multiple_errors.go b/logging/errors/multiple_errors.go new file mode 100644 index 0000000000000000000000000000000000000000..905da55ff76f56bf69df21ad06636b01761df1b2 --- /dev/null +++ b/logging/errors/multiple_errors.go @@ -0,0 +1,24 @@ +package errors + +import "strings" + +type MultipleErrors []error + +func CombineErrors(errs []error) error { + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return MultipleErrors(errs) + } +} + +func (errs MultipleErrors) Error() string { + var errStrings []string + for _, err := range errs { + errStrings = append(errStrings, err.Error()) + } + return strings.Join(errStrings, ";") +} diff --git a/logging/lifecycle/lifecycle.go b/logging/lifecycle/lifecycle.go index 4a5e1235d3a4a6720e62732da2d6d99b137d097f..bd9d6a839b1af71a62c4c1b43997557eb535e2c6 100644 --- a/logging/lifecycle/lifecycle.go +++ b/logging/lifecycle/lifecycle.go @@ -20,13 +20,18 @@ import ( "time" - "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/adapters/stdlib" tmLog15adapter "github.com/hyperledger/burrow/logging/adapters/tendermint_log15" + "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/logging/structure" + "fmt" + + "github.com/eapache/channels" kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/types" "github.com/streadway/simpleuuid" tmLog15 "github.com/tendermint/log15" ) @@ -35,38 +40,75 @@ import ( // to set up their root logger and capture any other logging output. // Obtain a logger from a LoggingConfig -func NewLoggerFromLoggingConfig(LoggingConfig *logging.LoggingConfig) loggers.InfoTraceLogger { - return NewStdErrLogger() +func NewLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (types.InfoTraceLogger, error) { + var logger types.InfoTraceLogger + var errCh channels.Channel + if loggingConfig == nil { + logger, errCh = NewStdErrLogger() + } else { + outputLogger, err := infoTraceLoggerFromLoggingConfig(loggingConfig) + if err != nil { + return nil, err + } + logger, errCh = NewLogger(outputLogger) + } + go func() { + err := <-errCh.Out() + if err != nil { + logger.Info("logging_error", err, + "logging_config", loggingConfig.RootTOMLString(), + "logger", fmt.Sprintf("%#v", logger)) + } + }() + + return logger, nil } -func NewStdErrLogger() loggers.InfoTraceLogger { - logger := tmLog15adapter.Log15HandlerAsKitLogger( - tmLog15.StreamHandler(os.Stderr, tmLog15.TerminalFormat())) - return NewLogger(logger, logger) +// Hot swap logging config by replacing output loggers of passed InfoTraceLogger +// with those built from loggingConfig +func SwapOutputLoggersFromLoggingConfig(logger types.InfoTraceLogger, + loggingConfig *config.LoggingConfig) error { + outputLogger, err := infoTraceLoggerFromLoggingConfig(loggingConfig) + if err != nil { + return err + } + logger.SwapOutput(outputLogger) + return nil } -// Provided a standard burrow logger that outputs to the supplied underlying info and trace -// loggers -func NewLogger(infoLogger, traceLogger kitlog.Logger) loggers.InfoTraceLogger { - infoTraceLogger := loggers.NewInfoTraceLogger( - loggers.MonaxFormatLogger(infoLogger), - loggers.MonaxFormatLogger(traceLogger)) +func NewStdErrLogger() (types.InfoTraceLogger, channels.Channel) { + logger := loggers.NewStreamLogger(os.Stderr, "terminal") + return NewLogger(logger) +} + +// Provided a standard logger that outputs to the supplied underlying outputLogger +func NewLogger(outputLogger kitlog.Logger) (types.InfoTraceLogger, channels.Channel) { + infoTraceLogger, errCh := loggers.NewInfoTraceLogger(outputLogger) // Create a random ID based on start time uuid, _ := simpleuuid.NewTime(time.Now()) var runId string if uuid != nil { runId = uuid.String() } - return logging.WithMetadata(infoTraceLogger.With(structure.RunId, runId)) + return logging.WithMetadata(infoTraceLogger.With(structure.RunId, runId)), errCh } -func CaptureTendermintLog15Output(infoTraceLogger loggers.InfoTraceLogger) { +func CaptureTendermintLog15Output(infoTraceLogger types.InfoTraceLogger) { tmLog15.Root().SetHandler( tmLog15adapter.InfoTraceLoggerAsLog15Handler(infoTraceLogger. With(structure.CapturedLoggingSourceKey, "tendermint_log15"))) } -func CaptureStdlibLogOutput(infoTraceLogger loggers.InfoTraceLogger) { +func CaptureStdlibLogOutput(infoTraceLogger types.InfoTraceLogger) { stdlib.CaptureRootLogger(infoTraceLogger. With(structure.CapturedLoggingSourceKey, "stdlib_log")) } + +// Helpers +func infoTraceLoggerFromLoggingConfig(loggingConfig *config.LoggingConfig) (kitlog.Logger, error) { + outputLogger, _, err := loggingConfig.RootSink.BuildLogger() + if err != nil { + return nil, err + } + return outputLogger, nil +} diff --git a/logging/lifecycle/lifecycle_test.go b/logging/lifecycle/lifecycle_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f0de3789dd94f58836ac24372aa5d0cdcf37da7f --- /dev/null +++ b/logging/lifecycle/lifecycle_test.go @@ -0,0 +1,59 @@ +package lifecycle + +import ( + "os" + "testing" + + "bufio" + + . "github.com/hyperledger/burrow/logging/config" + "github.com/stretchr/testify/assert" + "github.com/tendermint/log15" +) + +func TestNewLoggerFromLoggingConfig(t *testing.T) { + reader := CaptureStderr(t, func() { + logger, err := NewLoggerFromLoggingConfig(nil) + assert.NoError(t, err) + logger.Info("Quick", "Test") + }) + line, _, err := reader.ReadLine() + assert.NoError(t, err) + lineString := string(line) + assert.NotEmpty(t, lineString) +} + +func TestCaptureTendermintLog15Output(t *testing.T) { + reader := CaptureStderr(t, func() { + loggingConfig := &LoggingConfig{ + RootSink: Sink(). + SetOutput(StderrOutput().SetFormat("logfmt")). + SetTransform(FilterTransform(ExcludeWhenAllMatch, + "log_channel", "Trace", + )), + } + outputLogger, err := NewLoggerFromLoggingConfig(loggingConfig) + assert.NoError(t, err) + CaptureTendermintLog15Output(outputLogger) + log15Logger := log15.New() + log15Logger.Info("bar", "number_of_forks", 2) + }) + line, _, err := reader.ReadLine() + assert.NoError(t, err) + assert.Contains(t, string(line), "number_of_forks=2") + assert.Contains(t, string(line), "message=bar") +} + +func CaptureStderr(t *testing.T, runner func()) *bufio.Reader { + stderr := os.Stderr + defer func() { + os.Stderr = stderr + }() + r, w, err := os.Pipe() + assert.NoError(t, err, "Couldn't make fifo") + os.Stderr = w + + runner() + + return bufio.NewReader(r) +} diff --git a/logging/loggers/eris_format_logger.go b/logging/loggers/burrow_format_logger.go similarity index 82% rename from logging/loggers/eris_format_logger.go rename to logging/loggers/burrow_format_logger.go index e9d65d3c8b9e04d3427d75221e55a44d04cb7754..2af1b4032eb3d0576fbffc9296ad238dd500bf92 100644 --- a/logging/loggers/eris_format_logger.go +++ b/logging/loggers/burrow_format_logger.go @@ -20,6 +20,7 @@ import ( "github.com/hyperledger/burrow/logging/structure" kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/word256" ) // Logger that implements some formatting conventions for burrow and burrow-client @@ -34,6 +35,13 @@ type burrowFormatLogger struct { var _ kitlog.Logger = &burrowFormatLogger{} func (efl *burrowFormatLogger) Log(keyvals ...interface{}) error { + if efl.logger == nil { + return nil + } + if len(keyvals)%2 != 0 { + return fmt.Errorf("Log line contains an odd number of elements so "+ + "was dropped: %v", keyvals) + } return efl.logger.Log(structure.MapKeyValues(keyvals, burrowFormatKeyValueMapper)...) } @@ -43,11 +51,14 @@ func burrowFormatKeyValueMapper(key, value interface{}) (interface{}, interface{ switch v := value.(type) { case []byte: return key, fmt.Sprintf("%X", v) + case word256.Word256: + return burrowFormatKeyValueMapper(key, v.Bytes()) } + } return key, value } -func MonaxFormatLogger(logger kitlog.Logger) *burrowFormatLogger { +func BurrowFormatLogger(logger kitlog.Logger) *burrowFormatLogger { return &burrowFormatLogger{logger: logger} } diff --git a/logging/loggers/capture_logger.go b/logging/loggers/capture_logger.go new file mode 100644 index 0000000000000000000000000000000000000000..e195c3a454c24fdd9d8056cf937197a27642b7a5 --- /dev/null +++ b/logging/loggers/capture_logger.go @@ -0,0 +1,85 @@ +package loggers + +import ( + "sync" + + "github.com/eapache/channels" + kitlog "github.com/go-kit/kit/log" +) + +type CaptureLogger struct { + bufferLogger *ChannelLogger + outputLogger kitlog.Logger + passthrough bool + sync.RWMutex +} + +var _ kitlog.Logger = (*CaptureLogger)(nil) + +// Capture logger captures output set to it into a buffer logger and retains +// a reference to an output logger (the logger whose input it is capturing). +// It can optionally passthrough logs to the output logger. +// Because it holds a reference to its output it can also be used to coordinate +// Flushing of the buffer to the output logger in exceptional circumstances only +func NewCaptureLogger(outputLogger kitlog.Logger, bufferCap channels.BufferCap, + passthrough bool) *CaptureLogger { + return &CaptureLogger{ + bufferLogger: NewChannelLogger(bufferCap), + outputLogger: outputLogger, + passthrough: passthrough, + } +} + +func (cl *CaptureLogger) Log(keyvals ...interface{}) error { + err := cl.bufferLogger.Log(keyvals...) + if cl.Passthrough() { + err = cl.outputLogger.Log(keyvals...) + } + return err +} + +// Sets whether the CaptureLogger is forwarding log lines sent to it through +// to its output logger. Concurrently safe. +func (cl *CaptureLogger) SetPassthrough(passthrough bool) { + cl.RWMutex.Lock() + cl.passthrough = passthrough + cl.RWMutex.Unlock() +} + +// Gets whether the CaptureLogger is forwarding log lines sent to through to its +// OutputLogger. Concurrently Safe. +func (cl *CaptureLogger) Passthrough() bool { + cl.RWMutex.RLock() + passthrough := cl.passthrough + cl.RWMutex.RUnlock() + return passthrough +} + +// Flushes every log line available in the buffer at the time of calling +// to the OutputLogger and returns. Does not block indefinitely. +// +// Note: will remove log lines from buffer so they will not be produced on any +// subsequent flush of buffer +func (cl *CaptureLogger) Flush() { + cl.bufferLogger.Flush(cl.outputLogger) +} + +// Flushes every log line available in the buffer at the time of calling +// to a slice and returns it. Does not block indefinitely. +// +// Note: will remove log lines from buffer so they will not be produced on any +// subsequent flush of buffer +func (cl *CaptureLogger) FlushLogLines() [][]interface{} { + return cl.bufferLogger.FlushLogLines() +} + +// The OutputLogger whose input this CaptureLogger is capturing +func (cl *CaptureLogger) OutputLogger() kitlog.Logger { + return cl.outputLogger +} + +// The BufferLogger where the input into these CaptureLogger is stored in a ring +// buffer of log lines. +func (cl *CaptureLogger) BufferLogger() *ChannelLogger { + return cl.bufferLogger +} diff --git a/logging/loggers/capture_logger_test.go b/logging/loggers/capture_logger_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d6caea72cadaf5ee23665d0a7e32f4a0387adbe4 --- /dev/null +++ b/logging/loggers/capture_logger_test.go @@ -0,0 +1,49 @@ +package loggers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlushCaptureLogger(t *testing.T) { + outputLogger := newTestLogger() + cl := NewCaptureLogger(outputLogger, 100, false) + buffered := 50 + for i := 0; i < buffered; i++ { + cl.Log("Foo", "Bar", "Index", i) + } + assert.True(t, outputLogger.empty()) + + // Flush the ones we bufferred + cl.Flush() + _, err := outputLogger.logLines(buffered) + assert.NoError(t, err) +} + +func TestTeeCaptureLogger(t *testing.T) { + outputLogger := newTestLogger() + cl := NewCaptureLogger(outputLogger, 100, true) + buffered := 50 + for i := 0; i < buffered; i++ { + cl.Log("Foo", "Bar", "Index", i) + } + // Check passthrough to output + ll, err := outputLogger.logLines(buffered) + assert.NoError(t, err) + assert.Equal(t, ll, cl.BufferLogger().FlushLogLines()) + + cl.SetPassthrough(false) + buffered = 110 + for i := 0; i < buffered; i++ { + cl.Log("Foo", "Bar", "Index", i) + } + assert.True(t, outputLogger.empty()) + + cl.Flush() + _, err = outputLogger.logLines(100) + assert.NoError(t, err) + _, err = outputLogger.logLines(1) + // Expect timeout + assert.Error(t, err) +} diff --git a/logging/loggers/channel_logger.go b/logging/loggers/channel_logger.go index 1bacfa44697847389ab8b689f0a672cb3a3fa53b..47aeb38179a988243d2a3a9498ea1b76009fde5d 100644 --- a/logging/loggers/channel_logger.go +++ b/logging/loggers/channel_logger.go @@ -15,73 +15,142 @@ package loggers import ( + "sync" + "github.com/eapache/channels" kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging/errors" ) const ( - LoggingRingBufferCap channels.BufferCap = 100 + DefaultLoggingRingBufferCap channels.BufferCap = 100 ) type ChannelLogger struct { ch channels.Channel + sync.RWMutex } var _ kitlog.Logger = (*ChannelLogger)(nil) -// Creates a Logger that uses a uses a non-blocking channel. -// -// We would like calls to Log to never block so we use a channel implementation -// that is non-blocking on writes and is able to be so by using a finite ring -// buffer. -func newChannelLogger() *ChannelLogger { +// Creates a Logger that uses a uses a non-blocking ring buffered channel. +// This logger provides a common abstraction for both a buffered, flushable +// logging cache. And a non-blocking conduit to transmit logs via +// DrainForever (or NonBlockingLogger). +func NewChannelLogger(loggingRingBufferCap channels.BufferCap) *ChannelLogger { return &ChannelLogger{ - ch: channels.NewRingChannel(LoggingRingBufferCap), + ch: channels.NewRingChannel(loggingRingBufferCap), } } func (cl *ChannelLogger) Log(keyvals ...interface{}) error { + // In case channel is being reset + cl.RWMutex.RLock() cl.ch.In() <- keyvals + cl.RWMutex.RUnlock() // We don't have a way to pass on any logging errors, but that's okay: Log is // a maximal interface and the error return type is only there for special // cases. return nil } +// Get the current occupancy level of the ring buffer +func (cl *ChannelLogger) BufferLength() int { + return cl.ch.Len() +} + +// Get the cap off the internal ring buffer +func (cl *ChannelLogger) BufferCap() channels.BufferCap { + return cl.ch.Cap() +} + // Read a log line by waiting until one is available and returning it func (cl *ChannelLogger) WaitReadLogLine() []interface{} { - log := <-cl.ch.Out() - // We are passing slices of interfaces down this channel (go-kit log's Log - // interface type), a panic is the right thing to do if this type assertion - // fails. - return log.([]interface{}) + logLine, ok := <-cl.ch.Out() + return readLogLine(logLine, ok) } // Tries to read a log line from the channel buffer or returns nil if none is // immediately available func (cl *ChannelLogger) ReadLogLine() []interface{} { select { - case log := <-cl.ch.Out(): - // See WaitReadLogLine - return log.([]interface{}) + case logLine, ok := <-cl.ch.Out(): + return readLogLine(logLine, ok) default: return nil } } +func readLogLine(logLine interface{}, ok bool) []interface{} { + if !ok { + // Channel closed + return nil + } + // We are passing slices of interfaces down this channel (go-kit log's Log + // interface type), a panic is the right thing to do if this type assertion + // fails. + return logLine.([]interface{}) +} + // Enters an infinite loop that will drain any log lines from the passed logger. +// You may pass in a channel // // Exits if the channel is closed. -func (cl *ChannelLogger) DrainChannelToLogger(logger kitlog.Logger) { - for cl.ch.Out() != nil { - logger.Log(cl.WaitReadLogLine()...) +func (cl *ChannelLogger) DrainForever(logger kitlog.Logger, errCh channels.Channel) { + // logLine could be nil if channel was closed while waiting for next line + for logLine := cl.WaitReadLogLine(); logLine != nil; logLine = cl.WaitReadLogLine() { + err := logger.Log(logLine...) + if err != nil && errCh != nil { + errCh.In() <- err + } + } +} + +// Drains everything that is available at the time of calling +func (cl *ChannelLogger) Flush(logger kitlog.Logger) error { + // Grab the buffer at the here rather than within loop condition so that we + // do not drain the buffer forever + bufferLength := cl.BufferLength() + var errs []error + for i := 0; i < bufferLength; i++ { + logLine := cl.WaitReadLogLine() + if logLine != nil { + err := logger.Log(logLine...) + if err != nil { + errs = append(errs, err) + } + } } + return errors.CombineErrors(errs) +} + +// Drains the next contiguous segment of loglines up to the buffer cap waiting +// for at least one line +func (cl *ChannelLogger) FlushLogLines() [][]interface{} { + logLines := make([][]interface{}, 0, cl.ch.Len()) + cl.Flush(kitlog.LoggerFunc(func(keyvals ...interface{}) error { + logLines = append(logLines, keyvals) + return nil + })) + return logLines +} + +// Close the existing channel halting goroutines that are draining the channel +// and create a new channel to buffer into. Should not cause any log lines +// arriving concurrently to be lost, but any that have not been drained from +// old channel may be. +func (cl *ChannelLogger) Reset() { + cl.RWMutex.Lock() + cl.ch.Close() + cl.ch = channels.NewRingChannel(cl.ch.Cap()) + cl.RWMutex.Unlock() } -// Wraps an underlying Logger baseLogger to provide a Logger that is -// is non-blocking on calls to Log. -func NonBlockingLogger(logger kitlog.Logger) *ChannelLogger { - cl := newChannelLogger() - go cl.DrainChannelToLogger(logger) - return cl +// Returns a Logger that wraps the outputLogger passed and does not block on +// calls to Log and a channel of any errors from the underlying logger +func NonBlockingLogger(outputLogger kitlog.Logger) (*ChannelLogger, channels.Channel) { + cl := NewChannelLogger(DefaultLoggingRingBufferCap) + errCh := channels.NewRingChannel(cl.BufferCap()) + go cl.DrainForever(outputLogger, errCh) + return cl, errCh } diff --git a/logging/loggers/channel_logger_test.go b/logging/loggers/channel_logger_test.go index 0573556e15f70b11ec1163e36ab5435106e612ab..ee8a105e9156b6dd8fdae00d285c0d8c047668b0 100644 --- a/logging/loggers/channel_logger_test.go +++ b/logging/loggers/channel_logger_test.go @@ -17,21 +17,25 @@ package loggers import ( "testing" + "time" + "fmt" + "github.com/eapache/channels" "github.com/stretchr/testify/assert" ) func TestChannelLogger(t *testing.T) { - cl := newChannelLogger() + loggingRingBufferCap := channels.BufferCap(5) + cl := NewChannelLogger(loggingRingBufferCap) // Push a larger number of log messages than will fit into ring buffer - for i := 0; i < int(LoggingRingBufferCap)+10; i++ { + for i := 0; i < int(loggingRingBufferCap)+10; i++ { cl.Log("log line", i) } // Observe that oldest 10 messages are overwritten (so first message is 10) - for i := 0; i < int(LoggingRingBufferCap); i++ { + for i := 0; i < int(loggingRingBufferCap); i++ { ll := cl.WaitReadLogLine() assert.Equal(t, 10+i, ll[1]) } @@ -40,8 +44,43 @@ func TestChannelLogger(t *testing.T) { "should be no more log lines.") } -func TestBlether(t *testing.T) { - var bs []byte - ext := append(bs) - fmt.Println(ext) +func TestChannelLogger_Reset(t *testing.T) { + loggingRingBufferCap := channels.BufferCap(5) + cl := NewChannelLogger(loggingRingBufferCap) + for i := 0; i < int(loggingRingBufferCap); i++ { + cl.Log("log line", i) + } + cl.Reset() + for i := 0; i < int(loggingRingBufferCap); i++ { + cl.Log("log line", i) + } + for i := 0; i < int(loggingRingBufferCap); i++ { + ll := cl.WaitReadLogLine() + assert.Equal(t, i, ll[1]) + } + assert.Nil(t, cl.ReadLogLine(), "Since we have drained the buffer there "+ + "should be no more log lines.") +} + +func TestNonBlockingLogger(t *testing.T) { + tl := newTestLogger() + nbl, _ := NonBlockingLogger(tl) + nbl.Log("Foo", "Bar") + nbl.Log("Baz", "Bur") + nbl.Log("Badger", "Romeo") + time.Sleep(time.Second) + + lls, err := tl.logLines(3) + assert.NoError(t, err) + assert.Equal(t, logLines("Foo", "Bar", "", + "Baz", "Bur", "", + "Badger", "Romeo"), lls) +} + +func TestNonBlockingLoggerErrors(t *testing.T) { + el := newErrorLogger("Should surface") + nbl, errCh := NonBlockingLogger(el) + nbl.Log("failure", "true") + assert.Equal(t, "Should surface", + fmt.Sprintf("%s", <-errCh.Out())) } diff --git a/logging/loggers/filter_logger.go b/logging/loggers/filter_logger.go new file mode 100644 index 0000000000000000000000000000000000000000..ba9e542f525c41ec0b54b1b8e292ee2e22134ab6 --- /dev/null +++ b/logging/loggers/filter_logger.go @@ -0,0 +1,28 @@ +package loggers + +import kitlog "github.com/go-kit/kit/log" + +// Filter logger allows us to filter lines logged to it before passing on to underlying +// output logger +type filterLogger struct { + logger kitlog.Logger + predicate func(keyvals []interface{}) bool +} + +var _ kitlog.Logger = (*filterLogger)(nil) + +func (fl filterLogger) Log(keyvals ...interface{}) error { + if !fl.predicate(keyvals) { + return fl.logger.Log(keyvals...) + } + return nil +} + +// Creates a logger that removes lines from output when the predicate evaluates true +func NewFilterLogger(outputLogger kitlog.Logger, + predicate func(keyvals []interface{}) bool) kitlog.Logger { + return &filterLogger{ + logger: outputLogger, + predicate: predicate, + } +} diff --git a/logging/loggers/filter_logger_test.go b/logging/loggers/filter_logger_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f200a8276668fd5d88b7a85586ad6f5a40e2e712 --- /dev/null +++ b/logging/loggers/filter_logger_test.go @@ -0,0 +1,18 @@ +package loggers + +import ( + "testing" + + . "github.com/hyperledger/burrow/util/slice" + "github.com/stretchr/testify/assert" +) + +func TestFilterLogger(t *testing.T) { + testLogger := NewChannelLogger(100) + filterLogger := NewFilterLogger(testLogger, func(keyvals []interface{}) bool { + return len(keyvals) > 0 && keyvals[0] == "Spoon" + }) + filterLogger.Log("Fish", "Present") + filterLogger.Log("Spoon", "Present") + assert.Equal(t, [][]interface{}{Slice("Fish", "Present")}, testLogger.FlushLogLines()) +} diff --git a/logging/loggers/info_trace_logger.go b/logging/loggers/info_trace_logger.go index 02016a3e44d85c6094284a1a943ed4ddf5cffe6b..2b9a553933f8f34132edf520af276f91764f264e 100644 --- a/logging/loggers/info_trace_logger.go +++ b/logging/loggers/info_trace_logger.go @@ -15,127 +15,87 @@ package loggers import ( + "github.com/eapache/channels" kitlog "github.com/go-kit/kit/log" "github.com/hyperledger/burrow/logging/structure" -) - -const ( - InfoChannelName = "Info" - TraceChannelName = "Trace" - - InfoLevelName = InfoChannelName - TraceLevelName = TraceChannelName + "github.com/hyperledger/burrow/logging/types" ) type infoTraceLogger struct { - infoLogger *kitlog.Context - traceLogger *kitlog.Context -} - -// InfoTraceLogger maintains two independent concurrently-safe channels of -// logging. The idea behind the independence is that you can ignore one channel -// with no performance penalty. For more fine grained filtering or aggregation -// the Info and Trace loggers can be decorated loggers that perform arbitrary -// filtering/routing/aggregation on log messages. -type InfoTraceLogger interface { - // Send a log message to the default channel - kitlog.Logger - - // Send an log message to the Info channel, formed of a sequence of key value - // pairs. Info messages should be operationally interesting to a human who is - // monitoring the logs. But not necessarily a human who is trying to - // understand or debug the system. Any handled errors or warnings should be - // sent to the Info channel (where you may wish to tag them with a suitable - // key-value pair to categorise them as such). - Info(keyvals ...interface{}) error - - // Send an log message to the Trace channel, formed of a sequence of key-value - // pairs. Trace messages can be used for any state change in the system that - // may be of interest to a machine consumer or a human who is trying to debug - // the system or trying to understand the system in detail. If the messages - // are very point-like and contain little structure, consider using a metric - // instead. - Trace(keyvals ...interface{}) error - - // A logging context (see go-kit log's Context). Takes a sequence key values - // via With or WithPrefix and ensures future calls to log will have those - // contextual values appended to the call to an underlying logger. - // Values can be dynamic by passing an instance of the kitlog.Valuer interface - // This provides an interface version of the kitlog.Context struct to be used - // For implementations that wrap a kitlog.Context. In addition it makes no - // assumption about the name or signature of the logging method(s). - // See InfoTraceLogger - - // Establish a context by appending contextual key-values to any existing - // contextual values - With(keyvals ...interface{}) InfoTraceLogger - - // Establish a context by prepending contextual key-values to any existing - // contextual values - WithPrefix(keyvals ...interface{}) InfoTraceLogger + infoContext kitlog.Logger + traceContext kitlog.Logger + outputLogger *kitlog.SwapLogger + outputLoggerErrors channels.Channel } // Interface assertions -var _ InfoTraceLogger = (*infoTraceLogger)(nil) -var _ kitlog.Logger = (InfoTraceLogger)(nil) +var _ types.InfoTraceLogger = (*infoTraceLogger)(nil) +var _ kitlog.Logger = (types.InfoTraceLogger)(nil) -func NewInfoTraceLogger(infoLogger, traceLogger kitlog.Logger) InfoTraceLogger { +// Create an InfoTraceLogger by passing the initial outputLogger. +func NewInfoTraceLogger(outputLogger kitlog.Logger) (types.InfoTraceLogger, channels.Channel) { // We will never halt the progress of a log emitter. If log output takes too // long will start dropping log lines by using a ring buffer. - // We also guard against any concurrency bugs in underlying loggers by feeding - // them from a single channel - logger := kitlog.NewContext(NonBlockingLogger(VectorValuedLogger( - MultipleChannelLogger( - map[string]kitlog.Logger{ - InfoChannelName: infoLogger, - TraceChannelName: traceLogger, - })))) + swapLogger := new(kitlog.SwapLogger) + swapLogger.Swap(outputLogger) + wrappedOutputLogger, errCh := wrapOutputLogger(swapLogger) return &infoTraceLogger{ - infoLogger: logger.With( - structure.ChannelKey, InfoChannelName, - structure.LevelKey, InfoLevelName, + outputLogger: swapLogger, + outputLoggerErrors: errCh, + // logging contexts + infoContext: kitlog.With(wrappedOutputLogger, + structure.ChannelKey, types.InfoChannelName, ), - traceLogger: logger.With( - structure.ChannelKey, TraceChannelName, - structure.LevelKey, TraceLevelName, + traceContext: kitlog.With(wrappedOutputLogger, + structure.ChannelKey, types.TraceChannelName, ), - } + }, errCh } -func NewNoopInfoTraceLogger() InfoTraceLogger { - noopLogger := kitlog.NewNopLogger() - return NewInfoTraceLogger(noopLogger, noopLogger) +func NewNoopInfoTraceLogger() types.InfoTraceLogger { + logger, _ := NewInfoTraceLogger(nil) + return logger } -func (l *infoTraceLogger) With(keyvals ...interface{}) InfoTraceLogger { +func (l *infoTraceLogger) With(keyvals ...interface{}) types.InfoTraceLogger { return &infoTraceLogger{ - infoLogger: l.infoLogger.With(keyvals...), - traceLogger: l.traceLogger.With(keyvals...), + outputLogger: l.outputLogger, + infoContext: kitlog.With(l.infoContext, keyvals...), + traceContext: kitlog.With(l.traceContext, keyvals...), } } -func (l *infoTraceLogger) WithPrefix(keyvals ...interface{}) InfoTraceLogger { +func (l *infoTraceLogger) WithPrefix(keyvals ...interface{}) types.InfoTraceLogger { return &infoTraceLogger{ - infoLogger: l.infoLogger.WithPrefix(keyvals...), - traceLogger: l.traceLogger.WithPrefix(keyvals...), + outputLogger: l.outputLogger, + infoContext: kitlog.WithPrefix(l.infoContext, keyvals...), + traceContext: kitlog.WithPrefix(l.traceContext, keyvals...), } } func (l *infoTraceLogger) Info(keyvals ...interface{}) error { - // We send Info and Trace log lines down the same pipe to keep them ordered - return l.infoLogger.Log(keyvals...) + return l.infoContext.Log(keyvals...) } func (l *infoTraceLogger) Trace(keyvals ...interface{}) error { - return l.traceLogger.Log(keyvals...) + return l.traceContext.Log(keyvals...) +} + +func (l *infoTraceLogger) SwapOutput(infoLogger kitlog.Logger) { + l.outputLogger.Swap(infoLogger) } // If logged to as a plain kitlog logger presume the message is for Trace // This favours keeping Info reasonably quiet. Note that an InfoTraceLogger -// aware adapter can make its own choices, but we tend to thing of logs from +// aware adapter can make its own choices, but we tend to think of logs from // dependencies as less interesting than logs generated by us or specifically // routed by us. func (l *infoTraceLogger) Log(keyvals ...interface{}) error { - l.Trace(keyvals...) - return nil + return l.Trace(keyvals...) +} + +// Wrap the output loggers with a a set of standard transforms, a non-blocking +// ChannelLogger and an outer context +func wrapOutputLogger(outputLogger kitlog.Logger) (kitlog.Logger, channels.Channel) { + return NonBlockingLogger(BurrowFormatLogger(VectorValuedLogger(outputLogger))) } diff --git a/logging/loggers/info_trace_logger_test.go b/logging/loggers/info_trace_logger_test.go index 1717455401d50c787be69f7a9ee99991c3836366..2356748dc102e873c86d02a327c0da1c0fa984cf 100644 --- a/logging/loggers/info_trace_logger_test.go +++ b/logging/loggers/info_trace_logger_test.go @@ -23,6 +23,11 @@ import ( func TestLogger(t *testing.T) { stderrLogger := kitlog.NewLogfmtLogger(os.Stderr) - logger := NewInfoTraceLogger(stderrLogger, stderrLogger) + logger, _ := NewInfoTraceLogger(stderrLogger) logger.Trace("hello", "barry") } + +func TestNewNoopInfoTraceLogger(t *testing.T) { + logger := NewNoopInfoTraceLogger() + logger.Trace("goodbye", "trevor") +} diff --git a/logging/loggers/logging_test.go b/logging/loggers/logging_test.go deleted file mode 100644 index 103d8ddf7c295cd5749e66ea5697c4eaa55791cf..0000000000000000000000000000000000000000 --- a/logging/loggers/logging_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package loggers - -import "errors" - -type testLogger struct { - logLines [][]interface{} - err error -} - -func newErrorLogger(errMessage string) *testLogger { - return &testLogger{err: errors.New(errMessage)} -} - -func newTestLogger() *testLogger { - return &testLogger{} -} - -func (tl *testLogger) Log(keyvals ...interface{}) error { - tl.logLines = append(tl.logLines, keyvals) - return tl.err -} diff --git a/logging/loggers/multiple_channel_logger.go b/logging/loggers/multiple_channel_logger.go deleted file mode 100644 index 7e43d93dbf1c79deb9d9a6965a1b1d94eb9c0c25..0000000000000000000000000000000000000000 --- a/logging/loggers/multiple_channel_logger.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package loggers - -import ( - "fmt" - - kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/structure" -) - -// This represents a 'SELECT ONE' type logger. When logged to it will search -// for the ChannelKey field, look that up in its map and send the log line there -// Otherwise logging is a noop (but an error will be returned - which is optional) -type MultipleChannelLogger map[string]kitlog.Logger - -var _ kitlog.Logger = MultipleChannelLogger(nil) - -// Like go-kit log's Log method only logs a message to the specified channelName -// which must be a member of this MultipleChannelLogger -func (mcl MultipleChannelLogger) Log(keyvals ...interface{}) error { - channel := structure.Value(keyvals, structure.ChannelKey) - if channel == nil { - return fmt.Errorf("MultipleChannelLogger could not select channel because"+ - " '%s' was not set in log message", structure.ChannelKey) - } - channelName, ok := channel.(string) - if !ok { - return fmt.Errorf("MultipleChannelLogger could not select channel because"+ - " channel was set to non-string value %v", channel) - } - logger := mcl[channelName] - if logger == nil { - return fmt.Errorf("Could not log to channel '%s', since it is not "+ - "registered with this MultipleChannelLogger (the underlying logger may "+ - "have been nil when passed to NewMultipleChannelLogger)", channelName) - } - return logger.Log(keyvals...) -} diff --git a/logging/loggers/multiple_channel_logger_test.go b/logging/loggers/multiple_channel_logger_test.go deleted file mode 100644 index 237729d8ee87e0af4d79607a94d82bd49e1b664d..0000000000000000000000000000000000000000 --- a/logging/loggers/multiple_channel_logger_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package loggers - -import ( - "runtime" - "testing" - "time" - - kitlog "github.com/go-kit/kit/log" - "github.com/hyperledger/burrow/logging/structure" - "github.com/stretchr/testify/assert" -) - -func TestMultipleChannelLogger(t *testing.T) { - boringLogger, interestingLogger := newTestLogger(), newTestLogger() - mcl := kitlog.NewContext(MultipleChannelLogger(map[string]kitlog.Logger{ - "Boring": boringLogger, - "Interesting": interestingLogger, - })) - err := mcl.With("time", kitlog.Valuer(func() interface{} { return "aa" })). - Log(structure.ChannelKey, "Boring", "foo", "bar") - assert.NoError(t, err, "Should log without an error") - // Wait for channel to drain - time.Sleep(time.Second) - runtime.Gosched() - assert.Equal(t, []interface{}{"time", "aa", structure.ChannelKey, "Boring", - "foo", "bar"}, - boringLogger.logLines[0]) -} diff --git a/logging/loggers/multiple_output_logger.go b/logging/loggers/multiple_output_logger.go index 89ae50a978964758ead7891c5e79ece832cd87ce..601cba2f138fb02bbd9323a0152d8776bd348ead 100644 --- a/logging/loggers/multiple_output_logger.go +++ b/logging/loggers/multiple_output_logger.go @@ -15,9 +15,8 @@ package loggers import ( - "strings" - kitlog "github.com/go-kit/kit/log" + "github.com/hyperledger/burrow/logging/errors" ) // This represents an 'AND' type logger. When logged to it will log to each of @@ -34,31 +33,19 @@ func (mol MultipleOutputLogger) Log(keyvals ...interface{}) error { errs = append(errs, err) } } - return combineErrors(errs) + return errors.CombineErrors(errs) } // Creates a logger that forks log messages to each of its outputLoggers func NewMultipleOutputLogger(outputLoggers ...kitlog.Logger) kitlog.Logger { - return MultipleOutputLogger(outputLoggers) -} - -type multipleErrors []error - -func combineErrors(errs []error) error { - switch len(errs) { - case 0: - return nil - case 1: - return errs[0] - default: - return multipleErrors(errs) - } -} - -func (errs multipleErrors) Error() string { - var errStrings []string - for _, err := range errs { - errStrings = append(errStrings, err.Error()) + moLogger := make(MultipleOutputLogger, 0, len(outputLoggers)) + // Flatten any MultipleOutputLoggers + for _, ol := range outputLoggers { + if ls, ok := ol.(MultipleOutputLogger); ok { + moLogger = append(moLogger, ls...) + } else { + moLogger = append(moLogger, ol) + } } - return strings.Join(errStrings, ";") + return moLogger } diff --git a/logging/loggers/multiple_output_logger_test.go b/logging/loggers/multiple_output_logger_test.go index 4f606376af8c44d8441bbb33d6d49d322138a833..ad41b3ca5bb3c0864271e92525492f89a8f086f3 100644 --- a/logging/loggers/multiple_output_logger_test.go +++ b/logging/loggers/multiple_output_logger_test.go @@ -17,16 +17,22 @@ package loggers import ( "testing" + "github.com/hyperledger/burrow/logging/errors" "github.com/stretchr/testify/assert" ) func TestNewMultipleOutputLogger(t *testing.T) { - a, b := newErrorLogger("error a"), newErrorLogger("error b") + a := newErrorLogger("error a") + b := newErrorLogger("error b") mol := NewMultipleOutputLogger(a, b) logLine := []interface{}{"msg", "hello"} - err := mol.Log(logLine...) + errLog := mol.Log(logLine...) expected := [][]interface{}{logLine} - assert.Equal(t, expected, a.logLines) - assert.Equal(t, expected, b.logLines) - assert.IsType(t, multipleErrors{}, err) + logLineA, err := a.logLines(1) + assert.NoError(t, err) + logLineB, err := b.logLines(1) + assert.NoError(t, err) + assert.Equal(t, expected, logLineA) + assert.Equal(t, expected, logLineB) + assert.IsType(t, errors.MultipleErrors{}, errLog) } diff --git a/logging/loggers/output_loggers.go b/logging/loggers/output_loggers.go new file mode 100644 index 0000000000000000000000000000000000000000..28f46fae38973c5df4c6ec858e717a2a0a8c83f8 --- /dev/null +++ b/logging/loggers/output_loggers.go @@ -0,0 +1,67 @@ +package loggers + +import ( + "io" + + "log/syslog" + "net/url" + + kitlog "github.com/go-kit/kit/log" + log15a "github.com/hyperledger/burrow/logging/adapters/tendermint_log15" + "github.com/tendermint/log15" +) + +const ( + syslogPriority = syslog.LOG_LOCAL0 + JSONFormat = "json" + LogfmtFormat = "logfmt" + TerminalFormat = "terminal" + defaultFormatName = TerminalFormat +) + +func NewStreamLogger(writer io.Writer, formatName string) kitlog.Logger { + switch formatName { + case JSONFormat: + return kitlog.NewJSONLogger(writer) + case LogfmtFormat: + return kitlog.NewLogfmtLogger(writer) + default: + return log15a.Log15HandlerAsKitLogger(log15.StreamHandler(writer, + format(formatName))) + } +} + +func NewFileLogger(path string, formatName string) (kitlog.Logger, error) { + handler, err := log15.FileHandler(path, format(formatName)) + return log15a.Log15HandlerAsKitLogger(handler), err +} + +func NewRemoteSyslogLogger(url *url.URL, tag, formatName string) (kitlog.Logger, error) { + handler, err := log15.SyslogNetHandler(url.Scheme, url.Host, syslogPriority, + tag, format(formatName)) + if err != nil { + return nil, err + } + return log15a.Log15HandlerAsKitLogger(handler), nil +} + +func NewSyslogLogger(tag, formatName string) (kitlog.Logger, error) { + handler, err := log15.SyslogHandler(syslogPriority, tag, format(formatName)) + if err != nil { + return nil, err + } + return log15a.Log15HandlerAsKitLogger(handler), nil +} + +func format(name string) log15.Format { + switch name { + case JSONFormat: + return log15.JsonFormat() + case LogfmtFormat: + return log15.LogfmtFormat() + case TerminalFormat: + return log15.TerminalFormat() + default: + return format(defaultFormatName) + } +} diff --git a/logging/loggers/shared_test.go b/logging/loggers/shared_test.go new file mode 100644 index 0000000000000000000000000000000000000000..94c2a15e7be3fc55334cca1226204da04bf277d9 --- /dev/null +++ b/logging/loggers/shared_test.go @@ -0,0 +1,81 @@ +package loggers + +import ( + "errors" + "fmt" + "time" + + kitlog "github.com/go-kit/kit/log" +) + +const logLineTimeout time.Duration = time.Second + +type testLogger struct { + channelLogger *ChannelLogger + logLineCh chan ([]interface{}) + err error +} + +func (tl *testLogger) empty() bool { + return tl.channelLogger.BufferLength() == 0 +} + +func (tl *testLogger) logLines(numberOfLines int) ([][]interface{}, error) { + logLines := make([][]interface{}, numberOfLines) + for i := 0; i < numberOfLines; i++ { + select { + case logLine := <-tl.logLineCh: + logLines[i] = logLine + case <-time.After(logLineTimeout): + return logLines, fmt.Errorf("Timed out waiting for log line "+ + "(waited %s)", logLineTimeout) + } + } + return logLines, nil +} + +func (tl *testLogger) Log(keyvals ...interface{}) error { + tl.channelLogger.Log(keyvals...) + return tl.err +} + +func newErrorLogger(errMessage string) *testLogger { + return makeTestLogger(errors.New(errMessage)) +} + +func newTestLogger() *testLogger { + return makeTestLogger(nil) +} + +func makeTestLogger(err error) *testLogger { + cl := NewChannelLogger(100) + logLineCh := make(chan ([]interface{})) + go cl.DrainForever(kitlog.LoggerFunc(func(keyvals ...interface{}) error { + logLineCh <- keyvals + return nil + }), nil) + return &testLogger{ + channelLogger: cl, + logLineCh: logLineCh, + err: err, + } +} + +// Takes a variadic argument of log lines as a list of key value pairs delimited +// by the empty string +func logLines(keyvals ...string) [][]interface{} { + llines := make([][]interface{}, 0) + line := make([]interface{}, 0) + for _, kv := range keyvals { + if kv == "" { + llines = append(llines, line) + line = make([]interface{}, 0) + } else { + line = append(line, kv) + } + } + if len(line) > 0 { + llines = append(llines, line) + } + return llines +} diff --git a/logging/loggers/vector_valued_logger_test.go b/logging/loggers/vector_valued_logger_test.go index a9e66ffc1344a8453b95714a592ffb849f4bb306..62d85f613d04008ee5036202149855163f65835b 100644 --- a/logging/loggers/vector_valued_logger_test.go +++ b/logging/loggers/vector_valued_logger_test.go @@ -25,7 +25,8 @@ func TestVectorValuedLogger(t *testing.T) { logger := newTestLogger() vvl := VectorValuedLogger(logger) vvl.Log("foo", "bar", "seen", 1, "seen", 3, "seen", 2) - + lls, err := logger.logLines(1) + assert.NoError(t, err) assert.Equal(t, Slice("foo", "bar", "seen", Slice(1, 3, 2)), - logger.logLines[0]) + lls[0]) } diff --git a/logging/metadata.go b/logging/metadata.go index 846adf52bd56b3884afb3a18c9c9cd2bd429433f..8d8ca1d32b25ecf59d02c2b57a972b9227db2c49 100644 --- a/logging/metadata.go +++ b/logging/metadata.go @@ -19,8 +19,8 @@ import ( kitlog "github.com/go-kit/kit/log" "github.com/go-stack/stack" - "github.com/hyperledger/burrow/logging/loggers" "github.com/hyperledger/burrow/logging/structure" + "github.com/hyperledger/burrow/logging/types" ) const ( @@ -36,12 +36,12 @@ var defaultTimestampUTCValuer kitlog.Valuer = func() interface{} { return time.Now() } -func WithMetadata(infoTraceLogger loggers.InfoTraceLogger) loggers.InfoTraceLogger { +func WithMetadata(infoTraceLogger types.InfoTraceLogger) types.InfoTraceLogger { return infoTraceLogger.With(structure.TimeKey, defaultTimestampUTCValuer, structure.CallerKey, kitlog.Caller(infoTraceLoggerCallDepth), - "trace", TraceValuer()) + structure.TraceKey, TraceValuer()) } func TraceValuer() kitlog.Valuer { - return func() interface{} { return stack.Trace() } + return func() interface{} { return stack.Trace().TrimBelow(stack.Caller(infoTraceLoggerCallDepth - 1)) } } diff --git a/logging/structure/structure.go b/logging/structure/structure.go index 39ea04ae7bf8541245e2d56885fcdd8b23fd7cfd..271d8564a5e266ac0eddfe6abed20c9f42a64207 100644 --- a/logging/structure/structure.go +++ b/logging/structure/structure.go @@ -14,21 +14,19 @@ package structure -import ( - "reflect" - - . "github.com/hyperledger/burrow/util/slice" -) +import . "github.com/hyperledger/burrow/util/slice" const ( // Log time (time.Time) TimeKey = "time" // Call site for log invocation (go-stack.Call) CallerKey = "caller" + // Trace for log call + TraceKey = "trace" // Level name (string) LevelKey = "level" // Channel name in a vector channel logging context - ChannelKey = "channel" + ChannelKey = "log_channel" // Log message (string) MessageKey = "message" // Captured logging source (like tendermint_log15, stdlib_log) @@ -48,6 +46,7 @@ const ( // the unmatched remainder keyvals as context as a slice of key-values. func ValuesAndContext(keyvals []interface{}, keys ...interface{}) (map[interface{}]interface{}, []interface{}) { + vals := make(map[interface{}]interface{}, len(keys)) context := make([]interface{}, len(keyvals)) copy(context, keyvals) @@ -74,6 +73,21 @@ func ValuesAndContext(keyvals []interface{}, return vals, context } +// Drops all key value pairs where the key is in keys +func RemoveKeys(keyvals []interface{}, keys ...interface{}) []interface{} { + keyvalsWithoutKeys := make([]interface{}, 0, len(keyvals)) +NEXT_KEYVAL: + for i := 0; i < 2*(len(keyvals)/2); i += 2 { + for _, key := range keys { + if keyvals[i] == key { + continue NEXT_KEYVAL + } + } + keyvalsWithoutKeys = append(keyvalsWithoutKeys, keyvals[i], keyvals[i+1]) + } + return keyvalsWithoutKeys +} + // Stateful index that tracks the location of a possible vector value type vectorValueindex struct { // Location of the value belonging to a key in output slice @@ -136,18 +150,6 @@ func Value(keyvals []interface{}, key interface{}) interface{} { return nil } -// Obtain a canonical key from a value. Useful for structured logging where the -// type of value alone may be sufficient to determine its key. Providing this -// function centralises any convention over type names -func KeyFromValue(val interface{}) string { - switch val.(type) { - case string: - return "text" - default: - return reflect.TypeOf(val).Name() - } -} - // Maps key values pairs with a function (key, value) -> (new key, new value) func MapKeyValues(keyvals []interface{}, fn func(interface{}, interface{}) (interface{}, interface{})) []interface{} { mappedKeyvals := make([]interface{}, len(keyvals)) diff --git a/logging/structure/structure_test.go b/logging/structure/structure_test.go index 29252df91f748b8b4a27c6e287bf5beb2fe56d6f..d7e147356460f27cdec71e8c4f30f28df44687f6 100644 --- a/logging/structure/structure_test.go +++ b/logging/structure/structure_test.go @@ -40,6 +40,7 @@ func TestVectorise(t *testing.T) { ) kvsVector := Vectorise(kvs, "occupation", "scope") + // Vectorise scope assert.Equal(t, Slice( "scope", Slice("lawnmower", "hose pipe", "rake"), "hub", "budub", @@ -48,3 +49,24 @@ func TestVectorise(t *testing.T) { ), kvsVector) } + +func TestRemoveKeys(t *testing.T) { + // Remove multiple of same key + assert.Equal(t, Slice("Fish", 9), + RemoveKeys(Slice("Foo", "Bar", "Fish", 9, "Foo", "Baz", "odd-key"), + "Foo")) + + // Remove multiple different keys + assert.Equal(t, Slice("Fish", 9), + RemoveKeys(Slice("Foo", "Bar", "Fish", 9, "Foo", "Baz", "Bar", 89), + "Foo", "Bar")) + + // Remove nothing but supply keys + assert.Equal(t, Slice("Foo", "Bar", "Fish", 9), + RemoveKeys(Slice("Foo", "Bar", "Fish", 9), + "A", "B", "C")) + + // Remove nothing since no keys supplied + assert.Equal(t, Slice("Foo", "Bar", "Fish", 9), + RemoveKeys(Slice("Foo", "Bar", "Fish", 9))) +} diff --git a/logging/types/info_trace_logger.go b/logging/types/info_trace_logger.go new file mode 100644 index 0000000000000000000000000000000000000000..613cf07ca00f6bc307998dcddee092938280e40f --- /dev/null +++ b/logging/types/info_trace_logger.go @@ -0,0 +1,58 @@ +package types + +import kitlog "github.com/go-kit/kit/log" + +const ( + InfoChannelName = "Info" + TraceChannelName = "Trace" + + InfoLevelName = InfoChannelName + TraceLevelName = TraceChannelName +) + +// InfoTraceLogger maintains provides two logging 'channels' that are interlaced +// to provide a coarse grained filter to distinguish human-consumable 'Info' +// messages and execution level 'Trace' messages. +type InfoTraceLogger interface { + // Send a log message to the default channel of the implementation + kitlog.Logger + + // Send an log message to the Info channel, formed of a sequence of key value + // pairs. Info messages should be operationally interesting to a human who is + // monitoring the logs. But not necessarily a human who is trying to + // understand or debug the system. Any handled errors or warnings should be + // sent to the Info channel (where you may wish to tag them with a suitable + // key-value pair to categorise them as such). + Info(keyvals ...interface{}) error + + // Send an log message to the Trace channel, formed of a sequence of key-value + // pairs. Trace messages can be used for any state change in the system that + // may be of interest to a machine consumer or a human who is trying to debug + // the system or trying to understand the system in detail. If the messages + // are very point-like and contain little structure, consider using a metric + // instead. + Trace(keyvals ...interface{}) error + + // A logging context (see go-kit log's Context). Takes a sequence key values + // via With or WithPrefix and ensures future calls to log will have those + // contextual values appended to the call to an underlying logger. + // Values can be dynamic by passing an instance of the kitlog.Valuer interface + // This provides an interface version of the kitlog.Context struct to be used + // For implementations that wrap a kitlog.Context. In addition it makes no + // assumption about the name or signature of the logging method(s). + // See InfoTraceLogger + + // Establish a context by appending contextual key-values to any existing + // contextual values + With(keyvals ...interface{}) InfoTraceLogger + + // Establish a context by prepending contextual key-values to any existing + // contextual values + WithPrefix(keyvals ...interface{}) InfoTraceLogger + + // Hot swap the underlying outputLogger with another one to re-route messages + SwapOutput(outputLogger kitlog.Logger) +} + +// Interface assertions +var _ kitlog.Logger = (InfoTraceLogger)(nil) diff --git a/manager/burrow-mint/accounts.go b/manager/burrow-mint/accounts.go index 1443a653e92afc4eb44f30790cfb1bfb530598ab..b0e58c3566d5c09ed40778ef2115c9f874368600 100644 --- a/manager/burrow-mint/accounts.go +++ b/manager/burrow-mint/accounts.go @@ -226,8 +226,3 @@ func (this *AccountBalanceFilter) Match(v interface{}) bool { } return this.match(int64(acc.Balance), this.value) } - -// Function for matching accounts against filter data. -func (this *accounts) matchBlock(block, fda []*event.FilterData) bool { - return false -} diff --git a/manager/burrow-mint/burrow-mint.go b/manager/burrow-mint/burrow-mint.go index 92219def5d8cfe35193d0820d5dcca46794b79f1..d3255452a3585f2da307dce4b65226e6767a2231 100644 --- a/manager/burrow-mint/burrow-mint.go +++ b/manager/burrow-mint/burrow-mint.go @@ -24,9 +24,9 @@ import ( tendermint_events "github.com/tendermint/go-events" wire "github.com/tendermint/go-wire" + consensus_types "github.com/hyperledger/burrow/consensus/types" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" - + logging_types "github.com/hyperledger/burrow/logging/types" sm "github.com/hyperledger/burrow/manager/burrow-mint/state" manager_types "github.com/hyperledger/burrow/manager/types" "github.com/hyperledger/burrow/txs" @@ -48,16 +48,25 @@ type BurrowMint struct { evsw tendermint_events.EventSwitch nTxs int // count txs in a block - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger +} + +// Currently we just wrap ConsensusEngine but this interface can give us +// arbitrary control over the type of ConsensusEngine at such a point that we +// support others. For example it can demand a 'marker' function: +// func IsBurrowMint_0.XX.XX_CompatibleConsensusEngine() +type BurrowMintCompatibleConsensusEngine interface { + consensus_types.ConsensusEngine } // NOTE [ben] Compiler check to ensure BurrowMint successfully implements // burrow/manager/types.Application var _ manager_types.Application = (*BurrowMint)(nil) -// NOTE: [ben] also automatically implements abci.Application, -// undesired but unharmful -// var _ abci.Application = (*BurrowMint)(nil) +func (app *BurrowMint) CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool { + _, ok := consensusEngine.(BurrowMintCompatibleConsensusEngine) + return ok +} func (app *BurrowMint) GetState() *sm.State { app.mtx.Lock() @@ -72,7 +81,8 @@ func (app *BurrowMint) GetCheckCache() *sm.BlockCache { return app.checkCache } -func NewBurrowMint(s *sm.State, evsw tendermint_events.EventSwitch, logger loggers.InfoTraceLogger) *BurrowMint { +func NewBurrowMint(s *sm.State, evsw tendermint_events.EventSwitch, + logger logging_types.InfoTraceLogger) *BurrowMint { return &BurrowMint{ state: s, cache: sm.NewBlockCache(s), @@ -107,7 +117,7 @@ func (app *BurrowMint) DeliverTx(txBytes []byte) abci.Result { return abci.NewError(abci.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) } - err = sm.ExecTx(app.cache, *tx, true, app.evc) + err = sm.ExecTx(app.cache, *tx, true, app.evc, app.logger) if err != nil { return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) } @@ -129,7 +139,7 @@ func (app *BurrowMint) CheckTx(txBytes []byte) abci.Result { } // TODO: map ExecTx errors to sensible abci error codes - err = sm.ExecTx(app.checkCache, *tx, false, nil) + err = sm.ExecTx(app.checkCache, *tx, false, nil, app.logger) if err != nil { return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) } @@ -176,6 +186,31 @@ func (app *BurrowMint) Commit() (res abci.Result) { return abci.NewResultOK(appHash, "Success") } -func (app *BurrowMint) Query(query []byte) (res abci.Result) { - return abci.NewResultOK(nil, "Success") +func (app *BurrowMint) Query(query abci.RequestQuery) (res abci.ResponseQuery) { + return abci.ResponseQuery{ + Code: abci.CodeType_OK, + Log: "success", + } +} + +// BlockchainAware interface + +// Initialise the blockchain +// validators: genesis validators from tendermint core +func (app *BurrowMint) InitChain(validators []*abci.Validator) { + // Could verify agreement on initial validator set here +} + +// Signals the beginning of a block +func (app *BurrowMint) BeginBlock(hash []byte, header *abci.Header) { + +} + +// Signals the end of a blockchain, return value can be used to modify validator +// set and voting power distribution see our BlockchainAware interface +func (app *BurrowMint) EndBlock(height uint64) (respEndblock abci.ResponseEndBlock) { + // TODO: [Silas] Bondage + // TODO: [Silas] this might be a better place for us to dispatch new block + // events particularly if we want to separate ourselves from go-events + return } diff --git a/manager/burrow-mint/burrow-mint_test.go b/manager/burrow-mint/burrow-mint_test.go deleted file mode 100644 index 9c1a647c2f3f4f532b55f12c49250e6e7368cfd9..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/burrow-mint_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package burrowmint - -import ( - "testing" - - assert "github.com/stretchr/testify/assert" -) - -func TestCompatibleConsensus(t *testing.T) { - // TODO: [ben] expand by constructing and elementary testing for each listed - // compatible consensus engine - - for _, listedConsensus := range compatibleConsensus { - assert.Nil(t, AssertCompatibleConsensus(listedConsensus)) - } -} diff --git a/manager/burrow-mint/evm/log.go b/manager/burrow-mint/evm/log.go deleted file mode 100644 index 8b5565197bf62c487aed2852e7c65998cc5a0019..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/evm/log.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package vm - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "vm") diff --git a/manager/burrow-mint/evm/log_event_test.go b/manager/burrow-mint/evm/log_event_test.go index 91431adc7477fe1a29e77f4dfdf42aaa8e997f60..7dd569a63008a5cf0bbf43a0ef31f7f94e3cd451 100644 --- a/manager/burrow-mint/evm/log_event_test.go +++ b/manager/burrow-mint/evm/log_event_test.go @@ -47,7 +47,7 @@ func TestLog4(t *testing.T) { st.accounts[account1.Address.String()] = account1 st.accounts[account2.Address.String()] = account2 - ourVm := NewVM(st, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) eventSwitch := events.NewEventSwitch() _, err := eventSwitch.Start() diff --git a/manager/burrow-mint/evm/memory.go b/manager/burrow-mint/evm/memory.go new file mode 100644 index 0000000000000000000000000000000000000000..fc24dbd7d85adcfbbb1ca26f0508675b1047e5fb --- /dev/null +++ b/manager/burrow-mint/evm/memory.go @@ -0,0 +1,115 @@ +package vm + +import ( + "fmt" + "math" +) + +const ( + defaultInitialMemoryCapacity = 0x100000 // 1 MiB + defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB +) + +// Change the length of this zero array to tweak the size of the block of zeros +// written to the backing slice at a time when it is grown. A larger number may +// lead to less calls to append to achieve the desired capacity although it is +// unlikely to make a lot of difference. +var zeroBlock []byte = make([]byte, 32) + +// Interface for a bounded linear memory indexed by a single int64 parameter +// for each byte in the memory. +type Memory interface { + // Read a value from the memory store starting at offset + // (index of first byte will equal offset). The value will be returned as a + // length-bytes byte slice. Returns an error if the memory cannot be read or + // is not allocated. + // + // The value returned should be copy of any underlying memory, not a reference + // to the underlying store. + Read(offset, length int64) ([]byte, error) + // Write a value to the memory starting at offset (the index of the first byte + // written will equal offset). The value is provided as bytes to be written + // consecutively to the memory store. Return an error if the memory cannot be + // written or allocated. + Write(offset int64, value []byte) error + // Returns the current capacity of the memory. For dynamically allocating + // memory this capacity can be used as a write offset that is guaranteed to be + // unused. Solidity in particular makes this assumption when using MSIZE to + // get the current allocated memory. + Capacity() int64 +} + +func NewDynamicMemory(initialCapacity, maximumCapacity int64) Memory { + return &dynamicMemory{ + slice: make([]byte, initialCapacity), + maximumCapacity: maximumCapacity, + } +} + +func DefaultDynamicMemoryProvider() Memory { + return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity) +} + +// Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic +// array allocation via a backing slice +type dynamicMemory struct { + slice []byte + maximumCapacity int64 +} + +func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) { + capacity := offset + length + err := mem.ensureCapacity(capacity) + if err != nil { + return nil, err + } + value := make([]byte, length) + copy(value, mem.slice[offset:capacity]) + return value, nil +} + +func (mem *dynamicMemory) Write(offset int64, value []byte) error { + capacity := offset + int64(len(value)) + err := mem.ensureCapacity(capacity) + if err != nil { + return err + } + copy(mem.slice[offset:capacity], value) + return nil +} + +func (mem *dynamicMemory) Capacity() int64 { + return int64(len(mem.slice)) +} + +// Ensures the current memory store can hold newCapacity. Will only grow the +// memory (will not shrink). +func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { + if newCapacity > math.MaxInt32 { + // If we ever did want to then we would need to maintain multiple pages + // of memory + return fmt.Errorf("Cannot address memory beyond a maximum index "+ + "of Int32 type (%v bytes)", math.MaxInt32) + } + newCapacityInt := int(newCapacity) + // We're already big enough so return + if newCapacityInt <= len(mem.slice) { + return nil + } + if newCapacity > mem.maximumCapacity { + return fmt.Errorf("Cannot grow memory because it would exceed the "+ + "current maximum limit of %v bytes", mem.maximumCapacity) + } + // Ensure the backing array of slice is big enough + // Grow the memory one word at time using the pre-allocated zeroBlock to avoid + // unnecessary allocations. Use append to make use of any spare capacity in + // the slice's backing array. + for newCapacityInt > cap(mem.slice) { + // We'll trust Go exponentially grow our arrays (at first). + mem.slice = append(mem.slice, zeroBlock...) + } + // Now we've ensured the backing array of the slice is big enough we can + // just re-slice (even if len(mem.slice) < newCapacity) + mem.slice = mem.slice[:newCapacity] + return nil +} diff --git a/manager/burrow-mint/evm/memory_test.go b/manager/burrow-mint/evm/memory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2477b32d8292aa2c0aab36d4af575458322fb26b --- /dev/null +++ b/manager/burrow-mint/evm/memory_test.go @@ -0,0 +1,120 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test static memory allocation with maximum == initial capacity - memory should not grow +func TestDynamicMemory_StaticAllocation(t *testing.T) { + mem := NewDynamicMemory(4, 4).(*dynamicMemory) + mem.Write(0, []byte{1}) + mem.Write(1, []byte{0, 0, 1}) + assert.Equal(t, []byte{1, 0, 0, 1}, mem.slice) + assert.Equal(t, 4, cap(mem.slice), "Slice capacity should not grow") +} + +// Test reading beyond the current capacity - memory should grow +func TestDynamicMemory_ReadAhead(t *testing.T) { + mem := NewDynamicMemory(4, 8).(*dynamicMemory) + value, err := mem.Read(2, 4) + assert.NoError(t, err) + // Value should be size requested + assert.Equal(t, []byte{0, 0, 0, 0}, value) + // Slice should have grown to that plus offset + assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, mem.slice) + + value, err = mem.Read(2, 6) + assert.NoError(t, err) + assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, value) + assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0}, mem.slice) + + // Check cannot read out of bounds + _, err = mem.Read(2, 7) + assert.Error(t, err) +} + +// Test writing beyond the current capacity - memory should grow +func TestDynamicMemory_WriteAhead(t *testing.T) { + mem := NewDynamicMemory(4, 8).(*dynamicMemory) + err := mem.Write(4, []byte{1, 2, 3, 4}) + assert.NoError(t, err) + assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice) + + err = mem.Write(4, []byte{1, 2, 3, 4, 5}) + assert.Error(t, err) +} + +func TestDynamicMemory_WriteRead(t *testing.T) { + mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) + // Text is out of copyright + bytesToWrite := []byte(`He paused. He felt the rhythm of the verse about him in the room. +How melancholy it was! Could he, too, write like that, express the +melancholy of his soul in verse? There were so many things he wanted +to describe: his sensation of a few hours before on Grattan Bridge, for +example. If he could get back again into that mood....`) + + // Write the bytes + offset := 0x1000000 + err := mem.Write(int64(offset), bytesToWrite) + assert.NoError(t, err) + assert.Equal(t, append(make([]byte, offset), bytesToWrite...), mem.slice) + assert.Equal(t, offset+len(bytesToWrite), len(mem.slice)) + + // Read them back + value, err := mem.Read(int64(offset), int64(len(bytesToWrite))) + assert.NoError(t, err) + assert.Equal(t, bytesToWrite, value) +} + +func TestDynamicMemory_ZeroInitialMemory(t *testing.T) { + mem := NewDynamicMemory(0, 16).(*dynamicMemory) + err := mem.Write(4, []byte{1, 2, 3, 4}) + assert.NoError(t, err) + assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice) +} + +func TestDynamicMemory_Capacity(t *testing.T) { + mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) + + assert.Equal(t, int64(1), mem.Capacity()) + + capacity := int64(1234) + err := mem.ensureCapacity(capacity) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) + + capacity = int64(123456789) + err = mem.ensureCapacity(capacity) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) + + // Check doesn't shrink or err + err = mem.ensureCapacity(12) + assert.NoError(t, err) + assert.Equal(t, capacity, mem.Capacity()) +} + +func TestDynamicMemory_ensureCapacity(t *testing.T) { + mem := NewDynamicMemory(4, 16).(*dynamicMemory) + // Check we can grow within bounds + err := mem.ensureCapacity(8) + assert.NoError(t, err) + expected := make([]byte, 8) + assert.Equal(t, expected, mem.slice) + + // Check we can grow to bounds + err = mem.ensureCapacity(16) + assert.NoError(t, err) + expected = make([]byte, 16) + assert.Equal(t, expected, mem.slice) + + err = mem.ensureCapacity(1) + assert.NoError(t, err) + assert.Equal(t, 16, len(mem.slice)) + + err = mem.ensureCapacity(17) + assert.Error(t, err, "Should not be possible to grow over capacity") + +} diff --git a/manager/burrow-mint/evm/native.go b/manager/burrow-mint/evm/native.go index ab4e32074c1eb459da62275a78778d6b01a2c17e..17478055c1ad61d55ec150cbf1f8028c900370f3 100644 --- a/manager/burrow-mint/evm/native.go +++ b/manager/burrow-mint/evm/native.go @@ -24,8 +24,8 @@ import ( var registeredNativeContracts = make(map[Word256]NativeContract) -func RegisteredNativeContract(addr Word256) bool { - _, ok := registeredNativeContracts[addr] +func RegisteredNativeContract(address Word256) bool { + _, ok := registeredNativeContracts[address] return ok } diff --git a/manager/burrow-mint/evm/opcodes/opcodes.go b/manager/burrow-mint/evm/opcodes/opcodes.go index d633b5e5f717fe034dc5b065d6f4c461a8203a0a..a8139d82f25bde769328ea169b176facd913a75c 100644 --- a/manager/burrow-mint/evm/opcodes/opcodes.go +++ b/manager/burrow-mint/evm/opcodes/opcodes.go @@ -185,7 +185,7 @@ const ( DELEGATECALL // 0x70 range - other - SUICIDE = 0xff + SELFDESTRUCT = 0xff ) // Since the opcodes aren't all in order we can't use a regular slice @@ -244,9 +244,7 @@ var opCodeToString = map[OpCode]string{ EXTCODECOPY: "EXTCODECOPY", // 0x50 range - 'storage' and execution - POP: "POP", - //DUP: "DUP", - //SWAP: "SWAP", + POP: "POP", MLOAD: "MLOAD", MSTORE: "MSTORE", MSTORE8: "MSTORE8", @@ -340,7 +338,7 @@ var opCodeToString = map[OpCode]string{ DELEGATECALL: "DELEGATECALL", // 0x70 range - other - SUICIDE: "SUICIDE", + SELFDESTRUCT: "SELFDESTRUCT", } func (o OpCode) String() string { diff --git a/manager/burrow-mint/evm/sha3/keccakf.go b/manager/burrow-mint/evm/sha3/keccakf.go index 107156cc9d325766aaa3dccf737fb7201334641d..e12792fdc6525bac4455556dd69ed67ab8bbf48d 100644 --- a/manager/burrow-mint/evm/sha3/keccakf.go +++ b/manager/burrow-mint/evm/sha3/keccakf.go @@ -40,7 +40,7 @@ var rc = [...]uint64{ // ro_xx represent the rotation offsets for use in the χ step. // Defining them as const instead of in an array allows the compiler to insert constant shifts. const ( - ro_00 = 0 + ro_00 = 0 // not used ro_01 = 36 ro_02 = 3 ro_03 = 41 diff --git a/manager/burrow-mint/evm/snative.go b/manager/burrow-mint/evm/snative.go index 37e1031c34ced1480c53a39ccbb26fdec475d42b..3f628e99d328e1f44ec04c0ad9ff4b19b8f5adc4 100644 --- a/manager/burrow-mint/evm/snative.go +++ b/manager/burrow-mint/evm/snative.go @@ -87,10 +87,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "addRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.AddRole, addRole}, @@ -102,10 +102,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "removeRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.RmRole, removeRole}, @@ -117,10 +117,10 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasRole", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_role", roleTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_role", roleTypeName), }, - ret("result", abi.BoolTypeName), + abiReturn("result", abi.BoolTypeName), ptypes.HasRole, hasRole}, @@ -133,11 +133,11 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName), - arg("_set", abi.BoolTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName), + abiArg("_set", abi.BoolTypeName), }, - ret("result", permFlagTypeName), + abiReturn("result", permFlagTypeName), ptypes.SetBase, setBase}, @@ -149,9 +149,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "unsetBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName)}, - ret("result", permFlagTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName)}, + abiReturn("result", permFlagTypeName), ptypes.UnsetBase, unsetBase}, @@ -163,9 +163,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "hasBase", []abi.Arg{ - arg("_account", abi.AddressTypeName), - arg("_permission", permFlagTypeName)}, - ret("result", permFlagTypeName), + abiArg("_account", abi.AddressTypeName), + abiArg("_permission", permFlagTypeName)}, + abiReturn("result", abi.BoolTypeName), ptypes.HasBase, hasBase}, @@ -177,9 +177,9 @@ func SNativeContracts() map[string]*SNativeContractDescription { `, "setGlobal", []abi.Arg{ - arg("_permission", permFlagTypeName), - arg("_set", abi.BoolTypeName)}, - ret("result", permFlagTypeName), + abiArg("_permission", permFlagTypeName), + abiArg("_set", abi.BoolTypeName)}, + abiReturn("result", permFlagTypeName), ptypes.SetGlobal, setGlobal}, ), @@ -324,14 +324,14 @@ func (function *SNativeFunctionDescription) NArgs() int { return len(function.Args) } -func arg(name string, abiTypeName abi.TypeName) abi.Arg { +func abiArg(name string, abiTypeName abi.TypeName) abi.Arg { return abi.Arg{ Name: name, TypeName: abiTypeName, } } -func ret(name string, abiTypeName abi.TypeName) abi.Return { +func abiReturn(name string, abiTypeName abi.TypeName) abi.Return { return abi.Return{ Name: name, TypeName: abiTypeName, @@ -464,10 +464,7 @@ func (e ErrInvalidPermission) Error() string { // Checks if a permission flag is valid (a known base chain or snative permission) func ValidPermN(n ptypes.PermFlag) bool { - if n > ptypes.TopPermFlag { - return false - } - return true + return n <= ptypes.TopPermFlag } // Get the global BasePermissions diff --git a/manager/burrow-mint/evm/stack.go b/manager/burrow-mint/evm/stack.go index e383be25d60d3d479623b6e3b92c7ebbcd674d6e..f9d48986b2040aa7592f512f5e53aa7b89ca4a8c 100644 --- a/manager/burrow-mint/evm/stack.go +++ b/manager/burrow-mint/evm/stack.go @@ -106,7 +106,6 @@ func (st *Stack) Swap(n int) { return } st.data[st.ptr-n], st.data[st.ptr-1] = st.data[st.ptr-1], st.data[st.ptr-n] - return } func (st *Stack) Dup(n int) { @@ -116,7 +115,6 @@ func (st *Stack) Dup(n int) { return } st.Push(st.data[st.ptr-n]) - return } // Not an opcode, costs no gas. diff --git a/manager/burrow-mint/evm/vm.go b/manager/burrow-mint/evm/vm.go index d2c6e88d2be81fdfcee76458e093c5f5b010657d..60ae0f64e3bc0ffffa65e4f7849f75bb6221dab9 100644 --- a/manager/burrow-mint/evm/vm.go +++ b/manager/burrow-mint/evm/vm.go @@ -20,7 +20,6 @@ import ( "fmt" "math/big" - "github.com/hyperledger/burrow/common/math/integral" "github.com/hyperledger/burrow/common/sanity" . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" @@ -57,8 +56,7 @@ func (err ErrPermission) Error() string { const ( dataStackCapacity = 1024 - callStackCapacity = 100 // TODO ensure usage. - memoryCapacity = 1024 * 1024 // 1 MB + callStackCapacity = 100 // TODO ensure usage. ) type Debug bool @@ -76,23 +74,26 @@ func (d Debug) Printf(s string, a ...interface{}) { } type VM struct { - appState AppState - params Params - origin Word256 - txid []byte + appState AppState + memoryProvider func() Memory + params Params + origin Word256 + txid []byte callDepth int evc events.Fireable } -func NewVM(appState AppState, params Params, origin Word256, txid []byte) *VM { +func NewVM(appState AppState, memoryProvider func() Memory, params Params, + origin Word256, txid []byte) *VM { return &VM{ - appState: appState, - params: params, - origin: origin, - callDepth: 0, - txid: txid, + appState: appState, + memoryProvider: memoryProvider, + params: params, + origin: origin, + callDepth: 0, + txid: txid, } } @@ -106,12 +107,12 @@ func (vm *VM) SetFireable(evc events.Fireable) { // (unlike in state/execution, where we guarantee HasPermission is called // on known permissions and panics else) // If the perm is not defined in the acc nor set by default in GlobalPermissions, -// prints a log warning and returns false. +// this function returns false. func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool { v, err := acc.Permissions.Base.Get(perm) if _, ok := err.(ptypes.ErrValueNotSet); ok { if appState == nil { - log.Warn(fmt.Sprintf("\n\n***** Unknown permission %b! ********\n\n", perm)) + // In this case the permission is unknown return false } return HasPermission(nil, appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) @@ -211,7 +212,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas var ( pc int64 = 0 stack = NewStack(dataStackCapacity, gas, &err) - memory = make([]byte, memoryCapacity) + memory = vm.memoryProvider() ) for { @@ -488,8 +489,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas return nil, err } offset, size := stack.Pop64(), stack.Pop64() - data, ok := subslice(memory, offset, size) - if !ok { + data, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } data = sha3.Sha3(data) @@ -547,11 +549,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrInputOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 @@ -567,11 +569,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A @@ -617,11 +619,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) } - dest, ok := subslice(memory, memOff, length) - if !ok { + memErr := memory.Write(memOff, data) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data) dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 @@ -652,28 +654,30 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case MLOAD: // 0x51 offset := stack.Pop64() - data, ok := subslice(memory, offset, 32) - if !ok { + data, memErr := memory.Read(offset, 32) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(LeftPadWord256(data)) - dbg.Printf(" => 0x%X\n", data) + dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE: // 0x52 offset, data := stack.Pop64(), stack.Pop() - dest, ok := subslice(memory, offset, 32) - if !ok { + memErr := memory.Write(offset, data.Bytes()) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, data[:]) - dbg.Printf(" => 0x%X\n", data) + dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) - if len(memory) <= int(offset) { + memErr := memory.Write(offset, []byte{val}) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - memory[offset] = val dbg.Printf(" => [%v] 0x%X\n", offset, val) case SLOAD: // 0x54 @@ -710,7 +714,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas stack.Push64(pc) case MSIZE: // 0x59 - stack.Push64(int64(len(memory))) + // Note: Solidity will write to this offset expecting to find guaranteed + // free memory to be allocated for it if a subsequent MSTORE is made to + // this offset. + capacity := memory.Capacity() + stack.Push64(capacity) + dbg.Printf(" => 0x%X\n", capacity) case GAS: // 0x5A stack.Push64(*gas) @@ -750,11 +759,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas for i := 0; i < n; i++ { topics[i] = stack.Pop() } - data, ok := subslice(memory, offset, size) - if !ok { + data, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - data = copyslice(data) if vm.evc != nil { eventID := txs.EventStringLogEvent(callee.Address.Postfix(20)) fmt.Printf("eventID: %s\n", eventID) @@ -774,8 +783,9 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } contractValue := stack.Pop64() offset, size := stack.Pop64(), stack.Pop64() - input, ok := subslice(memory, offset, size) - if !ok { + input, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } @@ -785,7 +795,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := vm.appState.CreateAccount(callee) // Run the input to get the contract code. @@ -817,11 +826,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas dbg.Printf(" => %X\n", addr) // Get the arguments from the memory - args, ok := subslice(memory, inOffset, inSize) - if !ok { + args, memErr := memory.Read(inOffset, inSize) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - args = copyslice(args) // Ensure that gasLimit is reasonable if *gas < gasLimit { @@ -885,11 +894,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas stack.Push(Zero256) } else { stack.Push(One256) - dest, ok := subslice(memory, retOffset, retSize) - if !ok { + + // Should probably only be necessary when there is no return value and + // ret is empty, but since EVM expects retSize to be respected this will + // defensively pad or truncate the portion of ret to be returned. + memErr := memory.Write(retOffset, RightPadBytes(ret, int(retSize))) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - copy(dest, ret) } // Handle remaining gas. @@ -899,15 +912,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() - ret, ok := subslice(memory, offset, size) - if !ok { + output, memErr := memory.Read(offset, size) + if memErr != nil { + dbg.Printf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) - output = copyslice(ret) + dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil - case SUICIDE: // 0xFF + case SELFDESTRUCT: // 0xFF addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err @@ -937,6 +950,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } } +// TODO: [Silas] this function seems extremely dubious to me. It was being used +// in circumstances where its behaviour did not match the intention. It's bounds +// check is strange (treats a read at data length as a zero read of arbitrary length) +// I have left it in for now to be conservative about where its behaviour is being used +// +// Returns a subslice from offset of length length and a bool +// (true iff slice was possible). If the subslice +// extends past the end of data it returns A COPY of the segment at the end of +// data padded with zeroes on the right. If offset == len(data) it returns all +// zeroes. if offset > len(data) it returns a false func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { size := int64(len(data)) if size < offset { @@ -950,18 +973,6 @@ func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { return } -func copyslice(src []byte) (dest []byte) { - dest = make([]byte, len(src)) - copy(dest, src) - return dest -} - -func rightMostBytes(data []byte, n int) []byte { - size := integral.MinInt(len(data), n) - offset := len(data) - size - return data[offset:] -} - func codeGetOp(code []byte, n int64) OpCode { if int64(len(code)) <= n { return OpCode(0) // stop diff --git a/manager/burrow-mint/evm/vm_test.go b/manager/burrow-mint/evm/vm_test.go index dc316ec68856934aed5d7ce37c52d9e6dbc1c6f0..0dd356827dbb1fa1d732ddf5cdd321af523dc16d 100644 --- a/manager/burrow-mint/evm/vm_test.go +++ b/manager/burrow-mint/evm/vm_test.go @@ -65,7 +65,7 @@ func makeBytes(n int) []byte { // Runs a basic loop func TestVM(t *testing.T) { - ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -91,7 +91,7 @@ func TestVM(t *testing.T) { } func TestJumpErr(t *testing.T) { - ourVm := NewVM(newAppState(), newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -103,11 +103,10 @@ func TestJumpErr(t *testing.T) { var gas int64 = 100000 code := []byte{0x60, 0x10, 0x56} // jump to position 16, a clear failure - var output []byte var err error ch := make(chan struct{}) go func() { - output, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + _, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) ch <- struct{}{} }() tick := time.NewTicker(time.Second * 2) @@ -134,7 +133,7 @@ func TestSubcurrency(t *testing.T) { st.accounts[account1.Address.String()] = account1 st.accounts[account2.Address.String()] = account2 - ourVm := NewVM(st, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) var gas int64 = 1000 code_parts := []string{"620f42403355", @@ -156,7 +155,7 @@ func TestSubcurrency(t *testing.T) { // Test sending tokens from a contract to another account func TestSendCall(t *testing.T) { fakeAppState := newAppState() - ourVm := NewVM(fakeAppState, newParams(), Zero256, nil) + ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) // Create accounts account1 := &Account{ @@ -199,7 +198,7 @@ func TestSendCall(t *testing.T) { // and then run it with 1 gas unit less, expecting a failure func TestDelegateCallGas(t *testing.T) { appState := newAppState() - ourVm := NewVM(appState, newParams(), Zero256, nil) + ourVm := NewVM(appState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) inOff := 0 inSize := 0 // no call data @@ -255,6 +254,66 @@ func TestDelegateCallGas(t *testing.T) { assert.Error(t, err, "Should have insufficient funds for call") } +func TestMemoryBounds(t *testing.T) { + appState := newAppState() + memoryProvider := func() Memory { + return NewDynamicMemory(1024, 2048) + } + ourVm := NewVM(appState, memoryProvider, newParams(), Zero256, nil) + caller, _ := makeAccountWithCode(appState, "caller", nil) + callee, _ := makeAccountWithCode(appState, "callee", nil) + gas := int64(100000) + // This attempts to store a value at the memory boundary and return it + word := One256 + output, err := ourVm.call(caller, callee, + Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Same with number + word = Int64ToWord256(232234234432) + output, err = ourVm.call(caller, callee, + Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Now test a series of boundary stores + code := pushWord(word) + for i := 0; i < 10; i++ { + code = Bytecode(code, storeAtEnd(), MLOAD) + } + output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.NoError(t, err) + assert.Equal(t, word.Bytes(), output) + + // Same as above but we should breach the upper memory limit set in memoryProvider + code = pushWord(word) + for i := 0; i < 100; i++ { + code = Bytecode(code, storeAtEnd(), MLOAD) + } + output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + nil, 0, &gas) + assert.Error(t, err, "Should hit memory out of bounds") +} + +// These code segment helpers exercise the MSTORE MLOAD MSTORE cycle to test +// both of the memory operations. Each MSTORE is done on the memory boundary +// (at MSIZE) which Solidity uses to find guaranteed unallocated memory. + +// storeAtEnd expects the value to be stored to be on top of the stack, it then +// stores that value at the current memory boundary +func storeAtEnd() []byte { + // Pull in MSIZE (to carry forward to MLOAD), swap in value to store, store it at MSIZE + return Bytecode(MSIZE, SWAP1, DUP2, MSTORE) +} + +func returnAfterStore() []byte { + return Bytecode(PUSH1, 32, DUP2, RETURN) +} + // Store the top element of the stack (which is a 32-byte word) in memory // and return it. Useful for a simple return value. func return1() []byte { @@ -346,6 +405,37 @@ func callContractCode(addr []byte) []byte { PUSH1, retOff, RETURN) } +func pushInt64(i int64) []byte { + return pushWord(Int64ToWord256(i)) +} + +// Produce bytecode for a PUSH<N>, b_1, ..., b_N where the N is number of bytes +// contained in the unpadded word +func pushWord(word Word256) []byte { + leadingZeros := byte(0) + for leadingZeros < 32 { + if word[leadingZeros] == 0 { + leadingZeros++ + } else { + return Bytecode(byte(PUSH32)-leadingZeros, word[leadingZeros:]) + } + } + return Bytecode(PUSH1, 0) +} + +func TestPushWord(t *testing.T) { + word := Int64ToWord256(int64(2133213213)) + assert.Equal(t, Bytecode(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + word[0] = 1 + assert.Equal(t, Bytecode(PUSH32, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + assert.Equal(t, Bytecode(PUSH1, 0), pushWord(Word256{})) + assert.Equal(t, Bytecode(PUSH1, 1), pushWord(Int64ToWord256(1))) +} + func TestBytecode(t *testing.T) { assert.Equal(t, Bytecode(1, 2, 3, 4, 5, 6), diff --git a/manager/burrow-mint/pipe.go b/manager/burrow-mint/pipe.go index ef3beb33ed07e7e0627828c689f328e11207af52..cb91fb2a2fb97791c904271713cd81a59e4f00e9 100644 --- a/manager/burrow-mint/pipe.go +++ b/manager/burrow-mint/pipe.go @@ -35,7 +35,7 @@ import ( edb_event "github.com/hyperledger/burrow/event" genesis "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" "github.com/hyperledger/burrow/manager/burrow-mint/state" manager_types "github.com/hyperledger/burrow/manager/types" @@ -57,7 +57,7 @@ type burrowMintPipe struct { // Genesis cache genesisDoc *genesis.GenesisDoc genesisState *state.State - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // Interface type assertions @@ -67,7 +67,7 @@ var _ definitions.TendermintPipe = (*burrowMintPipe)(nil) func NewBurrowMintPipe(moduleConfig *config.ModuleConfig, eventSwitch go_events.EventSwitch, - logger loggers.InfoTraceLogger) (*burrowMintPipe, error) { + logger logging_types.InfoTraceLogger) (*burrowMintPipe, error) { startedState, genesisDoc, err := startState(moduleConfig.DataDir, moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile, @@ -141,8 +141,8 @@ func startState(dataDir, backend, genesisFile, chainId string) (*state.State, // avoid Tendermints PanicSanity and return a clean error if backend != db.MemDBBackendStr && backend != db.LevelDBBackendStr { - return nil, nil, fmt.Errorf("Database backend %s is not supported by %s", - backend, GetBurrowMintVersion) + return nil, nil, fmt.Errorf("Database backend %s is not supported "+ + "by burrowmint", backend) } stateDB := db.NewDB("burrowmint", backend, dataDir) @@ -177,7 +177,7 @@ func startState(dataDir, backend, genesisFile, chainId string) (*state.State, //------------------------------------------------------------------------------ // Implement definitions.Pipe for burrowMintPipe -func (pipe *burrowMintPipe) Logger() loggers.InfoTraceLogger { +func (pipe *burrowMintPipe) Logger() logging_types.InfoTraceLogger { return pipe.logger } @@ -246,24 +246,26 @@ func (pipe *burrowMintPipe) consensusAndManagerEvents() edb_event.EventEmitter { //------------------------------------------------------------------------------ // Implement definitions.TendermintPipe for burrowMintPipe -func (pipe *burrowMintPipe) Subscribe(event string, +func (pipe *burrowMintPipe) Subscribe(eventId string, rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) { subscriptionId, err := edb_event.GenerateSubId() if err != nil { return nil, err logging.InfoMsg(pipe.logger, "Subscribing to event", - "event", event, "subscriptionId", subscriptionId) + "eventId", eventId, "subscriptionId", subscriptionId) } - pipe.consensusAndManagerEvents().Subscribe(subscriptionId, event, + pipe.consensusAndManagerEvents().Subscribe(subscriptionId, eventId, func(eventData txs.EventData) { - result := rpc_tm_types.BurrowResult(&rpc_tm_types.ResultEvent{event, - txs.EventData(eventData)}) + result := rpc_tm_types.BurrowResult( + &rpc_tm_types.ResultEvent{ + Event: eventId, + Data: txs.EventData(eventData)}) // NOTE: EventSwitch callbacks must be nonblocking rpcResponseWriter(result) }) return &rpc_tm_types.ResultSubscribe{ SubscriptionId: subscriptionId, - Event: event, + Event: eventId, }, nil } @@ -297,7 +299,7 @@ func (pipe *burrowMintPipe) Status() (*rpc_tm_types.ResultStatus, error) { ) if latestHeight != 0 { latestBlockMeta = pipe.blockchain.BlockMeta(latestHeight) - latestBlockHash = latestBlockMeta.Hash + latestBlockHash = latestBlockMeta.Header.Hash() latestBlockTime = latestBlockMeta.Header.Time.UnixNano() } return &rpc_tm_types.ResultStatus{ @@ -407,6 +409,11 @@ func (pipe *burrowMintPipe) DumpStorage(address []byte) (*rpc_tm_types.ResultDum // TODO: [ben] resolve incompatibilities in byte representation for 0.12.0 release func (pipe *burrowMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, error) { + if vm.RegisteredNativeContract(word256.LeftPadWord256(toAddress)) { + return nil, fmt.Errorf("Attempt to call native contract at address "+ + "%X, but native contracts can not be called directly. Use a deployed "+ + "contract that calls the native function instead.", toAddress) + } st := pipe.burrowMint.GetState() cache := state.NewBlockCache(st) outAcc := cache.GetAccount(toAddress) @@ -427,7 +434,8 @@ func (pipe *burrowMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_t GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) if err != nil { @@ -454,7 +462,8 @@ func (pipe *burrowMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tm_ty GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil { @@ -581,7 +590,7 @@ func (pipe *burrowMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroa return resultBroadCastTx, fmt.Errorf(resultBroadCastTx.Log) default: logging.InfoMsg(pipe.logger, "Unknown error returned from Tendermint CheckTx on BroadcastTxSync", - "application", GetBurrowMintVersion().GetVersionString(), + "application", "burrowmint", "abci_code_type", responseCheckTx.Code, "abci_log", responseCheckTx.Log, ) diff --git a/manager/burrow-mint/state/execution.go b/manager/burrow-mint/state/execution.go index 0302a4da477df42714fc87713a2b4227f788ea4f..4c765ec7f985fa2b92ce500e0be8d2f8b36c3d79 100644 --- a/manager/burrow-mint/state/execution.go +++ b/manager/burrow-mint/state/execution.go @@ -16,17 +16,18 @@ package state import ( "bytes" - "errors" "fmt" acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/common/sanity" core_types "github.com/hyperledger/burrow/core/types" + logging_types "github.com/hyperledger/burrow/logging/types" "github.com/hyperledger/burrow/manager/burrow-mint/evm" ptypes "github.com/hyperledger/burrow/permission/types" // for GlobalPermissionAddress ... "github.com/hyperledger/burrow/txs" . "github.com/hyperledger/burrow/word256" + "github.com/hyperledger/burrow/logging" "github.com/tendermint/go-events" ) @@ -183,7 +184,8 @@ func getInputs(state AccountGetter, ins []*txs.TxInput) (map[string]*acm.Account return accounts, nil } -func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, outs []*txs.TxOutput) (map[string]*acm.Account, error) { +func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, + outs []*txs.TxOutput, logger logging_types.InfoTraceLogger) (map[string]*acm.Account, error) { if accounts == nil { accounts = make(map[string]*acm.Account) } @@ -199,7 +201,7 @@ func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, out // output account may be nil (new) if acc == nil { if !checkedCreatePerms { - if !hasCreateAccountPermission(state, accounts) { + if !hasCreateAccountPermission(state, accounts, logger) { return nil, fmt.Errorf("At least one input does not have permission to create accounts") } checkedCreatePerms = true @@ -316,8 +318,10 @@ func adjustByOutputs(accounts map[string]*acm.Account, outs []*txs.TxOutput) { // If the tx is invalid, an error will be returned. // Unlike ExecBlock(), state will not be altered. -func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable) (err error) { +func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable, + logger logging_types.InfoTraceLogger) (err error) { + logger = logging.WithScope(logger, "ExecTx") // TODO: do something with fees fees := int64(0) _s := blockCache.State() // hack to access validators and block height @@ -331,13 +335,13 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // ensure all inputs have send permissions - if !hasSendPermission(blockCache, accounts) { + if !hasSendPermission(blockCache, accounts, logger) { return fmt.Errorf("At least one input lacks permission for SendTx") } // add outputs to accounts map // if any outputs don't exist, all inputs must have CreateAccount perm - accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs) + accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs, logger) if err != nil { return err } @@ -382,46 +386,54 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) if inAcc == nil { - log.Info(fmt.Sprintf("Can't find in account %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find input account", + "tx_input", tx.Input) return txs.ErrTxInvalidAddress } createContract := len(tx.Address) == 0 if createContract { - if !hasCreateContractPermission(blockCache, inAcc) { + if !hasCreateContractPermission(blockCache, inAcc, logger) { return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address) } } else { - if !hasCallPermission(blockCache, inAcc) { + if !hasCallPermission(blockCache, inAcc, logger) { return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) } } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - log.Info(fmt.Sprintf("Can't find pubkey for %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find public key for input account", + "tx_input", tx.Input) return err } signBytes := acm.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { - log.Info(fmt.Sprintf("validateInput failed on %X: %v", tx.Input.Address, err)) + logging.InfoMsg(logger, "validateInput failed", + "tx_input", tx.Input, "error", err) return err } if tx.Input.Amount < tx.Fee { - log.Info(fmt.Sprintf("Sender did not send enough to cover the fee %X", tx.Input.Address)) + logging.InfoMsg(logger, "Sender did not send enough to cover the fee", + "tx_input", tx.Input) return txs.ErrTxInsufficientFunds } if !createContract { // Validate output if len(tx.Address) != 20 { - log.Info(fmt.Sprintf("Destination address is not 20 bytes %X", tx.Address)) + logging.InfoMsg(logger, "Destination address is not 20 bytes", + "address", tx.Address) return txs.ErrTxInvalidAddress } // check if its a native contract if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { - return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)") + return fmt.Errorf("Attempt to call a native contract at %X, "+ + "but native contracts cannot be called using CallTx. Use a "+ + "contract that calls the native contract or the appropriate tx "+ + "type (eg. PermissionsTx, NameTx).", tx.Address) } // Output account may be nil if we are still in mempool and contract was created in same block as this tx @@ -430,7 +442,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable outAcc = blockCache.GetAccount(tx.Address) } - log.Info(fmt.Sprintf("Out account: %v", outAcc)) + logger.Trace("output_account", outAcc) // Good! value := tx.Input.Amount - tx.Fee @@ -468,11 +480,13 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // you have to wait a block to avoid a re-ordering attack // that will take your fees if outAcc == nil { - log.Info(fmt.Sprintf("%X tries to call %X but it does not exist.", - inAcc.Address, tx.Address)) + logging.InfoMsg(logger, "Call to address that does not exist", + "caller_address", inAcc.Address, + "callee_address", tx.Address) } else { - log.Info(fmt.Sprintf("%X tries to call %X but code is blank.", - inAcc.Address, tx.Address)) + logging.InfoMsg(logger, "Call to address that holds no code", + "caller_address", inAcc.Address, + "callee_address", tx.Address) } err = txs.ErrTxInvalidAddress goto CALL_COMPLETE @@ -482,31 +496,37 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable if createContract { // We already checked for permission callee = txCache.CreateAccount(caller) - log.Info(fmt.Sprintf("Created new contract %X", callee.Address)) + logging.TraceMsg(logger, "Created new contract", + "contract_address", callee.Address, + "contract_code", callee.Code) code = tx.Data } else { callee = toVMAccount(outAcc) - log.Info(fmt.Sprintf("Calling contract %X with code %X", callee.Address, callee.Code)) + logging.TraceMsg(logger, "Calling existing contract", + "contract_address", callee.Address, + "contract_code", callee.Code) code = callee.Code } - log.Info(fmt.Sprintf("Code for this contract: %X", code)) + logger.Trace("callee_") // Run VM call and sync txCache to blockCache. { // Capture scope for goto. // Write caller/callee to txCache. txCache.UpdateAccount(caller) txCache.UpdateAccount(callee) - vmach := vm.NewVM(txCache, params, caller.Address, txs.TxHash(_s.ChainID, tx)) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, txs.TxHash(_s.ChainID, tx)) vmach.SetFireable(evc) // NOTE: Call() transfers the value from caller to callee iff call succeeds. ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas) if err != nil { // Failure. Charge the gas fee. The 'value' was otherwise not transferred. - log.Info(fmt.Sprintf("Error on execution: %v", err)) + logging.InfoMsg(logger, "Error on execution", + "error", err) goto CALL_COMPLETE } - log.Info("Successful execution") + logging.TraceMsg(logger, "Successful execution") if createContract { callee.Code = ret } @@ -515,8 +535,12 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable CALL_COMPLETE: // err may or may not be nil. - // Create a receipt from the ret and whether errored. - log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) + // Create a receipt from the ret and whether it erred. + logging.TraceMsg(logger, "VM call complete", + "caller", caller, + "callee", callee, + "return", ret, + "error", err) // Fire Events for sender and receiver // a separate event will be fired from vm for each additional call @@ -548,27 +572,30 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) if inAcc == nil { - log.Info(fmt.Sprintf("Can't find in account %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find input account", + "tx_input", tx.Input) return txs.ErrTxInvalidAddress } // check permission - if !hasNamePermission(blockCache, inAcc) { + if !hasNamePermission(blockCache, inAcc, logger) { return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address) } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - log.Info(fmt.Sprintf("Can't find pubkey for %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find public key for input account", + "tx_input", tx.Input) return err } signBytes := acm.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { - log.Info(fmt.Sprintf("validateInput failed on %X: %v", tx.Input.Address, err)) + logging.InfoMsg(logger, "validateInput failed", + "tx_input", tx.Input, "error", err) return err } - // fee is in addition to the amount which is used to determine the TTL if tx.Input.Amount < tx.Fee { - log.Info(fmt.Sprintf("Sender did not send enough to cover the fee %X", tx.Input.Address)) + logging.InfoMsg(logger, "Sender did not send enough to cover the fee", + "tx_input", tx.Input) return txs.ErrTxInsufficientFunds } @@ -584,7 +611,11 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable expiresIn := int(value / costPerBlock) lastBlockHeight := _s.LastBlockHeight - log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight) + logging.TraceMsg(logger, "New NameTx", + "value", value, + "cost_per_block", costPerBlock, + "expires_in", expiresIn, + "last_block_height", lastBlockHeight) // check if the name exists entry := blockCache.GetNameRegEntry(tx.Name) @@ -595,8 +626,10 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // if the entry already exists, and hasn't expired, we must be owner if entry.Expires > lastBlockHeight { // ensure we are owner - if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { - log.Info(fmt.Sprintf("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) + if !bytes.Equal(entry.Owner, tx.Input.Address) { + logging.InfoMsg(logger, "Sender is trying to update a name for which they are not an owner", + "sender_address", tx.Input.Address, + "name", tx.Name) return txs.ErrTxPermissionDenied } } else { @@ -607,18 +640,22 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable if value == 0 && len(tx.Data) == 0 { // maybe we reward you for telling us we can delete this crap // (owners if not expired, anyone if expired) - log.Info("Removing namereg entry", "name", entry.Name) + logging.TraceMsg(logger, "Removing NameReg entry (no value and empty data in tx requests this)", + "name", entry.Name) blockCache.RemoveNameRegEntry(entry.Name) } else { // update the entry by bumping the expiry // and changing the data if expired { if expiresIn < txs.MinNameRegistrationPeriod { - return errors.New(fmt.Sprintf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } entry.Expires = lastBlockHeight + expiresIn entry.Owner = tx.Input.Address - log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner) + logging.TraceMsg(logger, "An old NameReg entry has expired and been reclaimed", + "name", entry.Name, + "expires_in", expiresIn, + "owner", entry.Owner) } else { // since the size of the data may have changed // we use the total amount of "credit" @@ -626,17 +663,22 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable credit := oldCredit + value expiresIn = int(credit / costPerBlock) if expiresIn < txs.MinNameRegistrationPeriod { - return errors.New(fmt.Sprintf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } entry.Expires = lastBlockHeight + expiresIn - log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit) + logging.TraceMsg(logger, "Updated NameReg entry", + "name", entry.Name, + "expires_in", expiresIn, + "old_credit", oldCredit, + "value", value, + "credit", credit) } entry.Data = tx.Data blockCache.UpdateNameRegEntry(entry) } } else { if expiresIn < txs.MinNameRegistrationPeriod { - return errors.New(fmt.Sprintf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod)) + return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } // entry does not exist, so create it entry = &core_types.NameRegEntry{ @@ -645,7 +687,9 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable Data: tx.Data, Expires: lastBlockHeight + expiresIn, } - log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn) + logging.TraceMsg(logger, "Creating NameReg entry", + "name", entry.Name, + "expires_in", expiresIn) blockCache.UpdateNameRegEntry(entry) } @@ -848,31 +892,37 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) if inAcc == nil { - log.Debug(fmt.Sprintf("Can't find in account %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find input account", + "tx_input", tx.Input) return txs.ErrTxInvalidAddress } permFlag := tx.PermArgs.PermFlag() // check permission - if !HasPermission(blockCache, inAcc, permFlag) { + if !HasPermission(blockCache, inAcc, permFlag, logger) { return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag) } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { - log.Debug(fmt.Sprintf("Can't find pubkey for %X", tx.Input.Address)) + logging.InfoMsg(logger, "Cannot find public key for input account", + "tx_input", tx.Input) return err } signBytes := acm.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { - log.Debug(fmt.Sprintf("validateInput failed on %X: %v", tx.Input.Address, err)) + logging.InfoMsg(logger, "validateInput failed", + "tx_input", tx.Input, + "error", err) return err } value := tx.Input.Amount - log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs) + logging.TraceMsg(logger, "New PermissionsTx", + "perm_flag", ptypes.PermFlagToString(permFlag), + "perm_args", tx.PermArgs) var permAcc *acm.Account switch args := tx.PermArgs.(type) { @@ -944,16 +994,17 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable //--------------------------------------------------------------- // Get permission on an account or fall back to global value -func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) bool { +func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, + logger logging_types.InfoTraceLogger) bool { if perm > ptypes.AllPermFlags { sanity.PanicSanity("Checking an unknown permission in state should never happen") } - if acc == nil { - // TODO - // this needs to fall back to global or do some other specific things - // eg. a bondAcc may be nil and so can only bond if global bonding is true - } + //if acc == nil { + // TODO + // this needs to fall back to global or do some other specific things + // eg. a bondAcc may be nil and so can only bond if global bonding is true + //} permString := ptypes.PermFlagToString(perm) v, err := acc.Permissions.Base.Get(perm) @@ -961,55 +1012,67 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag) if state == nil { sanity.PanicSanity("All known global permissions should be set!") } - log.Info("Permission for account is not set. Querying GlobalPermissionsAddress", "perm", permString) - return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm) + logging.TraceMsg(logger, "Permission for account is not set. Querying GlobalPermissionsAddres.", + "perm_flag", permString) + return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm, logger) } else if v { - log.Info("Account has permission", "address", fmt.Sprintf("%X", acc.Address), "perm", permString) + logging.TraceMsg(logger, "Account has permission", + "account_address", acc.Address, + "perm_flag", permString) } else { - log.Info("Account does not have permission", "address", fmt.Sprintf("%X", acc.Address), "perm", permString) + logging.TraceMsg(logger, "Account does not have permission", + "account_address", acc.Address, + "perm_flag", permString) } return v } // TODO: for debug log the failed accounts -func hasSendPermission(state AccountGetter, accs map[string]*acm.Account) bool { +func hasSendPermission(state AccountGetter, accs map[string]*acm.Account, + logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Send) { + if !HasPermission(state, acc, ptypes.Send, logger) { return false } } return true } -func hasNamePermission(state AccountGetter, acc *acm.Account) bool { - return HasPermission(state, acc, ptypes.Name) +func hasNamePermission(state AccountGetter, acc *acm.Account, + logger logging_types.InfoTraceLogger) bool { + return HasPermission(state, acc, ptypes.Name, logger) } -func hasCallPermission(state AccountGetter, acc *acm.Account) bool { - return HasPermission(state, acc, ptypes.Call) +func hasCallPermission(state AccountGetter, acc *acm.Account, + logger logging_types.InfoTraceLogger) bool { + return HasPermission(state, acc, ptypes.Call, logger) } -func hasCreateContractPermission(state AccountGetter, acc *acm.Account) bool { - return HasPermission(state, acc, ptypes.CreateContract) +func hasCreateContractPermission(state AccountGetter, acc *acm.Account, + logger logging_types.InfoTraceLogger) bool { + return HasPermission(state, acc, ptypes.CreateContract, logger) } -func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account) bool { +func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account, + logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.CreateAccount) { + if !HasPermission(state, acc, ptypes.CreateAccount, logger) { return false } } return true } -func hasBondPermission(state AccountGetter, acc *acm.Account) bool { - return HasPermission(state, acc, ptypes.Bond) +func hasBondPermission(state AccountGetter, acc *acm.Account, + logger logging_types.InfoTraceLogger) bool { + return HasPermission(state, acc, ptypes.Bond, logger) } -func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account) bool { +func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account, + logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Bond) { - if !HasPermission(state, acc, ptypes.Send) { + if !HasPermission(state, acc, ptypes.Bond, logger) { + if !HasPermission(state, acc, ptypes.Send, logger) { return false } } diff --git a/manager/burrow-mint/state/genesis_test.go b/manager/burrow-mint/state/genesis_test.go index d699bc623150164bd3fb142e94312b4fd20e4ac3..983b5cb58432e1d1603f7dd9c1de77483609b46d 100644 --- a/manager/burrow-mint/state/genesis_test.go +++ b/manager/burrow-mint/state/genesis_test.go @@ -33,7 +33,7 @@ import ( var chain_id = "lone_ranger" var addr1, _ = hex.DecodeString("964B1493BBE3312278B7DEB94C39149F7899A345") -var send1, name1, call1 = 1, 1, 0 +var send1 = 1 var perms, setbit = 66, 70 var accName = "me" var roles1 = []string{"master", "universal-ruler"} @@ -79,7 +79,7 @@ func TestGenesisReadable(t *testing.T) { t.Fatalf("Incorrect chain id. Got %d, expected %d\n", genDoc.ChainID, chain_id) } acc := genDoc.Accounts[0] - if bytes.Compare(acc.Address, addr1) != 0 { + if !bytes.Equal(acc.Address, addr1) { t.Fatalf("Incorrect address for account. Got %X, expected %X\n", acc.Address, addr1) } if acc.Amount != amt1 { diff --git a/manager/burrow-mint/state/log.go b/manager/burrow-mint/state/log.go deleted file mode 100644 index d8e71b796dd6dc0599c7bebea5257b0a2283c1d7..0000000000000000000000000000000000000000 --- a/manager/burrow-mint/state/log.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package state - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "state") diff --git a/manager/burrow-mint/state/permissions_test.go b/manager/burrow-mint/state/permissions_test.go index 51d52e4e4b258e0803c9e21ffda71f742f2b783f..4f550efde98d9cb239d742c58cb84d84d0535334 100644 --- a/manager/burrow-mint/state/permissions_test.go +++ b/manager/burrow-mint/state/permissions_test.go @@ -29,6 +29,7 @@ import ( "github.com/hyperledger/burrow/txs" . "github.com/hyperledger/burrow/word256" + "github.com/hyperledger/burrow/logging/lifecycle" "github.com/tendermint/go-crypto" dbm "github.com/tendermint/go-db" "github.com/tendermint/go-events" @@ -109,6 +110,7 @@ x - roles: has, add, rm // keys var user = makeUsers(10) var chainID = "testchain" +var logger, _ = lifecycle.NewStdErrLogger() func makeUsers(n int) []*acm.PrivAccount { accounts := []*acm.PrivAccount{} @@ -175,7 +177,7 @@ func TestSendFails(t *testing.T) { } tx.AddOutput(user[1].Address, 5) tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -188,7 +190,7 @@ func TestSendFails(t *testing.T) { } tx.AddOutput(user[4].Address, 5) tx.SignInput(chainID, 0, user[2]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -201,7 +203,7 @@ func TestSendFails(t *testing.T) { } tx.AddOutput(user[4].Address, 5) tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -217,7 +219,7 @@ func TestSendFails(t *testing.T) { } tx.AddOutput(user[6].Address, 5) tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -241,7 +243,7 @@ func TestName(t *testing.T) { t.Fatal(err) } tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -253,7 +255,7 @@ func TestName(t *testing.T) { t.Fatal(err) } tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal(err) } } @@ -273,7 +275,7 @@ func TestCallFails(t *testing.T) { // simple call tx should fail tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -282,7 +284,7 @@ func TestCallFails(t *testing.T) { // simple call tx with send permission should fail tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -291,7 +293,7 @@ func TestCallFails(t *testing.T) { // simple call tx with create permission should fail tx, _ = txs.NewCallTx(blockCache, user[3].PubKey, user[4].Address, nil, 100, 100, 100) tx.Sign(chainID, user[3]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -303,7 +305,7 @@ func TestCallFails(t *testing.T) { // simple call create tx should fail tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, nil, 100, 100, 100) tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -312,7 +314,7 @@ func TestCallFails(t *testing.T) { // simple call create tx with send perm should fail tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, nil, nil, 100, 100, 100) tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -321,7 +323,7 @@ func TestCallFails(t *testing.T) { // simple call create tx with call perm should fail tx, _ = txs.NewCallTx(blockCache, user[2].PubKey, nil, nil, 100, 100, 100) tx.Sign(chainID, user[2]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -342,7 +344,7 @@ func TestSendPermission(t *testing.T) { } tx.AddOutput(user[1].Address, 5) tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Transaction failed", err) } @@ -357,7 +359,7 @@ func TestSendPermission(t *testing.T) { tx.AddOutput(user[2].Address, 10) tx.SignInput(chainID, 0, user[0]) tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -390,7 +392,7 @@ func TestCallPermission(t *testing.T) { // A single input, having the permission, should succeed tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Transaction failed", err) } @@ -504,7 +506,7 @@ func TestCreatePermission(t *testing.T) { // A single input, having the permission, should succeed tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, nil, createCode, 100, 100, 100) tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Transaction failed", err) } // ensure the contract is there @@ -513,7 +515,7 @@ func TestCreatePermission(t *testing.T) { if contractAcc == nil { t.Fatalf("failed to create contract %X", contractAddr) } - if bytes.Compare(contractAcc.Code, contractCode) != 0 { + if !bytes.Equal(contractAcc.Code, contractCode) { t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, contractCode) } @@ -529,7 +531,7 @@ func TestCreatePermission(t *testing.T) { // A single input, having the permission, should succeed tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, createFactoryCode, 100, 100, 100) tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Transaction failed", err) } // ensure the contract is there @@ -538,7 +540,7 @@ func TestCreatePermission(t *testing.T) { if contractAcc == nil { t.Fatalf("failed to create contract %X", contractAddr) } - if bytes.Compare(contractAcc.Code, factoryCode) != 0 { + if !bytes.Equal(contractAcc.Code, factoryCode) { t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, factoryCode) } @@ -747,7 +749,7 @@ func TestCreateAccountPermission(t *testing.T) { } tx.AddOutput(user[6].Address, 5) tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Transaction failed", err) } @@ -762,7 +764,7 @@ func TestCreateAccountPermission(t *testing.T) { tx.AddOutput(user[7].Address, 10) tx.SignInput(chainID, 0, user[0]) tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -780,7 +782,7 @@ func TestCreateAccountPermission(t *testing.T) { tx.AddOutput(user[4].Address, 6) tx.SignInput(chainID, 0, user[0]) tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { t.Fatal("Expected error") } else { fmt.Println(err) @@ -800,7 +802,7 @@ func TestCreateAccountPermission(t *testing.T) { tx.AddOutput(user[7].Address, 10) tx.SignInput(chainID, 0, user[0]) tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Unexpected error", err) } @@ -816,7 +818,7 @@ func TestCreateAccountPermission(t *testing.T) { tx.AddOutput(user[4].Address, 3) tx.SignInput(chainID, 0, user[0]) tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { + if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { t.Fatal("Unexpected error", err) } @@ -1091,7 +1093,7 @@ func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx txs.Tx, eventid st }) evc := events.NewEventCache(evsw) go func() { - if err := ExecTx(blockCache, tx, true, evc); err != nil { + if err := ExecTx(blockCache, tx, true, evc, logger); err != nil { ch <- err.Error() } evc.Flush() @@ -1174,7 +1176,7 @@ func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm p } tx, _ := txs.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs) tx.Sign(chainID, user[0]) - err := ExecTx(blockCache, tx, true, nil) + err := ExecTx(blockCache, tx, true, nil, logger) if expectPass { if err != nil { t.Fatal("Unexpected exception", err) diff --git a/manager/burrow-mint/state/state_test.go b/manager/burrow-mint/state/state_test.go index 7c13e2f7e97a843e218b40799f4339100a35eb5d..f9f91d9b76561772a5f654f35ea71b1da468e766 100644 --- a/manager/burrow-mint/state/state_test.go +++ b/manager/burrow-mint/state/state_test.go @@ -34,7 +34,7 @@ func init() { func execTxWithState(state *State, tx txs.Tx, runCall bool) error { cache := NewBlockCache(state) - if err := ExecTx(cache, tx, runCall, nil); err != nil { + if err := ExecTx(cache, tx, runCall, nil, logger); err != nil { return err } else { cache.Sync() @@ -275,7 +275,7 @@ func TestNameTxs(t *testing.T) { if entry == nil { t.Fatalf("Could not find name %s", name) } - if bytes.Compare(entry.Owner, addr) != 0 { + if !bytes.Equal(entry.Owner, addr) { t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr) } if data != entry.Data { @@ -744,7 +744,7 @@ proof-of-work chain as proof of what happened while they were gone ` } -func TestSuicide(t *testing.T) { +func TestSelfDestruct(t *testing.T) { state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) @@ -756,28 +756,28 @@ func TestSuicide(t *testing.T) { newAcc1 := state.GetAccount(acc1.Address) - // store 0x1 at 0x1, push an address, then suicide :) + // store 0x1 at 0x1, push an address, then self-destruct:) contractCode := []byte{0x60, 0x01, 0x60, 0x01, 0x55, 0x73} contractCode = append(contractCode, acc2.Address...) contractCode = append(contractCode, 0xff) newAcc1.Code = contractCode state.UpdateAccount(newAcc1) - // send call tx with no data, cause suicide + // send call tx with no data, cause self-destruct tx := txs.NewCallTxWithNonce(acc0PubKey, acc1.Address, nil, sendingAmount, 1000, 0, acc0.Sequence+1) tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) // we use cache instead of execTxWithState so we can run the tx twice cache := NewBlockCache(state) - if err := ExecTx(cache, tx, true, nil); err != nil { + if err := ExecTx(cache, tx, true, nil, logger); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - // if we do it again, we won't get an error, but the suicide + // if we do it again, we won't get an error, but the self-destruct // shouldn't happen twice and the caller should lose fee tx.Input.Sequence += 1 tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - if err := ExecTx(cache, tx, true, nil); err != nil { + if err := ExecTx(cache, tx, true, nil, logger); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } diff --git a/manager/burrow-mint/transactor.go b/manager/burrow-mint/transactor.go index 5ee610b26a401abf07cd6d9b8bee37d9dc76e0e1..b4f16d891dbeb2cf584cb91bcd2d89066d55fe43 100644 --- a/manager/burrow-mint/transactor.go +++ b/manager/burrow-mint/transactor.go @@ -23,7 +23,7 @@ import ( "github.com/hyperledger/burrow/account" core_types "github.com/hyperledger/burrow/core/types" - event "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/event" "github.com/hyperledger/burrow/manager/burrow-mint/evm" "github.com/hyperledger/burrow/manager/burrow-mint/state" "github.com/hyperledger/burrow/txs" @@ -85,7 +85,8 @@ func (this *transactor) Call(fromAddress, toAddress, data []byte) ( GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) vmach.SetFireable(this.eventSwitch) gas := gasLimit ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) @@ -118,7 +119,8 @@ func (this *transactor) CallCode(fromAddress, code, data []byte) ( GasLimit: gasLimit, } - vmach := vm.NewVM(txCache, params, caller.Address, nil) + vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, + caller.Address, nil) gas := gasLimit ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil { @@ -399,12 +401,10 @@ func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) ( input.PubKey = privAccounts[i].PubKey input.Signature = privAccounts[i].Sign(this.chainID, sendTx) } - break case *txs.CallTx: callTx := tx.(*txs.CallTx) callTx.Input.PubKey = privAccounts[0].PubKey callTx.Input.Signature = privAccounts[0].Sign(this.chainID, callTx) - break case *txs.BondTx: bondTx := tx.(*txs.BondTx) // the first privaccount corresponds to the BondTx pub key. @@ -414,15 +414,12 @@ func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) ( input.PubKey = privAccounts[i+1].PubKey input.Signature = privAccounts[i+1].Sign(this.chainID, bondTx) } - break case *txs.UnbondTx: unbondTx := tx.(*txs.UnbondTx) unbondTx.Signature = privAccounts[0].Sign(this.chainID, unbondTx).(crypto.SignatureEd25519) - break case *txs.RebondTx: rebondTx := tx.(*txs.RebondTx) rebondTx.Signature = privAccounts[0].Sign(this.chainID, rebondTx).(crypto.SignatureEd25519) - break default: return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) } diff --git a/manager/burrow-mint/version.go b/manager/burrow-mint/version.go index 13a30f57a4cdb5dc4e166515561db8cedfce67cb..888de79cbca1c81d032954b4c9fc78bf34237bdc 100644 --- a/manager/burrow-mint/version.go +++ b/manager/burrow-mint/version.go @@ -26,15 +26,15 @@ const ( // Major version component of the current release burrowMintVersionMajor = 0 // Minor version component of the current release - burrowMintVersionMinor = 16 + burrowMintVersionMinor = 17 // Patch version component of the current release - burrowMintVersionPatch = 4 + burrowMintVersionPatch = 0 ) // Define the compatible consensus engines this application manager // is compatible and has been tested with. var compatibleConsensus = [...]string{ - "tendermint-0.8", + "tendermint-0.9", } func GetBurrowMintVersion() *version.VersionIdentifier { diff --git a/manager/config.go b/manager/config.go deleted file mode 100644 index 27d80979525f7fa7e75cf7694e3826b8982454b4..0000000000000000000000000000000000000000 --- a/manager/config.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manager - -import ( - burrowmint "github.com/hyperledger/burrow/manager/burrow-mint" -) - -//------------------------------------------------------------------------------ -// Helper functions - -func AssertValidApplicationManagerModule(name, minorVersionString string) bool { - switch name { - case "burrowmint": - return minorVersionString == burrowmint.GetBurrowMintVersion().GetMinorVersionString() - case "geth": - // TODO: [ben] implement Geth 1.4 as an application manager - return false - } - return false -} diff --git a/manager/manager.go b/manager/manager.go index 4ec72bd6d79494cd4c44bfddc04dcd30a2f28d16..2724b12645714d68fd3cc0388a5367fe54453e6e 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -22,10 +22,9 @@ import ( config "github.com/hyperledger/burrow/config" definitions "github.com/hyperledger/burrow/definitions" burrowmint "github.com/hyperledger/burrow/manager/burrow-mint" - // types "github.com/hyperledger/burrow/manager/types" "github.com/hyperledger/burrow/logging" - "github.com/hyperledger/burrow/logging/loggers" + logging_types "github.com/hyperledger/burrow/logging/types" ) // NewApplicationPipe returns an initialised Pipe interface @@ -34,17 +33,11 @@ import ( // of an application. It is feasible this will be insufficient to support // different types of applications later down the line. func NewApplicationPipe(moduleConfig *config.ModuleConfig, - evsw events.EventSwitch, logger loggers.InfoTraceLogger, - consensusMinorVersion string) (definitions.Pipe, - error) { + evsw events.EventSwitch, + logger logging_types.InfoTraceLogger) (definitions.Pipe, error) { switch moduleConfig.Name { case "burrowmint": - if err := burrowmint.AssertCompatibleConsensus(consensusMinorVersion); err != nil { - return nil, err - } - logging.InfoMsg(logger, "Loading BurrowMint", - "compatibleConsensus", consensusMinorVersion, - "burrowMintVersion", burrowmint.GetBurrowMintVersion().GetVersionString()) + logging.InfoMsg(logger, "Loading BurrowMint") return burrowmint.NewBurrowMintPipe(moduleConfig, evsw, logger) } return nil, fmt.Errorf("Failed to return Pipe for %s", moduleConfig.Name) diff --git a/manager/types/application.go b/manager/types/application.go index d2eda8f38318e28b993f490a857548bfad091d84..2025742bbd5cee5b62b95f348478d382f0acab31 100644 --- a/manager/types/application.go +++ b/manager/types/application.go @@ -18,7 +18,8 @@ import ( // TODO: [ben] this is currently only used for abci result type; but should // be removed as abci dependencies shouldn't feature in the application // manager - abci_types "github.com/tendermint/abci/types" + consensus_types "github.com/hyperledger/burrow/consensus/types" + abci "github.com/tendermint/abci/types" ) // NOTE: [ben] this interface is likely to be changed. Currently it is taken @@ -29,7 +30,7 @@ type Application interface { // Info returns application information as a string // NOTE: [ben] likely to move - Info() (info abci_types.ResponseInfo) + Info() (info abci.ResponseInfo) // Set application option (e.g. mode=mempool, mode=consensus) // NOTE: [ben] taken from tendermint, but it is unclear what the use is, @@ -48,7 +49,7 @@ type Application interface { // TODO: implementation notes: // 1. at this point the transaction should already be strongly typed // 2. - DeliverTx(tx []byte) abci_types.Result + DeliverTx(tx []byte) abci.Result // Check Transaction validates a transaction before being allowed into the // consensus' engine memory pool. This is the original defintion and @@ -59,7 +60,7 @@ type Application interface { // TODO: implementation notes: // 1. at this point the transaction should already be strongly typed // 2. - CheckTx(tx []byte) abci_types.Result + CheckTx(tx []byte) abci.Result // Commit returns the root hash of the current application state // NOTE: [ben] Because the concept of the block has been erased here @@ -67,30 +68,37 @@ type Application interface { // the opposit the principle of explicit stateless functions. // This will be amended when we introduce the concept of (streaming) // blocks in the pipe. - Commit() abci_types.Result + Commit() abci.Result // Query for state. This query request is not passed over the p2p network - // and is called from Tendermint rpc directly up to the application. + // and is called from Tenderpmint rpc directly up to the application. // NOTE: [ben] burrow will give preference to queries from the local client // directly over the burrow rpc. // We will support this for Tendermint compatibility. - Query(query []byte) abci_types.Result -} + Query(reqQuery abci.RequestQuery) abci.ResponseQuery -// Tendermint has a separate interface for reintroduction of blocks -type BlockchainAware interface { + // Tendermint acbi_types.Application extends our base definition of an + // Application with a parenthetical (begin/end) streaming block interface // Initialise the blockchain - // validators: genesis validators from tendermint core - InitChain(validators []*abci_types.Validator) + // When Tendermint initialises the genesis validators from tendermint core + // are passed in as validators + InitChain(validators []*abci.Validator) - // Signals the beginning of a block; - // NOTE: [ben] currently not supported by tendermint - BeginBlock(height uint64) + // Signals the beginning of communicating a block (all transactions have been + // closed into the block already + BeginBlock(hash []byte, header *abci.Header) // Signals the end of a blockchain - // validators: changed validators from app to Tendermint - // NOTE: [ben] currently not supported by tendermint - // not yet well defined what the change set contains. - EndBlock(height uint64) (validators []*abci_types.Validator) + // ResponseEndBlock wraps a slice of Validators with the Diff field. A Validator + // is a public key and a voting power. Returning a Validator within this slice + // asks Tendermint to set that validator's voting power to the Power provided. + // Note: although the field is named 'Diff' the intention is that it declares + // the what the new voting power should be (for validators specified, + // those omitted are left alone) it is not an relative increment to + // be added (or subtracted) from voting power. + EndBlock(height uint64) abci.ResponseEndBlock + + // Is this the passed ConsensusEngine compatible with this manager + CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool } diff --git a/rpc/tendermint/client/client.go b/rpc/tendermint/client/client.go index d8ca980094524562d9e70a8d13b6e7e19c6cd6bf..fe9f8ca55e774e61098074c89a94b9f2e2b957e5 100644 --- a/rpc/tendermint/client/client.go +++ b/rpc/tendermint/client/client.go @@ -22,36 +22,39 @@ import ( core_types "github.com/hyperledger/burrow/core/types" rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" "github.com/hyperledger/burrow/txs" - rpcclient "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" ) -func Status(client rpcclient.Client) (*rpc_types.ResultStatus, error) { - res, err := performCall(client, "status") +type RPCClient interface { + Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) +} + +func Status(client RPCClient) (*rpc_types.ResultStatus, error) { + res, err := call(client, "status") if err != nil { return nil, err } return res.(*rpc_types.ResultStatus), nil } -func ChainId(client rpcclient.Client) (*rpc_types.ResultChainId, error) { - res, err := performCall(client, "chain_id") +func ChainId(client RPCClient) (*rpc_types.ResultChainId, error) { + res, err := call(client, "chain_id") if err != nil { return nil, err } return res.(*rpc_types.ResultChainId), nil } -func GenPrivAccount(client rpcclient.Client) (*acm.PrivAccount, error) { - res, err := performCall(client, "unsafe/gen_priv_account") +func GenPrivAccount(client RPCClient) (*acm.PrivAccount, error) { + res, err := call(client, "unsafe/gen_priv_account") if err != nil { return nil, err } return res.(*rpc_types.ResultGenPrivAccount).PrivAccount, nil } -func GetAccount(client rpcclient.Client, address []byte) (*acm.Account, error) { - res, err := performCall(client, "get_account", +func GetAccount(client RPCClient, address []byte) (*acm.Account, error) { + res, err := call(client, "get_account", "address", address) if err != nil { return nil, err @@ -59,9 +62,9 @@ func GetAccount(client rpcclient.Client, address []byte) (*acm.Account, error) { return res.(*rpc_types.ResultGetAccount).Account, nil } -func SignTx(client rpcclient.Client, tx txs.Tx, +func SignTx(client RPCClient, tx txs.Tx, privAccounts []*acm.PrivAccount) (txs.Tx, error) { - res, err := performCall(client, "unsafe/sign_tx", + res, err := call(client, "unsafe/sign_tx", "tx", wrappedTx{tx}, "privAccounts", privAccounts) if err != nil { @@ -70,9 +73,9 @@ func SignTx(client rpcclient.Client, tx txs.Tx, return res.(*rpc_types.ResultSignTx).Tx, nil } -func BroadcastTx(client rpcclient.Client, +func BroadcastTx(client RPCClient, tx txs.Tx) (txs.Receipt, error) { - res, err := performCall(client, "broadcast_tx", + res, err := call(client, "broadcast_tx", "tx", wrappedTx{tx}) if err != nil { return txs.Receipt{}, err @@ -84,9 +87,9 @@ func BroadcastTx(client rpcclient.Client, } -func DumpStorage(client rpcclient.Client, +func DumpStorage(client RPCClient, address []byte) (*rpc_types.ResultDumpStorage, error) { - res, err := performCall(client, "dump_storage", + res, err := call(client, "dump_storage", "address", address) if err != nil { return nil, err @@ -94,8 +97,8 @@ func DumpStorage(client rpcclient.Client, return res.(*rpc_types.ResultDumpStorage), err } -func GetStorage(client rpcclient.Client, address, key []byte) ([]byte, error) { - res, err := performCall(client, "get_storage", +func GetStorage(client RPCClient, address, key []byte) ([]byte, error) { + res, err := call(client, "get_storage", "address", address, "key", key) if err != nil { @@ -104,9 +107,9 @@ func GetStorage(client rpcclient.Client, address, key []byte) ([]byte, error) { return res.(*rpc_types.ResultGetStorage).Value, nil } -func CallCode(client rpcclient.Client, fromAddress, code, +func CallCode(client RPCClient, fromAddress, code, data []byte) (*rpc_types.ResultCall, error) { - res, err := performCall(client, "call_code", + res, err := call(client, "call_code", "fromAddress", fromAddress, "code", code, "data", data) @@ -116,9 +119,9 @@ func CallCode(client rpcclient.Client, fromAddress, code, return res.(*rpc_types.ResultCall), err } -func Call(client rpcclient.Client, fromAddress, toAddress, +func Call(client RPCClient, fromAddress, toAddress, data []byte) (*rpc_types.ResultCall, error) { - res, err := performCall(client, "call", + res, err := call(client, "call", "fromAddress", fromAddress, "toAddress", toAddress, "data", data) @@ -128,8 +131,8 @@ func Call(client rpcclient.Client, fromAddress, toAddress, return res.(*rpc_types.ResultCall), err } -func GetName(client rpcclient.Client, name string) (*core_types.NameRegEntry, error) { - res, err := performCall(client, "get_name", +func GetName(client RPCClient, name string) (*core_types.NameRegEntry, error) { + res, err := call(client, "get_name", "name", name) if err != nil { return nil, err @@ -137,9 +140,9 @@ func GetName(client rpcclient.Client, name string) (*core_types.NameRegEntry, er return res.(*rpc_types.ResultGetName).Entry, nil } -func BlockchainInfo(client rpcclient.Client, minHeight, +func BlockchainInfo(client RPCClient, minHeight, maxHeight int) (*rpc_types.ResultBlockchainInfo, error) { - res, err := performCall(client, "blockchain", + res, err := call(client, "blockchain", "minHeight", minHeight, "maxHeight", maxHeight) if err != nil { @@ -148,8 +151,8 @@ func BlockchainInfo(client rpcclient.Client, minHeight, return res.(*rpc_types.ResultBlockchainInfo), err } -func GetBlock(client rpcclient.Client, height int) (*rpc_types.ResultGetBlock, error) { - res, err := performCall(client, "get_block", +func GetBlock(client RPCClient, height int) (*rpc_types.ResultGetBlock, error) { + res, err := call(client, "get_block", "height", height) if err != nil { return nil, err @@ -157,69 +160,57 @@ func GetBlock(client rpcclient.Client, height int) (*rpc_types.ResultGetBlock, e return res.(*rpc_types.ResultGetBlock), err } -func ListUnconfirmedTxs(client rpcclient.Client) (*rpc_types.ResultListUnconfirmedTxs, error) { - res, err := performCall(client, "list_unconfirmed_txs") +func ListUnconfirmedTxs(client RPCClient) (*rpc_types.ResultListUnconfirmedTxs, error) { + res, err := call(client, "list_unconfirmed_txs") if err != nil { return nil, err } return res.(*rpc_types.ResultListUnconfirmedTxs), err } -func ListValidators(client rpcclient.Client) (*rpc_types.ResultListValidators, error) { - res, err := performCall(client, "list_validators") +func ListValidators(client RPCClient) (*rpc_types.ResultListValidators, error) { + res, err := call(client, "list_validators") if err != nil { return nil, err } return res.(*rpc_types.ResultListValidators), err } -func DumpConsensusState(client rpcclient.Client) (*rpc_types.ResultDumpConsensusState, error) { - res, err := performCall(client, "dump_consensus_state") +func DumpConsensusState(client RPCClient) (*rpc_types.ResultDumpConsensusState, error) { + res, err := call(client, "dump_consensus_state") if err != nil { return nil, err } return res.(*rpc_types.ResultDumpConsensusState), err } -func performCall(client rpcclient.Client, method string, +func call(client RPCClient, method string, paramKeyVals ...interface{}) (res rpc_types.BurrowResult, err error) { - paramsMap, paramsSlice, err := mapAndValues(paramKeyVals...) + pMap, err := paramsMap(paramKeyVals...) if err != nil { return } - switch cli := client.(type) { - case *rpcclient.ClientJSONRPC: - _, err = cli.Call(method, paramsSlice, &res) - case *rpcclient.ClientURI: - _, err = cli.Call(method, paramsMap, &res) - default: - panic(fmt.Errorf("peformCall called against an unknown rpcclient.Client %v", - cli)) - } + _, err = client.Call(method, pMap, &res) return - } -func mapAndValues(orderedKeyVals ...interface{}) (map[string]interface{}, - []interface{}, error) { +func paramsMap(orderedKeyVals ...interface{}) (map[string]interface{}, error) { if len(orderedKeyVals)%2 != 0 { - return nil, nil, fmt.Errorf("mapAndValues requires a even length list of"+ + return nil, fmt.Errorf("mapAndValues requires a even length list of"+ " keys and values but got: %v (length %v)", orderedKeyVals, len(orderedKeyVals)) } paramsMap := make(map[string]interface{}) - paramsSlice := make([]interface{}, len(orderedKeyVals)/2) for i := 0; i < len(orderedKeyVals); i += 2 { key, ok := orderedKeyVals[i].(string) if !ok { - return nil, nil, errors.New("mapAndValues requires every even element" + + return nil, errors.New("mapAndValues requires every even element" + " of orderedKeyVals to be a string key") } val := orderedKeyVals[i+1] paramsMap[key] = val - paramsSlice[i/2] = val } - return paramsMap, paramsSlice, nil + return paramsMap, nil } type wrappedTx struct { diff --git a/rpc/tendermint/client/client_test.go b/rpc/tendermint/client/client_test.go index 105a9d2b92fddd75508c9eadbe209859fbf88b44..13a5c98e48e668d53accfb113e91bf06c1ef72d1 100644 --- a/rpc/tendermint/client/client_test.go +++ b/rpc/tendermint/client/client_test.go @@ -20,29 +20,28 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMapsAndValues(t *testing.T) { +func TestParamsMap(t *testing.T) { type aStruct struct { Baz int } - dict, vals, err := mapAndValues("Foo", aStruct{5}, + dict, err := paramsMap("Foo", aStruct{5}, "Bar", "Nibbles") + assert.NoError(t, err, "Should not be a paramsMaperror") assert.Equal(t, map[string]interface{}{ "Foo": aStruct{5}, "Bar": "Nibbles", }, dict) - assert.Equal(t, []interface{}{aStruct{5}, "Nibbles"}, vals) // Empty map - dict, vals, err = mapAndValues() + dict, err = paramsMap() assert.Equal(t, map[string]interface{}{}, dict) - assert.Equal(t, []interface{}{}, vals) assert.NoError(t, err, "Empty mapsAndValues call should be fine") // Invalid maps assert.NoError(t, err, "Empty mapsAndValues call should be fine") - _, _, err = mapAndValues("Foo", 4, "Bar") + _, err = paramsMap("Foo", 4, "Bar") assert.Error(t, err, "Should be an error to get an odd number of arguments") - _, _, err = mapAndValues("Foo", 4, 4, "Bar") + _, err = paramsMap("Foo", 4, 4, "Bar") assert.Error(t, err, "Should be an error to provide non-string keys") } diff --git a/rpc/tendermint/client/websocket_client.go b/rpc/tendermint/client/websocket_client.go new file mode 100644 index 0000000000000000000000000000000000000000..4749517b72febf7b62929fe4fa7b18c986567bdb --- /dev/null +++ b/rpc/tendermint/client/websocket_client.go @@ -0,0 +1,39 @@ +// Copyright 2017 Monax Industries Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import "github.com/tendermint/go-rpc/types" + +type WebsocketClient interface { + WriteJSON(v interface{}) error +} + +func Subscribe(websocketClient WebsocketClient, eventId string) error { + return websocketClient.WriteJSON(rpctypes.RPCRequest{ + JSONRPC: "2.0", + ID: "", + Method: "subscribe", + Params: map[string]interface{}{"eventId": eventId}, + }) +} + +func Unsubscribe(websocketClient WebsocketClient, subscriptionId string) error { + return websocketClient.WriteJSON(rpctypes.RPCRequest{ + JSONRPC: "2.0", + ID: "", + Method: "unsubscribe", + Params: map[string]interface{}{"subscriptionId": subscriptionId}, + }) +} diff --git a/rpc/tendermint/core/routes.go b/rpc/tendermint/core/routes.go index 83573742edc1cfb31083dd3dd5fb6c51a8944b8f..b295b3a6de5d12b5b71dccb8b274f35aad7143c3 100644 --- a/rpc/tendermint/core/routes.go +++ b/rpc/tendermint/core/routes.go @@ -38,7 +38,7 @@ type TendermintRoutes struct { func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { var routes = map[string]*rpc.RPCFunc{ - "subscribe": rpc.NewWSRPCFunc(tmRoutes.Subscribe, "event"), + "subscribe": rpc.NewWSRPCFunc(tmRoutes.Subscribe, "eventId"), "unsubscribe": rpc.NewWSRPCFunc(tmRoutes.Unsubscribe, "subscriptionId"), "status": rpc.NewRPCFunc(tmRoutes.StatusResult, ""), "net_info": rpc.NewRPCFunc(tmRoutes.NetInfoResult, ""), @@ -66,7 +66,7 @@ func (tmRoutes *TendermintRoutes) GetRoutes() map[string]*rpc.RPCFunc { } func (tmRoutes *TendermintRoutes) Subscribe(wsCtx rpctypes.WSRPCContext, - event string) (ctypes.BurrowResult, error) { + eventId string) (ctypes.BurrowResult, error) { // NOTE: RPCResponses of subscribed events have id suffix "#event" // TODO: we really ought to allow multiple subscriptions from the same client address // to the same event. The code as it stands reflects the somewhat broken tendermint @@ -74,7 +74,7 @@ func (tmRoutes *TendermintRoutes) Subscribe(wsCtx rpctypes.WSRPCContext, // and return it in the result. This would require clients to hang on to a // subscription id if they wish to unsubscribe, but then again they can just // drop their connection - result, err := tmRoutes.tendermintPipe.Subscribe(event, + result, err := tmRoutes.tendermintPipe.Subscribe(eventId, func(result ctypes.BurrowResult) { wsCtx.GetRemoteAddr() // NOTE: EventSwitch callbacks must be nonblocking diff --git a/rpc/tendermint/test/common.go b/rpc/tendermint/test/common.go deleted file mode 100644 index ff618906ca09d88167e6ee883219caff0326cecf..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/common.go +++ /dev/null @@ -1,74 +0,0 @@ -// Space above here matters -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -import ( - "fmt" - - vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - rpc_core "github.com/hyperledger/burrow/rpc/tendermint/core" - "github.com/hyperledger/burrow/test/fixtures" -) - -// Needs to be referenced by a *_test.go file to be picked up -func TestWrapper(runner func() int) int { - fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/common.go)...") - ffs := fixtures.NewFileFixtures("burrow") - - defer ffs.RemoveAll() - - vm.SetDebug(true) - err := initGlobalVariables(ffs) - - if err != nil { - panic(err) - } - - // start a node - ready := make(chan error) - server := make(chan *rpc_core.TendermintWebsocketServer) - defer func() { - // Shutdown -- make sure we don't hit a race on ffs.RemoveAll - tmServer := <-server - tmServer.Shutdown() - }() - - go newNode(ready, server) - err = <-ready - - if err != nil { - panic(err) - } - - return runner() -} - -// This main function exists as a little convenience mechanism for running the -// delve debugger which doesn't work well from go test yet. In due course it can -// be removed, but it's flux between pull requests should be considered -// inconsequential, so feel free to insert your own code if you want to use it -// as an application entry point for delve debugging. -func DebugMain() { - //t := &testing.T{} - TestWrapper(func() int { - //testNameReg(t, "JSONRPC") - return 0 - }) -} - -func Successor(x int) int { - return x + 1 -} diff --git a/rpc/tendermint/test/config.go b/rpc/tendermint/test/config.go deleted file mode 100644 index de892e5d32bb08031ff894fc562ca321518413f9..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/config.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -// TODO [Silas]: this needs to be auto-generated -var defaultConfig = `# Copyright 2017 Monax Industries Limited -# -# 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.. - -# This is a TOML configuration for burrow chains - -[chain] - -# ChainId is a human-readable name to identify the chain. -# This must correspond to the chain_id defined in the genesis file -# and the assertion here provides a safe-guard on misconfiguring chains. -assert_chain_id = "MyChainId" -# semantic major and minor version -major_version = 0 -minor_version = 16 -# genesis file, relative path is to burrow working directory -genesis_file = "genesis.json" - - -############################################################################### -## -## consensus -## -############################################################################### - - [chain.consensus] - # consensus defines the module to use for consensus and - # this will define the peer-to-peer consensus network; - # accepted values are "noops", "abci", "tendermint" - name = "tendermint" - # version is the major and minor semantic version; - # the version will be asserted on - major_version = 0 - minor_version = 8 - # relative path to consensus' module root folder - relative_root = "tendermint" - -############################################################################### -## -## application manager -## -############################################################################### - - [chain.manager] - # application manager name defines the module to use for handling - name = "burrowmint" - # version is the major and minor semantic version; - # the version will be asserted on - major_version = 0 - minor_version = 16 - # relative path to application manager root folder - relative_root = "burrowmint" - -################################################################################ -################################################################################ -## -## Server configurations -## -################################################################################ -################################################################################ - -[servers] - - [servers.bind] - address = "" - port = 1337 - - [servers.tls] - tls = false - cert_path = "" - key_path = "" - - [servers.cors] - enable = false - allow_origins = [] - allow_credentials = false - allow_methods = [] - allow_headers = [] - expose_headers = [] - max_age = 0 - - [servers.http] - json_rpc_endpoint = "/rpc" - - [servers.websocket] - endpoint = "/socketrpc" - max_sessions = 50 - read_buffer_size = 4096 - write_buffer_size = 4096 - - [servers.tendermint] - # Multiple listeners can be separated with a comma - rpc_local_address = "0.0.0.0:36657" - endpoint = "/websocket" - - [servers.logging] - console_log_level = "info" - file_log_level = "warn" - log_file = "" - -################################################################################ -################################################################################ -## -## Module configurations - dynamically loaded based on chain configuration -## -################################################################################ -################################################################################ - - -################################################################################ -## -## Tendermint Socket Protocol (abci) -## -## abci expects a tendermint consensus process to run and connect to burrow -## -################################################################################ - -[abci] -# listener address for accepting tendermint socket protocol connections -listener = "tcp://0.0.0.0:46658" - -################################################################################ -##yeah we had partial support for that with TMSP -## Tendermint -## -## in-process execution of Tendermint consensus engine -## -################################################################################ - -[tendermint] -# private validator file is used by tendermint to keep the status -# of the private validator, but also (currently) holds the private key -# for the private vaildator to sign with. This private key needs to be moved -# out and directly managed by monax-keys -# This file needs to be in the root directory -private_validator_file = "priv_validator.json" - - # Tendermint requires additional configuration parameters. - # burrow's tendermint consensus module will load [tendermint.configuration] - # as the configuration for Tendermint. - # burrow will respect the configurations set in this file where applicable, - # but reserves the option to override or block conflicting settings. - [tendermint.configuration] - # moniker is the name of the node on the tendermint p2p network - moniker = "anonymous_marmot" - # seeds lists the peers tendermint can connect to join the network - seeds = "" - # fast_sync allows a tendermint node to catch up faster when joining - # the network. - # NOTE: Tendermint has reported potential issues with fast_sync enabled. - # The recommended setting is for keeping it disabled. - fast_sync = false - db_backend = "leveldb" - log_level = "info" - # node local address - node_laddr = "0.0.0.0:46656" - # rpc local address - # NOTE: value is ignored when run in-process as RPC is - # handled by [servers.tendermint] - rpc_laddr = "" - # proxy application address - used for abci connections, - # and this port should not be exposed for in-process Tendermint - proxy_app = "tcp://127.0.0.1:46658" - - # Extended Tendermint configuration settings - # for reference to Tendermint see https://github.com/tendermint/tendermint/blob/master/config/tendermint/config.go - - # genesis_file = "./data/tendermint/genesis.json" - # skip_upnp = false - # addrbook_file = "./data/tendermint/addrbook.json" - # priv_validator_file = "./data/tendermint/priv_validator.json" - # db_dir = "./data/tendermint/data" - # prof_laddr = "" - # revision_file = "./data/tendermint/revision" - # cswal = "./data/tendermint/data/cswal" - # cswal_light = false - - # block_size = 10000 - # disable_data_hash = false - # timeout_propose = 3000 - # timeout_propose_delta = 500 - # timeout_prevote = 1000 - # timeout_prevote_delta = 500 - # timeout_precommit = 1000 - # timeout_precommit_delta = 500 - # timeout_commit = 1000 - # mempool_recheck = true - # mempool_recheck_empty = true - # mempool_broadcast = true - -################################################################################ -## -## burrow-mint -## -## The original Ethereum virtual machine with IAVL merkle trees -## and tendermint/go-wire encoding -## -################################################################################ - -[burrowmint] -# Database backend to use for BurrowMint state database. -db_backend = "leveldb" -` diff --git a/rpc/tendermint/test/genesis.go b/rpc/tendermint/test/genesis.go deleted file mode 100644 index 9ca0e6e83c0575b733b841f0af44402adc83d19d..0000000000000000000000000000000000000000 --- a/rpc/tendermint/test/genesis.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -// priv keys generated deterministically eg rpc/tests/shared.go -var defaultGenesis = `{ - "chain_id" : "MyChainId", - "accounts": [ - { - "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB", - "amount": 200000000 - }, - { - "address": "DFE4AFFA4CEE17CD01CB9E061D77C3ECED29BD88", - "amount": 200000000 - }, - { - "address": "F60D30722E7B497FA532FB3207C3FB29C31B1992", - "amount": 200000000 - }, - { - "address": "336CB40A5EB92E496E19B74FDFF2BA017C877FD6", - "amount": 200000000 - }, - { - "address": "D218F0F439BF0384F6F5EF8D0F8B398D941BD1DC", - "amount": 200000000 - } - ], - "validators": [ - { - "pub_key": [1, "583779C3BFA3F6C7E23C7D830A9C3D023A216B55079AD38BFED1207B94A19548"], - "amount": 1000000, - "unbond_to": [ - { - "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB", - "amount": 100000 - } - ] - } - ] -}` diff --git a/rpc/tendermint/test/rpc_client_test.go b/rpc/tendermint/test/rpc_client_test.go index fc554ae9ae972f815db0af00c5820e0c2d337bd4..3f7da59afb790e358e2d0dda423ae7e2291067cc 100644 --- a/rpc/tendermint/test/rpc_client_test.go +++ b/rpc/tendermint/test/rpc_client_test.go @@ -20,17 +20,17 @@ package test import ( "bytes" "fmt" - "golang.org/x/crypto/ripemd160" "testing" "time" + "golang.org/x/crypto/ripemd160" + consensus_types "github.com/hyperledger/burrow/consensus/types" - edbcli "github.com/hyperledger/burrow/rpc/tendermint/client" + burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" "github.com/hyperledger/burrow/txs" "github.com/hyperledger/burrow/word256" "github.com/stretchr/testify/assert" - rpcclient "github.com/tendermint/go-rpc/client" _ "github.com/tendermint/tendermint/config/tendermint_test" ) @@ -41,7 +41,7 @@ import ( // due to weirdness with go-wire's interface registration, and those global // registrations not being available within a *_test.go runtime context. func testWithAllClients(t *testing.T, - testFunction func(*testing.T, string, rpcclient.Client)) { + testFunction func(*testing.T, string, burrow_client.RPCClient)) { for clientName, client := range clients { testFunction(t, clientName, client) } @@ -49,8 +49,8 @@ func testWithAllClients(t *testing.T, //-------------------------------------------------------------------------------- func TestStatus(t *testing.T) { - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - resp, err := edbcli.Status(client) + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + resp, err := burrow_client.Status(client) assert.NoError(t, err) fmt.Println(resp) if resp.NodeInfo.Network != chainID { @@ -62,12 +62,11 @@ func TestStatus(t *testing.T) { func TestBroadcastTx(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { // Avoid duplicate Tx in mempool amt := hashString(clientName) % 1000 - toAddr := user[1].Address + toAddr := users[1].Address tx := makeDefaultSendTxSigned(t, client, toAddr, amt) - //receipt := broadcastTx(t, client, tx) receipt, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.NoError(t, err) if receipt.CreatesContract > 0 { @@ -96,14 +95,14 @@ func TestGetAccount(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { - acc := getAccount(t, client, user[0].Address) + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { + acc := getAccount(t, client, users[0].Address) if acc == nil { t.Fatal("Account was nil") } - if bytes.Compare(acc.Address, user[0].Address) != 0 { + if bytes.Compare(acc.Address, users[0].Address) != 0 { t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, - user[0].Address) + users[0].Address) } }) } @@ -113,12 +112,14 @@ func TestGetStorage(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + defer func() { + wsc.Stop() + }() + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { eid := txs.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) - wsc.Stop() }() amt, gasLim, fee := int64(1100), int64(1000), int64(1000) @@ -150,13 +151,13 @@ func TestCallCode(t *testing.T) { t.Skip("skipping test in short mode.") } - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { // add two integers and return the result code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3} data := []byte{} expected := []byte{0xb} - callCode(t, client, user[0].PubKey.Address(), code, data, expected) + callCode(t, client, users[0].PubKey.Address(), code, data, expected) // pass two ints as calldata, add, and return the result code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, @@ -164,7 +165,7 @@ func TestCallCode(t *testing.T) { data = append(word256.LeftPadWord256([]byte{0x5}).Bytes(), word256.LeftPadWord256([]byte{0x6}).Bytes()...) expected = []byte{0xb} - callCode(t, client, user[0].PubKey.Address(), code, data, expected) + callCode(t, client, users[0].PubKey.Address(), code, data, expected) }) } @@ -173,12 +174,14 @@ func TestCallContract(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + defer func() { + wsc.Stop() + }() + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { eid := txs.EventStringNewBlock() subscribe(t, wsc, eid) defer func() { unsubscribe(t, wsc, eid) - wsc.Stop() }() // create the contract @@ -201,7 +204,7 @@ func TestCallContract(t *testing.T) { // run a call through the contract data := []byte{} expected := []byte{0xb} - callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected) + callContract(t, client, users[0].PubKey.Address(), contractAddr, data, expected) }) } @@ -210,7 +213,7 @@ func TestNameReg(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { txs.MinNameRegistrationPeriod = 1 @@ -242,7 +245,7 @@ func TestNameReg(t *testing.T) { entry := getNameRegEntry(t, client, name) assert.Equal(t, data, entry.Data) - assert.Equal(t, user[0].Address, entry.Owner) + assert.Equal(t, users[0].Address, entry.Owner) // update the data as the owner, make sure still there numDesiredBlocks = int64(5) @@ -259,9 +262,9 @@ func TestNameReg(t *testing.T) { assert.Equal(t, updatedData, entry.Data) // try to update as non owner, should fail - tx = txs.NewNameTxWithNonce(user[1].PubKey, name, "never mind", amt, fee, - getNonce(t, client, user[1].Address)+1) - tx.Sign(chainID, user[1]) + tx = txs.NewNameTxWithNonce(users[1].PubKey, name, "never mind", amt, fee, + getNonce(t, client, users[1].Address)+1) + tx.Sign(chainID, users[1]) _, err := broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.Error(t, err, "Expected error when updating someone else's unexpired"+ @@ -276,28 +279,28 @@ func TestNameReg(t *testing.T) { //now the entry should be expired, so we can update as non owner const data2 = "this is not my beautiful house" - tx = txs.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, - getNonce(t, client, user[1].Address)+1) - tx.Sign(chainID, user[1]) + tx = txs.NewNameTxWithNonce(users[1].PubKey, name, data2, amt, fee, + getNonce(t, client, users[1].Address)+1) + tx.Sign(chainID, users[1]) _, err = broadcastTxAndWaitForBlock(t, client, wsc, tx) assert.NoError(t, err, "Should be able to update a previously expired name"+ " registry entry as a different address") mempoolCount = 0 entry = getNameRegEntry(t, client, name) assert.Equal(t, data2, entry.Data) - assert.Equal(t, user[1].Address, entry.Owner) + assert.Equal(t, users[1].Address, entry.Owner) }) } func TestBlockchainInfo(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { // wait a mimimal number of blocks to ensure that the later query for block // headers has a non-trivial length nBlocks := 4 waitNBlocks(t, wsc, nBlocks) - resp, err := edbcli.BlockchainInfo(client, 0, 0) + resp, err := burrow_client.BlockchainInfo(client, 0, 0) if err != nil { t.Fatalf("Failed to get blockchain info: %v", err) } @@ -309,18 +312,18 @@ func TestBlockchainInfo(t *testing.T) { "Should see at least 4 BlockMetas after waiting for 4 blocks") // For the maximum number (default to 20) of retrieved block headers, // check that they correctly chain to each other. - lastBlockHash := resp.BlockMetas[nMetaBlocks-1].Hash + lastBlockHash := resp.BlockMetas[nMetaBlocks-1].Header.Hash() for i := nMetaBlocks - 2; i >= 0; i-- { // the blockhash in header of height h should be identical to the hash // in the LastBlockID of the header of block height h+1. assert.Equal(t, lastBlockHash, resp.BlockMetas[i].Header.LastBlockID.Hash, "Blockchain should be a hash tree!") - lastBlockHash = resp.BlockMetas[i].Hash + lastBlockHash = resp.BlockMetas[i].Header.Hash() } // Now retrieve only two blockheaders (h=1, and h=2) and check that we got // two results. - resp, err = edbcli.BlockchainInfo(client, 1, 2) + resp, err = burrow_client.BlockchainInfo(client, 1, 2) assert.NoError(t, err) assert.Equal(t, 2, len(resp.BlockMetas), "Should see 2 BlockMetas after extracting 2 blocks") @@ -332,7 +335,7 @@ func TestListUnconfirmedTxs(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { amt, gasLim, fee := int64(1100), int64(1000), int64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} // Call with nil address will create a contract @@ -348,7 +351,7 @@ func TestListUnconfirmedTxs(t *testing.T) { go func() { for { - resp, err := edbcli.ListUnconfirmedTxs(client) + resp, err := burrow_client.ListUnconfirmedTxs(client) assert.NoError(t, err) if resp.N > 0 { txChan <- resp.Txs @@ -374,9 +377,9 @@ func TestListUnconfirmedTxs(t *testing.T) { func TestGetBlock(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := edbcli.GetBlock(client, 2) + resp, err := burrow_client.GetBlock(client, 2) assert.NoError(t, err) assert.Equal(t, 2, resp.Block.Height) assert.Equal(t, 2, resp.BlockMeta.Header.Height) @@ -385,9 +388,9 @@ func TestGetBlock(t *testing.T) { func TestListValidators(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := edbcli.ListValidators(client) + resp, err := burrow_client.ListValidators(client) assert.NoError(t, err) assert.Len(t, resp.BondedValidators, 1) validator := resp.BondedValidators[0].(*consensus_types.TendermintValidator) @@ -397,9 +400,9 @@ func TestListValidators(t *testing.T) { func TestDumpConsensusState(t *testing.T) { wsc := newWSClient() - testWithAllClients(t, func(t *testing.T, clientName string, client rpcclient.Client) { + testWithAllClients(t, func(t *testing.T, clientName string, client burrow_client.RPCClient) { waitNBlocks(t, wsc, 3) - resp, err := edbcli.DumpConsensusState(client) + resp, err := burrow_client.DumpConsensusState(client) assert.NoError(t, err) startTime := resp.ConsensusState.StartTime // TODO: uncomment lines involving commitTime when diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go index 40cc976d49d4dfdc96fa5b86a3b10ac27f61d1a5..4ca904ddea2817bcffe61c0e16f5e11966c85692 100644 --- a/rpc/tendermint/test/shared.go +++ b/rpc/tendermint/test/shared.go @@ -16,53 +16,109 @@ package test import ( "bytes" + "errors" + "fmt" "hash/fnv" "path" "strconv" "testing" + "time" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/config" "github.com/hyperledger/burrow/core" core_types "github.com/hyperledger/burrow/core/types" + genesis "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging/lifecycle" + "github.com/hyperledger/burrow/manager/burrow-mint/evm" + ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/rpc/tendermint/client" edbcli "github.com/hyperledger/burrow/rpc/tendermint/client" - rpc_core "github.com/hyperledger/burrow/rpc/tendermint/core" rpc_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" "github.com/hyperledger/burrow/server" "github.com/hyperledger/burrow/test/fixtures" "github.com/hyperledger/burrow/txs" "github.com/hyperledger/burrow/word256" - - genesis "github.com/hyperledger/burrow/genesis" "github.com/spf13/viper" "github.com/tendermint/go-crypto" rpcclient "github.com/tendermint/go-rpc/client" "github.com/tendermint/tendermint/types" ) +const chainID = "RPC_Test_Chain" + // global variables for use across all tests var ( - config = server.DefaultServerConfig() + serverConfig *server.ServerConfig rootWorkDir string mempoolCount = 0 - chainID string websocketAddr string genesisDoc *genesis.GenesisDoc websocketEndpoint string - user = makeUsers(5) // make keys - jsonRpcClient rpcclient.Client - httpClient rpcclient.Client - clients map[string]rpcclient.Client + users = makeUsers(5) // make keys + jsonRpcClient client.RPCClient + httpClient client.RPCClient + clients map[string]client.RPCClient testCore *core.Core ) +// We use this to wrap tests +func TestWrapper(runner func() int) int { + fmt.Println("Running with integration TestWrapper (rpc/tendermint/test/shared_test.go)...") + ffs := fixtures.NewFileFixtures("burrow") + + defer func() { + // Tendermint likes to try and save to priv_validator.json after its been + // asked to shutdown so we pause to try and avoid collision + time.Sleep(time.Second) + ffs.RemoveAll() + }() + + vm.SetDebug(true) + err := initGlobalVariables(ffs) + + if err != nil { + panic(err) + } + + tmServer, err := testCore.NewGatewayTendermint(serverConfig) + defer func() { + // Shutdown -- make sure we don't hit a race on ffs.RemoveAll + tmServer.Shutdown() + testCore.Stop() + }() + + if err != nil { + panic(err) + } + + return runner() +} + // initialize config and create new node func initGlobalVariables(ffs *fixtures.FileFixtures) error { - testConfigFile := ffs.AddFile("config.toml", defaultConfig) + configBytes, err := config.GetConfigurationFileBytes(chainID, + "test_single_node", + "", + "burrow", + true, + "46657", + "burrow serve") + if err != nil { + return err + } + + genesisBytes, err := genesisFileBytesFromUsers(chainID, users) + if err != nil { + return err + } + + testConfigFile := ffs.AddFile("config.toml", string(configBytes)) rootWorkDir = ffs.AddDir("rootWorkDir") rootDataDir := ffs.AddDir("rootDataDir") - genesisFile := ffs.AddFile("rootWorkDir/genesis.json", defaultGenesis) - genesisDoc = genesis.GenesisDocFromJSON([]byte(defaultGenesis)) + genesisFile := ffs.AddFile("rootWorkDir/genesis.json", string(genesisBytes)) + genesisDoc = genesis.GenesisDocFromJSON(genesisBytes) if ffs.Error != nil { return ffs.Error @@ -70,14 +126,19 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error { testConfig := viper.New() testConfig.SetConfigFile(testConfigFile) - err := testConfig.ReadInConfig() + err = testConfig.ReadInConfig() + + if err != nil { + return err + } + sconf, err := core.LoadServerConfig(chainID, testConfig) if err != nil { return err } + serverConfig = sconf - chainID = testConfig.GetString("chain.assert_chain_id") - rpcAddr := config.Tendermint.RpcLocalAddress + rpcAddr := serverConfig.Tendermint.RpcLocalAddress websocketAddr = rpcAddr websocketEndpoint = "/websocket" @@ -96,7 +157,7 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error { // Set up priv_validator.json before we start tendermint (otherwise it will // create its own one. saveNewPriv() - logger := lifecycle.NewStdErrLogger() + logger, _ := lifecycle.NewStdErrLogger() // To spill tendermint logs on the floor: // lifecycle.CaptureTendermintLog15Output(loggers.NewNoopInfoTraceLogger()) lifecycle.CaptureTendermintLog15Output(logger) @@ -108,41 +169,67 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error { return err } - jsonRpcClient = rpcclient.NewClientJSONRPC(rpcAddr) - httpClient = rpcclient.NewClientURI(rpcAddr) + jsonRpcClient = rpcclient.NewJSONRPCClient(rpcAddr) + httpClient = rpcclient.NewURIClient(rpcAddr) - clients = map[string]rpcclient.Client{ + clients = map[string]client.RPCClient{ "JSONRPC": jsonRpcClient, "HTTP": httpClient, } return nil } -// deterministic account generation, synced with genesis file in config/tendermint_test/config.go +// Deterministic account generation helper. Pass number of accounts to make func makeUsers(n int) []*acm.PrivAccount { accounts := []*acm.PrivAccount{} for i := 0; i < n; i++ { - secret := ("mysecret" + strconv.Itoa(i)) + secret := "mysecret" + strconv.Itoa(i) user := acm.GenPrivAccountFromSecret(secret) accounts = append(accounts, user) } return accounts } -func newNode(ready chan error, - tmServer chan *rpc_core.TendermintWebsocketServer) { - // Run the 'tendermint' rpc server - server, err := testCore.NewGatewayTendermint(config) - ready <- err - tmServer <- server +func genesisFileBytesFromUsers(chainName string, accounts []*acm.PrivAccount) ([]byte, error) { + if len(accounts) < 1 { + return nil, errors.New("Please pass in at least 1 account to be the validator") + } + genesisValidators := make([]*genesis.GenesisValidator, 1) + genesisAccounts := make([]*genesis.GenesisAccount, len(accounts)) + genesisValidators[0] = genesisValidatorFromPrivAccount(accounts[0]) + + for i, acc := range accounts { + genesisAccounts[i] = genesisAccountFromPrivAccount(acc) + } + + return genesis.GenerateGenesisFileBytes(chainName, genesisAccounts, genesisValidators) +} + +func genesisValidatorFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisValidator { + return &genesis.GenesisValidator{ + Amount: 1000000, + Name: fmt.Sprintf("full-account_%X", account.Address), + PubKey: account.PubKey, + UnbondTo: []genesis.BasicAccount{ + { + Address: account.Address, + Amount: 100, + }, + }, + } +} + +func genesisAccountFromPrivAccount(account *acm.PrivAccount) *genesis.GenesisAccount { + return genesis.NewGenesisAccount(account.Address, 100000, + fmt.Sprintf("account_%X", account.Address), &ptypes.DefaultAccountPermissions) } func saveNewPriv() { // Save new priv_validator file. priv := &types.PrivValidator{ - Address: user[0].Address, - PubKey: crypto.PubKeyEd25519(user[0].PubKey.(crypto.PubKeyEd25519)), - PrivKey: crypto.PrivKeyEd25519(user[0].PrivKey.(crypto.PrivKeyEd25519)), + Address: users[0].Address, + PubKey: crypto.PubKeyEd25519(users[0].PubKey.(crypto.PubKeyEd25519)), + PrivKey: crypto.PrivKeyEd25519(users[0].PrivKey.(crypto.PrivKeyEd25519)), } priv.SetFile(path.Join(rootWorkDir, "priv_validator.json")) priv.Save() @@ -151,36 +238,36 @@ func saveNewPriv() { //------------------------------------------------------------------------------- // some default transaction functions -func makeDefaultSendTx(t *testing.T, client rpcclient.Client, addr []byte, +func makeDefaultSendTx(t *testing.T, client client.RPCClient, addr []byte, amt int64) *txs.SendTx { - nonce := getNonce(t, client, user[0].Address) + nonce := getNonce(t, client, users[0].Address) tx := txs.NewSendTx() - tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1) + tx.AddInputWithNonce(users[0].PubKey, amt, nonce+1) tx.AddOutput(addr, amt) return tx } -func makeDefaultSendTxSigned(t *testing.T, client rpcclient.Client, addr []byte, +func makeDefaultSendTxSigned(t *testing.T, client client.RPCClient, addr []byte, amt int64) *txs.SendTx { tx := makeDefaultSendTx(t, client, addr, amt) - tx.SignInput(chainID, 0, user[0]) + tx.SignInput(chainID, 0, users[0]) return tx } -func makeDefaultCallTx(t *testing.T, client rpcclient.Client, addr, code []byte, amt, gasLim, +func makeDefaultCallTx(t *testing.T, client client.RPCClient, addr, code []byte, amt, gasLim, fee int64) *txs.CallTx { - nonce := getNonce(t, client, user[0].Address) - tx := txs.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, + nonce := getNonce(t, client, users[0].Address) + tx := txs.NewCallTxWithNonce(users[0].PubKey, addr, code, amt, gasLim, fee, nonce+1) - tx.Sign(chainID, user[0]) + tx.Sign(chainID, users[0]) return tx } -func makeDefaultNameTx(t *testing.T, client rpcclient.Client, name, value string, amt, +func makeDefaultNameTx(t *testing.T, client client.RPCClient, name, value string, amt, fee int64) *txs.NameTx { - nonce := getNonce(t, client, user[0].Address) - tx := txs.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1) - tx.Sign(chainID, user[0]) + nonce := getNonce(t, client, users[0].Address) + tx := txs.NewNameTxWithNonce(users[0].PubKey, name, value, amt, fee, nonce+1) + tx.Sign(chainID, users[0]) return tx } @@ -188,7 +275,7 @@ func makeDefaultNameTx(t *testing.T, client rpcclient.Client, name, value string // rpc call wrappers (fail on err) // get an account's nonce -func getNonce(t *testing.T, client rpcclient.Client, addr []byte) int { +func getNonce(t *testing.T, client client.RPCClient, addr []byte) int { ac, err := edbcli.GetAccount(client, addr) if err != nil { t.Fatal(err) @@ -200,7 +287,7 @@ func getNonce(t *testing.T, client rpcclient.Client, addr []byte) int { } // get the account -func getAccount(t *testing.T, client rpcclient.Client, addr []byte) *acm.Account { +func getAccount(t *testing.T, client client.RPCClient, addr []byte) *acm.Account { ac, err := edbcli.GetAccount(client, addr) if err != nil { t.Fatal(err) @@ -209,7 +296,7 @@ func getAccount(t *testing.T, client rpcclient.Client, addr []byte) *acm.Account } // sign transaction -func signTx(t *testing.T, client rpcclient.Client, tx txs.Tx, +func signTx(t *testing.T, client client.RPCClient, tx txs.Tx, privAcc *acm.PrivAccount) txs.Tx { signedTx, err := edbcli.SignTx(client, tx, []*acm.PrivAccount{privAcc}) if err != nil { @@ -219,7 +306,7 @@ func signTx(t *testing.T, client rpcclient.Client, tx txs.Tx, } // broadcast transaction -func broadcastTx(t *testing.T, client rpcclient.Client, tx txs.Tx) txs.Receipt { +func broadcastTx(t *testing.T, client client.RPCClient, tx txs.Tx) txs.Receipt { rec, err := edbcli.BroadcastTx(client, tx) if err != nil { t.Fatal(err) @@ -238,7 +325,7 @@ func dumpStorage(t *testing.T, addr []byte) *rpc_types.ResultDumpStorage { return resp } -func getStorage(t *testing.T, client rpcclient.Client, addr, key []byte) []byte { +func getStorage(t *testing.T, client client.RPCClient, addr, key []byte) []byte { resp, err := edbcli.GetStorage(client, addr, key) if err != nil { t.Fatal(err) @@ -246,7 +333,7 @@ func getStorage(t *testing.T, client rpcclient.Client, addr, key []byte) []byte return resp } -func callCode(t *testing.T, client rpcclient.Client, fromAddress, code, data, +func callCode(t *testing.T, client client.RPCClient, fromAddress, code, data, expected []byte) { resp, err := edbcli.CallCode(client, fromAddress, code, data) if err != nil { @@ -259,7 +346,7 @@ func callCode(t *testing.T, client rpcclient.Client, fromAddress, code, data, } } -func callContract(t *testing.T, client rpcclient.Client, fromAddress, toAddress, +func callContract(t *testing.T, client client.RPCClient, fromAddress, toAddress, data, expected []byte) { resp, err := edbcli.Call(client, fromAddress, toAddress, data) if err != nil { @@ -273,7 +360,7 @@ func callContract(t *testing.T, client rpcclient.Client, fromAddress, toAddress, } // get the namereg entry -func getNameRegEntry(t *testing.T, client rpcclient.Client, name string) *core_types.NameRegEntry { +func getNameRegEntry(t *testing.T, client client.RPCClient, name string) *core_types.NameRegEntry { entry, err := edbcli.GetName(client, name) if err != nil { t.Fatal(err) diff --git a/rpc/tendermint/test/common_test.go b/rpc/tendermint/test/shared_test.go similarity index 100% rename from rpc/tendermint/test/common_test.go rename to rpc/tendermint/test/shared_test.go diff --git a/rpc/tendermint/test/websocket_client_test.go b/rpc/tendermint/test/websocket_client_test.go index 046f13ea44c0d239fcd89655d6cb4c55c6bfca4d..4bb2609269bfdbb85978cc15f23fbea6d2292f3b 100644 --- a/rpc/tendermint/test/websocket_client_test.go +++ b/rpc/tendermint/test/websocket_client_test.go @@ -93,9 +93,9 @@ func TestWSBlockchainGrowth(t *testing.T) { // send a transaction and validate the events from listening for both sender and receiver func TestWSSend(t *testing.T) { wsc := newWSClient() - toAddr := user[1].Address + toAddr := users[1].Address amt := int64(100) - eidInput := txs.EventStringAccInput(user[0].Address) + eidInput := txs.EventStringAccInput(users[0].Address) eidOutput := txs.EventStringAccOutput(toAddr) subIdInput := subscribeAndGetSubscriptionId(t, wsc, eidInput) subIdOutput := subscribeAndGetSubscriptionId(t, wsc, eidOutput) @@ -119,14 +119,14 @@ func TestWSDoubleFire(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid := txs.EventStringAccInput(user[0].Address) + eid := txs.EventStringAccInput(users[0].Address) subId := subscribeAndGetSubscriptionId(t, wsc, eid) defer func() { unsubscribe(t, wsc, subId) wsc.Stop() }() amt := int64(100) - toAddr := user[1].Address + toAddr := users[1].Address // broadcast the transaction, wait to hear about it waitForEvent(t, wsc, eid, func() { tx := makeDefaultSendTxSigned(t, jsonRpcClient, toAddr, amt) @@ -150,7 +150,7 @@ func TestWSCallWait(t *testing.T) { t.Skip("skipping test in short mode.") } wsc := newWSClient() - eid1 := txs.EventStringAccInput(user[0].Address) + eid1 := txs.EventStringAccInput(users[0].Address) subId1 := subscribeAndGetSubscriptionId(t, wsc, eid1) defer func() { unsubscribe(t, wsc, subId1) @@ -252,7 +252,7 @@ func TestWSCallCall(t *testing.T) { tx := makeDefaultCallTx(t, jsonRpcClient, contractAddr2, nil, amt, gasLim, fee) broadcastTx(t, jsonRpcClient, tx) *txid = txs.TxHash(chainID, tx) - }, unmarshalValidateCall(user[0].Address, returnVal, txid)) + }, unmarshalValidateCall(users[0].Address, returnVal, txid)) } func TestSubscribe(t *testing.T) { @@ -260,11 +260,12 @@ func TestSubscribe(t *testing.T) { var subId string subscribe(t, wsc, txs.EventStringNewBlock()) - timeout := time.NewTimer(timeoutSeconds * time.Second) + // timeout to check subscription process is live + timeout := time.After(timeoutSeconds * time.Second) Subscribe: for { select { - case <-timeout.C: + case <-timeout: t.Fatal("Timed out waiting for subscription result") case bs := <-wsc.ResultsCh: @@ -277,12 +278,13 @@ Subscribe: } } - seenBlock := false - timeout = time.NewTimer(timeoutSeconds * time.Second) + blocksSeen := 0 for { select { - case <-timeout.C: - if !seenBlock { + // wait long enough to check we don't see another new block event even though + // a block will have come + case <-time.After(expectBlockInSeconds * time.Second): + if blocksSeen == 0 { t.Fatal("Timed out without seeing a NewBlock event") } return @@ -292,13 +294,13 @@ Subscribe: if ok { _, ok := resultEvent.Data.(txs.EventDataNewBlock) if ok { - if seenBlock { - // There's a mild race here, but when we enter we've just seen a block - // so we should be able to unsubscribe before we see another block + if blocksSeen > 1 { t.Fatal("Continued to see NewBlock event after unsubscribing") } else { - seenBlock = true - unsubscribe(t, wsc, subId) + if blocksSeen == 0 { + unsubscribe(t, wsc, subId) + } + blocksSeen++ } } } diff --git a/rpc/tendermint/test/websocket_helpers.go b/rpc/tendermint/test/websocket_helpers.go index 6cb6a2a4f22b343e7b6b11186ac1ebe540602d2a..1e013b5203dade9e0b76eac80e5f1361b88bac5b 100644 --- a/rpc/tendermint/test/websocket_helpers.go +++ b/rpc/tendermint/test/websocket_helpers.go @@ -20,17 +20,18 @@ import ( "testing" "time" + burrow_client "github.com/hyperledger/burrow/rpc/tendermint/client" ctypes "github.com/hyperledger/burrow/rpc/tendermint/core/types" "github.com/hyperledger/burrow/txs" - tm_types "github.com/tendermint/tendermint/types" - edbcli "github.com/hyperledger/burrow/rpc/tendermint/client" rpcclient "github.com/tendermint/go-rpc/client" "github.com/tendermint/go-wire" + tm_types "github.com/tendermint/tendermint/types" ) const ( - timeoutSeconds = 2 + timeoutSeconds = 2 + expectBlockInSeconds = timeoutSeconds * 2 ) //-------------------------------------------------------------------------------- @@ -48,14 +49,14 @@ func newWSClient() *rpcclient.WSClient { // subscribe to an event func subscribe(t *testing.T, wsc *rpcclient.WSClient, eventId string) { - if err := wsc.Subscribe(eventId); err != nil { + if err := burrow_client.Subscribe(wsc, eventId); err != nil { t.Fatal(err) } } func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, eventId string) string { - if err := wsc.Subscribe(eventId); err != nil { + if err := burrow_client.Subscribe(wsc, eventId); err != nil { t.Fatal(err) } @@ -75,19 +76,19 @@ func subscribeAndGetSubscriptionId(t *testing.T, wsc *rpcclient.WSClient, // unsubscribe from an event func unsubscribe(t *testing.T, wsc *rpcclient.WSClient, subscriptionId string) { - if err := wsc.Unsubscribe(subscriptionId); err != nil { + if err := burrow_client.Unsubscribe(wsc, subscriptionId); err != nil { t.Fatal(err) } } // broadcast transaction and wait for new block -func broadcastTxAndWaitForBlock(t *testing.T, client rpcclient.Client, wsc *rpcclient.WSClient, - tx txs.Tx) (txs.Receipt, error) { +func broadcastTxAndWaitForBlock(t *testing.T, client burrow_client.RPCClient, + wsc *rpcclient.WSClient, tx txs.Tx) (txs.Receipt, error) { var rec txs.Receipt var err error runThenWaitForBlock(t, wsc, nextBlockPredicateFn(), func() { - rec, err = edbcli.BroadcastTx(client, tx) + rec, err = burrow_client.BroadcastTx(client, tx) mempoolCount += 1 }) return rec, err @@ -231,14 +232,14 @@ func unmarshalValidateSend(amt int64, return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.SendTx) - if !bytes.Equal(tx.Inputs[0].Address, user[0].Address) { - return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address) + if !bytes.Equal(tx.Inputs[0].Address, users[0].Address) { + return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, users[0].Address) } if tx.Inputs[0].Amount != amt { return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) } if !bytes.Equal(tx.Outputs[0].Address, toAddr) { - return true, fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address) + return true, fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, users[0].Address) } return true, nil } @@ -252,9 +253,9 @@ func unmarshalValidateTx(amt int64, return true, fmt.Errorf(data.Exception) } tx := data.Tx.(*txs.CallTx) - if !bytes.Equal(tx.Input.Address, user[0].Address) { + if !bytes.Equal(tx.Input.Address, users[0].Address) { return true, fmt.Errorf("Senders do not match up! Got %x, expected %x", - tx.Input.Address, user[0].Address) + tx.Input.Address, users[0].Address) } if tx.Input.Amount != amt { return true, fmt.Errorf("Amt does not match up! Got %d, expected %d", diff --git a/rpc/v0/json_service.go b/rpc/v0/json_service.go index c04120714ba551456ae4d2df0cbcdea59733202e..6cb9f8ca76a724dd37f99dce677bd718a4bdb9ca 100644 --- a/rpc/v0/json_service.go +++ b/rpc/v0/json_service.go @@ -20,7 +20,7 @@ import ( definitions "github.com/hyperledger/burrow/definitions" event "github.com/hyperledger/burrow/event" - rpc "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc" server "github.com/hyperledger/burrow/server" "github.com/gin-gonic/gin" diff --git a/rpc/v0/rest_server.go b/rpc/v0/rest_server.go index 3f76af2042c5ac47bad6da66ed62e58636c711c0..03c70c5afd3af3bc8cd2104844646d5d0e874ad9 100644 --- a/rpc/v0/rest_server.go +++ b/rpc/v0/rest_server.go @@ -500,15 +500,6 @@ func heightParam(c *gin.Context) { c.Next() } -func subIdParam(c *gin.Context) { - subId := c.Param("id") - if len(subId) != 64 || !util.IsHex(subId) { - c.AbortWithError(400, fmt.Errorf("Malformed event id")) - } - c.Set("id", subId) - c.Next() -} - // TODO func peerAddressParam(c *gin.Context) { subId := c.Param("address") diff --git a/rpc/v0/rest_server_pipe_test.go b/rpc/v0/rest_server_pipe_test.go index 52b1863461b4ca7e9ff4b2ffd8cdc6bb2a9c2b53..1cd8282288fb26024268a52b694c76856142c71c 100644 --- a/rpc/v0/rest_server_pipe_test.go +++ b/rpc/v0/rest_server_pipe_test.go @@ -24,6 +24,7 @@ import ( blockchain_types "github.com/hyperledger/burrow/blockchain/types" consensus_types "github.com/hyperledger/burrow/consensus/types" + logging_types "github.com/hyperledger/burrow/logging/types" manager_types "github.com/hyperledger/burrow/manager/types" "github.com/hyperledger/burrow/txs" @@ -43,7 +44,7 @@ type MockPipe struct { events event.EventEmitter namereg definitions.NameReg transactor definitions.Transactor - logger loggers.InfoTraceLogger + logger logging_types.InfoTraceLogger } // Create a new mock tendermint pipe. @@ -85,7 +86,7 @@ func (pipe *MockPipe) Transactor() definitions.Transactor { return pipe.transactor } -func (pipe *MockPipe) Logger() loggers.InfoTraceLogger { +func (pipe *MockPipe) Logger() logging_types.InfoTraceLogger { return pipe.logger } @@ -236,6 +237,10 @@ func (cons *consensusEngine) PeerConsensusStates() map[string]string { return map[string]string{} } +func (cons *consensusEngine) Stop() bool { + return true +} + // Events type eventer struct { testData *TestData @@ -281,7 +286,7 @@ func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { } func (trans *transactor) Transact(privKey, address, data []byte, gasLimit, fee int64) (*txs.Receipt, error) { - if address == nil || len(address) == 0 { + if len(address) == 0 { return trans.testData.TransactCreate.Output, nil } return trans.testData.Transact.Output, nil diff --git a/rpc/v0/rest_server_test.go b/rpc/v0/rest_server_test.go index b488bd25d07c88478a90c66ee58f5b978becaafd..2134e951294ad82a18949f508cff0ad608023ee5 100644 --- a/rpc/v0/rest_server_test.go +++ b/rpc/v0/rest_server_test.go @@ -32,11 +32,14 @@ import ( "github.com/hyperledger/burrow/txs" "github.com/gin-gonic/gin" + "github.com/hyperledger/burrow/logging/lifecycle" "github.com/hyperledger/burrow/rpc/v0/shared" "github.com/stretchr/testify/suite" "github.com/tendermint/log15" ) +var logger, _ = lifecycle.NewStdErrLogger() + func init() { runtime.GOMAXPROCS(runtime.NumCPU()) log15.Root().SetHandler(log15.LvlFilterHandler( @@ -48,7 +51,6 @@ func init() { type MockSuite struct { suite.Suite - baseDir string serveProcess *server.ServeProcess codec rpc.Codec sUrl string @@ -67,7 +69,7 @@ func (mockSuite *MockSuite) SetupSuite() { sConf := server.DefaultServerConfig() sConf.Bind.Port = 31402 // Create a server process. - proc, _ := server.NewServeProcess(sConf, restServer) + proc, _ := server.NewServeProcess(sConf, logger, restServer) err := proc.Start() if err != nil { panic(err) diff --git a/server/config.go b/server/config.go index 4dcce19dd18199cfbbd19ae0f54bed9554b8db53..7a7a60425c43f9a1305a2b26a243aaefc8cfa4ec 100644 --- a/server/config.go +++ b/server/config.go @@ -30,7 +30,6 @@ type ( HTTP HTTP `toml:"HTTP"` WebSocket WebSocket `toml:"web_socket"` Tendermint Tendermint - Logging Logging `toml:"logging"` } Bind struct { @@ -70,12 +69,6 @@ type ( RpcLocalAddress string Endpoint string } - - Logging struct { - ConsoleLogLevel string `toml:"console_log_level"` - FileLogLevel string `toml:"file_log_level"` - LogFile string `toml:"log_file"` - } ) func ReadServerConfig(viper *viper.Viper) (*ServerConfig, error) { @@ -160,11 +153,6 @@ func ReadServerConfig(viper *viper.Viper) (*ServerConfig, error) { RpcLocalAddress: viper.GetString("tendermint.rpc_local_address"), Endpoint: viper.GetString("tendermint.endpoint"), }, - Logging: Logging{ - ConsoleLogLevel: viper.GetString("logging.console_log_level"), - FileLogLevel: viper.GetString("logging.file_log_level"), - LogFile: viper.GetString("logging.log_file"), - }, }, nil } @@ -194,10 +182,5 @@ func DefaultServerConfig() *ServerConfig { RpcLocalAddress: "0.0.0.0:46657", Endpoint: "/websocket", }, - Logging: Logging{ - ConsoleLogLevel: "info", - FileLogLevel: "warn", - LogFile: "", - }, } } diff --git a/server/logging.go b/server/logging.go deleted file mode 100644 index 4a38e0f82d039b18400ec07d1767812e1210c574..0000000000000000000000000000000000000000 --- a/server/logging.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "fmt" - "os" - - "github.com/tendermint/log15" -) - -var rootHandler log15.Handler - -// This is basically the same code as in tendermint. Initialize root -// and maybe later also track the loggers here. -func InitLogger(config *ServerConfig) { - - consoleLogLevel := config.Logging.ConsoleLogLevel - - // stdout handler - handlers := []log15.Handler{} - stdoutHandler := log15.LvlFilterHandler( - getLevel(consoleLogLevel), - log15.StreamHandler(os.Stdout, log15.TerminalFormat()), - ) - handlers = append(handlers, stdoutHandler) - - if config.Logging.LogFile != "" { - fileLogLevel := config.Logging.FileLogLevel - fh, err := log15.FileHandler(config.Logging.LogFile, log15.LogfmtFormat()) - if err != nil { - fmt.Println("Error creating log file: " + err.Error()) - os.Exit(1) - } - fileHandler := log15.LvlFilterHandler(getLevel(fileLogLevel), fh) - handlers = append(handlers, fileHandler) - } - - rootHandler = log15.MultiHandler(handlers...) - - // By setting handlers on the root, we handle events from all loggers. - log15.Root().SetHandler(rootHandler) -} - -// See binary/log for an example of usage. -func RootHandler() log15.Handler { - return rootHandler -} - -func New(ctx ...interface{}) log15.Logger { - return log15.Root().New(ctx...) -} - -func getLevel(lvlString string) log15.Lvl { - lvl, err := log15.LvlFromString(lvlString) - if err != nil { - fmt.Printf("Invalid log level %v: %v", lvlString, err) - os.Exit(1) - } - return lvl -} diff --git a/server/server.go b/server/server.go index 28d107eec1740b5cd0ed10fa6bd6d725b0178e73..2a3d7bc7cff5cd21147323b10f1e337021871285 100644 --- a/server/server.go +++ b/server/server.go @@ -22,6 +22,8 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/hyperledger/burrow/logging" + logging_types "github.com/hyperledger/burrow/logging/types" cors "github.com/tommy351/gin-cors" "gopkg.in/tylerb/graceful.v1" ) @@ -57,19 +59,18 @@ type ServeProcess struct { startListenChans []chan struct{} stopListenChans []chan struct{} srv *graceful.Server + logger logging_types.InfoTraceLogger } // Initializes all the servers and starts listening for connections. -func (this *ServeProcess) Start() error { +func (serveProcess *ServeProcess) Start() error { router := gin.New() - config := this.config - - InitLogger(config) + config := serveProcess.config ch := NewCORSMiddleware(config.CORS) - router.Use(gin.Recovery(), logHandler, contentTypeMW, ch) + router.Use(gin.Recovery(), logHandler(serveProcess.logger), contentTypeMW, ch) address := config.Bind.Address port := config.Bind.Port @@ -86,7 +87,7 @@ func (this *ServeProcess) Start() error { } // Start the servers/handlers. - for _, s := range this.servers { + for _, s := range serveProcess.servers { s.Start(config, router) } @@ -119,15 +120,18 @@ func (this *ServeProcess) Start() error { } else { lst = l } - this.srv = srv - log.Info("Server started.") - for _, c := range this.startListenChans { + serveProcess.srv = srv + logging.InfoMsg(serveProcess.logger, "Server started.", + "chain_id", serveProcess.config.ChainId, + "address", serveProcess.config.Bind.Address, + "port", serveProcess.config.Bind.Port) + for _, c := range serveProcess.startListenChans { c <- struct{}{} } // Start the serve routine. go func() { - this.srv.Serve(lst) - for _, s := range this.servers { + serveProcess.srv.Serve(lst) + for _, s := range serveProcess.servers { s.ShutDown() } }() @@ -135,16 +139,16 @@ func (this *ServeProcess) Start() error { // on the graceful Server. This happens when someone // calls 'Stop' on the process. go func() { - <-this.stopChan - log.Info("Close signal sent to server.") - this.srv.Stop(killTime) + <-serveProcess.stopChan + logging.InfoMsg(serveProcess.logger, "Close signal sent to server.") + serveProcess.srv.Stop(killTime) }() // Listen to the servers stop event. It is triggered when // the server has been fully shut down. go func() { - <-this.srv.StopChan() - log.Info("Server stop event fired. Good bye.") - for _, c := range this.stopListenChans { + <-serveProcess.srv.StopChan() + logging.InfoMsg(serveProcess.logger, "Server stop event fired. Good bye.") + for _, c := range serveProcess.stopListenChans { c <- struct{}{} } }() @@ -154,8 +158,8 @@ func (this *ServeProcess) Start() error { // Stop will release the port, process any remaining requests // up until the timeout duration is passed, at which point it // will abort them and shut down. -func (this *ServeProcess) Stop(timeout time.Duration) error { - for _, s := range this.servers { +func (serveProcess *ServeProcess) Stop(timeout time.Duration) error { + for _, s := range serveProcess.servers { s.ShutDown() } toChan := make(chan struct{}) @@ -166,8 +170,8 @@ func (this *ServeProcess) Stop(timeout time.Duration) error { }() } - lChan := this.StopEventChannel() - this.stopChan <- struct{}{} + lChan := serveProcess.StopEventChannel() + serveProcess.stopChan <- struct{}{} select { case <-lChan: return nil @@ -180,9 +184,9 @@ func (this *ServeProcess) Stop(timeout time.Duration) error { // is fired after the Start() function is called, and after // the server has started listening for incoming connections. // An error here . -func (this *ServeProcess) StartEventChannel() <-chan struct{} { +func (serveProcess *ServeProcess) StartEventChannel() <-chan struct{} { lChan := make(chan struct{}, 1) - this.startListenChans = append(this.startListenChans, lChan) + serveProcess.startListenChans = append(serveProcess.startListenChans, lChan) return lChan } @@ -191,15 +195,15 @@ func (this *ServeProcess) StartEventChannel() <-chan struct{} { // timeout has passed. When the timeout has passed it will wait // for confirmation from the http.Server, which normally takes // a very short time (milliseconds). -func (this *ServeProcess) StopEventChannel() <-chan struct{} { +func (serveProcess *ServeProcess) StopEventChannel() <-chan struct{} { lChan := make(chan struct{}, 1) - this.stopListenChans = append(this.stopListenChans, lChan) + serveProcess.stopListenChans = append(serveProcess.stopListenChans, lChan) return lChan } // Creates a new serve process. -func NewServeProcess(config *ServerConfig, servers ...Server) (*ServeProcess, - error) { +func NewServeProcess(config *ServerConfig, logger logging_types.InfoTraceLogger, + servers ...Server) (*ServeProcess, error) { var scfg ServerConfig if config == nil { return nil, fmt.Errorf("Nil passed as server configuration") @@ -210,25 +214,40 @@ func NewServeProcess(config *ServerConfig, servers ...Server) (*ServeProcess, stoppedChan := make(chan struct{}, 1) startListeners := make([]chan struct{}, 0) stopListeners := make([]chan struct{}, 0) - sp := &ServeProcess{&scfg, servers, stopChan, stoppedChan, startListeners, stopListeners, nil} + sp := &ServeProcess{ + config: &scfg, + servers: servers, + stopChan: stopChan, + stoppedChan: stoppedChan, + startListenChans: startListeners, + stopListenChans: stopListeners, + srv: nil, + logger: logging.WithScope(logger, "ServeProcess"), + } return sp, nil } // Used to enable log15 logging instead of the default Gin logging. -func logHandler(c *gin.Context) { - - path := c.Request.URL.Path +func logHandler(logger logging_types.InfoTraceLogger) gin.HandlerFunc { + logger = logging.WithScope(logger, "ginLogHandler") + return func(c *gin.Context) { - // Process request - c.Next() + path := c.Request.URL.Path - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - comment := c.Errors.String() + // Process request + c.Next() - log.Info("[GIN] HTTP: "+clientIP, "Code", statusCode, "Method", method, "path", path, "error", comment) + clientIP := c.ClientIP() + method := c.Request.Method + statusCode := c.Writer.Status() + comment := c.Errors.String() + logger.Info("client_ip", clientIP, + "status_code", statusCode, + "method", method, + "path", path, + "error", comment) + } } func NewCORSMiddleware(options CORS) gin.HandlerFunc { diff --git a/server/websocket.go b/server/websocket.go index aff2f94c307aa13c3383292ad9dd44f4b054b8e2..b7c8a111cfbeb07a269cdde76df8f6f6b67e2989 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -22,6 +22,8 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + "github.com/hyperledger/burrow/logging" + logging_types "github.com/hyperledger/burrow/logging/types" ) // TODO too much fluff. Should probably phase gorilla out and move closer @@ -56,6 +58,7 @@ type WebSocketServer struct { sessionManager *SessionManager config *ServerConfig allOrigins bool + logger logging_types.InfoTraceLogger } // Create a new server. @@ -63,69 +66,72 @@ type WebSocketServer struct { // NOTE: This is not the total number of connections allowed - only those that are // upgraded to websockets. Requesting a websocket connection will fail with a 503 if // the server is at capacity. -func NewWebSocketServer(maxSessions uint16, service WebSocketService) *WebSocketServer { +func NewWebSocketServer(maxSessions uint16, service WebSocketService, + logger logging_types.InfoTraceLogger) *WebSocketServer { return &WebSocketServer{ maxSessions: maxSessions, - sessionManager: NewSessionManager(maxSessions, service), + sessionManager: NewSessionManager(maxSessions, service, logger), + logger: logging.WithScope(logger, "WebSocketServer"), } } // Start the server. Adds the handler to the router and sets everything up. -func (this *WebSocketServer) Start(config *ServerConfig, router *gin.Engine) { +func (wsServer *WebSocketServer) Start(config *ServerConfig, router *gin.Engine) { - this.config = config + wsServer.config = config - this.upgrader = websocket.Upgrader{ + wsServer.upgrader = websocket.Upgrader{ ReadBufferSize: int(config.WebSocket.ReadBufferSize), // TODO Will this be enough for massive "get blockchain" requests? WriteBufferSize: int(config.WebSocket.WriteBufferSize), } - this.upgrader.CheckOrigin = func(r *http.Request) bool { return true } - router.GET(config.WebSocket.WebSocketEndpoint, this.handleFunc) - this.running = true + wsServer.upgrader.CheckOrigin = func(r *http.Request) bool { return true } + router.GET(config.WebSocket.WebSocketEndpoint, wsServer.handleFunc) + wsServer.running = true } // Is the server currently running. -func (this *WebSocketServer) Running() bool { - return this.running +func (wsServer *WebSocketServer) Running() bool { + return wsServer.running } // Shut the server down. -func (this *WebSocketServer) ShutDown() { - this.sessionManager.Shutdown() - this.running = false +func (wsServer *WebSocketServer) ShutDown() { + wsServer.sessionManager.Shutdown() + wsServer.running = false } // Get the session-manager. -func (this *WebSocketServer) SessionManager() *SessionManager { - return this.sessionManager +func (wsServer *WebSocketServer) SessionManager() *SessionManager { + return wsServer.sessionManager } // Handler for websocket requests. -func (this *WebSocketServer) handleFunc(c *gin.Context) { +func (wsServer *WebSocketServer) handleFunc(c *gin.Context) { r := c.Request w := c.Writer // Upgrade to websocket. - wsConn, uErr := this.upgrader.Upgrade(w, r, nil) + wsConn, uErr := wsServer.upgrader.Upgrade(w, r, nil) if uErr != nil { - uErrStr := "Failed to upgrade to websocket connection: " + uErr.Error() - http.Error(w, uErrStr, 400) - log.Info(uErrStr) + errMsg := "Failed to upgrade to websocket connection" + http.Error(w, fmt.Sprintf("%s: %s", errMsg, uErr.Error()), 400) + logging.InfoMsg(wsServer.logger, errMsg, "error", uErr) return } - session, cErr := this.sessionManager.createSession(wsConn) + session, cErr := wsServer.sessionManager.createSession(wsConn) if cErr != nil { - cErrStr := "Failed to establish websocket connection: " + cErr.Error() - http.Error(w, cErrStr, 503) - log.Info(cErrStr) + errMsg := "Failed to establish websocket connection" + http.Error(w, fmt.Sprintf("%s: %s", errMsg, cErr.Error()), 503) + logging.InfoMsg(wsServer.logger, errMsg, "error", cErr) return } // Start the connection. - log.Info("New websocket connection.", "sessionId", session.id) + logging.InfoMsg(wsServer.logger, "New websocket connection", + "session_id", session.id) session.Open() } @@ -148,57 +154,59 @@ type WSSession struct { service WebSocketService opened bool closed bool + logger logging_types.InfoTraceLogger } // Write a text message to the client. -func (this *WSSession) Write(msg []byte) error { - if this.closed { - log.Warn("Attempting to write to closed session.", "sessionId", this.id) +func (wsSession *WSSession) Write(msg []byte) error { + if wsSession.closed { + logging.InfoMsg(wsSession.logger, "Attempting to write to closed session.") return fmt.Errorf("Session is closed") } - this.writeChan <- msg + wsSession.writeChan <- msg return nil } // Private. Helper for writing control messages. -func (this *WSSession) write(mt int, payload []byte) error { - this.wsConn.SetWriteDeadline(time.Now().Add(writeWait)) - return this.wsConn.WriteMessage(mt, payload) +func (wsSession *WSSession) write(mt int, payload []byte) error { + wsSession.wsConn.SetWriteDeadline(time.Now().Add(writeWait)) + return wsSession.wsConn.WriteMessage(mt, payload) } // Get the session id number. -func (this *WSSession) Id() uint { - return this.id +func (wsSession *WSSession) Id() uint { + return wsSession.id } // Starts the read and write pumps. Blocks on the former. // Notifies all the observers. -func (this *WSSession) Open() { - this.opened = true - this.sessionManager.notifyOpened(this) - go this.writePump() - this.readPump() +func (wsSession *WSSession) Open() { + wsSession.opened = true + wsSession.sessionManager.notifyOpened(wsSession) + go wsSession.writePump() + wsSession.readPump() } // Closes the net connection and cleans up. Notifies all the observers. -func (this *WSSession) Close() { - if !this.closed { - this.closed = true - this.wsConn.Close() - this.sessionManager.removeSession(this.id) - log.Info("Closing websocket connection.", "sessionId", this.id, "remaining", len(this.sessionManager.activeSessions)) - this.sessionManager.notifyClosed(this) +func (wsSession *WSSession) Close() { + if !wsSession.closed { + wsSession.closed = true + wsSession.wsConn.Close() + wsSession.sessionManager.removeSession(wsSession.id) + logging.InfoMsg(wsSession.logger, "Closing websocket connection.", + "remaining_active_sessions", len(wsSession.sessionManager.activeSessions)) + wsSession.sessionManager.notifyClosed(wsSession) } } // Has the session been opened? -func (this *WSSession) Opened() bool { - return this.opened +func (wsSession *WSSession) Opened() bool { + return wsSession.opened } // Has the session been closed? -func (this *WSSession) Closed() bool { - return this.closed +func (wsSession *WSSession) Closed() bool { + return wsSession.closed } // Pump debugging @@ -210,7 +218,7 @@ var wpm *sync.Mutex = &sync.Mutex{} */ // Read loop. Will terminate on a failed read. -func (this *WSSession) readPump() { +func (wsSession *WSSession) readPump() { /* rpm.Lock() rp++ @@ -223,38 +231,40 @@ func (this *WSSession) readPump() { rpm.Unlock() }() */ - this.wsConn.SetReadLimit(maxMessageSize) + wsSession.wsConn.SetReadLimit(maxMessageSize) // this.wsConn.SetReadDeadline(time.Now().Add(pongWait)) // this.wsConn.SetPongHandler(func(string) error { this.wsConn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { // Read - msgType, msg, err := this.wsConn.ReadMessage() + msgType, msg, err := wsSession.wsConn.ReadMessage() // Read error. if err != nil { // Socket could have been gracefully closed, so not really an error. - log.Info("Socket closed. Removing.", "error", err.Error()) - this.writeCloseChan <- struct{}{} + logging.InfoMsg(wsSession.logger, + "Socket closed. Removing.", "error", err) + wsSession.writeCloseChan <- struct{}{} return } if msgType != websocket.TextMessage { - log.Info("Receiving non text-message from client, closing.") - this.writeCloseChan <- struct{}{} + logging.InfoMsg(wsSession.logger, + "Receiving non text-message from client, closing.") + wsSession.writeCloseChan <- struct{}{} return } go func() { // Process the request. - this.service.Process(msg, this) + wsSession.service.Process(msg, wsSession) }() } } // Writes messages coming in on the write channel. Will terminate on failed writes, // if pings are not responded to, or if a message comes in on the write close channel. -func (this *WSSession) writePump() { +func (wsSession *WSSession) writePump() { /* wpm.Lock() wp++ @@ -271,23 +281,24 @@ func (this *WSSession) writePump() { defer func() { // ticker.Stop() - this.Close() + wsSession.Close() }() // Write loop. Blocks while waiting for data to come in over a channel. for { select { // Write request. - case msg := <-this.writeChan: + case msg := <-wsSession.writeChan: // Write the bytes to the socket. - err := this.wsConn.WriteMessage(websocket.TextMessage, msg) + err := wsSession.wsConn.WriteMessage(websocket.TextMessage, msg) if err != nil { // Could be due to the socket being closed so not really an error. - log.Info("Writing to socket failed. Closing.") + logging.InfoMsg(wsSession.logger, + "Writing to socket failed. Closing.") return } - case <-this.writeCloseChan: + case <-wsSession.writeCloseChan: return // Ticker run out. Time for another ping message. /* @@ -311,10 +322,12 @@ type SessionManager struct { service WebSocketService openEventChans []chan *WSSession closeEventChans []chan *WSSession + logger logging_types.InfoTraceLogger } // Create a new WebsocketManager. -func NewSessionManager(maxSessions uint16, wss WebSocketService) *SessionManager { +func NewSessionManager(maxSessions uint16, wss WebSocketService, + logger logging_types.InfoTraceLogger) *SessionManager { return &SessionManager{ maxSessions: maxSessions, activeSessions: make(map[uint]*WSSession), @@ -323,30 +336,30 @@ func NewSessionManager(maxSessions uint16, wss WebSocketService) *SessionManager service: wss, openEventChans: []chan *WSSession{}, closeEventChans: []chan *WSSession{}, + logger: logging.WithScope(logger, "SessionManager"), } } // TODO -func (this *SessionManager) Shutdown() { - this.activeSessions = nil +func (sessionManager *SessionManager) Shutdown() { + sessionManager.activeSessions = nil } // Add a listener to session open events. -func (this *SessionManager) SessionOpenEventChannel() <-chan *WSSession { +func (sessionManager *SessionManager) SessionOpenEventChannel() <-chan *WSSession { lChan := make(chan *WSSession, 1) - this.openEventChans = append(this.openEventChans, lChan) + sessionManager.openEventChans = append(sessionManager.openEventChans, lChan) return lChan } // Remove a listener from session open events. -func (this *SessionManager) RemoveSessionOpenEventChannel(lChan chan *WSSession) bool { - ec := this.openEventChans +func (sessionManager *SessionManager) RemoveSessionOpenEventChannel(lChan chan *WSSession) bool { + ec := sessionManager.openEventChans if len(ec) == 0 { return false } - for i, c := range ec { + for _, c := range ec { if lChan == c { - ec[i], ec = ec[len(ec)-1], ec[:len(ec)-1] return true } } @@ -354,21 +367,20 @@ func (this *SessionManager) RemoveSessionOpenEventChannel(lChan chan *WSSession) } // Add a listener to session close events -func (this *SessionManager) SessionCloseEventChannel() <-chan *WSSession { +func (sessionManager *SessionManager) SessionCloseEventChannel() <-chan *WSSession { lChan := make(chan *WSSession, 1) - this.closeEventChans = append(this.closeEventChans, lChan) + sessionManager.closeEventChans = append(sessionManager.closeEventChans, lChan) return lChan } // Remove a listener from session close events. -func (this *SessionManager) RemoveSessionCloseEventChannel(lChan chan *WSSession) bool { - ec := this.closeEventChans +func (sessionManager *SessionManager) RemoveSessionCloseEventChannel(lChan chan *WSSession) bool { + ec := sessionManager.closeEventChans if len(ec) == 0 { return false } - for i, c := range ec { + for _, c := range ec { if lChan == c { - ec[i], ec = ec[len(ec)-1], ec[:len(ec)-1] return true } } @@ -376,55 +388,57 @@ func (this *SessionManager) RemoveSessionCloseEventChannel(lChan chan *WSSession } // Used to notify all observers that a new session was opened. -func (this *SessionManager) notifyOpened(session *WSSession) { - for _, lChan := range this.openEventChans { +func (sessionManager *SessionManager) notifyOpened(session *WSSession) { + for _, lChan := range sessionManager.openEventChans { lChan <- session } } // Used to notify all observers that a new session was closed. -func (this *SessionManager) notifyClosed(session *WSSession) { - for _, lChan := range this.closeEventChans { +func (sessionManager *SessionManager) notifyClosed(session *WSSession) { + for _, lChan := range sessionManager.closeEventChans { lChan <- session } } // Creates a new session and adds it to the manager. -func (this *SessionManager) createSession(wsConn *websocket.Conn) (*WSSession, error) { +func (sessionManager *SessionManager) createSession(wsConn *websocket.Conn) (*WSSession, error) { // Check that the capacity hasn't been exceeded. - this.mtx.Lock() - defer this.mtx.Unlock() - if this.atCapacity() { + sessionManager.mtx.Lock() + defer sessionManager.mtx.Unlock() + if sessionManager.atCapacity() { return nil, fmt.Errorf("Already at capacity") } // Create and start - newId, _ := this.idPool.GetId() + newId, _ := sessionManager.idPool.GetId() conn := &WSSession{ - sessionManager: this, + sessionManager: sessionManager, id: newId, wsConn: wsConn, writeChan: make(chan []byte, maxMessageSize), writeCloseChan: make(chan struct{}), - service: this.service, + service: sessionManager.service, + logger: logging.WithScope(sessionManager.logger, "WSSession"). + With("session_id", newId), } - this.activeSessions[conn.id] = conn + sessionManager.activeSessions[conn.id] = conn return conn, nil } // Remove a session from the list. -func (this *SessionManager) removeSession(id uint) { - this.mtx.Lock() - defer this.mtx.Unlock() +func (sessionManager *SessionManager) removeSession(id uint) { + sessionManager.mtx.Lock() + defer sessionManager.mtx.Unlock() // Check that it exists. - _, ok := this.activeSessions[id] + _, ok := sessionManager.activeSessions[id] if ok { - delete(this.activeSessions, id) - this.idPool.ReleaseId(id) + delete(sessionManager.activeSessions, id) + sessionManager.idPool.ReleaseId(id) } } // True if the number of active connections is at the maximum. -func (this *SessionManager) atCapacity() bool { - return len(this.activeSessions) >= int(this.maxSessions) +func (sessionManager *SessionManager) atCapacity() bool { + return len(sessionManager.activeSessions) >= int(sessionManager.maxSessions) } diff --git a/test/filters/filter_test.go b/test/filters/filter_test.go index e3cf1052c20d0e0d01e8281a935bf6abac2ab350..35601f4696a5f5df43b50376c49491699c415176 100644 --- a/test/filters/filter_test.go +++ b/test/filters/filter_test.go @@ -96,7 +96,7 @@ type FilterSuite struct { } func (this *FilterSuite) SetupSuite() { - objects := make([]FilterableObject, OBJECTS, OBJECTS) + objects := make([]FilterableObject, OBJECTS) for i := 0; i < 100; i++ { objects[i] = FilterableObject{i, fmt.Sprintf("string%d", i)} diff --git a/test/fixtures/file_fixtures.go b/test/fixtures/file_fixtures.go index d6926c0972911d33b3f4d3272645388e36bec8bd..875e8398e5e007b4b4289f67348301689961a91b 100644 --- a/test/fixtures/file_fixtures.go +++ b/test/fixtures/file_fixtures.go @@ -73,17 +73,16 @@ func (ffs *FileFixtures) AddDir(name string) string { // Cleans up the the temporary files (with fire) func (ffs *FileFixtures) RemoveAll() { - if err := os.RemoveAll(ffs.tempDir); err != nil { - // Since we expect to be called from being deferred in a test it's - // better if we panic here so that the caller finds - panic(err) - } + os.RemoveAll(ffs.tempDir) } // Create a text file at filename with contents content func createWriteClose(filename, content string) error { // We'll create any parent dirs, with permissive permissions err := os.MkdirAll(path.Dir(filename), 0777) + if err != nil { + return err + } f, err := os.Create(filename) if err != nil { return err diff --git a/test/server/scumbag.go b/test/server/scumbag_test.go similarity index 86% rename from test/server/scumbag.go rename to test/server/scumbag_test.go index a4d902036a236dad47b71116873dee3de845c7aa..92eb0c03e8b8494c493f05464fb8c5753f136d37 100644 --- a/test/server/scumbag.go +++ b/test/server/scumbag_test.go @@ -19,13 +19,16 @@ import ( "os" "runtime" - rpc "github.com/hyperledger/burrow/rpc" + "github.com/hyperledger/burrow/rpc" "github.com/hyperledger/burrow/server" "github.com/gin-gonic/gin" + "github.com/hyperledger/burrow/logging/lifecycle" "github.com/tendermint/log15" ) +var logger, _ = lifecycle.NewStdErrLogger() + func init() { runtime.GOMAXPROCS(runtime.NumCPU()) log15.Root().SetHandler(log15.LvlFilterHandler( @@ -68,13 +71,13 @@ func (this *ScumSocketService) Process(data []byte, session *server.WSSession) { func NewScumsocketServer(maxConnections uint16) *server.WebSocketServer { sss := &ScumSocketService{} - return server.NewWebSocketServer(maxConnections, sss) + return server.NewWebSocketServer(maxConnections, sss, logger) } func NewServeScumbag() (*server.ServeProcess, error) { cfg := server.DefaultServerConfig() cfg.Bind.Port = uint16(31400) - return server.NewServeProcess(cfg, NewScumbagServer()) + return server.NewServeProcess(cfg, logger, NewScumbagServer()) } func NewServeScumSocket(wsServer *server.WebSocketServer) (*server.ServeProcess, @@ -82,5 +85,5 @@ func NewServeScumSocket(wsServer *server.WebSocketServer) (*server.ServeProcess, cfg := server.DefaultServerConfig() cfg.WebSocket.WebSocketEndpoint = "/scumsocket" cfg.Bind.Port = uint16(31401) - return server.NewServeProcess(cfg, wsServer) + return server.NewServeProcess(cfg, logger, wsServer) } diff --git a/test/server/ws_burst_test.go b/test/server/ws_burst_test.go index 820e173ab15842380365707b21a228828eb4b155..1721fa9a22ffad3b54b4bc0fdef8935df9448bdd 100644 --- a/test/server/ws_burst_test.go +++ b/test/server/ws_burst_test.go @@ -94,7 +94,7 @@ func runWs() error { runners := uint16(0) for runners < CONNS { select { - case _ = <-doneChan: + case <-doneChan: runners++ case err := <-errChan: return err diff --git a/tests/build_tool.sh b/tests/build_tool.sh deleted file mode 100755 index b91a56c2a0b5da97ba333c242a35a106b993a42b..0000000000000000000000000000000000000000 --- a/tests/build_tool.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -# ---------------------------------------------------------- -# PURPOSE - -# This is the build script for the Monax stack. It will -# build the tool into docker containers in a reliable and -# predictable manner. - -# ---------------------------------------------------------- -# REQUIREMENTS - -# docker installed locally - -# ---------------------------------------------------------- -# USAGE - -# build_tool.sh - -# ---------------------------------------------------------- - -TARGET=burrow -IMAGE=quay.io/monax/db - -set -e - -if [ "$JENKINS_URL" ] || [ "$CIRCLE_BRANCH" ] || [ "$CIRCLE_TAG" ] -then - REPO=`pwd` - CI="true" -else - REPO=$GOPATH/src/github.com/hyperledger/$TARGET -fi - -release_min=$(cat $REPO/version/version.go | tail -n 1 | cut -d \ -f 4 | tr -d '"') -release_maj=$(echo $release_min | cut -d . -f 1-2) - -# Build -mkdir -p $REPO/target/docker -docker build -t $IMAGE:build $REPO -docker run --rm --entrypoint cat $IMAGE:build /usr/local/bin/$TARGET > $REPO/target/docker/burrow.dockerartefact -docker run --rm --entrypoint cat $IMAGE:build /usr/local/bin/burrow-client > $REPO/target/docker/burrow-client.dockerartefact -docker build -t $IMAGE:$release_min -f Dockerfile.deploy $REPO - -# If provided, tag the image with the label provided -if [ "$1" ] -then - docker tag $IMAGE:$release_min $IMAGE:$1 - docker rmi $IMAGE:$release_min -fi - -# Cleanup -rm $REPO/target/docker/burrow.dockerartefact -rm $REPO/target/docker/burrow-client.dockerartefact - -# Remove build image so we don't push it when we push all tags -docker rmi -f $IMAGE:build diff --git a/txs/tx.go b/txs/tx.go index d7b1c50a84832f9b57a3a93f286088df18c260bf..44cd1b1056dadd93225cad00f904d110039d684e 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -360,9 +360,9 @@ type DupeoutTx struct { } func (tx *DupeoutTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - //PanicSanity("DupeoutTx has no sign bytes") + // PanicSanity("DupeoutTx has no sign bytes") // TODO - return + // return } func (tx *DupeoutTx) String() string { diff --git a/txs/tx_test.go b/txs/tx_test.go index 1ccef655be3c784226f0e0259073c964fd272f55..ca4b2ff0048278efdb71b55170bbd385d6e3af2e 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -212,6 +212,7 @@ func TestEncodeTxDecodeTx(t *testing.T) { t.Fatal(err) } txOut, err := DecodeTx(txBytes) + assert.NoError(t, err, "DecodeTx error") assert.Equal(t, tx, txOut) } diff --git a/util/hell/README.md b/util/hell/README.md deleted file mode 100644 index 715cb5522ca5a98143a45edf7e9ec49517b6778d..0000000000000000000000000000000000000000 --- a/util/hell/README.md +++ /dev/null @@ -1,11 +0,0 @@ -> Hell is other people's packages - -While we wait for working package management in go we need a way to make -maintaining the glide.lock by hand less painful. - -To interactively add a package run from the root: - -```bash -go run ./util/hell/cmd/hell/main.go get --interactive github.com/tendermint/tendermint -``` - diff --git a/util/hell/cmd/hell/main.go b/util/hell/cmd/hell/main.go deleted file mode 100644 index 5caa04ac071d3d4fb34a080da15a18dfbcbca2cb..0000000000000000000000000000000000000000 --- a/util/hell/cmd/hell/main.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/hyperledger/burrow/util/hell" - - "github.com/Masterminds/glide/action" - "github.com/Masterminds/glide/cache" - "github.com/Masterminds/glide/cfg" - "github.com/Masterminds/glide/msg" - "github.com/Masterminds/glide/path" - "github.com/Masterminds/glide/repo" - "github.com/Masterminds/glide/util" - "github.com/spf13/cobra" -) - -func main() { - hellCmd := &cobra.Command{ - Use: "hell", - Short: "Hell makes the most of it being warm", - Long: "", - Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, - } - - // Lock merge command - var baseGlideLockFile, depGlideLockFile string - lockMergeCmd := &cobra.Command{ - Use: "lock-merge", - Short: "Merge glide.lock files together", - Long: "This command merges two glide.lock files into a single one by copying all dependencies " + - "from a base glide.lock and an override glide.lock to an output glide.lock with dependencies " + - "from override taking precedence over those from base.", - Run: func(cmd *cobra.Command, args []string) { - baseLockFile, err := cfg.ReadLockFile(baseGlideLockFile) - if err != nil { - fmt.Printf("Could not read file: %s\n", err) - os.Exit(1) - } - overrideLockFile, err := cfg.ReadLockFile(depGlideLockFile) - if err != nil { - fmt.Printf("Could not read file: %s\n", err) - os.Exit(1) - } - mergedLockFile, err := hell.MergeGlideLockFiles(baseLockFile, overrideLockFile) - if err != nil { - fmt.Printf("Could not merge lock files: %s\n", err) - os.Exit(1) - } - mergedBytes, err := mergedLockFile.Marshal() - if err != nil { - fmt.Printf("Could not marshal lock file: %s\n", err) - os.Exit(1) - } - os.Stdout.Write(mergedBytes) - }, - } - lockMergeCmd.PersistentFlags().StringVarP(&baseGlideLockFile, "base", "b", "", "base lock file") - lockMergeCmd.PersistentFlags().StringVarP(&depGlideLockFile, "override", "o", "", "override lock file") - - // Lock update - interactive := false - getTransitiveCmd := &cobra.Command{ - Use: "get", - Short: "gets a remote dependency to this project along with its transtive dependencies.", - Long: "Gets a remote dependency and its transitive dependencies by adding the remote " + - "depednency to this project's glide.yaml and merging the remote dependency's " + - "glide.lock into this project's glide.lock", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - msg.Die("%s requires a single argument of the remote dependency\n", cmd.Name()) - } - rootPackage, _ := util.NormalizeName(args[0]) - // Add dependency to glide - installer := repo.NewInstaller() - action.Get(args, installer, false, true, false, !interactive, false) - // Now hunt down the repo cache - dep := action.EnsureConfig().Imports.Get(rootPackage) - - key, err := cache.Key(dep.Remote()) - if err != nil { - msg.Die("%s requires a single argument of the remote dependency\n", cmd.Name()) - } - cacheDir := filepath.Join(cache.Location(), "src", key) - repos, err := dep.GetRepo(cacheDir) - if err != nil { - msg.Die("Could not get repo: %s", err) - } - version, err := repos.Version() - if err != nil { - msg.Die("Could not get version: %s", err) - } - dep.Pin = version - lockPath := filepath.Join(".", path.LockFile) - baseLockFile, err := cfg.ReadLockFile(lockPath) - if err != nil { - msg.Die("Could not read base lock file: %s", err) - } - overrideLockFile := &cfg.Lockfile{} - if path.HasLock(cacheDir) { - msg.Info("Found dependency lock file so merging into project lock file") - overrideLockFile, err = cfg.ReadLockFile(filepath.Join(cacheDir, path.LockFile)) - if err != nil { - msg.Die("Could not read dependency lock file: %s", err) - } - } - // Add the package to glide lock too! - overrideLockFile.Imports = append(overrideLockFile.Imports, cfg.LockFromDependency(dep)) - - mergedLockFile, err := hell.MergeGlideLockFiles(baseLockFile, overrideLockFile) - if err != nil { - msg.Die("Could not merge lock files: %s\n", err) - } - err = mergedLockFile.WriteFile(lockPath) - if err != nil { - msg.Die("Could not write merged lock file: %s", err) - } - - action.Install(installer, false) - }, - } - - getTransitiveCmd.PersistentFlags().BoolVarP(&interactive, "interactive", "i", false, - "set dependency verion interactively") - - hellCmd.AddCommand(lockMergeCmd) - hellCmd.AddCommand(getTransitiveCmd) - lockMergeCmd.Execute() -} diff --git a/util/hell/merge.go b/util/hell/merge.go deleted file mode 100644 index 130a80065b9ba82b70a0cbe24ebac439dd79b974..0000000000000000000000000000000000000000 --- a/util/hell/merge.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hell - -import ( - "crypto/sha256" - "fmt" - "sort" - - "github.com/Masterminds/glide/cfg" -) - -// Merges two glide lock files together, letting dependencies from 'base' be overwritten -// by those from 'override'. Returns the resultant glide lock file bytes -func MergeGlideLockFiles(baseLockFile, overrideLockFile *cfg.Lockfile) (*cfg.Lockfile, error) { - imports := make(map[string]*cfg.Lock, len(baseLockFile.Imports)) - devImports := make(map[string]*cfg.Lock, len(baseLockFile.DevImports)) - // Copy the base dependencies into a map - for _, lock := range baseLockFile.Imports { - imports[lock.Name] = lock - } - for _, lock := range baseLockFile.DevImports { - devImports[lock.Name] = lock - } - // Override base dependencies and add any extra ones - for _, lock := range overrideLockFile.Imports { - imports[lock.Name] = mergeLocks(imports[lock.Name], lock) - } - for _, lock := range overrideLockFile.DevImports { - devImports[lock.Name] = mergeLocks(imports[lock.Name], lock) - } - - deps := make([]*cfg.Dependency, 0, len(imports)) - devDeps := make([]*cfg.Dependency, 0, len(devImports)) - - // Flatten to Dependencies - for _, lock := range imports { - deps = append(deps, pinnedDependencyFromLock(lock)) - } - - for _, lock := range devImports { - devDeps = append(devDeps, pinnedDependencyFromLock(lock)) - } - - hasher := sha256.New() - hasher.Write(([]byte)(baseLockFile.Hash)) - hasher.Write(([]byte)(overrideLockFile.Hash)) - - return cfg.NewLockfile(deps, devDeps, fmt.Sprintf("%x", hasher.Sum(nil))) -} - -func mergeLocks(baseLock, overrideLock *cfg.Lock) *cfg.Lock { - lock := overrideLock.Clone() - if baseLock == nil { - return lock - } - - // Merge and dedupe subpackages - subpackages := make([]string, 0, len(lock.Subpackages)+len(baseLock.Subpackages)) - for _, sp := range lock.Subpackages { - subpackages = append(subpackages, sp) - } - for _, sp := range baseLock.Subpackages { - subpackages = append(subpackages, sp) - } - - sort.Stable(sort.StringSlice(subpackages)) - - dedupeSubpackages := make([]string, 0, len(subpackages)) - - lastSp := "" - elided := 0 - for _, sp := range subpackages { - if lastSp == sp { - elided++ - } else { - dedupeSubpackages = append(dedupeSubpackages, sp) - lastSp = sp - } - } - lock.Subpackages = dedupeSubpackages[:len(dedupeSubpackages)-elided] - return lock -} - -func pinnedDependencyFromLock(lock *cfg.Lock) *cfg.Dependency { - dep := cfg.DependencyFromLock(lock) - dep.Pin = lock.Version - return dep -} diff --git a/util/hell/merge_test.go b/util/hell/merge_test.go deleted file mode 100644 index 5fffedb9c93d01cc24782a657a09282d408933a9..0000000000000000000000000000000000000000 --- a/util/hell/merge_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hell - -import ( - "strings" - "testing" - - "github.com/Masterminds/glide/cfg" - "github.com/stretchr/testify/assert" -) - -const baseLockYml = `imports: -- name: github.com/gogo/protobuf - version: 82d16f734d6d871204a3feb1a73cb220cc92574c -- name: github.com/tendermint/tendermint - version: aaea0c5d2e3ecfbf29f2608f9d43649ec7f07f50 - subpackages: - - node - - proxy - - types - - version - - consensus - - rpc/core/types - - blockchain - - mempool - - rpc/core - - state -` -const overrideLockYml = `imports: -- name: github.com/tendermint/tendermint - version: 764091dfbb035f1b28da4b067526e04c6a849966 - subpackages: - - benchmarks - - proxy - - types - - version -` -const expectedLockYml = `imports: -- name: github.com/gogo/protobuf - version: 82d16f734d6d871204a3feb1a73cb220cc92574c -- name: github.com/tendermint/tendermint - version: 764091dfbb035f1b28da4b067526e04c6a849966 - subpackages: - - benchmarks - - blockchain - - consensus - - mempool - - node - - proxy - - rpc/core - - rpc/core/types -testImports: [] -` - -func TestMergeGlideLockFiles(t *testing.T) { - baseLockFile, err := cfg.LockfileFromYaml(([]byte)(baseLockYml)) - assert.NoError(t, err, "Lockfile should parse") - - overrideLockFile, err := cfg.LockfileFromYaml(([]byte)(overrideLockYml)) - assert.NoError(t, err, "Lockfile should parse") - - mergedLockFile, err := MergeGlideLockFiles(baseLockFile, overrideLockFile) - assert.NoError(t, err, "Lockfiles should merge") - - mergedYmlBytes, err := mergedLockFile.Marshal() - assert.NoError(t, err, "Lockfile should marshal") - - ymlLines := strings.Split(string(mergedYmlBytes), "\n") - // Drop the updated and hash lines - actualYml := strings.Join(ymlLines[2:], "\n") - assert.Equal(t, expectedLockYml, actualYml) -} diff --git a/logging/adapters/logrus/logrus.go b/util/logging/cmd/main.go similarity index 50% rename from logging/adapters/logrus/logrus.go rename to util/logging/cmd/main.go index 84b6837188e6c6712ef05461227b658372ccd0bb..fc6d16dd616bc3f9b7bc8e99fdedf85eb0ae3d13 100644 --- a/logging/adapters/logrus/logrus.go +++ b/util/logging/cmd/main.go @@ -12,25 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -package adapters +package main import ( - "github.com/Sirupsen/logrus" - kitlog "github.com/go-kit/kit/log" -) - -type logrusLogger struct { - logger logrus.Logger -} + "fmt" -var _ kitlog.Logger = (*logrusLogger)(nil) + . "github.com/hyperledger/burrow/logging/config" +) -func NewLogrusLogger(logger logrus.Logger) *logrusLogger { - return &logrusLogger{ - logger: logger, +// Dump an example logging configuration +func main() { + loggingConfig := &LoggingConfig{ + RootSink: Sink(). + AddSinks( + // Log everything to Stderr + Sink().SetOutput(StderrOutput()), + Sink().SetTransform(FilterTransform(ExcludeWhenAllMatch, + "module", "p2p", + "captured_logging_source", "tendermint_log15")). + AddSinks( + Sink().SetOutput(SyslogOutput("Burrow-network")), + Sink().SetOutput(FileOutput("/var/log/burrow-network.log")), + ), + ), } -} - -func (ll *logrusLogger) Log(keyvals ...interface{}) error { - return nil + fmt.Println(loggingConfig.RootTOMLString()) } diff --git a/util/slice/slice.go b/util/slice/slice.go index 8c2b7f8914b86539c04051f7077830c82ec4b835..485510ff78e6da218bb3cd6abf8628f41a0e449f 100644 --- a/util/slice/slice.go +++ b/util/slice/slice.go @@ -14,6 +14,7 @@ package slice +// Convenience function func Slice(elements ...interface{}) []interface{} { return elements } diff --git a/util/snatives/templates/solidity_templates_test.go b/util/snatives/templates/solidity_templates_test.go index 6f77a6a08d70c312e6353e528d433f159fdf9b71..58fe37f7e461db87fbd9864263b312a5b6f5c300 100644 --- a/util/snatives/templates/solidity_templates_test.go +++ b/util/snatives/templates/solidity_templates_test.go @@ -16,9 +16,10 @@ package templates import ( "fmt" + "testing" + "github.com/hyperledger/burrow/manager/burrow-mint/evm" "github.com/stretchr/testify/assert" - "testing" ) func TestSNativeFuncTemplate(t *testing.T) { diff --git a/server/log.go b/util/version/cmd/main.go similarity index 81% rename from server/log.go rename to util/version/cmd/main.go index 274170d52f91770dddbf830038cc72a6003b5f89..c5711e205dc6e67ac7c472afe3922e0d6714c892 100644 --- a/server/log.go +++ b/util/version/cmd/main.go @@ -12,10 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package server +package main import ( - "github.com/tendermint/log15" + "fmt" + + "github.com/hyperledger/burrow/version" ) -var log = log15.New("module", "server") +// Print the Burrow version +func main() { + fmt.Println(version.VERSION) +} diff --git a/version/version.go b/version/version.go index 59e9eaa2cb28b2be821b8877105667bc93f36cad..b92218bb3ac749a50cbdf4601a4455c61f0955bb 100644 --- a/version/version.go +++ b/version/version.go @@ -30,43 +30,47 @@ const ( // Major version component of the current release versionMajor = 0 // Minor version component of the current release - versionMinor = 16 + versionMinor = 17 // Patch version component of the current release - versionPatch = 4 + versionPatch = 0 ) -var version *VersionIdentifier +var burrowVersion *VersionIdentifier func init() { - version = New(clientIdentifier, versionMajor, versionMinor, versionPatch) + burrowVersion = New(clientIdentifier, versionMajor, versionMinor, versionPatch) +} + +func GetBurrowVersion() *VersionIdentifier { + return burrowVersion } //------------------------------------------------------------------------------ // versioning globally for burrow and scoped for modules type VersionIdentifier struct { - clientIdentifier string - versionMajor uint8 - versionMinor uint8 - versionPatch uint8 + ClientIdentifier string + MajorVersion uint8 + MinorVersion uint8 + PatchVersion uint8 } func New(client string, major, minor, patch uint8) *VersionIdentifier { - v := new(VersionIdentifier) - v.clientIdentifier = client - v.versionMajor = major - v.versionMinor = minor - v.versionPatch = patch - return v + return &VersionIdentifier{ + ClientIdentifier: client, + MajorVersion: major, + MinorVersion: minor, + PatchVersion: patch, + } } // GetVersionString returns `client-major.minor.patch` for burrow // without a receiver, or for the version called on. // MakeVersionString builds the same version string with provided parameters. -func GetVersionString() string { return version.GetVersionString() } +func GetVersionString() string { return burrowVersion.GetVersionString() } func (v *VersionIdentifier) GetVersionString() string { - return fmt.Sprintf("%s-%d.%d.%d", v.clientIdentifier, v.versionMajor, - v.versionMinor, v.versionPatch) + return fmt.Sprintf("%s-%d.%d.%d", v.ClientIdentifier, v.MajorVersion, + v.MinorVersion, v.PatchVersion) } // note: the arguments are passed in as int (rather than uint8) @@ -82,10 +86,10 @@ func MakeVersionString(client string, major, minor, patch int) string { // without a receiver, or for the version called on. // MakeMinorVersionString builds the same version string with // provided parameters. -func GetMinorVersionString() string { return version.GetVersionString() } +func GetMinorVersionString() string { return burrowVersion.GetVersionString() } func (v *VersionIdentifier) GetMinorVersionString() string { - return fmt.Sprintf("%s-%d.%d", v.clientIdentifier, v.versionMajor, - v.versionMinor) + return fmt.Sprintf("%s-%d.%d", v.ClientIdentifier, v.MajorVersion, + v.MinorVersion) } // note: similar remark applies here on the use of `int` over `uint8` @@ -97,12 +101,13 @@ func MakeMinorVersionString(client string, major, minor, patch int) string { // GetVersion returns a tuple of client, major, minor, and patch as types, // either for burrow without a receiver or the called version structure. func GetVersion() (client string, major, minor, patch uint8) { - return version.GetVersion() + return burrowVersion.GetVersion() } + func (version *VersionIdentifier) GetVersion() ( client string, major, minor, patch uint8) { - return version.clientIdentifier, version.versionMajor, version.versionMinor, - version.versionPatch + return version.ClientIdentifier, version.MajorVersion, version.MinorVersion, + version.PatchVersion } //------------------------------------------------------------------------------ @@ -111,19 +116,17 @@ func (version *VersionIdentifier) GetVersion() ( // MatchesMinorVersion matches the client identifier, major and minor version // number of the reference version identifier to be equal with the receivers. func MatchesMinorVersion(referenceVersion *VersionIdentifier) bool { - return version.MatchesMinorVersion(referenceVersion) + return burrowVersion.MatchesMinorVersion(referenceVersion) } func (version *VersionIdentifier) MatchesMinorVersion( referenceVersion *VersionIdentifier) bool { referenceClient, referenceMajor, referenceMinor, _ := referenceVersion.GetVersion() - return version.clientIdentifier == referenceClient && - version.versionMajor == referenceMajor && - version.versionMinor == referenceMinor + return version.ClientIdentifier == referenceClient && + version.MajorVersion == referenceMajor && + version.MinorVersion == referenceMinor } //------------------------------------------------------------------------------ -// Version number for tests/build_tool.sh - -// IMPORTANT: burrow version must be on the last line of this file for -// the deployment script tests/build_tool.sh to pick up the right label. -const VERSION = "0.16.4" +// util/version/cmd prints this when run and is used to by build_tool.sh to obtain +// Burrow version +const VERSION = "0.17.0"