From cda7a31e4e0b1494652661f2fd8ce07a79629b75 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 25 Feb 2019 22:44:14 +0000 Subject: [PATCH 001/117] Initial commit --- Dockerfile | 1 + Gopkg.toml | 69 +++ Makefile | 57 +++ VERSION | 1 + circle.yml | 152 ++++++ cli/main.go | 5 + docs/contribution.md | 28 ++ docs/conventions.md | 17 + pkg/Readme.md | 12 + pkg/addrmgr/addrmgr.go | 285 +++++++++++ pkg/addrmgr/addrmgr_test.go | 167 +++++++ pkg/addrmgr/peers.json | 0 pkg/addrmgr/readme.md | 23 + pkg/blockchain/blockchain.go | 187 ++++++++ pkg/chainparams/asset.go | 7 + pkg/chainparams/config.go | 51 ++ pkg/connmgr/config.go | 25 + pkg/connmgr/connmgr.go | 272 +++++++++++ pkg/connmgr/connmgr_test.go | 106 +++++ pkg/connmgr/readme.md | 26 + pkg/connmgr/request.go | 13 + pkg/crypto/aes/aes256.go | 93 ++++ pkg/crypto/base58/base58.go | 78 +++ pkg/crypto/base58/base58_test.go | 32 ++ pkg/crypto/elliptic/Readme.md | 18 + pkg/crypto/elliptic/curves.go | 58 +++ pkg/crypto/elliptic/elliptic.go | 317 +++++++++++++ pkg/crypto/elliptic/elliptic_test.go | 231 +++++++++ pkg/crypto/hash/hash.go | 77 +++ pkg/crypto/hash/hash_test.go | 62 +++ pkg/crypto/privatekey/privatekey.go | 117 +++++ pkg/crypto/privatekey/privatekey_test.go | 48 ++ pkg/crypto/publickey/TestHelper/helper.go | 20 + .../publickey/TestHelper/helper_test.go | 34 ++ pkg/crypto/publickey/publickey.go | 160 +++++++ pkg/crypto/publickey/publickey_test.go | 81 ++++ pkg/crypto/rfc6979/LICENSE | 21 + pkg/crypto/rfc6979/dsa.go | 45 ++ pkg/crypto/rfc6979/dsa_test.go | 270 +++++++++++ pkg/crypto/rfc6979/ecdsa.go | 59 +++ pkg/crypto/rfc6979/ecdsa_test.go | 447 ++++++++++++++++++ pkg/crypto/rfc6979/example_test.go | 76 +++ pkg/crypto/rfc6979/rfc6979.go | 119 +++++ pkg/crypto/rfc6979/rfc6979_test.go | 28 ++ pkg/database/leveldb.go | 143 ++++++ pkg/database/leveldb_test.go | 80 ++++ pkg/database/table.go | 36 ++ pkg/mempool/config.go | 31 ++ pkg/mempool/mempool.go | 138 ++++++ pkg/mempool/mempool_test.go | 211 +++++++++ pkg/mempool/tx.go | 45 ++ pkg/peer/config.go | 48 ++ pkg/peer/peer.go | 433 +++++++++++++++++ pkg/peer/peer_test.go | 209 ++++++++ pkg/peer/peerhandshake.go | 131 +++++ pkg/peer/readme.md | 67 +++ pkg/peer/stall/stall.go | 174 +++++++ pkg/peer/stall/stall_test.go | 75 +++ pkg/peermanager/peermgr.go | 67 +++ pkg/pubsub/event.go | 14 + pkg/pubsub/pub.go | 20 + pkg/pubsub/sub.go | 6 + pkg/syncmanager/config.go | 11 + pkg/syncmanager/syncman.go | 152 ++++++ pkg/wire/Readme.md | 62 +++ pkg/wire/base.go | 44 ++ pkg/wire/command/command.go | 30 ++ pkg/wire/message.go | 148 ++++++ pkg/wire/message_test.go | 55 +++ pkg/wire/payload/block.go | 59 +++ pkg/wire/payload/block_test.go | 80 ++++ pkg/wire/payload/blockbase.go | 124 +++++ pkg/wire/payload/blockbase_test.go | 8 + pkg/wire/payload/maddr.go | 61 +++ pkg/wire/payload/maddr_test.go | 40 ++ pkg/wire/payload/mblock.go | 36 ++ pkg/wire/payload/mgetaddr.go | 29 ++ pkg/wire/payload/mgetaddr_test.go | 24 + pkg/wire/payload/mgetblocks.go | 22 + pkg/wire/payload/mgetblocks_test.go | 27 ++ pkg/wire/payload/mgetdata.go | 16 + pkg/wire/payload/mgetdata_test.go | 19 + pkg/wire/payload/mgetheaders.go | 59 +++ pkg/wire/payload/mgetheaders_test.go | 47 ++ pkg/wire/payload/mheaders.go | 78 +++ pkg/wire/payload/mheaders_test.go | 82 ++++ pkg/wire/payload/minventory.go | 110 +++++ pkg/wire/payload/minventory_test.go | 78 +++ pkg/wire/payload/mmempool.go | 29 ++ pkg/wire/payload/mtx.go | 34 ++ pkg/wire/payload/mverack.go | 29 ++ pkg/wire/payload/mverack_test.go | 17 + pkg/wire/payload/mversion.go | 97 ++++ pkg/wire/payload/mversion_test.go | 59 +++ pkg/wire/payload/net_addr.go | 54 +++ pkg/wire/payload/transaction/Attribute.go | 65 +++ pkg/wire/payload/transaction/Input.go | 28 ++ pkg/wire/payload/transaction/Output.go | 34 ++ pkg/wire/payload/transaction/Witness.go | 34 ++ pkg/wire/payload/transaction/assettype.go | 16 + pkg/wire/payload/transaction/attr_usage.go | 48 ++ pkg/wire/payload/transaction/base.go | 204 ++++++++ pkg/wire/payload/transaction/claim.go | 43 ++ pkg/wire/payload/transaction/claim_test.go | 38 ++ pkg/wire/payload/transaction/contract.go | 30 ++ pkg/wire/payload/transaction/contract_test.go | 45 ++ pkg/wire/payload/transaction/enrollment.go | 31 ++ .../payload/transaction/enrollment_test.go | 30 ++ pkg/wire/payload/transaction/invocation.go | 59 +++ .../payload/transaction/invocation_test.go | 44 ++ pkg/wire/payload/transaction/issue.go | 35 ++ pkg/wire/payload/transaction/issue_test.go | 8 + pkg/wire/payload/transaction/miner.go | 34 ++ pkg/wire/payload/transaction/miner_test.go | 37 ++ pkg/wire/payload/transaction/paramtype.go | 17 + pkg/wire/payload/transaction/publickey.go | 35 ++ pkg/wire/payload/transaction/publish.go | 88 ++++ pkg/wire/payload/transaction/publish_test.go | 33 ++ pkg/wire/payload/transaction/register.go | 56 +++ pkg/wire/payload/transaction/register_test.go | 54 +++ pkg/wire/payload/transaction/state.go | 40 ++ pkg/wire/payload/transaction/state_test.go | 47 ++ .../payload/transaction/statedescriptor.go | 52 ++ pkg/wire/payload/transaction/types/types.go | 59 +++ pkg/wire/payload/transaction/util.go | 61 +++ .../payload/transaction/version/version.go | 19 + pkg/wire/protocol/protocol.go | 27 ++ pkg/wire/util/Checksum/checksum.go | 29 ++ pkg/wire/util/address/address.go | 18 + pkg/wire/util/binaryReader.go | 59 +++ pkg/wire/util/binaryWriter.go | 68 +++ pkg/wire/util/crypto/base58/base58.go | 123 +++++ pkg/wire/util/crypto/base58/base58_test.go | 32 ++ pkg/wire/util/crypto/hash/hash.go | 77 +++ pkg/wire/util/crypto/hash/hash_test.go | 62 +++ pkg/wire/util/fixed8/fixed8.go | 55 +++ pkg/wire/util/fixed8/fixed8_test.go | 85 ++++ pkg/wire/util/io/io.go | 16 + pkg/wire/util/ip/ip.go | 19 + pkg/wire/util/slice/slice.go | 15 + pkg/wire/util/slice/slice_test.go | 33 ++ pkg/wire/util/uint160.go | 86 ++++ pkg/wire/util/uint160_test.go | 50 ++ pkg/wire/util/uint256.go | 70 +++ pkg/wire/util/uint256_test.go | 50 ++ pkg/wire/util/util.go | 47 ++ server.go | 178 +++++++ 147 files changed, 10366 insertions(+) create mode 100644 Dockerfile create mode 100644 Gopkg.toml create mode 100644 Makefile create mode 100644 VERSION create mode 100644 circle.yml create mode 100644 cli/main.go create mode 100644 docs/contribution.md create mode 100644 docs/conventions.md create mode 100644 pkg/Readme.md create mode 100644 pkg/addrmgr/addrmgr.go create mode 100644 pkg/addrmgr/addrmgr_test.go create mode 100644 pkg/addrmgr/peers.json create mode 100644 pkg/addrmgr/readme.md create mode 100644 pkg/blockchain/blockchain.go create mode 100644 pkg/chainparams/asset.go create mode 100644 pkg/chainparams/config.go create mode 100644 pkg/connmgr/config.go create mode 100644 pkg/connmgr/connmgr.go create mode 100644 pkg/connmgr/connmgr_test.go create mode 100644 pkg/connmgr/readme.md create mode 100644 pkg/connmgr/request.go create mode 100755 pkg/crypto/aes/aes256.go create mode 100755 pkg/crypto/base58/base58.go create mode 100755 pkg/crypto/base58/base58_test.go create mode 100755 pkg/crypto/elliptic/Readme.md create mode 100755 pkg/crypto/elliptic/curves.go create mode 100755 pkg/crypto/elliptic/elliptic.go create mode 100755 pkg/crypto/elliptic/elliptic_test.go create mode 100755 pkg/crypto/hash/hash.go create mode 100755 pkg/crypto/hash/hash_test.go create mode 100755 pkg/crypto/privatekey/privatekey.go create mode 100755 pkg/crypto/privatekey/privatekey_test.go create mode 100755 pkg/crypto/publickey/TestHelper/helper.go create mode 100755 pkg/crypto/publickey/TestHelper/helper_test.go create mode 100755 pkg/crypto/publickey/publickey.go create mode 100755 pkg/crypto/publickey/publickey_test.go create mode 100755 pkg/crypto/rfc6979/LICENSE create mode 100755 pkg/crypto/rfc6979/dsa.go create mode 100755 pkg/crypto/rfc6979/dsa_test.go create mode 100755 pkg/crypto/rfc6979/ecdsa.go create mode 100755 pkg/crypto/rfc6979/ecdsa_test.go create mode 100755 pkg/crypto/rfc6979/example_test.go create mode 100755 pkg/crypto/rfc6979/rfc6979.go create mode 100755 pkg/crypto/rfc6979/rfc6979_test.go create mode 100644 pkg/database/leveldb.go create mode 100644 pkg/database/leveldb_test.go create mode 100644 pkg/database/table.go create mode 100644 pkg/mempool/config.go create mode 100644 pkg/mempool/mempool.go create mode 100644 pkg/mempool/mempool_test.go create mode 100644 pkg/mempool/tx.go create mode 100644 pkg/peer/config.go create mode 100644 pkg/peer/peer.go create mode 100644 pkg/peer/peer_test.go create mode 100644 pkg/peer/peerhandshake.go create mode 100644 pkg/peer/readme.md create mode 100644 pkg/peer/stall/stall.go create mode 100644 pkg/peer/stall/stall_test.go create mode 100644 pkg/peermanager/peermgr.go create mode 100644 pkg/pubsub/event.go create mode 100644 pkg/pubsub/pub.go create mode 100644 pkg/pubsub/sub.go create mode 100644 pkg/syncmanager/config.go create mode 100644 pkg/syncmanager/syncman.go create mode 100644 pkg/wire/Readme.md create mode 100644 pkg/wire/base.go create mode 100644 pkg/wire/command/command.go create mode 100644 pkg/wire/message.go create mode 100644 pkg/wire/message_test.go create mode 100644 pkg/wire/payload/block.go create mode 100644 pkg/wire/payload/block_test.go create mode 100644 pkg/wire/payload/blockbase.go create mode 100644 pkg/wire/payload/blockbase_test.go create mode 100644 pkg/wire/payload/maddr.go create mode 100644 pkg/wire/payload/maddr_test.go create mode 100644 pkg/wire/payload/mblock.go create mode 100644 pkg/wire/payload/mgetaddr.go create mode 100644 pkg/wire/payload/mgetaddr_test.go create mode 100644 pkg/wire/payload/mgetblocks.go create mode 100644 pkg/wire/payload/mgetblocks_test.go create mode 100644 pkg/wire/payload/mgetdata.go create mode 100644 pkg/wire/payload/mgetdata_test.go create mode 100644 pkg/wire/payload/mgetheaders.go create mode 100644 pkg/wire/payload/mgetheaders_test.go create mode 100644 pkg/wire/payload/mheaders.go create mode 100644 pkg/wire/payload/mheaders_test.go create mode 100644 pkg/wire/payload/minventory.go create mode 100644 pkg/wire/payload/minventory_test.go create mode 100644 pkg/wire/payload/mmempool.go create mode 100644 pkg/wire/payload/mtx.go create mode 100644 pkg/wire/payload/mverack.go create mode 100644 pkg/wire/payload/mverack_test.go create mode 100644 pkg/wire/payload/mversion.go create mode 100644 pkg/wire/payload/mversion_test.go create mode 100644 pkg/wire/payload/net_addr.go create mode 100644 pkg/wire/payload/transaction/Attribute.go create mode 100644 pkg/wire/payload/transaction/Input.go create mode 100644 pkg/wire/payload/transaction/Output.go create mode 100644 pkg/wire/payload/transaction/Witness.go create mode 100644 pkg/wire/payload/transaction/assettype.go create mode 100644 pkg/wire/payload/transaction/attr_usage.go create mode 100644 pkg/wire/payload/transaction/base.go create mode 100644 pkg/wire/payload/transaction/claim.go create mode 100644 pkg/wire/payload/transaction/claim_test.go create mode 100644 pkg/wire/payload/transaction/contract.go create mode 100644 pkg/wire/payload/transaction/contract_test.go create mode 100644 pkg/wire/payload/transaction/enrollment.go create mode 100644 pkg/wire/payload/transaction/enrollment_test.go create mode 100644 pkg/wire/payload/transaction/invocation.go create mode 100644 pkg/wire/payload/transaction/invocation_test.go create mode 100644 pkg/wire/payload/transaction/issue.go create mode 100644 pkg/wire/payload/transaction/issue_test.go create mode 100644 pkg/wire/payload/transaction/miner.go create mode 100644 pkg/wire/payload/transaction/miner_test.go create mode 100644 pkg/wire/payload/transaction/paramtype.go create mode 100644 pkg/wire/payload/transaction/publickey.go create mode 100644 pkg/wire/payload/transaction/publish.go create mode 100644 pkg/wire/payload/transaction/publish_test.go create mode 100644 pkg/wire/payload/transaction/register.go create mode 100644 pkg/wire/payload/transaction/register_test.go create mode 100644 pkg/wire/payload/transaction/state.go create mode 100644 pkg/wire/payload/transaction/state_test.go create mode 100644 pkg/wire/payload/transaction/statedescriptor.go create mode 100644 pkg/wire/payload/transaction/types/types.go create mode 100644 pkg/wire/payload/transaction/util.go create mode 100644 pkg/wire/payload/transaction/version/version.go create mode 100644 pkg/wire/protocol/protocol.go create mode 100644 pkg/wire/util/Checksum/checksum.go create mode 100644 pkg/wire/util/address/address.go create mode 100644 pkg/wire/util/binaryReader.go create mode 100644 pkg/wire/util/binaryWriter.go create mode 100644 pkg/wire/util/crypto/base58/base58.go create mode 100644 pkg/wire/util/crypto/base58/base58_test.go create mode 100644 pkg/wire/util/crypto/hash/hash.go create mode 100644 pkg/wire/util/crypto/hash/hash_test.go create mode 100644 pkg/wire/util/fixed8/fixed8.go create mode 100644 pkg/wire/util/fixed8/fixed8_test.go create mode 100644 pkg/wire/util/io/io.go create mode 100644 pkg/wire/util/ip/ip.go create mode 100644 pkg/wire/util/slice/slice.go create mode 100644 pkg/wire/util/slice/slice_test.go create mode 100644 pkg/wire/util/uint160.go create mode 100644 pkg/wire/util/uint160_test.go create mode 100644 pkg/wire/util/uint256.go create mode 100644 pkg/wire/util/uint256_test.go create mode 100644 pkg/wire/util/util.go create mode 100644 server.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..6382bd874 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +FROM vidsyhq/go-base:latest diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 000000000..b91339241 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,69 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + +[[constraint]] + branch = "master" + name = "github.com/syndtr/goleveldb" + +[[constraint]] + name = "github.com/urfave/cli" + version = "1.20.0" + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" + +[[constraint]] + branch = "master" + name = "github.com/anthdm/rfc6979" + +[[constraint]] + name = "golang.org/x/text" + version = "0.3.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.1" + +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "1.0.5" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" + +[[constraint]] + name = "github.com/go-yaml/yaml" + version = "2.1.1" + +[[constraint]] + name = "github.com/go-redis/redis" + version = "6.10.2" \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..a2b7338e9 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +BRANCH = "master" +BUILD_TIME = "$(shell date -u +\"%Y-%m-%dT%H:%M:%SZ\")" +VERSION = $(shell cat ./VERSION) +REPONAME = "neo-go" +NETMODE ?= "privnet" + +build: + @echo "=> Building darwin binary" + @go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go + +build-image: + docker build -t cityofzion/neo-go --build-arg VERSION=${VERSION} . + +build-linux: + @echo "=> Building linux binary" + @GOOS=linux go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go + +check-version: + git fetch && (! git rev-list ${VERSION}) + +clean-cluster: + @echo "=> Removing all containers and chain storage" + @rm -rf chains/privnet-docker-one chains/privnet-docker-two chains/privnet-docker-three chains/privnet-docker-four + @docker-compose stop + @docker-compose rm -f + +deps: + @dep ensure + +push-tag: + git checkout ${BRANCH} + git pull origin ${BRANCH} + git tag ${VERSION} + git push origin ${VERSION} + +push-to-registry: + @docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS} + @docker tag CityOfZion/${REPONAME}:latest CityOfZion/${REPONAME}:${CIRCLE_TAG} + @docker push CityOfZion/${REPONAME}:${CIRCLE_TAG} + @docker push CityOfZion/${REPONAME} + +run: build + ./bin/neo-go node -config-path ./config -${NETMODE} + +run-cluster: build-linux + @echo "=> Starting docker-compose cluster" + @echo "=> Building container image" + @docker-compose build + @docker-compose up -d + @echo "=> Tailing logs, exiting this prompt will not stop the cluster" + @docker-compose logs -f + +test: + @go test ./... -cover + +vet: + @go vet ./... \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..25e8d483f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.44.8 \ No newline at end of file diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..47255ab3e --- /dev/null +++ b/circle.yml @@ -0,0 +1,152 @@ +version: 2 +jobs: + install_deps: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + environment: + BUILD: false + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - run: /scripts/build.sh + - save_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + paths: + - vendor + - /go/pkg + build_image: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: alpine + steps: + - run: apk update && apk add git make curl tar + - checkout + - restore_cache: + keys: + - dependency-cache-{{ .Revision }} + - restore_cache: + keys: + - dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - setup_remote_docker + - run: + name: Install Docker client + command: | + set -x + VER="17.03.0-ce" + curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz + tar -xz -C /tmp -f /tmp/docker-$VER.tgz + mv /tmp/docker/* /usr/bin + - run: make build-image + test: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - run: make test + vet: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - run: make vet + check_version: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - run: make check-version + build_cli: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: vidsyhq/go-builder:latest + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - run: make build + - save_cache: + key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + paths: + - bin/neo-go + deploy: + working_directory: /go/src/github.com/CityOfZion/neo-go + docker: + - image: alpine + steps: + - run: apk update && apk add git make curl tar + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - restore_cache: + key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} + - setup_remote_docker + - run: + name: Install Docker client + command: | + set -x + VER="17.03.0-ce" + curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz + tar -xz -C /tmp -f /tmp/docker-$VER.tgz + mv /tmp/docker/* /usr/bin + - run: make build-image + - deploy: + name: deploy + command: make push-to-registry +workflows: + version: 2 + workflow: + jobs: + - install_deps: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - test: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - vet: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - check_version: + filters: + branches: + ignore: master + - build_cli: + requires: + - install_deps + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - build_image: + requires: + - install_deps + - build_cli + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - deploy: + requires: + - build_image + - test + - vet + - check_version + filters: + tags: + only: + - /[0-9]+\.[0-9]+\.[0-9]+/ + branches: + ignore: /.*/ \ No newline at end of file diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 000000000..790580777 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/docs/contribution.md b/docs/contribution.md new file mode 100644 index 000000000..890e615f3 --- /dev/null +++ b/docs/contribution.md @@ -0,0 +1,28 @@ +# Contributions + + +This document will state guidelines for submitting code. + +## Pre-requisites + +In order to contribute to Golang, it is essential that you know Golang or language similar to Golang. It is also required that you are familiar with NEO infrastructure. + +Reading: + + http://docs.neo.org/en-us/network/network-protocol.html + + https://tour.golang.org/welcome/1 + + https://golang.org/doc/effective_go.html#introduction + +## 1. Open an Issue Prior + +Please open an issue prior to working on the feature, stating the feature you would like to work on. + +## 2. Unit test + +Unit tests are important, when submitting a PR please ensure that you have at least 70% test coverage. + +## 3. Avoid CGo + +Due to the complications that come with using CGo, if using it, please provide justification before using it. If using CGo, can provide significant speed boosts and or a large part of the code can be processed in CGo, then we should be able to come to consensus on using it. Generally, it is better to write everything in Golang, so that the code can be maintained by other Gophers. \ No newline at end of file diff --git a/docs/conventions.md b/docs/conventions.md new file mode 100644 index 000000000..e5a6f5eab --- /dev/null +++ b/docs/conventions.md @@ -0,0 +1,17 @@ +# Conventions + +This document will list conventions that this repo should follow. These are guidelines and if you believe that one should not be followed, then please state why in your PR. If you believe that a piece of code does not follow one of the conventions listed, then please open an issue before making any changes. + +When submitting a new convention, please open an issue for discussion, if possible please highlight parts in the code where this convention could help the code readiblity or simplicity. + + +## Avoid Named return paramters + + +func example(test int) (num int) { + a = test + 1 + num = a * test + return +} + +In the above function we have used a named return paramter, which allows you to include a simple return statement without the variables you are returning. This practice can cause confusion when functions become large or the logic becomes complex, so these should be avoided. \ No newline at end of file diff --git a/pkg/Readme.md b/pkg/Readme.md new file mode 100644 index 000000000..803fb5ffe --- /dev/null +++ b/pkg/Readme.md @@ -0,0 +1,12 @@ +# ReadMe + +Currently this package is in Development. + + +## References + +btcd https://github.com/btcsuite/btcd + +geth https://github.com/ethereum/go-ethereum + +aeternity https://github.com/aeternity/elixir-node \ No newline at end of file diff --git a/pkg/addrmgr/addrmgr.go b/pkg/addrmgr/addrmgr.go new file mode 100644 index 000000000..639fd6dff --- /dev/null +++ b/pkg/addrmgr/addrmgr.go @@ -0,0 +1,285 @@ +package addrmgr + +import ( + "errors" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +const ( + maxTries = 5 // Try to connect five times, if we cannot then we say it is bad + // We will keep bad addresses, so that we do not attempt to connect to them in the future. + // nodes at the moment seem to send a large percentage of bad nodes. + + maxFailures = 12 // This will be incremented when we connect to a node and for whatever reason, they keep disconnecting. + // The number is high because there could be a period of time, where a node is behaving unrepsonsively and + // we do not want to immmediately put it inside of the bad bucket. + + // This is the maximum amount of addresses that the addrmgr will hold + maxAllowedAddrs = 2000 +) + +type addrStats struct { + tries uint8 + failures uint8 + permanent bool // permanent = inbound, temp = outbound + lastTried time.Time + lastSuccess time.Time +} + +type Addrmgr struct { + addrmtx sync.RWMutex + goodAddrs map[*payload.Net_addr]addrStats + newAddrs map[*payload.Net_addr]struct{} + badAddrs map[*payload.Net_addr]addrStats + knownList map[string]*payload.Net_addr // this contains all of the addresses in badAddrs, newAddrs and goodAddrs +} + +func New() *Addrmgr { + + return &Addrmgr{ + sync.RWMutex{}, + make(map[*payload.Net_addr]addrStats, 100), + make(map[*payload.Net_addr]struct{}, 100), + make(map[*payload.Net_addr]addrStats, 100), + make(map[string]*payload.Net_addr, 100), + } +} + +//AddAddrs will add new add new addresses into the newaddr list +// This is safe for concurrent access. +func (a *Addrmgr) AddAddrs(newAddrs []*payload.Net_addr) { + + newAddrs = removeDuplicates(newAddrs) + + var nas []*payload.Net_addr + for _, addr := range newAddrs { + a.addrmtx.Lock() + + if _, ok := a.knownList[addr.IPPort()]; !ok { // filter unique addresses + nas = append(nas, addr) + } + a.addrmtx.Unlock() + } + + for _, addr := range nas { + a.addrmtx.Lock() + a.newAddrs[addr] = struct{}{} + a.knownList[addr.IPPort()] = addr + a.addrmtx.Unlock() + } +} + +// Good returns the good addresses. +// A good address is: +// - Known +// - has been successfully connected to in the last week +// - Note: that while users are launching full nodes from their laptops, the one week marker will need to be modified. +func (a *Addrmgr) Good() []payload.Net_addr { + + var goodAddrs []payload.Net_addr + + var oneWeekAgo = time.Now().Add(((time.Hour * 24) * 7) * -1) + + a.addrmtx.RLock() + // TODO: sort addresses, permanent ones go first + for addr, stat := range a.goodAddrs { + if stat.lastTried.Before(oneWeekAgo) { + continue + } + goodAddrs = append(goodAddrs, *addr) + + } + a.addrmtx.RUnlock() + + return goodAddrs +} + +// Unconnected Addrs are addresses in the newAddr List +func (a *Addrmgr) Unconnected() []payload.Net_addr { + + var unconnAddrs []payload.Net_addr + + a.addrmtx.RLock() + for addr := range a.newAddrs { + unconnAddrs = append(unconnAddrs, *addr) + } + a.addrmtx.RUnlock() + return unconnAddrs +} + +// Bad Addrs are addresses in the badAddr List +// They are put there if we have tried to connect +// to them in the past and it failed. +func (a *Addrmgr) Bad() []payload.Net_addr { + + var badAddrs []payload.Net_addr + + a.addrmtx.RLock() + for addr := range a.badAddrs { + badAddrs = append(badAddrs, *addr) + } + a.addrmtx.RUnlock() + return badAddrs +} + +// FetchMoreAddresses will return true if the numOfKnownAddrs are less than 100 +// This number is kept low because at the moment, there are not a lot of Good Addresses +// Tests have shown that at most there are < 100 +func (a *Addrmgr) FetchMoreAddresses() bool { + + var numOfKnownAddrs int + + a.addrmtx.RLock() + numOfKnownAddrs = len(a.knownList) + a.addrmtx.RUnlock() + + return numOfKnownAddrs < maxAllowedAddrs +} + +// ConnectionComplete will be called by the server when we have done the handshake +// and Not when we have successfully Connected with net.Conn +// It is to tell the AddrMgr that we have connected to a peer +// This peer should already be known to the AddrMgr because +// We send it to the Connmgr +func (a *Addrmgr) ConnectionComplete(addressport string, inbound bool) { + a.addrmtx.Lock() + defer a.addrmtx.Unlock() + + // if addrmgr does not know this, then we just return + if _, ok := a.knownList[addressport]; !ok { + fmt.Println("Connected to an unknown address:port ", addressport) + return + } + + na := a.knownList[addressport] + + // move it from newAddrs List to GoodAddr List + stats := a.goodAddrs[na] + stats.lastSuccess = time.Now() + stats.lastTried = time.Now() + stats.permanent = inbound + stats.tries++ + a.goodAddrs[na] = stats + + // remove it from new and bad, if it was there + delete(a.newAddrs, na) + delete(a.badAddrs, na) + + // Unfortunately, Will have a lot of bad nodes because of people connecting to nodes + // from their laptop. TODO Sort function in good will mitigate. + +} + +// Failed will be called by ConnMgr +// It is used to tell the AddrMgr that they had tried connecting an address an have failed +// This is concurrent safe +func (a *Addrmgr) Failed(addressport string) { + a.addrmtx.Lock() + defer a.addrmtx.Unlock() + + // if addrmgr does not know this, then we just return + if _, ok := a.knownList[addressport]; !ok { + fmt.Println("Connected to an unknown address:port ", addressport) + return + } + + na := a.knownList[addressport] + + // HMM: logic here could be simpler if we make it one list instead + + if stats, ok := a.badAddrs[na]; ok { + stats.lastTried = time.Now() + stats.failures++ + stats.tries++ + if float32(stats.failures)/float32(stats.tries) > 0.8 && stats.tries > 5 { + delete(a.badAddrs, na) + return + } + a.badAddrs[na] = stats + return + } + + if stats, ok := a.goodAddrs[na]; ok { + fmt.Println("We have a good addr", na.IPPort()) + stats.lastTried = time.Now() + stats.failures++ + stats.tries++ + if float32(stats.failures)/float32(stats.tries) > 0.5 && stats.tries > 10 { + delete(a.goodAddrs, na) + a.badAddrs[na] = stats + return + } + a.goodAddrs[na] = stats + return + } + + if _, ok := a.newAddrs[na]; ok { + delete(a.newAddrs, na) + a.badAddrs[na] = addrStats{} + } + +} + +//OnAddr is the responder for the Config file +// when a OnAddr is received by a peer +func (a *Addrmgr) OnAddr(p *peer.Peer, msg *payload.AddrMessage) { + a.AddAddrs(msg.AddrList) +} + +// OnGetAddr Is called when a peer sends a request for the addressList +// We will give them the best addresses we have from good. +func (a *Addrmgr) OnGetAddr(p *peer.Peer, msg *payload.GetAddrMessage) { + a.addrmtx.RLock() + defer a.addrmtx.RUnlock() + // Push most recent peers to peer + addrMsg, err := payload.NewAddrMessage() + if err != nil { + return + } + for _, add := range a.Good() { + addrMsg.AddNetAddr(&add) + } + + p.Write(addrMsg) +} + +// NewAddr will return an address for the external caller to +// connect to. In our case, it will be the connection manager. +func (a *Addrmgr) NewAddr() (string, error) { + // For now it just returns a random value from unconnected + // TODO: When an address is tried, the address manager is notified. + // When asked for a new address, this should be taken into account + // when choosing a new one, also the number of retries. + unconnected := a.Unconnected() + if len(unconnected) == 0 { + return "", errors.New("No Addresses to give") + } + rand := rand.Intn(len(unconnected)) + return unconnected[rand].IPPort(), nil +} + +// https://www.dotnetperls.com/duplicates-go +func removeDuplicates(elements []*payload.Net_addr) []*payload.Net_addr { + + encountered := map[string]bool{} + result := []*payload.Net_addr{} + + for _, element := range elements { + if encountered[element.IPPort()] == true { + // Do not add duplicate. + } else { + // Record this element as an encountered element. + encountered[element.IPPort()] = true + // Append to result slice. + result = append(result, element) + } + } + // Return the new slice. + return result +} diff --git a/pkg/addrmgr/addrmgr_test.go b/pkg/addrmgr/addrmgr_test.go new file mode 100644 index 000000000..5a35ea26b --- /dev/null +++ b/pkg/addrmgr/addrmgr_test.go @@ -0,0 +1,167 @@ +package addrmgr_test + +import ( + "crypto/rand" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + + "github.com/CityOfZion/neo-go/pkg/addrmgr" + "github.com/stretchr/testify/assert" +) + +func TestNewAddrs(t *testing.T) { + + addrmgr := addrmgr.New() + assert.NotEqual(t, nil, addrmgr) +} +func TestAddAddrs(t *testing.T) { + + addrmgr := addrmgr.New() + + ip := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) + + ip = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // same + addr2, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) + + ip = [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr3, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) + + addrs := []*payload.Net_addr{addr, addr2, addr, addr3} + + addrmgr.AddAddrs(addrs) + + assert.Equal(t, 2, len(addrmgr.Unconnected())) + assert.Equal(t, 0, len(addrmgr.Good())) + assert.Equal(t, 0, len(addrmgr.Bad())) +} + +func TestFetchMoreAddress(t *testing.T) { + + addrmgr := addrmgr.New() + + addrs := []*payload.Net_addr{} + + ip := make([]byte, 16) + + for i := 0; i <= 2000; i++ { // Add more than maxAllowedAddrs + rand.Read(ip) + + var nip [16]byte + copy(nip[:], ip[:16]) + + addr, _ := payload.NewNetAddr(0, nip, 1033, protocol.NodePeerService) + addrs = append(addrs, addr) + } + + addrmgr.AddAddrs(addrs) + + assert.Equal(t, false, addrmgr.FetchMoreAddresses()) + +} +func TestConnComplete(t *testing.T) { + + addrmgr := addrmgr.New() + + ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) + + ip2 := [16]byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr2, _ := payload.NewNetAddr(0, ip2, 1033, protocol.NodePeerService) + + ip3 := [16]byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr3, _ := payload.NewNetAddr(0, ip3, 1033, protocol.NodePeerService) + + addrs := []*payload.Net_addr{addr, addr2, addr3} + + addrmgr.AddAddrs(addrs) + + assert.Equal(t, len(addrs), len(addrmgr.Unconnected())) + + // a successful connection + addrmgr.ConnectionComplete(addr.IPPort(), true) + addrmgr.ConnectionComplete(addr.IPPort(), true) // should have no change + + assert.Equal(t, len(addrs)-1, len(addrmgr.Unconnected())) + assert.Equal(t, 1, len(addrmgr.Good())) + + // another successful connection + addrmgr.ConnectionComplete(addr2.IPPort(), true) + + assert.Equal(t, len(addrs)-2, len(addrmgr.Unconnected())) + assert.Equal(t, 2, len(addrmgr.Good())) + +} +func TestAttempted(t *testing.T) { + + addrmgr := addrmgr.New() + + ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) + + addrs := []*payload.Net_addr{addr} + + addrmgr.AddAddrs(addrs) + + addrmgr.Failed(addr.IPPort()) + + assert.Equal(t, 1, len(addrmgr.Bad())) // newAddrs was attmepted and failed. Move to Bad + +} +func TestAttemptedMoveFromGoodToBad(t *testing.T) { + + addrmgr := addrmgr.New() + + ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr, _ := payload.NewNetAddr(0, ip, 1043, protocol.NodePeerService) + + addrs := []*payload.Net_addr{addr} + + addrmgr.AddAddrs(addrs) + + addrmgr.ConnectionComplete(addr.IPPort(), true) + addrmgr.ConnectionComplete(addr.IPPort(), true) + addrmgr.ConnectionComplete(addr.IPPort(), true) + + assert.Equal(t, 1, len(addrmgr.Good())) + + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + addrmgr.Failed(addr.IPPort()) + // over threshhold, and will be classed as a badAddr L251 + + assert.Equal(t, 0, len(addrmgr.Good())) + assert.Equal(t, 1, len(addrmgr.Bad())) +} + +func TestGetAddress(t *testing.T) { + + addrmgr := addrmgr.New() + + ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr, _ := payload.NewNetAddr(0, ip, 10333, protocol.NodePeerService) + ip2 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr2, _ := payload.NewNetAddr(0, ip2, 10334, protocol.NodePeerService) + ip3 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different + addr3, _ := payload.NewNetAddr(0, ip3, 10335, protocol.NodePeerService) + + addrs := []*payload.Net_addr{addr, addr2, addr3} + + addrmgr.AddAddrs(addrs) + + fetchAddr, err := addrmgr.NewAddr() + assert.Equal(t, nil, err) + + ipports := []string{addr.IPPort(), addr2.IPPort(), addr3.IPPort()} + + assert.Contains(t, ipports, fetchAddr) + +} diff --git a/pkg/addrmgr/peers.json b/pkg/addrmgr/peers.json new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/addrmgr/readme.md b/pkg/addrmgr/readme.md new file mode 100644 index 000000000..1121e7972 --- /dev/null +++ b/pkg/addrmgr/readme.md @@ -0,0 +1,23 @@ +# Package - Address Manager + +This package can be used as a standalone to manage addresses on the NEO network. Although you can use it for other chains, the config parameters have been modified for the state of NEO as of now. + +## Responsibility + +To manage the data that the node knows about addresses; data on good addresses, retry rates, failures, and lastSuccessful connection will be managed by the address manager. Also, If a service wants to fetch good address then it will be asked for from the address manager. + + +## Features + +- On GetAddr it will give a list of good addresses to connect to + +- On Addr it will receive addresses and remove any duplicates. + +- General Management of Addresses + +- Periodically saves the peers and metadata about peer into a .json file for retrieval (Not implemented yet) + + +## Note + +The Address manager will not deal with making connections to nodes. Please check the tests for the use cases for this package. diff --git a/pkg/blockchain/blockchain.go b/pkg/blockchain/blockchain.go new file mode 100644 index 000000000..b63641675 --- /dev/null +++ b/pkg/blockchain/blockchain.go @@ -0,0 +1,187 @@ +package blockchain + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/chainparams" + + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +var ( + ErrBlockValidation = errors.New("Block failed sanity check") + ErrBlockVerification = errors.New("Block failed to be consistent with the current blockchain") +) + +// Blockchain holds the state of the chain +type Chain struct { + db *database.LDB + net protocol.Magic +} + +func New(db *database.LDB, net protocol.Magic) *Chain { + + marker := []byte("HasBeenInitialisedAlready") + + _, err := db.Get(marker) + + if err != nil { + // This is a new db + fmt.Println("New Database initialisation") + db.Put(marker, []byte{}) + + // We add the genesis block into the db + // along with the indexes for it + if net == protocol.MainNet { + + genesisBlock, err := hex.DecodeString(chainparams.GenesisBlock) + if err != nil { + fmt.Println("Could not add genesis header into db") + db.Delete(marker) + return nil + } + r := bytes.NewReader(genesisBlock) + b := payload.Block{} + err = b.Decode(r) + + if err != nil { + fmt.Println("could not Decode genesis block") + db.Delete(marker) + return nil + } + err = db.AddHeader(&b.BlockBase) + if err != nil { + fmt.Println("Could not add genesis header") + db.Delete(marker) + return nil + } + err = db.AddTransactions(b.Hash, b.Txs) + if err != nil { + fmt.Println("Could not add Genesis Transactions") + db.Delete(marker) + return nil + } + } + if net == protocol.TestNet { + fmt.Println("TODO: Setup the genesisBlock for TestNet") + return nil + } + + } + return &Chain{ + db, + net, + } +} +func (c *Chain) AddBlock(msg *payload.BlockMessage) error { + if !validateBlock(msg) { + return ErrBlockValidation + } + + if !c.verifyBlock(msg) { + return ErrBlockVerification + } + + fmt.Println("Block Hash is ", msg.Hash.String()) + + buf := new(bytes.Buffer) + err := msg.Encode(buf) + if err != nil { + return err + } + return c.db.Put(msg.Hash.Bytes(), buf.Bytes()) + +} + +// validateBlock will check the transactions, +// merkleroot is good, signature is good,every that does not require state +// This may be moved to the syncmanager. This function should not be done in a seperate go-routine +// We are intentionally blocking here because if the block is invalid, we will +// disconnect from the peer. +// We could have this return an error instead; where the error could even +// say where the validation failed, for the logs. +func validateBlock(msg *payload.BlockMessage) bool { + return true +} + +func (c *Chain) verifyBlock(msg *payload.BlockMessage) bool { + return true +} + +// This will add a header into the db, +// indexing it also, this method will not +// run any checks, like if it links with a header +// previously in the db +// func (c *Chain) addHeaderNoCheck(header *payload.BlockBase) error { + +// } + +//addHeaders is not safe for concurrent access +func (c *Chain) ValidateHeaders(msg *payload.HeadersMessage) error { + + table := database.NewTable(c.db, database.HEADER) + + latestHash, err := table.Get(database.LATESTHEADER) + if err != nil { + return err + } + + key := latestHash + val, err := table.Get(key) + + lastHeader := &payload.BlockBase{} + err = lastHeader.Decode(bytes.NewReader(val)) + if err != nil { + return err + } + + // TODO?:Maybe we should sort these headers using the Index + // We should not get them in mixed order, but doing it would not be expensive + // If they are already in order + + // Do checks on headers + for _, currentHeader := range msg.Headers { + + if lastHeader == nil { + // This should not happen as genesis header is added if new + // database, however we check nonetheless + return errors.New("Previous Header is nil") + } + + // Check current hash links with previous + if currentHeader.PrevHash != lastHeader.Hash { + return errors.New("Last Header hash != current header Prev hash") + } + + // Check current Index is one more than the previous Index + if currentHeader.Index != lastHeader.Index+1 { + return errors.New("Last Header Index != current header Index") + } + + // Check current timestamp is more than the previous header's timestamp + if lastHeader.Timestamp > currentHeader.Timestamp { + return errors.New("Timestamp of Previous Header is more than Timestamp of current Header") + } + + // NONONO:Do not check if current is more than 15 secs in future + // some blocks had delay from forks in past. + + // NOTE: These are the only non-contextual checks we can do without the blockchain state + lastHeader = currentHeader + } + return nil +} + +func (c *Chain) AddHeaders(msg *payload.HeadersMessage) error { + for _, header := range msg.Headers { + if err := c.db.AddHeader(header); err != nil { + return err + } + } + return nil +} diff --git a/pkg/chainparams/asset.go b/pkg/chainparams/asset.go new file mode 100644 index 000000000..99ff0248b --- /dev/null +++ b/pkg/chainparams/asset.go @@ -0,0 +1,7 @@ +package chainparams + +// GAS is the scripthash native neo asset: GAS +var GAS = "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" + +// NEO is the scripthash native neo asset: NEO +var NEO = "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b" diff --git a/pkg/chainparams/config.go b/pkg/chainparams/config.go new file mode 100644 index 000000000..8ec89d99f --- /dev/null +++ b/pkg/chainparams/config.go @@ -0,0 +1,51 @@ +package chainparams + +// For now we will just use this to store the +// peers, to test peer data +// Once complete, it will store the genesis params for testnet and mainnet + +var mainnetSeedList = []string{ + "seed1.neo.org:10333", // NOP + "seed2.neo.org:10333", // YEP + "seed3.neo.org:10333", // YEP + "seed4.neo.org:10333", // NOP + "seed5.neo.org:10333", // YEP + "13.59.52.94:10333", // NOP + "18.220.214.143:10333", // NOP + "13.58.198.112:10333", // NOP + "13.59.14.206:10333", // NOP + "18.216.9.7:10333", // NOP +} + +//MainnetSeedList is a string slice containing the initial seeds from protocol.mainnet +// That are replying +var MainnetSeedList = []string{ + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed5.neo.org:10333", + "http://seed1.ngd.network:10333", + "http://seed2.ngd.network:10333", + "http://seed3.ngd.network:10333", + "http://seed4.ngd.network:10333", + "http://seed5.ngd.network:10333", + "http://seed6.ngd.network:10333", + "http://seed7.ngd.network:10333", + "http://seed8.ngd.network:10333", + "http://seed9.ngd.network:10333", + "http://seed10.ngd.network:10333", +} + +var testNetSeedList = []string{ + + "18.218.97.227:20333", + "18.219.30.120:20333", + "18.219.13.91:20333", + "13.59.116.121:20333", + "18.218.255.178:20333", +} + +// GenesisHash for mainnet +var GenesisHash = "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf" + +// GenesisBlock for mainnet +var GenesisBlock = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151" diff --git a/pkg/connmgr/config.go b/pkg/connmgr/config.go new file mode 100644 index 000000000..e73f99245 --- /dev/null +++ b/pkg/connmgr/config.go @@ -0,0 +1,25 @@ +package connmgr + +import ( + "net" +) + +// Config contains all methods which will be set by the caller to setup the connection manager. +type Config struct { + // GetAddress will return a single address for the connection manager to connect to + GetAddress func() (string, error) + + // OnConnection is called by the connection manager when + // we successfully connect to a peer + // The caller should ideally inform the address manager that we have connected to this address in this function + OnConnection func(conn net.Conn, addr string) + + // OnAccept will take a established connection + OnAccept func(net.Conn) + + // Port is the port in the format "10333" + Port string + + // DialTimeout is the amount of time, before we can disconnect a pending dialed connection + DialTimeout int +} diff --git a/pkg/connmgr/connmgr.go b/pkg/connmgr/connmgr.go new file mode 100644 index 000000000..4d75b0c09 --- /dev/null +++ b/pkg/connmgr/connmgr.go @@ -0,0 +1,272 @@ +package connmgr + +import ( + "errors" + "fmt" + "net" + "net/http" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/util/ip" +) + +var ( + // maxOutboundConn is the maximum number of active peers + // that the connection manager will try to have + maxOutboundConn = 10 + + // maxRetries is the maximum amount of successive retries that + // we can have before we stop dialing that peer + maxRetries = uint8(5) +) + +// Connmgr manages pending/active/failed cnnections +type Connmgr struct { + config Config + PendingList map[string]*Request + ConnectedList map[string]*Request + actionch chan func() +} + +//New creates a new connection manager +func New(cfg Config) *Connmgr { + cnnmgr := &Connmgr{ + cfg, + make(map[string]*Request), + make(map[string]*Request), + make(chan func(), 300), + } + + go func() { + + ip := iputils.GetLocalIP() + addrPort := ip.String() + ":" + cfg.Port + + listener, err := net.Listen("tcp", addrPort) + + if err != nil { + fmt.Println("Error connecting to outbound ", err) + } + + defer func() { + listener.Close() + }() + + for { + + conn, err := listener.Accept() + + if err != nil { + continue + } + // TODO(kev): in the OnAccept the connection address will be added to AddrMgr + go cfg.OnAccept(conn) + } + + }() + + return cnnmgr +} + +// NewRequest will make a new connection +// Gets the address from address func in config +// Then dials it and assigns it to pending +func (c *Connmgr) NewRequest() { + + // Fetch address + addr, err := c.config.GetAddress() + if err != nil { + fmt.Println("Error getting address", err) + } + + // empty request item + r := &Request{} + + r.Addr = addr + fmt.Println("Connecting") + c.Connect(r) + +} + +func (c *Connmgr) Connect(r *Request) error { + + r.Retries++ + + conn, err := c.Dial(r.Addr) + if err != nil { + c.failed(r) + return err + } + + r.Conn = conn + r.Inbound = true + + // r.Permanent is set by the caller. default is false + // The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network + + return c.connected(r) +} + +func (cm *Connmgr) Disconnect(addr string) { + + // fetch from connected list + r, ok := cm.ConnectedList[addr] + + if !ok { + // If not in connected, check pending + r, ok = cm.PendingList[addr] + } + + cm.disconnected(r) + +} + +// Dial is used to dial up connections given the addres and ip in the form address:port +func (c *Connmgr) Dial(addr string) (net.Conn, error) { + dialTimeout := 1 * time.Second + conn, err := net.DialTimeout("tcp", addr, dialTimeout) + if err != nil { + if !isConnected() { + return nil, errors.New("Fatal Error: You do not seem to be connected to the internet") + } + return conn, err + } + return conn, nil +} +func (cm *Connmgr) failed(r *Request) { + + cm.actionch <- func() { + // priority to check if it is permanent or inbound + // if so then these peers are valuable in NEO and so we will just retry another time + if r.Inbound || r.Permanent { + + multiplier := time.Duration(r.Retries * 10) + time.AfterFunc(multiplier*time.Second, + func() { + cm.Connect(r) + }, + ) + // if not then we should check if this request has had maxRetries + // if it has then get a new address + // if not then call Connect on it again + } else if r.Retries > maxRetries { + if cm.config.GetAddress != nil { + go cm.NewRequest() + } + fmt.Println("This peer has been tried the maximum amount of times and a source of new address has not been specified.") + } else { + go cm.Connect(r) + } + + } + +} + +// Disconnected is called when a peer disconnects. +// we take the addr from peer, which is also it's key in the map +// and we use it to remove it from the connectedList +func (c *Connmgr) disconnected(r *Request) error { + + errChan := make(chan error, 0) + + c.actionch <- func() { + + var err error + + if r == nil { + err = errors.New("Request object is nil") + } + + r2 := *r // dereference it, so that r.Addr is not lost on delete + + // if for some reason the underlying connection is not closed, close it + r.Conn.Close() + r.Conn = nil + // if for some reason it is in pending list, remove it + delete(c.PendingList, r.Addr) + delete(c.ConnectedList, r.Addr) + c.failed(&r2) + errChan <- err + } + + return <-errChan +} + +//Connected is called when the connection manager +// makes a successful connection. +func (c *Connmgr) connected(r *Request) error { + + errorChan := make(chan error, 0) + + c.actionch <- func() { + + var err error + + // This should not be the case, since we connected + // Keeping it here to be safe + if r == nil { + err = errors.New("Request object as nil inside of the connected function") + } + + // reset retries to 0 + r.Retries = 0 + + // add to connectedList + c.ConnectedList[r.Addr] = r + + // remove from pending if it was there + delete(c.PendingList, r.Addr) + + if c.config.OnConnection != nil { + c.config.OnConnection(r.Conn, r.Addr) + } + + fmt.Println("Error connected", err) + + errorChan <- err + } + return <-errorChan +} + +// Pending is synchronous, we do not want to continue with logic +// until we are certain it has been added to the pendingList +func (c *Connmgr) pending(r *Request) error { + + errChan := make(chan error, 0) + + c.actionch <- func() { + + var err error + + if r == nil { + err = errors.New("Error : Request object is nil") + } + + c.PendingList[r.Addr] = r + errChan <- err + } + + return <-errChan +} + +func (c *Connmgr) Run() { + go c.loop() +} + +func (c *Connmgr) loop() { + for { + select { + case f := <-c.actionch: + f() + } + } +} + +// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application +func isConnected() (ok bool) { + _, err := http.Get("http://clients3.google.com/generate_204") + if err != nil { + return false + } + return true +} diff --git a/pkg/connmgr/connmgr_test.go b/pkg/connmgr/connmgr_test.go new file mode 100644 index 000000000..fbc591263 --- /dev/null +++ b/pkg/connmgr/connmgr_test.go @@ -0,0 +1,106 @@ +package connmgr_test + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/connmgr" + "github.com/stretchr/testify/assert" +) + +func TestDial(t *testing.T) { + cfg := connmgr.Config{ + GetAddress: nil, + OnConnection: nil, + OnAccept: nil, + Port: "", + DialTimeout: 0, + } + + cm := connmgr.New(cfg) + cm.Run() + + ipport := "google.com:80" // google unlikely to go offline, a better approach to test Dialing is welcome. + + conn, err := cm.Dial(ipport) + assert.Equal(t, nil, err) + assert.NotEqual(t, nil, conn) +} +func TestConnect(t *testing.T) { + cfg := connmgr.Config{ + GetAddress: nil, + OnConnection: nil, + OnAccept: nil, + Port: "", + DialTimeout: 0, + } + + cm := connmgr.New(cfg) + cm.Run() + + ipport := "google.com:80" + + r := connmgr.Request{Addr: ipport} + + cm.Connect(&r) + + assert.Equal(t, 1, len(cm.ConnectedList)) + +} +func TestNewRequest(t *testing.T) { + + address := "google.com:80" + + var getAddr = func() (string, error) { + return address, nil + } + + cfg := connmgr.Config{ + GetAddress: getAddr, + OnConnection: nil, + OnAccept: nil, + Port: "", + DialTimeout: 0, + } + + cm := connmgr.New(cfg) + + cm.Run() + + cm.NewRequest() + + if _, ok := cm.ConnectedList[address]; ok { + assert.Equal(t, true, ok) + assert.Equal(t, 1, len(cm.ConnectedList)) + return + } + + assert.Fail(t, "Could not find the address in the connected lists") + +} +func TestDisconnect(t *testing.T) { + + address := "google.com:80" + + var getAddr = func() (string, error) { + return address, nil + } + + cfg := connmgr.Config{ + GetAddress: getAddr, + OnConnection: nil, + OnAccept: nil, + Port: "", + DialTimeout: 0, + } + + cm := connmgr.New(cfg) + + cm.Run() + + cm.NewRequest() + + cm.Disconnect(address) + + assert.Equal(t, 0, len(cm.ConnectedList)) + +} diff --git a/pkg/connmgr/readme.md b/pkg/connmgr/readme.md new file mode 100644 index 000000000..d421b55f3 --- /dev/null +++ b/pkg/connmgr/readme.md @@ -0,0 +1,26 @@ +# Package - Connection Manager + +## Responsibility + +- Manages the active, failed and pending connections for the node. + +## Features + +- Takes an address, dials it and packages it into a request to manage. + +- Retry failed connections. + +- Uses one function as a source for it's addresses. It does not manage addresses. + + +## Usage + +The following methods are exposed from the Connection manager: + +- NewRequest() : This will fetch a new address and connect to it. + +- Connect(r *Request) : This takes a Request object and connects to it. It follow the same logic as NewRequest() however instead of getting the address from the datasource given upon initialisation, you directly feed the address you want to connect to. + +- Disconnect(addrport string) : Given an address:port, this will disconnect it, close the connection and remove it from the connected and pending list, if it was there. + +- Dial(addrport string) (net.Conn, error) : Given an address:port, this will connect to it and return a pointer to a connection plus a nil error if successful, or nil with an error. \ No newline at end of file diff --git a/pkg/connmgr/request.go b/pkg/connmgr/request.go new file mode 100644 index 000000000..5062064be --- /dev/null +++ b/pkg/connmgr/request.go @@ -0,0 +1,13 @@ +package connmgr + +import ( + "net" +) + +type Request struct { + Conn net.Conn + Addr string + Permanent bool + Inbound bool + Retries uint8 // should not be trying more than 255 tries +} diff --git a/pkg/crypto/aes/aes256.go b/pkg/crypto/aes/aes256.go new file mode 100755 index 000000000..6ef1b5c74 --- /dev/null +++ b/pkg/crypto/aes/aes256.go @@ -0,0 +1,93 @@ +package aes + +import ( + "crypto/aes" + "crypto/cipher" +) + +// AESEncrypt encrypts the key with the given source. +func Encrypt(src, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + ecb := newECBEncrypter(block) + out := make([]byte, len(src)) + ecb.CryptBlocks(out, src) + + return out, nil +} + +// AESDecrypt decrypts the encrypted source with the given key. +func Decrypt(crypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockMode := newECBDecrypter(block) + out := make([]byte, len(crypted)) + blockMode.CryptBlocks(out, crypted) + return out, nil +} + +type ecb struct { + b cipher.Block + blockSize int +} + +func newECB(b cipher.Block) *ecb { + return &ecb{ + b: b, + blockSize: b.BlockSize(), + } +} + +type ecbEncrypter ecb + +func newECBEncrypter(b cipher.Block) cipher.BlockMode { + return (*ecbEncrypter)(newECB(b)) +} + +func (ecb *ecbEncrypter) BlockSize() int { + return ecb.blockSize +} + +func (ecb *ecbEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%ecb.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + ecb.b.Encrypt(dst, src[:ecb.blockSize]) + src = src[ecb.blockSize:] + dst = dst[ecb.blockSize:] + } +} + +type ecbDecrypter ecb + +func newECBDecrypter(b cipher.Block) cipher.BlockMode { + return (*ecbDecrypter)(newECB(b)) +} + +func (ecb ecbDecrypter) BlockSize() int { + return ecb.blockSize +} + +func (ecb *ecbDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%ecb.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + ecb.b.Decrypt(dst, src[:ecb.blockSize]) + src = src[ecb.blockSize:] + dst = dst[ecb.blockSize:] + } +} diff --git a/pkg/crypto/base58/base58.go b/pkg/crypto/base58/base58.go new file mode 100755 index 000000000..a62181a72 --- /dev/null +++ b/pkg/crypto/base58/base58.go @@ -0,0 +1,78 @@ +package base58 + +import ( + "fmt" + "math/big" +) + +const prefix rune = '1' + +var decodeMap = map[rune]int64{ + '1': 0, '2': 1, '3': 2, '4': 3, '5': 4, + '6': 5, '7': 6, '8': 7, '9': 8, 'A': 9, + 'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14, + 'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19, + 'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24, + 'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29, + 'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34, + 'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39, + 'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44, + 'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49, + 's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54, + 'x': 55, 'y': 56, 'z': 57, +} + +// Base58Decode decodes the base58 encoded string. +func Decode(s string) ([]byte, error) { + var ( + startIndex = 0 + zero = 0 + ) + for i, c := range s { + if c == prefix { + zero++ + } else { + startIndex = i + break + } + } + + var ( + n = big.NewInt(0) + div = big.NewInt(58) + ) + for _, c := range s[startIndex:] { + charIndex, ok := decodeMap[c] + if !ok { + return nil, fmt.Errorf( + "invalid character '%c' when decoding this base58 string: '%s'", c, s, + ) + } + n.Add(n.Mul(n, div), big.NewInt(charIndex)) + } + + out := n.Bytes() + buf := make([]byte, (zero + len(out))) + copy(buf[zero:], out[:]) + + return buf, nil +} + +// Base58Encode encodes a byte slice to be a base58 encoded string. +func Encode(bytes []byte) string { + var ( + lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + x = new(big.Int).SetBytes(bytes) + r = new(big.Int) + m = big.NewInt(58) + zero = big.NewInt(0) + encoded string + ) + + for x.Cmp(zero) > 0 { + x.QuoRem(x, m, r) + encoded = string(lookupTable[r.Int64()]) + encoded + } + + return encoded +} diff --git a/pkg/crypto/base58/base58_test.go b/pkg/crypto/base58/base58_test.go new file mode 100755 index 000000000..524f0e55e --- /dev/null +++ b/pkg/crypto/base58/base58_test.go @@ -0,0 +1,32 @@ +package base58 + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecode(t *testing.T) { + input := "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" + + data, err := Decode(input) + if err != nil { + t.Fatal(err) + } + + expected := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" + actual := hex.EncodeToString(data) + assert.Equal(t, expected, actual) +} +func TestEncode(t *testing.T) { + input := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" + + inputBytes, _ := hex.DecodeString(input) + + data := Encode(inputBytes) + + expected := "F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" // Removed the 1 as it is not checkEncoding + actual := data + assert.Equal(t, expected, actual) +} diff --git a/pkg/crypto/elliptic/Readme.md b/pkg/crypto/elliptic/Readme.md new file mode 100755 index 000000000..ea1033488 --- /dev/null +++ b/pkg/crypto/elliptic/Readme.md @@ -0,0 +1,18 @@ +## Package - Elliptic + +### Why + +The curve and arithmetic functions have been modularised, so that curves can be swapped in and out, without effecting the functionality. + +The modular arithmetic used is not specialised for a specific curve. + +In order to use this package, you must declare an ellipticcurve struct and then set the curve. + +Example: + +` + + curve = NewEllipticCurve(Secp256k1) + +` +If no curve is set, the default curve is the r1 curve used for NEO. The tests are done using the k1 curve, so in the elliptic_test.go file, the curve is changed accordingly. diff --git a/pkg/crypto/elliptic/curves.go b/pkg/crypto/elliptic/curves.go new file mode 100755 index 000000000..33f19540b --- /dev/null +++ b/pkg/crypto/elliptic/curves.go @@ -0,0 +1,58 @@ +package elliptic + +/* + This file was originally made by vsergeev. + + Modifications have been made under the MIT license. + License: MIT + + +*/ + +import ( + "math/big" +) + +var curve Curve + +type curveType string + +const ( + Secp256r1 curveType = "Secp256r1" + Secp256k1 curveType = "Secp256k1" +) + +func (ChosenCurve *Curve) SetCurveSecp256r1() { + ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16) //Q + ChosenCurve.A, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16) + ChosenCurve.B, _ = new(big.Int).SetString("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16) + ChosenCurve.G.X, _ = new(big.Int).SetString("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16) + ChosenCurve.G.Y, _ = new(big.Int).SetString("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16) + ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16) + ChosenCurve.H, _ = new(big.Int).SetString("01", 16) + ChosenCurve.Name = "Secp256r1" +} + +func (ChosenCurve *Curve) SetCurveSecp256k1() { + ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) + ChosenCurve.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16) + ChosenCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) + ChosenCurve.G.X, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) + ChosenCurve.G.Y, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) + ChosenCurve.H, _ = new(big.Int).SetString("01", 16) + ChosenCurve.Name = "Secp256k1" +} + +func NewEllipticCurve(ct curveType) Curve { + var curve Curve + switch ct { + case Secp256k1: + curve.SetCurveSecp256k1() + case Secp256r1: + curve.SetCurveSecp256r1() + default: + curve.SetCurveSecp256r1() + } + return curve +} diff --git a/pkg/crypto/elliptic/elliptic.go b/pkg/crypto/elliptic/elliptic.go new file mode 100755 index 000000000..26a07388f --- /dev/null +++ b/pkg/crypto/elliptic/elliptic.go @@ -0,0 +1,317 @@ +/* +This file has been modified under the MIT license. +Original: https://github.com/vsergeev/btckeygenie +*/ + +package elliptic + +import ( + nativeelliptic "crypto/elliptic" + "encoding/hex" + "errors" + "fmt" + "math/big" +) + +// Point represents a point on an EllipticCurve. +type Point struct { + X *big.Int + Y *big.Int +} + +/* y**2 = x**3 + a*x + b % p */ +// Curve represents the parameters of a short Weierstrass equation elliptic curve. +type Curve struct { + A *big.Int + B *big.Int + P *big.Int + G Point + N *big.Int + H *big.Int + Name string +} + +// dump dumps the bytes of a point for debugging. +func (p *Point) dump() { + fmt.Print(p.format()) +} + +// format formats the bytes of a point for debugging. +func (p *Point) format() string { + if p.X == nil && p.Y == nil { + return "(inf,inf)" + } + return fmt.Sprintf("(%s,%s)", hex.EncodeToString(p.X.Bytes()), hex.EncodeToString(p.Y.Bytes())) +} + +func (ec Curve) Params() *nativeelliptic.CurveParams { + return &nativeelliptic.CurveParams{ + P: ec.P, + N: ec.N, + B: ec.B, + Gx: ec.G.X, + Gy: ec.G.Y, + BitSize: 256, + Name: ec.Name, + } +} + +/*** Modular Arithmetic ***/ + +/* NOTE: Returning a new z each time below is very space inefficient, but the + * alternate accumulator based design makes the point arithmetic functions look + * absolutely hideous. I may still change this in the future. */ + +// addMod computes z = (x + y) % p. +func addMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) { + z = new(big.Int).Add(x, y) + z.Mod(z, p) + return z +} + +// subMod computes z = (x - y) % p. +func subMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) { + z = new(big.Int).Sub(x, y) + z.Mod(z, p) + return z +} + +// mulMod computes z = (x * y) % p. +func mulMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) { + n := new(big.Int).Set(x) + z = big.NewInt(0) + + for i := 0; i < y.BitLen(); i++ { + if y.Bit(i) == 1 { + z = addMod(z, n, p) + } + n = addMod(n, n, p) + } + + return z +} + +// invMod computes z = (1/x) % p. +func invMod(x *big.Int, p *big.Int) (z *big.Int) { + z = new(big.Int).ModInverse(x, p) + return z +} + +// expMod computes z = (x^e) % p. +func expMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) { + z = new(big.Int).Exp(x, y, p) + return z +} + +// sqrtMod computes z = sqrt(x) % p. +func sqrtMod(x *big.Int, p *big.Int) (z *big.Int) { + /* assert that p % 4 == 3 */ + if new(big.Int).Mod(p, big.NewInt(4)).Cmp(big.NewInt(3)) != 0 { + panic("p is not equal to 3 mod 4!") + } + + /* z = sqrt(x) % p = x^((p+1)/4) % p */ + + /* e = (p+1)/4 */ + e := new(big.Int).Add(p, big.NewInt(1)) + e = e.Rsh(e, 2) + + z = expMod(x, e, p) + return z +} + +/*** Point Arithmetic on Curve ***/ + +// IsInfinity checks if point P is infinity on EllipticCurve ec. +func (ec *Curve) IsInfinity(P Point) bool { + /* We use (nil,nil) to represent O, the point at infinity. */ + + if P.X == nil && P.Y == nil { + return true + } + + return false +} + +// IsOnCurve checks if point P is on EllipticCurve ec. +func (ec Curve) IsOnCurve(P1, P2 *big.Int) bool { + P := Point{P1, P2} + if ec.IsInfinity(P) { + return false + } + + /* y**2 = x**3 + a*x + b % p */ + lhs := mulMod(P.Y, P.Y, ec.P) + rhs := addMod( + addMod( + expMod(P.X, big.NewInt(3), ec.P), + mulMod(ec.A, P.X, ec.P), ec.P), + ec.B, ec.P) + + if lhs.Cmp(rhs) == 0 { + return true + } + + return false +} + +// Add computes R = P + Q on EllipticCurve ec. +func (ec Curve) Add(P1, P2, Q1, Q2 *big.Int) (R1 *big.Int, R2 *big.Int) { + /* See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf */ + P := Point{P1, P2} + Q := Point{Q1, Q2} + R := Point{} + if ec.IsInfinity(P) && ec.IsInfinity(Q) { + /* Rule #1 Identity */ + /* R = O + O = O */ + + R.X = nil + R.Y = nil + + } else if ec.IsInfinity(P) { + /* Rule #2 Identity */ + /* R = O + Q = Q */ + + R.X = new(big.Int).Set(Q.X) + R.Y = new(big.Int).Set(Q.Y) + + } else if ec.IsInfinity(Q) { + /* Rule #2 Identity */ + /* R = P + O = P */ + + R.X = new(big.Int).Set(P.X) + R.Y = new(big.Int).Set(P.Y) + + } else if P.X.Cmp(Q.X) == 0 && addMod(P.Y, Q.Y, ec.P).Sign() == 0 { + /* Rule #3 Identity */ + /* R = (x,y) + (x,-y) = O */ + + R.X = nil + R.Y = nil + + } else if P.X.Cmp(Q.X) == 0 && P.Y.Cmp(Q.Y) == 0 && P.Y.Sign() != 0 { + /* Rule #5 Point doubling */ + /* R = P + P */ + + /* Lambda = (3*P.X*P.X + a) / (2*P.Y) */ + num := addMod( + mulMod(big.NewInt(3), + mulMod(P.X, P.X, ec.P), ec.P), + ec.A, ec.P) + den := invMod(mulMod(big.NewInt(2), P.Y, ec.P), ec.P) + lambda := mulMod(num, den, ec.P) + + /* R.X = lambda*lambda - 2*P.X */ + R.X = subMod( + mulMod(lambda, lambda, ec.P), + mulMod(big.NewInt(2), P.X, ec.P), + ec.P) + /* R.Y = lambda*(P.X - R.X) - P.Y */ + R.Y = subMod( + mulMod(lambda, subMod(P.X, R.X, ec.P), ec.P), + P.Y, ec.P) + + } else if P.X.Cmp(Q.X) != 0 { + /* Rule #4 Point addition */ + /* R = P + Q */ + + /* Lambda = (Q.Y - P.Y) / (Q.X - P.X) */ + num := subMod(Q.Y, P.Y, ec.P) + den := invMod(subMod(Q.X, P.X, ec.P), ec.P) + lambda := mulMod(num, den, ec.P) + + /* R.X = lambda*lambda - P.X - Q.X */ + R.X = subMod( + subMod( + mulMod(lambda, lambda, ec.P), + P.X, ec.P), + Q.X, ec.P) + + /* R.Y = lambda*(P.X - R.X) - P.Y */ + R.Y = subMod( + mulMod(lambda, + subMod(P.X, R.X, ec.P), ec.P), + P.Y, ec.P) + } else { + panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format())) + } + + return R.X, R.Y +} + +// ScalarMult computes Q = k * P on EllipticCurve ec. +func (ec Curve) ScalarMult(P1, P2 *big.Int, l []byte) (Q1, Q2 *big.Int) { + /* Note: this function is not constant time, due to the branching nature of + * the underlying point Add() function. */ + + /* Montgomery Ladder Point Multiplication + * + * Implementation based on pseudocode here: + * See https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder */ + + P := Point{P1, P2} + k := big.Int{} + k.SetBytes(l) + + var R0 Point + var R1 Point + + R0.X = nil + R0.Y = nil + R1.X = new(big.Int).Set(P.X) + R1.Y = new(big.Int).Set(P.Y) + + for i := ec.N.BitLen() - 1; i >= 0; i-- { + if k.Bit(i) == 0 { + R1.X, R1.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y) + R0.X, R0.Y = ec.Add(R0.X, R0.Y, R0.X, R0.Y) + } else { + R0.X, R0.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y) + R1.X, R1.Y = ec.Add(R1.X, R1.Y, R1.X, R1.Y) + } + } + + return R0.X, R0.Y +} + +// ScalarBaseMult computes Q = k * G on EllipticCurve ec. +func (ec Curve) ScalarBaseMult(k []byte) (Q1, Q2 *big.Int) { + + return ec.ScalarMult(ec.G.X, ec.G.Y, k) +} + +// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a Point P on EllipticCurve ec. +func (ec *Curve) Decompress(x *big.Int, ylsb uint) (P Point, err error) { + /* y**2 = x**3 + a*x + b % p */ + rhs := addMod( + addMod( + expMod(x, big.NewInt(3), ec.P), + mulMod(ec.A, x, ec.P), + ec.P), + ec.B, ec.P) + + /* y = sqrt(rhs) % p */ + y := sqrtMod(rhs, ec.P) + + /* Use -y if opposite lsb is required */ + if y.Bit(0) != (ylsb & 0x1) { + y = subMod(big.NewInt(0), y, ec.P) + } + + P.X = x + P.Y = y + + if !ec.IsOnCurve(P.X, P.Y) { + return P, errors.New("Compressed (x, ylsb) not on curve.") + } + + return P, nil +} + +func (ec Curve) Double(x1, y1 *big.Int) (x, y *big.Int) { + x = &big.Int{} + x.SetBytes([]byte{0x00}) + y = &big.Int{} + y.SetBytes([]byte{0x00}) + return x, y +} diff --git a/pkg/crypto/elliptic/elliptic_test.go b/pkg/crypto/elliptic/elliptic_test.go new file mode 100755 index 000000000..fa83dd034 --- /dev/null +++ b/pkg/crypto/elliptic/elliptic_test.go @@ -0,0 +1,231 @@ +/* btckeygenie v1.0.0 + * https://github.com/vsergeev/btckeygenie + * License: MIT + */ + +package elliptic + +import ( + "encoding/hex" + "math/big" + "testing" +) + +func init() { + + curve = NewEllipticCurve(Secp256k1) +} + +func hex2int(hexstring string) (v *big.Int) { + v, _ = new(big.Int).SetString(hexstring, 16) + return v +} + +func TestOnCurve(t *testing.T) { + if !curve.IsOnCurve(curve.G.X, curve.G.Y) { + t.Fatal("failure G on curve") + } + + t.Log("G on curve") +} + +func TestInfinity(t *testing.T) { + O := Point{nil, nil} + + /* O not on curve */ + if curve.IsOnCurve(O.X, O.Y) { + t.Fatal("failure O on curve") + } + + /* O is infinity */ + if !curve.IsInfinity(O) { + t.Fatal("failure O not infinity on curve") + } + + t.Log("O is not on curve and is infinity") +} + +func TestPointAdd(t *testing.T) { + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + + P := Point{hex2int(X), hex2int(Y)} + O := Point{nil, nil} + + /* R = O + O = O */ + { + R1, R2 := curve.Add(O.X, O.Y, O.X, O.Y) + R := Point{R1, R2} + if !curve.IsInfinity(R) { + t.Fatal("failure O + O = O") + } + t.Log("success O + O = O") + } + + /* R = P + O = P */ + { + R1, R2 := curve.Add(P.X, P.Y, O.X, O.Y) + R := Point{R1, R2} + if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 { + t.Fatal("failure P + O = P") + } + t.Log("success P + O = P") + } + + /* R = O + Q = Q */ + { + R1, R2 := curve.Add(O.X, O.Y, P.X, P.Y) + R := Point{R1, R2} + if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 { + t.Fatal("failure O + Q = Q") + } + t.Log("success O + Q = Q") + } + + /* R = (x,y) + (x,-y) = O */ + { + Q := Point{P.X, subMod(big.NewInt(0), P.Y, curve.P)} + + R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y) + R := Point{R1, R2} + if !curve.IsInfinity(R) { + t.Fatal("failure (x,y) + (x,-y) = O") + } + t.Log("success (x,y) + (x,-y) = O") + } + + /* R = P + P */ + { + PP := Point{hex2int("5dbcd5dfea550eb4fd3b5333f533f086bb5267c776e2a1a9d8e84c16a6743d82"), hex2int("8dde3986b6cbe395da64b6e95fb81f8af73f6e0cf1100555005bb4ba2a6a4a07")} + + R1, R2 := curve.Add(P.X, P.Y, P.X, P.Y) + R := Point{R1, R2} + if R.X.Cmp(PP.X) != 0 || R.Y.Cmp(PP.Y) != 0 { + t.Fatal("failure P + P") + } + t.Log("success P + P") + } + + Q := Point{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")} + PQ := Point{hex2int("fe7d540002e4355eb0ec36c217b4735495de7bd8634055ded3683b0e9da70ef1"), hex2int("fc033c1d74cb34e087a3495e505c0fc0e9e3e8297994878d89d882254ce8a9ef")} + + /* R = P + Q */ + { + R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y) + R := Point{R1, R2} + if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 { + t.Fatal("failure P + Q") + } + t.Log("success P + Q") + } + + /* R = Q + P */ + { + R1, R2 := curve.Add(Q.X, Q.Y, P.X, P.Y) + R := Point{R1, R2} + if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 { + t.Fatal("failure Q + P") + } + t.Log("success Q + P") + } +} + +func TestPointScalarMult(t *testing.T) { + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + P := Point{hex2int(X), hex2int(Y)} + + /* Q = k*P */ + { + T := Point{hex2int("87d592bfdd24adb52147fea343db93e10d0585bc66d91e365c359973c0dc7067"), hex2int("a374e206cb7c8cd1074bdf9bf6ddea135f983aaa6475c9ab3bb4c38a0046541b")} + input, _ := hex.DecodeString("14eb373700c3836404acd0820d9fa8dfa098d26177ca6e18b1c7f70c6af8fc18") + + Q1, Q2 := curve.ScalarMult(P.X, P.Y, input) + Q := Point{Q1, Q2} + if Q.X.Cmp(T.X) != 0 || Q.Y.Cmp(T.Y) != 0 { + t.Fatal("failure k*P") + } + t.Log("success k*P") + } + + /* Q = n*G = O */ + { + Q1, Q2 := curve.ScalarMult(curve.G.X, curve.G.Y, curve.N.Bytes()) + Q := Point{Q1, Q2} + if !curve.IsInfinity(Q) { + t.Fatal("failure n*G = O") + } + t.Log("success n*G = O") + } +} + +func TestPointScalarBaseMult(t *testing.T) { + /* Sample Private Key */ + D := "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725" + /* Sample Corresponding Public Key */ + X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" + Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6" + + P := Point{hex2int(X), hex2int(Y)} + + /* Q = d*G = P */ + + Q1, Q2 := curve.ScalarBaseMult(hex2int(D).Bytes()) + Q := Point{Q1, Q2} + if P.X.Cmp(Q.X) != 0 || P.Y.Cmp(Q.Y) != 0 { + t.Fatal("failure Q = d*G") + } + t.Log("success Q = d*G") + + /* Q on curve */ + if !curve.IsOnCurve(Q.X, Q.Y) { + t.Fatal("failure Q on curve") + } + t.Log("success Q on curve") + + /* R = 0*G = O */ + R1, R2 := curve.ScalarBaseMult(big.NewInt(0).Bytes()) + R := Point{R1, R2} + if !curve.IsInfinity(R) { + t.Fatal("failure 0*G = O") + } + t.Log("success 0*G = O") +} + +func TestPointDecompress(t *testing.T) { + /* Valid points */ + var validDecompressVectors = []Point{ + {hex2int("50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"), hex2int("2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6")}, + {hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")}, + {hex2int("f680556678e25084a82fa39e1b1dfd0944f7e69fddaa4e03ce934bd6b291dca0"), hex2int("52c10b721d34447e173721fb0151c68de1106badb089fb661523b8302a9097f5")}, + {hex2int("241febb8e23cbd77d664a18f66ad6240aaec6ecdc813b088d5b901b2e285131f"), hex2int("513378d9ff94f8d3d6c420bd13981df8cd50fd0fbd0cb5afabb3e66f2750026d")}, + } + + for i := 0; i < len(validDecompressVectors); i++ { + P, err := curve.Decompress(validDecompressVectors[i].X, validDecompressVectors[i].Y.Bit(0)) + if err != nil { + t.Fatalf("failure decompress P, got error %v on index %d", err, i) + } + if P.X.Cmp(validDecompressVectors[i].X) != 0 || P.Y.Cmp(validDecompressVectors[i].Y) != 0 { + t.Fatalf("failure decompress P, got mismatch on index %d", i) + } + } + t.Log("success Decompress() on valid vectors") + + /* Invalid points */ + var invalidDecompressVectors = []struct { + X *big.Int + YLsb uint + }{ + {hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 0}, + {hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 1}, + } + + for i := 0; i < len(invalidDecompressVectors); i++ { + _, err := curve.Decompress(invalidDecompressVectors[i].X, invalidDecompressVectors[i].YLsb) + if err == nil { + t.Fatalf("failure decompress invalid P, got decompressed point on index %d", i) + } + } + t.Log("success Decompress() on invalid vectors") +} diff --git a/pkg/crypto/hash/hash.go b/pkg/crypto/hash/hash.go new file mode 100755 index 000000000..22ab1c6d6 --- /dev/null +++ b/pkg/crypto/hash/hash.go @@ -0,0 +1,77 @@ +package hash + +import ( + "crypto/sha256" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + "golang.org/x/crypto/ripemd160" +) + +func Sha256(data []byte) (util.Uint256, error) { + var hash util.Uint256 + hasher := sha256.New() + hasher.Reset() + _, err := hasher.Write(data) + + hash, err = util.Uint256DecodeBytes(hasher.Sum(nil)) + if err != nil { + return hash, err + } + return hash, nil +} + +func DoubleSha256(data []byte) (util.Uint256, error) { + var hash util.Uint256 + + h1, err := Sha256(data) + if err != nil { + return hash, err + } + + hash, err = Sha256(h1.Bytes()) + if err != nil { + return hash, err + } + return hash, nil +} + +func RipeMD160(data []byte) (util.Uint160, error) { + var hash util.Uint160 + hasher := ripemd160.New() + hasher.Reset() + _, err := io.WriteString(hasher, string(data)) + + hash, err = util.Uint160DecodeBytes(hasher.Sum(nil)) + if err != nil { + return hash, err + } + return hash, nil +} + +func Hash160(data []byte) (util.Uint160, error) { + var hash util.Uint160 + h1, err := Sha256(data) + + h2, err := RipeMD160(h1.Bytes()) + + hash, err = util.Uint160DecodeBytes(h2.Bytes()) + + if err != nil { + return hash, err + } + return hash, nil +} + +func Checksum(data []byte) ([]byte, error) { + hash, err := Sum(data) + if err != nil { + return nil, err + } + return hash[:4], nil +} + +func Sum(b []byte) (util.Uint256, error) { + hash, err := DoubleSha256((b)) + return hash, err +} diff --git a/pkg/crypto/hash/hash_test.go b/pkg/crypto/hash/hash_test.go new file mode 100755 index 000000000..aa23718f6 --- /dev/null +++ b/pkg/crypto/hash/hash_test.go @@ -0,0 +1,62 @@ +package hash + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSha256(t *testing.T) { + input := []byte("hello") + data, err := Sha256(input) + + if err != nil { + t.Fatal(err) + } + expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + actual := hex.EncodeToString(data.Bytes()) // MARK: In the DecodeBytes function, there is a bytes reverse, not sure why? + + assert.Equal(t, expected, actual) +} + +func TestHashDoubleSha256(t *testing.T) { + input := []byte("hello") + data, err := DoubleSha256(input) + + if err != nil { + t.Fatal(err) + } + + firstSha, _ := Sha256(input) + doubleSha, _ := Sha256(firstSha.Bytes()) + expected := hex.EncodeToString(doubleSha.Bytes()) + + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} + +func TestHashRipeMD160(t *testing.T) { + input := []byte("hello") + data, err := RipeMD160(input) + + if err != nil { + t.Fatal(err) + } + expected := "108f07b8382412612c048d07d13f814118445acd" + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} + +func TestHash160(t *testing.T) { + input := "02cccafb41b220cab63fd77108d2d1ebcffa32be26da29a04dca4996afce5f75db" + publicKeyBytes, _ := hex.DecodeString(input) + data, err := Hash160(publicKeyBytes) + + if err != nil { + t.Fatal(err) + } + expected := "c8e2b685cc70ec96743b55beb9449782f8f775d8" + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} diff --git a/pkg/crypto/privatekey/privatekey.go b/pkg/crypto/privatekey/privatekey.go new file mode 100755 index 000000000..513d9366f --- /dev/null +++ b/pkg/crypto/privatekey/privatekey.go @@ -0,0 +1,117 @@ +package privatekey + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/crypto/publickey" + + "github.com/CityOfZion/neo-go/pkg/crypto/base58" + "github.com/CityOfZion/neo-go/pkg/crypto/elliptic" + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/rfc6979" +) + +// PrivateKey represents a NEO private key. +type PrivateKey struct { + b []byte +} + +func NewPrivateKey() (*PrivateKey, error) { + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + b := make([]byte, curve.N.BitLen()/8+8) + if _, err := io.ReadFull(rand.Reader, b); err != nil { + return nil, err + } + + d := new(big.Int).SetBytes(b) + d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1))) + d.Add(d, big.NewInt(1)) + + p := &PrivateKey{b: d.Bytes()} + return p, nil +} + +func NewPrivateKeyFromHex(str string) (*PrivateKey, error) { + b, err := hex.DecodeString(str) + if err != nil { + return nil, err + } + return NewPrivateKeyFromBytes(b) +} + +// NewPrivateKeyFromBytes returns a NEO PrivateKey from the given byte slice. +func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) { + if len(b) != 32 { + return nil, fmt.Errorf( + "invalid byte length: expected %d bytes got %d", 32, len(b), + ) + } + return &PrivateKey{b}, nil +} + +func (p *PrivateKey) PublicKey() (*publickey.PublicKey, error) { + var ( + c = elliptic.NewEllipticCurve(elliptic.Secp256r1) + q = new(big.Int).SetBytes(p.b) + ) + + p1, p2 := c.ScalarBaseMult(q.Bytes()) + point := elliptic.Point{ + X: p1, + Y: p2, + } + if !c.IsOnCurve(p1, p2) { + return nil, errors.New("failed to derive public key using elliptic curve") + } + + return &publickey.PublicKey{ + Curve: c, + Point: point, + }, nil + +} + +func WIFEncode(key []byte) (s string) { + if len(key) != 32 { + return "invalid private key length" + } + + buf := new(bytes.Buffer) + buf.WriteByte(0x80) + buf.Write(key) + + buf.WriteByte(0x01) + + checksum, _ := hash.Checksum(buf.Bytes()) + + buf.Write(checksum) + + WIF := base58.Encode(buf.Bytes()) + return WIF +} + +func (p *PrivateKey) Sign(data []byte) ([]byte, error) { + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + key := p.b + digest, _ := hash.Sha256(data) + + r, s, err := rfc6979.SignECDSA(curve, key, digest[:], sha256.New) + if err != nil { + return nil, err + } + + curveOrderByteSize := curve.P.BitLen() / 8 + rBytes, sBytes := r.Bytes(), s.Bytes() + signature := make([]byte, curveOrderByteSize*2) + copy(signature[curveOrderByteSize-len(rBytes):], rBytes) + copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes) + + return signature, nil +} diff --git a/pkg/crypto/privatekey/privatekey_test.go b/pkg/crypto/privatekey/privatekey_test.go new file mode 100755 index 000000000..7c46737b6 --- /dev/null +++ b/pkg/crypto/privatekey/privatekey_test.go @@ -0,0 +1,48 @@ +package privatekey + +import ( + "encoding/hex" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPrivateKeyToPublicKey(t *testing.T) { + input := "495d528227c7dcc234c690af1222e67cde916dac1652cad97e0263825a8268a6" + + privateKey, err := NewPrivateKeyFromHex(input) + if err != nil { + t.Fatal(err) + } + pubKey, _ := privateKey.PublicKey() + pubKeyBytes := pubKey.Bytes() + actual := hex.EncodeToString(pubKeyBytes) + expected := "03cd4c4ee9c8e1fae9d12ecf7c96cb3a057b550393f9e82182c4dae1139871682e" + assert.Equal(t, expected, actual) +} +func TestWIFEncode(t *testing.T) { + input := "29bbf53185a973d2e3803cb92908fd08117486d1f2e7bab73ed0d00255511637" + inputBytes, _ := hex.DecodeString(input) + + actual := WIFEncode(inputBytes) + expected := "KxcqV28rGDcpVR3fYg7R9vricLpyZ8oZhopyFLAWuRv7Y8TE9WhW" + assert.Equal(t, expected, actual) +} + +func TestSigning(t *testing.T) { + // These were taken from the rfcPage:https://tools.ietf.org/html/rfc6979#page-33 + // public key: U = xG + //Ux = 60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6 + //Uy = 7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299 + PrivateKey, _ := NewPrivateKeyFromHex("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721") + + data, err := PrivateKey.Sign([]byte("sample")) + if err != nil { + t.Fatal(err) + } + + r := "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716" + s := "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8" + assert.Equal(t, strings.ToLower(r+s), hex.EncodeToString(data)) +} diff --git a/pkg/crypto/publickey/TestHelper/helper.go b/pkg/crypto/publickey/TestHelper/helper.go new file mode 100755 index 000000000..23089ac0e --- /dev/null +++ b/pkg/crypto/publickey/TestHelper/helper.go @@ -0,0 +1,20 @@ +package pubkeytesthelper + +import ( + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/privatekey" +) + +func SignDataWithRandomPrivateKey(data []byte) (bool, error) { + + hashedData, _ := hash.Sha256(data) + + privKey, _ := privatekey.NewPrivateKey() + signedData, err := privKey.Sign(data) + pubKey, _ := privKey.PublicKey() + result := pubKey.Verify(signedData, hashedData.Bytes()) + if err != nil { + return false, err + } + return result, nil +} diff --git a/pkg/crypto/publickey/TestHelper/helper_test.go b/pkg/crypto/publickey/TestHelper/helper_test.go new file mode 100755 index 000000000..68f8c5047 --- /dev/null +++ b/pkg/crypto/publickey/TestHelper/helper_test.go @@ -0,0 +1,34 @@ +package pubkeytesthelper + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/privatekey" + "github.com/stretchr/testify/assert" +) + +func TestPubKeyVerify(t *testing.T) { + actual, err := SignDataWithRandomPrivateKey([]byte("sample")) + + if err != nil { + t.Fatal(err) + } + expected := true + + assert.Equal(t, expected, actual) +} + +func TestWrongPubKey(t *testing.T) { + privKey, _ := privatekey.NewPrivateKey() + sample := []byte("sample") + hashedData, _ := hash.Sha256(sample) + signedData, _ := privKey.Sign(sample) + + secondPrivKey, _ := privatekey.NewPrivateKey() + wrongPubKey, _ := secondPrivKey.PublicKey() + + actual := wrongPubKey.Verify(signedData, hashedData.Bytes()) + expcted := false + assert.Equal(t, expcted, actual) +} diff --git a/pkg/crypto/publickey/publickey.go b/pkg/crypto/publickey/publickey.go new file mode 100755 index 000000000..ac1b15b03 --- /dev/null +++ b/pkg/crypto/publickey/publickey.go @@ -0,0 +1,160 @@ +package publickey + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/crypto/base58" + "github.com/CityOfZion/neo-go/pkg/crypto/elliptic" + "github.com/CityOfZion/neo-go/pkg/crypto/hash" +) + +// PublicKeys is a list of public keys. +type PublicKeys []*PublicKey + +func (keys PublicKeys) Len() int { return len(keys) } +func (keys PublicKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] } +func (keys PublicKeys) Less(i, j int) bool { + + if keys[i].X.Cmp(keys[j].X) == -1 { + return true + } + if keys[i].X.Cmp(keys[j].X) == 1 { + return false + } + if keys[i].X.Cmp(keys[j].X) == 0 { + return false + } + + return keys[i].Y.Cmp(keys[j].Y) == -1 +} + +// PublicKey represents a public key and provides a high level +// API around the ECPoint. +type PublicKey struct { + Curve elliptic.Curve + elliptic.Point +} + +// NewPublicKeyFromString return a public key created from the +// given hex string. +func NewPublicKeyFromString(s string) (*PublicKey, error) { + b, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + + pubKey := &PublicKey{curve, elliptic.Point{}} + + if err := pubKey.DecodeBinary(bytes.NewReader(b)); err != nil { + return nil, err + } + + return pubKey, nil +} + +// Bytes returns the byte array representation of the public key. +func (p *PublicKey) Bytes() []byte { + if p.Curve.IsInfinity(p.Point) { + return []byte{0x00} + } + + var ( + x = p.X.Bytes() + paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...) + prefix = byte(0x03) + ) + + if p.Y.Bit(0) == 0 { + prefix = byte(0x02) + } + + return append([]byte{prefix}, paddedX...) +} +func (p *PublicKey) ToAddress() string { + + publicKeyBytes := p.Bytes() + + publicKeyBytes = append([]byte{0x21}, publicKeyBytes...) // 0x21 = length of pubKey + publicKeyBytes = append(publicKeyBytes, 0xAC) // 0xAC = CheckSig + + hash160PubKey, _ := hash.Hash160(publicKeyBytes) + + versionHash160PubKey := append([]byte{0x17}, hash160PubKey.Bytes()...) + + checksum, _ := hash.Checksum(versionHash160PubKey) + + checkVersionHash160 := append(versionHash160PubKey, checksum...) + + address := base58.Encode(checkVersionHash160) + + return address +} + +// DecodeBinary decodes a PublicKey from the given io.Reader. +func (p *PublicKey) DecodeBinary(r io.Reader) error { + + var prefix uint8 + if err := binary.Read(r, binary.LittleEndian, &prefix); err != nil { + return err + } + + // Infinity + if prefix == 0x00 { + p.Point = elliptic.Point{} + return nil + } + + // Compressed public keys. + if prefix == 0x02 || prefix == 0x03 { + + b := make([]byte, 32) + if err := binary.Read(r, binary.LittleEndian, b); err != nil { + return err + } + + var err error + + p.Point, err = p.Curve.Decompress(new(big.Int).SetBytes(b), uint(prefix&0x1)) + if err != nil { + return err + } + + } else if prefix == 0x04 { + buf := make([]byte, 65) + if err := binary.Read(r, binary.LittleEndian, buf); err != nil { + return err + } + p.X = new(big.Int).SetBytes(buf[1:33]) + p.Y = new(big.Int).SetBytes(buf[33:65]) + } else { + return fmt.Errorf("invalid prefix %d", prefix) + } + + return nil +} + +// EncodeBinary encodes a PublicKey to the given io.Writer. +func (p *PublicKey) EncodeBinary(w io.Writer) error { + return binary.Write(w, binary.LittleEndian, p.Bytes()) +} + +func (p *PublicKey) Verify(signature []byte, hash []byte) bool { + + publicKey := &ecdsa.PublicKey{} + publicKey.Curve = p.Curve + publicKey.X = p.X + publicKey.Y = p.Y + if p.X == nil || p.Y == nil { + return false + } + rBytes := new(big.Int).SetBytes(signature[0:32]) + sBytes := new(big.Int).SetBytes(signature[32:64]) + return ecdsa.Verify(publicKey, hash, rBytes, sBytes) +} diff --git a/pkg/crypto/publickey/publickey_test.go b/pkg/crypto/publickey/publickey_test.go new file mode 100755 index 000000000..0f93adb0c --- /dev/null +++ b/pkg/crypto/publickey/publickey_test.go @@ -0,0 +1,81 @@ +package publickey + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/crypto/elliptic" + "github.com/stretchr/testify/assert" +) + +func TestDecodeFromString(t *testing.T) { + str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c" + pubKey, err := NewPublicKeyFromString(str) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, str, hex.EncodeToString(pubKey.Bytes())) +} + +func TestEncodeDecodeInfinity(t *testing.T) { + + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + + key := &PublicKey{curve, elliptic.Point{}} + buf := new(bytes.Buffer) + assert.Nil(t, key.EncodeBinary(buf)) + assert.Equal(t, 1, buf.Len()) + + keyDecode := &PublicKey{} + assert.Nil(t, keyDecode.DecodeBinary(buf)) + assert.Equal(t, []byte{0x00}, keyDecode.Bytes()) +} + +func TestEncodeDecodePublicKey(t *testing.T) { + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + + for i := 0; i < 4; i++ { + p := &PublicKey{curve, randomECPoint()} + buf := new(bytes.Buffer) + assert.Nil(t, p.EncodeBinary(buf)) + + pDecode := &PublicKey{curve, elliptic.Point{}} + assert.Nil(t, pDecode.DecodeBinary(buf)) + assert.Equal(t, p.X, pDecode.X) + } +} + +func TestPubkeyToAddress(t *testing.T) { + + pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4") + if err != nil { + t.Fatal(err) + } + + actual := pubKey.ToAddress() + expected := "AUpGsNCHzSimeMRVPQfhwrVdiUp8Q2N2Qx" + assert.Equal(t, expected, actual) +} + +func randomECPoint() elliptic.Point { + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + b := make([]byte, curve.N.BitLen()/8+8) + if _, err := io.ReadFull(rand.Reader, b); err != nil { + return elliptic.Point{} + } + + d := new(big.Int).SetBytes(b) + d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1))) + d.Add(d, big.NewInt(1)) + + q := new(big.Int).SetBytes(d.Bytes()) + P1, P2 := curve.ScalarBaseMult(q.Bytes()) + return elliptic.Point{ + X: P1, + Y: P2, + } +} diff --git a/pkg/crypto/rfc6979/LICENSE b/pkg/crypto/rfc6979/LICENSE new file mode 100755 index 000000000..f9835c241 --- /dev/null +++ b/pkg/crypto/rfc6979/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Coda Hale + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/crypto/rfc6979/dsa.go b/pkg/crypto/rfc6979/dsa.go new file mode 100755 index 000000000..f15b3a73a --- /dev/null +++ b/pkg/crypto/rfc6979/dsa.go @@ -0,0 +1,45 @@ +package rfc6979 + +import ( + "crypto/dsa" + "hash" + "math/big" +) + +// SignDSA signs an arbitrary length hash (which should be the result of hashing +// a larger message) using the private key, priv. It returns the signature as a +// pair of integers. +// +// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated +// to the byte-length of the subgroup. This function does not perform that +// truncation itself. +func SignDSA(priv *dsa.PrivateKey, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) { + n := priv.Q.BitLen() + if n&7 != 0 { + err = dsa.ErrInvalidPublicKey + return + } + n >>= 3 + + generateSecret(priv.Q, priv.X, alg, hash, func(k *big.Int) bool { + inv := new(big.Int).ModInverse(k, priv.Q) + r = new(big.Int).Exp(priv.G, k, priv.P) + r.Mod(r, priv.Q) + + if r.Sign() == 0 { + return false + } + + z := new(big.Int).SetBytes(hash) + + s = new(big.Int).Mul(priv.X, r) + s.Add(s, z) + s.Mod(s, priv.Q) + s.Mul(s, inv) + s.Mod(s, priv.Q) + + return s.Sign() != 0 + }) + + return +} diff --git a/pkg/crypto/rfc6979/dsa_test.go b/pkg/crypto/rfc6979/dsa_test.go new file mode 100755 index 000000000..6b0f4e829 --- /dev/null +++ b/pkg/crypto/rfc6979/dsa_test.go @@ -0,0 +1,270 @@ +package rfc6979_test + +import ( + "crypto/dsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "hash" + "math/big" + "testing" + + "github.com/o3labs/neo-utils/neoutils/rfc6979" +) + +type dsaFixture struct { + name string + key *dsaKey + alg func() hash.Hash + message string + r, s string +} + +type dsaKey struct { + key *dsa.PrivateKey + subgroup int +} + +var dsa1024 = &dsaKey{ + key: &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: dsaLoadInt("86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED8873ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779"), + Q: dsaLoadInt("996F967F6C8E388D9E28D01E205FBA957A5698B1"), + G: dsaLoadInt("07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA417BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD"), + }, + Y: dsaLoadInt("5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F65392195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E682F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B"), + }, + X: dsaLoadInt("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7"), + }, + subgroup: 160, +} + +var dsa2048 = &dsaKey{ + key: &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: dsaLoadInt("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B"), + Q: dsaLoadInt("F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F"), + G: dsaLoadInt("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"), + }, + Y: dsaLoadInt("667098C654426C78D7F8201EAC6C203EF030D43605032C2F1FA937E5237DBD949F34A0A2564FE126DC8B715C5141802CE0979C8246463C40E6B6BDAA2513FA611728716C2E4FD53BC95B89E69949D96512E873B9C8F8DFD499CC312882561ADECB31F658E934C0C197F2C4D96B05CBAD67381E7B768891E4DA3843D24D94CDFB5126E9B8BF21E8358EE0E0A30EF13FD6A664C0DCE3731F7FB49A4845A4FD8254687972A2D382599C9BAC4E0ED7998193078913032558134976410B89D2C171D123AC35FD977219597AA7D15C1A9A428E59194F75C721EBCBCFAE44696A499AFA74E04299F132026601638CB87AB79190D4A0986315DA8EEC6561C938996BEADF"), + }, + X: dsaLoadInt("69C7548C21D0DFEA6B9A51C9EAD4E27C33D3B3F180316E5BCAB92C933F0E4DBC"), + }, + subgroup: 256, +} + +var dsaFixtures = []dsaFixture{ + // DSA, 1024 Bits + // https://tools.ietf.org/html/rfc6979#appendix-A.2.1 + dsaFixture{ + name: "1024/SHA-1 #1", + key: dsa1024, + alg: sha1.New, + message: "sample", + r: "2E1A0C2562B2912CAAF89186FB0F42001585DA55", + s: "29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5", + }, + dsaFixture{ + name: "1024/SHA-224 #1", + key: dsa1024, + alg: sha256.New224, + message: "sample", + r: "4BC3B686AEA70145856814A6F1BB53346F02101E", + s: "410697B92295D994D21EDD2F4ADA85566F6F94C1", + }, + dsaFixture{ + name: "1024/SHA-256 #1", + key: dsa1024, + alg: sha256.New, + message: "sample", + r: "81F2F5850BE5BC123C43F71A3033E9384611C545", + s: "4CDD914B65EB6C66A8AAAD27299BEE6B035F5E89", + }, + dsaFixture{ + name: "1024/SHA-384 #1", + key: dsa1024, + alg: sha512.New384, + message: "sample", + r: "07F2108557EE0E3921BC1774F1CA9B410B4CE65A", + s: "54DF70456C86FAC10FAB47C1949AB83F2C6F7595", + }, + dsaFixture{ + name: "1024/SHA-512 #1", + key: dsa1024, + alg: sha512.New, + message: "sample", + r: "16C3491F9B8C3FBBDD5E7A7B667057F0D8EE8E1B", + s: "02C36A127A7B89EDBB72E4FFBC71DABC7D4FC69C", + }, + dsaFixture{ + name: "1024/SHA-1 #2", + key: dsa1024, + alg: sha1.New, + message: "test", + r: "42AB2052FD43E123F0607F115052A67DCD9C5C77", + s: "183916B0230D45B9931491D4C6B0BD2FB4AAF088", + }, + dsaFixture{ + name: "1024/SHA-224 #2", + key: dsa1024, + alg: sha256.New224, + message: "test", + r: "6868E9964E36C1689F6037F91F28D5F2C30610F2", + s: "49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F", + }, + dsaFixture{ + name: "1024/SHA-256 #2", + key: dsa1024, + alg: sha256.New, + message: "test", + r: "22518C127299B0F6FDC9872B282B9E70D0790812", + s: "6837EC18F150D55DE95B5E29BE7AF5D01E4FE160", + }, + dsaFixture{ + name: "1024/SHA-384 #2", + key: dsa1024, + alg: sha512.New384, + message: "test", + r: "854CF929B58D73C3CBFDC421E8D5430CD6DB5E66", + s: "91D0E0F53E22F898D158380676A871A157CDA622", + }, + dsaFixture{ + name: "1024/SHA-512 #2", + key: dsa1024, + alg: sha512.New, + message: "test", + r: "8EA47E475BA8AC6F2D821DA3BD212D11A3DEB9A0", + s: "7C670C7AD72B6C050C109E1790008097125433E8", + }, + + // DSA, 2048 Bits + // https://tools.ietf.org/html/rfc6979#appendix-A.2.2 + dsaFixture{ + name: "2048/SHA-1 #1", + key: dsa2048, + alg: sha1.New, + message: "sample", + r: "3A1B2DBD7489D6ED7E608FD036C83AF396E290DBD602408E8677DAABD6E7445A", + s: "D26FCBA19FA3E3058FFC02CA1596CDBB6E0D20CB37B06054F7E36DED0CDBBCCF", + }, + dsaFixture{ + name: "2048/SHA-224 #1", + key: dsa2048, + alg: sha256.New224, + message: "sample", + r: "DC9F4DEADA8D8FF588E98FED0AB690FFCE858DC8C79376450EB6B76C24537E2C", + s: "A65A9C3BC7BABE286B195D5DA68616DA8D47FA0097F36DD19F517327DC848CEC", + }, + dsaFixture{ + name: "2048/SHA-256 #1", + key: dsa2048, + alg: sha256.New, + message: "sample", + r: "EACE8BDBBE353C432A795D9EC556C6D021F7A03F42C36E9BC87E4AC7932CC809", + s: "7081E175455F9247B812B74583E9E94F9EA79BD640DC962533B0680793A38D53", + }, + dsaFixture{ + name: "2048/SHA-384 #1", + key: dsa2048, + alg: sha512.New384, + message: "sample", + r: "B2DA945E91858834FD9BF616EBAC151EDBC4B45D27D0DD4A7F6A22739F45C00B", + s: "19048B63D9FD6BCA1D9BAE3664E1BCB97F7276C306130969F63F38FA8319021B", + }, + dsaFixture{ + name: "2048/SHA-512 #1", + key: dsa2048, + alg: sha512.New, + message: "sample", + r: "2016ED092DC5FB669B8EFB3D1F31A91EECB199879BE0CF78F02BA062CB4C942E", + s: "D0C76F84B5F091E141572A639A4FB8C230807EEA7D55C8A154A224400AFF2351", + }, + dsaFixture{ + name: "2048/SHA-1 #2", + key: dsa2048, + alg: sha1.New, + message: "test", + r: "C18270A93CFC6063F57A4DFA86024F700D980E4CF4E2CB65A504397273D98EA0", + s: "414F22E5F31A8B6D33295C7539C1C1BA3A6160D7D68D50AC0D3A5BEAC2884FAA", + }, + dsaFixture{ + name: "2048/SHA-224 #2", + key: dsa2048, + alg: sha256.New224, + message: "test", + r: "272ABA31572F6CC55E30BF616B7A265312018DD325BE031BE0CC82AA17870EA3", + s: "E9CC286A52CCE201586722D36D1E917EB96A4EBDB47932F9576AC645B3A60806", + }, + dsaFixture{ + name: "2048/SHA-256 #2", + key: dsa2048, + alg: sha256.New, + message: "test", + r: "8190012A1969F9957D56FCCAAD223186F423398D58EF5B3CEFD5A4146A4476F0", + s: "7452A53F7075D417B4B013B278D1BB8BBD21863F5E7B1CEE679CF2188E1AB19E", + }, + dsaFixture{ + name: "2048/SHA-384 #2", + key: dsa2048, + alg: sha512.New384, + message: "test", + r: "239E66DDBE8F8C230A3D071D601B6FFBDFB5901F94D444C6AF56F732BEB954BE", + s: "6BD737513D5E72FE85D1C750E0F73921FE299B945AAD1C802F15C26A43D34961", + }, + dsaFixture{ + name: "2048/SHA-512 #2", + key: dsa2048, + alg: sha512.New, + message: "test", + r: "89EC4BB1400ECCFF8E7D9AA515CD1DE7803F2DAFF09693EE7FD1353E90A68307", + s: "C9F0BDABCC0D880BB137A994CC7F3980CE91CC10FAF529FC46565B15CEA854E1", + }, +} + +func TestDSASignatures(t *testing.T) { + for _, f := range dsaFixtures { + testDsaFixture(&f, t) + } +} + +func testDsaFixture(f *dsaFixture, t *testing.T) { + t.Logf("Testing %s", f.name) + + h := f.alg() + h.Write([]byte(f.message)) + digest := h.Sum(nil) + + g := f.key.subgroup / 8 + if len(digest) > g { + digest = digest[0:g] + } + + r, s, err := rfc6979.SignDSA(f.key.key, digest, f.alg) + if err != nil { + t.Error(err) + return + } + + expectedR := dsaLoadInt(f.r) + expectedS := dsaLoadInt(f.s) + + if r.Cmp(expectedR) != 0 { + t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r) + } + + if s.Cmp(expectedS) != 0 { + t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s) + } +} + +func dsaLoadInt(s string) *big.Int { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + + return new(big.Int).SetBytes(b) +} diff --git a/pkg/crypto/rfc6979/ecdsa.go b/pkg/crypto/rfc6979/ecdsa.go new file mode 100755 index 000000000..8cf10a79b --- /dev/null +++ b/pkg/crypto/rfc6979/ecdsa.go @@ -0,0 +1,59 @@ +package rfc6979 + +import ( + "hash" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/crypto/elliptic" +) + +// SignECDSA signs an arbitrary length hash (which should be the result of +// hashing a larger message) using the private key, priv. It returns the +// signature as a pair of integers. +// +// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated +// to the byte-length of the subgroup. This function does not perform that +// truncation itself. +func SignECDSA(curve elliptic.Curve, priv []byte, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) { + c := curve + N := c.N + D := new(big.Int) + D.SetBytes(priv) + generateSecret(N, D, alg, hash, func(k *big.Int) bool { + + inv := new(big.Int).ModInverse(k, N) + + r, _ = curve.ScalarBaseMult(k.Bytes()) + r.Mod(r, N) + + if r.Sign() == 0 { + return false + } + + e := hashToInt(hash, c) + s = new(big.Int).Mul(D, r) + s.Add(s, e) + s.Mul(s, inv) + s.Mod(s, N) + + return s.Sign() != 0 + }) + + return +} + +// copied from crypto/ecdsa +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.N.BitLen() + orderBytes := (orderBits + 7) / 8 + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} diff --git a/pkg/crypto/rfc6979/ecdsa_test.go b/pkg/crypto/rfc6979/ecdsa_test.go new file mode 100755 index 000000000..f6ef52420 --- /dev/null +++ b/pkg/crypto/rfc6979/ecdsa_test.go @@ -0,0 +1,447 @@ +package rfc6979_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "hash" + "math/big" + "testing" + + "github.com/o3labs/neo-utils/neoutils/rfc6979" +) + +type ecdsaFixture struct { + name string + key *ecdsaKey + alg func() hash.Hash + message string + r, s string +} + +type ecdsaKey struct { + key *ecdsa.PrivateKey + subgroup int +} + +var p224 = &ecdsaKey{ + key: &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P224(), + X: ecdsaLoadInt("00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C"), + Y: ecdsaLoadInt("EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A"), + }, + D: ecdsaLoadInt("F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1"), + }, + subgroup: 224, +} + +var p256 = &ecdsaKey{ + key: &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: ecdsaLoadInt("60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"), + Y: ecdsaLoadInt("7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299"), + }, + D: ecdsaLoadInt("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"), + }, + subgroup: 256, +} + +var p384 = &ecdsaKey{ + key: &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: ecdsaLoadInt("EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13"), + Y: ecdsaLoadInt("8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720"), + }, + D: ecdsaLoadInt("6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5"), + }, + subgroup: 384, +} + +var p521 = &ecdsaKey{ + key: &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P521(), + X: ecdsaLoadInt("1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4"), + Y: ecdsaLoadInt("0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5"), + }, + D: ecdsaLoadInt("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538"), + }, + subgroup: 521, +} + +var fixtures = []ecdsaFixture{ + // ECDSA, 224 Bits (Prime Field) + // https://tools.ietf.org/html/rfc6979#appendix-A.2.4 + ecdsaFixture{ + name: "P224/SHA-1 #1", + key: p224, + alg: sha1.New, + message: "sample", + r: "22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC", + s: "66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D69", + }, + ecdsaFixture{ + name: "P224/SHA-224 #1", + key: p224, + alg: sha256.New224, + message: "sample", + r: "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E", + s: "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC", + }, + ecdsaFixture{ + name: "P224/SHA-256 #1", + key: p224, + alg: sha256.New, + message: "sample", + r: "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA", + s: "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101", + }, + ecdsaFixture{ + name: "P224/SHA-384 #1", + key: p224, + alg: sha512.New384, + message: "sample", + r: "0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953", + s: "830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1D", + }, + ecdsaFixture{ + name: "P224/SHA-512 #1", + key: p224, + alg: sha512.New, + message: "sample", + r: "074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397", + s: "A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB084", + }, + ecdsaFixture{ + name: "P224/SHA-1 #2", + key: p224, + alg: sha1.New, + message: "test", + r: "DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C", + s: "95987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD2", + }, + ecdsaFixture{ + name: "P224/SHA-224 #2", + key: p224, + alg: sha256.New224, + message: "test", + r: "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019", + s: "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4", + }, + ecdsaFixture{ + name: "P224/SHA-256 #2", + key: p224, + alg: sha256.New, + message: "test", + r: "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6", + s: "178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD", + }, + ecdsaFixture{ + name: "P224/SHA-384 #2", + key: p224, + alg: sha512.New384, + message: "test", + r: "389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4", + s: "414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAB", + }, + ecdsaFixture{ + name: "P224/SHA-512 #2", + key: p224, + alg: sha512.New, + message: "test", + r: "049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C", + s: "077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFF", + }, + // ECDSA, 256 Bits (Prime Field) + // https://tools.ietf.org/html/rfc6979#appendix-A.2.5 + ecdsaFixture{ + name: "P256/SHA-1 #1", + key: p256, + alg: sha1.New, + message: "sample", + r: "61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32", + s: "6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB", + }, + ecdsaFixture{ + name: "P256/SHA-224 #1", + key: p256, + alg: sha256.New224, + message: "sample", + r: "53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F", + s: "B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C", + }, + ecdsaFixture{ + name: "P256/SHA-256 #1", + key: p256, + alg: sha256.New, + message: "sample", + r: "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716", + s: "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8", + }, + ecdsaFixture{ + name: "P256/SHA-384 #1", + key: p256, + alg: sha512.New384, + message: "sample", + r: "0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719", + s: "4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954", + }, + ecdsaFixture{ + name: "P256/SHA-512 #1", + key: p256, + alg: sha512.New, + message: "sample", + r: "8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00", + s: "2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE", + }, + ecdsaFixture{ + name: "P256/SHA-1 #2", + key: p256, + alg: sha1.New, + message: "test", + r: "0CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89", + s: "01B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1", + }, + ecdsaFixture{ + name: "P256/SHA-224 #2", + key: p256, + alg: sha256.New224, + message: "test", + r: "C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692", + s: "C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D", + }, + ecdsaFixture{ + name: "P256/SHA-256 #2", + key: p256, + alg: sha256.New, + message: "test", + r: "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367", + s: "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083", + }, + ecdsaFixture{ + name: "P256/SHA-384 #2", + key: p256, + alg: sha512.New384, + message: "test", + r: "83910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB6", + s: "8DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C", + }, + ecdsaFixture{ + name: "P256/SHA-512 #2", + key: p256, + alg: sha512.New, + message: "test", + r: "461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04", + s: "39AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55", + }, + // ECDSA, 384 Bits (Prime Field) + // https://tools.ietf.org/html/rfc6979#appendix-A.2.6 + ecdsaFixture{ + name: "P384/SHA-1 #1", + key: p384, + alg: sha1.New, + message: "sample", + r: "EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2", + s: "A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443", + }, + ecdsaFixture{ + name: "P384/SHA-224 #1", + key: p384, + alg: sha256.New224, + message: "sample", + r: "42356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE060122", + s: "9DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D", + }, + ecdsaFixture{ + name: "P384/SHA-256 #1", + key: p384, + alg: sha256.New, + message: "sample", + r: "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD", + s: "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0", + }, + ecdsaFixture{ + name: "P384/SHA-384 #1", + key: p384, + alg: sha512.New384, + message: "sample", + r: "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46", + s: "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8", + }, + ecdsaFixture{ + name: "P384/SHA-512 #1", + key: p384, + alg: sha512.New, + message: "sample", + r: "ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD7882433709", + s: "512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5", + }, + ecdsaFixture{ + name: "P384/SHA-1 #2", + key: p384, + alg: sha1.New, + message: "test", + r: "4BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7", + s: "D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382282", + }, + ecdsaFixture{ + name: "P384/SHA-224 #2", + key: p384, + alg: sha256.New224, + message: "test", + r: "E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72", + s: "07041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66", + }, + ecdsaFixture{ + name: "P384/SHA-256 #2", + key: p384, + alg: sha256.New, + message: "test", + r: "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B", + s: "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265", + }, + ecdsaFixture{ + name: "P384/SHA-384 #2", + key: p384, + alg: sha512.New384, + message: "test", + r: "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB", + s: "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5", + }, + ecdsaFixture{ + name: "P384/SHA-512 #2", + key: p384, + alg: sha512.New, + message: "test", + r: "A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277", + s: "976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736", + }, + // ECDSA, 521 Bits (Prime Field) + // https://tools.ietf.org/html/rfc6979#appendix-A.2.7 + ecdsaFixture{ + name: "P521/SHA-1 #1", + key: p521, + alg: sha1.New, + message: "sample", + r: "0343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D", + s: "0E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16", + }, + ecdsaFixture{ + name: "P521/SHA-224 #1", + key: p521, + alg: sha256.New224, + message: "sample", + r: "1776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E", + s: "050CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F", + }, + ecdsaFixture{ + name: "P521/SHA-256 #1", + key: p521, + alg: sha256.New, + message: "sample", + r: "1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7", + s: "04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC", + }, + ecdsaFixture{ + name: "P521/SHA-384 #1", + key: p521, + alg: sha512.New384, + message: "sample", + r: "1EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451", + s: "1F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61", + }, + ecdsaFixture{ + name: "P521/SHA-512 #1", + key: p521, + alg: sha512.New, + message: "sample", + r: "0C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA", + s: "0617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A", + }, + ecdsaFixture{ + name: "P521/SHA-1 #2", + key: p521, + alg: sha1.New, + message: "test", + r: "13BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367", + s: "1E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF", + }, + ecdsaFixture{ + name: "P521/SHA-224 #2", + key: p521, + alg: sha256.New224, + message: "test", + r: "1C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB", + s: "177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4", + }, + ecdsaFixture{ + name: "P521/SHA-256 #2", + key: p521, + alg: sha256.New, + message: "test", + r: "00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8", + s: "0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86", + }, + ecdsaFixture{ + name: "P521/SHA-384 #2", + key: p521, + alg: sha512.New384, + message: "test", + r: "14BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C", + s: "133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979", + }, + ecdsaFixture{ + name: "P521/SHA-512 #2", + key: p521, + alg: sha512.New, + message: "test", + r: "13E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D", + s: "1FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3", + }, +} + +func TestECDSA(t *testing.T) { + for _, f := range fixtures { + testEcsaFixture(&f, t) + } +} + +func ecdsaLoadInt(s string) (n *big.Int) { + n, _ = new(big.Int).SetString(s, 16) + return +} + +func testEcsaFixture(f *ecdsaFixture, t *testing.T) { + t.Logf("Testing %s", f.name) + + h := f.alg() + h.Write([]byte(f.message)) + digest := h.Sum(nil) + + g := f.key.subgroup / 8 + if len(digest) > g { + digest = digest[0:g] + } + + r, s, err := rfc6979.SignECDSA(f.key.key, digest, f.alg) + if err != nil { + t.Error(err) + return + } + + expectedR := ecdsaLoadInt(f.r) + expectedS := ecdsaLoadInt(f.s) + + if r.Cmp(expectedR) != 0 { + t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r) + } + + if s.Cmp(expectedS) != 0 { + t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s) + } +} diff --git a/pkg/crypto/rfc6979/example_test.go b/pkg/crypto/rfc6979/example_test.go new file mode 100755 index 000000000..29693c272 --- /dev/null +++ b/pkg/crypto/rfc6979/example_test.go @@ -0,0 +1,76 @@ +package rfc6979 + +import ( + "crypto/dsa" + "crypto/ecdsa" + + "crypto/rand" + "crypto/sha1" + "crypto/sha512" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/crypto/elliptic" +) + +// Generates a 521-bit ECDSA key, uses SHA-512 to sign a message, then verifies +// it. +func ExampleSignECDSA() { + // Generate a key pair. + // You need a high-quality PRNG for this. + curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) + k, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + fmt.Println(err) + return + } + + // Hash a message. + alg := sha512.New() + alg.Write([]byte("I am a potato.")) + hash := alg.Sum(nil) + + // Sign the message. You don't need a PRNG for this. + + r, s, err := SignECDSA(curve, k.D.Bytes(), hash, sha512.New) + if err != nil { + fmt.Println(err) + return + } + + if !ecdsa.Verify(&k.PublicKey, hash, r, s) { + fmt.Println("Invalid signature!") + } +} + +// Generates a 1024-bit DSA key, uses SHA-1 to sign a message, then verifies it. +func ExampleSignDSA() { + // Here I'm generating some DSA params, but you should really pre-generate + // these and re-use them, since this takes a long time and isn't necessary. + k := new(dsa.PrivateKey) + dsa.GenerateParameters(&k.Parameters, rand.Reader, dsa.L1024N160) + + // Generate a key pair. + // You need a high-quality PRNG for this. + err := dsa.GenerateKey(k, rand.Reader) + if err != nil { + fmt.Println(err) + return + } + + // Hash a message. + alg := sha1.New() + alg.Write([]byte("I am a potato.")) + hash := alg.Sum(nil) + + // Sign the message. You don't need a PRNG for this. + r, s, err := SignDSA(k, hash, sha1.New) + if err != nil { + fmt.Println(err) + return + } + + if !dsa.Verify(&k.PublicKey, hash, r, s) { + fmt.Println("Invalid signature!") + } + +} diff --git a/pkg/crypto/rfc6979/rfc6979.go b/pkg/crypto/rfc6979/rfc6979.go new file mode 100755 index 000000000..cb7b4b0d8 --- /dev/null +++ b/pkg/crypto/rfc6979/rfc6979.go @@ -0,0 +1,119 @@ +/* +Package rfc6979 is an implementation of RFC 6979's deterministic DSA. + + Such signatures are compatible with standard Digital Signature Algorithm + (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA) digital + signatures and can be processed with unmodified verifiers, which need not be + aware of the procedure described therein. Deterministic signatures retain + the cryptographic security features associated with digital signatures but + can be more easily implemented in various environments, since they do not + need access to a source of high-quality randomness. + +(https://tools.ietf.org/html/rfc6979) + +Provides functions similar to crypto/dsa and crypto/ecdsa. +*/ +package rfc6979 + +import ( + "bytes" + "crypto/hmac" + "hash" + "math/big" +) + +// mac returns an HMAC of the given key and message. +func mac(alg func() hash.Hash, k, m, buf []byte) []byte { + h := hmac.New(alg, k) + h.Write(m) + return h.Sum(buf[:0]) +} + +// https://tools.ietf.org/html/rfc6979#section-2.3.2 +func bits2int(in []byte, qlen int) *big.Int { + vlen := len(in) * 8 + v := new(big.Int).SetBytes(in) + if vlen > qlen { + v = new(big.Int).Rsh(v, uint(vlen-qlen)) + } + return v +} + +// https://tools.ietf.org/html/rfc6979#section-2.3.3 +func int2octets(v *big.Int, rolen int) []byte { + out := v.Bytes() + + // pad with zeros if it's too short + if len(out) < rolen { + out2 := make([]byte, rolen) + copy(out2[rolen-len(out):], out) + return out2 + } + + // drop most significant bytes if it's too long + if len(out) > rolen { + out2 := make([]byte, rolen) + copy(out2, out[len(out)-rolen:]) + return out2 + } + + return out +} + +// https://tools.ietf.org/html/rfc6979#section-2.3.4 +func bits2octets(in []byte, q *big.Int, qlen, rolen int) []byte { + z1 := bits2int(in, qlen) + z2 := new(big.Int).Sub(z1, q) + if z2.Sign() < 0 { + return int2octets(z1, rolen) + } + return int2octets(z2, rolen) +} + +var one = big.NewInt(1) + +// https://tools.ietf.org/html/rfc6979#section-3.2 +func generateSecret(q, x *big.Int, alg func() hash.Hash, hash []byte, test func(*big.Int) bool) { + qlen := q.BitLen() + holen := alg().Size() + rolen := (qlen + 7) >> 3 + bx := append(int2octets(x, rolen), bits2octets(hash, q, qlen, rolen)...) + + // Step B + v := bytes.Repeat([]byte{0x01}, holen) + + // Step C + k := bytes.Repeat([]byte{0x00}, holen) + + // Step D + k = mac(alg, k, append(append(v, 0x00), bx...), k) + + // Step E + v = mac(alg, k, v, v) + + // Step F + k = mac(alg, k, append(append(v, 0x01), bx...), k) + + // Step G + v = mac(alg, k, v, v) + + // Step H + for { + // Step H1 + var t []byte + + // Step H2 + for len(t) < qlen/8 { + v = mac(alg, k, v, v) + t = append(t, v...) + } + + // Step H3 + secret := bits2int(t, qlen) + if secret.Cmp(one) >= 0 && secret.Cmp(q) < 0 && test(secret) { + return + } + k = mac(alg, k, append(v, 0x00), k) + v = mac(alg, k, v, v) + } +} diff --git a/pkg/crypto/rfc6979/rfc6979_test.go b/pkg/crypto/rfc6979/rfc6979_test.go new file mode 100755 index 000000000..cc9138653 --- /dev/null +++ b/pkg/crypto/rfc6979/rfc6979_test.go @@ -0,0 +1,28 @@ +package rfc6979 + +import ( + "crypto/sha256" + "encoding/hex" + "math/big" + "testing" +) + +// https://tools.ietf.org/html/rfc6979#appendix-A.1 +func TestGenerateSecret(t *testing.T) { + q, _ := new(big.Int).SetString("4000000000000000000020108A2E0CC0D99F8A5EF", 16) + + x, _ := new(big.Int).SetString("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16) + + hash, _ := hex.DecodeString("AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF") + + expected, _ := new(big.Int).SetString("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16) + var actual *big.Int + generateSecret(q, x, sha256.New, hash, func(k *big.Int) bool { + actual = k + return true + }) + + if actual.Cmp(expected) != 0 { + t.Errorf("Expected %x, got %x", expected, actual) + } +} diff --git a/pkg/database/leveldb.go b/pkg/database/leveldb.go new file mode 100644 index 000000000..c079fa6a0 --- /dev/null +++ b/pkg/database/leveldb.go @@ -0,0 +1,143 @@ +package database + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" +) + +type LDB struct { + db *leveldb.DB + path string +} + +type Database interface { + Has(key []byte) (bool, error) + Put(key []byte, value []byte) error + Get(key []byte) ([]byte, error) + Delete(key []byte) error + Close() error +} + +var ( + // TX, HEADER AND UTXO are the prefixes for the db + TX = []byte("TX") + HEADER = []byte("HEADER") + LATESTHEADER = []byte("LH") + UTXO = []byte("UTXO") +) + +func New(path string) *LDB { + db, err := leveldb.OpenFile(path, nil) + + if _, corrupted := err.(*errors.ErrCorrupted); corrupted { + db, err = leveldb.RecoverFile(path, nil) + } + + if err != nil { + return nil + } + + return &LDB{ + db, + path, + } +} + +func (l *LDB) Has(key []byte) (bool, error) { + return l.db.Has(key, nil) +} + +func (l *LDB) Put(key []byte, value []byte) error { + return l.db.Put(key, value, nil) +} +func (l *LDB) Get(key []byte) ([]byte, error) { + return l.db.Get(key, nil) +} +func (l *LDB) Delete(key []byte) error { + return l.db.Delete(key, nil) +} +func (l *LDB) Close() error { + return l.db.Close() +} + +func (l *LDB) AddHeader(header *payload.BlockBase) error { + + table := NewTable(l, HEADER) + + byt, err := header.Bytes() + if err != nil { + fmt.Println("Could not Get bytes from decoded BlockBase") + return nil + } + + fmt.Println("Adding Header, This should be batched!!!!") + + // This is the main mapping + //Key: HEADER+BLOCKHASH Value: contents of blockhash + key := header.Hash.Bytes() + err = table.Put(key, byt) + if err != nil { + fmt.Println("Error trying to add the original mapping into the DB for Header. Mapping is [Header]+[Hash]") + return err + } + + // This is the secondary mapping + // Key: HEADER + BLOCKHEIGHT Value: blockhash + + bh := uint32ToBytes(header.Index) + key = []byte(bh) + err = table.Put(key, header.Hash.Bytes()) + if err != nil { + return err + } + // This is the third mapping + // WARNING: This assumes that headers are adding in order. + return table.Put(LATESTHEADER, header.Hash.Bytes()) +} + +func (l *LDB) AddTransactions(blockhash util.Uint256, txs []transaction.Transactioner) error { + + // SHOULD BE DONE IN BATCH!!!! + for i, tx := range txs { + buf := new(bytes.Buffer) + fmt.Println(tx.ID()) + tx.Encode(buf) + txByt := buf.Bytes() + txhash, err := tx.ID() + if err != nil { + fmt.Println("Error adding transaction with bytes", txByt) + return err + } + // This is the original mapping + // Key: [TX] + TXHASH + key := append(TX, txhash.Bytes()...) + l.Put(key, txByt) + + // This is the index + // Key: [TX] + BLOCKHASH + I <- i is the incrementer from the for loop + //Value : TXHASH + key = append(TX, blockhash.Bytes()...) + key = append(key, uint32ToBytes(uint32(i))...) + + err = l.Put(key, txhash.Bytes()) + if err != nil { + fmt.Println("Error could not add tx index into db") + return err + } + } + return nil +} + +// BigEndian +func uint32ToBytes(h uint32) []byte { + a := make([]byte, 4) + binary.BigEndian.PutUint32(a, h) + return a +} diff --git a/pkg/database/leveldb_test.go b/pkg/database/leveldb_test.go new file mode 100644 index 000000000..0991682ee --- /dev/null +++ b/pkg/database/leveldb_test.go @@ -0,0 +1,80 @@ +package database_test + +import ( + "os" + "testing" + + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/stretchr/testify/assert" + "github.com/syndtr/goleveldb/leveldb/errors" +) + +const path = "temp" + +func cleanup(db *database.LDB) { + db.Close() + os.RemoveAll(path) +} +func TestDBCreate(t *testing.T) { + db := database.New(path) + assert.NotEqual(t, nil, db) + cleanup(db) +} +func TestPutGet(t *testing.T) { + db := database.New(path) + + key := []byte("Hello") + value := []byte("World") + + err := db.Put(key, value) + assert.Equal(t, nil, err) + + res, err := db.Get(key) + assert.Equal(t, nil, err) + assert.Equal(t, value, res) + cleanup(db) +} +func TestPutDelete(t *testing.T) { + + db := database.New(path) + + key := []byte("Hello") + value := []byte("World") + + err := db.Put(key, value) + + err = db.Delete(key) + assert.Equal(t, nil, err) + + res, err := db.Get(key) + + assert.Equal(t, errors.ErrNotFound, err) + assert.Equal(t, res, []byte{}) + cleanup(db) +} + +func TestHas(t *testing.T) { + db := database.New("temp") + + res, err := db.Has([]byte("NotExist")) + assert.Equal(t, res, false) + assert.Equal(t, err, nil) + + key := []byte("Hello") + value := []byte("World") + + err = db.Put(key, value) + assert.Equal(t, nil, err) + + res, err = db.Has(key) + assert.Equal(t, res, true) + assert.Equal(t, err, nil) + cleanup(db) + +} +func TestDBClose(t *testing.T) { + db := database.New("temp") + err := db.Close() + assert.Equal(t, nil, err) + cleanup(db) +} diff --git a/pkg/database/table.go b/pkg/database/table.go new file mode 100644 index 000000000..549e50229 --- /dev/null +++ b/pkg/database/table.go @@ -0,0 +1,36 @@ +package database + +//Table is an abstract datastructure built on +// top of a db +type Table struct { + prefix []byte + db Database +} + +func NewTable(db Database, prefix []byte) *Table { + return &Table{ + prefix, + db, + } +} + +func (t *Table) Has(key []byte) (bool, error) { + key = append(t.prefix, key...) + return t.db.Has(key) +} + +func (t *Table) Put(key []byte, value []byte) error { + key = append(t.prefix, key...) + return t.db.Put(key, value) +} +func (t *Table) Get(key []byte) ([]byte, error) { + key = append(t.prefix, key...) + return t.db.Get(key) +} +func (t *Table) Delete(key []byte) error { + key = append(t.prefix, key...) + return t.db.Delete(key) +} +func (t *Table) Close() error { + return nil +} diff --git a/pkg/mempool/config.go b/pkg/mempool/config.go new file mode 100644 index 000000000..19f529d0e --- /dev/null +++ b/pkg/mempool/config.go @@ -0,0 +1,31 @@ +package mempool + +import "time" + +type Config struct { + + // This is the maximum amount + // of transactions that we will allow in the mempool + MaxNumOfTX uint64 + + // FreeTX defines the maximum amount of free txs that can be in the mempool at one time + // Default is 20 + FreeTX uint32 + + // MinTXFee is a number in Fixed8 format. If set at 1GAS, minTXFee would equal 1e8 + // The mineTXFee is used to set the floor, it defaults to zero meaning we will allow all transactions + // with a fee of 0 or more + MinTXFee uint64 + + // MaxTXSize is the maximum number of bytes a tx can have to be entered into the pool + MaxTXSize uint64 + + // TXTTL is the duration to which we should keep an item in the mempool before removing it + // HMM: Should this be amount of blocks instead? For when blocks take time a long time + // to process? + TXTTL time.Duration + + // SigLimit is the maximum amount of signatures + // that we will allow a tx to have, default will be 20 + SigLimit uint8 +} diff --git a/pkg/mempool/mempool.go b/pkg/mempool/mempool.go new file mode 100644 index 000000000..07c1adb9c --- /dev/null +++ b/pkg/mempool/mempool.go @@ -0,0 +1,138 @@ +package mempool + +import ( + "errors" + "fmt" + "sync" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + ErrMemPoolFull = errors.New("mempool is currently full") + ErrMempoolEmpty = errors.New("There are no TXs in the mempool") + ErrTXTooBig = errors.New("TX has exceed the maximum threshold") + ErrTXTooManyWitnesses = errors.New("Too many witness scripts") + ErrFeeTooLow = errors.New("Fee for transaction too low") + ErrDuplicateTX = errors.New("TX Already in pool") +) + +type Mempool struct { + mtx sync.RWMutex + pool map[util.Uint256]*TX + + cfg Config +} + +func New(cfg Config) *Mempool { + mem := &Mempool{ + sync.RWMutex{}, + make(map[util.Uint256]*TX, 200), + cfg, + } + + return mem +} +func (m *Mempool) AddTransaction(trans transaction.Transactioner) error { + + hash, err := trans.ID() + if err != nil { + return err + } + + // check if tx already in pool + if m.Exists(hash) { + return ErrDuplicateTX + } + + m.mtx.Lock() + defer m.mtx.Unlock() + + if m.cfg.MaxNumOfTX == uint64(len(m.pool)) { + return ErrMemPoolFull + } + + // TODO:Check for double spend from blockchain itself + + // create tx descriptor + tx := Descriptor(trans) + + // check TX size + if tx.Size > m.cfg.MaxTXSize { + return ErrTXTooBig + } + + // check witness length + if tx.NumWitness > m.cfg.SigLimit { + return ErrTXTooManyWitnesses + } + + // TODO: check witness data is good -- Add method to take the Witness and tx return true or false.(blockchain) + + //check fee is over minimum cnfigured + if tx.Fee < m.cfg.MinTXFee { + return ErrFeeTooLow + } + + // Add into pool + m.pool[hash] = tx + + return nil +} + +// RemoveTransaction will remove a transaction from the nodes mempool +func (m *Mempool) RemoveTransaction(hash util.Uint256) error { + m.mtx.Lock() + defer m.mtx.Unlock() + if len(m.pool) == 0 { + return ErrMempoolEmpty + } + // deletes regardless of whether key is there or not. So do not check for existence before delete. + // Use Exists() for this. + delete(m.pool, hash) + + return nil +} + +// Size returns the size of the mempool +func (m *Mempool) Size() uint64 { + m.mtx.RLock() + len := uint64(len(m.pool)) + m.mtx.RUnlock() + + return len +} + +// ReturnAllTransactions will return all transactions in the +// mempool, will be mostly used by the RPC server +func (m *Mempool) ReturnAllTransactions() ([]transaction.Transactioner, error) { + transactions := make([]transaction.Transactioner, 0) + + m.mtx.RLock() + defer m.mtx.RUnlock() + if len(m.pool) == 0 { + return nil, ErrMempoolEmpty + } + + for _, t := range m.pool { + + if t.ParentTX == nil { + fmt.Println(t, "NILNIL") + } + transactions = append(transactions, *t.ParentTX) + fmt.Println(transactions) + } + + return transactions, nil + +} + +// Exists check whether the transaction exists in the mempool +func (m *Mempool) Exists(hash util.Uint256) bool { + m.mtx.RLock() + _, ok := m.pool[hash] + m.mtx.RUnlock() + + return ok +} diff --git a/pkg/mempool/mempool_test.go b/pkg/mempool/mempool_test.go new file mode 100644 index 000000000..7d75a03d6 --- /dev/null +++ b/pkg/mempool/mempool_test.go @@ -0,0 +1,211 @@ +package mempool_test + +import ( + "testing" + "time" + + "github.com/CityOfZion/neo-go/pkg/mempool" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/stretchr/testify/assert" +) + +func TestMempoolExists(t *testing.T) { + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 10 * time.Minute, + SigLimit: 20, + } + mem := mempool.New(cfg) + + trans := transaction.NewContract(0) + + assert.Equal(t, false, mem.Exists(trans.Hash)) + + err := mem.AddTransaction(trans) + assert.Equal(t, nil, err) + + assert.Equal(t, true, mem.Exists(trans.Hash)) +} +func TestMempoolFullPool(t *testing.T) { + + maxTx := uint64(100) + cfg := mempool.Config{ + MaxNumOfTX: maxTx, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 10 * time.Minute, + SigLimit: 20, + } + mem := mempool.New(cfg) + + for i := uint64(1); i <= maxTx; i++ { + trans := transaction.NewContract(0) + attr := &transaction.Attribute{ + Usage: transaction.Remark, + Data: []byte{byte(i)}, + } + trans.AddAttribute(attr) + err := mem.AddTransaction(trans) + assert.Equal(t, nil, err) + } + trans := transaction.NewContract(0) + err := mem.AddTransaction(trans) + assert.NotEqual(t, nil, err) + + assert.Equal(t, mempool.ErrMemPoolFull, err) +} +func TestMempoolLargeTX(t *testing.T) { + + maxTxSize := uint64(100) + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: maxTxSize, + TXTTL: 10 * time.Minute, + SigLimit: 20, + } + mem := mempool.New(cfg) + + trans := transaction.NewContract(0) + for i := uint64(1); i <= 100; i++ { // 100 attributes will be over 100 bytes + attr := &transaction.Attribute{ + Usage: transaction.Remark, + Data: []byte{byte(i)}, + } + trans.AddAttribute(attr) + } + + err := mem.AddTransaction(trans) + assert.NotEqual(t, nil, err) + assert.Equal(t, mempool.ErrTXTooBig, err) +} +func TestMempoolTooManyWitness(t *testing.T) { + + maxWitness := uint8(3) + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 10 * time.Minute, + SigLimit: maxWitness, + } + mem := mempool.New(cfg) + + trans := transaction.NewContract(0) + for i := uint8(1); i <= maxWitness; i++ { // 100 attributes will be over 100 bytes + wit := &transaction.Witness{ + InvocationScript: []byte{byte(i)}, + VerificationScript: []byte{byte(i)}, + } + trans.AddWitness(wit) + } + + trans.AddWitness(&transaction.Witness{ + InvocationScript: []byte{}, + VerificationScript: []byte{}, + }) + + err := mem.AddTransaction(trans) + assert.NotEqual(t, nil, err) + assert.Equal(t, mempool.ErrTXTooManyWitnesses, err) +} +func TestMempoolDuplicate(t *testing.T) { + + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 10 * time.Minute, + SigLimit: 1, + } + mem := mempool.New(cfg) + + trans := transaction.NewContract(0) + + err := mem.AddTransaction(trans) + assert.Equal(t, nil, err) + + err = mem.AddTransaction(trans) + assert.NotEqual(t, nil, err) + assert.Equal(t, mempool.ErrDuplicateTX, err) +} +func TestMempoolReturnAll(t *testing.T) { + + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 10 * time.Minute, + SigLimit: 1, + } + mem := mempool.New(cfg) + + numTx := uint64(10) + + for i := uint64(1); i <= numTx; i++ { + trans := transaction.NewContract(0) + attr := &transaction.Attribute{ + Usage: transaction.Remark, + Data: []byte{byte(i)}, + } + trans.AddAttribute(attr) + err := mem.AddTransaction(trans) + assert.Equal(t, nil, err) + } + + AllTrans, err := mem.ReturnAllTransactions() + assert.Equal(t, nil, err) + + assert.Equal(t, numTx, uint64(len(AllTrans))) + +} +func TestMempoolRemove(t *testing.T) { + + cfg := mempool.Config{ + MaxNumOfTX: 100, + FreeTX: 20, + MinTXFee: 0, + MaxTXSize: 10000, + TXTTL: 3 * time.Minute, + SigLimit: 1, + } + mem := mempool.New(cfg) + + // Remove a transaction when mempool is empty + trans := transaction.NewContract(0) + hash, _ := trans.ID() + err := mem.RemoveTransaction(hash) + assert.Equal(t, mempool.ErrMempoolEmpty, err) + + // Add tx1 into mempool + err = mem.AddTransaction(trans) + assert.Equal(t, nil, err) + + diffTrans := transaction.NewContract(0) // TX2 + + diffTrans.AddAttribute( + &transaction.Attribute{ + Usage: transaction.Remark, + Data: []byte{}, + }) + + diffHash, _ := diffTrans.ID() + + // Try removing TX2, when only TX1 is in mempool + err = mem.RemoveTransaction(diffHash) + assert.Equal(t, nil, err) + assert.Equal(t, uint64(1), mem.Size()) + // Remove hash that is in mempool + err = mem.RemoveTransaction(hash) + assert.Equal(t, nil, err) + assert.Equal(t, uint64(0), mem.Size()) + +} diff --git a/pkg/mempool/tx.go b/pkg/mempool/tx.go new file mode 100644 index 000000000..ac793af5f --- /dev/null +++ b/pkg/mempool/tx.go @@ -0,0 +1,45 @@ +package mempool + +import ( + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" +) + +// TX is a wrapper struct around a normal tx +// which includes extra information about the TX +// +type TX struct { + ParentTX *transaction.Transactioner + Added time.Time + Fee uint64 + Size uint64 + NumWitness uint8 + Free bool +} + +// Descriptor takes a transaction and puts it into a new TX struct along with metadata +func Descriptor(trans transaction.Transactioner) *TX { + + var desc TX + desc.ParentTX = &trans + desc.Fee = getFee(trans.TXOs(), trans.UTXOs()) + desc.Free = desc.Fee != 0 + desc.Added = time.Now() + desc.Size = uint64(len(trans.Bytes())) + + numWit := len(trans.Witness()) + if numWit > 255 || numWit < 0 { // < 0 should not happen + numWit = 255 + } + desc.NumWitness = uint8(numWit) + + return &desc +} + +// TODO: need blockchain package complete for fee calculation +// HMM:Could also put the function in the config +func getFee(in []*transaction.Input, out []*transaction.Output) uint64 { + // query utxo set for inputs, then subtract from out to get fee + return 0 +} diff --git a/pkg/peer/config.go b/pkg/peer/config.go new file mode 100644 index 000000000..0614e5952 --- /dev/null +++ b/pkg/peer/config.go @@ -0,0 +1,48 @@ +package peer + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +// LocalConfig specifies the properties that should be available for each remote peer + +type LocalConfig struct { + Net protocol.Magic + UserAgent string + Services protocol.ServiceFlag + Nonce uint32 + ProtocolVer protocol.Version + Relay bool + Port uint16 + // pointer to config will keep the startheight updated for each version + //Message we plan to send + StartHeight func() uint32 + OnHeader func(*Peer, *payload.HeadersMessage) + OnGetHeaders func(msg *payload.GetHeadersMessage) // returns HeaderMessage + OnAddr func(*Peer, *payload.AddrMessage) + OnGetAddr func(*Peer, *payload.GetAddrMessage) + OnInv func(*Peer, *payload.InvMessage) + OnGetData func(msg *payload.GetDataMessage) + OnBlock func(*Peer, *payload.BlockMessage) + OnGetBlocks func(msg *payload.GetBlocksMessage) +} + +// func DefaultConfig() LocalConfig { +// return LocalConfig{ +// Net: protocol.MainNet, +// UserAgent: "NEO-GO-Default", +// Services: protocol.NodePeerService, +// Nonce: 1200, +// ProtocolVer: 0, +// Relay: false, +// Port: 10332, +// // pointer to config will keep the startheight updated for each version +// //Message we plan to send +// StartHeight: DefaultHeight, +// } +// } + +// func DefaultHeight() uint32 { +// return 10 +// } diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go new file mode 100644 index 000000000..11668f478 --- /dev/null +++ b/pkg/peer/peer.go @@ -0,0 +1,433 @@ +// This impl uses channels to simulate the queue handler with the actor model. +// A suitable number k ,should be set for channel size, because if #numOfMsg > k, +// we lose determinism. k chosen should be large enough that when filled, it shall indicate that +// the peer has stopped responding, since we do not have a pingMSG, we will need another way to shut down +// peers + +package peer + +import ( + "errors" + "fmt" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + + "github.com/CityOfZion/neo-go/pkg/peer/stall" + "github.com/CityOfZion/neo-go/pkg/wire" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +const ( + maxOutboundConnections = 100 + protocolVer = protocol.DefaultVersion + handshakeTimeout = 30 * time.Second + idleTimeout = 5 * time.Minute // If no message received after idleTimeout, then peer disconnects + + // nodes will have `responseTime` seconds to reply with a response + responseTime = 120 * time.Second + + // the stall detector will check every `tickerInterval` to see if messages + // are overdue. Should be less than `responseTime` + tickerInterval = 30 * time.Second + + // The input buffer size is the amount of mesages that + // can be buffered into the channel to receive at once before + // blocking, and before determinism is broken + inputBufferSize = 100 + + // The output buffer size is the amount of messages that + // can be buffered into the channel to send at once before + // blocking, and before determinism is broken. + outputBufferSize = 100 + + // pingInterval = 20 * time.Second //Not implemented in neo clients +) + +var ( + errHandShakeTimeout = errors.New("Handshake timed out, peers have " + string(handshakeTimeout) + " Seconds to Complete the handshake") +) + +type Peer struct { + config LocalConfig + conn net.Conn + + // atomic vals + disconnected int32 + + //unchangeable state: concurrent safe + addr string + protoVer protocol.Version + port uint16 + inbound bool + userAgent string + services protocol.ServiceFlag + createdAt time.Time + relay bool + + statemutex sync.Mutex + verackReceived bool + versionKnown bool + + *stall.Detector + + inch chan func() // will handle all incoming connections from peer + outch chan func() // will handle all outcoming connections from peer + quitch chan struct{} +} + +func NewPeer(con net.Conn, inbound bool, cfg LocalConfig) *Peer { + p := Peer{} + p.inch = make(chan func(), inputBufferSize) + p.outch = make(chan func(), outputBufferSize) + p.quitch = make(chan struct{}, 1) + p.inbound = inbound + p.config = cfg + p.conn = con + p.createdAt = time.Now() + p.addr = p.conn.RemoteAddr().String() + + p.Detector = stall.NewDetector(responseTime, tickerInterval) + + // TODO: set the unchangeable states + return &p +} + +// Write to a peer +func (p *Peer) Write(msg wire.Messager) error { + return wire.WriteMessage(p.conn, p.config.Net, msg) +} + +// Read to a peer +func (p *Peer) Read() (wire.Messager, error) { + return wire.ReadMessage(p.conn, p.config.Net) +} + +// Disconnects from a peer +func (p *Peer) Disconnect() { + + // return if already disconnected + if atomic.LoadInt32(&p.disconnected) != 0 { + return + } + + atomic.AddInt32(&p.disconnected, 1) + + p.Detector.Quit() + close(p.quitch) + p.conn.Close() + + fmt.Println("Disconnected Peer with address", p.RemoteAddr().String()) + +} + +// Exposed API functions below +func (p *Peer) Port() uint16 { + return p.port +} +func (p *Peer) CreatedAt() time.Time { + return p.createdAt +} +func (p *Peer) CanRelay() bool { + return p.relay +} +func (p *Peer) LocalAddr() net.Addr { + return p.conn.LocalAddr() +} +func (p *Peer) RemoteAddr() net.Addr { + return p.conn.RemoteAddr() +} +func (p *Peer) Services() protocol.ServiceFlag { + return p.config.Services +} +func (p *Peer) Inbound() bool { + return p.inbound +} +func (p *Peer) UserAgent() string { + return p.config.UserAgent +} +func (p *Peer) IsVerackReceived() bool { + return p.verackReceived +} +func (p *Peer) NotifyDisconnect() bool { + fmt.Println("Peer has not disconnected yet") + <-p.quitch + fmt.Println("Peer has just disconnected") + return true +} + +//End of Exposed API functions// + +// Ping not impl. in neo yet, adding it now +// will cause this client to disconnect from all other implementations +func (p *Peer) PingLoop() { /*not implemented in other neo clients*/ } + +// Run is used to start communicating with the peer +// completes the handshake and starts observing +// for messages coming in +func (p *Peer) Run() error { + + err := p.Handshake() + + go p.StartProtocol() + go p.ReadLoop() + go p.WriteLoop() + + //go p.PingLoop() // since it is not implemented. It will disconnect all other impls. + return err + +} + +// run as a go-routine, will act as our queue for messages +// should be ran after handshake +func (p *Peer) StartProtocol() { +loop: + for atomic.LoadInt32(&p.disconnected) == 0 { + select { + case f := <-p.inch: + f() + case <-p.quitch: + break loop + case <-p.Detector.Quitch: + fmt.Println("Peer stalled, disconnecting") + break loop + } + } + p.Disconnect() +} + +// Should only be called after handshake is complete +// on a seperate go-routine. +// ReadLoop Will block on the read until a message is +// read + +func (p *Peer) ReadLoop() { + + idleTimer := time.AfterFunc(idleTimeout, func() { + fmt.Println("Timing out peer") + p.Disconnect() + }) + +loop: + for atomic.LoadInt32(&p.disconnected) == 0 { + + idleTimer.Reset(idleTimeout) // reset timer on each loop + + readmsg, err := p.Read() + + // Message read; stop Timer + idleTimer.Stop() + + if err != nil { + fmt.Println("Err on read", err) // This will also happen if Peer is disconnected + break loop + } + + // Remove message as pending from the stall detector + p.Detector.RemoveMessage(readmsg.Command()) + + switch msg := readmsg.(type) { + + case *payload.VersionMessage: + fmt.Println("Already received a Version, disconnecting. " + p.RemoteAddr().String()) + break loop // We have already done the handshake, break loop and disconnect + case *payload.VerackMessage: + if p.verackReceived { + fmt.Println("Already received a Verack, disconnecting. " + p.RemoteAddr().String()) + break loop + } + p.statemutex.Lock() // This should not happen, however if it does, then we should set it. + p.verackReceived = true + p.statemutex.Unlock() + case *payload.AddrMessage: + p.OnAddr(msg) + case *payload.GetAddrMessage: + p.OnGetAddr(msg) + case *payload.GetBlocksMessage: + p.OnGetBlocks(msg) + case *payload.BlockMessage: + p.OnBlocks(msg) + case *payload.HeadersMessage: + p.OnHeaders(msg) + case *payload.GetHeadersMessage: + p.OnGetHeaders(msg) + case *payload.InvMessage: + p.OnInv(msg) + case *payload.GetDataMessage: + p.OnGetData(msg) + case *payload.TXMessage: + p.OnTX(msg) + default: + fmt.Println("Cannot recognise message", msg.Command()) //Do not disconnect peer, just Log Message + } + } + + idleTimer.Stop() + p.Disconnect() +} + +// WriteLoop will Queue all messages to be written to +// the peer. +func (p *Peer) WriteLoop() { + for atomic.LoadInt32(&p.disconnected) == 0 { + select { + case f := <-p.outch: + f() + case <-p.Detector.Quitch: // if the detector quits, disconnect peer + p.Disconnect() + } + } +} + +func (p *Peer) OnGetData(msg *payload.GetDataMessage) { + + p.inch <- func() { + // fmt.Println(msg.Hashes) + fmt.Println("That was an getdata Message please pass func down through config", msg.Command()) + } +} +func (p *Peer) OnTX(msg *payload.TXMessage) { + + p.inch <- func() { + // fmt.Println(msg.Hashes) + getdata, err := payload.NewGetDataMessage(payload.InvTypeTx) + if err != nil { + fmt.Println("Eor", err) + } + id, err := msg.Tx.ID() + getdata.AddHash(id) + p.Write(getdata) + fmt.Println("That was an tx Message please pass func down through config", msg.Command()) + } +} + +func (p *Peer) OnInv(msg *payload.InvMessage) { + + p.inch <- func() { + if p.config.OnInv != nil { + p.config.OnInv(p, msg) + } + fmt.Println("That was an inv Message please pass func down through config", msg.Command()) + } +} + +// OnGetHeaders Listener, outside of the anonymous func will be extra functionality +// like timing +func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) { + p.inch <- func() { + if p.config.OnGetHeaders != nil { + p.config.OnGetHeaders(msg) + } + fmt.Println("That was a getheaders message, please pass func down through config", msg.Command()) + + } +} + +// OnAddr Listener +func (p *Peer) OnAddr(msg *payload.AddrMessage) { + p.inch <- func() { + if p.config.OnAddr != nil { + p.config.OnAddr(p, msg) + } + fmt.Println("That was a addr message, please pass func down through config", msg.Command()) + + } +} + +// OnGetAddr Listener +func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) { + p.inch <- func() { + if p.config.OnGetAddr != nil { + p.config.OnGetAddr(p, msg) + } + fmt.Println("That was a getaddr message, please pass func down through config", msg.Command()) + + } +} + +// OnGetBlocks Listener +func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) { + p.inch <- func() { + if p.config.OnGetBlocks != nil { + p.config.OnGetBlocks(msg) + } + fmt.Println("That was a getblocks message, please pass func down through config", msg.Command()) + } +} + +// OnBlocks Listener +func (p *Peer) OnBlocks(msg *payload.BlockMessage) { + p.inch <- func() { + if p.config.OnBlock != nil { + p.config.OnBlock(p, msg) + } + } +} + +// OnVersion Listener will be called +// during the handshake, any error checking should be done here for the versionMessage. +// This should only ever be called during the handshake. Any other place and the peer will disconnect. +func (p *Peer) OnVersion(msg *payload.VersionMessage) error { + if msg.Nonce == p.config.Nonce { + p.conn.Close() + return errors.New("Self connection, disconnecting Peer") + } + p.versionKnown = true + p.port = msg.Port + p.services = msg.Services + p.userAgent = string(msg.UserAgent) + p.createdAt = time.Now() + p.relay = msg.Relay + return nil +} + +// OnHeaders Listener +func (p *Peer) OnHeaders(msg *payload.HeadersMessage) { + fmt.Println("We have received the headers") + p.inch <- func() { + if p.config.OnHeader != nil { + p.config.OnHeader(p, msg) + } + } +} + +// RequestHeaders will write a getheaders to peer +func (p *Peer) RequestHeaders(hash util.Uint256) error { + fmt.Println("Sending header request") + c := make(chan error, 0) + p.outch <- func() { + p.Detector.AddMessage(command.GetHeaders) + getHeaders, err := payload.NewGetHeadersMessage([]util.Uint256{hash}, util.Uint256{}) + err = p.Write(getHeaders) + c <- err + } + + return <-c + +} + +// RequestBlocks will ask a peer for a block +func (p *Peer) RequestBlocks(hashes []util.Uint256) error { + fmt.Println("Requesting block from peer") + c := make(chan error, 0) + + p.outch <- func() { + p.Detector.AddMessage(command.GetData) + getdata, err := payload.NewGetDataMessage(payload.InvTypeBlock) + err = getdata.AddHashes(hashes) + if err != nil { + c <- err + return + } + err = p.Write(getdata) + c <- err + } + + return <-c + +} diff --git a/pkg/peer/peer_test.go b/pkg/peer/peer_test.go new file mode 100644 index 000000000..adcb4d342 --- /dev/null +++ b/pkg/peer/peer_test.go @@ -0,0 +1,209 @@ +package peer_test + +import ( + "fmt" + "net" + "testing" + "time" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/stretchr/testify/assert" +) + +func returnConfig() peer.LocalConfig { + + DefaultHeight := func() uint32 { + return 10 + } + + OnAddr := func(p *peer.Peer, msg *payload.AddrMessage) {} + OnHeader := func(p *peer.Peer, msg *payload.HeadersMessage) {} + OnGetHeaders := func(msg *payload.GetHeadersMessage) {} + OnInv := func(p *peer.Peer, msg *payload.InvMessage) {} + OnGetData := func(msg *payload.GetDataMessage) {} + OnBlock := func(p *peer.Peer, msg *payload.BlockMessage) {} + OnGetBlocks := func(msg *payload.GetBlocksMessage) {} + + return peer.LocalConfig{ + Net: protocol.MainNet, + UserAgent: "NEO-GO-Default", + Services: protocol.NodePeerService, + Nonce: 1200, + ProtocolVer: 0, + Relay: false, + Port: 10332, + // pointer to config will keep the startheight updated for each version + //Message we plan to send + StartHeight: DefaultHeight, + OnHeader: OnHeader, + OnAddr: OnAddr, + OnGetHeaders: OnGetHeaders, + OnInv: OnInv, + OnGetData: OnGetData, + OnBlock: OnBlock, + OnGetBlocks: OnGetBlocks, + } +} + +func TestHandshake(t *testing.T) { + address := ":20338" + go func() { + + conn, err := net.DialTimeout("tcp", address, 2*time.Second) + if err != nil { + t.Fatal(err) + } + p := peer.NewPeer(conn, true, returnConfig()) + err = p.Run() + verack, err := payload.NewVerackMessage() + if err != nil { + t.Fail() + } + if err := p.Write(verack); err != nil { + t.Fatal(err) + } + + assert.Equal(t, true, p.IsVerackReceived()) + + }() + + listener, err := net.Listen("tcp", address) + if err != nil { + t.Fatal(err) + return + } + + defer func() { + listener.Close() + }() + + for { + + conn, err := listener.Accept() + if err != nil { + t.Fatal(err) + } + + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("82.2.97.142"), Port: 20338} + nonce := uint32(100) + messageVer, err := payload.NewVersionMessage(tcpAddrMe, 2595770, false, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService) + + if err != nil { + t.Fatal(err) + } + + if err := wire.WriteMessage(conn, protocol.MainNet, messageVer); err != nil { + t.Fatal(err) + return + } + + readmsg, err := wire.ReadMessage(conn, protocol.MainNet) + if err != nil { + t.Fatal(err) + } + version, ok := readmsg.(*payload.VersionMessage) + if !ok { + t.Fatal(err) + } + + assert.NotEqual(t, nil, version) + + messageVrck, err := payload.NewVerackMessage() + if err != nil { + t.Fatal(err) + } + + assert.NotEqual(t, nil, messageVrck) + + if err := wire.WriteMessage(conn, protocol.MainNet, messageVrck); err != nil { + t.Fatal(err) + } + + readmsg, err = wire.ReadMessage(conn, protocol.MainNet) + if err != nil { + t.Fatal(err) + } + + assert.NotEqual(t, nil, readmsg) + + verk, ok := readmsg.(*payload.VerackMessage) + if !ok { + t.Fatal(err) + } + assert.NotEqual(t, nil, verk) + + return + } + +} + +func TestConfigurations(t *testing.T) { + _, conn := net.Pipe() + + inbound := true + + config := returnConfig() + + p := peer.NewPeer(conn, inbound, config) + + // test inbound + assert.Equal(t, inbound, p.Inbound()) + + // handshake not done, should be false + assert.Equal(t, false, p.IsVerackReceived()) + + assert.Equal(t, config.Services, p.Services()) + + assert.Equal(t, config.UserAgent, p.UserAgent()) + + assert.Equal(t, config.Relay, p.CanRelay()) + + assert.WithinDuration(t, time.Now(), p.CreatedAt(), 1*time.Second) + +} + +func TestHandshakeCancelled(t *testing.T) { + // These are the conditions which should invalidate the handshake. + // Make sure peer is disconnected. +} + +func TestPeerDisconnect(t *testing.T) { + // Make sure everything is shutdown + // Make sure timer is shutdown in stall detector too. Should maybe put this part of test into stall detector. + + _, conn := net.Pipe() + inbound := true + config := returnConfig() + p := peer.NewPeer(conn, inbound, config) + fmt.Println("Calling disconnect") + p.Disconnect() + fmt.Println("Disconnect finished calling") + verack, _ := payload.NewVerackMessage() + + fmt.Println(" We good here") + + err := p.Write(verack) + + assert.NotEqual(t, err, nil) + + // Check if Stall detector is still running + _, ok := <-p.Detector.Quitch + assert.Equal(t, ok, false) + +} + +func TestNotifyDisconnect(t *testing.T) { + + _, conn := net.Pipe() + inbound := true + config := returnConfig() + p := peer.NewPeer(conn, inbound, config) + + p.Disconnect() + p.NotifyDisconnect() + // TestNotify uses default test timeout as the passing condition + // Failure condition can be seen when you comment out p.Disconnect() +} diff --git a/pkg/peer/peerhandshake.go b/pkg/peer/peerhandshake.go new file mode 100644 index 000000000..2cad19c5d --- /dev/null +++ b/pkg/peer/peerhandshake.go @@ -0,0 +1,131 @@ +package peer + +import ( + "fmt" + "net" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util/ip" +) + +func (p *Peer) Handshake() error { + + handshakeErr := make(chan error, 1) + go func() { + if p.inbound { + handshakeErr <- p.inboundHandShake() + } else { + handshakeErr <- p.outboundHandShake() + } + }() + + select { + case err := <-handshakeErr: + if err != nil { + return err + } + case <-time.After(handshakeTimeout): + return errHandShakeTimeout + } + + // This is purely here for Logs + if p.inbound { + fmt.Println("inbound handshake with", p.RemoteAddr().String(), "successful") + } else { + + fmt.Println("outbound handshake with", p.RemoteAddr().String(), "successful") + } + return nil +} + +// If this peer has an inbound conn (conn that is going into another peer) +// then he has dialed and so, we must read the version message +func (p *Peer) inboundHandShake() error { + var err error + if err := p.writeLocalVersionMSG(); err != nil { + return err + } + if err := p.readRemoteVersionMSG(); err != nil { + return err + } + verack, err := payload.NewVerackMessage() + if err != nil { + return err + } + err = p.Write(verack) + return p.readVerack() +} +func (p *Peer) outboundHandShake() error { + var err error + err = p.readRemoteVersionMSG() + if err != nil { + return err + } + + err = p.writeLocalVersionMSG() + if err != nil { + return err + } + + err = p.readVerack() + if err != nil { + return err + } + verack, err := payload.NewVerackMessage() + if err != nil { + return err + } + return p.Write(verack) +} +func (p *Peer) writeLocalVersionMSG() error { + + nonce := p.config.Nonce + relay := p.config.Relay + port := int(p.config.Port) + ua := p.config.UserAgent + sh := p.config.StartHeight() + services := p.config.Services + proto := p.config.ProtocolVer + ip := iputils.GetLocalIP() + tcpAddrMe := &net.TCPAddr{IP: ip, Port: port} + + messageVer, err := payload.NewVersionMessage(tcpAddrMe, sh, relay, proto, ua, nonce, services) + + if err != nil { + return err + } + return p.Write(messageVer) +} + +func (p *Peer) readRemoteVersionMSG() error { + readmsg, err := wire.ReadMessage(p.conn, p.config.Net) + if err != nil { + return err + } + + version, ok := readmsg.(*payload.VersionMessage) + if !ok { + return err + } + return p.OnVersion(version) +} + +func (p *Peer) readVerack() error { + readmsg, err := wire.ReadMessage(p.conn, p.config.Net) + + if err != nil { + return err + } + + _, ok := readmsg.(*payload.VerackMessage) + + if !ok { + return err + } + // should only be accessed on one go-routine + p.verackReceived = true + + return nil +} diff --git a/pkg/peer/readme.md b/pkg/peer/readme.md new file mode 100644 index 000000000..ea13a62a5 --- /dev/null +++ b/pkg/peer/readme.md @@ -0,0 +1,67 @@ +# Package - Peer + + + +## Responsibility + +Once a connection has been made. The connection will represent a established peer to the localNode. Since a connection and the `Wire` is a golang primitive, that we cannot do much with. The peer package will encapsulate both, while adding extra functionality. + + +## Features + +- The handshake protocol is automatically executed and handled by the peer package. If a Version/Verack is received twice, the peer will be disconnected. + +- IdleTimeouts: If a Message is not received from the peer within a set period of time, the peer will be disconnected. + +- StallTimeouts: For Example, If a GetHeaders, is sent to the Peer and a Headers Response is not received within a certain period of time, then the peer is disconnected. + +- Concurrency Model: The concurrency model used is similar to Actor model, with a few changes. Messages can be sent to a peer asynchronously or synchronously. An example of an synchornous message send is the `RequestHeaders` method, where the channel blocks until an error value is received. The `OnHeaders` message is however asynchronously called. Furthermore, all methods passed through the config, are wrapped inside of an additional `Peers` method, this is to lay the ground work to capturing statistics regarding a specific command. These are also used so that we can pass behaviour to be executed down the channel. + +- Configuration: Each Peer will have a config struct passed to it, with information about the Local Peer and functions that will encapsulate the behaviour of what the peer should do, given a request. This way, the peer is not dependent on any other package. + +## Usage + + conn, err := net.Dial("tcp", "seed2.neo.org:10333") + if err != nil { + fmt.Println("Error dialing connection", err.Error()) + return + } + + config := peer.LocalConfig{ + Net: protocol.MainNet, + UserAgent: "NEO-G", + Services: protocol.NodePeerService, + Nonce: 1200, + ProtocolVer: 0, + Relay: false, + Port: 10332, + StartHeight: LocalHeight, + OnHeader: OnHeader, + } + + p := peer.NewPeer(conn, false, config) + err = p.Run() + + hash, err := util.Uint256DecodeString(chainparams.GenesisHash) + // hash2, err := util.Uint256DecodeString("ff8fe95efc5d1cc3a22b17503aecaf289cef68f94b79ddad6f613569ca2342d8") + err = p.RequestHeaders(hash) + + func OnHeader(peer *peer.Peer, msg *payload.HeadersMessage) { + // This function is passed to peer + // and the peer will execute it on receiving a header + } + + func LocalHeight() uint32 { + // This will be a function from the object that handles the block heights + return 10 + } + + +### Notes + + +Should we follow the actor model for Peers? Each Peer will have a ID, which we can take as the PID or if +we launch a go-routine for each peer, then we can use that as an implicit PID. + +Peer information should be stored into a database, if no db exists, we should get it from an initial peers file. +We can use this to periodically store information about a peer. \ No newline at end of file diff --git a/pkg/peer/stall/stall.go b/pkg/peer/stall/stall.go new file mode 100644 index 000000000..85bf11858 --- /dev/null +++ b/pkg/peer/stall/stall.go @@ -0,0 +1,174 @@ +package stall + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +// stall detector will keep track of all pendingMessages +// If any message takes too long to reply +// the detector will disconnect the peer + +type Detector struct { + responseTime time.Duration + tickInterval time.Duration + + lock sync.Mutex + responses map[command.Type]time.Time + + // The detector is embedded into a peer and the peer watches this quit chan + // If this chan is closed, the peer disconnects + Quitch chan struct{} + + // atomic vals + disconnected int32 +} + +// rT is the responseTime and signals how long +// a peer has to reply back to a sent message +// tickerInterval is how often the detector wil check for stalled messages +func NewDetector(rTime time.Duration, tickerInterval time.Duration) *Detector { + d := &Detector{ + responseTime: rTime, + tickInterval: tickerInterval, + lock: sync.Mutex{}, + responses: map[command.Type]time.Time{}, + Quitch: make(chan struct{}), + } + go d.loop() + return d +} + +func (d *Detector) loop() { + ticker := time.NewTicker(d.tickInterval) + +loop: + for { + select { + case <-ticker.C: + now := time.Now() + for _, deadline := range d.responses { + if now.After(deadline) { + fmt.Println("Deadline passed") + ticker.Stop() + break loop + } + } + + } + } + d.Quit() + d.DeleteAll() + ticker.Stop() +} + +// Quit is a concurrent safe way to call the Quit channel +// Without blocking +func (d *Detector) Quit() { + // return if already disconnected + if atomic.LoadInt32(&d.disconnected) != 0 { + return + } + + atomic.AddInt32(&d.disconnected, 1) + close(d.Quitch) +} + +// Call this function when we send a message to a peer +// The command passed through is the command that we sent +// and not the command we expect to receive +func (d *Detector) AddMessage(cmd command.Type) { + cmds := d.addMessage(cmd) + d.lock.Lock() + for _, cmd := range cmds { + d.responses[cmd] = time.Now().Add(d.responseTime) + } + d.lock.Unlock() +} + +// Call this function when we receive a message from +// peer. This will remove the pendingresponse message from the map. +// The command passed through is the command we received +func (d *Detector) RemoveMessage(cmd command.Type) { + cmds := d.addMessage(cmd) + d.lock.Lock() + for _, cmd := range cmds { + delete(d.responses, cmd) + } + d.lock.Unlock() +} + +// DeleteAll empties the map of all contents and +// is called when the detector is being shut down +func (d *Detector) DeleteAll() { + d.lock.Lock() + d.responses = make(map[command.Type]time.Time) + d.lock.Unlock() +} + +// GetMessages Will return a map of all of the pendingResponses +// and their deadlines +func (d *Detector) GetMessages() map[command.Type]time.Time { + var resp map[command.Type]time.Time + d.lock.Lock() + resp = d.responses + d.lock.Unlock() + return resp +} + +// when a message is added, we will add a deadline for +// expected response +func (d *Detector) addMessage(cmd command.Type) []command.Type { + + cmds := []command.Type{} + + switch cmd { + case command.GetHeaders: + // We now will expect a Headers Message + cmds = append(cmds, command.Headers) + case command.GetAddr: + // We now will expect a Headers Message + cmds = append(cmds, command.Addr) + + case command.GetData: + // We will now expect a block/tx message + // We can optimise this by including the exact inventory type, however it is not needed + cmds = append(cmds, command.Block) + cmds = append(cmds, command.TX) + case command.GetBlocks: + // we will now expect a inv message + cmds = append(cmds, command.Inv) + case command.Version: + // We will now expect a verack + cmds = append(cmds, command.Verack) + } + return cmds +} + +// if receive a message, we will delete it from pending +func (d *Detector) removeMessage(cmd command.Type) []command.Type { + + cmds := []command.Type{} + + switch cmd { + case command.Block: + // We will now expect a block/tx message + cmds = append(cmds, command.Block) + cmds = append(cmds, command.TX) + case command.TX: + // We will now expect a block/tx message + cmds = append(cmds, command.Block) + cmds = append(cmds, command.TX) + case command.GetBlocks: + // we will now expect a inv message + cmds = append(cmds, command.Inv) + default: + // We will now expect a verack + cmds = append(cmds, cmd) + } + return cmds +} diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go new file mode 100644 index 000000000..5b6a2b755 --- /dev/null +++ b/pkg/peer/stall/stall_test.go @@ -0,0 +1,75 @@ +package stall + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +func TestAddRemoveMessage(t *testing.T) { + + responseTime := 2 * time.Second + tickerInterval := 1 * time.Second + + d := NewDetector(responseTime, tickerInterval) + d.AddMessage(command.GetAddr) + mp := d.GetMessages() + + assert.Equal(t, 1, len(mp)) + assert.IsType(t, time.Time{}, mp[command.GetAddr]) + + d.RemoveMessage(command.GetAddr) + mp = d.GetMessages() + + assert.Equal(t, 0, len(mp)) + assert.Empty(t, mp[command.GetAddr]) +} + +type mockPeer struct { + online bool + detector *Detector +} + +func (mp *mockPeer) loop() { +loop: + for { + select { + case <-mp.detector.Quitch: + + break loop + } + } + // cleanup + mp.online = false +} +func TestDeadlineWorks(t *testing.T) { + + responseTime := 2 * time.Second + tickerInterval := 1 * time.Second + + d := NewDetector(responseTime, tickerInterval) + mp := mockPeer{online: true, detector: d} + go mp.loop() + + d.AddMessage(command.GetAddr) + time.Sleep(responseTime + 1*time.Second) + + k := make(map[command.Type]time.Time) + assert.Equal(t, k, d.responses) + assert.Equal(t, false, mp.online) + +} +func TestDeadlineShouldNotBeEmpty(t *testing.T) { + responseTime := 10 * time.Second + tickerInterval := 1 * time.Second + + d := NewDetector(responseTime, tickerInterval) + d.AddMessage(command.GetAddr) + time.Sleep(1 * time.Second) + + k := make(map[command.Type]time.Time) + assert.NotEqual(t, k, d.responses) +} diff --git a/pkg/peermanager/peermgr.go b/pkg/peermanager/peermgr.go new file mode 100644 index 000000000..9083b135d --- /dev/null +++ b/pkg/peermanager/peermgr.go @@ -0,0 +1,67 @@ +package peermanager + +import ( + "errors" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// NOTE: This package may be removed in the future +// and so full functionality is not yet implemented, see Issue #33 for more details. + +//PeerMgr will act as a convenience Mgr +// It will be notified of added Peers +// It will take care of sending messages to the right peers. In this way, it acts as a load balancer +// If we send a getdata to one peer, it will be smart and send it to another peer who is not as busy +// Using subscription model, we can have the syncmanager/other modules notify the peermgr when they have received data +type PeerMgr struct { + peers []*peer.Peer +} + +// New will create a new peer manager +// As of now it just returns a peerMgr struct and so +// the New method is redundant. A config file will be passed as a parameter, +// if it is decided that we will use this. +func New() *PeerMgr { + return &PeerMgr{} +} + +// Disconnect will close the connection on a peer and +// remove it from the list +// TODO: remove from list once disconnected +func (pm *PeerMgr) Disconnect(p *peer.Peer) { + p.Disconnect() + // Once disconnected, we remove it from the list + // and look for more peers to connect to +} + +// RequestHeaders will request the headers from the most available peer +// As of now, it requests from the first peer in the list, TODO(Kev) +func (pm *PeerMgr) RequestHeaders(hash util.Uint256) (*peer.Peer, error) { + + if len(pm.peers) == 0 { + return nil, errors.New("Peer manager currently has no peers") + } + + return pm.peers[0], pm.peers[0].RequestHeaders(hash) +} + +// RequestBlocks will request blocks from the most available peer +// As of now, it requests from the first peer in the list, TODO(Kev) +func (pm *PeerMgr) RequestBlocks(hash []util.Uint256) (*peer.Peer, error) { + + if len(pm.peers) == 0 { + return nil, errors.New("Peer manager currently has no peers") + } + + return pm.peers[0], pm.peers[0].RequestBlocks(hash) +} + +// AddPeer will add a new peer for the PeerManager to use +func (pm *PeerMgr) AddPeer(p *peer.Peer) error { + pm.peers = append(pm.peers, p) + fmt.Println("Adding peers into the peermanager") + return nil +} diff --git a/pkg/pubsub/event.go b/pkg/pubsub/event.go new file mode 100644 index 000000000..c24893a40 --- /dev/null +++ b/pkg/pubsub/event.go @@ -0,0 +1,14 @@ +package pubsub + +type EventType int + +const ( + NewBlock EventType = iota // When blockchain connects a new block, it will emit an NewBlock Event + BadBlock // When blockchain declines a block, it will emit a new block event + BadHeader // When blockchain rejects a Header, it will emit this event +) + +type Event struct { + Type EventType // E.g. event.NewBlock + data []byte // Raw information +} diff --git a/pkg/pubsub/pub.go b/pkg/pubsub/pub.go new file mode 100644 index 000000000..a0eacb182 --- /dev/null +++ b/pkg/pubsub/pub.go @@ -0,0 +1,20 @@ +package pubsub + +type Publisher struct { + subs []Subscriber +} + +// Send iterates over each subscriber and checks +// if they are interested in the Event +// By looking at their topics, if they are then +// the event is emitted to them +func (p *Publisher) Send(e Event) error { + for _, sub := range p.subs { + for _, topic := range sub.Topics() { + if e.Type == topic { + sub.Emit(e) + } + } + } + return nil +} diff --git a/pkg/pubsub/sub.go b/pkg/pubsub/sub.go new file mode 100644 index 000000000..21a51eb30 --- /dev/null +++ b/pkg/pubsub/sub.go @@ -0,0 +1,6 @@ +package pubsub + +type Subscriber interface { + Topics() []EventType + Emit(Event) +} diff --git a/pkg/syncmanager/config.go b/pkg/syncmanager/config.go new file mode 100644 index 000000000..9b8cbbf4a --- /dev/null +++ b/pkg/syncmanager/config.go @@ -0,0 +1,11 @@ +package syncmanager + +import ( + "github.com/CityOfZion/neo-go/pkg/blockchain" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Config struct { + Chain *blockchain.Chain + BestHash util.Uint256 +} diff --git a/pkg/syncmanager/syncman.go b/pkg/syncmanager/syncman.go new file mode 100644 index 000000000..2c6d863d1 --- /dev/null +++ b/pkg/syncmanager/syncman.go @@ -0,0 +1,152 @@ +// The syncmanager will use a modified verison of the initial block download in bitcoin +// Seen here: https://en.bitcoinwiki.org/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download +// MovingWindow is a desired featured from the original codebase + +package syncmanager + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/peermanager" + + "github.com/CityOfZion/neo-go/pkg/blockchain" + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + // This is the maximum amount of inflight objects that we would like to have + // Number taken from original codebase + maxBlockRequest = 1024 + + // This is the maximum amount of blocks that we will ask for from a single peer + // Number taken from original codebase + maxBlockRequestPerPeer = 16 +) + +type Syncmanager struct { + pmgr *peermanager.PeerMgr + Mode int // 1 = headersFirst, 2 = Blocks, 3 = Maintain + chain *blockchain.Chain + headers []util.Uint256 + inflightBlockReqs map[util.Uint256]*peer.Peer // when we send a req for block, we will put hash in here, along with peer who we requested it from +} + +// New will setup the syncmanager with the required +// parameters +func New(cfg Config) *Syncmanager { + return &Syncmanager{ + peermanager.New(), + 1, + cfg.Chain, + []util.Uint256{}, + make(map[util.Uint256]*peer.Peer, 2000), + } +} + +func (s *Syncmanager) AddPeer(peer *peer.Peer) error { + return s.pmgr.AddPeer(peer) +} + +func (s *Syncmanager) OnHeaders(p *peer.Peer, msg *payload.HeadersMessage) { + fmt.Println("Sync manager On Headers called") + // On receipt of Headers + // check what mode we are in + // HeadersMode, we check if there is 2k. If so call again. If not then change mode into BlocksOnly + if s.Mode == 1 { + err := s.HeadersFirstMode(p, msg) + if err != nil { + fmt.Println("Error re blocks", err) + return // We should custom name error so, that we can do something on WrongHash Error, Peer disconnect error + } + return + } +} + +func (s *Syncmanager) HeadersFirstMode(p *peer.Peer, msg *payload.HeadersMessage) error { + + fmt.Println("Headers first mode") + + // Validate Headers + err := s.chain.ValidateHeaders(msg) + + if err != nil { + // Re-request headers from a different peer + s.pmgr.Disconnect(p) + fmt.Println("Error Validating headers", err) + return err + } + + // Add Headers into db + err = s.chain.AddHeaders(msg) + if err != nil { + // Try addding them into the db again? + // Since this is simply a db insert, any problems here means trouble + //TODO(KEV) : Should we Switch off system or warn the user that the system is corrupted? + fmt.Println("Error Adding headers", err) + + //TODO: Batching is not yet implemented, + // So here we would need to remove headers which have been added + // from the slice + return err + } + + // Add header hashes into slice + // Requets first batch of blocks here + var hashes []util.Uint256 + for _, header := range msg.Headers { + hashes = append(hashes, header.Hash) + } + s.headers = append(s.headers, hashes...) + + if len(msg.Headers) == 2*1e3 { // should be less than 2000, leave it as this for tests + fmt.Println("Switching to BlocksOnly Mode") + s.Mode = 2 // switch to BlocksOnly. XXX: because HeadersFirst is not in parallel, no race condition here. + return s.RequestMoreBlocks() + } + lastHeader := msg.Headers[len(msg.Headers)-1] + _, err = s.pmgr.RequestHeaders(lastHeader.Hash) + return err +} + +func (s *Syncmanager) RequestMoreBlocks() error { + + var blockReq []util.Uint256 + + var reqAmount int + + if len(s.headers) >= maxBlockRequestPerPeer { + reqAmount = maxBlockRequestPerPeer + blockReq = s.headers[:reqAmount] + } else { + reqAmount = len(s.headers) + blockReq = s.headers[:reqAmount] + } + peer, err := s.pmgr.RequestBlocks(blockReq) + if err != nil { // This could happen if the peermanager has no valid peers to connect to. We should wait a bit and re-request + return err // alternatively we could make RequestBlocks blocking, then make sure it is not triggered when a block is received + } + + //XXX: Possible race condition, between us requesting the block and adding it to + // the inflight block map? Give that node a medal. + + for _, hash := range s.headers { + s.inflightBlockReqs[hash] = peer + } + s.headers = s.headers[reqAmount:] + // NONONO: Here we do not pass all of the hashes to peermanager because + // it is not the peermanagers responsibility to mange inflight blocks + return err +} + +// OnBlock receives a block from a peer, then passes it to the blockchain to process. +// For now we will only use this simple setup, to allow us to test the other parts of the system. +// See Issue #24 +func (s *Syncmanager) OnBlock(p *peer.Peer, msg *payload.BlockMessage) { + err := s.chain.AddBlock(msg) + if err != nil { + // Put headers back in front of queue to fetch block for. + fmt.Println("Block had an error", err) + } +} diff --git a/pkg/wire/Readme.md b/pkg/wire/Readme.md new file mode 100644 index 000000000..e121047bd --- /dev/null +++ b/pkg/wire/Readme.md @@ -0,0 +1,62 @@ +# Package - Wire + + +The neo wire package will implement the network protocol displayed here: http://docs.neo.org/en-us/network/network-protocol.html + +This package will act as a standalone package. + +# Responsibility + +This package will solely be responsible for Encoding and decoding a Message. +It will return a Messager interface, which means that the caller of the package will need to type assert it to the appropriate type. + +# Usage + +## Write Message + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + message, err := NewVersionMessage(tcpAddrMe, 0, true, defaultVersion) + + conn := new(bytes.Buffer) + if err := WriteMessage(con, Production, message); err != nil { + // handle Error + } + +## Read Message + + readmsg, err := ReadMessage(conn, Production) + + if err != nil { + // Log error + } + version := readmsg.(*VersionMessage) + +## RoadMap + +These below commands are left to implement. + + [ x ] CMDVersion (Tests added) + [ x ] CMDVerack (Tests Added) + [ x ] CMDGetAddr(Tests Added) + [ x ] CMDAddr (Tests Added) + [ x ] CMDGetHeaders (Tests Added) + [ x ] CMDHeaders (Tests Added) + [ x ] CMDGetBlocks (Tests Added) + [ x ] CMDInv (Tests Added) + [ x ] CMDGetData (Tests Added) + [ x ] CMDBlock (Tests Added) + [ x ] CMDTX // Each tx implments the messager interface + [ ] CMDConsensus + +## Notes + +Please not that this package will do sanity checks on the fields, however it will not verify if any of the items are valid for the current state of the system. Please see `Responbilities`. + +The difference between Encode/Decode and EncodePayload/DecodePayload, is the parameter type. +In most cases, Encode/Decode is just a convenience method. +# Contributors + +When modifying this package, please ensure that it does not depend on any other package and that it conforms to the Single Responsibility Principle. If you see somewhere in the current implementation that does not do this, then please tell me. + diff --git a/pkg/wire/base.go b/pkg/wire/base.go new file mode 100644 index 000000000..1d778f45e --- /dev/null +++ b/pkg/wire/base.go @@ -0,0 +1,44 @@ +package wire + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Base is everything in the message except the payload + +type Base struct { + Magic uint32 + CMD command.Type + PayloadLength uint32 + Checksum uint32 +} + +// Note, That there is no EncodeBase +// As the header is implicitly inferred from +// the message on Encode To send +func (h *Base) DecodeBase(r io.Reader) (io.Reader, error) { + br := &util.BinReader{R: r} + + br.Read(&h.Magic) + + var cmd [12]byte + br.Read(&cmd) + h.CMD = command.Type(cmdByteArrayToString(cmd)) + + br.Read(&h.PayloadLength) + br.Read(&h.Checksum) + return br.R, br.Err +} + +func cmdByteArrayToString(cmd [command.Size]byte) string { + buf := []byte{} + for i := 0; i < command.Size; i++ { + if cmd[i] != 0 { + buf = append(buf, cmd[i]) + } + } + return string(buf) +} diff --git a/pkg/wire/command/command.go b/pkg/wire/command/command.go new file mode 100644 index 000000000..3d9b749fe --- /dev/null +++ b/pkg/wire/command/command.go @@ -0,0 +1,30 @@ +package command + +// Size of command field in bytes +const ( + Size = 12 +) + +// CommandType represents the type of a message command. +type Type string + +// Valid protocol commands used to send between nodes. +// use this to get +const ( + Version Type = "version" + Mempool Type = "mempool" + Ping Type = "ping" + Pong Type = "pong" + Verack Type = "verack" + GetAddr Type = "getaddr" + Addr Type = "addr" + GetHeaders Type = "getheaders" + Headers Type = "headers" + GetBlocks Type = "getblocks" + Inv Type = "inv" + GetData Type = "getdata" + Block Type = "block" + TX Type = "tx" + Consensus Type = "consensus" + Unknown Type = "unknown" +) diff --git a/pkg/wire/message.go b/pkg/wire/message.go new file mode 100644 index 000000000..d02c5ce92 --- /dev/null +++ b/pkg/wire/message.go @@ -0,0 +1,148 @@ +package wire + +import ( + "bufio" + "bytes" + "errors" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + checksum "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Messager interface { + EncodePayload(w io.Writer) error + DecodePayload(r io.Reader) error + Command() command.Type +} + +const ( + // Magic + cmd + length + checksum + minMsgSize = 4 + 12 + 4 + 4 +) + +var ( + errChecksumMismatch = errors.New("checksum mismatch") +) + +func WriteMessage(w io.Writer, magic protocol.Magic, message Messager) error { + bw := &util.BinWriter{W: w} + bw.Write(magic) + bw.Write(cmdToByteArray(message.Command())) + + buf := new(bytes.Buffer) + if err := message.EncodePayload(buf); err != nil { + return err + } + + payloadLen := util.BufferLength(buf) + checksum := checksum.FromBytes(buf.Bytes()) + + bw.Write(payloadLen) + bw.Write(checksum) + + bw.WriteBigEnd(buf.Bytes()) + + return bw.Err +} + +func ReadMessage(r io.Reader, magic protocol.Magic) (Messager, error) { + + byt := make([]byte, minMsgSize) + + if _, err := io.ReadFull(r, byt); err != nil { + return nil, err + } + + reader := bytes.NewReader(byt) + + var header Base + _, err := header.DecodeBase(reader) + + if err != nil { + return nil, errors.New("Error decoding into the header base") + } + + buf := new(bytes.Buffer) + + _, err = io.CopyN(buf, r, int64(header.PayloadLength)) + if err != nil { + return nil, err + } + + // Compare the checksum of the payload. + if !checksum.Compare(header.Checksum, buf.Bytes()) { + return nil, errChecksumMismatch + } + switch header.CMD { + case command.Version: + v := &payload.VersionMessage{} + err := v.DecodePayload(buf) + return v, err + case command.Verack: + v, err := payload.NewVerackMessage() + err = v.DecodePayload(buf) + return v, err + case command.Inv: + v, err := payload.NewInvMessage(0) + err = v.DecodePayload(buf) + return v, err + case command.GetAddr: + v, err := payload.NewGetAddrMessage() + err = v.DecodePayload(buf) + return v, err + case command.Addr: + v, err := payload.NewAddrMessage() + err = v.DecodePayload(buf) + return v, err + case command.Block: + v, err := payload.NewBlockMessage() + err = v.DecodePayload(buf) + return v, err + case command.GetBlocks: + v, err := payload.NewGetBlocksMessage([]util.Uint256{}, util.Uint256{}) + err = v.DecodePayload(buf) + return v, err + case command.GetData: + v, err := payload.NewGetDataMessage(payload.InvTypeTx) + err = v.DecodePayload(buf) + return v, err + case command.GetHeaders: + v, err := payload.NewGetHeadersMessage([]util.Uint256{}, util.Uint256{}) + err = v.DecodePayload(buf) + return v, err + case command.Headers: + v, err := payload.NewHeadersMessage() + err = v.DecodePayload(buf) + return v, err + case command.TX: + reader := bufio.NewReader(buf) + tx, err := transaction.FromBytes(reader) + if err != nil { + return nil, err + } + return payload.NewTXMessage(tx) + } + return nil, errors.New("Unknown Message found") + +} + +func cmdToByteArray(cmd command.Type) [command.Size]byte { + cmdLen := len(cmd) + if cmdLen > command.Size { + panic("exceeded command max length of size 12") + } + + // The command can have max 12 bytes, rest is filled with 0. + b := [command.Size]byte{} + for i := 0; i < cmdLen; i++ { + b[i] = cmd[i] + } + + return b +} diff --git a/pkg/wire/message_test.go b/pkg/wire/message_test.go new file mode 100644 index 000000000..320653801 --- /dev/null +++ b/pkg/wire/message_test.go @@ -0,0 +1,55 @@ +package wire + +import ( + "bytes" + "net" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/stretchr/testify/assert" +) + +// This is quite hard to test because the message uses time.Now() +// TODO: Test each field expect time.Now(), just make sure it is a uint32 +func TestWriteMessageLen(t *testing.T) { + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + + message, err := payload.NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService) + if err != nil { + assert.Fail(t, err.Error()) + } + + buf := new(bytes.Buffer) + if err := WriteMessage(buf, protocol.MainNet, message); err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, 60, len(buf.Bytes())) +} +func TestReadMessage(t *testing.T) { + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + + message, err := payload.NewVersionMessage(tcpAddrMe, 23, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService) + if err != nil { + assert.Fail(t, err.Error()) + } + buf := new(bytes.Buffer) + if err := WriteMessage(buf, protocol.MainNet, message); err != nil { + assert.Fail(t, err.Error()) + } + + readmsg, err := ReadMessage(buf, protocol.MainNet) + + if err != nil { + assert.Fail(t, err.Error()) + } + version := readmsg.(*payload.VersionMessage) + assert.Equal(t, 23, int(version.StartHeight)) + // If MessageReading was unsuccessfull it will return a nil object +} diff --git a/pkg/wire/payload/block.go b/pkg/wire/payload/block.go new file mode 100644 index 000000000..7dc4e000e --- /dev/null +++ b/pkg/wire/payload/block.go @@ -0,0 +1,59 @@ +package payload + +import ( + "bufio" + "bytes" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Block struct { + BlockBase + Txs []transaction.Transactioner +} + +func (b *Block) Decode(r io.Reader) error { + br := &util.BinReader{R: r} + b.DecodePayload(br) + return br.Err +} +func (b *Block) Encode(w io.Writer) error { + bw := &util.BinWriter{W: w} + b.EncodePayload(bw) + return bw.Err +} +func (b *Block) EncodePayload(bw *util.BinWriter) { + b.BlockBase.EncodePayload(bw) + bw.VarUint(uint64(len(b.Txs))) + for _, tx := range b.Txs { + tx.Encode(bw.W) + } +} + +func (b *Block) DecodePayload(br *util.BinReader) error { + + b.BlockBase.DecodePayload(br) + lenTXs := br.VarUint() + + b.Txs = make([]transaction.Transactioner, lenTXs) + + reader := bufio.NewReader(br.R) + for i := 0; i < int(lenTXs); i++ { + + tx, err := transaction.FromBytes(reader) + if err != nil { + return err + } + b.Txs[i] = tx + + } + return nil +} + +func (b *Block) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := b.Encode(buf) + return buf.Bytes(), err +} diff --git a/pkg/wire/payload/block_test.go b/pkg/wire/payload/block_test.go new file mode 100644 index 000000000..2de7cdb4e --- /dev/null +++ b/pkg/wire/payload/block_test.go @@ -0,0 +1,80 @@ +package payload + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/stretchr/testify/assert" +) + +func TestBlockDecodeEncode(t *testing.T) { + // transaction taken from mainnet: 2000000 + rawtx := "00000000e5a49e24ee36e972e1bbee16c6897b88050e95e40db157d901cbb68de5243dc93482b51e7ce810eca512afe201768668de5910d4373db067418ad1cf95cd291de424a15a80841e00ec3bd62b5562099d59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd450140f9ef37e9a31614d0c42aca576d11fcd2ca4cade56143e725ab45e2c7372601e5322a89e0585b44f6f436147be6dc6513ebe781c358abadb1336cadc8f1fdf2e4407fedf529ec4b16ada7fec16efcb377e9c0ea515b12b98a8bed01c385999f8f6121dd5fad32abe4d95dc0c11e9a3a6ce093a7f550b96b779c45f584022bb8a93640d266010bee43509f70c9e7d86cd5037214718de5682abeb42141d1691a1595e5ee188393c26b9ca9f31e4db2d87c3c76869c4b02d081672909268e4d53bcc850401866a84eafd9003c17f1469f1830c5c5f2976da54991f7a1ed292a8af0de2ce202d8f15cb0f362f0ae0ee8bf43886785db45fed0d77b5254503ac105e694a7ac40bfc7166d3495ad4ab540e287ec51afc0569f292e106055b13765d6dacc1ed14807eb63cfeb04b50977c2a64735a4d7496c95f361b773dc58ae29a11b8183f717f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae120000ec3bd62b0000000080000001e91406db8ce12ff273d9c04cb0f224870d43eeb53c7522a6ad47b0c2ad9614cc0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5005ed0b200000000606fe152cafd010c1911b9c7932b77562d13b02f01414054f533865c49ae27ee9898f18c3ad18e2bb27928af9dbfdca44713b666f343e06416fe04869c3b5355934b101ac40caa4910cebdcfc3fec7322d12b0aa4d5bd7232103dfe98cbad29e3116324a5125a32b36250679190f74dca7425c40fff589cc530cac800000012ae528ca6f1c1a740a716603aac1e167cad482d49c41e4a356565cefa50b5dbd0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5008d190207000000be87c8c3cb58ca27e5235c5d451474435b895386014140fe20765111b9845d7d842cf09205931ea35e0de5d7078c3831e2b36e41e2298a1cf3d8bc697676ae48a577d70757ecf262085ed314c2d077e0e7a11830dc1b7d232103f2ad7aebe26d46541cb99deb3ff06fd24f0b87efa7e52a1aba6a2d3167be6044acd1015b0600a0724e1809146063795d3b9b3cd55aef026eae992b91063db0db1458f310994f5561f8759ebbdab2e87a05fa4d256553c1087472616e7366657267f91d6b7085db7c5aaf09f19eeec1ca3c0db2c6ecf166aacd14a39b8747f90000000000000000012058f310994f5561f8759ebbdab2e87a05fa4d25650000014140664803b370fb1125fccb44b470a9062d2f1108f4b9c43b7aa35fd56eb52c0564c72e649eefacf82bfb25d252aef5e677307bd0142cecc60e369265b3d053a8d9232103a95af1ee45fe9a2cbad6358e3357da7a13e994361b385c72141ca5a47ef993f6ac800000028190ef017f831707830dd8dc2867572136e7d73bdc13a7421433072e36830c6c00008190ef017f831707830dd8dc2867572136e7d73bdc13a7421433072e36830c6c010001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c600300000000000000494a6c348bdcbf192fb1a88ee41883fcdd11ef37014140a2dc5f161bcb696deeae6f078702627280cfc360d5bf0cae13a417212da2bc87c207d58cca83deb3e6fd76c05bc135367eb520e0138fbfc25e05985ddfa514352321039beeb554e21dfdd414fd07844460d2d1117d26e6b6d18314c535ef25511cee16ac8000000144a1e8637a27d17be06f4d5e4483841c5db27420757350833e970861cf0f6c650500019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c817a80400000034a3393450b5217977ba2efebad0dd6c37bb0a690141409f6eec2a9983ae782e3d7bdf8ce02f2c9b6db4660ee93197ed8ae85ae9ef424d421ba35a3b70738618b255ab387fe32a0dee1ccd6c0bb8d1e306e3947c383557232103994c84c3cbbc3a75be1045a2ce795d2d5733bb8425f2580e19ff7f179bb8719bac80000001e04c80df4a53b8ba9dca435630f1170d3cc6f15f508e98006ef44bf9f38de82d0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500bca06501000000d32f28fd47cc176e3d2bf6ac30eb87dfb838ef99014140b8c50990b976e837033eb09da821dae396782bd0df404e1e0675f2db9b7fc6b681b1cb6cb717a546268363f63ae65f89a18769d201b531fee4afd78b2fffb9aa232103ef4c441d2c4cd1a2e3f255366d149553a7f8f799edf1a49a0bc1af0081cc65beac80000001a6f0562becc89fd0ab4c36e2bcc4eedeb6de9fa6bb398be22dafc4e0512b55bc0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50001b2c4000000001decb55a65ad7d23a4d474a1e75ff24c9a71dcc4014140175ea6f8e03e7fbf4254ba16ccdd06a6d6c71b7b4d3965bc0913cf02f16ce3180dc278cde426f493225c4e51db28d934da120f5ef2b723585c5aafc8493071e4232103f3215374a4384eb8e2a60635f1e8394ccbe1e21a7b90e0063f5d5ac1836402ceacd100530800fee9e85100000014f9dfa019739920e7ab28544e979c0e6613f54536140a551a5ab4acf6a31dc6fc2a91d05684424a620e53c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce0001243726662530e8d5576aced9b76cc57b078a9e2857e1ae1bd04869a608657f00000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000000a551a5ab4acf6a31dc6fc2a91d05684424a620e014140fd662c8eeaeb3fdc5e9906277c79e31e521b2aa503f0e0684ce876283b27949b2366cd5034a74d7b0b0c47e0648b57a2d569378ee0f82d2bdb3717c4cb3d5c4c2321032204ffdc2173ee04c61f3dbe1ba1ca743106f7cccc7cf6fff86be5dcd6ac63d6ac02000134c42a75a09535438eda8df28e58f8fdc7c5d3329ed6f1d9a845627f7beb53410000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60429415000000000046a030db679a5ce51ac083c73b44f0ff6729561c01414065eb2a2a52e6c34ce3a2925ec03b958eeda5cc310468f198ac9c7c111f9a62778214e9b8234c92aef5dba75a29536d6b58b8a074eeb547c53a0e26dc0eaadb86232102670e8d99a3c28d947ddfaa76416a4f53dbcde3ee3d271b1bc78941d5f6d7b829acd1015b0600a0724e1809146063795d3b9b3cd55aef026eae992b91063db0db1458f310994f5561f8759ebbdab2e87a05fa4d256553c1087472616e7366657267f91d6b7085db7c5aaf09f19eeec1ca3c0db2c6ecf1669fb73cc8cde6c72f0000000000000000012058f310994f5561f8759ebbdab2e87a05fa4d25650000014140a1cfd844d2623cfb2b867ec8bf24d3951bd7fcdd5de1e9d59a97b3e240f80020cc7612a3a7dfa32696b7837a1a38f85d27227f76a45b0e6c956abb8b68183177232103a95af1ee45fe9a2cbad6358e3357da7a13e994361b385c72141ca5a47ef993f6ac800000016ab96ecf9f3a32fd8e209d2b5650fb2d12c39bc89493421bdd290d69bbe74d540000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500cf14130400000088eb48d2d1c288c0982ae188b13d5a2a994ab254014140e3269b08dfc3409031a24b7493376a9597eca37a6d6da868fda33005b596accb6e2b7ad4f137d597420d29f080ee9ba545935b47e6358f3f8c1f9118b4e4a085232102e93db927a1f516b37962c4262e78ad56b20e4d349cb1908ee612526ccd0d1118acd10153088005a34c17000000147f808fa952008067570e27dfe0fdc97f647964a51439de68a3e72a015de6ea5543dc896d8609c1c45153c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce00000000000000000001cd018dd92f5209d47590d74fdd3f982e1ac86d202c12bc948a2890cdb857cff1000002e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60010000000000000039de68a3e72a015de6ea5543dc896d8609c1c451e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60a9301b000000000039de68a3e72a015de6ea5543dc896d8609c1c4510141407fad0f5501e9ce3fa9bef9d378257ce233f41d5fd4955e69367ad57392f9feac7608d90553e3e285785cf87fe01b8ab1266d9c73de1e33ceff1c51133735e39b2321028ad2a0b73a8624cc027d80b6ef4af9de5167740aede9cb6401ccc0faef7244bbac0200012d5a5b03a06bbcb891054ae72e64e2d9a3e46077fecf8d2d6af4496771671d910000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60a0490100000000008e7778b7ff900a0b82779d0d768cb63b11dcd685014140c54d9d6148503a45de3c037aa6c08ccede953246bdac680a9e9f4ca2414cb02ee130c9a246771191df6bda8796f01f432bfaa464f331137d75865d0c3310f4dc2321036f97d99a1fa298e82ab6ccdedd6febd72fa5b3fa415e09f47354f3cecec51f07acd101530800ba1dd20500000014e31c39cf87027016bf0b925cb6db0699e41d235514bd12389c3eede155f28d6f72c44cb70756aff3f253c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce0000000000000000000191c12f3ad7a6dc3b8c9cfb6e47d5adb94ef31e76b8dbac538a20a103ba11931f000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c600100000000000000bd12389c3eede155f28d6f72c44cb70756aff3f201414026034a105f0a694f0320b617f112eea7b21a268c54d383e8964e9045e970b8bcc020c3c12e99d560657f2f5874d0a508748bf362bccb0d15f0c9a2c22641ceba2321036200e224b07b5f88b934e36933e5c31826d3b2dc9a5a0a72a555cd5fd70feda5acd1015308803b081117000000141be8765dcb5600ac58a6cc9be39f8d5734c4f1d3148cba604700afc229870ec93ae4f2c25e78f95c4f53c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce00000000000000000001d4d309fddde1101278ec53443cabb902cbbb2330ce4a16b0876c7f2c411990b5000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000008cba604700afc229870ec93ae4f2c25e78f95c4f01414057755879205922936aef9d641f7ac2fd59a9605c01ae5e2cf730ededcc59ef60eaf5d1a5f150c5bd33c3c78b86e441d4d464856bf30ca7b9d89f5299c86d3d7e23210237965fbbff6457d897a9763d6bf97c8f700735e938fa21236aa23e648e641494ac02000109992fadaed0cf0609e42d5fea6fc5057a340893b756c19d73bd0ed92109a0a00000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c606027000000000000fa1f3b71b6332c5880e1b2808f86e3bf730fbdec0141409ee7a22eee8a502ea3bdd264ba81f36cf7360332456fae92f50d4d89c06cbeda083c61562af8c279e91e490bbabc2e4bee481d85887ff5a36f8dbb26d1308fa22321036980dc27f23a7e3c0ea0fe50f66f6b37a7d21ffaa66d5571ce6d4d317e22a5e7ac80000001cba3499f1640f91f7074593e632d18224e5ca94054c63a72cc3c208fe76a65960100019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500aaa0680b0000007c2f0bc324085be8b5dcf68bc8ed142620b18d850141406020880ded8ae65e6b8af680882f4c3e702cb41f9dcd8a898ea69a32077d05a0503df9d70ff1e71a400dd9a7a2222df9ab348e6cd501473b05add04fb028f960232102f4830c08c254e6e81839396c44c039639f1e30b6e53878269b567d9253590aacac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + b := Block{} + + r := bytes.NewReader(rawtxBytes) + b.Decode(r) + + expected := map[string]bool{ // 18 trans + + "009f61f481f47eb7478e887871e4e744669d461b13d68e04250035260171d706": false, + "3a62e473c1d67ac561b98e8131f7f7ceded4cd250edb78a6814ec9915930ad93": false, + "d56a545d2f9400c09d5aa4e8cc37cc994d5a6892f9c30de95ff69a3b647b27a2": false, + "57f6baa9cb23ce9117d66aee7c31ba6d1e82e140a805db1c5992ada849f6a7c7": false, + "f700be9c723ed44900ac9d70874b9d8601033bb78883c0a43ab38b5d96c09c11": false, + "31674ac8553b371ddf06db6a3aef09b8d6f37da03a8cc2868b71044c54ef0034": false, + "44858de48ec97cea2f823128e9d58981dde11f28a6ebf0a2cb745ea13223dd71": false, + "317f3ff3768b2aebe3d4866f6e0e8b875cc7937a1b8b5f91be066dc51ed61be2": false, + "8c24f44f1533567c71e722f49bc7a4d9b323a09e2950fd975291817578119508": false, + "55a7a738aaee8f7e6d7bcd4c8a38813e57763bff8bfb296418b6cac6d5bfb89a": false, + "dfa5f84366cf0b48f1b1e9b24a73557e657f6ac21b676528401f5a630aece571": false, + "5839fbcbbca68aef41dfa9a371222565519626affad6be0977d38a82259480a5": false, + "6873568cae35e4ce0a7d07ef080ef6eb699b2b9dcbc419fad1c4f645ff8579fc": false, + "dbb3c0688003bede7e7bc56d2c9d6362b594512ac686820739d963ef91e2eb9e": false, + "3d12353cb8bae8be928131580e960a82f37ca3ad6957ad22c8cadc1b21b2dd1a": false, + "8ca87fd5843f000939244151ce027bad5c1f30f1867c7054918b7f9a66b949e8": false, + "ad088940e45a73e00a3cdb7f3248c67a3f6e5d1f05d4cfd44c4e1f4d26cfef87": false, + "908a398dd65dfd2aad6c06090c5a71d5e5280746577a6ddd5a1f2c1453f71ead": false, + } + + hashes := []string{} + + for _, tx := range b.Txs { + switch t := tx.(type) { + case *transaction.Contract: + hashes = append(hashes, t.Hash.String()) + case *transaction.Miner: + hashes = append(hashes, t.Hash.String()) + case *transaction.Claim: + hashes = append(hashes, t.Hash.String()) + case *transaction.Invocation: + hashes = append(hashes, t.Hash.String()) + } + } + + assert.Equal(t, len(expected), len(hashes)) + + // changes value in map to true, if hash found + for _, hash := range hashes { + expected[hash] = true + } + + // iterate map; all vlaues should be true + val := true + for _, v := range expected { + if v == false { + val = false + } + } + assert.Equal(t, true, val) + + buf := new(bytes.Buffer) + + b.Encode(buf) + + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/wire/payload/blockbase.go b/pkg/wire/payload/blockbase.go new file mode 100644 index 000000000..2feec8ec0 --- /dev/null +++ b/pkg/wire/payload/blockbase.go @@ -0,0 +1,124 @@ +package payload + +import ( + "bytes" + "errors" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + ErrPadding = errors.New("There is a padding mismatch") +) + +type BlockBase struct { + // Version of the block. + Version uint32 `json:"version"` + + // hash of the previous block. + PrevHash util.Uint256 `json:"previousblockhash"` + + // Root hash of a transaction list. + MerkleRoot util.Uint256 `json:"merkleroot"` + + // The time stamp of each block must be later than previous block's time stamp. + // Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed. + // The height of the block must be exactly equal to the height of the previous block plus 1. + Timestamp uint32 `json:"time"` + + // index/height of the block + Index uint32 `json:"height"` + + // Random number also called nonce + ConsensusData uint64 `json:"nonce"` + + // Contract addresss of the next miner + NextConsensus util.Uint160 `json:"next_consensus"` + + // Padding that is fixed to 1 + _ uint8 + + // Script used to validate the block + Witness transaction.Witness `json:"script"` + + // hash of this block, created when binary encoded. + Hash util.Uint256 +} + +func (b *BlockBase) EncodePayload(bw *util.BinWriter) error { + + b.encodeHashableFields(bw) + + bw.Write(uint8(1)) + b.Witness.Encode(bw) + + return bw.Err +} + +func (b *BlockBase) Decode(r io.Reader) error { + br := &util.BinReader{R: r} + b.DecodePayload(br) + return br.Err +} +func (b *BlockBase) Encode(w io.Writer) error { + bw := &util.BinWriter{W: w} + b.EncodePayload(bw) + return bw.Err +} + +func (b *BlockBase) encodeHashableFields(bw *util.BinWriter) { + bw.Write(b.Version) + bw.Write(b.PrevHash) + bw.Write(b.MerkleRoot) + bw.Write(b.Timestamp) + bw.Write(b.Index) + bw.Write(b.ConsensusData) + bw.Write(b.NextConsensus) +} + +func (b *BlockBase) DecodePayload(br *util.BinReader) error { + + b.decodeHashableFields(br) + + var padding uint8 + br.Read(&padding) + if padding != 1 { + return ErrPadding + } + + b.Witness = transaction.Witness{} + b.Witness.Decode(br) + + err := b.createHash() + + if err != nil { + return err + } + + return br.Err +} + +func (b *BlockBase) decodeHashableFields(br *util.BinReader) { + br.Read(&b.Version) + br.Read(&b.PrevHash) + br.Read(&b.MerkleRoot) + br.Read(&b.Timestamp) + br.Read(&b.Index) + br.Read(&b.ConsensusData) + br.Read(&b.NextConsensus) +} + +func (b *BlockBase) createHash() error { + + hash, err := util.CalculateHash(b.encodeHashableFields) + b.Hash = hash + return err +} + +func (b *BlockBase) Bytes() ([]byte, error) { + buf := new(bytes.Buffer) + err := b.Encode(buf) + return buf.Bytes(), err +} diff --git a/pkg/wire/payload/blockbase_test.go b/pkg/wire/payload/blockbase_test.go new file mode 100644 index 000000000..09241399f --- /dev/null +++ b/pkg/wire/payload/blockbase_test.go @@ -0,0 +1,8 @@ +package payload + +import "testing" + +func Test(t *testing.T) { + //tests for this have been included in the mheaders_test file + +} diff --git a/pkg/wire/payload/maddr.go b/pkg/wire/payload/maddr.go new file mode 100644 index 000000000..674013dfa --- /dev/null +++ b/pkg/wire/payload/maddr.go @@ -0,0 +1,61 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type AddrMessage struct { + AddrList []*Net_addr +} + +func NewAddrMessage() (*AddrMessage, error) { + addrMess := &AddrMessage{ + nil, + } + return addrMess, nil +} + +func (a *AddrMessage) AddNetAddr(n *Net_addr) error { + a.AddrList = append(a.AddrList, n) + // TODO:check if max reached, if so return err. What is max? + + return nil +} + +// Implements Messager interface +func (a *AddrMessage) DecodePayload(r io.Reader) error { + + br := &util.BinReader{R: r} + listLen := br.VarUint() + + a.AddrList = make([]*Net_addr, listLen) + for i := 0; i < int(listLen); i++ { + a.AddrList[i] = &Net_addr{} + a.AddrList[i].DecodePayload(br) + if br.Err != nil { + return br.Err + } + } + return br.Err +} + +// Implements messager interface +func (v *AddrMessage) EncodePayload(w io.Writer) error { + bw := &util.BinWriter{W: w} + + listLen := uint64(len(v.AddrList)) + bw.VarUint(listLen) + + for _, addr := range v.AddrList { + addr.EncodePayload(bw) + } + return bw.Err +} + +// Implements messager interface +func (v *AddrMessage) Command() command.Type { + return command.Addr +} diff --git a/pkg/wire/payload/maddr_test.go b/pkg/wire/payload/maddr_test.go new file mode 100644 index 000000000..100972f6b --- /dev/null +++ b/pkg/wire/payload/maddr_test.go @@ -0,0 +1,40 @@ +package payload + +import ( + "bytes" + "net" + "testing" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum" + + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/stretchr/testify/assert" +) + +func TestAddrMessageEncodeDecode(t *testing.T) { + + ip := []byte(net.ParseIP("127.0.0.1").To16()) + + var ipByte [16]byte + copy(ipByte[:], ip) + + netaddr, err := NewNetAddr(uint32(time.Now().Unix()), ipByte, 8080, protocol.NodePeerService) + addrmsg, err := NewAddrMessage() + addrmsg.AddNetAddr(netaddr) + + buf := new(bytes.Buffer) + err = addrmsg.EncodePayload(buf) + expected := checksum.FromBuf(buf) + + addrmsgDec, err := NewAddrMessage() + r := bytes.NewReader(buf.Bytes()) + err = addrmsgDec.DecodePayload(r) + + buf = new(bytes.Buffer) + err = addrmsgDec.EncodePayload(buf) + have := checksum.FromBuf(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, expected, have) +} diff --git a/pkg/wire/payload/mblock.go b/pkg/wire/payload/mblock.go new file mode 100644 index 000000000..399d28965 --- /dev/null +++ b/pkg/wire/payload/mblock.go @@ -0,0 +1,36 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +type BlockMessage struct { + Block +} + +func NewBlockMessage() (*BlockMessage, error) { + return &BlockMessage{}, nil +} + +// Implements Messager interface +func (b *BlockMessage) DecodePayload(r io.Reader) error { + br := &util.BinReader{R: r} + b.Block.DecodePayload(br) + return br.Err +} + +// Implements messager interface +func (b *BlockMessage) EncodePayload(w io.Writer) error { + bw := &util.BinWriter{W: w} + b.Block.EncodePayload(bw) + return bw.Err +} + +// Implements messager interface +func (v *BlockMessage) Command() command.Type { + return command.Block +} diff --git a/pkg/wire/payload/mgetaddr.go b/pkg/wire/payload/mgetaddr.go new file mode 100644 index 000000000..02473ba12 --- /dev/null +++ b/pkg/wire/payload/mgetaddr.go @@ -0,0 +1,29 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +// No payload +type GetAddrMessage struct{} + +func NewGetAddrMessage() (*GetAddrMessage, error) { + return &GetAddrMessage{}, nil +} + +// Implements Messager interface +func (v *GetAddrMessage) DecodePayload(r io.Reader) error { + return nil +} + +// Implements messager interface +func (v *GetAddrMessage) EncodePayload(w io.Writer) error { + return nil +} + +// Implements messager interface +func (v *GetAddrMessage) Command() command.Type { + return command.GetAddr +} diff --git a/pkg/wire/payload/mgetaddr_test.go b/pkg/wire/payload/mgetaddr_test.go new file mode 100644 index 000000000..db6491d5a --- /dev/null +++ b/pkg/wire/payload/mgetaddr_test.go @@ -0,0 +1,24 @@ +package payload + +import ( + "bytes" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/stretchr/testify/assert" +) + +func TestNewGetAddr(t *testing.T) { + + getAddrMessage, err := NewGetAddrMessage() + assert.Equal(t, nil, err) + + assert.Equal(t, command.GetAddr, getAddrMessage.Command()) + + buf := new(bytes.Buffer) + + assert.Equal(t, int(3806393949), int(checksum.FromBuf(buf))) + assert.Equal(t, int(0), len(buf.Bytes())) +} diff --git a/pkg/wire/payload/mgetblocks.go b/pkg/wire/payload/mgetblocks.go new file mode 100644 index 000000000..660e17d96 --- /dev/null +++ b/pkg/wire/payload/mgetblocks.go @@ -0,0 +1,22 @@ +package payload + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type GetBlocksMessage struct { + *GetHeadersMessage +} + +func NewGetBlocksMessage(start []util.Uint256, stop util.Uint256) (*GetBlocksMessage, error) { + GetHeaders, err := newAbstractGetHeaders(start, stop, command.GetBlocks) + + if err != nil { + return nil, err + } + return &GetBlocksMessage{ + GetHeaders, + }, nil + +} diff --git a/pkg/wire/payload/mgetblocks_test.go b/pkg/wire/payload/mgetblocks_test.go new file mode 100644 index 000000000..d413bfc9a --- /dev/null +++ b/pkg/wire/payload/mgetblocks_test.go @@ -0,0 +1,27 @@ +package payload + +import ( + "crypto/sha256" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/stretchr/testify/assert" +) + +func TestGetBlocksCommandType(t *testing.T) { + var ( + start = []util.Uint256{ + sha256.Sum256([]byte("a")), + sha256.Sum256([]byte("b")), + sha256.Sum256([]byte("c")), + sha256.Sum256([]byte("d")), + } + stop = sha256.Sum256([]byte("e")) + ) + + getBlocks, err := NewGetBlocksMessage(start, stop) + + assert.Equal(t, err, nil) + assert.Equal(t, command.GetBlocks, getBlocks.Command()) +} diff --git a/pkg/wire/payload/mgetdata.go b/pkg/wire/payload/mgetdata.go new file mode 100644 index 000000000..d13fbab30 --- /dev/null +++ b/pkg/wire/payload/mgetdata.go @@ -0,0 +1,16 @@ +package payload + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +type GetDataMessage struct { + *InvMessage +} + +func NewGetDataMessage(typ InvType) (*GetDataMessage, error) { + getData, err := newAbstractInv(typ, command.GetData) + return &GetDataMessage{ + getData, + }, err +} diff --git a/pkg/wire/payload/mgetdata_test.go b/pkg/wire/payload/mgetdata_test.go new file mode 100644 index 000000000..44c4918b4 --- /dev/null +++ b/pkg/wire/payload/mgetdata_test.go @@ -0,0 +1,19 @@ +package payload + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/stretchr/testify/assert" +) + +func TestGetDataCommandType(t *testing.T) { + getData, err := NewGetDataMessage(InvTypeBlock) + + assert.Equal(t, err, nil) + assert.Equal(t, command.GetData, getData.Command()) +} + +func TestOtherFunctions(t *testing.T) { + // Other capabilities are tested in the inherited struct +} diff --git a/pkg/wire/payload/mgetheaders.go b/pkg/wire/payload/mgetheaders.go new file mode 100644 index 000000000..09d91c5e0 --- /dev/null +++ b/pkg/wire/payload/mgetheaders.go @@ -0,0 +1,59 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type GetHeadersMessage struct { + cmd command.Type + hashStart []util.Uint256 + hashStop util.Uint256 +} + +// Start contains the list of all headers you want to fetch +// End contains the list of the highest header hash you would like to fetch +func NewGetHeadersMessage(start []util.Uint256, stop util.Uint256) (*GetHeadersMessage, error) { + getHeaders := &GetHeadersMessage{command.GetHeaders, start, stop} + + return getHeaders, nil + +} + +func newAbstractGetHeaders(start []util.Uint256, stop util.Uint256, cmd command.Type) (*GetHeadersMessage, error) { + getHeaders, err := NewGetHeadersMessage(start, stop) + + if err != nil { + return nil, err + } + getHeaders.cmd = cmd + return getHeaders, nil +} + +// Implements Messager interface +func (v *GetHeadersMessage) DecodePayload(r io.Reader) error { + + br := util.BinReader{R: r} + lenStart := br.VarUint() + v.hashStart = make([]util.Uint256, lenStart) + br.Read(&v.hashStart) + br.Read(&v.hashStop) + + return br.Err +} + +// Implements messager interface +func (v *GetHeadersMessage) EncodePayload(w io.Writer) error { + bw := &util.BinWriter{W: w} + bw.VarUint(uint64(len(v.hashStart))) + bw.Write(v.hashStart) + bw.Write(v.hashStop) + return bw.Err +} + +// Implements messager interface +func (v *GetHeadersMessage) Command() command.Type { + return v.cmd +} diff --git a/pkg/wire/payload/mgetheaders_test.go b/pkg/wire/payload/mgetheaders_test.go new file mode 100644 index 000000000..7ba0d6a10 --- /dev/null +++ b/pkg/wire/payload/mgetheaders_test.go @@ -0,0 +1,47 @@ +package payload + +import ( + "bytes" + "crypto/sha256" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/stretchr/testify/assert" +) + +// Test taken from neo-go v1 +func TestGetHeadersEncodeDecode(t *testing.T) { + + var ( + start = []util.Uint256{ + sha256.Sum256([]byte("a")), + sha256.Sum256([]byte("b")), + sha256.Sum256([]byte("c")), + sha256.Sum256([]byte("d")), + } + stop = sha256.Sum256([]byte("e")) + ) + msgGetHeaders, err := NewGetHeadersMessage(start, stop) + assert.Equal(t, nil, err) + + buf := new(bytes.Buffer) + + err = msgGetHeaders.EncodePayload(buf) + assert.Equal(t, nil, err) + expected := checksum.FromBuf(buf) + + msgGetHeadersDec, err := NewGetHeadersMessage([]util.Uint256{}, util.Uint256{}) + assert.Equal(t, nil, err) + + r := bytes.NewReader(buf.Bytes()) + err = msgGetHeadersDec.DecodePayload(r) + assert.Equal(t, nil, err) + + buf = new(bytes.Buffer) + err = msgGetHeadersDec.EncodePayload(buf) + have := checksum.FromBuf(buf) + + assert.Equal(t, expected, have) +} diff --git a/pkg/wire/payload/mheaders.go b/pkg/wire/payload/mheaders.go new file mode 100644 index 000000000..b7d008d3d --- /dev/null +++ b/pkg/wire/payload/mheaders.go @@ -0,0 +1,78 @@ +package payload + +import ( + "errors" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type HeadersMessage struct { + Headers []*BlockBase + + // Padding that is fixed to 0 + _ uint8 +} + +// Users can at most request 2k header +const ( + maxHeadersAllowed = 2000 +) + +var ( + ErrMaxHeaders = errors.New("Maximum amount of headers allowed is 2000") +) + +func NewHeadersMessage() (*HeadersMessage, error) { + + headers := &HeadersMessage{nil, 0} + return headers, nil +} + +func (h *HeadersMessage) AddHeader(head *BlockBase) error { + if len(h.Headers)+1 > maxHeadersAllowed { + return ErrMaxHeaders + } + h.Headers = append(h.Headers, head) + + return nil +} + +// Implements Messager interface +func (v *HeadersMessage) DecodePayload(r io.Reader) error { + + br := &util.BinReader{R: r} + + lenHeaders := br.VarUint() + v.Headers = make([]*BlockBase, lenHeaders) + + for i := 0; i < int(lenHeaders); i++ { + header := &BlockBase{} + header.DecodePayload(br) + var padding uint8 + br.Read(&padding) + if padding != 0 { + return ErrPadding + } + v.Headers[i] = header + } + + return br.Err +} + +// Implements messager interface +func (v *HeadersMessage) EncodePayload(w io.Writer) error { + bw := &util.BinWriter{W: w} + bw.VarUint(uint64(len(v.Headers))) + for _, header := range v.Headers { + header.EncodePayload(bw) + bw.Write(uint8(0)) + } + return bw.Err +} + +// Implements messager interface +func (v *HeadersMessage) Command() command.Type { + return command.Headers +} diff --git a/pkg/wire/payload/mheaders_test.go b/pkg/wire/payload/mheaders_test.go new file mode 100644 index 000000000..a03f43486 --- /dev/null +++ b/pkg/wire/payload/mheaders_test.go @@ -0,0 +1,82 @@ +package payload + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/CityOfZion/neo-go/pkg/wire/util/address" + "github.com/stretchr/testify/assert" +) + +func TestNewHeaderMessage(t *testing.T) { + msgHeaders, err := NewHeadersMessage() + + assert.Equal(t, nil, err) + assert.Equal(t, 0, len(msgHeaders.Headers)) + +} + +func TestAddAndEncodeHeaders(t *testing.T) { + + // uses block10 from mainnet + + msgHeaders, _ := NewHeadersMessage() + + prevH, _ := util.Uint256DecodeString("005fb74a6de169ce5daf59a114405e5b27238b2489690e3b2a60c14ddfc3b326") + merkleRoot, _ := util.Uint256DecodeString("ca6d58bcb837472c2f77877e68495b83fd5b714dfe0c8230a525f4511a3239f4") + invocationScript, _ := hex.DecodeString("4036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495a") + verificationScript, _ := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae") + nextCon, _ := util.Uint160DecodeString(address.ToScriptHash("APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR")) + + msgHeaders.AddHeader(&BlockBase{ + Version: 0, + Index: 10, + PrevHash: prevH.Reverse(), + MerkleRoot: merkleRoot.Reverse(), + Timestamp: 1476647551, + ConsensusData: 0xc0f0280216ff14bf, + NextConsensus: nextCon, + Witness: transaction.Witness{ + InvocationScript: invocationScript, + VerificationScript: verificationScript, + }, + }) + + assert.Equal(t, 1, len(msgHeaders.Headers)) + + err := msgHeaders.Headers[0].createHash() + assert.Equal(t, nil, err) + // Hash being correct, automatically verifies that the fields are encoded properly + assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", msgHeaders.Headers[0].Hash.String()) + +} + +func TestEncodeDecode(t *testing.T) { + rawBlockHeaders := "010000000026b3c3df4dc1602a3b0e6989248b23275b5e4014a159af5dce69e16d4ab75f00f439321a51f425a530820cfe4d715bfd835b49687e87772f2c4737b8bc586dca7fda03580a000000bf14ff160228f0c059e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd45014036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495af1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae00" + + var headerMsg HeadersMessage + + rawBlockBytes, _ := hex.DecodeString(rawBlockHeaders) + + r := bytes.NewReader(rawBlockBytes) + + err := headerMsg.DecodePayload(r) + + assert.Equal(t, 1, len(headerMsg.Headers)) + + header := headerMsg.Headers[0] + err = header.createHash() + + assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", header.Hash.String()) + + buf := new(bytes.Buffer) + + err = headerMsg.EncodePayload(buf) + + assert.Equal(t, nil, err) + + assert.Equal(t, hex.EncodeToString(rawBlockBytes), hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/wire/payload/minventory.go b/pkg/wire/payload/minventory.go new file mode 100644 index 000000000..d2f4207dd --- /dev/null +++ b/pkg/wire/payload/minventory.go @@ -0,0 +1,110 @@ +package payload + +import ( + "errors" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type InvType uint8 + +//Inventory types +const ( + InvTypeTx InvType = 0x01 + InvTypeBlock InvType = 0x02 + InvTypeConsensus InvType = 0xe0 +) + +const ( + maxHashes = 0x10000000 +) + +var ( + MaxHashError = errors.New("Max size For Hashes reached") +) + +type InvMessage struct { + cmd command.Type + Type InvType + Hashes []util.Uint256 +} + +func NewInvMessage(typ InvType) (*InvMessage, error) { + + inv := &InvMessage{ + command.Inv, + typ, + nil, + } + return inv, nil +} + +func newAbstractInv(typ InvType, cmd command.Type) (*InvMessage, error) { + inv, err := NewInvMessage(typ) + + if err != nil { + return nil, err + } + inv.cmd = cmd + + return inv, nil + +} + +func (i *InvMessage) AddHash(h util.Uint256) error { + if len(i.Hashes)+1 > maxHashes { + return MaxHashError + } + i.Hashes = append(i.Hashes, h) + return nil +} +func (i *InvMessage) AddHashes(hashes []util.Uint256) error { + var err error + for _, hash := range hashes { + err = i.AddHash(hash) + if err != nil { + break + } + } + return err +} + +// Implements Messager interface +func (v *InvMessage) DecodePayload(r io.Reader) error { + br := &util.BinReader{R: r} + + br.Read(&v.Type) + + listLen := br.VarUint() + v.Hashes = make([]util.Uint256, listLen) + + for i := 0; i < int(listLen); i++ { + br.Read(&v.Hashes[i]) + } + return nil +} + +// Implements messager interface +func (v *InvMessage) EncodePayload(w io.Writer) error { + + bw := &util.BinWriter{W: w} + bw.Write(v.Type) + + lenhashes := len(v.Hashes) + bw.VarUint(uint64(lenhashes)) + + for _, hash := range v.Hashes { + + bw.Write(hash) + + } + + return bw.Err +} + +// Implements messager interface +func (v *InvMessage) Command() command.Type { + return v.cmd +} diff --git a/pkg/wire/payload/minventory_test.go b/pkg/wire/payload/minventory_test.go new file mode 100644 index 000000000..9e9c82dc9 --- /dev/null +++ b/pkg/wire/payload/minventory_test.go @@ -0,0 +1,78 @@ +package payload + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/util" + + "github.com/stretchr/testify/assert" +) + +func TestNewInventory(t *testing.T) { + msgInv, err := NewInvMessage(InvTypeBlock) + + assert.Equal(t, nil, err) + assert.Equal(t, command.Inv, msgInv.Command()) + + hash, _ := util.Uint256DecodeBytes([]byte("hello")) + err = msgInv.AddHash(hash) + assert.Equal(t, nil, err) +} + +// Adjust test time or it will timeout +// func TestMaxHashes(t *testing.T) { +// msgInv, err := NewInvMessage(InvTypeBlock) +// assert.Equal(t, nil, err) + +// hash, _ := util.Uint256DecodeBytes([]byte("hello")) + +// for i := 0; i <= maxHashes+1; i++ { +// err = msgInv.AddHash(hash) +// } +// if err == nil { +// assert.Fail(t, "Max Hashes Exceeded, only allowed %v but have %v", maxHashes, len(msgInv.Hashes)) +// } else if err != MaxHashError { +// assert.Fail(t, "Expected a MaxHashError, however we got %s", err.Error()) +// } +// } +func TestEncodeDecodePayload(t *testing.T) { + msgInv, err := NewInvMessage(InvTypeBlock) + assert.Equal(t, nil, err) + + blockOneHash := "d782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9" + hash, _ := util.Uint256DecodeString(blockOneHash) + + err = msgInv.AddHash(hash) + assert.Equal(t, nil, err) + + buf := new(bytes.Buffer) + err = msgInv.EncodePayload(buf) + assert.Equal(t, nil, err) + + numOfHashes := []byte{1} + expected := append([]byte{uint8(InvTypeBlock)}, numOfHashes...) + expected = append(expected, hash.Bytes()...) + + assert.Equal(t, hex.EncodeToString(expected), hex.EncodeToString(buf.Bytes())) + + var InvDec InvMessage + r := bytes.NewReader(buf.Bytes()) + err = InvDec.DecodePayload(r) + assert.Equal(t, nil, err) + + assert.Equal(t, 1, len(InvDec.Hashes)) + assert.Equal(t, blockOneHash, hex.EncodeToString(InvDec.Hashes[0].Bytes())) + +} +func TestEmptyInv(t *testing.T) { + msgInv, err := NewInvMessage(InvTypeBlock) + assert.Equal(t, nil, err) + + buf := new(bytes.Buffer) + msgInv.EncodePayload(buf) + assert.Equal(t, []byte{byte(InvTypeBlock), 0}, buf.Bytes()) + assert.Equal(t, 0, len(msgInv.Hashes)) +} diff --git a/pkg/wire/payload/mmempool.go b/pkg/wire/payload/mmempool.go new file mode 100644 index 000000000..5408c826b --- /dev/null +++ b/pkg/wire/payload/mmempool.go @@ -0,0 +1,29 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +// No payload +type GetMempool struct{} + +func NewGetMempool() (*GetMempool, error) { + return &GetMempool{}, nil +} + +// Implements Messager interface +func (v *GetMempool) DecodePayload(r io.Reader) error { + return nil +} + +// Implements messager interface +func (v *GetMempool) EncodePayload(w io.Writer) error { + return nil +} + +// Implements messager interface +func (v *GetMempool) Command() command.Type { + return command.Mempool +} diff --git a/pkg/wire/payload/mtx.go b/pkg/wire/payload/mtx.go new file mode 100644 index 000000000..8b6c08b28 --- /dev/null +++ b/pkg/wire/payload/mtx.go @@ -0,0 +1,34 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" +) + +type TXMessage struct { + // w *bytes.Buffer + Tx transaction.Transactioner +} + +func NewTXMessage(tx transaction.Transactioner) (*TXMessage, error) { + + Tx := &TXMessage{tx} + return Tx, nil +} + +// Implements Messager interface +func (t *TXMessage) DecodePayload(r io.Reader) error { + return t.Tx.Decode(r) +} + +// Implements messager interface +func (t *TXMessage) EncodePayload(w io.Writer) error { + return t.Tx.Encode(w) +} + +// Implements messager interface +func (v *TXMessage) Command() command.Type { + return command.TX +} diff --git a/pkg/wire/payload/mverack.go b/pkg/wire/payload/mverack.go new file mode 100644 index 000000000..249579415 --- /dev/null +++ b/pkg/wire/payload/mverack.go @@ -0,0 +1,29 @@ +package payload + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/command" +) + +// No payload +type VerackMessage struct{} + +func NewVerackMessage() (*VerackMessage, error) { + return &VerackMessage{}, nil +} + +// Implements Messager interface +func (v *VerackMessage) DecodePayload(r io.Reader) error { + return nil +} + +// Implements messager interface +func (v *VerackMessage) EncodePayload(w io.Writer) error { + return nil +} + +// Implements messager interface +func (v *VerackMessage) Command() command.Type { + return command.Verack +} diff --git a/pkg/wire/payload/mverack_test.go b/pkg/wire/payload/mverack_test.go new file mode 100644 index 000000000..5c4832e73 --- /dev/null +++ b/pkg/wire/payload/mverack_test.go @@ -0,0 +1,17 @@ +package payload + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + + "github.com/stretchr/testify/assert" +) + +func TestNewVerack(t *testing.T) { + + verackMessage, err := NewVerackMessage() + + assert.Equal(t, nil, err) + assert.Equal(t, command.Verack, verackMessage.Command()) +} diff --git a/pkg/wire/payload/mversion.go b/pkg/wire/payload/mversion.go new file mode 100644 index 000000000..1a7cff06e --- /dev/null +++ b/pkg/wire/payload/mversion.go @@ -0,0 +1,97 @@ +// Copied and Modified for NEO from: https://github.com/decred/dcrd/blob/master/wire/VersionMessage.go + +package payload + +import ( + "errors" + "io" + "net" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +const ( + minMsgVersionSize = 28 +) + +// TODO: Refactor to pull out the useragent out of initialiser +// and have a seperate method to add it + +type VersionMessage struct { + // w *bytes.Buffer + Version protocol.Version + Timestamp uint32 + Services protocol.ServiceFlag + IP net.IP + Port uint16 + Nonce uint32 + UserAgent []byte + StartHeight uint32 + Relay bool +} + +var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr") + +func NewVersionMessage(addr net.Addr, startHeight uint32, relay bool, pver protocol.Version, userAgent string, nonce uint32, services protocol.ServiceFlag) (*VersionMessage, error) { + + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return nil, ErrInvalidNetAddr + } + + version := &VersionMessage{ + pver, + uint32(time.Now().Unix()), + services, + tcpAddr.IP, + uint16(tcpAddr.Port), + nonce, + []byte(userAgent), + startHeight, + relay, + } + return version, nil +} + +// Implements Messager interface +func (v *VersionMessage) DecodePayload(r io.Reader) error { + br := &util.BinReader{R: r} + br.Read(&v.Version) + br.Read(&v.Services) + br.Read(&v.Timestamp) + br.Read(&v.Port) // Port is not BigEndian + br.Read(&v.Nonce) + + var lenUA uint8 + br.Read(&lenUA) + + v.UserAgent = make([]byte, lenUA) + br.Read(&v.UserAgent) + br.Read(&v.StartHeight) + br.Read(&v.Relay) + return br.Err +} + +// Implements messager interface +func (v *VersionMessage) EncodePayload(w io.Writer) error { + bw := &util.BinWriter{W: w} + + bw.Write(v.Version) + bw.Write(v.Services) + bw.Write(v.Timestamp) + bw.Write(v.Port) // Not big End + bw.Write(v.Nonce) + bw.Write(uint8(len(v.UserAgent))) + bw.Write(v.UserAgent) + bw.Write(v.StartHeight) + bw.Write(v.Relay) + return bw.Err +} + +// Implements messager interface +func (v *VersionMessage) Command() command.Type { + return command.Version +} diff --git a/pkg/wire/payload/mversion_test.go b/pkg/wire/payload/mversion_test.go new file mode 100644 index 000000000..9f7b1c129 --- /dev/null +++ b/pkg/wire/payload/mversion_test.go @@ -0,0 +1,59 @@ +package payload + +import ( + "bytes" + "math/rand" + "net" + "testing" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/stretchr/testify/assert" +) + +func TestValidNewVersionMessage(t *testing.T) { + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + nonce := randRange(12949672, 42949672) + message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService) + + assert.Equal(t, nil, err) + assert.Equal(t, expectedIP, message.IP.String()) + assert.Equal(t, uint16(expectedPort), message.Port) + assert.Equal(t, protocol.DefaultVersion, message.Version) +} +func TestEncode(t *testing.T) { + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + nonce := randRange(12949672, 42949672) + message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService) + + buf := new(bytes.Buffer) + err = message.EncodePayload(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, int(buf.Len())) +} +func TestLenIsCorrect(t *testing.T) { + + expectedIP := "127.0.0.1" + expectedPort := 8333 + tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort} + nonce := randRange(12949672, 42949672) + message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService) + + buf := new(bytes.Buffer) + err = message.EncodePayload(buf) + assert.Equal(t, nil, err) + + assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, len(buf.Bytes())) +} + +func randRange(min, max int) uint32 { + rand.Seed(time.Now().Unix() + int64(rand.Uint64())) + return uint32(rand.Intn(max-min) + min) +} diff --git a/pkg/wire/payload/net_addr.go b/pkg/wire/payload/net_addr.go new file mode 100644 index 000000000..5e1c528d0 --- /dev/null +++ b/pkg/wire/payload/net_addr.go @@ -0,0 +1,54 @@ +package payload + +import ( + "net" + "strconv" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Once a VersionMessage is received, we can then store it inside of AddrMessage struct +// TODO: store this inside version message and have a bool to indicate whether to encode ip +// VersionMessage does not encodeIP +type Net_addr struct { + Timestamp uint32 + IP [16]byte + Port uint16 + Service protocol.ServiceFlag +} + +func NewNetAddr(time uint32, ip [16]byte, port uint16, service protocol.ServiceFlag) (*Net_addr, error) { + return &Net_addr{time, ip, port, service}, nil +} + +func NewAddrFromVersionMessage(version VersionMessage) (*Net_addr, error) { + + var ip [16]byte + + copy(ip[:], []byte(version.IP)[:16]) + + return NewNetAddr(version.Timestamp, ip, version.Port, version.Services) +} + +func (n *Net_addr) EncodePayload(bw *util.BinWriter) { + + bw.Write(uint32(time.Now().Unix())) + bw.Write(protocol.NodePeerService) + bw.WriteBigEnd(n.IP) + bw.WriteBigEnd(n.Port) +} +func (n *Net_addr) DecodePayload(br *util.BinReader) { + + br.Read(&n.Timestamp) + br.Read(&n.Service) + br.ReadBigEnd(&n.IP) + br.ReadBigEnd(&n.Port) +} +func (n *Net_addr) IPPort() string { + ip := net.IP(n.IP[:]).String() + port := strconv.Itoa(int(n.Port)) + ipport := ip + ":" + port + return ipport +} diff --git a/pkg/wire/payload/transaction/Attribute.go b/pkg/wire/payload/transaction/Attribute.go new file mode 100644 index 000000000..64e98df34 --- /dev/null +++ b/pkg/wire/payload/transaction/Attribute.go @@ -0,0 +1,65 @@ +package transaction + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Attribute represents a Transaction attribute. +type Attribute struct { + Usage AttrUsage + Data []byte +} + +var ( + ErrMaxData = errors.New("Max Size of Attribute reached") +) + +const ( + maxAttrSize = 65535 +) + +func (a *Attribute) Encode(bw *util.BinWriter) { + if len(a.Data) > maxAttrSize { + bw.Err = ErrMaxData + return + } + bw.Write(uint8(a.Usage)) + + if a.Usage == DescriptionURL || a.Usage == Vote || (a.Usage >= Hash1 && a.Usage <= Hash15) { + bw.Write(a.Data[:32]) + + } else if a.Usage == Script { + bw.Write(a.Data[:20]) + } else if a.Usage == ECDH02 || a.Usage == ECDH03 { + bw.Write(a.Data[1:33]) + } else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { + bw.VarUint(uint64(len(a.Data))) + bw.Write(a.Data) + } else { + bw.Write(a.Data) + } + +} + +func (a *Attribute) Decode(br *util.BinReader) { + br.Read(&a.Usage) + if a.Usage == DescriptionURL || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 { + a.Data = make([]byte, 32) + br.Read(&a.Data) + + } else if a.Usage == Script { + a.Data = make([]byte, 20) + br.Read(&a.Data) + } else if a.Usage == ECDH02 || a.Usage == ECDH03 { + a.Data = make([]byte, 32) + br.Read(&a.Data) + } else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { + lenData := br.VarUint() + a.Data = make([]byte, lenData) + br.Read(&a.Data) + } else { + br.Read(&a.Data) + } +} diff --git a/pkg/wire/payload/transaction/Input.go b/pkg/wire/payload/transaction/Input.go new file mode 100644 index 000000000..788e47960 --- /dev/null +++ b/pkg/wire/payload/transaction/Input.go @@ -0,0 +1,28 @@ +package transaction + +import "github.com/CityOfZion/neo-go/pkg/wire/util" + +// Input represents a Transaction input. +type Input struct { + // The hash of the previous transaction. + PrevHash util.Uint256 + + // The index of the previous transaction. + PrevIndex uint16 +} + +func NewInput(prevHash util.Uint256, prevIndex uint16) *Input { + return &Input{ + prevHash, + prevIndex, + } +} +func (i *Input) Encode(bw *util.BinWriter) { + bw.Write(i.PrevHash) + bw.Write(i.PrevIndex) +} + +func (i *Input) Decode(br *util.BinReader) { + br.Read(&i.PrevHash) + br.Read(&i.PrevIndex) +} diff --git a/pkg/wire/payload/transaction/Output.go b/pkg/wire/payload/transaction/Output.go new file mode 100644 index 000000000..9af346dc3 --- /dev/null +++ b/pkg/wire/payload/transaction/Output.go @@ -0,0 +1,34 @@ +package transaction + +import "github.com/CityOfZion/neo-go/pkg/wire/util" + +type Output struct { + // The NEO asset id used in the transaction. + AssetID util.Uint256 + + // Amount of AssetType send or received. + Amount int64 + + // The address of the remittee. + ScriptHash util.Uint160 +} + +func NewOutput(assetID util.Uint256, Amount int64, ScriptHash util.Uint160) *Output { + return &Output{ + assetID, + Amount, + ScriptHash, + } +} + +func (o *Output) Encode(bw *util.BinWriter) { + bw.Write(o.AssetID) + bw.Write(o.Amount) + bw.Write(o.ScriptHash) +} + +func (o *Output) Decode(br *util.BinReader) { + br.Read(&o.AssetID) + br.Read(&o.Amount) + br.Read(&o.ScriptHash) +} diff --git a/pkg/wire/payload/transaction/Witness.go b/pkg/wire/payload/transaction/Witness.go new file mode 100644 index 000000000..8b0d2fa0b --- /dev/null +++ b/pkg/wire/payload/transaction/Witness.go @@ -0,0 +1,34 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Witness struct { + InvocationScript []byte + VerificationScript []byte +} + +func (s *Witness) Encode(bw *util.BinWriter) error { + + bw.VarUint(uint64(len(s.InvocationScript))) + bw.Write(s.InvocationScript) + + bw.VarUint(uint64(len(s.VerificationScript))) + bw.Write(s.VerificationScript) + + return bw.Err +} + +func (s *Witness) Decode(br *util.BinReader) error { + + lenb := br.VarUint() + s.InvocationScript = make([]byte, lenb) + br.Read(s.InvocationScript) + + lenb = br.VarUint() + s.VerificationScript = make([]byte, lenb) + br.Read(s.VerificationScript) + + return br.Err +} diff --git a/pkg/wire/payload/transaction/assettype.go b/pkg/wire/payload/transaction/assettype.go new file mode 100644 index 000000000..82fc4913a --- /dev/null +++ b/pkg/wire/payload/transaction/assettype.go @@ -0,0 +1,16 @@ +package transaction + +// AssetType represent a NEO asset type +type AssetType uint8 + +// Valid asset types. +const ( + CreditFlag AssetType = 0x40 + DutyFlag AssetType = 0x80 + GoverningToken AssetType = 0x00 + UtilityToken AssetType = 0x01 + Currency AssetType = 0x08 + Share AssetType = DutyFlag | 0x10 + Invoice AssetType = DutyFlag | 0x18 + Token AssetType = CreditFlag | 0x20 +) diff --git a/pkg/wire/payload/transaction/attr_usage.go b/pkg/wire/payload/transaction/attr_usage.go new file mode 100644 index 000000000..b8f3a3469 --- /dev/null +++ b/pkg/wire/payload/transaction/attr_usage.go @@ -0,0 +1,48 @@ +package transaction + +type AttrUsage uint8 + +// List of valid attribute usages. +const ( + ContractHash AttrUsage = 0x00 + ECDH02 AttrUsage = 0x02 + ECDH03 AttrUsage = 0x03 + Script AttrUsage = 0x20 + Vote AttrUsage = 0x30 + CertURL AttrUsage = 0x80 + DescriptionURL AttrUsage = 0x81 + Description AttrUsage = 0x90 + + Hash1 AttrUsage = 0xa1 + Hash2 AttrUsage = 0xa2 + Hash3 AttrUsage = 0xa3 + Hash4 AttrUsage = 0xa4 + Hash5 AttrUsage = 0xa5 + Hash6 AttrUsage = 0xa6 + Hash7 AttrUsage = 0xa7 + Hash8 AttrUsage = 0xa8 + Hash9 AttrUsage = 0xa9 + Hash10 AttrUsage = 0xaa + Hash11 AttrUsage = 0xab + Hash12 AttrUsage = 0xac + Hash13 AttrUsage = 0xad + Hash14 AttrUsage = 0xae + Hash15 AttrUsage = 0xaf + + Remark AttrUsage = 0xf0 + Remark1 AttrUsage = 0xf1 + Remark2 AttrUsage = 0xf2 + Remark3 AttrUsage = 0xf3 + Remark4 AttrUsage = 0xf4 + Remark5 AttrUsage = 0xf5 + Remark6 AttrUsage = 0xf6 + Remark7 AttrUsage = 0xf7 + Remark8 AttrUsage = 0xf8 + Remark9 AttrUsage = 0xf9 + Remark10 AttrUsage = 0xfa + Remark11 AttrUsage = 0xfb + Remark12 AttrUsage = 0xfc + Remark13 AttrUsage = 0xfd + Remark14 AttrUsage = 0xfe + Remark15 AttrUsage = 0xff +) diff --git a/pkg/wire/payload/transaction/base.go b/pkg/wire/payload/transaction/base.go new file mode 100644 index 000000000..9770e3bd1 --- /dev/null +++ b/pkg/wire/payload/transaction/base.go @@ -0,0 +1,204 @@ +package transaction + +import ( + "bytes" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type encodeExclusiveFields func(bw *util.BinWriter) +type decodeExclusiveFields func(br *util.BinReader) + +// Transactioner is the interface that will unite the +// transaction types. Each transaction will implement this interface +// and so wil be a transactioner +type Transactioner interface { + Encode(w io.Writer) error + Decode(r io.Reader) error + ID() (util.Uint256, error) + Bytes() []byte + UTXOs() []*Output + TXOs() []*Input + Witness() []*Witness +} + +// Base transaction is the template for all other transactions +// It contains all of the shared fields between transactions and +// the additional encodeExclusive and decodeExclusive methods, which +// can be overwitten in the other transactions to encode the non shared fields +type Base struct { + Type types.TX + Version version.TX + Inputs []*Input + Outputs []*Output + Attributes []*Attribute + Witnesses []*Witness + Hash util.Uint256 + encodeExclusive encodeExclusiveFields + decodeExclusive decodeExclusiveFields +} + +func createBaseTransaction(typ types.TX, ver version.TX) *Base { + return &Base{ + Type: typ, + Version: ver, + Inputs: []*Input{}, + Outputs: []*Output{}, + Attributes: []*Attribute{}, + Witnesses: []*Witness{}, + } + +} + +func (b *Base) Decode(r io.Reader) error { + br := &util.BinReader{R: r} + return b.DecodePayload(br) +} +func (b *Base) Encode(w io.Writer) error { + bw := &util.BinWriter{W: w} + b.EncodePayload(bw) + return bw.Err +} + +func (b *Base) EncodePayload(bw *util.BinWriter) { + b.encodeHashableFields(bw) + + lenWitnesses := uint64(len(b.Witnesses)) + bw.VarUint(lenWitnesses) + + for _, witness := range b.Witnesses { + witness.Encode(bw) + } +} + +func (b *Base) DecodePayload(br *util.BinReader) error { + b.decodeHashableFields(br) + + lenWitnesses := br.VarUint() + + b.Witnesses = make([]*Witness, lenWitnesses) + for i := 0; i < int(lenWitnesses); i++ { + b.Witnesses[i] = &Witness{} + b.Witnesses[i].Decode(br) + } + + if br.Err != nil { + return br.Err + } + + return b.createHash() +} + +func (b *Base) encodeHashableFields(bw *util.BinWriter) { + b.Type.Encode(bw) + b.Version.Encode(bw) + + b.encodeExclusive(bw) + + lenAttrs := uint64(len(b.Attributes)) + lenInputs := uint64(len(b.Inputs)) + lenOutputs := uint64(len(b.Outputs)) + + bw.VarUint(lenAttrs) + for _, attr := range b.Attributes { + attr.Encode(bw) + } + + bw.VarUint(lenInputs) + for _, input := range b.Inputs { + input.Encode(bw) + } + + bw.VarUint(lenOutputs) + for _, output := range b.Outputs { + output.Encode(bw) + } +} + +// created for consistency +func (b *Base) decodeHashableFields(br *util.BinReader) { + b.Type.Decode(br) + + b.Version.Decode(br) + + b.decodeExclusive(br) + + lenAttrs := br.VarUint() + b.Attributes = make([]*Attribute, lenAttrs) + for i := 0; i < int(lenAttrs); i++ { + + b.Attributes[i] = &Attribute{} + b.Attributes[i].Decode(br) + } + + lenInputs := br.VarUint() + + b.Inputs = make([]*Input, lenInputs) + for i := 0; i < int(lenInputs); i++ { + b.Inputs[i] = &Input{} + b.Inputs[i].Decode(br) + } + + lenOutputs := br.VarUint() + b.Outputs = make([]*Output, lenOutputs) + for i := 0; i < int(lenOutputs); i++ { + b.Outputs[i] = &Output{} + b.Outputs[i].Decode(br) + } + +} + +func (b *Base) AddInput(i *Input) { + b.Inputs = append(b.Inputs, i) +} +func (b *Base) AddOutput(o *Output) { + b.Outputs = append(b.Outputs, o) +} +func (b *Base) AddAttribute(a *Attribute) { + b.Attributes = append(b.Attributes, a) +} +func (b *Base) AddWitness(w *Witness) { + b.Witnesses = append(b.Witnesses, w) +} + +func (b *Base) createHash() error { + + hash, err := util.CalculateHash(b.encodeHashableFields) + b.Hash = hash + return err +} + +// ID returns the TXID of the transaction +func (b *Base) ID() (util.Uint256, error) { + var emptyHash util.Uint256 + var err error + if b.Hash == emptyHash { + err = b.createHash() + } + return b.Hash, err +} + +// Bytes returns the raw bytes of the tx +func (b *Base) Bytes() []byte { + buf := new(bytes.Buffer) + b.Encode(buf) + return buf.Bytes() +} + +// UTXOs returns the outputs in the tx +func (b *Base) UTXOs() []*Output { + return b.Outputs +} + +// TXOs returns the inputs in the tx +func (b *Base) TXOs() []*Input { + return b.Inputs +} + +// Witness returns the witnesses in the tx +func (b *Base) Witness() []*Witness { + return b.Witnesses +} diff --git a/pkg/wire/payload/transaction/claim.go b/pkg/wire/payload/transaction/claim.go new file mode 100644 index 000000000..d93e4263c --- /dev/null +++ b/pkg/wire/payload/transaction/claim.go @@ -0,0 +1,43 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Claim struct { + *Base + Claims []*Input +} + +func NewClaim(ver version.TX) *Claim { + basicTrans := createBaseTransaction(types.Contract, ver) + + claim := &Claim{} + claim.Base = basicTrans + claim.encodeExclusive = claim.encodeExcl + claim.decodeExclusive = claim.decodeExcl + return claim +} + +func (c *Claim) encodeExcl(bw *util.BinWriter) { + + bw.VarUint(uint64(len(c.Claims))) + for _, claim := range c.Claims { + claim.Encode(bw) + } +} + +func (c *Claim) decodeExcl(br *util.BinReader) { + lenClaims := br.VarUint() + + c.Claims = make([]*Input, lenClaims) + for i := 0; i < int(lenClaims); i++ { + c.Claims[i] = &Input{} + c.Claims[i].Decode(br) + } + +} + +// use encode and decode exclusive to make the interface diff --git a/pkg/wire/payload/transaction/claim_test.go b/pkg/wire/payload/transaction/claim_test.go new file mode 100644 index 000000000..fb049fb58 --- /dev/null +++ b/pkg/wire/payload/transaction/claim_test.go @@ -0,0 +1,38 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeClaim(t *testing.T) { + + // test taken from mainnet: abf142faf539c340e42722b5b34b505cf4fd73185fed775784e37c2c5ef1b866 + rawtx := "020001af1b3a0f3729572893ce4e82f2113d18ec9a5e9d6fe02117eaa9e0c5a43770490000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6048ae5801000000001123b6b74273562540479eea5cd0139f88ac7dd301414085f6d9edc24ab68c5d15c8e164de6702106c53bc15fa2c45b575bd3543c19132de61dd1922407be56affbcea73e5f8878811549340fd3c951e8593d51f3c8a962321028cf5e5a4d430db0202755c2cf1b3c99efcb4da4e41e182450dc5e1ddffb54bbfac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + c := NewClaim(0) + + r := bytes.NewReader(rawtxBytes) + err := c.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Claim, c.Type) + assert.Equal(t, 0, int(c.Version)) + assert.Equal(t, 1, int(len(c.Claims))) + + claim := c.Claims[0] + assert.Equal(t, "497037a4c5e0a9ea1721e06f9d5e9aec183d11f2824ece93285729370f3a1baf", claim.PrevHash.String()) + assert.Equal(t, uint16(0), claim.PrevIndex) + assert.Equal(t, "abf142faf539c340e42722b5b34b505cf4fd73185fed775784e37c2c5ef1b866", c.Hash.String()) + + // Encode + buf := new(bytes.Buffer) + err = c.Encode(buf) + assert.Equal(t, nil, err) + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/wire/payload/transaction/contract.go b/pkg/wire/payload/transaction/contract.go new file mode 100644 index 000000000..dc93ef0ef --- /dev/null +++ b/pkg/wire/payload/transaction/contract.go @@ -0,0 +1,30 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Contract struct { + *Base +} + +func NewContract(ver version.TX) *Contract { + basicTrans := createBaseTransaction(types.Contract, ver) + + contract := &Contract{ + basicTrans, + } + contract.encodeExclusive = contract.encodeExcl + contract.decodeExclusive = contract.decodeExcl + return contract +} + +func (c *Contract) encodeExcl(bw *util.BinWriter) { + return +} + +func (c *Contract) decodeExcl(br *util.BinReader) { + return +} diff --git a/pkg/wire/payload/transaction/contract_test.go b/pkg/wire/payload/transaction/contract_test.go new file mode 100644 index 000000000..ea0393301 --- /dev/null +++ b/pkg/wire/payload/transaction/contract_test.go @@ -0,0 +1,45 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeContract(t *testing.T) { + + // mainnet transaction: bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79 + rawtx := "80000001888da99f8f497fd65c4325786a09511159c279af4e7eb532e9edd628c87cc1ee0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50082167010000000a8666b4830229d6a1a9b80f6088059191c122d2b0141409e79e132290c82916a88f1a3db5cf9f3248b780cfece938ab0f0812d0e188f3a489c7d1a23def86bd69d863ae67de753b2c2392e9497eadc8eb9fc43aa52c645232103e2f6a334e05002624cf616f01a62cff2844c34a3b08ca16048c259097e315078ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + c := NewContract(30) + + r := bytes.NewReader(rawtxBytes) + err := c.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Contract, c.Type) + assert.Equal(t, 0, int(c.Version)) + assert.Equal(t, 1, int(len(c.Inputs))) + + input := c.Inputs[0] + + assert.Equal(t, "eec17cc828d6ede932b57e4eaf79c2591151096a7825435cd67f498f9fa98d88", input.PrevHash.String()) + assert.Equal(t, 0, int(input.PrevIndex)) + assert.Equal(t, int64(70600000000), c.Outputs[0].Amount) + assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", c.Outputs[0].AssetID.String()) + assert.Equal(t, "a8666b4830229d6a1a9b80f6088059191c122d2b", c.Outputs[0].ScriptHash.String()) + assert.Equal(t, "bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79", c.Hash.String()) + + // Encode + buf := new(bytes.Buffer) + + err = c.Encode(buf) + assert.Equal(t, nil, err) + + assert.Equal(t, rawtxBytes, buf.Bytes()) + +} diff --git a/pkg/wire/payload/transaction/enrollment.go b/pkg/wire/payload/transaction/enrollment.go new file mode 100644 index 000000000..317124783 --- /dev/null +++ b/pkg/wire/payload/transaction/enrollment.go @@ -0,0 +1,31 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Enrollment struct { + *Base + Key PublicKey +} + +func NewEnrollment(ver version.TX) *Enrollment { + basicTrans := createBaseTransaction(types.Enrollment, ver) + + Enrollment := &Enrollment{} + Enrollment.Base = basicTrans + Enrollment.encodeExclusive = Enrollment.encodeExcl + Enrollment.decodeExclusive = Enrollment.decodeExcl + return Enrollment +} + +func (e *Enrollment) encodeExcl(bw *util.BinWriter) { + e.Key.Encode(bw) + +} + +func (e *Enrollment) decodeExcl(br *util.BinReader) { + e.Key.Decode(br) +} diff --git a/pkg/wire/payload/transaction/enrollment_test.go b/pkg/wire/payload/transaction/enrollment_test.go new file mode 100644 index 000000000..cf76af8e7 --- /dev/null +++ b/pkg/wire/payload/transaction/enrollment_test.go @@ -0,0 +1,30 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeEnrollment(t *testing.T) { + rawtx := "200002ff8ac54687f36bbc31a91b730cc385da8af0b581f2d59d82b5cfef824fd271f60001d3d3b7028d61fea3b7803fda3d7f0a1f7262d38e5e1c8987b0313e0a94574151000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60005441d11600000050ac4949596f5b62fef7be4d1c3e494e6048ed4a01414079d78189d591097b17657a62240c93595e8233dc81157ea2cd477813f09a11fd72845e6bd97c5a3dda125985ea3d5feca387e9933649a9a671a69ab3f6301df6232102ff8ac54687f36bbc31a91b730cc385da8af0b581f2d59d82b5cfef824fd271f6ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + enroll := NewEnrollment(30) + + r := bytes.NewReader(rawtxBytes) + err := enroll.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Enrollment, enroll.Type) + + buf := new(bytes.Buffer) + err = enroll.Encode(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) + assert.Equal(t, "988832f693785dcbcb8d5a0e9d5d22002adcbfb1eb6bbeebf8c494fff580e147", enroll.Hash.String()) +} diff --git a/pkg/wire/payload/transaction/invocation.go b/pkg/wire/payload/transaction/invocation.go new file mode 100644 index 000000000..04fe85a89 --- /dev/null +++ b/pkg/wire/payload/transaction/invocation.go @@ -0,0 +1,59 @@ +package transaction + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/CityOfZion/neo-go/pkg/wire/util/fixed8" +) + +type Invocation struct { + *Base + Script []byte + Gas fixed8.Fixed8 +} + +func NewInvocation(ver version.TX) *Invocation { + basicTrans := createBaseTransaction(types.Invocation, ver) + + invocation := &Invocation{} + invocation.Base = basicTrans + invocation.encodeExclusive = invocation.encodeExcl + invocation.decodeExclusive = invocation.decodeExcl + return invocation +} + +func (c *Invocation) encodeExcl(bw *util.BinWriter) { + bw.VarUint(uint64(len(c.Script))) + bw.Write(c.Script) + + switch c.Version { + case 0: + c.Gas = fixed8.Fixed8(0) + case 1: + bw.Write(&c.Gas) + default: + bw.Write(&c.Gas) + } + + return +} + +func (c *Invocation) decodeExcl(br *util.BinReader) { + + lenScript := br.VarUint() + c.Script = make([]byte, lenScript) + br.Read(&c.Script) + + switch c.Version { + case 0: + c.Gas = fixed8.Fixed8(0) + case 1: + br.Read(&c.Gas) + default: + br.Err = errors.New("Invalid Version Number for Invocation Transaction") + } + return +} diff --git a/pkg/wire/payload/transaction/invocation_test.go b/pkg/wire/payload/transaction/invocation_test.go new file mode 100644 index 000000000..82ce2fcb6 --- /dev/null +++ b/pkg/wire/payload/transaction/invocation_test.go @@ -0,0 +1,44 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeInvoc(t *testing.T) { + // taken from mainnet b2a22cd9dd7636ae23e25576866cd1d9e2f3d85a85e80874441f085cd60006d1 + + rawtx := "d10151050034e23004141ad842821c7341d5a32b17d7177a1750d30014ca14628c9e5bc6a9346ca6bcdf050ceabdeb2bdc774953c1087472616e736665726703e1df72015bdef1a1b9567d4700635f23b1f406f100000000000000000220628c9e5bc6a9346ca6bcdf050ceabdeb2bdc7749f02f31363a30373a3032203a2030333366616431392d643638322d343035382d626437662d31356339333132343433653800000141403ced56c16f933e0a0a7d37470e114f6a4216ef9b834d61db67b74b9bd117370d10870857c0ee8adcf9956bc9fc92c5158de0c2db34ef459c17de042f20ad8fe92321027392870a5994b090d1750dda173a54df8dad324ed6d9ed25290d17c59059a112ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + i := NewInvocation(30) + + r := bytes.NewReader(rawtxBytes) + err := i.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Invocation, i.Type) + + assert.Equal(t, 2, len(i.Attributes)) + + attr1 := i.Attributes[0] + assert.Equal(t, Script, attr1.Usage) + assert.Equal(t, "628c9e5bc6a9346ca6bcdf050ceabdeb2bdc7749", hex.EncodeToString(attr1.Data)) + + attr2 := i.Attributes[1] + assert.Equal(t, Remark, attr2.Usage) + assert.Equal(t, "31363a30373a3032203a2030333366616431392d643638322d343035382d626437662d313563393331323434336538", hex.EncodeToString(attr2.Data)) + + assert.Equal(t, "050034e23004141ad842821c7341d5a32b17d7177a1750d30014ca14628c9e5bc6a9346ca6bcdf050ceabdeb2bdc774953c1087472616e736665726703e1df72015bdef1a1b9567d4700635f23b1f406f1", hex.EncodeToString(i.Script)) + assert.Equal(t, "b2a22cd9dd7636ae23e25576866cd1d9e2f3d85a85e80874441f085cd60006d1", i.Hash.String()) + + // Encode + buf := new(bytes.Buffer) + err = i.Encode(buf) + assert.Equal(t, nil, err) + assert.Equal(t, rawtxBytes, buf.Bytes()) +} diff --git a/pkg/wire/payload/transaction/issue.go b/pkg/wire/payload/transaction/issue.go new file mode 100644 index 000000000..c64f7bfb8 --- /dev/null +++ b/pkg/wire/payload/transaction/issue.go @@ -0,0 +1,35 @@ +package transaction + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Issue struct { + *Base +} + +func NewIssue(ver version.TX) *Issue { + basicTrans := createBaseTransaction(types.Issue, ver) + + Issue := &Issue{ + basicTrans, + } + Issue.encodeExclusive = Issue.encodeExcl + Issue.decodeExclusive = Issue.decodeExcl + return Issue +} + +func (c *Issue) encodeExcl(bw *util.BinWriter) { + if c.Version > 1 { + bw.Err = errors.New("Version Number Invalid, Issue cannot be more than 0") + } + return +} + +func (c *Issue) decodeExcl(br *util.BinReader) { + return +} diff --git a/pkg/wire/payload/transaction/issue_test.go b/pkg/wire/payload/transaction/issue_test.go new file mode 100644 index 000000000..4cbfd1697 --- /dev/null +++ b/pkg/wire/payload/transaction/issue_test.go @@ -0,0 +1,8 @@ +package transaction + +import "testing" + +func TestEncodeDecodeIssue(t *testing.T) { + + // This is the same as Contract, as it has no special fields. +} diff --git a/pkg/wire/payload/transaction/miner.go b/pkg/wire/payload/transaction/miner.go new file mode 100644 index 000000000..24fdaea5b --- /dev/null +++ b/pkg/wire/payload/transaction/miner.go @@ -0,0 +1,34 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Miner struct { + *Base + Nonce uint32 +} + +func NewMiner(ver version.TX) *Miner { + basicTrans := createBaseTransaction(types.Miner, ver) + + Miner := &Miner{} + Miner.Base = basicTrans + Miner.encodeExclusive = Miner.encodeExcl + Miner.decodeExclusive = Miner.decodeExcl + return Miner +} + +func (c *Miner) encodeExcl(bw *util.BinWriter) { + + bw.Write(c.Nonce) + return +} + +func (c *Miner) decodeExcl(br *util.BinReader) { + + br.Read(&c.Nonce) + +} diff --git a/pkg/wire/payload/transaction/miner_test.go b/pkg/wire/payload/transaction/miner_test.go new file mode 100644 index 000000000..d053ce414 --- /dev/null +++ b/pkg/wire/payload/transaction/miner_test.go @@ -0,0 +1,37 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeMiner(t *testing.T) { + // transaction from mainnet a1f219dc6be4c35eca172e65e02d4591045220221b1543f1a4b67b9e9442c264 + + rawtx := "0000fcd30e22000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60c8000000000000001f72e68b4e39602912106d53b229378a082784b200" + rawtxBytes, _ := hex.DecodeString(rawtx) + + m := NewMiner(0) + + r := bytes.NewReader(rawtxBytes) + err := m.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Miner, m.Type) + assert.Equal(t, uint32(571397116), m.Nonce) + + assert.Equal(t, "a1f219dc6be4c35eca172e65e02d4591045220221b1543f1a4b67b9e9442c264", m.Hash.String()) + + // Encode + buf := new(bytes.Buffer) + + err = m.Encode(buf) + assert.Equal(t, nil, err) + + assert.Equal(t, rawtxBytes, buf.Bytes()) + +} diff --git a/pkg/wire/payload/transaction/paramtype.go b/pkg/wire/payload/transaction/paramtype.go new file mode 100644 index 000000000..5f65e4906 --- /dev/null +++ b/pkg/wire/payload/transaction/paramtype.go @@ -0,0 +1,17 @@ +package transaction + +// ParamType represent the Type of the contract parameter +type ParamType uint8 + +// A list of supported smart contract parameter types. +const ( + SignatureType ParamType = iota + BoolType + IntegerType + Hash160Type + Hash256Type + ByteArrayType + PublicKeyType + StringType + ArrayType +) diff --git a/pkg/wire/payload/transaction/publickey.go b/pkg/wire/payload/transaction/publickey.go new file mode 100644 index 000000000..b5ea2c72e --- /dev/null +++ b/pkg/wire/payload/transaction/publickey.go @@ -0,0 +1,35 @@ +package transaction + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type PublicKey struct { + Key []byte +} + +func (p *PublicKey) Encode(bw *util.BinWriter) { + bw.Write(p.Key) +} +func (p *PublicKey) Decode(br *util.BinReader) { + var prefix uint8 + br.Read(&prefix) + + // Compressed public keys. + if prefix == 0x02 || prefix == 0x03 { + p.Key = make([]byte, 32) + br.Read(p.Key) + } else if prefix == 0x04 { + p.Key = make([]byte, 65) + br.Read(p.Key) + } else if prefix == 0x00 { + // do nothing, For infinity, the p.Key == 0x00, included in the prefix + } else { + br.Err = errors.New("Prefix not recognised for public key") + return + } + + p.Key = append([]byte{prefix}, p.Key...) +} diff --git a/pkg/wire/payload/transaction/publish.go b/pkg/wire/payload/transaction/publish.go new file mode 100644 index 000000000..07988b364 --- /dev/null +++ b/pkg/wire/payload/transaction/publish.go @@ -0,0 +1,88 @@ +package transaction + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type Publish struct { + *Base + Script []byte + ParamList []ParamType + ReturnType ParamType + NeedStorage byte + Name string + CodeVersion string + Author string + Email string + Description string +} + +func NewPublish(ver version.TX) *Publish { + basicTrans := createBaseTransaction(types.Publish, ver) + + Publish := &Publish{} + Publish.Base = basicTrans + Publish.encodeExclusive = Publish.encodeExcl + Publish.decodeExclusive = Publish.decodeExcl + return Publish +} + +func (p *Publish) encodeExcl(bw *util.BinWriter) { + bw.VarBytes(p.Script) + bw.VarUint(uint64(len(p.ParamList))) + for _, param := range p.ParamList { + bw.Write(param) + } + + bw.Write(p.ReturnType) + switch p.Version { + case 0: + p.NeedStorage = byte(0) + case 1: + bw.Write(p.NeedStorage) + default: + bw.Err = errors.New("Version Number unknown for Publish Transaction") + } + + bw.VarString(p.Name) + bw.VarString(p.CodeVersion) + bw.VarString(p.Author) + bw.VarString(p.Email) + bw.VarString(p.Description) + +} + +func (p *Publish) decodeExcl(br *util.BinReader) { + p.Script = br.VarBytes() + + lenParams := br.VarUint() + p.ParamList = make([]ParamType, lenParams) + for i := 0; i < int(lenParams); i++ { + var ptype uint8 + br.Read(&ptype) + p.ParamList[i] = ParamType(ptype) + } + + var rtype uint8 + br.Read(&rtype) + p.ReturnType = ParamType(rtype) + + switch p.Version { + case 0: + p.NeedStorage = byte(0) + case 1: + br.Read(&p.NeedStorage) + default: + br.Err = errors.New("Version Number unknown for Publish Transaction") + } + + p.Name = br.VarString() + p.CodeVersion = br.VarString() + p.Author = br.VarString() + p.Email = br.VarString() + p.Description = br.VarString() +} diff --git a/pkg/wire/payload/transaction/publish_test.go b/pkg/wire/payload/transaction/publish_test.go new file mode 100644 index 000000000..1c9bb1de1 --- /dev/null +++ b/pkg/wire/payload/transaction/publish_test.go @@ -0,0 +1,33 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodePublish(t *testing.T) { + ///transaction taken from neo-python; can be found on testnet 5467a1fc8723ceffa8e5ee59399b02eea1df6fbaa53768c6704b90b960d223fa + // taken from neo-python; + rawtx := "d000fd3f01746b4c04000000004c04000000004c040000000061681e416e745368617265732e426c6f636b636861696e2e476574486569676874681d416e745368617265732e426c6f636b636861696e2e476574426c6f636b744c0400000000948c6c766b947275744c0402000000936c766b9479744c0400000000948c6c766b9479681d416e745368617265732e4865616465722e47657454696d657374616d70a0744c0401000000948c6c766b947275744c0401000000948c6c766b9479641b004c0400000000744c0402000000948c6c766b947275623000744c0401000000936c766b9479744c0400000000936c766b9479ac744c0402000000948c6c766b947275620300744c0402000000948c6c766b947961748c6c766b946d748c6c766b946d748c6c766b946d746c768c6b946d746c768c6b946d746c768c6b946d6c75660302050001044c6f636b0c312e302d70726576696577310a4572696b205a68616e67126572696b40616e747368617265732e6f7267234c6f636b20796f75722061737365747320756e74696c20612074696d657374616d702e00014e23ac4c4851f93407d4c59e1673171f39859db9e7cac72540cd3cc1ae0cca87000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6000ebcaaa0d00000067f97110a66136d38badc7b9f88eab013027ce49014140c298da9f06d5687a0bb87ea3bba188b7dcc91b9667ea5cb71f6fdefe388f42611df29be9b2d6288655b9f2188f46796886afc3b37d8b817599365d9e161ecfb62321034b44ed9c8a88fb2497b6b57206cc08edd42c5614bd1fee790e5b795dee0f4e11ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + publ := NewPublish(30) + + r := bytes.NewReader(rawtxBytes) + err := publ.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Publish, publ.Type) + + buf := new(bytes.Buffer) + err = publ.Encode(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) + assert.Equal(t, "5467a1fc8723ceffa8e5ee59399b02eea1df6fbaa53768c6704b90b960d223fa", publ.Hash.String()) + +} diff --git a/pkg/wire/payload/transaction/register.go b/pkg/wire/payload/transaction/register.go new file mode 100644 index 000000000..36bc41772 --- /dev/null +++ b/pkg/wire/payload/transaction/register.go @@ -0,0 +1,56 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/CityOfZion/neo-go/pkg/wire/util/fixed8" +) + +type Register struct { + *Base + // The type of the asset being registered. + AssetType AssetType + + // Name of the asset being registered. + Name string + + // Amount registered + // Unlimited mode -0.00000001 + Amount fixed8.Fixed8 + + // Decimals + Precision uint8 + + // Public key of the owner + Owner PublicKey + + Admin util.Uint160 +} + +func NewRegister(ver version.TX) *Register { + basicTrans := createBaseTransaction(types.Register, ver) + Register := &Register{} + Register.Base = basicTrans + Register.encodeExclusive = Register.encodeExcl + Register.decodeExclusive = Register.decodeExcl + return Register +} + +func (r *Register) encodeExcl(bw *util.BinWriter) { + bw.Write(r.AssetType) + bw.VarString(r.Name) + bw.Write(r.Amount) + bw.Write(r.Precision) + r.Owner.Encode(bw) + bw.Write(r.Admin) +} + +func (r *Register) decodeExcl(br *util.BinReader) { + br.Read(&r.AssetType) + r.Name = br.VarString() + br.Read(&r.Amount) + br.Read(&r.Precision) + r.Owner.Decode(br) + br.Read(&r.Admin) +} diff --git a/pkg/wire/payload/transaction/register_test.go b/pkg/wire/payload/transaction/register_test.go new file mode 100644 index 000000000..d5492a6d8 --- /dev/null +++ b/pkg/wire/payload/transaction/register_test.go @@ -0,0 +1,54 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeRegister(t *testing.T) { + // transaction taken from neo-python; can be found on testnet 0c092117b4ba47b81001712425e6e7f760a637695eaf23741ba335925b195ecd + + rawtx := "400060245b7b226c616e67223a227a682d434e222c226e616d65223a2254657374436f696e227d5dffffffffffffffff08034b44ed9c8a88fb2497b6b57206cc08edd42c5614bd1fee790e5b795dee0f4e1167f97110a66136d38badc7b9f88eab013027ce4900014423a26aeca49cdeeb9522c720e1ae3a93bbe27d53662839b16a438305c20906010001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60001e1a210b00000067f97110a66136d38badc7b9f88eab013027ce490141405d8223ec807e3416a220a75ef9805dfa2e36bd4f6dcc7372373aa45f15c7fadfc96a8642e52acf56c2c66d549be4ba820484873d5cada00b9c1ce9674fbf96382321034b44ed9c8a88fb2497b6b57206cc08edd42c5614bd1fee790e5b795dee0f4e11ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + reg := NewRegister(0) + + r := bytes.NewReader(rawtxBytes) + err := reg.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Register, reg.Type) + + buf := new(bytes.Buffer) + err = reg.Encode(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) + assert.Equal(t, "0c092117b4ba47b81001712425e6e7f760a637695eaf23741ba335925b195ecd", reg.Hash.String()) +} +func TestEncodeDecodeGenesisRegister(t *testing.T) { + + // genesis transaction taken from mainnet; can be found on mainnet(Block 0) : c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b + + rawtx := "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000" + rawtxBytes, _ := hex.DecodeString(rawtx) + + reg := NewRegister(0) + + r := bytes.NewReader(rawtxBytes) + err := reg.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Register, reg.Type) + + buf := new(bytes.Buffer) + err = reg.Encode(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) + assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", reg.Hash.String()) +} diff --git a/pkg/wire/payload/transaction/state.go b/pkg/wire/payload/transaction/state.go new file mode 100644 index 000000000..2dcbbeebd --- /dev/null +++ b/pkg/wire/payload/transaction/state.go @@ -0,0 +1,40 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/version" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type StateTX struct { + *Base + Descriptors []*StateDescriptor +} + +func NewStateTX(ver version.TX) *StateTX { + basicTrans := createBaseTransaction(types.State, ver) + + StateTX := &StateTX{} + StateTX.Base = basicTrans + StateTX.encodeExclusive = StateTX.encodeExcl + StateTX.decodeExclusive = StateTX.decodeExcl + return StateTX +} + +func (s *StateTX) encodeExcl(bw *util.BinWriter) { + + bw.VarUint(uint64(len(s.Descriptors))) + for _, desc := range s.Descriptors { + desc.Encode(bw) + } +} + +func (s *StateTX) decodeExcl(br *util.BinReader) { + lenDesc := br.VarUint() + + s.Descriptors = make([]*StateDescriptor, lenDesc) + for i := 0; i < int(lenDesc); i++ { + s.Descriptors[i] = &StateDescriptor{} + s.Descriptors[i].Decode(br) + } +} diff --git a/pkg/wire/payload/transaction/state_test.go b/pkg/wire/payload/transaction/state_test.go new file mode 100644 index 000000000..209d9b9c0 --- /dev/null +++ b/pkg/wire/payload/transaction/state_test.go @@ -0,0 +1,47 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" + "github.com/stretchr/testify/assert" +) + +func TestEncodeDecodeState(t *testing.T) { + + // transaction taken from testnet 8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724 + rawtx := "900001482103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c10a5265676973746572656401010001cb4184f0a96e72656c1fbdd4f75cca567519e909fd43cefcec13d6c6abcb92a1000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6000b8fb050109000071f9cf7f0ec74ec0b0f28a92b12e1081574c0af00141408780d7b3c0aadc5398153df5e2f1cf159db21b8b0f34d3994d865433f79fafac41683783c48aef510b67660e3157b701b9ca4dd9946a385d578fba7dd26f4849232103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + s := NewStateTX(0) + + r := bytes.NewReader(rawtxBytes) + err := s.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.State, s.Type) + + assert.Equal(t, 1, len(s.Inputs)) + input := s.Inputs[0] + assert.Equal(t, "a192cbabc6d613ecfcce43fd09e9197556ca5cf7d4bd1f6c65726ea9f08441cb", input.PrevHash.String()) + assert.Equal(t, uint16(0), input.PrevIndex) + + assert.Equal(t, 1, len(s.Descriptors)) + descriptor := s.Descriptors[0] + assert.Equal(t, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1", hex.EncodeToString(descriptor.Key)) + assert.Equal(t, "52656769737465726564", hex.EncodeToString(descriptor.Value)) + assert.Equal(t, "\x01", descriptor.Field) + assert.Equal(t, Validator, descriptor.Type) + + // Encode + + buf := new(bytes.Buffer) + + err = s.Encode(buf) + + assert.Equal(t, nil, err) + assert.Equal(t, rawtxBytes, buf.Bytes()) + assert.Equal(t, "8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724", s.Hash.String()) +} diff --git a/pkg/wire/payload/transaction/statedescriptor.go b/pkg/wire/payload/transaction/statedescriptor.go new file mode 100644 index 000000000..17e09a457 --- /dev/null +++ b/pkg/wire/payload/transaction/statedescriptor.go @@ -0,0 +1,52 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// DescStateType represents the type of StateDescriptor. +type DescStateType uint8 + +// Valid DescStateType constants. +const ( + Account DescStateType = 0x40 + Validator DescStateType = 0x48 +) + +// StateDescriptor .. +type StateDescriptor struct { + Type DescStateType + Key []byte + Value []byte + Field string +} + +func (s *StateDescriptor) Decode(br *util.BinReader) { + br.Read(&s.Type) + + keyLen := br.VarUint() + s.Key = make([]byte, keyLen) + br.Read(s.Key) + + valLen := br.VarUint() + s.Value = make([]byte, valLen) + br.Read(s.Value) + + fieldLen := br.VarUint() + field := make([]byte, fieldLen) + br.Read(field) + + s.Field = string(field) +} +func (s *StateDescriptor) Encode(bw *util.BinWriter) { + bw.Write(s.Type) + + bw.VarUint(uint64(len(s.Key))) + bw.Write(s.Key) + + bw.VarUint(uint64(len(s.Value))) + bw.Write(s.Value) + + bw.VarString(s.Field) + +} diff --git a/pkg/wire/payload/transaction/types/types.go b/pkg/wire/payload/transaction/types/types.go new file mode 100644 index 000000000..e39138600 --- /dev/null +++ b/pkg/wire/payload/transaction/types/types.go @@ -0,0 +1,59 @@ +package types + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// TX is the type of a transaction. +type TX uint8 + +const ( + Miner TX = 0x00 + Issue TX = 0x01 + Claim TX = 0x02 + Enrollment TX = 0x20 + Voting TX = 0x24 + Register TX = 0x40 + Contract TX = 0x80 + State TX = 0x90 + Agency TX = 0xb0 + Publish TX = 0xd0 + Invocation TX = 0xd1 +) + +func (t *TX) Encode(bw *util.BinWriter) { + bw.Write(t) +} +func (t *TX) Decode(br *util.BinReader) { + br.Read(t) +} + +// String implements the stringer interface. +func (t TX) String() string { + switch t { + case Miner: + return "MinerTransaction" + case Issue: + return "IssueTransaction" + case Claim: + return "ClaimTransaction" + case Enrollment: + return "EnrollmentTransaction" + case Voting: + return "VotingTransaction" + case Register: + return "RegisterTransaction" + case Contract: + return "ContractTransaction" + case State: + return "StateTransaction" + case Agency: + return "AgencyTransaction" + case Publish: + return "PublishTransaction" + case Invocation: + return "InvocationTransaction" + default: + return "UnkownTransaction" + } +} diff --git a/pkg/wire/payload/transaction/util.go b/pkg/wire/payload/transaction/util.go new file mode 100644 index 000000000..63fdd458c --- /dev/null +++ b/pkg/wire/payload/transaction/util.go @@ -0,0 +1,61 @@ +package transaction + +import ( + "bufio" + "encoding/hex" + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" +) + +func FromBytes(reader *bufio.Reader) (Transactioner, error) { + + t, err := reader.Peek(1) + + typ := types.TX(t[0]) + var trans Transactioner + + switch typ { + case types.Miner: + miner := NewMiner(0) + err = miner.Decode(reader) + trans = miner + case types.Contract: + contract := NewContract(0) + err = contract.Decode(reader) + trans = contract + case types.Invocation: + invoc := NewInvocation(0) + err = invoc.Decode(reader) + trans = invoc + case types.Claim: + claim := NewClaim(0) + err = claim.Decode(reader) + trans = claim + case types.Register: + reg := NewRegister(0) + err = reg.Decode(reader) + trans = reg + case types.Issue: + iss := NewIssue(0) + err = iss.Decode(reader) + trans = iss + case types.Publish: + pub := NewPublish(0) + err = pub.Decode(reader) + trans = pub + case types.State: + sta := NewStateTX(0) + err = sta.Decode(reader) + trans = sta + case types.Enrollment: + enr := NewEnrollment(0) + err = enr.Decode(reader) + trans = enr + case types.Agency: + err = errors.New("Unsupported transaction type: Agency") + default: + err = errors.New("Unsupported transaction with byte type " + hex.EncodeToString([]byte{t[0]})) + } + return trans, err +} diff --git a/pkg/wire/payload/transaction/version/version.go b/pkg/wire/payload/transaction/version/version.go new file mode 100644 index 000000000..9b9e9ce57 --- /dev/null +++ b/pkg/wire/payload/transaction/version/version.go @@ -0,0 +1,19 @@ +package version + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type TX uint8 + +const ( + Contract TX = 0 +) + +func (v *TX) Encode(bw *util.BinWriter) { + bw.Write(v) +} + +func (v *TX) Decode(br *util.BinReader) { + br.Read(v) +} diff --git a/pkg/wire/protocol/protocol.go b/pkg/wire/protocol/protocol.go new file mode 100644 index 000000000..3f9493f4e --- /dev/null +++ b/pkg/wire/protocol/protocol.go @@ -0,0 +1,27 @@ +package protocol + +type Version uint32 + +const ( + DefaultVersion Version = 0 + UserAgent = "/NEO-GO/" // TODO: This may be relocated to a config file +) + +// ServiceFlag indicates the services provided by the node. 1 = P2P Full Node +type ServiceFlag uint64 + +const ( + NodePeerService ServiceFlag = 1 + // BloomFilerService ServiceFlag = 2 // Not implemented + // PrunedNode ServiceFlag = 3 // Not implemented + // LightNode ServiceFlag = 4 // Not implemented + +) + +// Magic is the network that NEO is running on +type Magic uint32 + +const ( + MainNet Magic = 7630401 + TestNet Magic = 0x74746e41 +) diff --git a/pkg/wire/util/Checksum/checksum.go b/pkg/wire/util/Checksum/checksum.go new file mode 100644 index 000000000..092ea177b --- /dev/null +++ b/pkg/wire/util/Checksum/checksum.go @@ -0,0 +1,29 @@ +package checksum + +import ( + "bytes" + "encoding/binary" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" +) + +func Compare(have uint32, b []byte) bool { + want := FromBytes(b) + return have == want +} + +func FromBuf(buf *bytes.Buffer) uint32 { + + return FromBytes(buf.Bytes()) +} + +func FromBytes(buf []byte) uint32 { + b, err := hash.DoubleSha256(buf) + + if err != nil { + return 0 + } + + // checksum := SumSHA256(SumSHA256(buf.Bytes())) + return binary.LittleEndian.Uint32(b.Bytes()[:4]) +} diff --git a/pkg/wire/util/address/address.go b/pkg/wire/util/address/address.go new file mode 100644 index 000000000..6a9baeb8b --- /dev/null +++ b/pkg/wire/util/address/address.go @@ -0,0 +1,18 @@ +package address + +import ( + "encoding/hex" + + "github.com/CityOfZion/neo-go/pkg/crypto/base58" +) + +func ToScriptHash(address string) string { + + decodedAddressAsBytes, err := base58.Decode(address) + if err != nil { + return "" + } + decodedAddressAsHex := hex.EncodeToString(decodedAddressAsBytes) + scriptHash := (decodedAddressAsHex[2:42]) + return scriptHash +} diff --git a/pkg/wire/util/binaryReader.go b/pkg/wire/util/binaryReader.go new file mode 100644 index 000000000..ce2c0219b --- /dev/null +++ b/pkg/wire/util/binaryReader.go @@ -0,0 +1,59 @@ +package util + +import ( + "encoding/binary" + "io" +) + +type BinReader struct { + R io.Reader + Err error +} + +func (r *BinReader) Read(v interface{}) { + if r.Err != nil { + return + } + r.Err = binary.Read(r.R, binary.LittleEndian, v) +} +func (r *BinReader) ReadBigEnd(v interface{}) { + if r.Err != nil { + return + } + r.Err = binary.Read(r.R, binary.BigEndian, v) +} + +func (r *BinReader) VarUint() uint64 { + var b uint8 + r.Err = binary.Read(r.R, binary.LittleEndian, &b) + + if b == 0xfd { + var v uint16 + r.Err = binary.Read(r.R, binary.LittleEndian, &v) + return uint64(v) + } + if b == 0xfe { + var v uint32 + r.Err = binary.Read(r.R, binary.LittleEndian, &v) + return uint64(v) + } + if b == 0xff { + var v uint64 + r.Err = binary.Read(r.R, binary.LittleEndian, &v) + return v + } + + return uint64(b) +} + +func (r *BinReader) VarBytes() []byte { + n := r.VarUint() + b := make([]byte, n) + r.Read(b) + return b +} + +func (r *BinReader) VarString() string { + b := r.VarBytes() + return string(b) +} diff --git a/pkg/wire/util/binaryWriter.go b/pkg/wire/util/binaryWriter.go new file mode 100644 index 000000000..076e78dea --- /dev/null +++ b/pkg/wire/util/binaryWriter.go @@ -0,0 +1,68 @@ +package util + +import ( + "encoding/binary" + "errors" + "io" +) + +type BinWriter struct { + W io.Writer + Err error +} + +func (w *BinWriter) Write(v interface{}) { + if w.Err != nil { + return + } + w.Err = binary.Write(w.W, binary.LittleEndian, v) +} + +// Only used for IP and PORT. Additional method makes the default LittleEndian case clean +func (w *BinWriter) WriteBigEnd(v interface{}) { + if w.Err != nil { + return + } + w.Err = binary.Write(w.W, binary.BigEndian, v) +} + +func (w *BinWriter) VarString(s string) { + w.VarBytes([]byte(s)) +} + +func (w *BinWriter) VarUint(val uint64) { + if val < 0 { + w.Err = errors.New("value out of range") + return + } + + if w.Err != nil { + return + } + + if val < 0xfd { + w.Err = binary.Write(w.W, binary.LittleEndian, uint8(val)) + return + } + if val < 0xFFFF { + w.Err = binary.Write(w.W, binary.LittleEndian, byte(0xfd)) + w.Err = binary.Write(w.W, binary.LittleEndian, uint16(val)) + return + } + if val < 0xFFFFFFFF { + w.Err = binary.Write(w.W, binary.LittleEndian, byte(0xfe)) + w.Err = binary.Write(w.W, binary.LittleEndian, uint32(val)) + return + + } + + w.Err = binary.Write(w.W, binary.LittleEndian, byte(0xff)) + w.Err = binary.Write(w.W, binary.LittleEndian, val) + +} + +// WriteVarBytes writes a variable length byte array. +func (w *BinWriter) VarBytes(b []byte) { + w.VarUint(uint64(len(b))) + w.Write(b) +} diff --git a/pkg/wire/util/crypto/base58/base58.go b/pkg/wire/util/crypto/base58/base58.go new file mode 100644 index 000000000..b0a67a912 --- /dev/null +++ b/pkg/wire/util/crypto/base58/base58.go @@ -0,0 +1,123 @@ +package base58 + +import ( + "fmt" + "math/big" +) + +const prefix rune = '1' + +var decodeMap = map[rune]int64{ + '1': 0, '2': 1, '3': 2, '4': 3, '5': 4, + '6': 5, '7': 6, '8': 7, '9': 8, 'A': 9, + 'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14, + 'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19, + 'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24, + 'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29, + 'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34, + 'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39, + 'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44, + 'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49, + 's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54, + 'x': 55, 'y': 56, 'z': 57, +} + +// Base58Decode decodes the base58 encoded string. +func Decode(s string) ([]byte, error) { + var ( + startIndex = 0 + zero = 0 + ) + for i, c := range s { + if c == prefix { + zero++ + } else { + startIndex = i + break + } + } + + var ( + n = big.NewInt(0) + div = big.NewInt(58) + ) + for _, c := range s[startIndex:] { + charIndex, ok := decodeMap[c] + if !ok { + return nil, fmt.Errorf( + "invalid character '%c' when decoding this base58 string: '%s'", c, s, + ) + } + n.Add(n.Mul(n, div), big.NewInt(charIndex)) + } + + out := n.Bytes() + buf := make([]byte, (zero + len(out))) + copy(buf[zero:], out[:]) + + return buf, nil +} + +// Base58Encode encodes a byte slice to be a base58 encoded string. +func Encode(bytes []byte) string { + var ( + lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + x = new(big.Int).SetBytes(bytes) + r = new(big.Int) + m = big.NewInt(58) + zero = big.NewInt(0) + encoded string + ) + + for x.Cmp(zero) > 0 { + x.QuoRem(x, m, r) + encoded = string(lookupTable[r.Int64()]) + encoded + } + + return encoded +} + +// Base58CheckDecode decodes the given string. +// func CheckDecode(s string) (b []byte, err error) { +// b, err = Decode(s) +// if err != nil { +// return nil, err +// } + +// for i := 0; i < len(s); i++ { +// if s[i] != '1' { +// break +// } +// b = append([]byte{0x00}, b...) +// } + +// if len(b) < 5 { +// return nil, fmt.Errorf("Invalid base-58 check string: missing checksum. -1") +// } + +// hash, err := hash.DoubleSha256(b[:len(b)-4]) + +// if err != nil { +// return nil, fmt.Errorf("Could not double sha256 data") +// } + +// if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 { +// return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum. -2") +// } + +// // Strip the 4 byte long hash. +// b = b[:len(b)-4] + +// return b, nil +// } + +// // Base58checkEncode encodes b into a base-58 check encoded string. +// func CheckEncode(b []byte) (string, error) { +// hash, err := hash.DoubleSha256(b) +// if err != nil { +// return "", fmt.Errorf("Could not double sha256 data") +// } +// b = append(b, hash[0:4]...) + +// return Encode(b), nil +// } diff --git a/pkg/wire/util/crypto/base58/base58_test.go b/pkg/wire/util/crypto/base58/base58_test.go new file mode 100644 index 000000000..524f0e55e --- /dev/null +++ b/pkg/wire/util/crypto/base58/base58_test.go @@ -0,0 +1,32 @@ +package base58 + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecode(t *testing.T) { + input := "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" + + data, err := Decode(input) + if err != nil { + t.Fatal(err) + } + + expected := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" + actual := hex.EncodeToString(data) + assert.Equal(t, expected, actual) +} +func TestEncode(t *testing.T) { + input := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" + + inputBytes, _ := hex.DecodeString(input) + + data := Encode(inputBytes) + + expected := "F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" // Removed the 1 as it is not checkEncoding + actual := data + assert.Equal(t, expected, actual) +} diff --git a/pkg/wire/util/crypto/hash/hash.go b/pkg/wire/util/crypto/hash/hash.go new file mode 100644 index 000000000..22ab1c6d6 --- /dev/null +++ b/pkg/wire/util/crypto/hash/hash.go @@ -0,0 +1,77 @@ +package hash + +import ( + "crypto/sha256" + "io" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + "golang.org/x/crypto/ripemd160" +) + +func Sha256(data []byte) (util.Uint256, error) { + var hash util.Uint256 + hasher := sha256.New() + hasher.Reset() + _, err := hasher.Write(data) + + hash, err = util.Uint256DecodeBytes(hasher.Sum(nil)) + if err != nil { + return hash, err + } + return hash, nil +} + +func DoubleSha256(data []byte) (util.Uint256, error) { + var hash util.Uint256 + + h1, err := Sha256(data) + if err != nil { + return hash, err + } + + hash, err = Sha256(h1.Bytes()) + if err != nil { + return hash, err + } + return hash, nil +} + +func RipeMD160(data []byte) (util.Uint160, error) { + var hash util.Uint160 + hasher := ripemd160.New() + hasher.Reset() + _, err := io.WriteString(hasher, string(data)) + + hash, err = util.Uint160DecodeBytes(hasher.Sum(nil)) + if err != nil { + return hash, err + } + return hash, nil +} + +func Hash160(data []byte) (util.Uint160, error) { + var hash util.Uint160 + h1, err := Sha256(data) + + h2, err := RipeMD160(h1.Bytes()) + + hash, err = util.Uint160DecodeBytes(h2.Bytes()) + + if err != nil { + return hash, err + } + return hash, nil +} + +func Checksum(data []byte) ([]byte, error) { + hash, err := Sum(data) + if err != nil { + return nil, err + } + return hash[:4], nil +} + +func Sum(b []byte) (util.Uint256, error) { + hash, err := DoubleSha256((b)) + return hash, err +} diff --git a/pkg/wire/util/crypto/hash/hash_test.go b/pkg/wire/util/crypto/hash/hash_test.go new file mode 100644 index 000000000..aa23718f6 --- /dev/null +++ b/pkg/wire/util/crypto/hash/hash_test.go @@ -0,0 +1,62 @@ +package hash + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSha256(t *testing.T) { + input := []byte("hello") + data, err := Sha256(input) + + if err != nil { + t.Fatal(err) + } + expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + actual := hex.EncodeToString(data.Bytes()) // MARK: In the DecodeBytes function, there is a bytes reverse, not sure why? + + assert.Equal(t, expected, actual) +} + +func TestHashDoubleSha256(t *testing.T) { + input := []byte("hello") + data, err := DoubleSha256(input) + + if err != nil { + t.Fatal(err) + } + + firstSha, _ := Sha256(input) + doubleSha, _ := Sha256(firstSha.Bytes()) + expected := hex.EncodeToString(doubleSha.Bytes()) + + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} + +func TestHashRipeMD160(t *testing.T) { + input := []byte("hello") + data, err := RipeMD160(input) + + if err != nil { + t.Fatal(err) + } + expected := "108f07b8382412612c048d07d13f814118445acd" + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} + +func TestHash160(t *testing.T) { + input := "02cccafb41b220cab63fd77108d2d1ebcffa32be26da29a04dca4996afce5f75db" + publicKeyBytes, _ := hex.DecodeString(input) + data, err := Hash160(publicKeyBytes) + + if err != nil { + t.Fatal(err) + } + expected := "c8e2b685cc70ec96743b55beb9449782f8f775d8" + actual := hex.EncodeToString(data.Bytes()) + assert.Equal(t, expected, actual) +} diff --git a/pkg/wire/util/fixed8/fixed8.go b/pkg/wire/util/fixed8/fixed8.go new file mode 100644 index 000000000..9a8119ae7 --- /dev/null +++ b/pkg/wire/util/fixed8/fixed8.go @@ -0,0 +1,55 @@ +package fixed8 + +import ( + "errors" + "fmt" + "strconv" +) + +const ( + decimals = 100000000 +) + +var errInvalidString = errors.New("Fixed8 must satisfy following regex \\d+(\\.\\d{1,8})?") + +// Fixed8 represents a fixed-point number with precision 10^-8. +type Fixed8 int64 + +// String implements the Stringer interface. +func (f Fixed8) String() string { + val := f.Value() + return strconv.FormatFloat(val, 'f', -1, 64) +} + +// Value returns the original value representing the Fixed8. +func (f Fixed8) Value() float64 { + return float64(f) / float64(decimals) +} +func (f Fixed8) Add(val Fixed8) Fixed8 { + a := int64(f.Value()) + b := int64(val.Value()) + c := a + b + return FromInt(c) +} +func (f Fixed8) Sub(val Fixed8) Fixed8 { + a := int64(f.Value()) + b := int64(val.Value()) + c := a - b + return FromInt(c) +} + +func FromInt(val int64) Fixed8 { + return Fixed8(val * decimals) +} + +func FromFloat(val float64) Fixed8 { + return Fixed8(val * decimals) +} + +func FromString(val string) (Fixed8, error) { + res, err := strconv.ParseFloat(val, 64) + if err != nil { + return 0, fmt.Errorf("Failed at parsing string %s", val) + } + return FromFloat(res), nil +} diff --git a/pkg/wire/util/fixed8/fixed8_test.go b/pkg/wire/util/fixed8/fixed8_test.go new file mode 100644 index 000000000..8ee5ab37f --- /dev/null +++ b/pkg/wire/util/fixed8/fixed8_test.go @@ -0,0 +1,85 @@ +package fixed8 + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFixed8Value(t *testing.T) { + + input := int64(12) + assert.Equal(t, float64(input), FromInt(input).Value()) + +} +func TestFixed8Add(t *testing.T) { + + a := FromInt(1) + b := FromInt(2) + + c := a.Add(b) + expected := float64(3) + assert.Equal(t, expected, c.Value()) + +} +func TestFixed8AddRecursive(t *testing.T) { + + a := FromInt(1) + sum := int64(1) + + for i := int64(2); i <= 10; i++ { + + sum += i + b := FromInt(i) + c := a.Add(b) + a = c // 1 + 2 + 3 ... + 10 + } + assert.Equal(t, float64(sum), a.Value()) + +} + +func TestFromInt(t *testing.T) { + + inputs := []int64{12, 23, 100, 456789} + + for _, val := range inputs { + assert.Equal(t, Fixed8(val*decimals), FromInt(val)) + assert.Equal(t, float64(val), FromInt(val).Value()) + } + + for _, val := range inputs { + valString := strconv.FormatInt(val, 10) + assert.Equal(t, valString, FromInt(val).String()) + } + +} +func TestFromFloat(t *testing.T) { + inputs := []float64{12.98, 23.87654333, 100.654322, 456789.12345665} + + for _, val := range inputs { + assert.Equal(t, Fixed8(val*decimals), FromFloat(val)) + assert.Equal(t, float64(val), FromFloat(val).Value()) + } +} +func TestFromString(t *testing.T) { + inputs := []string{"9000", "100000000", "5", "10945", "20.45", "0.00000001"} + + for _, val := range inputs { + + n, err := FromString(val) + assert.Nil(t, err) + assert.Equal(t, val, n.String()) + + } + + val := "123456789.12345678" + n, err := FromString(val) + assert.Nil(t, err) + assert.Equal(t, Fixed8(12345678912345678), n) + + val = "901.2341" + n, err = FromString(val) + assert.Nil(t, err) + assert.Equal(t, Fixed8(90123410000), n) +} diff --git a/pkg/wire/util/io/io.go b/pkg/wire/util/io/io.go new file mode 100644 index 000000000..0d6ada43c --- /dev/null +++ b/pkg/wire/util/io/io.go @@ -0,0 +1,16 @@ +package fileutils + +import ( + "os" +) + +func UpdateFile(filename string, data []byte) error { + + f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + dataWNewline := append(data, []byte("\n")...) + + _, err = f.Write(dataWNewline) + err = f.Close() + return err +} diff --git a/pkg/wire/util/ip/ip.go b/pkg/wire/util/ip/ip.go new file mode 100644 index 000000000..7befd8cca --- /dev/null +++ b/pkg/wire/util/ip/ip.go @@ -0,0 +1,19 @@ +package iputils + +import ( + "log" + "net" +) + +// https://stackoverflow.com/a/37382208 +func GetLocalIP() net.IP { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP +} diff --git a/pkg/wire/util/slice/slice.go b/pkg/wire/util/slice/slice.go new file mode 100644 index 000000000..0cf29435f --- /dev/null +++ b/pkg/wire/util/slice/slice.go @@ -0,0 +1,15 @@ +package slice + +// SliceReverse return a reversed version of the given byte slice. +func Reverse(b []byte) []byte { + // Protect from big.Ints that have 1 len bytes. + if len(b) < 2 { + return b + } + + dest := make([]byte, len(b)) + for i, j := 0, len(b)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = b[j], b[i] + } + return dest +} diff --git a/pkg/wire/util/slice/slice_test.go b/pkg/wire/util/slice/slice_test.go new file mode 100644 index 000000000..e65c63f65 --- /dev/null +++ b/pkg/wire/util/slice/slice_test.go @@ -0,0 +1,33 @@ +package slice + +import ( + "bytes" + "testing" +) + +func TestSliceReverse(t *testing.T) { + arr := []byte{0x01, 0x02, 0x03, 0x04} + have := Reverse(arr) + want := []byte{0x04, 0x03, 0x02, 0x01} + if bytes.Compare(have, want) != 0 { + t.Fatalf("expected %v got %v", want, have) + } +} +func TestSliceReverseOddNumberOfElements(t *testing.T) { + arr := []byte{0x01, 0x02, 0x03, 0x04, 0x05} + have := Reverse(arr) + want := []byte{0x05, 0x04, 0x03, 0x02, 0x01} + if bytes.Compare(have, want) != 0 { + t.Fatalf("expected %v got %v", want, have) + } +} + +// This tests a bug that occured with arrays of size 1 +func TestSliceReverseLen2(t *testing.T) { + arr := []byte{0x01} + have := Reverse(arr) + want := []byte{0x01} + if bytes.Compare(have, want) != 0 { + t.Fatalf("expected %v got %v", want, have) + } +} diff --git a/pkg/wire/util/uint160.go b/pkg/wire/util/uint160.go new file mode 100644 index 000000000..429fe5d95 --- /dev/null +++ b/pkg/wire/util/uint160.go @@ -0,0 +1,86 @@ +package util + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/wire/util/slice" + "golang.org/x/crypto/ripemd160" +) + +const uint160Size = 20 + +// Uint160 is a 20 byte long unsigned integer. +type Uint160 [uint160Size]uint8 + +// Uint160DecodeString attempts to decode the given string into an Uint160. +func Uint160DecodeString(s string) (u Uint160, err error) { + if len(s) != uint160Size*2 { + return u, fmt.Errorf("expected string size of %d got %d", uint160Size*2, len(s)) + } + b, err := hex.DecodeString(s) + if err != nil { + return u, err + } + return Uint160DecodeBytes(b) +} + +// Uint160DecodeBytes attempts to decode the given bytes into an Uint160. +func Uint160DecodeBytes(b []byte) (u Uint160, err error) { + if len(b) != uint160Size { + return u, fmt.Errorf("expected byte size of %d got %d", uint160Size, len(b)) + } + for i := 0; i < uint160Size; i++ { + u[i] = b[i] + } + return +} + +// Uint160FromScript returns a Uint160 type from a raw script. +func Uint160FromScript(script []byte) (u Uint160, err error) { + sha := sha256.New() + sha.Write(script) + b := sha.Sum(nil) + ripemd := ripemd160.New() + ripemd.Write(b) + b = ripemd.Sum(nil) + return Uint160DecodeBytes(b) +} + +// Bytes returns the byte slice representation of u. +func (u Uint160) Bytes() []byte { + b := make([]byte, uint160Size) + for i := 0; i < uint160Size; i++ { + b[i] = byte(u[i]) + } + return b +} + +// BytesReverse return a reversed byte representation of u. +func (u Uint160) BytesReverse() []byte { + return slice.Reverse(u.Bytes()) +} + +// String implements the stringer interface. +func (u Uint160) String() string { + return hex.EncodeToString(u.Bytes()) +} + +// Equals returns true if both Uint256 values are the same. +func (u Uint160) Equals(other Uint160) bool { + for i := 0; i < uint160Size; i++ { + if u[i] != other[i] { + return false + } + } + return true +} + +// MarshalJSON implements the json marshaller interface. +func (u Uint160) MarshalJSON() ([]byte, error) { + return json.Marshal( + fmt.Sprintf("0x%s", hex.EncodeToString(slice.Reverse(u.Bytes()))), + ) +} diff --git a/pkg/wire/util/uint160_test.go b/pkg/wire/util/uint160_test.go new file mode 100644 index 000000000..96416653c --- /dev/null +++ b/pkg/wire/util/uint160_test.go @@ -0,0 +1,50 @@ +package util + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUInt160DecodeString(t *testing.T) { + hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302" + val, err := Uint160DecodeString(hexStr) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hexStr, val.String()) +} + +func TestUint160DecodeBytes(t *testing.T) { + hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302" + b, err := hex.DecodeString(hexStr) + if err != nil { + t.Fatal(err) + } + val, err := Uint160DecodeBytes(b) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hexStr, val.String()) +} + +func TestUInt160Equals(t *testing.T) { + a := "2d3b96ae1bcc5a585e075e3b81920210dec16302" + b := "4d3b96ae1bcc5a585e075e3b81920210dec16302" + + ua, err := Uint160DecodeString(a) + if err != nil { + t.Fatal(err) + } + ub, err := Uint160DecodeString(b) + if err != nil { + t.Fatal(err) + } + if ua.Equals(ub) { + t.Fatalf("%s and %s cannot be equal", ua, ub) + } + if !ua.Equals(ua) { + t.Fatalf("%s and %s must be equal", ua, ua) + } +} diff --git a/pkg/wire/util/uint256.go b/pkg/wire/util/uint256.go new file mode 100644 index 000000000..e1a5a914b --- /dev/null +++ b/pkg/wire/util/uint256.go @@ -0,0 +1,70 @@ +package util + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/CityOfZion/neo-go/pkg/wire/util/slice" +) + +const uint256Size = 32 + +// Uint256 is a 32 byte long unsigned integer. +type Uint256 [uint256Size]uint8 + +// Uint256DecodeString attempts to decode the given string into an Uint256. +func Uint256DecodeString(s string) (u Uint256, err error) { + if len(s) != uint256Size*2 { + return u, fmt.Errorf("expected string size of %d got %d", uint256Size*2, len(s)) + } + b, err := hex.DecodeString(s) + if err != nil { + return u, err + } + return Uint256DecodeBytes(b) +} + +// Uint256DecodeBytes attempts to decode the given string into an Uint256. +func Uint256DecodeBytes(b []byte) (u Uint256, err error) { + if len(b) != uint256Size { + return u, fmt.Errorf("expected []byte of size %d got %d", uint256Size, len(b)) + } + for i := 0; i < uint256Size; i++ { + u[i] = b[i] + } + return u, nil +} + +// Bytes returns a byte slice representation of u. +func (u Uint256) Bytes() []byte { + b := make([]byte, uint256Size) + for i := 0; i < uint256Size; i++ { + b[i] = byte(u[i]) + } + return b +} +func (u Uint256) Reverse() (res Uint256) { + res, _ = Uint256DecodeBytes(u.BytesReverse()) + return +} + +// BytesReverse return a reversed byte representation of u. +func (u Uint256) BytesReverse() []byte { + return slice.Reverse(u.Bytes()) +} + +// Equals returns true if both Uint256 values are the same. +func (u Uint256) Equals(other Uint256) bool { + return u.String() == other.String() +} + +// String implements the stringer interface. +func (u Uint256) String() string { + return hex.EncodeToString(slice.Reverse(u.Bytes())) +} + +// MarshalJSON implements the json marshaller interface. +func (u Uint256) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("0x%s", u.String())) +} diff --git a/pkg/wire/util/uint256_test.go b/pkg/wire/util/uint256_test.go new file mode 100644 index 000000000..120bee380 --- /dev/null +++ b/pkg/wire/util/uint256_test.go @@ -0,0 +1,50 @@ +package util + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUint256DecodeString(t *testing.T) { + hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" + val, err := Uint256DecodeString(hexStr) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hexStr, val.Reverse().String()) +} + +func TestUint256DecodeBytes(t *testing.T) { + hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" + b, err := hex.DecodeString(hexStr) + if err != nil { + t.Fatal(err) + } + val, err := Uint256DecodeBytes(b) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, hexStr, val.Reverse().String()) +} + +func TestUInt256Equals(t *testing.T) { + a := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" + b := "e287c5b29a1b66092be6803c59c765308ac20287e1b4977fd399da5fc8f66ab5" + + ua, err := Uint256DecodeString(a) + if err != nil { + t.Fatal(err) + } + ub, err := Uint256DecodeString(b) + if err != nil { + t.Fatal(err) + } + if ua.Equals(ub) { + t.Fatalf("%s and %s cannot be equal", ua, ub) + } + if !ua.Equals(ua) { + t.Fatalf("%s and %s must be equal", ua, ua) + } +} diff --git a/pkg/wire/util/util.go b/pkg/wire/util/util.go new file mode 100644 index 000000000..65c485617 --- /dev/null +++ b/pkg/wire/util/util.go @@ -0,0 +1,47 @@ +package util + +import ( + "bytes" + "crypto/sha256" + + "io" + "io/ioutil" +) + +// Functions +func BufferLength(buf *bytes.Buffer) uint32 { + + return uint32(buf.Len()) +} + +func SumSHA256(b []byte) []byte { + h := sha256.New() + h.Write(b) + return h.Sum(nil) +} + +func CalculateHash(f func(bw *BinWriter)) (Uint256, error) { + buf := new(bytes.Buffer) + bw := &BinWriter{W: buf} + + f(bw) + + var hash Uint256 + hash = sha256.Sum256(buf.Bytes()) + hash = sha256.Sum256(hash.Bytes()) + return hash, bw.Err + +} + +func ReaderToBuffer(r io.Reader) (buf *bytes.Buffer, err error) { + byt, err := ioutil.ReadAll(r) + + if err != nil { + + return + } + + buf = bytes.NewBuffer(byt) + + return +} diff --git a/server.go b/server.go new file mode 100644 index 000000000..ed87daef7 --- /dev/null +++ b/server.go @@ -0,0 +1,178 @@ +package main + +import ( + "errors" + "fmt" + "net" + + "github.com/CityOfZion/neo-go/pkg/blockchain" + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/syncmanager" + + "github.com/CityOfZion/neo-go/pkg/connmgr" + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/CityOfZion/neo-go/pkg/wire/util/io" +) + +// this file will act as a stub server +// Will create a server package + +type Server struct { + chain *blockchain.Chain + db *database.LDB // TODO(Kev) change to database.Database + sm *syncmanager.Syncmanager + cm *connmgr.Connmgr + + peercfg peer.LocalConfig + + latestHash util.Uint256 +} + +func (s *Server) setupConnMgr() error { + // Connection Manager - Integrate + s.cm = connmgr.New(connmgr.Config{ + GetAddress: nil, + OnConnection: s.OnConn, + OnAccept: nil, + Port: "10333", + }) + + return nil +} +func (s *Server) setupDatabase() error { + // Database -- Integrate + s.db = database.New("test") + return nil +} +func (s *Server) setupChain() error { + // Blockchain - Integrate + s.chain = blockchain.New(s.db, protocol.MainNet) + + if s.chain != nil { + table := database.NewTable(s.db, database.HEADER) + resa, err := table.Get(database.LATESTHEADER) + s.latestHash, err = util.Uint256DecodeBytes(resa) + if err != nil { + return errors.New("Failed to get LastHeader " + err.Error()) + } + } else { + return errors.New("Failed to add genesis block") + } + return nil +} +func (s *Server) setupSyncManager() error { + // Sync Manager - Integrate + s.sm = syncmanager.New(syncmanager.Config{ + Chain: s.chain, + BestHash: s.latestHash, + }) + return nil +} +func (s *Server) setupPeerConfig() error { + // Peer config struct - Integrate + s.peercfg = peer.LocalConfig{ + Net: protocol.MainNet, + UserAgent: "DIG", + Services: protocol.NodePeerService, + Nonce: 1200, + ProtocolVer: 0, + Relay: false, + Port: 10332, + StartHeight: LocalHeight, + OnHeader: s.sm.OnHeaders, + OnBlock: s.sm.OnBlock, + } + return nil +} + +func (s *Server) Run() error { + + // Add all other run based methods for modules + + // Connmgr - Run + s.cm.Run() + // Initial hardcoded nodes to connect to + err := s.cm.Connect(&connmgr.Request{ + Addr: "seed1.ngd.network:10333", + }) + return err +} + +func main() { + + setup() +} + +func setup() { + + server := Server{} + fmt.Println(server.sm) + + err := server.setupConnMgr() + err = server.setupDatabase() + err = server.setupChain() + err = server.setupSyncManager() + err = server.setupPeerConfig() + + fmt.Println(server.sm) + + err = server.Run() + if err != nil { + fmt.Println(err) + } + + <-make(chan struct{}) + +} + +func OnHeader(peer *peer.Peer, msg *payload.HeadersMessage) { + + for _, header := range msg.Headers { + if err := fileutils.UpdateFile("headers.txt", []byte(header.Hash.String())); err != nil { + fmt.Println("Error writing headers to file") + break + } + } + if len(msg.Headers) == 2000 { // reached tip + lastHeader := msg.Headers[len(msg.Headers)-1] + + fmt.Println("Latest hash is", lastHeader.Hash.String()) + fmt.Println("Latest Header height is", lastHeader.Index) + + err := peer.RequestHeaders(lastHeader.Hash.Reverse()) + if err != nil { + fmt.Println("Error getting more headers", err) + } + } +} + +func LocalHeight() uint32 { + return 10 +} + +// OnConn is called when a successful connection has been made +func (s *Server) OnConn(conn net.Conn, addr string) { + fmt.Println(conn.RemoteAddr().String()) + fmt.Println(addr) + + p := peer.NewPeer(conn, false, s.peercfg) + err := p.Run() + + if err != nil { + fmt.Println("Error running peer" + err.Error()) + } + + if err == nil { + s.sm.AddPeer(p) + } + + // This is here just to quickly test the system + err = p.RequestHeaders(s.latestHash) + fmt.Println("For tests, we are only fetching first 2k batch") + if err != nil { + fmt.Println(err.Error()) + } +} From 7eafd1ac17909e25e516f6251c9df9a698790a25 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 25 Feb 2019 22:44:56 +0000 Subject: [PATCH 002/117] Remove extraneous packages --- Dockerfile | 1 - Gopkg.toml | 69 --------- Makefile | 57 ------- VERSION | 1 - circle.yml | 152 ------------------- pkg/addrmgr/addrmgr.go | 285 ----------------------------------- pkg/addrmgr/addrmgr_test.go | 167 -------------------- pkg/addrmgr/peers.json | 0 pkg/addrmgr/readme.md | 23 --- pkg/blockchain/blockchain.go | 187 ----------------------- pkg/chainparams/asset.go | 7 - pkg/chainparams/config.go | 51 ------- pkg/connmgr/config.go | 25 --- pkg/connmgr/connmgr.go | 272 --------------------------------- pkg/connmgr/connmgr_test.go | 106 ------------- pkg/connmgr/readme.md | 26 ---- pkg/connmgr/request.go | 13 -- pkg/mempool/config.go | 31 ---- pkg/mempool/mempool.go | 138 ----------------- pkg/mempool/mempool_test.go | 211 -------------------------- pkg/mempool/tx.go | 45 ------ pkg/peermanager/peermgr.go | 67 -------- pkg/syncmanager/config.go | 11 -- pkg/syncmanager/syncman.go | 152 ------------------- server.go | 178 ---------------------- 25 files changed, 2275 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Gopkg.toml delete mode 100644 Makefile delete mode 100644 VERSION delete mode 100644 circle.yml delete mode 100644 pkg/addrmgr/addrmgr.go delete mode 100644 pkg/addrmgr/addrmgr_test.go delete mode 100644 pkg/addrmgr/peers.json delete mode 100644 pkg/addrmgr/readme.md delete mode 100644 pkg/blockchain/blockchain.go delete mode 100644 pkg/chainparams/asset.go delete mode 100644 pkg/chainparams/config.go delete mode 100644 pkg/connmgr/config.go delete mode 100644 pkg/connmgr/connmgr.go delete mode 100644 pkg/connmgr/connmgr_test.go delete mode 100644 pkg/connmgr/readme.md delete mode 100644 pkg/connmgr/request.go delete mode 100644 pkg/mempool/config.go delete mode 100644 pkg/mempool/mempool.go delete mode 100644 pkg/mempool/mempool_test.go delete mode 100644 pkg/mempool/tx.go delete mode 100644 pkg/peermanager/peermgr.go delete mode 100644 pkg/syncmanager/config.go delete mode 100644 pkg/syncmanager/syncman.go delete mode 100644 server.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6382bd874..000000000 --- a/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM vidsyhq/go-base:latest diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index b91339241..000000000 --- a/Gopkg.toml +++ /dev/null @@ -1,69 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - -[[constraint]] - branch = "master" - name = "github.com/syndtr/goleveldb" - -[[constraint]] - name = "github.com/urfave/cli" - version = "1.20.0" - -[prune] - go-tests = true - unused-packages = true - -[[constraint]] - branch = "master" - name = "golang.org/x/crypto" - -[[constraint]] - branch = "master" - name = "github.com/anthdm/rfc6979" - -[[constraint]] - name = "golang.org/x/text" - version = "0.3.0" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.2.1" - -[[constraint]] - name = "github.com/sirupsen/logrus" - version = "1.0.5" - -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" - -[[constraint]] - name = "github.com/go-yaml/yaml" - version = "2.1.1" - -[[constraint]] - name = "github.com/go-redis/redis" - version = "6.10.2" \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index a2b7338e9..000000000 --- a/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -BRANCH = "master" -BUILD_TIME = "$(shell date -u +\"%Y-%m-%dT%H:%M:%SZ\")" -VERSION = $(shell cat ./VERSION) -REPONAME = "neo-go" -NETMODE ?= "privnet" - -build: - @echo "=> Building darwin binary" - @go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go - -build-image: - docker build -t cityofzion/neo-go --build-arg VERSION=${VERSION} . - -build-linux: - @echo "=> Building linux binary" - @GOOS=linux go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go - -check-version: - git fetch && (! git rev-list ${VERSION}) - -clean-cluster: - @echo "=> Removing all containers and chain storage" - @rm -rf chains/privnet-docker-one chains/privnet-docker-two chains/privnet-docker-three chains/privnet-docker-four - @docker-compose stop - @docker-compose rm -f - -deps: - @dep ensure - -push-tag: - git checkout ${BRANCH} - git pull origin ${BRANCH} - git tag ${VERSION} - git push origin ${VERSION} - -push-to-registry: - @docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS} - @docker tag CityOfZion/${REPONAME}:latest CityOfZion/${REPONAME}:${CIRCLE_TAG} - @docker push CityOfZion/${REPONAME}:${CIRCLE_TAG} - @docker push CityOfZion/${REPONAME} - -run: build - ./bin/neo-go node -config-path ./config -${NETMODE} - -run-cluster: build-linux - @echo "=> Starting docker-compose cluster" - @echo "=> Building container image" - @docker-compose build - @docker-compose up -d - @echo "=> Tailing logs, exiting this prompt will not stop the cluster" - @docker-compose logs -f - -test: - @go test ./... -cover - -vet: - @go vet ./... \ No newline at end of file diff --git a/VERSION b/VERSION deleted file mode 100644 index 25e8d483f..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.44.8 \ No newline at end of file diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 47255ab3e..000000000 --- a/circle.yml +++ /dev/null @@ -1,152 +0,0 @@ -version: 2 -jobs: - install_deps: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: vidsyhq/go-builder:latest - environment: - BUILD: false - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - run: /scripts/build.sh - - save_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - paths: - - vendor - - /go/pkg - build_image: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: alpine - steps: - - run: apk update && apk add git make curl tar - - checkout - - restore_cache: - keys: - - dependency-cache-{{ .Revision }} - - restore_cache: - keys: - - dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - setup_remote_docker - - run: - name: Install Docker client - command: | - set -x - VER="17.03.0-ce" - curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz - tar -xz -C /tmp -f /tmp/docker-$VER.tgz - mv /tmp/docker/* /usr/bin - - run: make build-image - test: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: vidsyhq/go-builder:latest - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - run: make test - vet: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: vidsyhq/go-builder:latest - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - run: make vet - check_version: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: vidsyhq/go-builder:latest - steps: - - checkout - - run: make check-version - build_cli: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: vidsyhq/go-builder:latest - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - run: make build - - save_cache: - key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - paths: - - bin/neo-go - deploy: - working_directory: /go/src/github.com/CityOfZion/neo-go - docker: - - image: alpine - steps: - - run: apk update && apk add git make curl tar - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - restore_cache: - key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }} - - setup_remote_docker - - run: - name: Install Docker client - command: | - set -x - VER="17.03.0-ce" - curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz - tar -xz -C /tmp -f /tmp/docker-$VER.tgz - mv /tmp/docker/* /usr/bin - - run: make build-image - - deploy: - name: deploy - command: make push-to-registry -workflows: - version: 2 - workflow: - jobs: - - install_deps: - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ - - test: - requires: - - install_deps - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ - - vet: - requires: - - install_deps - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ - - check_version: - filters: - branches: - ignore: master - - build_cli: - requires: - - install_deps - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ - - build_image: - requires: - - install_deps - - build_cli - filters: - tags: - only: /[0-9]+\.[0-9]+\.[0-9]+/ - - deploy: - requires: - - build_image - - test - - vet - - check_version - filters: - tags: - only: - - /[0-9]+\.[0-9]+\.[0-9]+/ - branches: - ignore: /.*/ \ No newline at end of file diff --git a/pkg/addrmgr/addrmgr.go b/pkg/addrmgr/addrmgr.go deleted file mode 100644 index 639fd6dff..000000000 --- a/pkg/addrmgr/addrmgr.go +++ /dev/null @@ -1,285 +0,0 @@ -package addrmgr - -import ( - "errors" - "fmt" - "math/rand" - "sync" - "time" - - "github.com/CityOfZion/neo-go/pkg/peer" - "github.com/CityOfZion/neo-go/pkg/wire/payload" -) - -const ( - maxTries = 5 // Try to connect five times, if we cannot then we say it is bad - // We will keep bad addresses, so that we do not attempt to connect to them in the future. - // nodes at the moment seem to send a large percentage of bad nodes. - - maxFailures = 12 // This will be incremented when we connect to a node and for whatever reason, they keep disconnecting. - // The number is high because there could be a period of time, where a node is behaving unrepsonsively and - // we do not want to immmediately put it inside of the bad bucket. - - // This is the maximum amount of addresses that the addrmgr will hold - maxAllowedAddrs = 2000 -) - -type addrStats struct { - tries uint8 - failures uint8 - permanent bool // permanent = inbound, temp = outbound - lastTried time.Time - lastSuccess time.Time -} - -type Addrmgr struct { - addrmtx sync.RWMutex - goodAddrs map[*payload.Net_addr]addrStats - newAddrs map[*payload.Net_addr]struct{} - badAddrs map[*payload.Net_addr]addrStats - knownList map[string]*payload.Net_addr // this contains all of the addresses in badAddrs, newAddrs and goodAddrs -} - -func New() *Addrmgr { - - return &Addrmgr{ - sync.RWMutex{}, - make(map[*payload.Net_addr]addrStats, 100), - make(map[*payload.Net_addr]struct{}, 100), - make(map[*payload.Net_addr]addrStats, 100), - make(map[string]*payload.Net_addr, 100), - } -} - -//AddAddrs will add new add new addresses into the newaddr list -// This is safe for concurrent access. -func (a *Addrmgr) AddAddrs(newAddrs []*payload.Net_addr) { - - newAddrs = removeDuplicates(newAddrs) - - var nas []*payload.Net_addr - for _, addr := range newAddrs { - a.addrmtx.Lock() - - if _, ok := a.knownList[addr.IPPort()]; !ok { // filter unique addresses - nas = append(nas, addr) - } - a.addrmtx.Unlock() - } - - for _, addr := range nas { - a.addrmtx.Lock() - a.newAddrs[addr] = struct{}{} - a.knownList[addr.IPPort()] = addr - a.addrmtx.Unlock() - } -} - -// Good returns the good addresses. -// A good address is: -// - Known -// - has been successfully connected to in the last week -// - Note: that while users are launching full nodes from their laptops, the one week marker will need to be modified. -func (a *Addrmgr) Good() []payload.Net_addr { - - var goodAddrs []payload.Net_addr - - var oneWeekAgo = time.Now().Add(((time.Hour * 24) * 7) * -1) - - a.addrmtx.RLock() - // TODO: sort addresses, permanent ones go first - for addr, stat := range a.goodAddrs { - if stat.lastTried.Before(oneWeekAgo) { - continue - } - goodAddrs = append(goodAddrs, *addr) - - } - a.addrmtx.RUnlock() - - return goodAddrs -} - -// Unconnected Addrs are addresses in the newAddr List -func (a *Addrmgr) Unconnected() []payload.Net_addr { - - var unconnAddrs []payload.Net_addr - - a.addrmtx.RLock() - for addr := range a.newAddrs { - unconnAddrs = append(unconnAddrs, *addr) - } - a.addrmtx.RUnlock() - return unconnAddrs -} - -// Bad Addrs are addresses in the badAddr List -// They are put there if we have tried to connect -// to them in the past and it failed. -func (a *Addrmgr) Bad() []payload.Net_addr { - - var badAddrs []payload.Net_addr - - a.addrmtx.RLock() - for addr := range a.badAddrs { - badAddrs = append(badAddrs, *addr) - } - a.addrmtx.RUnlock() - return badAddrs -} - -// FetchMoreAddresses will return true if the numOfKnownAddrs are less than 100 -// This number is kept low because at the moment, there are not a lot of Good Addresses -// Tests have shown that at most there are < 100 -func (a *Addrmgr) FetchMoreAddresses() bool { - - var numOfKnownAddrs int - - a.addrmtx.RLock() - numOfKnownAddrs = len(a.knownList) - a.addrmtx.RUnlock() - - return numOfKnownAddrs < maxAllowedAddrs -} - -// ConnectionComplete will be called by the server when we have done the handshake -// and Not when we have successfully Connected with net.Conn -// It is to tell the AddrMgr that we have connected to a peer -// This peer should already be known to the AddrMgr because -// We send it to the Connmgr -func (a *Addrmgr) ConnectionComplete(addressport string, inbound bool) { - a.addrmtx.Lock() - defer a.addrmtx.Unlock() - - // if addrmgr does not know this, then we just return - if _, ok := a.knownList[addressport]; !ok { - fmt.Println("Connected to an unknown address:port ", addressport) - return - } - - na := a.knownList[addressport] - - // move it from newAddrs List to GoodAddr List - stats := a.goodAddrs[na] - stats.lastSuccess = time.Now() - stats.lastTried = time.Now() - stats.permanent = inbound - stats.tries++ - a.goodAddrs[na] = stats - - // remove it from new and bad, if it was there - delete(a.newAddrs, na) - delete(a.badAddrs, na) - - // Unfortunately, Will have a lot of bad nodes because of people connecting to nodes - // from their laptop. TODO Sort function in good will mitigate. - -} - -// Failed will be called by ConnMgr -// It is used to tell the AddrMgr that they had tried connecting an address an have failed -// This is concurrent safe -func (a *Addrmgr) Failed(addressport string) { - a.addrmtx.Lock() - defer a.addrmtx.Unlock() - - // if addrmgr does not know this, then we just return - if _, ok := a.knownList[addressport]; !ok { - fmt.Println("Connected to an unknown address:port ", addressport) - return - } - - na := a.knownList[addressport] - - // HMM: logic here could be simpler if we make it one list instead - - if stats, ok := a.badAddrs[na]; ok { - stats.lastTried = time.Now() - stats.failures++ - stats.tries++ - if float32(stats.failures)/float32(stats.tries) > 0.8 && stats.tries > 5 { - delete(a.badAddrs, na) - return - } - a.badAddrs[na] = stats - return - } - - if stats, ok := a.goodAddrs[na]; ok { - fmt.Println("We have a good addr", na.IPPort()) - stats.lastTried = time.Now() - stats.failures++ - stats.tries++ - if float32(stats.failures)/float32(stats.tries) > 0.5 && stats.tries > 10 { - delete(a.goodAddrs, na) - a.badAddrs[na] = stats - return - } - a.goodAddrs[na] = stats - return - } - - if _, ok := a.newAddrs[na]; ok { - delete(a.newAddrs, na) - a.badAddrs[na] = addrStats{} - } - -} - -//OnAddr is the responder for the Config file -// when a OnAddr is received by a peer -func (a *Addrmgr) OnAddr(p *peer.Peer, msg *payload.AddrMessage) { - a.AddAddrs(msg.AddrList) -} - -// OnGetAddr Is called when a peer sends a request for the addressList -// We will give them the best addresses we have from good. -func (a *Addrmgr) OnGetAddr(p *peer.Peer, msg *payload.GetAddrMessage) { - a.addrmtx.RLock() - defer a.addrmtx.RUnlock() - // Push most recent peers to peer - addrMsg, err := payload.NewAddrMessage() - if err != nil { - return - } - for _, add := range a.Good() { - addrMsg.AddNetAddr(&add) - } - - p.Write(addrMsg) -} - -// NewAddr will return an address for the external caller to -// connect to. In our case, it will be the connection manager. -func (a *Addrmgr) NewAddr() (string, error) { - // For now it just returns a random value from unconnected - // TODO: When an address is tried, the address manager is notified. - // When asked for a new address, this should be taken into account - // when choosing a new one, also the number of retries. - unconnected := a.Unconnected() - if len(unconnected) == 0 { - return "", errors.New("No Addresses to give") - } - rand := rand.Intn(len(unconnected)) - return unconnected[rand].IPPort(), nil -} - -// https://www.dotnetperls.com/duplicates-go -func removeDuplicates(elements []*payload.Net_addr) []*payload.Net_addr { - - encountered := map[string]bool{} - result := []*payload.Net_addr{} - - for _, element := range elements { - if encountered[element.IPPort()] == true { - // Do not add duplicate. - } else { - // Record this element as an encountered element. - encountered[element.IPPort()] = true - // Append to result slice. - result = append(result, element) - } - } - // Return the new slice. - return result -} diff --git a/pkg/addrmgr/addrmgr_test.go b/pkg/addrmgr/addrmgr_test.go deleted file mode 100644 index 5a35ea26b..000000000 --- a/pkg/addrmgr/addrmgr_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package addrmgr_test - -import ( - "crypto/rand" - "testing" - - "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/protocol" - - "github.com/CityOfZion/neo-go/pkg/addrmgr" - "github.com/stretchr/testify/assert" -) - -func TestNewAddrs(t *testing.T) { - - addrmgr := addrmgr.New() - assert.NotEqual(t, nil, addrmgr) -} -func TestAddAddrs(t *testing.T) { - - addrmgr := addrmgr.New() - - ip := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) - - ip = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // same - addr2, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) - - ip = [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr3, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) - - addrs := []*payload.Net_addr{addr, addr2, addr, addr3} - - addrmgr.AddAddrs(addrs) - - assert.Equal(t, 2, len(addrmgr.Unconnected())) - assert.Equal(t, 0, len(addrmgr.Good())) - assert.Equal(t, 0, len(addrmgr.Bad())) -} - -func TestFetchMoreAddress(t *testing.T) { - - addrmgr := addrmgr.New() - - addrs := []*payload.Net_addr{} - - ip := make([]byte, 16) - - for i := 0; i <= 2000; i++ { // Add more than maxAllowedAddrs - rand.Read(ip) - - var nip [16]byte - copy(nip[:], ip[:16]) - - addr, _ := payload.NewNetAddr(0, nip, 1033, protocol.NodePeerService) - addrs = append(addrs, addr) - } - - addrmgr.AddAddrs(addrs) - - assert.Equal(t, false, addrmgr.FetchMoreAddresses()) - -} -func TestConnComplete(t *testing.T) { - - addrmgr := addrmgr.New() - - ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) - - ip2 := [16]byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr2, _ := payload.NewNetAddr(0, ip2, 1033, protocol.NodePeerService) - - ip3 := [16]byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr3, _ := payload.NewNetAddr(0, ip3, 1033, protocol.NodePeerService) - - addrs := []*payload.Net_addr{addr, addr2, addr3} - - addrmgr.AddAddrs(addrs) - - assert.Equal(t, len(addrs), len(addrmgr.Unconnected())) - - // a successful connection - addrmgr.ConnectionComplete(addr.IPPort(), true) - addrmgr.ConnectionComplete(addr.IPPort(), true) // should have no change - - assert.Equal(t, len(addrs)-1, len(addrmgr.Unconnected())) - assert.Equal(t, 1, len(addrmgr.Good())) - - // another successful connection - addrmgr.ConnectionComplete(addr2.IPPort(), true) - - assert.Equal(t, len(addrs)-2, len(addrmgr.Unconnected())) - assert.Equal(t, 2, len(addrmgr.Good())) - -} -func TestAttempted(t *testing.T) { - - addrmgr := addrmgr.New() - - ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService) - - addrs := []*payload.Net_addr{addr} - - addrmgr.AddAddrs(addrs) - - addrmgr.Failed(addr.IPPort()) - - assert.Equal(t, 1, len(addrmgr.Bad())) // newAddrs was attmepted and failed. Move to Bad - -} -func TestAttemptedMoveFromGoodToBad(t *testing.T) { - - addrmgr := addrmgr.New() - - ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr, _ := payload.NewNetAddr(0, ip, 1043, protocol.NodePeerService) - - addrs := []*payload.Net_addr{addr} - - addrmgr.AddAddrs(addrs) - - addrmgr.ConnectionComplete(addr.IPPort(), true) - addrmgr.ConnectionComplete(addr.IPPort(), true) - addrmgr.ConnectionComplete(addr.IPPort(), true) - - assert.Equal(t, 1, len(addrmgr.Good())) - - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - addrmgr.Failed(addr.IPPort()) - // over threshhold, and will be classed as a badAddr L251 - - assert.Equal(t, 0, len(addrmgr.Good())) - assert.Equal(t, 1, len(addrmgr.Bad())) -} - -func TestGetAddress(t *testing.T) { - - addrmgr := addrmgr.New() - - ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr, _ := payload.NewNetAddr(0, ip, 10333, protocol.NodePeerService) - ip2 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr2, _ := payload.NewNetAddr(0, ip2, 10334, protocol.NodePeerService) - ip3 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different - addr3, _ := payload.NewNetAddr(0, ip3, 10335, protocol.NodePeerService) - - addrs := []*payload.Net_addr{addr, addr2, addr3} - - addrmgr.AddAddrs(addrs) - - fetchAddr, err := addrmgr.NewAddr() - assert.Equal(t, nil, err) - - ipports := []string{addr.IPPort(), addr2.IPPort(), addr3.IPPort()} - - assert.Contains(t, ipports, fetchAddr) - -} diff --git a/pkg/addrmgr/peers.json b/pkg/addrmgr/peers.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/addrmgr/readme.md b/pkg/addrmgr/readme.md deleted file mode 100644 index 1121e7972..000000000 --- a/pkg/addrmgr/readme.md +++ /dev/null @@ -1,23 +0,0 @@ -# Package - Address Manager - -This package can be used as a standalone to manage addresses on the NEO network. Although you can use it for other chains, the config parameters have been modified for the state of NEO as of now. - -## Responsibility - -To manage the data that the node knows about addresses; data on good addresses, retry rates, failures, and lastSuccessful connection will be managed by the address manager. Also, If a service wants to fetch good address then it will be asked for from the address manager. - - -## Features - -- On GetAddr it will give a list of good addresses to connect to - -- On Addr it will receive addresses and remove any duplicates. - -- General Management of Addresses - -- Periodically saves the peers and metadata about peer into a .json file for retrieval (Not implemented yet) - - -## Note - -The Address manager will not deal with making connections to nodes. Please check the tests for the use cases for this package. diff --git a/pkg/blockchain/blockchain.go b/pkg/blockchain/blockchain.go deleted file mode 100644 index b63641675..000000000 --- a/pkg/blockchain/blockchain.go +++ /dev/null @@ -1,187 +0,0 @@ -package blockchain - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - - "github.com/CityOfZion/neo-go/pkg/chainparams" - - "github.com/CityOfZion/neo-go/pkg/database" - "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/protocol" -) - -var ( - ErrBlockValidation = errors.New("Block failed sanity check") - ErrBlockVerification = errors.New("Block failed to be consistent with the current blockchain") -) - -// Blockchain holds the state of the chain -type Chain struct { - db *database.LDB - net protocol.Magic -} - -func New(db *database.LDB, net protocol.Magic) *Chain { - - marker := []byte("HasBeenInitialisedAlready") - - _, err := db.Get(marker) - - if err != nil { - // This is a new db - fmt.Println("New Database initialisation") - db.Put(marker, []byte{}) - - // We add the genesis block into the db - // along with the indexes for it - if net == protocol.MainNet { - - genesisBlock, err := hex.DecodeString(chainparams.GenesisBlock) - if err != nil { - fmt.Println("Could not add genesis header into db") - db.Delete(marker) - return nil - } - r := bytes.NewReader(genesisBlock) - b := payload.Block{} - err = b.Decode(r) - - if err != nil { - fmt.Println("could not Decode genesis block") - db.Delete(marker) - return nil - } - err = db.AddHeader(&b.BlockBase) - if err != nil { - fmt.Println("Could not add genesis header") - db.Delete(marker) - return nil - } - err = db.AddTransactions(b.Hash, b.Txs) - if err != nil { - fmt.Println("Could not add Genesis Transactions") - db.Delete(marker) - return nil - } - } - if net == protocol.TestNet { - fmt.Println("TODO: Setup the genesisBlock for TestNet") - return nil - } - - } - return &Chain{ - db, - net, - } -} -func (c *Chain) AddBlock(msg *payload.BlockMessage) error { - if !validateBlock(msg) { - return ErrBlockValidation - } - - if !c.verifyBlock(msg) { - return ErrBlockVerification - } - - fmt.Println("Block Hash is ", msg.Hash.String()) - - buf := new(bytes.Buffer) - err := msg.Encode(buf) - if err != nil { - return err - } - return c.db.Put(msg.Hash.Bytes(), buf.Bytes()) - -} - -// validateBlock will check the transactions, -// merkleroot is good, signature is good,every that does not require state -// This may be moved to the syncmanager. This function should not be done in a seperate go-routine -// We are intentionally blocking here because if the block is invalid, we will -// disconnect from the peer. -// We could have this return an error instead; where the error could even -// say where the validation failed, for the logs. -func validateBlock(msg *payload.BlockMessage) bool { - return true -} - -func (c *Chain) verifyBlock(msg *payload.BlockMessage) bool { - return true -} - -// This will add a header into the db, -// indexing it also, this method will not -// run any checks, like if it links with a header -// previously in the db -// func (c *Chain) addHeaderNoCheck(header *payload.BlockBase) error { - -// } - -//addHeaders is not safe for concurrent access -func (c *Chain) ValidateHeaders(msg *payload.HeadersMessage) error { - - table := database.NewTable(c.db, database.HEADER) - - latestHash, err := table.Get(database.LATESTHEADER) - if err != nil { - return err - } - - key := latestHash - val, err := table.Get(key) - - lastHeader := &payload.BlockBase{} - err = lastHeader.Decode(bytes.NewReader(val)) - if err != nil { - return err - } - - // TODO?:Maybe we should sort these headers using the Index - // We should not get them in mixed order, but doing it would not be expensive - // If they are already in order - - // Do checks on headers - for _, currentHeader := range msg.Headers { - - if lastHeader == nil { - // This should not happen as genesis header is added if new - // database, however we check nonetheless - return errors.New("Previous Header is nil") - } - - // Check current hash links with previous - if currentHeader.PrevHash != lastHeader.Hash { - return errors.New("Last Header hash != current header Prev hash") - } - - // Check current Index is one more than the previous Index - if currentHeader.Index != lastHeader.Index+1 { - return errors.New("Last Header Index != current header Index") - } - - // Check current timestamp is more than the previous header's timestamp - if lastHeader.Timestamp > currentHeader.Timestamp { - return errors.New("Timestamp of Previous Header is more than Timestamp of current Header") - } - - // NONONO:Do not check if current is more than 15 secs in future - // some blocks had delay from forks in past. - - // NOTE: These are the only non-contextual checks we can do without the blockchain state - lastHeader = currentHeader - } - return nil -} - -func (c *Chain) AddHeaders(msg *payload.HeadersMessage) error { - for _, header := range msg.Headers { - if err := c.db.AddHeader(header); err != nil { - return err - } - } - return nil -} diff --git a/pkg/chainparams/asset.go b/pkg/chainparams/asset.go deleted file mode 100644 index 99ff0248b..000000000 --- a/pkg/chainparams/asset.go +++ /dev/null @@ -1,7 +0,0 @@ -package chainparams - -// GAS is the scripthash native neo asset: GAS -var GAS = "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7" - -// NEO is the scripthash native neo asset: NEO -var NEO = "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b" diff --git a/pkg/chainparams/config.go b/pkg/chainparams/config.go deleted file mode 100644 index 8ec89d99f..000000000 --- a/pkg/chainparams/config.go +++ /dev/null @@ -1,51 +0,0 @@ -package chainparams - -// For now we will just use this to store the -// peers, to test peer data -// Once complete, it will store the genesis params for testnet and mainnet - -var mainnetSeedList = []string{ - "seed1.neo.org:10333", // NOP - "seed2.neo.org:10333", // YEP - "seed3.neo.org:10333", // YEP - "seed4.neo.org:10333", // NOP - "seed5.neo.org:10333", // YEP - "13.59.52.94:10333", // NOP - "18.220.214.143:10333", // NOP - "13.58.198.112:10333", // NOP - "13.59.14.206:10333", // NOP - "18.216.9.7:10333", // NOP -} - -//MainnetSeedList is a string slice containing the initial seeds from protocol.mainnet -// That are replying -var MainnetSeedList = []string{ - "seed2.neo.org:10333", - "seed3.neo.org:10333", - "seed5.neo.org:10333", - "http://seed1.ngd.network:10333", - "http://seed2.ngd.network:10333", - "http://seed3.ngd.network:10333", - "http://seed4.ngd.network:10333", - "http://seed5.ngd.network:10333", - "http://seed6.ngd.network:10333", - "http://seed7.ngd.network:10333", - "http://seed8.ngd.network:10333", - "http://seed9.ngd.network:10333", - "http://seed10.ngd.network:10333", -} - -var testNetSeedList = []string{ - - "18.218.97.227:20333", - "18.219.30.120:20333", - "18.219.13.91:20333", - "13.59.116.121:20333", - "18.218.255.178:20333", -} - -// GenesisHash for mainnet -var GenesisHash = "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf" - -// GenesisBlock for mainnet -var GenesisBlock = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151" diff --git a/pkg/connmgr/config.go b/pkg/connmgr/config.go deleted file mode 100644 index e73f99245..000000000 --- a/pkg/connmgr/config.go +++ /dev/null @@ -1,25 +0,0 @@ -package connmgr - -import ( - "net" -) - -// Config contains all methods which will be set by the caller to setup the connection manager. -type Config struct { - // GetAddress will return a single address for the connection manager to connect to - GetAddress func() (string, error) - - // OnConnection is called by the connection manager when - // we successfully connect to a peer - // The caller should ideally inform the address manager that we have connected to this address in this function - OnConnection func(conn net.Conn, addr string) - - // OnAccept will take a established connection - OnAccept func(net.Conn) - - // Port is the port in the format "10333" - Port string - - // DialTimeout is the amount of time, before we can disconnect a pending dialed connection - DialTimeout int -} diff --git a/pkg/connmgr/connmgr.go b/pkg/connmgr/connmgr.go deleted file mode 100644 index 4d75b0c09..000000000 --- a/pkg/connmgr/connmgr.go +++ /dev/null @@ -1,272 +0,0 @@ -package connmgr - -import ( - "errors" - "fmt" - "net" - "net/http" - "time" - - "github.com/CityOfZion/neo-go/pkg/wire/util/ip" -) - -var ( - // maxOutboundConn is the maximum number of active peers - // that the connection manager will try to have - maxOutboundConn = 10 - - // maxRetries is the maximum amount of successive retries that - // we can have before we stop dialing that peer - maxRetries = uint8(5) -) - -// Connmgr manages pending/active/failed cnnections -type Connmgr struct { - config Config - PendingList map[string]*Request - ConnectedList map[string]*Request - actionch chan func() -} - -//New creates a new connection manager -func New(cfg Config) *Connmgr { - cnnmgr := &Connmgr{ - cfg, - make(map[string]*Request), - make(map[string]*Request), - make(chan func(), 300), - } - - go func() { - - ip := iputils.GetLocalIP() - addrPort := ip.String() + ":" + cfg.Port - - listener, err := net.Listen("tcp", addrPort) - - if err != nil { - fmt.Println("Error connecting to outbound ", err) - } - - defer func() { - listener.Close() - }() - - for { - - conn, err := listener.Accept() - - if err != nil { - continue - } - // TODO(kev): in the OnAccept the connection address will be added to AddrMgr - go cfg.OnAccept(conn) - } - - }() - - return cnnmgr -} - -// NewRequest will make a new connection -// Gets the address from address func in config -// Then dials it and assigns it to pending -func (c *Connmgr) NewRequest() { - - // Fetch address - addr, err := c.config.GetAddress() - if err != nil { - fmt.Println("Error getting address", err) - } - - // empty request item - r := &Request{} - - r.Addr = addr - fmt.Println("Connecting") - c.Connect(r) - -} - -func (c *Connmgr) Connect(r *Request) error { - - r.Retries++ - - conn, err := c.Dial(r.Addr) - if err != nil { - c.failed(r) - return err - } - - r.Conn = conn - r.Inbound = true - - // r.Permanent is set by the caller. default is false - // The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network - - return c.connected(r) -} - -func (cm *Connmgr) Disconnect(addr string) { - - // fetch from connected list - r, ok := cm.ConnectedList[addr] - - if !ok { - // If not in connected, check pending - r, ok = cm.PendingList[addr] - } - - cm.disconnected(r) - -} - -// Dial is used to dial up connections given the addres and ip in the form address:port -func (c *Connmgr) Dial(addr string) (net.Conn, error) { - dialTimeout := 1 * time.Second - conn, err := net.DialTimeout("tcp", addr, dialTimeout) - if err != nil { - if !isConnected() { - return nil, errors.New("Fatal Error: You do not seem to be connected to the internet") - } - return conn, err - } - return conn, nil -} -func (cm *Connmgr) failed(r *Request) { - - cm.actionch <- func() { - // priority to check if it is permanent or inbound - // if so then these peers are valuable in NEO and so we will just retry another time - if r.Inbound || r.Permanent { - - multiplier := time.Duration(r.Retries * 10) - time.AfterFunc(multiplier*time.Second, - func() { - cm.Connect(r) - }, - ) - // if not then we should check if this request has had maxRetries - // if it has then get a new address - // if not then call Connect on it again - } else if r.Retries > maxRetries { - if cm.config.GetAddress != nil { - go cm.NewRequest() - } - fmt.Println("This peer has been tried the maximum amount of times and a source of new address has not been specified.") - } else { - go cm.Connect(r) - } - - } - -} - -// Disconnected is called when a peer disconnects. -// we take the addr from peer, which is also it's key in the map -// and we use it to remove it from the connectedList -func (c *Connmgr) disconnected(r *Request) error { - - errChan := make(chan error, 0) - - c.actionch <- func() { - - var err error - - if r == nil { - err = errors.New("Request object is nil") - } - - r2 := *r // dereference it, so that r.Addr is not lost on delete - - // if for some reason the underlying connection is not closed, close it - r.Conn.Close() - r.Conn = nil - // if for some reason it is in pending list, remove it - delete(c.PendingList, r.Addr) - delete(c.ConnectedList, r.Addr) - c.failed(&r2) - errChan <- err - } - - return <-errChan -} - -//Connected is called when the connection manager -// makes a successful connection. -func (c *Connmgr) connected(r *Request) error { - - errorChan := make(chan error, 0) - - c.actionch <- func() { - - var err error - - // This should not be the case, since we connected - // Keeping it here to be safe - if r == nil { - err = errors.New("Request object as nil inside of the connected function") - } - - // reset retries to 0 - r.Retries = 0 - - // add to connectedList - c.ConnectedList[r.Addr] = r - - // remove from pending if it was there - delete(c.PendingList, r.Addr) - - if c.config.OnConnection != nil { - c.config.OnConnection(r.Conn, r.Addr) - } - - fmt.Println("Error connected", err) - - errorChan <- err - } - return <-errorChan -} - -// Pending is synchronous, we do not want to continue with logic -// until we are certain it has been added to the pendingList -func (c *Connmgr) pending(r *Request) error { - - errChan := make(chan error, 0) - - c.actionch <- func() { - - var err error - - if r == nil { - err = errors.New("Error : Request object is nil") - } - - c.PendingList[r.Addr] = r - errChan <- err - } - - return <-errChan -} - -func (c *Connmgr) Run() { - go c.loop() -} - -func (c *Connmgr) loop() { - for { - select { - case f := <-c.actionch: - f() - } - } -} - -// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application -func isConnected() (ok bool) { - _, err := http.Get("http://clients3.google.com/generate_204") - if err != nil { - return false - } - return true -} diff --git a/pkg/connmgr/connmgr_test.go b/pkg/connmgr/connmgr_test.go deleted file mode 100644 index fbc591263..000000000 --- a/pkg/connmgr/connmgr_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package connmgr_test - -import ( - "testing" - - "github.com/CityOfZion/neo-go/pkg/connmgr" - "github.com/stretchr/testify/assert" -) - -func TestDial(t *testing.T) { - cfg := connmgr.Config{ - GetAddress: nil, - OnConnection: nil, - OnAccept: nil, - Port: "", - DialTimeout: 0, - } - - cm := connmgr.New(cfg) - cm.Run() - - ipport := "google.com:80" // google unlikely to go offline, a better approach to test Dialing is welcome. - - conn, err := cm.Dial(ipport) - assert.Equal(t, nil, err) - assert.NotEqual(t, nil, conn) -} -func TestConnect(t *testing.T) { - cfg := connmgr.Config{ - GetAddress: nil, - OnConnection: nil, - OnAccept: nil, - Port: "", - DialTimeout: 0, - } - - cm := connmgr.New(cfg) - cm.Run() - - ipport := "google.com:80" - - r := connmgr.Request{Addr: ipport} - - cm.Connect(&r) - - assert.Equal(t, 1, len(cm.ConnectedList)) - -} -func TestNewRequest(t *testing.T) { - - address := "google.com:80" - - var getAddr = func() (string, error) { - return address, nil - } - - cfg := connmgr.Config{ - GetAddress: getAddr, - OnConnection: nil, - OnAccept: nil, - Port: "", - DialTimeout: 0, - } - - cm := connmgr.New(cfg) - - cm.Run() - - cm.NewRequest() - - if _, ok := cm.ConnectedList[address]; ok { - assert.Equal(t, true, ok) - assert.Equal(t, 1, len(cm.ConnectedList)) - return - } - - assert.Fail(t, "Could not find the address in the connected lists") - -} -func TestDisconnect(t *testing.T) { - - address := "google.com:80" - - var getAddr = func() (string, error) { - return address, nil - } - - cfg := connmgr.Config{ - GetAddress: getAddr, - OnConnection: nil, - OnAccept: nil, - Port: "", - DialTimeout: 0, - } - - cm := connmgr.New(cfg) - - cm.Run() - - cm.NewRequest() - - cm.Disconnect(address) - - assert.Equal(t, 0, len(cm.ConnectedList)) - -} diff --git a/pkg/connmgr/readme.md b/pkg/connmgr/readme.md deleted file mode 100644 index d421b55f3..000000000 --- a/pkg/connmgr/readme.md +++ /dev/null @@ -1,26 +0,0 @@ -# Package - Connection Manager - -## Responsibility - -- Manages the active, failed and pending connections for the node. - -## Features - -- Takes an address, dials it and packages it into a request to manage. - -- Retry failed connections. - -- Uses one function as a source for it's addresses. It does not manage addresses. - - -## Usage - -The following methods are exposed from the Connection manager: - -- NewRequest() : This will fetch a new address and connect to it. - -- Connect(r *Request) : This takes a Request object and connects to it. It follow the same logic as NewRequest() however instead of getting the address from the datasource given upon initialisation, you directly feed the address you want to connect to. - -- Disconnect(addrport string) : Given an address:port, this will disconnect it, close the connection and remove it from the connected and pending list, if it was there. - -- Dial(addrport string) (net.Conn, error) : Given an address:port, this will connect to it and return a pointer to a connection plus a nil error if successful, or nil with an error. \ No newline at end of file diff --git a/pkg/connmgr/request.go b/pkg/connmgr/request.go deleted file mode 100644 index 5062064be..000000000 --- a/pkg/connmgr/request.go +++ /dev/null @@ -1,13 +0,0 @@ -package connmgr - -import ( - "net" -) - -type Request struct { - Conn net.Conn - Addr string - Permanent bool - Inbound bool - Retries uint8 // should not be trying more than 255 tries -} diff --git a/pkg/mempool/config.go b/pkg/mempool/config.go deleted file mode 100644 index 19f529d0e..000000000 --- a/pkg/mempool/config.go +++ /dev/null @@ -1,31 +0,0 @@ -package mempool - -import "time" - -type Config struct { - - // This is the maximum amount - // of transactions that we will allow in the mempool - MaxNumOfTX uint64 - - // FreeTX defines the maximum amount of free txs that can be in the mempool at one time - // Default is 20 - FreeTX uint32 - - // MinTXFee is a number in Fixed8 format. If set at 1GAS, minTXFee would equal 1e8 - // The mineTXFee is used to set the floor, it defaults to zero meaning we will allow all transactions - // with a fee of 0 or more - MinTXFee uint64 - - // MaxTXSize is the maximum number of bytes a tx can have to be entered into the pool - MaxTXSize uint64 - - // TXTTL is the duration to which we should keep an item in the mempool before removing it - // HMM: Should this be amount of blocks instead? For when blocks take time a long time - // to process? - TXTTL time.Duration - - // SigLimit is the maximum amount of signatures - // that we will allow a tx to have, default will be 20 - SigLimit uint8 -} diff --git a/pkg/mempool/mempool.go b/pkg/mempool/mempool.go deleted file mode 100644 index 07c1adb9c..000000000 --- a/pkg/mempool/mempool.go +++ /dev/null @@ -1,138 +0,0 @@ -package mempool - -import ( - "errors" - "fmt" - "sync" - - "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" - "github.com/CityOfZion/neo-go/pkg/wire/util" -) - -var ( - ErrMemPoolFull = errors.New("mempool is currently full") - ErrMempoolEmpty = errors.New("There are no TXs in the mempool") - ErrTXTooBig = errors.New("TX has exceed the maximum threshold") - ErrTXTooManyWitnesses = errors.New("Too many witness scripts") - ErrFeeTooLow = errors.New("Fee for transaction too low") - ErrDuplicateTX = errors.New("TX Already in pool") -) - -type Mempool struct { - mtx sync.RWMutex - pool map[util.Uint256]*TX - - cfg Config -} - -func New(cfg Config) *Mempool { - mem := &Mempool{ - sync.RWMutex{}, - make(map[util.Uint256]*TX, 200), - cfg, - } - - return mem -} -func (m *Mempool) AddTransaction(trans transaction.Transactioner) error { - - hash, err := trans.ID() - if err != nil { - return err - } - - // check if tx already in pool - if m.Exists(hash) { - return ErrDuplicateTX - } - - m.mtx.Lock() - defer m.mtx.Unlock() - - if m.cfg.MaxNumOfTX == uint64(len(m.pool)) { - return ErrMemPoolFull - } - - // TODO:Check for double spend from blockchain itself - - // create tx descriptor - tx := Descriptor(trans) - - // check TX size - if tx.Size > m.cfg.MaxTXSize { - return ErrTXTooBig - } - - // check witness length - if tx.NumWitness > m.cfg.SigLimit { - return ErrTXTooManyWitnesses - } - - // TODO: check witness data is good -- Add method to take the Witness and tx return true or false.(blockchain) - - //check fee is over minimum cnfigured - if tx.Fee < m.cfg.MinTXFee { - return ErrFeeTooLow - } - - // Add into pool - m.pool[hash] = tx - - return nil -} - -// RemoveTransaction will remove a transaction from the nodes mempool -func (m *Mempool) RemoveTransaction(hash util.Uint256) error { - m.mtx.Lock() - defer m.mtx.Unlock() - if len(m.pool) == 0 { - return ErrMempoolEmpty - } - // deletes regardless of whether key is there or not. So do not check for existence before delete. - // Use Exists() for this. - delete(m.pool, hash) - - return nil -} - -// Size returns the size of the mempool -func (m *Mempool) Size() uint64 { - m.mtx.RLock() - len := uint64(len(m.pool)) - m.mtx.RUnlock() - - return len -} - -// ReturnAllTransactions will return all transactions in the -// mempool, will be mostly used by the RPC server -func (m *Mempool) ReturnAllTransactions() ([]transaction.Transactioner, error) { - transactions := make([]transaction.Transactioner, 0) - - m.mtx.RLock() - defer m.mtx.RUnlock() - if len(m.pool) == 0 { - return nil, ErrMempoolEmpty - } - - for _, t := range m.pool { - - if t.ParentTX == nil { - fmt.Println(t, "NILNIL") - } - transactions = append(transactions, *t.ParentTX) - fmt.Println(transactions) - } - - return transactions, nil - -} - -// Exists check whether the transaction exists in the mempool -func (m *Mempool) Exists(hash util.Uint256) bool { - m.mtx.RLock() - _, ok := m.pool[hash] - m.mtx.RUnlock() - - return ok -} diff --git a/pkg/mempool/mempool_test.go b/pkg/mempool/mempool_test.go deleted file mode 100644 index 7d75a03d6..000000000 --- a/pkg/mempool/mempool_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package mempool_test - -import ( - "testing" - "time" - - "github.com/CityOfZion/neo-go/pkg/mempool" - "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" - "github.com/stretchr/testify/assert" -) - -func TestMempoolExists(t *testing.T) { - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 10 * time.Minute, - SigLimit: 20, - } - mem := mempool.New(cfg) - - trans := transaction.NewContract(0) - - assert.Equal(t, false, mem.Exists(trans.Hash)) - - err := mem.AddTransaction(trans) - assert.Equal(t, nil, err) - - assert.Equal(t, true, mem.Exists(trans.Hash)) -} -func TestMempoolFullPool(t *testing.T) { - - maxTx := uint64(100) - cfg := mempool.Config{ - MaxNumOfTX: maxTx, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 10 * time.Minute, - SigLimit: 20, - } - mem := mempool.New(cfg) - - for i := uint64(1); i <= maxTx; i++ { - trans := transaction.NewContract(0) - attr := &transaction.Attribute{ - Usage: transaction.Remark, - Data: []byte{byte(i)}, - } - trans.AddAttribute(attr) - err := mem.AddTransaction(trans) - assert.Equal(t, nil, err) - } - trans := transaction.NewContract(0) - err := mem.AddTransaction(trans) - assert.NotEqual(t, nil, err) - - assert.Equal(t, mempool.ErrMemPoolFull, err) -} -func TestMempoolLargeTX(t *testing.T) { - - maxTxSize := uint64(100) - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: maxTxSize, - TXTTL: 10 * time.Minute, - SigLimit: 20, - } - mem := mempool.New(cfg) - - trans := transaction.NewContract(0) - for i := uint64(1); i <= 100; i++ { // 100 attributes will be over 100 bytes - attr := &transaction.Attribute{ - Usage: transaction.Remark, - Data: []byte{byte(i)}, - } - trans.AddAttribute(attr) - } - - err := mem.AddTransaction(trans) - assert.NotEqual(t, nil, err) - assert.Equal(t, mempool.ErrTXTooBig, err) -} -func TestMempoolTooManyWitness(t *testing.T) { - - maxWitness := uint8(3) - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 10 * time.Minute, - SigLimit: maxWitness, - } - mem := mempool.New(cfg) - - trans := transaction.NewContract(0) - for i := uint8(1); i <= maxWitness; i++ { // 100 attributes will be over 100 bytes - wit := &transaction.Witness{ - InvocationScript: []byte{byte(i)}, - VerificationScript: []byte{byte(i)}, - } - trans.AddWitness(wit) - } - - trans.AddWitness(&transaction.Witness{ - InvocationScript: []byte{}, - VerificationScript: []byte{}, - }) - - err := mem.AddTransaction(trans) - assert.NotEqual(t, nil, err) - assert.Equal(t, mempool.ErrTXTooManyWitnesses, err) -} -func TestMempoolDuplicate(t *testing.T) { - - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 10 * time.Minute, - SigLimit: 1, - } - mem := mempool.New(cfg) - - trans := transaction.NewContract(0) - - err := mem.AddTransaction(trans) - assert.Equal(t, nil, err) - - err = mem.AddTransaction(trans) - assert.NotEqual(t, nil, err) - assert.Equal(t, mempool.ErrDuplicateTX, err) -} -func TestMempoolReturnAll(t *testing.T) { - - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 10 * time.Minute, - SigLimit: 1, - } - mem := mempool.New(cfg) - - numTx := uint64(10) - - for i := uint64(1); i <= numTx; i++ { - trans := transaction.NewContract(0) - attr := &transaction.Attribute{ - Usage: transaction.Remark, - Data: []byte{byte(i)}, - } - trans.AddAttribute(attr) - err := mem.AddTransaction(trans) - assert.Equal(t, nil, err) - } - - AllTrans, err := mem.ReturnAllTransactions() - assert.Equal(t, nil, err) - - assert.Equal(t, numTx, uint64(len(AllTrans))) - -} -func TestMempoolRemove(t *testing.T) { - - cfg := mempool.Config{ - MaxNumOfTX: 100, - FreeTX: 20, - MinTXFee: 0, - MaxTXSize: 10000, - TXTTL: 3 * time.Minute, - SigLimit: 1, - } - mem := mempool.New(cfg) - - // Remove a transaction when mempool is empty - trans := transaction.NewContract(0) - hash, _ := trans.ID() - err := mem.RemoveTransaction(hash) - assert.Equal(t, mempool.ErrMempoolEmpty, err) - - // Add tx1 into mempool - err = mem.AddTransaction(trans) - assert.Equal(t, nil, err) - - diffTrans := transaction.NewContract(0) // TX2 - - diffTrans.AddAttribute( - &transaction.Attribute{ - Usage: transaction.Remark, - Data: []byte{}, - }) - - diffHash, _ := diffTrans.ID() - - // Try removing TX2, when only TX1 is in mempool - err = mem.RemoveTransaction(diffHash) - assert.Equal(t, nil, err) - assert.Equal(t, uint64(1), mem.Size()) - // Remove hash that is in mempool - err = mem.RemoveTransaction(hash) - assert.Equal(t, nil, err) - assert.Equal(t, uint64(0), mem.Size()) - -} diff --git a/pkg/mempool/tx.go b/pkg/mempool/tx.go deleted file mode 100644 index ac793af5f..000000000 --- a/pkg/mempool/tx.go +++ /dev/null @@ -1,45 +0,0 @@ -package mempool - -import ( - "time" - - "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" -) - -// TX is a wrapper struct around a normal tx -// which includes extra information about the TX -// -type TX struct { - ParentTX *transaction.Transactioner - Added time.Time - Fee uint64 - Size uint64 - NumWitness uint8 - Free bool -} - -// Descriptor takes a transaction and puts it into a new TX struct along with metadata -func Descriptor(trans transaction.Transactioner) *TX { - - var desc TX - desc.ParentTX = &trans - desc.Fee = getFee(trans.TXOs(), trans.UTXOs()) - desc.Free = desc.Fee != 0 - desc.Added = time.Now() - desc.Size = uint64(len(trans.Bytes())) - - numWit := len(trans.Witness()) - if numWit > 255 || numWit < 0 { // < 0 should not happen - numWit = 255 - } - desc.NumWitness = uint8(numWit) - - return &desc -} - -// TODO: need blockchain package complete for fee calculation -// HMM:Could also put the function in the config -func getFee(in []*transaction.Input, out []*transaction.Output) uint64 { - // query utxo set for inputs, then subtract from out to get fee - return 0 -} diff --git a/pkg/peermanager/peermgr.go b/pkg/peermanager/peermgr.go deleted file mode 100644 index 9083b135d..000000000 --- a/pkg/peermanager/peermgr.go +++ /dev/null @@ -1,67 +0,0 @@ -package peermanager - -import ( - "errors" - "fmt" - - "github.com/CityOfZion/neo-go/pkg/peer" - "github.com/CityOfZion/neo-go/pkg/wire/util" -) - -// NOTE: This package may be removed in the future -// and so full functionality is not yet implemented, see Issue #33 for more details. - -//PeerMgr will act as a convenience Mgr -// It will be notified of added Peers -// It will take care of sending messages to the right peers. In this way, it acts as a load balancer -// If we send a getdata to one peer, it will be smart and send it to another peer who is not as busy -// Using subscription model, we can have the syncmanager/other modules notify the peermgr when they have received data -type PeerMgr struct { - peers []*peer.Peer -} - -// New will create a new peer manager -// As of now it just returns a peerMgr struct and so -// the New method is redundant. A config file will be passed as a parameter, -// if it is decided that we will use this. -func New() *PeerMgr { - return &PeerMgr{} -} - -// Disconnect will close the connection on a peer and -// remove it from the list -// TODO: remove from list once disconnected -func (pm *PeerMgr) Disconnect(p *peer.Peer) { - p.Disconnect() - // Once disconnected, we remove it from the list - // and look for more peers to connect to -} - -// RequestHeaders will request the headers from the most available peer -// As of now, it requests from the first peer in the list, TODO(Kev) -func (pm *PeerMgr) RequestHeaders(hash util.Uint256) (*peer.Peer, error) { - - if len(pm.peers) == 0 { - return nil, errors.New("Peer manager currently has no peers") - } - - return pm.peers[0], pm.peers[0].RequestHeaders(hash) -} - -// RequestBlocks will request blocks from the most available peer -// As of now, it requests from the first peer in the list, TODO(Kev) -func (pm *PeerMgr) RequestBlocks(hash []util.Uint256) (*peer.Peer, error) { - - if len(pm.peers) == 0 { - return nil, errors.New("Peer manager currently has no peers") - } - - return pm.peers[0], pm.peers[0].RequestBlocks(hash) -} - -// AddPeer will add a new peer for the PeerManager to use -func (pm *PeerMgr) AddPeer(p *peer.Peer) error { - pm.peers = append(pm.peers, p) - fmt.Println("Adding peers into the peermanager") - return nil -} diff --git a/pkg/syncmanager/config.go b/pkg/syncmanager/config.go deleted file mode 100644 index 9b8cbbf4a..000000000 --- a/pkg/syncmanager/config.go +++ /dev/null @@ -1,11 +0,0 @@ -package syncmanager - -import ( - "github.com/CityOfZion/neo-go/pkg/blockchain" - "github.com/CityOfZion/neo-go/pkg/wire/util" -) - -type Config struct { - Chain *blockchain.Chain - BestHash util.Uint256 -} diff --git a/pkg/syncmanager/syncman.go b/pkg/syncmanager/syncman.go deleted file mode 100644 index 2c6d863d1..000000000 --- a/pkg/syncmanager/syncman.go +++ /dev/null @@ -1,152 +0,0 @@ -// The syncmanager will use a modified verison of the initial block download in bitcoin -// Seen here: https://en.bitcoinwiki.org/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download -// MovingWindow is a desired featured from the original codebase - -package syncmanager - -import ( - "fmt" - - "github.com/CityOfZion/neo-go/pkg/peermanager" - - "github.com/CityOfZion/neo-go/pkg/blockchain" - "github.com/CityOfZion/neo-go/pkg/peer" - "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/util" -) - -var ( - // This is the maximum amount of inflight objects that we would like to have - // Number taken from original codebase - maxBlockRequest = 1024 - - // This is the maximum amount of blocks that we will ask for from a single peer - // Number taken from original codebase - maxBlockRequestPerPeer = 16 -) - -type Syncmanager struct { - pmgr *peermanager.PeerMgr - Mode int // 1 = headersFirst, 2 = Blocks, 3 = Maintain - chain *blockchain.Chain - headers []util.Uint256 - inflightBlockReqs map[util.Uint256]*peer.Peer // when we send a req for block, we will put hash in here, along with peer who we requested it from -} - -// New will setup the syncmanager with the required -// parameters -func New(cfg Config) *Syncmanager { - return &Syncmanager{ - peermanager.New(), - 1, - cfg.Chain, - []util.Uint256{}, - make(map[util.Uint256]*peer.Peer, 2000), - } -} - -func (s *Syncmanager) AddPeer(peer *peer.Peer) error { - return s.pmgr.AddPeer(peer) -} - -func (s *Syncmanager) OnHeaders(p *peer.Peer, msg *payload.HeadersMessage) { - fmt.Println("Sync manager On Headers called") - // On receipt of Headers - // check what mode we are in - // HeadersMode, we check if there is 2k. If so call again. If not then change mode into BlocksOnly - if s.Mode == 1 { - err := s.HeadersFirstMode(p, msg) - if err != nil { - fmt.Println("Error re blocks", err) - return // We should custom name error so, that we can do something on WrongHash Error, Peer disconnect error - } - return - } -} - -func (s *Syncmanager) HeadersFirstMode(p *peer.Peer, msg *payload.HeadersMessage) error { - - fmt.Println("Headers first mode") - - // Validate Headers - err := s.chain.ValidateHeaders(msg) - - if err != nil { - // Re-request headers from a different peer - s.pmgr.Disconnect(p) - fmt.Println("Error Validating headers", err) - return err - } - - // Add Headers into db - err = s.chain.AddHeaders(msg) - if err != nil { - // Try addding them into the db again? - // Since this is simply a db insert, any problems here means trouble - //TODO(KEV) : Should we Switch off system or warn the user that the system is corrupted? - fmt.Println("Error Adding headers", err) - - //TODO: Batching is not yet implemented, - // So here we would need to remove headers which have been added - // from the slice - return err - } - - // Add header hashes into slice - // Requets first batch of blocks here - var hashes []util.Uint256 - for _, header := range msg.Headers { - hashes = append(hashes, header.Hash) - } - s.headers = append(s.headers, hashes...) - - if len(msg.Headers) == 2*1e3 { // should be less than 2000, leave it as this for tests - fmt.Println("Switching to BlocksOnly Mode") - s.Mode = 2 // switch to BlocksOnly. XXX: because HeadersFirst is not in parallel, no race condition here. - return s.RequestMoreBlocks() - } - lastHeader := msg.Headers[len(msg.Headers)-1] - _, err = s.pmgr.RequestHeaders(lastHeader.Hash) - return err -} - -func (s *Syncmanager) RequestMoreBlocks() error { - - var blockReq []util.Uint256 - - var reqAmount int - - if len(s.headers) >= maxBlockRequestPerPeer { - reqAmount = maxBlockRequestPerPeer - blockReq = s.headers[:reqAmount] - } else { - reqAmount = len(s.headers) - blockReq = s.headers[:reqAmount] - } - peer, err := s.pmgr.RequestBlocks(blockReq) - if err != nil { // This could happen if the peermanager has no valid peers to connect to. We should wait a bit and re-request - return err // alternatively we could make RequestBlocks blocking, then make sure it is not triggered when a block is received - } - - //XXX: Possible race condition, between us requesting the block and adding it to - // the inflight block map? Give that node a medal. - - for _, hash := range s.headers { - s.inflightBlockReqs[hash] = peer - } - s.headers = s.headers[reqAmount:] - // NONONO: Here we do not pass all of the hashes to peermanager because - // it is not the peermanagers responsibility to mange inflight blocks - return err -} - -// OnBlock receives a block from a peer, then passes it to the blockchain to process. -// For now we will only use this simple setup, to allow us to test the other parts of the system. -// See Issue #24 -func (s *Syncmanager) OnBlock(p *peer.Peer, msg *payload.BlockMessage) { - err := s.chain.AddBlock(msg) - if err != nil { - // Put headers back in front of queue to fetch block for. - fmt.Println("Block had an error", err) - } -} diff --git a/server.go b/server.go deleted file mode 100644 index ed87daef7..000000000 --- a/server.go +++ /dev/null @@ -1,178 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net" - - "github.com/CityOfZion/neo-go/pkg/blockchain" - "github.com/CityOfZion/neo-go/pkg/database" - "github.com/CityOfZion/neo-go/pkg/syncmanager" - - "github.com/CityOfZion/neo-go/pkg/connmgr" - "github.com/CityOfZion/neo-go/pkg/peer" - "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/protocol" - "github.com/CityOfZion/neo-go/pkg/wire/util" - "github.com/CityOfZion/neo-go/pkg/wire/util/io" -) - -// this file will act as a stub server -// Will create a server package - -type Server struct { - chain *blockchain.Chain - db *database.LDB // TODO(Kev) change to database.Database - sm *syncmanager.Syncmanager - cm *connmgr.Connmgr - - peercfg peer.LocalConfig - - latestHash util.Uint256 -} - -func (s *Server) setupConnMgr() error { - // Connection Manager - Integrate - s.cm = connmgr.New(connmgr.Config{ - GetAddress: nil, - OnConnection: s.OnConn, - OnAccept: nil, - Port: "10333", - }) - - return nil -} -func (s *Server) setupDatabase() error { - // Database -- Integrate - s.db = database.New("test") - return nil -} -func (s *Server) setupChain() error { - // Blockchain - Integrate - s.chain = blockchain.New(s.db, protocol.MainNet) - - if s.chain != nil { - table := database.NewTable(s.db, database.HEADER) - resa, err := table.Get(database.LATESTHEADER) - s.latestHash, err = util.Uint256DecodeBytes(resa) - if err != nil { - return errors.New("Failed to get LastHeader " + err.Error()) - } - } else { - return errors.New("Failed to add genesis block") - } - return nil -} -func (s *Server) setupSyncManager() error { - // Sync Manager - Integrate - s.sm = syncmanager.New(syncmanager.Config{ - Chain: s.chain, - BestHash: s.latestHash, - }) - return nil -} -func (s *Server) setupPeerConfig() error { - // Peer config struct - Integrate - s.peercfg = peer.LocalConfig{ - Net: protocol.MainNet, - UserAgent: "DIG", - Services: protocol.NodePeerService, - Nonce: 1200, - ProtocolVer: 0, - Relay: false, - Port: 10332, - StartHeight: LocalHeight, - OnHeader: s.sm.OnHeaders, - OnBlock: s.sm.OnBlock, - } - return nil -} - -func (s *Server) Run() error { - - // Add all other run based methods for modules - - // Connmgr - Run - s.cm.Run() - // Initial hardcoded nodes to connect to - err := s.cm.Connect(&connmgr.Request{ - Addr: "seed1.ngd.network:10333", - }) - return err -} - -func main() { - - setup() -} - -func setup() { - - server := Server{} - fmt.Println(server.sm) - - err := server.setupConnMgr() - err = server.setupDatabase() - err = server.setupChain() - err = server.setupSyncManager() - err = server.setupPeerConfig() - - fmt.Println(server.sm) - - err = server.Run() - if err != nil { - fmt.Println(err) - } - - <-make(chan struct{}) - -} - -func OnHeader(peer *peer.Peer, msg *payload.HeadersMessage) { - - for _, header := range msg.Headers { - if err := fileutils.UpdateFile("headers.txt", []byte(header.Hash.String())); err != nil { - fmt.Println("Error writing headers to file") - break - } - } - if len(msg.Headers) == 2000 { // reached tip - lastHeader := msg.Headers[len(msg.Headers)-1] - - fmt.Println("Latest hash is", lastHeader.Hash.String()) - fmt.Println("Latest Header height is", lastHeader.Index) - - err := peer.RequestHeaders(lastHeader.Hash.Reverse()) - if err != nil { - fmt.Println("Error getting more headers", err) - } - } -} - -func LocalHeight() uint32 { - return 10 -} - -// OnConn is called when a successful connection has been made -func (s *Server) OnConn(conn net.Conn, addr string) { - fmt.Println(conn.RemoteAddr().String()) - fmt.Println(addr) - - p := peer.NewPeer(conn, false, s.peercfg) - err := p.Run() - - if err != nil { - fmt.Println("Error running peer" + err.Error()) - } - - if err == nil { - s.sm.AddPeer(p) - } - - // This is here just to quickly test the system - err = p.RequestHeaders(s.latestHash) - fmt.Println("For tests, we are only fetching first 2k batch") - if err != nil { - fmt.Println(err.Error()) - } -} From 1d1f81e16814643624312b1143148631be424031 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 26 Feb 2019 19:31:07 +0300 Subject: [PATCH 003/117] Add go.mod to dev-branch (#160) fix #158 --- go.mod | 8 ++++++++ go.sum | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..4887a0098 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/CityOfZion/neo-go + +require ( + github.com/o3labs/neo-utils v0.0.0-20190129071622-8ae0bc31751f + github.com/stretchr/testify v1.3.0 + github.com/syndtr/goleveldb v1.0.0 + golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..ffd5defcc --- /dev/null +++ b/go.sum @@ -0,0 +1,42 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/o3labs/neo-utils v0.0.0-20190129071622-8ae0bc31751f h1:4yWWfO+KKZ4hu48H0rYKwG/L5SGPaeKAyiYWm4lZRiU= +github.com/o3labs/neo-utils v0.0.0-20190129071622-8ae0bc31751f/go.mod h1:wEsUnV+NySTnPRnGbY3n9FJ12fg34XMiw0Ln9+ruYPk= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b h1:+/WWzjwW6gidDJnMKWLKLX1gxn7irUTF1fLpQovfQ5M= +golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 926dd2079281a2a04513a6eec0dfdaad6046448b Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 26 Feb 2019 20:28:38 +0300 Subject: [PATCH 004/117] Fix possible data race in pkg/stall (#163) fix #162 --- pkg/peer/stall/stall.go | 33 +++++++++++++++++---------------- pkg/peer/stall/stall_test.go | 13 +++++++++++-- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/pkg/peer/stall/stall.go b/pkg/peer/stall/stall.go index 85bf11858..052a22eda 100644 --- a/pkg/peer/stall/stall.go +++ b/pkg/peer/stall/stall.go @@ -17,7 +17,7 @@ type Detector struct { responseTime time.Duration tickInterval time.Duration - lock sync.Mutex + lock *sync.RWMutex responses map[command.Type]time.Time // The detector is embedded into a peer and the peer watches this quit chan @@ -35,7 +35,7 @@ func NewDetector(rTime time.Duration, tickerInterval time.Duration) *Detector { d := &Detector{ responseTime: rTime, tickInterval: tickerInterval, - lock: sync.Mutex{}, + lock: new(sync.RWMutex), responses: map[command.Type]time.Time{}, Quitch: make(chan struct{}), } @@ -46,24 +46,27 @@ func NewDetector(rTime time.Duration, tickerInterval time.Duration) *Detector { func (d *Detector) loop() { ticker := time.NewTicker(d.tickInterval) -loop: + defer func() { + d.Quit() + d.DeleteAll() + ticker.Stop() + }() + for { select { case <-ticker.C: now := time.Now() - for _, deadline := range d.responses { + d.lock.RLock() + resp := d.responses + d.lock.RUnlock() + for _, deadline := range resp { if now.After(deadline) { fmt.Println("Deadline passed") - ticker.Stop() - break loop + return } } - } } - d.Quit() - d.DeleteAll() - ticker.Stop() } // Quit is a concurrent safe way to call the Quit channel @@ -114,17 +117,16 @@ func (d *Detector) DeleteAll() { // and their deadlines func (d *Detector) GetMessages() map[command.Type]time.Time { var resp map[command.Type]time.Time - d.lock.Lock() + d.lock.RLock() resp = d.responses - d.lock.Unlock() + d.lock.RUnlock() return resp } // when a message is added, we will add a deadline for // expected response func (d *Detector) addMessage(cmd command.Type) []command.Type { - - cmds := []command.Type{} + var cmds []command.Type switch cmd { case command.GetHeaders: @@ -151,8 +153,7 @@ func (d *Detector) addMessage(cmd command.Type) []command.Type { // if receive a message, we will delete it from pending func (d *Detector) removeMessage(cmd command.Type) []command.Type { - - cmds := []command.Type{} + var cmds []command.Type switch cmd { case command.Block: diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go index 5b6a2b755..b86412b2a 100644 --- a/pkg/peer/stall/stall_test.go +++ b/pkg/peer/stall/stall_test.go @@ -1,6 +1,7 @@ package stall import ( + "sync" "testing" "time" @@ -29,6 +30,7 @@ func TestAddRemoveMessage(t *testing.T) { } type mockPeer struct { + lock *sync.RWMutex online bool detector *Detector } @@ -43,7 +45,9 @@ loop: } } // cleanup + mp.lock.Lock() mp.online = false + mp.lock.Unlock() } func TestDeadlineWorks(t *testing.T) { @@ -51,16 +55,19 @@ func TestDeadlineWorks(t *testing.T) { tickerInterval := 1 * time.Second d := NewDetector(responseTime, tickerInterval) - mp := mockPeer{online: true, detector: d} + mp := mockPeer{online: true, detector: d, lock: new(sync.RWMutex)} go mp.loop() d.AddMessage(command.GetAddr) time.Sleep(responseTime + 1*time.Second) k := make(map[command.Type]time.Time) + d.lock.RLock() assert.Equal(t, k, d.responses) + d.lock.RUnlock() + mp.lock.RLock() assert.Equal(t, false, mp.online) - + mp.lock.RUnlock() } func TestDeadlineShouldNotBeEmpty(t *testing.T) { responseTime := 10 * time.Second @@ -71,5 +78,7 @@ func TestDeadlineShouldNotBeEmpty(t *testing.T) { time.Sleep(1 * time.Second) k := make(map[command.Type]time.Time) + d.lock.RLock() assert.NotEqual(t, k, d.responses) + d.lock.RUnlock() } From bf16bcfc359c11b158ffdbfa0e288c264010d9c5 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Tue, 26 Feb 2019 23:30:05 +0300 Subject: [PATCH 005/117] Setup TravisCI for dev branch (#161) fix #157 before merge, needs #160 to be merged Just need enable TravisCI, who has write access for this repo --- .travis.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..cf7f94759 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: go +go: + - 1.11.x + - 1.12.x +env: + - GO111MODULE=on +install: + - go get -v golang.org/x/lint/golint + - go mod tidy -v +script: + - golint -set_exit_status ./... + - go test -race -coverprofile=coverage.txt -covermode=atomic ./... +after_success: + - bash <(curl -s https://codecov.io/bash) +matrix: + allow_failures: + - go: tip \ No newline at end of file From 94eb16c9ca498ecdb8830553144326063c95b9cc Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Wed, 27 Feb 2019 16:49:16 +0300 Subject: [PATCH 006/117] Add CircleCI to dev-branch (#165) --- .circleci/config.yml | 84 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..c4c2ca127 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,84 @@ +version: 2.1 +executors: + go1_11: + docker: + - image: circleci/golang:1.11 + environment: + GO111MODULE: "on" + go1_12: + docker: + - image: circleci/golang:1.12 + environment: + GO111MODULE: "on" + +commands: + gomod: + steps: + - restore_cache: + keys: [deps-] + - run: + name: Download go module dependencies + command: go mod download + - save_cache: + key: deps-{{ checksum "go.sum" }}-{{ checksum "go.sum" }} + paths: [/go/pkg/mod] + +jobs: + lint: + working_directory: /go/src/github.com/CityOfZion/neo-go + executor: go1_12 + steps: + - checkout + - gomod + - run: + name: go-lint + command: | + go get -u -v golang.org/x/lint/golint + golint -set_exit_status ./... + + vet: + working_directory: /go/src/github.com/CityOfZion/neo-go + executor: go1_12 + steps: + - checkout + - gomod + - run: + name: go-vet + command: go vet ./... + + test_1_11: + working_directory: /go/src/github.com/CityOfZion/neo-go + executor: go1_11 + steps: + - checkout + - gomod + - run: go test -v -race ./... + + test_1_12: + working_directory: /go/src/github.com/CityOfZion/neo-go + executor: go1_12 + steps: + - checkout + - gomod + - run: go test -v -race ./... + +workflows: + version: 2.1 + workflow: + jobs: + - vet: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - lint: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - test_1_11: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ + - test_1_12: + filters: + tags: + only: /[0-9]+\.[0-9]+\.[0-9]+/ From e29b85d0d760e09b3a653bba55285d049cfc4673 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:50:00 +0000 Subject: [PATCH 007/117] VM:Add abstract stack item --- pkg/vm/stack/stackitem.go | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 pkg/vm/stack/stackitem.go diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go new file mode 100644 index 000000000..9f29f9d3b --- /dev/null +++ b/pkg/vm/stack/stackitem.go @@ -0,0 +1,42 @@ +package stack + +import ( + "errors" +) + +//Item is an interface which represents object that can be placed on the stack +type Item interface { + Integer() (*Int, error) + Boolean() (*Boolean, error) + ByteArray() (*ByteArray, error) + Array() (*Array, error) +} + +// Represents an `abstract` stack item +// which will hold default values for stack items +// this is intended to be embedded into types that you will use on the stack +type abstractItem struct{} + +// Integer is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Integer() (*Int, error) { + return nil, errors.New("This stack item is not an Integer") +} + +// Boolean is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Boolean() (*Boolean, error) { + return nil, errors.New("This stack item is not a Boolean") +} + +// ByteArray is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) ByteArray() (*ByteArray, error) { + return nil, errors.New("This stack item is not a byte array") +} + +// Array is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Array() (*Array, error) { + return nil, errors.New("This stack item is not an array") +} From d8d27761aed3adf71d30635eb18c4ef07525ae27 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:55:48 +0000 Subject: [PATCH 008/117] VM: Add stackItems; Array, Boolean, Int and ByteArray --- pkg/vm/stack/Int.go | 39 +++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/array.go | 13 +++++++++++++ pkg/vm/stack/boolean.go | 13 +++++++++++++ pkg/vm/stack/bytearray.go | 12 ++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 pkg/vm/stack/Int.go create mode 100644 pkg/vm/stack/array.go create mode 100644 pkg/vm/stack/boolean.go create mode 100644 pkg/vm/stack/bytearray.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go new file mode 100644 index 000000000..6ba864b90 --- /dev/null +++ b/pkg/vm/stack/Int.go @@ -0,0 +1,39 @@ +package stack + +import "math/big" + +// Int represents an integer on the stack +type Int struct { + *abstractItem + val *big.Int +} + +// NewInt will convert a big integer into +// a StackInteger +func NewInt(val *big.Int) (*Int, error) { + return &Int{ + abstractItem: &abstractItem{}, + val: val, + }, nil +} + +// Equal will check if two integers hold equal value +func (i *Int) Equal(s *Int) bool { + if i.val.Cmp(s.val) != 0 { + return false + } + return true +} + +// Add will add two stackIntegers together +func (i *Int) Add(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Sub(i.val, s.val), + }, nil +} + +// Integer will overwrite the default implementation +// to allow go to cast this item as an integer. +func (i *Int) Integer() (*Int, error) { + return i, nil +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go new file mode 100644 index 000000000..96fe876a4 --- /dev/null +++ b/pkg/vm/stack/array.go @@ -0,0 +1,13 @@ +package stack + +// Array represents an Array of stackItems on the stack +type Array struct { + *abstractItem + val []Item +} + +// Array overrides the default implementation +// by the abstractItem, returning an Array struct +func (a *Array) Array() (*Array, error) { + return a, nil +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go new file mode 100644 index 000000000..d6a2c12ac --- /dev/null +++ b/pkg/vm/stack/boolean.go @@ -0,0 +1,13 @@ +package stack + +// Boolean represents a boolean value on the stack +type Boolean struct { + *abstractItem + val bool +} + +// Boolean overrides the default implementation +// by the abstractItem, returning a Boolean struct +func (b *Boolean) Boolean() (*Boolean, error) { + return b, nil +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go new file mode 100644 index 000000000..23b7fc805 --- /dev/null +++ b/pkg/vm/stack/bytearray.go @@ -0,0 +1,12 @@ +package stack + +// ByteArray represents a slice of bytes on the stack +type ByteArray struct { + *abstractItem + val []byte +} + +//ByteArray overrides the default abstractItem Bytes array method +func (ba *ByteArray) ByteArray() (*ByteArray, error) { + return ba, nil +} From b79602cc5d113552131b3adbed142b48bfab0bd7 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:56:19 +0000 Subject: [PATCH 009/117] VM: Add tests for stack item --- pkg/vm/stack/stackitem_test.go | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 pkg/vm/stack/stackitem_test.go diff --git a/pkg/vm/stack/stackitem_test.go b/pkg/vm/stack/stackitem_test.go new file mode 100644 index 000000000..741d6f530 --- /dev/null +++ b/pkg/vm/stack/stackitem_test.go @@ -0,0 +1,68 @@ +package stack + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// A simple test to ensure that by embedding the abstract interface +// we immediately become a stack item, with the default values set to nil +func TestInterfaceEmbedding(t *testing.T) { + + // Create an anonymous struct that embeds the abstractItem + a := struct { + *abstractItem + }{ + &abstractItem{}, + } + + // Since interface checking can be done at compile time. + // If he abstractItem did not implement all methods of our interface `Item` + // Then any struct which embeds it, will also not implement the Item interface. + // This test would then give errors, at compile time. + var Items []Item + Items = append(Items, a) + + // Default methods should give errors + // Here we just need to test against one of the methods in the interface + for _, element := range Items { + x, err := element.Integer() + assert.Nil(t, x) + assert.NotNil(t, err, nil) + } + +} + +// TestIntCasting is a simple test to test that the Integer method is overwritten +// from the abstractItem +func TestIntMethodOverride(t *testing.T) { + + testValues := []int64{0, 10, 200, 30, 90} + var Items []Item + + // Convert a range of int64s into Stack Integers + // Adding them into an array of StackItems + for _, num := range testValues { + stackInteger, err := NewInt(big.NewInt(num)) + if err != nil { + t.Fail() + } + Items = append(Items, stackInteger) + } + + // For each item, call the Integer method on the interface + // Which should return an integer and no error + // as the stack integer struct overrides that method + for i, element := range Items { + k, err := element.Integer() + if err != nil { + t.Fail() + } + if k.val.Cmp(big.NewInt(testValues[i])) != 0 { + t.Fail() + } + } + +} From c163ae201937f12a3415212cc812d9cf62f669ce Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 20:58:17 +0000 Subject: [PATCH 010/117] VM: first pass at Random Access Stack object --- pkg/vm/stack/stack.go | 123 ++++++++++++++++++++++++ pkg/vm/stack/stack_test.go | 188 +++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 pkg/vm/stack/stack.go create mode 100644 pkg/vm/stack/stack_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go new file mode 100644 index 000000000..0136f3f03 --- /dev/null +++ b/pkg/vm/stack/stack.go @@ -0,0 +1,123 @@ +package stack + +import ( + "errors" + "fmt" +) + +const ( + // StackAverageSize is used to set the capacity of the stack + // setting this number too low, will cause extra allocations + StackAverageSize = 20 +) + +// RandomAccess represents a Random Access Stack +type RandomAccess struct { + vals []Item +} + +// New will return a new random access stack +func New() *RandomAccess { + return &RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + } +} + +// Items will return all items in the stack +func (ras *RandomAccess) items() []Item { + return ras.vals +} + +//Len will return the length of the stack +func (ras *RandomAccess) Len() int { + if ras.vals == nil { + return -1 + } + return len(ras.vals) +} + +// Clear will remove all items in the stack +func (ras *RandomAccess) Clear() { + ras.vals = make([]Item, 0, StackAverageSize) +} + +// Pop will remove the last stack item that was added +func (ras *RandomAccess) Pop() (Item, error) { + if len(ras.vals) == 0 { + return nil, errors.New("There are no items on the stack to pop") + } + if ras.vals == nil { + return nil, errors.New("Cannot pop from a nil stack") + } + + l := len(ras.vals) + item := ras.vals[l-1] + ras.vals = ras.vals[:l-1] + + return item, nil +} + +// Push will put a stack item onto the top of the stack +func (ras *RandomAccess) Push(item Item) *RandomAccess { + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + ras.vals = append(ras.vals, item) + + return ras +} + +// Insert will push a stackItem onto the stack at position `n` +// Note; index 0 is the top of the stack, which is the end of slice +// REDO: +func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) { + + if n == 0 { + return ras.Push(item), nil + } + + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + // Check that we are not inserting out of the bounds + stackSize := uint16(len(ras.vals)) + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to insert at index %d when length of stack is %d", n, len(ras.vals)) + } + + index := stackSize - n + + ras.vals = append(ras.vals, item) + copy(ras.vals[index:], ras.vals[index-1:]) + ras.vals[index] = item + + return ras, nil +} + +// Peek will check an element at a given index +// Note: 0 is the top of the stack, which is the end of the slice +func (ras *RandomAccess) Peek(n uint16) (Item, error) { + + stackSize := uint16(len(ras.vals)) + + if n == 0 { + index := stackSize - 1 + return ras.vals[index], nil + } + + if ras.vals == nil { + return nil, errors.New("Cannot peak at a nil stack") + } + + // Check that we are not peeking out of the bounds + + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) + } + + index := stackSize - n - 1 + + return ras.vals[index], nil +} diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go new file mode 100644 index 000000000..05a192bcc --- /dev/null +++ b/pkg/vm/stack/stack_test.go @@ -0,0 +1,188 @@ +package stack + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStackPushPop(t *testing.T) { + // Create two stack Integers + a, err := NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + // Create a new stack + testStack := New() + + // Push to stack + testStack.Push(a).Push(b) + + // There should only be two values on the stack + assert.Equal(t, 2, testStack.Len()) + + // Pop first element and it should be equal to b + stackElement, err := testStack.Pop() + if err != nil { + t.Fail() + } + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(b)) + + // Pop second element and it should be equal to a + stackElement, err = testStack.Pop() + if err != nil { + t.Fail() + } + item, err = stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(a)) + + // We should get an error as there are nomore items left to pop + stackElement, err = testStack.Pop() + assert.NotNil(t, err) + +} + +// For this test to pass, we should get an error when popping from a nil stack +// and we should initialise and push an element if pushing to an empty stack +func TestPushPopNil(t *testing.T) { + + // stack is nil when initialised without New constructor + testStack := RandomAccess{} + + // Popping from nil stack + // - should give an error + // - element returned should be nil + stackElement, err := testStack.Pop() + assert.NotNil(t, err) + assert.Nil(t, stackElement) + + // stack should still be nil after failing to pop + assert.Nil(t, testStack.vals) + + // create a random test stack item + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + + // push random item to stack + testStack.Push(a) + + // push should initialise the stack and put one element on the stack + assert.Equal(t, 1, testStack.Len()) +} + +// Test passes if we can peek and modify an item +//without modifying the value on the stack +func TestStackPeekMutability(t *testing.T) { + + testStack := New() + + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + b, err := NewInt(big.NewInt(3)) + assert.Nil(t, err) + + testStack.Push(a).Push(b) + + peekedItem := testPeakInteger(t, testStack, 0) + assert.Equal(t, true, peekedItem.Equal(b)) + + // Check that by modifying the peeked value, + // we did not modify the item on the stack + peekedItem = a + peekedItem.val = big.NewInt(0) + + // Pop item from stack and check it is still the same + poppedItem := testPopInteger(t, testStack) + assert.Equal(t, true, poppedItem.Equal(b)) +} +func TestStackPeek(t *testing.T) { + + testStack := New() + + values := []int64{23, 45, 67, 89, 12, 344} + for _, val := range values { + a := testMakeStackInt(t, val) + testStack.Push(a) + } + + // i starts at 0, j starts at len(values)-1 + for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 { + + peekedItem := testPeakInteger(t, testStack, uint16(i)) + a := testMakeStackInt(t, values[j]) + + fmt.Printf("%#v\n", peekedItem.val.Int64()) + + assert.Equal(t, true, a.Equal(peekedItem)) + + } + +} + +func TestStackInsert(t *testing.T) { + + testStack := New() + + a := testMakeStackInt(t, 2) + b := testMakeStackInt(t, 4) + c := testMakeStackInt(t, 6) + + // insert on an empty stack should put element on top + _, err := testStack.Insert(0, a) + assert.Equal(t, err, nil) + _, err = testStack.Insert(0, b) + assert.Equal(t, err, nil) + _, err = testStack.Insert(1, c) + assert.Equal(t, err, nil) + + // Order should be [a,c,b] + pop1 := testPopInteger(t, testStack) + pop2 := testPopInteger(t, testStack) + pop3 := testPopInteger(t, testStack) + + assert.Equal(t, true, pop1.Equal(b)) + assert.Equal(t, true, pop2.Equal(c)) + assert.Equal(t, true, pop3.Equal(a)) + +} + +// helper functions +func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { + stackElement, err := tStack.Peek(n) + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { + stackElement, err := tStack.Pop() + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testMakeStackInt(t *testing.T, num int64) *Int { + a, err := NewInt(big.NewInt(num)) + assert.Nil(t, err) + return a +} From 64491a4d83ab99f81ee10c708723e47fb3c54838 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:38:11 +0000 Subject: [PATCH 011/117] VM: Add Sub, Mul, Mod LSH, RSH --- pkg/vm/stack/Int.go | 35 ++++++++++++++++++++++++ pkg/vm/stack/int_test.go | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 pkg/vm/stack/int_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 6ba864b90..ae81a2120 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -27,11 +27,46 @@ func (i *Int) Equal(s *Int) bool { // Add will add two stackIntegers together func (i *Int) Add(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Add(i.val, s.val), + }, nil +} + +// Sub will subtract two stackIntegers together +func (i *Int) Sub(s *Int) (*Int, error) { return &Int{ val: new(big.Int).Sub(i.val, s.val), }, nil } +// Mul will multiply two stackIntegers together +func (i *Int) Mul(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mul(i.val, s.val), + }, nil +} + +// Mod will take the mod of two stackIntegers together +func (i *Int) Mod(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mod(i.val, s.val), + }, nil +} + +// Rsh will shift the integer b to the right by `n` bits +func (i *Int) Rsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Rsh(i.val, uint(n.val.Int64())), + }, nil +} + +// Lsh will shift the integer b to the left by `n` bits +func (i *Int) Lsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Lsh(i.val, uint(n.val.Int64())), + }, nil +} + // Integer will overwrite the default implementation // to allow go to cast this item as an integer. func (i *Int) Integer() (*Int, error) { diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go new file mode 100644 index 000000000..40ef3c8b2 --- /dev/null +++ b/pkg/vm/stack/int_test.go @@ -0,0 +1,57 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 30) + c, err := a.Add(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestSub(t *testing.T) { + a := testMakeStackInt(t, 30) + b := testMakeStackInt(t, 200) + expected := testMakeStackInt(t, 170) + c, err := b.Sub(a) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMul(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 200) + c, err := a.Mul(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMod(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 10) + c, err := a.Mod(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestLsh(t *testing.T) { + a := testMakeStackInt(t, 23) + b := testMakeStackInt(t, 8) + expected := testMakeStackInt(t, 5888) + c, err := a.Lsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} + +func TestRsh(t *testing.T) { + a := testMakeStackInt(t, 128) + b := testMakeStackInt(t, 3) + expected := testMakeStackInt(t, 16) + c, err := a.Rsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} From 263bc4c1b1ddbff84ac2382ca1366dc3b7f2ab88 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:38:39 +0000 Subject: [PATCH 012/117] VM: moved test helper functions into separate file --- pkg/vm/stack/testhelper.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pkg/vm/stack/testhelper.go diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/testhelper.go new file mode 100644 index 000000000..443a522ce --- /dev/null +++ b/pkg/vm/stack/testhelper.go @@ -0,0 +1,35 @@ +package stack + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// helper functions +func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { + stackElement, err := tStack.Peek(n) + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { + stackElement, err := tStack.Pop() + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testMakeStackInt(t *testing.T, num int64) *Int { + a, err := NewInt(big.NewInt(num)) + assert.Nil(t, err) + return a +} From 8d55ea12f00c6d3d1cea2b02e353754d61e1b2db Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 21:40:31 +0000 Subject: [PATCH 013/117] VM: removed helper functions from stack_test.go --- pkg/vm/stack/stack_test.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 05a192bcc..246e983f4 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -159,30 +159,3 @@ func TestStackInsert(t *testing.T) { assert.Equal(t, true, pop3.Equal(a)) } - -// helper functions -func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { - stackElement, err := tStack.Peek(n) - assert.Nil(t, err) - item, err := stackElement.Integer() - if err != nil { - t.Fail() - } - return item -} - -func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { - stackElement, err := tStack.Pop() - assert.Nil(t, err) - item, err := stackElement.Integer() - if err != nil { - t.Fail() - } - return item -} - -func testMakeStackInt(t *testing.T, num int64) *Int { - a, err := NewInt(big.NewInt(num)) - assert.Nil(t, err) - return a -} From da0a56f9222f06198a867c14247c6bbff00baa33 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 22:41:46 +0000 Subject: [PATCH 014/117] Add conversions for bytearray and Int stack items --- pkg/vm/stack/Int.go | 21 +++++++++++++++++ pkg/vm/stack/bytearray.go | 47 +++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/stack.go | 2 -- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ae81a2120..4f1d070a5 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -72,3 +72,24 @@ func (i *Int) Lsh(n *Int) (*Int, error) { func (i *Int) Integer() (*Int, error) { return i, nil } + +// ByteArray override the default ByteArray method +// to convert a Integer into a byte Array +func (i *Int) ByteArray() (*ByteArray, error) { + return &ByteArray{ + i.abstractItem, + i.val.Bytes(), + }, nil +} + +//Boolean override the default Boolean method +// to convert an Integer into a Boolean StackItem +func (i *Int) Boolean() (*Boolean, error) { + + boolean := (i.val.Int64() != 0) + return &Boolean{ + i.abstractItem, + boolean, + }, nil + +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 23b7fc805..4ff318e2b 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -1,12 +1,59 @@ package stack +import ( + "errors" + "math/big" + "strconv" +) + // ByteArray represents a slice of bytes on the stack type ByteArray struct { *abstractItem val []byte } +//NewByteArray returns a ByteArray stack item +// given a byte slice +func NewByteArray(val []byte) *ByteArray { + return &ByteArray{ + &abstractItem{}, + val, + } +} + //ByteArray overrides the default abstractItem Bytes array method func (ba *ByteArray) ByteArray() (*ByteArray, error) { return ba, nil } + +//Integer overrides the default Integer method to convert an +// ByteArray Into an integer +func (ba *ByteArray) Integer() (*Int, error) { + + dest := make([]byte, 0) + + for i, j := 0, len(ba.val)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = ba.val[j], ba.val[i] + } + + integerVal := new(big.Int).SetBytes(dest) + + return &Int{ + ba.abstractItem, + integerVal, + }, nil + + // return ba, nil +} + +// Boolean will convert +func (ba *ByteArray) Boolean() (*Boolean, error) { + boolean, err := strconv.ParseBool(string(ba.val)) + if err != nil { + return nil, errors.New("cannot convert byte array to a boolean") + } + return &Boolean{ + ba.abstractItem, + boolean, + }, nil +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 0136f3f03..3c01ad4e7 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -70,7 +70,6 @@ func (ras *RandomAccess) Push(item Item) *RandomAccess { // Insert will push a stackItem onto the stack at position `n` // Note; index 0 is the top of the stack, which is the end of slice -// REDO: func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) { if n == 0 { @@ -112,7 +111,6 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { } // Check that we are not peeking out of the bounds - if n > stackSize-1 { return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) } From 5789aba4b29783afc7236249b2135f6d707d5d23 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Wed, 27 Feb 2019 22:52:58 +0000 Subject: [PATCH 015/117] Add instructions file for vm --- pkg/vm/instructions.go | 133 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 pkg/vm/instructions.go diff --git a/pkg/vm/instructions.go b/pkg/vm/instructions.go new file mode 100644 index 000000000..6a78f6a6a --- /dev/null +++ b/pkg/vm/instructions.go @@ -0,0 +1,133 @@ +package vm + +// Instruction represents a operation code in the neovm +type Instruction byte + +// Viable list of supported instruction constants. +const ( + // Constants + PUSH0 Instruction = 0x00 + PUSHF Instruction = PUSH0 + PUSHBYTES1 Instruction = 0x01 + PUSHBYTES75 Instruction = 0x4B + PUSHDATA1 Instruction = 0x4C + PUSHDATA2 Instruction = 0x4D + PUSHDATA4 Instruction = 0x4E + PUSHM1 Instruction = 0x4F + PUSH1 Instruction = 0x51 + PUSHT Instruction = PUSH1 + PUSH2 Instruction = 0x52 + PUSH3 Instruction = 0x53 + PUSH4 Instruction = 0x54 + PUSH5 Instruction = 0x55 + PUSH6 Instruction = 0x56 + PUSH7 Instruction = 0x57 + PUSH8 Instruction = 0x58 + PUSH9 Instruction = 0x59 + PUSH10 Instruction = 0x5A + PUSH11 Instruction = 0x5B + PUSH12 Instruction = 0x5C + PUSH13 Instruction = 0x5D + PUSH14 Instruction = 0x5E + PUSH15 Instruction = 0x5F + PUSH16 Instruction = 0x60 + + // Flow control + NOP Instruction = 0x61 + JMP Instruction = 0x62 + JMPIF Instruction = 0x63 + JMPIFNOT Instruction = 0x64 + CALL Instruction = 0x65 + RET Instruction = 0x66 + APPCALL Instruction = 0x67 + SYSCALL Instruction = 0x68 + TAILCALL Instruction = 0x69 + + // Stack + DUPFROMALTSTACK Instruction = 0x6A + TOALTSTACK Instruction = 0x6B + FROMALTSTACK Instruction = 0x6C + XDROP Instruction = 0x6D + XSWAP Instruction = 0x72 + XTUCK Instruction = 0x73 + DEPTH Instruction = 0x74 + DROP Instruction = 0x75 + DUP Instruction = 0x76 + NIP Instruction = 0x77 + OVER Instruction = 0x78 + PICK Instruction = 0x79 + ROLL Instruction = 0x7A + ROT Instruction = 0x7B + SWAP Instruction = 0x7C + TUCK Instruction = 0x7D + + // Splice + CAT Instruction = 0x7E + SUBSTR Instruction = 0x7F + LEFT Instruction = 0x80 + RIGHT Instruction = 0x81 + SIZE Instruction = 0x82 + + // Bitwise logic + INVERT Instruction = 0x83 + AND Instruction = 0x84 + OR Instruction = 0x85 + XOR Instruction = 0x86 + EQUAL Instruction = 0x87 + + // Arithmetic + INC Instruction = 0x8B + DEC Instruction = 0x8C + SIGN Instruction = 0x8D + NEGATE Instruction = 0x8F + ABS Instruction = 0x90 + NOT Instruction = 0x91 + NZ Instruction = 0x92 + ADD Instruction = 0x93 + SUB Instruction = 0x94 + MUL Instruction = 0x95 + DIV Instruction = 0x96 + MOD Instruction = 0x97 + SHL Instruction = 0x98 + SHR Instruction = 0x99 + BOOLAND Instruction = 0x9A + BOOLOR Instruction = 0x9B + NUMEQUAL Instruction = 0x9C + NUMNOTEQUAL Instruction = 0x9E + LT Instruction = 0x9F + GT Instruction = 0xA0 + LTE Instruction = 0xA1 + GTE Instruction = 0xA2 + MIN Instruction = 0xA3 + MAX Instruction = 0xA4 + WITHIN Instruction = 0xA5 + + // Crypto + SHA1 Instruction = 0xA7 + SHA256 Instruction = 0xA8 + HASH160 Instruction = 0xA9 + HASH256 Instruction = 0xAA + CHECKSIG Instruction = 0xAC + CHECKMULTISIG Instruction = 0xAE + + // Array + ARRAYSIZE Instruction = 0xC0 + PACK Instruction = 0xC1 + UNPACK Instruction = 0xC2 + PICKITEM Instruction = 0xC3 + SETITEM Instruction = 0xC4 + NEWARRAY Instruction = 0xC5 + NEWSTRUCT Instruction = 0xC6 + APPEND Instruction = 0xC8 + REVERSE Instruction = 0xC9 + REMOVE Instruction = 0xCA + + // Exceptions + THROW Instruction = 0xF0 + THROWIFNOT Instruction = 0xF1 +) + +// Value returns the byte-value of the opcode. +func (i Instruction) Value() byte { + return byte(i) +} From f60d65f1a428b8bb98b05960f228bcc387377dee Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Thu, 28 Feb 2019 13:51:02 +0000 Subject: [PATCH 016/117] - Add guide to stack readme - Add testReadInt64 --- pkg/vm/stack/Readme.md | 24 ++++++++++++++++++++++++ pkg/vm/stack/bytearray.go | 2 -- pkg/vm/stack/testhelper.go | 9 +++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 pkg/vm/stack/Readme.md diff --git a/pkg/vm/stack/Readme.md b/pkg/vm/stack/Readme.md new file mode 100644 index 000000000..2e1b6ba78 --- /dev/null +++ b/pkg/vm/stack/Readme.md @@ -0,0 +1,24 @@ +## VM - Stack + +- How do i implement a new StackItem? + +Answer: You add it's type to the Item interface, then you implement the default return method on the abstract stack item, this should be the behaviour of the stack item, if it is not the new type. Then you embed the abstract item in the new struct and override the method. + +For example, If I wanted to add a new type called `HashMap` + +type Item interface{ + HashMap()(*HashMap, error) +} + +func (a *abstractItem) HashMap() (*HashMap, error) { + return nil, errors.New(This stack item is not a hashmap) +} + +type HashMap struct { + *abstractItem + // Variables needed for hashmap +} + +func (h *HashMap) HashMap()(*HashMap, error) { + // logic to override default behaviour +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 4ff318e2b..7d57b12f0 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -42,8 +42,6 @@ func (ba *ByteArray) Integer() (*Int, error) { ba.abstractItem, integerVal, }, nil - - // return ba, nil } // Boolean will convert diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/testhelper.go index 443a522ce..15c6f87de 100644 --- a/pkg/vm/stack/testhelper.go +++ b/pkg/vm/stack/testhelper.go @@ -1,6 +1,8 @@ package stack import ( + "bytes" + "encoding/binary" "math/big" "testing" @@ -33,3 +35,10 @@ func testMakeStackInt(t *testing.T, num int64) *Int { assert.Nil(t, err) return a } + +func testReadInt64(data []byte) int64 { + var ret int64 + buf := bytes.NewBuffer(data) + binary.Read(buf, binary.LittleEndian, &ret) + return ret +} From e2ef6bd2f4d7adc4127b4f972b12537dbefd6034 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:27:34 +0000 Subject: [PATCH 017/117] Add Builder --- pkg/vm/stack/builder.go | 177 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 pkg/vm/stack/builder.go diff --git a/pkg/vm/stack/builder.go b/pkg/vm/stack/builder.go new file mode 100644 index 000000000..e50587aff --- /dev/null +++ b/pkg/vm/stack/builder.go @@ -0,0 +1,177 @@ +package stack + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Builder follows the builder pattern and will be used to build scripts +type Builder struct { + w *bytes.Buffer + err error +} + +// NewBuilder returns a new builder object +func NewBuilder() *Builder { + return &Builder{ + w: &bytes.Buffer{}, + err: nil, + } +} + +// Bytes returns the byte representation of the built buffer +func (br *Builder) Bytes() []byte { + return br.w.Bytes() +} + +// Emit a VM Opcode with data to the given buffer. +func (br *Builder) Emit(op Instruction, b []byte) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + _, br.err = br.w.Write(b) + return br +} + +// EmitOpcode emits a single VM Opcode the given buffer. +func (br *Builder) EmitOpcode(op Instruction) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + return br +} + +// EmitBool emits a bool type the given buffer. +func (br *Builder) EmitBool(ok bool) *Builder { + if br.err != nil { + return br + } + op := PUSHT + if !ok { + op = PUSHF + } + return br.EmitOpcode(op) +} + +// EmitInt emits a int type to the given buffer. +func (br *Builder) EmitInt(i int64) *Builder { + if br.err != nil { + return br + } + if i == -1 { + return br.EmitOpcode(PUSHM1) + } + if i == 0 { + return br.EmitOpcode(PUSHF) + } + if i > 0 && i < 16 { + val := Instruction(int(PUSH1) - 1 + int(i)) + return br.EmitOpcode(val) + } + + bInt := big.NewInt(i) + val := reverse(bInt.Bytes()) + return br.EmitBytes(val) +} + +// EmitString emits a string to the given buffer. +func (br *Builder) EmitString(s string) *Builder { + if br.err != nil { + return br + } + return br.EmitBytes([]byte(s)) +} + +// EmitBytes emits a byte array to the given buffer. +func (br *Builder) EmitBytes(b []byte) *Builder { + if br.err != nil { + return br + } + var ( + n = len(b) + ) + + if n <= int(PUSHBYTES75) { + return br.Emit(Instruction(n), b) + } else if n < 0x100 { + br.Emit(PUSHDATA1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + br.Emit(PUSHDATA2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + br.Emit(PUSHDATA4, buf) + } + _, br.err = br.w.Write(b) + return br +} + +// EmitSyscall emits the syscall API to the given buffer. +// Syscall API string cannot be 0. +func (br *Builder) EmitSyscall(api string) *Builder { + if br.err != nil { + return br + } + if len(api) == 0 { + br.err = errors.New("syscall api cannot be of length 0") + } + buf := make([]byte, len(api)+1) + buf[0] = byte(len(api)) + copy(buf[1:], []byte(api)) + return br.Emit(SYSCALL, buf) +} + +// EmitCall emits a call Opcode with label to the given buffer. +func (br *Builder) EmitCall(op Instruction, label int16) *Builder { + return br.EmitJmp(op, label) +} + +// EmitJmp emits a jump Opcode along with label to the given buffer. +func (br *Builder) EmitJmp(op Instruction, label int16) *Builder { + if !isOpcodeJmp(op) { + br.err = fmt.Errorf("opcode %d is not a jump or call type", op) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return br.Emit(op, buf) +} + +// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be +// emitted instead. +func (br *Builder) EmitAppCall(scriptHash util.Uint160, tailCall bool) *Builder { + op := APPCALL + if tailCall { + op = TAILCALL + } + return br.Emit(op, scriptHash.Bytes()) +} + +// EmitAppCallWithOperationAndData emits an appcall with the given operation and data. +func (br *Builder) EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) *Builder { + br.EmitBytes(data) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +// EmitAppCallWithOperation emits an appcall with the given operation. +func (br *Builder) EmitAppCallWithOperation(scriptHash util.Uint160, operation string) *Builder { + br.EmitBool(false) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +func isOpcodeJmp(op Instruction) bool { + if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL { + return true + } + return false +} From 04c56b514c745f1a4d5f64b4c84b4e158fb8e6ab Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:30:25 +0000 Subject: [PATCH 018/117] Refactor Int, Boolean, ByteArray conversion --- pkg/vm/stack/Int.go | 21 +++++++++++---------- pkg/vm/stack/boolean.go | 8 ++++++++ pkg/vm/stack/bytearray.go | 36 ++++++++++++++++++++---------------- pkg/vm/stack/int_test.go | 15 +++++++++++++++ 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 4f1d070a5..08ee41629 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -11,6 +11,8 @@ type Int struct { // NewInt will convert a big integer into // a StackInteger func NewInt(val *big.Int) (*Int, error) { + // TOODO: check it is 32 bytes + return &Int{ abstractItem: &abstractItem{}, val: val, @@ -76,20 +78,19 @@ func (i *Int) Integer() (*Int, error) { // ByteArray override the default ByteArray method // to convert a Integer into a byte Array func (i *Int) ByteArray() (*ByteArray, error) { - return &ByteArray{ - i.abstractItem, - i.val.Bytes(), - }, nil + b := i.val.Bytes() + dest := reverse(b) + return NewByteArray(dest), nil } //Boolean override the default Boolean method // to convert an Integer into a Boolean StackItem func (i *Int) Boolean() (*Boolean, error) { - boolean := (i.val.Int64() != 0) - return &Boolean{ - i.abstractItem, - boolean, - }, nil - + return NewBoolean(boolean) +} + +//Value returns the underlying big.Int +func (i *Int) Value() *big.Int { + return i.val } diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index d6a2c12ac..2441a303c 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -6,6 +6,14 @@ type Boolean struct { val bool } +//NewBoolean returns a new boolean stack item +func NewBoolean(val bool) (*Boolean, error) { + return &Boolean{ + &abstractItem{}, + val, + }, nil +} + // Boolean overrides the default implementation // by the abstractItem, returning a Boolean struct func (b *Boolean) Boolean() (*Boolean, error) { diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d57b12f0..6b1de0d79 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -29,29 +29,33 @@ func (ba *ByteArray) ByteArray() (*ByteArray, error) { //Integer overrides the default Integer method to convert an // ByteArray Into an integer func (ba *ByteArray) Integer() (*Int, error) { - - dest := make([]byte, 0) - - for i, j := 0, len(ba.val)-1; i < j+1; i, j = i+1, j-1 { - dest[i], dest[j] = ba.val[j], ba.val[i] - } - + dest := reverse(ba.val) integerVal := new(big.Int).SetBytes(dest) + return NewInt(integerVal) - return &Int{ - ba.abstractItem, - integerVal, - }, nil } -// Boolean will convert +// Boolean will convert a byte array into a boolean stack item func (ba *ByteArray) Boolean() (*Boolean, error) { boolean, err := strconv.ParseBool(string(ba.val)) if err != nil { return nil, errors.New("cannot convert byte array to a boolean") } - return &Boolean{ - ba.abstractItem, - boolean, - }, nil + return NewBoolean(boolean) +} + +// XXX: move this into a pkg/util/slice folder +// Go mod not working +func reverse(b []byte) []byte { + if len(b) < 2 { + return b + } + + dest := make([]byte, len(b)) + + for i, j := 0, len(b)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = b[j], b[i] + } + + return dest } diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 40ef3c8b2..25d360183 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -55,3 +55,18 @@ func TestRsh(t *testing.T) { assert.Nil(t, err) assert.Equal(t, true, expected.Equal(c)) } + +func TestByteArrConversion(t *testing.T) { + + var num int64 = 100000 + + a := testMakeStackInt(t, num) + ba, err := a.ByteArray() + assert.Nil(t, err) + + have, err := ba.Integer() + assert.Nil(t, err) + + assert.Equal(t, num, have.val.Int64()) + +} From 1ff0caf40e69239be962263d25888975fe5a9144 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:32:08 +0000 Subject: [PATCH 019/117] Add Context stack Item --- pkg/vm/stack/context.go | 137 +++++++++++++++++++++++++++++++++++ pkg/vm/stack/context_test.go | 20 +++++ pkg/vm/stack/stackitem.go | 7 ++ 3 files changed, 164 insertions(+) create mode 100644 pkg/vm/stack/context.go create mode 100644 pkg/vm/stack/context_test.go diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go new file mode 100644 index 000000000..37e342739 --- /dev/null +++ b/pkg/vm/stack/context.go @@ -0,0 +1,137 @@ +package stack + +import ( + "encoding/binary" +) + +// Context represent the current execution context of the VM. +// context will be treated as stack item +// and placed onto the invocation stack +type Context struct { + *abstractItem + + // Instruction pointer. + ip int + + // The raw program script. + prog []byte + + // Breakpoints + breakPoints []int + + // Evaluation Stack + Estack RandomAccess +} + +// NewContext return a new Context object. +func NewContext(b []byte) *Context { + return &Context{ + abstractItem: &abstractItem{}, + ip: -1, + prog: b, + breakPoints: []int{}, + } +} + +// Context overrides the default implementation +// to return a context item +func (c *Context) Context() (*Context, error) { + return c, nil +} + +// Next return the next instruction to execute. +func (c *Context) Next() Instruction { + c.ip++ + if c.ip >= len(c.prog) { + return RET + } + return Instruction(c.prog[c.ip]) +} + +// IP returns the absolute instruction without taking 0 into account. +// If that program starts the ip = 0 but IP() will return 1, cause its +// the first instruction. +func (c *Context) IP() int { + return c.ip + 1 +} + +// LenInstr returns the number of instructions loaded. +func (c *Context) LenInstr() int { + return len(c.prog) +} + +// CurrInstr returns the current instruction and opcode. +func (c *Context) CurrInstr() (int, Instruction) { + if c.ip < 0 { + return c.ip, Instruction(0x00) + } + return c.ip, Instruction(c.prog[c.ip]) +} + +// Copy returns an new exact copy of c. +func (c *Context) Copy() *Context { + return &Context{ + ip: c.ip, + prog: c.prog, + breakPoints: c.breakPoints, + } +} + +// Program returns the loaded program. +func (c *Context) Program() []byte { + return c.prog +} + +func (c *Context) atBreakPoint() bool { + for _, n := range c.breakPoints { + if n == c.ip { + return true + } + } + return false +} + +func (c *Context) String() string { + return "execution context" +} + +func (c *Context) readUint32() uint32 { + start, end := c.IP(), c.IP()+4 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint32(c.prog[start:end]) + c.ip += 4 + return val +} + +func (c *Context) readUint16() uint16 { + start, end := c.IP(), c.IP()+2 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint16(c.prog[start:end]) + c.ip += 2 + return val +} + +func (c *Context) readByte() byte { + return c.readBytes(1)[0] +} + +func (c *Context) readBytes(n int) []byte { + start, end := c.IP(), c.IP()+n + if end > len(c.prog) { + return nil + } + + out := make([]byte, n) + copy(out, c.prog[start:end]) + c.ip += n + return out +} + +func (c *Context) readVarBytes() []byte { + n := c.readByte() + return c.readBytes(int(n)) +} diff --git a/pkg/vm/stack/context_test.go b/pkg/vm/stack/context_test.go new file mode 100644 index 000000000..6dbe36bb6 --- /dev/null +++ b/pkg/vm/stack/context_test.go @@ -0,0 +1,20 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNextInstruction(t *testing.T) { + // PUSHBYTES1 2 + builder := NewBuilder() + builder.EmitBytes([]byte{0x02}) //[]byte{0x01, 0x02} + + ctx := NewContext(builder.Bytes()) + op := ctx.Next() + byt := ctx.readByte() + + assert.Equal(t, PUSHBYTES1, op) + assert.Equal(t, byte(2), byt) +} diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go index 9f29f9d3b..beed13363 100644 --- a/pkg/vm/stack/stackitem.go +++ b/pkg/vm/stack/stackitem.go @@ -10,6 +10,7 @@ type Item interface { Boolean() (*Boolean, error) ByteArray() (*ByteArray, error) Array() (*Array, error) + Context() (*Context, error) } // Represents an `abstract` stack item @@ -40,3 +41,9 @@ func (a *abstractItem) ByteArray() (*ByteArray, error) { func (a *abstractItem) Array() (*Array, error) { return nil, errors.New("This stack item is not an array") } + +// Context is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Context() (*Context, error) { + return nil, errors.New("This stack item is not of type context") +} From f954e6f2ca536751f60ee6820c329d9f7be5eccb Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:33:32 +0000 Subject: [PATCH 020/117] Add Invocation stack - convenience RAS --- pkg/vm/stack/Int.go | 2 -- pkg/vm/stack/invocation.go | 53 ++++++++++++++++++++++++++++++++++++++ pkg/vm/stack/stack.go | 12 +++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 pkg/vm/stack/invocation.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 08ee41629..2483a78ea 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -11,8 +11,6 @@ type Int struct { // NewInt will convert a big integer into // a StackInteger func NewInt(val *big.Int) (*Int, error) { - // TOODO: check it is 32 bytes - return &Int{ abstractItem: &abstractItem{}, val: val, diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocation.go new file mode 100644 index 000000000..991e28231 --- /dev/null +++ b/pkg/vm/stack/invocation.go @@ -0,0 +1,53 @@ +package stack + +import "errors" + +// Invocation embeds a Random Access stack +// Providing helper methods for the context object +type Invocation struct{ RandomAccess } + +//NewInvocation will return a new +// Invocation stack +func NewInvocation() *Invocation { + return &Invocation{ + RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + }, + } +} + +func (i *Invocation) peekContext(n uint16) (*Context, error) { + item, err := i.Peek(n) + if err != nil { + return nil, err + } + ctx, err := item.Context() + if err != nil { + return nil, err + } + return ctx, nil +} + +// CurrentContext returns the current context on the invocation stack +func (i *Invocation) CurrentContext() (*Context, error) { + return i.peekContext(0) +} + +// CallingContext will return the cntext item +// that will be called next. +func (i *Invocation) CallingContext() (*Context, error) { + if i.Len() < 1 { + return nil, errors.New("Length of invocation stack is < 1, no calling context") + } + return i.peekContext(1) +} + +// EntryContext will return the context item that +// started the program +func (i *Invocation) EntryContext() (*Context, error) { + + // firstItemIndex refers to the first item + // that was popped on the stack + firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned + return i.peekContext(firstItemIndex) +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 3c01ad4e7..a298897e4 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -119,3 +119,15 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } + +// Convenience Functions + +// PopInt will remove the last stack item that was added +// And cast it to an integer +func (ras *RandomAccess) PopInt() (*Int, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.Integer() +} From ce2cad0817e7a9901e01630f7c4b5358e0ca5e48 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:34:04 +0000 Subject: [PATCH 021/117] rename testhelper to test_helper --- pkg/vm/stack/{testhelper.go => test_helper.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/vm/stack/{testhelper.go => test_helper.go} (100%) diff --git a/pkg/vm/stack/testhelper.go b/pkg/vm/stack/test_helper.go similarity index 100% rename from pkg/vm/stack/testhelper.go rename to pkg/vm/stack/test_helper.go From 101d48cd27f55302e373e2e2e2678bd669d86d1a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:35:12 +0000 Subject: [PATCH 022/117] Move opcode file --- pkg/vm/{instructions.go => stack/instruction.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename pkg/vm/{instructions.go => stack/instruction.go} (99%) diff --git a/pkg/vm/instructions.go b/pkg/vm/stack/instruction.go similarity index 99% rename from pkg/vm/instructions.go rename to pkg/vm/stack/instruction.go index 6a78f6a6a..1317a1e3e 100644 --- a/pkg/vm/instructions.go +++ b/pkg/vm/stack/instruction.go @@ -1,4 +1,4 @@ -package vm +package stack // Instruction represents a operation code in the neovm type Instruction byte From c7fb4c3bdf9ad324a832acbceb9cdef32a513592 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:36:16 +0000 Subject: [PATCH 023/117] - Add `Add` OpCode - Add Opcode Function map --- pkg/vm/vm_ops.go | 7 +++++++ pkg/vm/vm_ops_maths.go | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 pkg/vm/vm_ops.go create mode 100644 pkg/vm/vm_ops_maths.go diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go new file mode 100644 index 000000000..456552597 --- /dev/null +++ b/pkg/vm/vm_ops.go @@ -0,0 +1,7 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ + stack.ADD: Add, +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go new file mode 100644 index 000000000..f31aab6ec --- /dev/null +++ b/pkg/vm/vm_ops_maths.go @@ -0,0 +1,25 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +// Add adds two stack Items together. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be added together +func Add(ctx *stack.Context, istack *stack.Invocation) error { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return err + } + res, err := operandA.Add(operandB) + if err != nil { + return err + } + + ctx.Estack.Push(res) + + return nil +} From baf9d2b76850fb184f30680a06704d996d999468 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:42:35 +0000 Subject: [PATCH 024/117] - Add test for math `Add` opcode - basic opcode execution --- pkg/vm/state.go | 10 ++++++++++ pkg/vm/vm.go | 38 ++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 pkg/vm/state.go create mode 100644 pkg/vm/vm.go create mode 100644 pkg/vm/vm_ops_maths_test.go diff --git a/pkg/vm/state.go b/pkg/vm/state.go new file mode 100644 index 000000000..4090ec86e --- /dev/null +++ b/pkg/vm/state.go @@ -0,0 +1,10 @@ +package vm + +type vmstate byte + +const ( + NONE = 0 + HALT = 1 << 0 + FAULT = 1 << 1 + BREAK = 1 << 2 +) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go new file mode 100644 index 000000000..1793be031 --- /dev/null +++ b/pkg/vm/vm.go @@ -0,0 +1,38 @@ +package vm + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// VM represents an instance of a Neo Virtual Machine +type VM struct { + InvocationStack stack.Invocation + state vmstate +} + +//NewVM loads in a script +// uses the script to initiate a Context object +// pushes the context to the invocation stack +func NewVM(script []byte) *VM { + ctx := stack.NewContext(script) + v := &VM{ + state: NONE, + } + v.InvocationStack.Push(ctx) + return v +} + +// ExecuteOp will execute one opcode for a given context +func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { + handleOp, ok := opFunc[op] + if !ok { + return fmt.Errorf("unknown opcode entered %v", op) + } + err := handleOp(ctx, &v.InvocationStack) + if err != nil { + return err + } + return nil +} diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go new file mode 100644 index 000000000..a9d80373a --- /dev/null +++ b/pkg/vm/vm_ops_maths_test.go @@ -0,0 +1,39 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestAddOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(23)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.ExecuteOp(stack.ADD, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(43), item.Value().Int64()) + +} From abc3b46f1ce9e3b7a9ad575f8c3ffbdaf8b047a4 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:54:52 +0000 Subject: [PATCH 025/117] Add popTwoIntegers convenience func --- pkg/vm/vm_ops_maths.go | 45 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index f31aab6ec..b49c11d3e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -1,19 +1,16 @@ package vm -import "github.com/CityOfZion/neo-go/pkg/vm/stack" +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together func Add(ctx *stack.Context, istack *stack.Invocation) error { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return err - } + + operandA, operandB, err := popTwoIntegers(ctx) + res, err := operandA.Add(operandB) if err != nil { return err @@ -23,3 +20,33 @@ func Add(ctx *stack.Context, istack *stack.Invocation) error { return nil } + +// Sub subtracts two stack Items. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be subtracted together +func Sub(ctx *stack.Context, istack *stack.Invocation) error { + + operandA, operandB, err := popTwoIntegers(ctx) + + res, err := operandB.Sub(operandA) + if err != nil { + return err + } + + ctx.Estack.Push(res) + + return nil +} + +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} From 280d526f417333ed50aebcfa3113cfbeda72142a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 22:55:08 +0000 Subject: [PATCH 026/117] Add `SUB` Opcode --- pkg/vm/vm_ops.go | 1 + pkg/vm/vm_ops_maths_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 456552597..36b22e472 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -4,4 +4,5 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ stack.ADD: Add, + stack.SUB: Sub, } diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index a9d80373a..6b0e13bbe 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -37,3 +37,33 @@ func TestAddOp(t *testing.T) { assert.Equal(t, int64(43), item.Value().Int64()) } + +func TestSubOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(30)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(40)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.ExecuteOp(stack.SUB, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(-10), item.Value().Int64()) + +} From 9a59755745e35935a96b16c3b91371d1fe2a6218 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 23:21:30 +0000 Subject: [PATCH 027/117] Export Context Read methods - Return errors where failable --- pkg/vm/stack/context.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index 37e342739..343800afc 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -2,6 +2,7 @@ package stack import ( "encoding/binary" + "errors" ) // Context represent the current execution context of the VM. @@ -95,7 +96,8 @@ func (c *Context) String() string { return "execution context" } -func (c *Context) readUint32() uint32 { +// ReadUint32 reads a uint32 from the script +func (c *Context) ReadUint32() uint32 { start, end := c.IP(), c.IP()+4 if end > len(c.prog) { return 0 @@ -105,7 +107,8 @@ func (c *Context) readUint32() uint32 { return val } -func (c *Context) readUint16() uint16 { +// ReadUint16 reads a uint16 from the script +func (c *Context) ReadUint16() uint16 { start, end := c.IP(), c.IP()+2 if end > len(c.prog) { return 0 @@ -115,23 +118,33 @@ func (c *Context) readUint16() uint16 { return val } -func (c *Context) readByte() byte { - return c.readBytes(1)[0] +// ReadByte reads one byte from the script +func (c *Context) ReadByte() (byte, error) { + byt, err := c.ReadBytes(1) + if err != nil { + return 0, err + } + + return byt[0], nil } -func (c *Context) readBytes(n int) []byte { +// ReadBytes will read n bytes from the context +func (c *Context) ReadBytes(n int) ([]byte, error) { start, end := c.IP(), c.IP()+n if end > len(c.prog) { - return nil + return nil, errors.New("Too many bytes to read, pointer goes past end of program") } out := make([]byte, n) copy(out, c.prog[start:end]) c.ip += n - return out + return out, nil } -func (c *Context) readVarBytes() []byte { - n := c.readByte() - return c.readBytes(int(n)) +func (c *Context) readVarBytes() ([]byte, error) { + n, err := c.ReadByte() + if err != nil { + return nil, err + } + return c.ReadBytes(int(n)) } From 80fd427517dc63e3e314bea2be8e8e9c637f8036 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Fri, 15 Mar 2019 23:37:54 +0000 Subject: [PATCH 028/117] - Add `Op` to handleOP func signature - Add PushNBytes OPcode --- pkg/vm/vm.go | 3 ++- pkg/vm/vm_ops.go | 14 +++++++++++--- pkg/vm/vm_ops_maths.go | 4 ++-- pkg/vm/vm_ops_stackmani.go | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 pkg/vm/vm_ops_stackmani.go diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1793be031..766e43422 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -26,11 +26,12 @@ func NewVM(script []byte) *VM { // ExecuteOp will execute one opcode for a given context func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { + handleOp, ok := opFunc[op] if !ok { return fmt.Errorf("unknown opcode entered %v", op) } - err := handleOp(ctx, &v.InvocationStack) + err := handleOp(op, ctx, &v.InvocationStack) if err != nil { return err } diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 36b22e472..f2424d81a 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,7 +2,15 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(ctx *stack.Context, istack *stack.Invocation) error{ - stack.ADD: Add, - stack.SUB: Sub, +var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error{ + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, +} + +func init() { + for i := int(stack.PUSHBYTES1); i <= int(stack.PUSHBYTES75); i++ { + opFunc[stack.Instruction(i)] = PushNBytes + } } diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index b49c11d3e..7d6afad41 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,7 +7,7 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(ctx *stack.Context, istack *stack.Invocation) error { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { operandA, operandB, err := popTwoIntegers(ctx) @@ -24,7 +24,7 @@ func Add(ctx *stack.Context, istack *stack.Invocation) error { // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(ctx *stack.Context, istack *stack.Invocation) error { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { operandA, operandB, err := popTwoIntegers(ctx) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go new file mode 100644 index 000000000..c510eb7db --- /dev/null +++ b/pkg/vm/vm_ops_stackmani.go @@ -0,0 +1,19 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Stack Manipulation Opcodes + +// PushNBytes will Read N Bytes from the script and push it onto the stack +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { + + val, err := ctx.ReadBytes(int(op)) + if err != nil { + return err + } + ba := stack.NewByteArray(val) + ctx.Estack.Push(ba) + return nil +} From 48413900ca000b28469599ec64d7b98a8195e801 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:44:03 +0000 Subject: [PATCH 029/117] remove error on NewBoolean Expose underlying with Getter on Boolean StackItem Add Equals method for ByteArray --- pkg/vm/stack/Int.go | 2 +- pkg/vm/stack/boolean.go | 9 +++++++-- pkg/vm/stack/bytearray.go | 12 +++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 2483a78ea..ac9a4cdba 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -85,7 +85,7 @@ func (i *Int) ByteArray() (*ByteArray, error) { // to convert an Integer into a Boolean StackItem func (i *Int) Boolean() (*Boolean, error) { boolean := (i.val.Int64() != 0) - return NewBoolean(boolean) + return NewBoolean(boolean), nil } //Value returns the underlying big.Int diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 2441a303c..66e3647e5 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -7,11 +7,11 @@ type Boolean struct { } //NewBoolean returns a new boolean stack item -func NewBoolean(val bool) (*Boolean, error) { +func NewBoolean(val bool) *Boolean { return &Boolean{ &abstractItem{}, val, - }, nil + } } // Boolean overrides the default implementation @@ -19,3 +19,8 @@ func NewBoolean(val bool) (*Boolean, error) { func (b *Boolean) Boolean() (*Boolean, error) { return b, nil } + +// Value returns the underlying boolean value +func (b *Boolean) Value() bool { + return b.val +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 6b1de0d79..7d1c3c818 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -1,6 +1,7 @@ package stack import ( + "bytes" "errors" "math/big" "strconv" @@ -26,6 +27,15 @@ func (ba *ByteArray) ByteArray() (*ByteArray, error) { return ba, nil } +//Equals returns true, if two bytearrays are equal +func (ba *ByteArray) Equals(other *ByteArray) *Boolean { + // If either are nil, return false + if ba == nil || other == nil { + return NewBoolean(false) + } + return NewBoolean(bytes.Equal(ba.val, other.val)) +} + //Integer overrides the default Integer method to convert an // ByteArray Into an integer func (ba *ByteArray) Integer() (*Int, error) { @@ -41,7 +51,7 @@ func (ba *ByteArray) Boolean() (*Boolean, error) { if err != nil { return nil, errors.New("cannot convert byte array to a boolean") } - return NewBoolean(boolean) + return NewBoolean(boolean), nil } // XXX: move this into a pkg/util/slice folder From 9eb11d2822b02bd76754f72ef7388fc9b7bb1432 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:45:48 +0000 Subject: [PATCH 030/117] Make Next() method on Context failable refactor peekContext and Peek --- pkg/vm/stack/context.go | 6 +++--- pkg/vm/stack/invocation.go | 13 ++++++++----- pkg/vm/stack/stack.go | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index 343800afc..ee6cce1a3 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -41,12 +41,12 @@ func (c *Context) Context() (*Context, error) { } // Next return the next instruction to execute. -func (c *Context) Next() Instruction { +func (c *Context) Next() (Instruction, error) { c.ip++ if c.ip >= len(c.prog) { - return RET + return RET, errors.New("program pointer is more than the length of program. RETURNING") } - return Instruction(c.prog[c.ip]) + return Instruction(c.prog[c.ip]), nil } // IP returns the absolute instruction without taking 0 into account. diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocation.go index 991e28231..a3a66b8ec 100644 --- a/pkg/vm/stack/invocation.go +++ b/pkg/vm/stack/invocation.go @@ -21,11 +21,7 @@ func (i *Invocation) peekContext(n uint16) (*Context, error) { if err != nil { return nil, err } - ctx, err := item.Context() - if err != nil { - return nil, err - } - return ctx, nil + return item.Context() } // CurrentContext returns the current context on the invocation stack @@ -33,6 +29,13 @@ func (i *Invocation) CurrentContext() (*Context, error) { return i.peekContext(0) } +// RemoveCurrentContext removes the context on the top of the invocation stack +// This is a convenience method for Pop +func (i *Invocation) RemoveCurrentContext() error { + _, err := i.Pop() + return err +} + // CallingContext will return the cntext item // that will be called next. func (i *Invocation) CallingContext() (*Context, error) { diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index a298897e4..e857a9ada 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -106,15 +106,14 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } - if ras.vals == nil { - return nil, errors.New("Cannot peak at a nil stack") + if ras.Len() < 1 { + return nil, fmt.Errorf("cannot peak at a stack with no item, length of stack is %d", ras.Len()) } // Check that we are not peeking out of the bounds if n > stackSize-1 { return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) } - index := stackSize - n - 1 return ras.vals[index], nil @@ -131,3 +130,13 @@ func (ras *RandomAccess) PopInt() (*Int, error) { } return item.Integer() } + +// PopByteArray will remove the last stack item that was added +// And cast it to an ByteArray +func (ras *RandomAccess) PopByteArray() (*ByteArray, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.ByteArray() +} From a7e973030c6c15a80831889827a14f4df23e66cf Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 21:52:05 +0000 Subject: [PATCH 031/117] Remove context_test rename invocation to invocationstack export vmstate --- pkg/vm/stack/context_test.go | 20 ------------------- .../{invocation.go => invocationstack.go} | 0 pkg/vm/state.go | 3 ++- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 pkg/vm/stack/context_test.go rename pkg/vm/stack/{invocation.go => invocationstack.go} (100%) diff --git a/pkg/vm/stack/context_test.go b/pkg/vm/stack/context_test.go deleted file mode 100644 index 6dbe36bb6..000000000 --- a/pkg/vm/stack/context_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package stack - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNextInstruction(t *testing.T) { - // PUSHBYTES1 2 - builder := NewBuilder() - builder.EmitBytes([]byte{0x02}) //[]byte{0x01, 0x02} - - ctx := NewContext(builder.Bytes()) - op := ctx.Next() - byt := ctx.readByte() - - assert.Equal(t, PUSHBYTES1, op) - assert.Equal(t, byte(2), byt) -} diff --git a/pkg/vm/stack/invocation.go b/pkg/vm/stack/invocationstack.go similarity index 100% rename from pkg/vm/stack/invocation.go rename to pkg/vm/stack/invocationstack.go diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 4090ec86e..64db70f2f 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -1,6 +1,7 @@ package vm -type vmstate byte +//Vmstate represents all possible states that the neo-vm can be in +type Vmstate byte const ( NONE = 0 From 329f8f388c16088b389d08b5ad2c2fe2f165d78c Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:05:00 +0000 Subject: [PATCH 032/117] Add ExecuteOp, Step and Run methods on the VM --- pkg/vm/vm.go | 52 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 766e43422..604348baf 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -9,12 +9,13 @@ import ( // VM represents an instance of a Neo Virtual Machine type VM struct { InvocationStack stack.Invocation - state vmstate + state Vmstate } -//NewVM loads in a script -// uses the script to initiate a Context object -// pushes the context to the invocation stack +// NewVM will: +// Set the state of the VM to NONE +// instantiate a script as a new context +// Push the Context to the Invocation stack func NewVM(script []byte) *VM { ctx := stack.NewContext(script) v := &VM{ @@ -24,16 +25,43 @@ func NewVM(script []byte) *VM { return v } -// ExecuteOp will execute one opcode for a given context -func (v *VM) ExecuteOp(op stack.Instruction, ctx *stack.Context) error { +// Run loops over the current context by continuously steppping. +// Run breaks; once step returns an error or any state that is not NONE +func (v *VM) Run() (Vmstate, error) { + for { + state, err := v.step() + if err != nil || state != NONE { + return state, err + } + } +} +// step will read `one` opcode from the script in the current context +// Then excute that opcode +func (v *VM) step() (Vmstate, error) { + // Get Current Context + ctx, err := v.InvocationStack.CurrentContext() + if err != nil { + return FAULT, err + } + // Read Opcode from context + op, _ := ctx.Next() // The only error that can occur from this, is if the pointer goes over the pointer + // In the NEO-VM specs, this is ignored and we return the RET opcode + // Execute OpCode + state, err := v.executeOp(stack.Instruction(op), ctx) + if err != nil { + return FAULT, err + } + return state, nil +} + +// ExecuteOp will execute one opcode on a given context. +// If the opcode is not registered, then an unknown opcode error will be returned +func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error) { + //Find function which handles that specific opcode handleOp, ok := opFunc[op] if !ok { - return fmt.Errorf("unknown opcode entered %v", op) + return FAULT, fmt.Errorf("unknown opcode entered %v", op) } - err := handleOp(op, ctx, &v.InvocationStack) - if err != nil { - return err - } - return nil + return handleOp(op, ctx, &v.InvocationStack) } From 31511e55d02ffde0400a08aea98d3cae3389d0b6 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:06:32 +0000 Subject: [PATCH 033/117] Add Equal Opcode --- pkg/vm/vm_ops_bitwise.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pkg/vm/vm_ops_bitwise.go diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go new file mode 100644 index 000000000..8c73ce25e --- /dev/null +++ b/pkg/vm/vm_ops_bitwise.go @@ -0,0 +1,17 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +// Bitwise logic + +// EQUAL pushes true to the stack +// If the two top items on the stack are equal +func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + itemA, itemB, err := popTwoByteArrays(ctx) + if err != nil { + return FAULT, err + } + ctx.Estack.Push(itemA.Equals(itemB)) + return NONE, nil +} From 79e92d5e1494715a080e5805ad35d51234b97b42 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:08:35 +0000 Subject: [PATCH 034/117] Add THROWIFNOT Opcode --- pkg/vm/vm_ops_exceptions.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 pkg/vm/vm_ops_exceptions.go diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go new file mode 100644 index 000000000..bdf45dbfa --- /dev/null +++ b/pkg/vm/vm_ops_exceptions.go @@ -0,0 +1,33 @@ +package vm + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// vm exceptions + +// THROWIFNOT faults if the item on the top of the stack +// does not evaluate to true +// For specific logic on how a number of bytearray is evaluated can be seen +// from the boolean conversion methods on the stack items +func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + // Pop item from top of stack + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + // Convert to a boolean + ok, err := item.Boolean() + if err != nil { + return FAULT, err + } + + // If false, throw + if !ok.Value() { + return FAULT, errors.New("Item on top of stack evaluates to false") + } + return NONE, nil +} From 7b519eba0dd6b0efd48ee5e9bc602d48bba000e8 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:04 +0000 Subject: [PATCH 035/117] Add RET Opcode --- pkg/vm/vm_ops_flow.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pkg/vm/vm_ops_flow.go diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go new file mode 100644 index 000000000..7a90638b0 --- /dev/null +++ b/pkg/vm/vm_ops_flow.go @@ -0,0 +1,25 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Flow control + +// RET Returns from the current context +// Returns HALT if there are nomore context's to run +func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { + + // Pop current context from the Inovation stack + err := istack.RemoveCurrentContext() + if err != nil { + return FAULT, err + } + + // If there are no-more context's left to ran, then we HALT + if istack.Len() == 0 { + return HALT, nil + } + + return NONE, nil +} From c7e32e7eb303a236887faeced5311fda217c9a66 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:23 +0000 Subject: [PATCH 036/117] Refactor PushNBytes Opcode --- pkg/vm/vm_ops_stackmani.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index c510eb7db..366beb15b 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -7,13 +7,13 @@ import ( // Stack Manipulation Opcodes // PushNBytes will Read N Bytes from the script and push it onto the stack -func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { val, err := ctx.ReadBytes(int(op)) if err != nil { - return err + return FAULT, err } ba := stack.NewByteArray(val) ctx.Estack.Push(ba) - return nil + return NONE, nil } From 17c53d10812b31e88e74302a219cf88b623894de Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:09:34 +0000 Subject: [PATCH 037/117] refactor Add, Sub to return VMSTATE add popTwoByteArrays helper function --- pkg/vm/vm_ops_maths.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 7d6afad41..6085dccb8 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,35 +7,39 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) - + if err != nil { + return FAULT, err + } res, err := operandA.Add(operandB) if err != nil { - return err + return FAULT, err } ctx.Estack.Push(res) - return nil + return NONE, nil } // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) - + if err != nil { + return FAULT, err + } res, err := operandB.Sub(operandA) if err != nil { - return err + return HALT, err } ctx.Estack.Push(res) - return nil + return NONE, nil } func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { @@ -50,3 +54,17 @@ func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { return operandA, operandB, nil } + +func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { + // Pop first stack item and cast as byte array + ba1, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + // Pop second stack item and cast as byte array + ba2, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + return ba1, ba2, nil +} From ef364900bb6940fd36a176a218ff5f5973cb15f3 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 16 Mar 2019 22:15:08 +0000 Subject: [PATCH 038/117] Add basic tests for vm --- pkg/vm/vm_ops.go | 5 +- pkg/vm/vm_ops_maths_test.go | 4 +- pkg/vm/vm_test.go | 113 ++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 pkg/vm/vm_test.go diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index f2424d81a..bf9291933 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,11 +2,14 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) error{ +var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error){ stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, } func init() { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 6b0e13bbe..4964e6923 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -24,7 +24,7 @@ func TestAddOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.ExecuteOp(stack.ADD, ctx) + v.executeOp(stack.ADD, ctx) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -54,7 +54,7 @@ func TestSubOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.ExecuteOp(stack.SUB, ctx) + v.executeOp(stack.SUB, ctx) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go new file mode 100644 index 000000000..db12c014c --- /dev/null +++ b/pkg/vm/vm_test.go @@ -0,0 +1,113 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestPushAdd(t *testing.T) { + builder := stack.NewBuilder() + + // PUSH TWO NUMBER + // ADD THEM TOGETHER + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Execute first OPCODE + // Should be PUSH(20) + state, err := vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 20 on stack + ok := peekTopEStackIsValue(t, vm, 20) + assert.True(t, ok) + + // Excute second OPCODE + // Should be PUSH(34) + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 34 at the top of the stack + ok = peekTopEStackIsValue(t, vm, 34) + assert.True(t, ok) + + // Excute third OPCODE + // Should Add both values on the stack + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should now have one value on the stack + //It should be equal to 20+34 = 54 + ok = EstackLen(t, vm, 1) + assert.True(t, ok) + ok = peekTopEStackIsValue(t, vm, 54) + assert.True(t, ok) + + // If we try to step again, we should get an error and HALT + // because we have gone over the instruction pointer + state, err = vm.step() + assert.Equal(t, HALT, int(state)) + assert.NotNil(t, err) + +} + +func TestSimpleRun(t *testing.T) { + + // Program pushes 20 and 34 to the stack + // Adds them together + // pushes 54 to the stack + // Checks if result of addition and 54 are equal + // Faults if not + + // Push(20) + // Push(34) + // Add + // Push(54) + // Equal + //THROWIFNOT + builder := stack.NewBuilder() + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT) + // Pass program to VM + vm := NewVM(builder.Bytes()) + + _, err := vm.Run() + assert.Nil(t, err) + +} + +// returns true if the value at the top of the evaluation stack is a integer +// and equals the value passed in +func peekTopEStackIsValue(t *testing.T, vm *VM, value int64) bool { + item := peakTopEstack(t, vm) + integer, err := item.Integer() + assert.Nil(t, err) + return value == integer.Value().Int64() +} + +// peaks the stack item on the top of the evaluation stack +// if the current context and returns it +func peakTopEstack(t *testing.T, vm *VM) stack.Item { + ctx, err := vm.InvocationStack.CurrentContext() + fmt.Println(err) + assert.Nil(t, err) + item, err := ctx.Estack.Peek(0) + assert.Nil(t, err) + return item +} + +// returns true if the total number of items on the evaluation stack +// is equal to value +func EstackLen(t *testing.T, vm *VM, value int) bool { + ctx, err := vm.InvocationStack.CurrentContext() + assert.Nil(t, err) + return value == ctx.Estack.Len() +} From f8979fe7afee16653fd7d7d06c94dd91cf9595ca Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Sun, 17 Mar 2019 18:26:35 +0000 Subject: [PATCH 039/117] Fix lint errors (#182) * golint and minor changes to make code readable --- go.mod | 1 + go.sum | 3 + pkg/crypto/aes/aes256.go | 4 +- pkg/crypto/base58/base58.go | 4 +- pkg/crypto/elliptic/curves.go | 6 ++ pkg/crypto/elliptic/elliptic.go | 6 +- pkg/crypto/hash/hash.go | 11 +++ pkg/crypto/privatekey/privatekey.go | 8 ++ pkg/crypto/publickey/TestHelper/helper.go | 25 ++++-- pkg/crypto/publickey/publickey.go | 4 + pkg/database/leveldb.go | 29 ++++++- pkg/database/table.go | 12 ++- pkg/peer/config.go | 1 - pkg/peer/peer.go | 68 ++++++++++------ pkg/peer/peerhandshake.go | 3 +- pkg/peer/stall/stall.go | 8 +- pkg/pubsub/event.go | 12 ++- pkg/pubsub/pub.go | 1 + pkg/pubsub/sub.go | 1 + pkg/wire/base.go | 6 +- pkg/wire/command/command.go | 3 +- pkg/wire/message.go | 10 ++- pkg/wire/payload/block.go | 10 ++- pkg/wire/payload/blockbase.go | 12 ++- pkg/wire/payload/maddr.go | 25 +++--- pkg/wire/payload/mblock.go | 10 ++- pkg/wire/payload/mgetaddr.go | 9 ++- pkg/wire/payload/mgetblocks.go | 2 + pkg/wire/payload/mgetdata.go | 2 + pkg/wire/payload/mgetheaders.go | 8 +- pkg/wire/payload/mheaders.go | 30 ++++---- pkg/wire/payload/minventory.go | 60 ++++++++------- pkg/wire/payload/mmempool.go | 9 ++- pkg/wire/payload/mtx.go | 11 +-- pkg/wire/payload/mverack.go | 9 ++- pkg/wire/payload/mversion.go | 22 +++--- pkg/wire/payload/net_addr.go | 25 +++--- pkg/wire/payload/transaction/Attribute.go | 12 ++- pkg/wire/payload/transaction/Input.go | 4 + pkg/wire/payload/transaction/Output.go | 4 + pkg/wire/payload/transaction/Witness.go | 3 + pkg/wire/payload/transaction/attr_usage.go | 1 + pkg/wire/payload/transaction/base.go | 13 +++- pkg/wire/payload/transaction/claim.go | 4 +- pkg/wire/payload/transaction/contract.go | 10 +-- pkg/wire/payload/transaction/enrollment.go | 14 ++-- pkg/wire/payload/transaction/invocation.go | 4 +- pkg/wire/payload/transaction/issue.go | 7 +- pkg/wire/payload/transaction/miner.go | 6 +- pkg/wire/payload/transaction/publickey.go | 5 +- pkg/wire/payload/transaction/publish.go | 2 + pkg/wire/payload/transaction/register.go | 2 + pkg/wire/payload/transaction/state.go | 3 + .../payload/transaction/statedescriptor.go | 7 +- pkg/wire/payload/transaction/types/types.go | 4 + pkg/wire/payload/transaction/util.go | 13 ++-- .../payload/transaction/version/version.go | 7 +- pkg/wire/protocol/protocol.go | 7 +- pkg/wire/util/Checksum/checksum.go | 4 + pkg/wire/util/address/address.go | 1 + pkg/wire/util/binaryReader.go | 12 +++ pkg/wire/util/binaryWriter.go | 17 ++-- pkg/wire/util/crypto/base58/base58.go | 77 ++++++++++--------- pkg/wire/util/crypto/hash/hash.go | 6 ++ pkg/wire/util/fixed8/fixed8.go | 9 ++- pkg/wire/util/io/io.go | 1 + pkg/wire/util/ip/ip.go | 1 + pkg/wire/util/slice/slice.go | 2 +- pkg/wire/util/uint256.go | 8 +- pkg/wire/util/util.go | 20 ++--- 70 files changed, 502 insertions(+), 258 deletions(-) diff --git a/go.mod b/go.mod index 4887a0098..385728796 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ require ( github.com/o3labs/neo-utils v0.0.0-20190129071622-8ae0bc31751f github.com/stretchr/testify v1.3.0 github.com/syndtr/goleveldb v1.0.0 + golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c // indirect golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b ) diff --git a/go.sum b/go.sum index ffd5defcc..0764f88ed 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM= +golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b h1:+/WWzjwW6gidDJnMKWLKLX1gxn7irUTF1fLpQovfQ5M= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= @@ -40,3 +42,4 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/crypto/aes/aes256.go b/pkg/crypto/aes/aes256.go index 6ef1b5c74..c23c48843 100755 --- a/pkg/crypto/aes/aes256.go +++ b/pkg/crypto/aes/aes256.go @@ -5,7 +5,7 @@ import ( "crypto/cipher" ) -// AESEncrypt encrypts the key with the given source. +// Encrypt encrypts the key with the given source. func Encrypt(src, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { @@ -19,7 +19,7 @@ func Encrypt(src, key []byte) ([]byte, error) { return out, nil } -// AESDecrypt decrypts the encrypted source with the given key. +// Decrypt decrypts the encrypted source with the given key. func Decrypt(crypted, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { diff --git a/pkg/crypto/base58/base58.go b/pkg/crypto/base58/base58.go index a62181a72..bfac3d4f0 100755 --- a/pkg/crypto/base58/base58.go +++ b/pkg/crypto/base58/base58.go @@ -22,7 +22,7 @@ var decodeMap = map[rune]int64{ 'x': 55, 'y': 56, 'z': 57, } -// Base58Decode decodes the base58 encoded string. +// Decode decodes the base58 encoded string. func Decode(s string) ([]byte, error) { var ( startIndex = 0 @@ -58,7 +58,7 @@ func Decode(s string) ([]byte, error) { return buf, nil } -// Base58Encode encodes a byte slice to be a base58 encoded string. +// Encode encodes a byte slice to be a base58 encoded string. func Encode(bytes []byte) string { var ( lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" diff --git a/pkg/crypto/elliptic/curves.go b/pkg/crypto/elliptic/curves.go index 33f19540b..5b24bb638 100755 --- a/pkg/crypto/elliptic/curves.go +++ b/pkg/crypto/elliptic/curves.go @@ -18,10 +18,13 @@ var curve Curve type curveType string const ( + // Secp256r1 curve type Secp256r1 curveType = "Secp256r1" + // Secp256k1 curve type Secp256k1 curveType = "Secp256k1" ) +// SetCurveSecp256r1 Will set the curve parameters to match Secp256r1 func (ChosenCurve *Curve) SetCurveSecp256r1() { ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16) //Q ChosenCurve.A, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16) @@ -33,6 +36,7 @@ func (ChosenCurve *Curve) SetCurveSecp256r1() { ChosenCurve.Name = "Secp256r1" } +// SetCurveSecp256k1 Will set the curve parameters to match Secp256k1 func (ChosenCurve *Curve) SetCurveSecp256k1() { ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) ChosenCurve.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16) @@ -44,6 +48,8 @@ func (ChosenCurve *Curve) SetCurveSecp256k1() { ChosenCurve.Name = "Secp256k1" } +//NewEllipticCurve will instantiate a new EllipticCurve +//Defaults to secp256r1 func NewEllipticCurve(ct curveType) Curve { var curve Curve switch ct { diff --git a/pkg/crypto/elliptic/elliptic.go b/pkg/crypto/elliptic/elliptic.go index 26a07388f..a176c3021 100755 --- a/pkg/crypto/elliptic/elliptic.go +++ b/pkg/crypto/elliptic/elliptic.go @@ -19,8 +19,8 @@ type Point struct { Y *big.Int } -/* y**2 = x**3 + a*x + b % p */ // Curve represents the parameters of a short Weierstrass equation elliptic curve. +/* y**2 = x**3 + a*x + b % p */ type Curve struct { A *big.Int B *big.Int @@ -44,6 +44,7 @@ func (p *Point) format() string { return fmt.Sprintf("(%s,%s)", hex.EncodeToString(p.X.Bytes()), hex.EncodeToString(p.Y.Bytes())) } +// Params represent the paramters for the Elliptic Curve func (ec Curve) Params() *nativeelliptic.CurveParams { return &nativeelliptic.CurveParams{ P: ec.P, @@ -302,12 +303,13 @@ func (ec *Curve) Decompress(x *big.Int, ylsb uint) (P Point, err error) { P.Y = y if !ec.IsOnCurve(P.X, P.Y) { - return P, errors.New("Compressed (x, ylsb) not on curve.") + return P, errors.New("compressed (x, ylsb) not on curve") } return P, nil } +// Double will return the (x1+x1,y1+y1) func (ec Curve) Double(x1, y1 *big.Int) (x, y *big.Int) { x = &big.Int{} x.SetBytes([]byte{0x00}) diff --git a/pkg/crypto/hash/hash.go b/pkg/crypto/hash/hash.go index 22ab1c6d6..be7ab29b9 100755 --- a/pkg/crypto/hash/hash.go +++ b/pkg/crypto/hash/hash.go @@ -8,6 +8,8 @@ import ( "golang.org/x/crypto/ripemd160" ) +// Sha256 hashes the incoming byte slice +// using the sha256 algorithm func Sha256(data []byte) (util.Uint256, error) { var hash util.Uint256 hasher := sha256.New() @@ -21,6 +23,7 @@ func Sha256(data []byte) (util.Uint256, error) { return hash, nil } +// DoubleSha256 performs sha256 twice on the given data func DoubleSha256(data []byte) (util.Uint256, error) { var hash util.Uint256 @@ -36,6 +39,8 @@ func DoubleSha256(data []byte) (util.Uint256, error) { return hash, nil } +// RipeMD160 performs the RIPEMD160 hash algorithm +// on the given data func RipeMD160(data []byte) (util.Uint160, error) { var hash util.Uint160 hasher := ripemd160.New() @@ -49,6 +54,8 @@ func RipeMD160(data []byte) (util.Uint160, error) { return hash, nil } +// Hash160 performs sha256 and then ripemd160 +// on the given data func Hash160(data []byte) (util.Uint160, error) { var hash util.Uint160 h1, err := Sha256(data) @@ -63,6 +70,8 @@ func Hash160(data []byte) (util.Uint160, error) { return hash, nil } +// Checksum returns the checksum for a given piece of data +// using sha256 twice as the hash algorithm func Checksum(data []byte) ([]byte, error) { hash, err := Sum(data) if err != nil { @@ -71,6 +80,8 @@ func Checksum(data []byte) ([]byte, error) { return hash[:4], nil } +// Sum performs sha256 twice on the given data +// XXX(issue): We should remove this and just do doublesha256 func Sum(b []byte) (util.Uint256, error) { hash, err := DoubleSha256((b)) return hash, err diff --git a/pkg/crypto/privatekey/privatekey.go b/pkg/crypto/privatekey/privatekey.go index 513d9366f..fa8af0b1c 100755 --- a/pkg/crypto/privatekey/privatekey.go +++ b/pkg/crypto/privatekey/privatekey.go @@ -23,6 +23,8 @@ type PrivateKey struct { b []byte } +// NewPrivateKey will create a new private key +// With curve as Secp256r1 func NewPrivateKey() (*PrivateKey, error) { curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) b := make([]byte, curve.N.BitLen()/8+8) @@ -38,6 +40,7 @@ func NewPrivateKey() (*PrivateKey, error) { return p, nil } +// NewPrivateKeyFromHex will create a new private key hex string func NewPrivateKeyFromHex(str string) (*PrivateKey, error) { b, err := hex.DecodeString(str) if err != nil { @@ -56,6 +59,8 @@ func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) { return &PrivateKey{b}, nil } +// PublicKey returns a the public corresponding to the private key +// For the curve secp256r1 func (p *PrivateKey) PublicKey() (*publickey.PublicKey, error) { var ( c = elliptic.NewEllipticCurve(elliptic.Secp256r1) @@ -78,6 +83,8 @@ func (p *PrivateKey) PublicKey() (*publickey.PublicKey, error) { } +// WIFEncode will converts a private key +// to the Wallet Import Format for NEO func WIFEncode(key []byte) (s string) { if len(key) != 32 { return "invalid private key length" @@ -97,6 +104,7 @@ func WIFEncode(key []byte) (s string) { return WIF } +// Sign will sign the corresponding data using the private key func (p *PrivateKey) Sign(data []byte) ([]byte, error) { curve := elliptic.NewEllipticCurve(elliptic.Secp256r1) key := p.b diff --git a/pkg/crypto/publickey/TestHelper/helper.go b/pkg/crypto/publickey/TestHelper/helper.go index 23089ac0e..248e27be8 100755 --- a/pkg/crypto/publickey/TestHelper/helper.go +++ b/pkg/crypto/publickey/TestHelper/helper.go @@ -5,16 +5,29 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/privatekey" ) +// SignDataWithRandomPrivateKey will sign data with +// a random private key, then verify said data +// returning true if Verify returns true func SignDataWithRandomPrivateKey(data []byte) (bool, error) { - hashedData, _ := hash.Sha256(data) - - privKey, _ := privatekey.NewPrivateKey() - signedData, err := privKey.Sign(data) - pubKey, _ := privKey.PublicKey() - result := pubKey.Verify(signedData, hashedData.Bytes()) + hashedData, err := hash.Sha256(data) if err != nil { return false, err } + + privKey, err := privatekey.NewPrivateKey() + if err != nil { + return false, err + } + signedData, err := privKey.Sign(data) + if err != nil { + return false, err + } + pubKey, err := privKey.PublicKey() + if err != nil { + return false, err + } + result := pubKey.Verify(signedData, hashedData.Bytes()) + return result, nil } diff --git a/pkg/crypto/publickey/publickey.go b/pkg/crypto/publickey/publickey.go index ac1b15b03..285914a44 100755 --- a/pkg/crypto/publickey/publickey.go +++ b/pkg/crypto/publickey/publickey.go @@ -77,6 +77,8 @@ func (p *PublicKey) Bytes() []byte { return append([]byte{prefix}, paddedX...) } + +// ToAddress will convert a public key to it's neo-address func (p *PublicKey) ToAddress() string { publicKeyBytes := p.Bytes() @@ -145,6 +147,8 @@ func (p *PublicKey) EncodeBinary(w io.Writer) error { return binary.Write(w, binary.LittleEndian, p.Bytes()) } +// Verify returns true if the signature is valid and corresponds +// to the hash and public key func (p *PublicKey) Verify(signature []byte, hash []byte) bool { publicKey := &ecdsa.PublicKey{} diff --git a/pkg/database/leveldb.go b/pkg/database/leveldb.go index c079fa6a0..bb6c3ca0a 100644 --- a/pkg/database/leveldb.go +++ b/pkg/database/leveldb.go @@ -12,27 +12,38 @@ import ( "github.com/syndtr/goleveldb/leveldb/errors" ) +// LDB represents a leveldb object type LDB struct { db *leveldb.DB path string } +// Database contains all methods needed for an object to be a database type Database interface { + // Has checks whether the key is in the database Has(key []byte) (bool, error) + // Put adds the key value pair into the pair Put(key []byte, value []byte) error + // Get returns the value for the given key Get(key []byte) ([]byte, error) + // Delete deletes the given value for the key from the database Delete(key []byte) error + // Close closes the underlying db object Close() error } var ( - // TX, HEADER AND UTXO are the prefixes for the db - TX = []byte("TX") - HEADER = []byte("HEADER") + // TX is the prefix used when inserting a tx into the db + TX = []byte("TX") + // HEADER is the prefix used when inserting a header into the db + HEADER = []byte("HEADER") + // LATESTHEADER is the prefix used when inserting the latests header into the db LATESTHEADER = []byte("LH") - UTXO = []byte("UTXO") + // UTXO is the prefix used when inserting a utxo into the db + UTXO = []byte("UTXO") ) +// New will return a new leveldb instance func New(path string) *LDB { db, err := leveldb.OpenFile(path, nil) @@ -50,23 +61,32 @@ func New(path string) *LDB { } } +// Has implements the database interface func (l *LDB) Has(key []byte) (bool, error) { return l.db.Has(key, nil) } +// Put implements the database interface func (l *LDB) Put(key []byte, value []byte) error { return l.db.Put(key, value, nil) } + +// Get implements the database interface func (l *LDB) Get(key []byte) ([]byte, error) { return l.db.Get(key, nil) } + +// Delete implements the database interface func (l *LDB) Delete(key []byte) error { return l.db.Delete(key, nil) } + +// Close implements the database interface func (l *LDB) Close() error { return l.db.Close() } +// AddHeader adds a header into the database func (l *LDB) AddHeader(header *payload.BlockBase) error { table := NewTable(l, HEADER) @@ -102,6 +122,7 @@ func (l *LDB) AddHeader(header *payload.BlockBase) error { return table.Put(LATESTHEADER, header.Hash.Bytes()) } +// AddTransactions adds a set of transactions into the database func (l *LDB) AddTransactions(blockhash util.Uint256, txs []transaction.Transactioner) error { // SHOULD BE DONE IN BATCH!!!! diff --git a/pkg/database/table.go b/pkg/database/table.go index 549e50229..c36b80185 100644 --- a/pkg/database/table.go +++ b/pkg/database/table.go @@ -1,12 +1,12 @@ package database -//Table is an abstract datastructure built on -// top of a db +//Table is an abstract data structure built on top of a db type Table struct { prefix []byte db Database } +//NewTable creates a new table on the given database func NewTable(db Database, prefix []byte) *Table { return &Table{ prefix, @@ -14,23 +14,31 @@ func NewTable(db Database, prefix []byte) *Table { } } +// Has implements the database interface func (t *Table) Has(key []byte) (bool, error) { key = append(t.prefix, key...) return t.db.Has(key) } +// Put implements the database interface func (t *Table) Put(key []byte, value []byte) error { key = append(t.prefix, key...) return t.db.Put(key, value) } + +// Get implements the database interface func (t *Table) Get(key []byte) ([]byte, error) { key = append(t.prefix, key...) return t.db.Get(key) } + +// Delete implements the database interface func (t *Table) Delete(key []byte) error { key = append(t.prefix, key...) return t.db.Delete(key) } + +// Close implements the database interface func (t *Table) Close() error { return nil } diff --git a/pkg/peer/config.go b/pkg/peer/config.go index 0614e5952..e4794c282 100644 --- a/pkg/peer/config.go +++ b/pkg/peer/config.go @@ -6,7 +6,6 @@ import ( ) // LocalConfig specifies the properties that should be available for each remote peer - type LocalConfig struct { Net protocol.Magic UserAgent string diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go index 11668f478..6a87a4aa6 100644 --- a/pkg/peer/peer.go +++ b/pkg/peer/peer.go @@ -53,6 +53,7 @@ var ( errHandShakeTimeout = errors.New("Handshake timed out, peers have " + string(handshakeTimeout) + " Seconds to Complete the handshake") ) +// Peer represents a peer on the neo network type Peer struct { config LocalConfig conn net.Conn @@ -81,6 +82,7 @@ type Peer struct { quitch chan struct{} } +// NewPeer returns a new NEO peer func NewPeer(con net.Conn, inbound bool, cfg LocalConfig) *Peer { p := Peer{} p.inch = make(chan func(), inputBufferSize) @@ -108,7 +110,7 @@ func (p *Peer) Read() (wire.Messager, error) { return wire.ReadMessage(p.conn, p.config.Net) } -// Disconnects from a peer +// Disconnect disconnects a peer and closes the connection func (p *Peer) Disconnect() { // return if already disconnected @@ -126,34 +128,54 @@ func (p *Peer) Disconnect() { } -// Exposed API functions below +// Port returns the peers port func (p *Peer) Port() uint16 { return p.port } + +// CreatedAt returns the time at which the connection was made func (p *Peer) CreatedAt() time.Time { return p.createdAt } + +// CanRelay returns true, if the peer can relay information func (p *Peer) CanRelay() bool { return p.relay } + +// LocalAddr returns this node's local address func (p *Peer) LocalAddr() net.Addr { return p.conn.LocalAddr() } + +// RemoteAddr returns the remote address of the connected peer func (p *Peer) RemoteAddr() net.Addr { return p.conn.RemoteAddr() } + +// Services returns the services offered by the peer func (p *Peer) Services() protocol.ServiceFlag { return p.config.Services } + +//Inbound returns true whether this peer is an inbound peer func (p *Peer) Inbound() bool { return p.inbound } + +// UserAgent returns this nodes, useragent func (p *Peer) UserAgent() string { return p.config.UserAgent } + +// IsVerackReceived returns true, if this node has +// received a verack from this peer func (p *Peer) IsVerackReceived() bool { return p.verackReceived } + +//NotifyDisconnect returns once the peer has disconnected +// Blocking func (p *Peer) NotifyDisconnect() bool { fmt.Println("Peer has not disconnected yet") <-p.quitch @@ -163,7 +185,7 @@ func (p *Peer) NotifyDisconnect() bool { //End of Exposed API functions// -// Ping not impl. in neo yet, adding it now +// PingLoop not impl. in neo yet, adding it now // will cause this client to disconnect from all other implementations func (p *Peer) PingLoop() { /*not implemented in other neo clients*/ } @@ -183,7 +205,7 @@ func (p *Peer) Run() error { } -// run as a go-routine, will act as our queue for messages +// StartProtocol run as a go-routine, will act as our queue for messages // should be ran after handshake func (p *Peer) StartProtocol() { loop: @@ -201,11 +223,9 @@ loop: p.Disconnect() } +// ReadLoop Will block on the read until a message is read // Should only be called after handshake is complete // on a seperate go-routine. -// ReadLoop Will block on the read until a message is -// read - func (p *Peer) ReadLoop() { idleTimer := time.AfterFunc(idleTimeout, func() { @@ -271,8 +291,7 @@ loop: p.Disconnect() } -// WriteLoop will Queue all messages to be written to -// the peer. +// WriteLoop will Queue all messages to be written to the peer. func (p *Peer) WriteLoop() { for atomic.LoadInt32(&p.disconnected) == 0 { select { @@ -284,17 +303,21 @@ func (p *Peer) WriteLoop() { } } +// OnGetData is called when a GetData message is received func (p *Peer) OnGetData(msg *payload.GetDataMessage) { p.inch <- func() { - // fmt.Println(msg.Hashes) + if p.config.OnInv != nil { + p.config.OnGetData(msg) + } fmt.Println("That was an getdata Message please pass func down through config", msg.Command()) } } + +//OnTX is callwed when a TX message is received func (p *Peer) OnTX(msg *payload.TXMessage) { p.inch <- func() { - // fmt.Println(msg.Hashes) getdata, err := payload.NewGetDataMessage(payload.InvTypeTx) if err != nil { fmt.Println("Eor", err) @@ -302,10 +325,10 @@ func (p *Peer) OnTX(msg *payload.TXMessage) { id, err := msg.Tx.ID() getdata.AddHash(id) p.Write(getdata) - fmt.Println("That was an tx Message please pass func down through config", msg.Command()) } } +// OnInv is called when a Inv message is received func (p *Peer) OnInv(msg *payload.InvMessage) { p.inch <- func() { @@ -316,8 +339,7 @@ func (p *Peer) OnInv(msg *payload.InvMessage) { } } -// OnGetHeaders Listener, outside of the anonymous func will be extra functionality -// like timing +// OnGetHeaders is called when a GetHeaders message is received func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) { p.inch <- func() { if p.config.OnGetHeaders != nil { @@ -328,7 +350,7 @@ func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) { } } -// OnAddr Listener +// OnAddr is called when a Addr message is received func (p *Peer) OnAddr(msg *payload.AddrMessage) { p.inch <- func() { if p.config.OnAddr != nil { @@ -339,7 +361,7 @@ func (p *Peer) OnAddr(msg *payload.AddrMessage) { } } -// OnGetAddr Listener +// OnGetAddr is called when a GetAddr message is received func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) { p.inch <- func() { if p.config.OnGetAddr != nil { @@ -350,7 +372,7 @@ func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) { } } -// OnGetBlocks Listener +// OnGetBlocks is called when a GetBlocks message is received func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) { p.inch <- func() { if p.config.OnGetBlocks != nil { @@ -360,7 +382,7 @@ func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) { } } -// OnBlocks Listener +// OnBlocks is called when a Blocks message is received func (p *Peer) OnBlocks(msg *payload.BlockMessage) { p.inch <- func() { if p.config.OnBlock != nil { @@ -386,7 +408,7 @@ func (p *Peer) OnVersion(msg *payload.VersionMessage) error { return nil } -// OnHeaders Listener +// OnHeaders is called when a Headers message is received func (p *Peer) OnHeaders(msg *payload.HeadersMessage) { fmt.Println("We have received the headers") p.inch <- func() { @@ -396,9 +418,8 @@ func (p *Peer) OnHeaders(msg *payload.HeadersMessage) { } } -// RequestHeaders will write a getheaders to peer +// RequestHeaders will write a getheaders to this peer func (p *Peer) RequestHeaders(hash util.Uint256) error { - fmt.Println("Sending header request") c := make(chan error, 0) p.outch <- func() { p.Detector.AddMessage(command.GetHeaders) @@ -406,14 +427,11 @@ func (p *Peer) RequestHeaders(hash util.Uint256) error { err = p.Write(getHeaders) c <- err } - return <-c - } -// RequestBlocks will ask a peer for a block +// RequestBlocks will ask this peer for a set of blocks func (p *Peer) RequestBlocks(hashes []util.Uint256) error { - fmt.Println("Requesting block from peer") c := make(chan error, 0) p.outch <- func() { diff --git a/pkg/peer/peerhandshake.go b/pkg/peer/peerhandshake.go index 2cad19c5d..90e4ecc81 100644 --- a/pkg/peer/peerhandshake.go +++ b/pkg/peer/peerhandshake.go @@ -7,9 +7,10 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire" "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/util/ip" + iputils "github.com/CityOfZion/neo-go/pkg/wire/util/ip" ) +// Handshake will initiate a handshake with this peer func (p *Peer) Handshake() error { handshakeErr := make(chan error, 1) diff --git a/pkg/peer/stall/stall.go b/pkg/peer/stall/stall.go index 052a22eda..fc19891fa 100644 --- a/pkg/peer/stall/stall.go +++ b/pkg/peer/stall/stall.go @@ -9,10 +9,9 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) -// stall detector will keep track of all pendingMessages +// Detector (stall detector) will keep track of all pendingMessages // If any message takes too long to reply // the detector will disconnect the peer - type Detector struct { responseTime time.Duration tickInterval time.Duration @@ -28,6 +27,7 @@ type Detector struct { disconnected int32 } +// NewDetector will create a new stall detector // rT is the responseTime and signals how long // a peer has to reply back to a sent message // tickerInterval is how often the detector wil check for stalled messages @@ -81,9 +81,10 @@ func (d *Detector) Quit() { close(d.Quitch) } +//AddMessage will add a message to the responses map // Call this function when we send a message to a peer // The command passed through is the command that we sent -// and not the command we expect to receive +// we will then set a timer for the expected message(s) func (d *Detector) AddMessage(cmd command.Type) { cmds := d.addMessage(cmd) d.lock.Lock() @@ -93,6 +94,7 @@ func (d *Detector) AddMessage(cmd command.Type) { d.lock.Unlock() } +// RemoveMessage remove messages from the responses map // Call this function when we receive a message from // peer. This will remove the pendingresponse message from the map. // The command passed through is the command we received diff --git a/pkg/pubsub/event.go b/pkg/pubsub/event.go index c24893a40..1a57e86cd 100644 --- a/pkg/pubsub/event.go +++ b/pkg/pubsub/event.go @@ -1,13 +1,19 @@ package pubsub +// EventType is an enum +// representing the types of messages we can subscribe to type EventType int const ( - NewBlock EventType = iota // When blockchain connects a new block, it will emit an NewBlock Event - BadBlock // When blockchain declines a block, it will emit a new block event - BadHeader // When blockchain rejects a Header, it will emit this event + // NewBlock is called When blockchain connects a new block, it will emit an NewBlock Event + NewBlock EventType = iota + // BadBlock is called When blockchain declines a block, it will emit a new block event + BadBlock + // BadHeader is called When blockchain rejects a Header, it will emit this event + BadHeader ) +// Event represents a new Event that a subscriber can listen to type Event struct { Type EventType // E.g. event.NewBlock data []byte // Raw information diff --git a/pkg/pubsub/pub.go b/pkg/pubsub/pub.go index a0eacb182..a9f6084a1 100644 --- a/pkg/pubsub/pub.go +++ b/pkg/pubsub/pub.go @@ -1,5 +1,6 @@ package pubsub +// Publisher sends events to subscribers type Publisher struct { subs []Subscriber } diff --git a/pkg/pubsub/sub.go b/pkg/pubsub/sub.go index 21a51eb30..4540870ff 100644 --- a/pkg/pubsub/sub.go +++ b/pkg/pubsub/sub.go @@ -1,5 +1,6 @@ package pubsub +// Subscriber will listen for Events from publishers type Subscriber interface { Topics() []EventType Emit(Event) diff --git a/pkg/wire/base.go b/pkg/wire/base.go index 1d778f45e..bcbc5c1e0 100644 --- a/pkg/wire/base.go +++ b/pkg/wire/base.go @@ -8,7 +8,6 @@ import ( ) // Base is everything in the message except the payload - type Base struct { Magic uint32 CMD command.Type @@ -16,9 +15,8 @@ type Base struct { Checksum uint32 } -// Note, That there is no EncodeBase -// As the header is implicitly inferred from -// the message on Encode To send +// DecodeBase will decode an io.Reader into a Base object +// Note, That there is no EncodeBase, As the header is implicitly inferred from the message on Encode To send func (h *Base) DecodeBase(r io.Reader) (io.Reader, error) { br := &util.BinReader{R: r} diff --git a/pkg/wire/command/command.go b/pkg/wire/command/command.go index 3d9b749fe..d89dcd335 100644 --- a/pkg/wire/command/command.go +++ b/pkg/wire/command/command.go @@ -5,11 +5,10 @@ const ( Size = 12 ) -// CommandType represents the type of a message command. +// Type represents the type of a message command. type Type string // Valid protocol commands used to send between nodes. -// use this to get const ( Version Type = "version" Mempool Type = "mempool" diff --git a/pkg/wire/message.go b/pkg/wire/message.go index d02c5ce92..da0742c63 100644 --- a/pkg/wire/message.go +++ b/pkg/wire/message.go @@ -15,9 +15,15 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// Messager is implemented by any object that can +// Encode and Decode Payloads type Messager interface { + // EncodePayload takes a message payload and encodes it EncodePayload(w io.Writer) error + // DecodePayload takes an io.Reader and decodes it into + // a message payload DecodePayload(r io.Reader) error + // Command returns the assosciated command type Command() command.Type } @@ -30,6 +36,7 @@ var ( errChecksumMismatch = errors.New("checksum mismatch") ) +// WriteMessage will write a message to a given io.Writer func WriteMessage(w io.Writer, magic protocol.Magic, message Messager) error { bw := &util.BinWriter{W: w} bw.Write(magic) @@ -51,6 +58,7 @@ func WriteMessage(w io.Writer, magic protocol.Magic, message Messager) error { return bw.Err } +// ReadMessage will read a message from a given io.Reader func ReadMessage(r io.Reader, magic protocol.Magic) (Messager, error) { byt := make([]byte, minMsgSize) @@ -122,7 +130,7 @@ func ReadMessage(r io.Reader, magic protocol.Magic) (Messager, error) { return v, err case command.TX: reader := bufio.NewReader(buf) - tx, err := transaction.FromBytes(reader) + tx, err := transaction.FromReader(reader) if err != nil { return nil, err } diff --git a/pkg/wire/payload/block.go b/pkg/wire/payload/block.go index 7dc4e000e..b7c186fc0 100644 --- a/pkg/wire/payload/block.go +++ b/pkg/wire/payload/block.go @@ -9,21 +9,27 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// Block representa a Block in the neo-network type Block struct { BlockBase Txs []transaction.Transactioner } +// Decode decodes an io.Reader into a Block func (b *Block) Decode(r io.Reader) error { br := &util.BinReader{R: r} b.DecodePayload(br) return br.Err } + +// Encode writes a block into a io.Writer func (b *Block) Encode(w io.Writer) error { bw := &util.BinWriter{W: w} b.EncodePayload(bw) return bw.Err } + +//EncodePayload implements Messager interface func (b *Block) EncodePayload(bw *util.BinWriter) { b.BlockBase.EncodePayload(bw) bw.VarUint(uint64(len(b.Txs))) @@ -32,6 +38,7 @@ func (b *Block) EncodePayload(bw *util.BinWriter) { } } +// DecodePayload implements Messager interface func (b *Block) DecodePayload(br *util.BinReader) error { b.BlockBase.DecodePayload(br) @@ -42,7 +49,7 @@ func (b *Block) DecodePayload(br *util.BinReader) error { reader := bufio.NewReader(br.R) for i := 0; i < int(lenTXs); i++ { - tx, err := transaction.FromBytes(reader) + tx, err := transaction.FromReader(reader) if err != nil { return err } @@ -52,6 +59,7 @@ func (b *Block) DecodePayload(br *util.BinReader) error { return nil } +// Bytes returns the Byte representation of Block func (b *Block) Bytes() ([]byte, error) { buf := new(bytes.Buffer) err := b.Encode(buf) diff --git a/pkg/wire/payload/blockbase.go b/pkg/wire/payload/blockbase.go index 2feec8ec0..7d8e55828 100644 --- a/pkg/wire/payload/blockbase.go +++ b/pkg/wire/payload/blockbase.go @@ -10,9 +10,11 @@ import ( ) var ( - ErrPadding = errors.New("There is a padding mismatch") + errPadding = errors.New("There is a padding mismatch") ) +//BlockBase represents the base of the block +// This is different than the block header. See HeadersMessage type BlockBase struct { // Version of the block. Version uint32 `json:"version"` @@ -47,6 +49,7 @@ type BlockBase struct { Hash util.Uint256 } +// EncodePayload implements the Messager interface func (b *BlockBase) EncodePayload(bw *util.BinWriter) error { b.encodeHashableFields(bw) @@ -57,11 +60,14 @@ func (b *BlockBase) EncodePayload(bw *util.BinWriter) error { return bw.Err } +// Decode decodes an io.Reader into a Blockbase func (b *BlockBase) Decode(r io.Reader) error { br := &util.BinReader{R: r} b.DecodePayload(br) return br.Err } + +// Encode encodes a blockbase into an io.Writer func (b *BlockBase) Encode(w io.Writer) error { bw := &util.BinWriter{W: w} b.EncodePayload(bw) @@ -78,6 +84,7 @@ func (b *BlockBase) encodeHashableFields(bw *util.BinWriter) { bw.Write(b.NextConsensus) } +// DecodePayload implements the messager interface func (b *BlockBase) DecodePayload(br *util.BinReader) error { b.decodeHashableFields(br) @@ -85,7 +92,7 @@ func (b *BlockBase) DecodePayload(br *util.BinReader) error { var padding uint8 br.Read(&padding) if padding != 1 { - return ErrPadding + return errPadding } b.Witness = transaction.Witness{} @@ -117,6 +124,7 @@ func (b *BlockBase) createHash() error { return err } +// Bytes returns the byte representation of Blockbase func (b *BlockBase) Bytes() ([]byte, error) { buf := new(bytes.Buffer) err := b.Encode(buf) diff --git a/pkg/wire/payload/maddr.go b/pkg/wire/payload/maddr.go index 674013dfa..0fd3ca189 100644 --- a/pkg/wire/payload/maddr.go +++ b/pkg/wire/payload/maddr.go @@ -7,10 +7,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// AddrMessage represents an address message on the neo network type AddrMessage struct { - AddrList []*Net_addr + AddrList []*NetAddr } +// NewAddrMessage instantiates a new AddrMessage func NewAddrMessage() (*AddrMessage, error) { addrMess := &AddrMessage{ nil, @@ -18,22 +20,23 @@ func NewAddrMessage() (*AddrMessage, error) { return addrMess, nil } -func (a *AddrMessage) AddNetAddr(n *Net_addr) error { +// AddNetAddr will add a net address into the Address message +func (a *AddrMessage) AddNetAddr(n *NetAddr) error { a.AddrList = append(a.AddrList, n) // TODO:check if max reached, if so return err. What is max? return nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (a *AddrMessage) DecodePayload(r io.Reader) error { br := &util.BinReader{R: r} listLen := br.VarUint() - a.AddrList = make([]*Net_addr, listLen) + a.AddrList = make([]*NetAddr, listLen) for i := 0; i < int(listLen); i++ { - a.AddrList[i] = &Net_addr{} + a.AddrList[i] = &NetAddr{} a.AddrList[i].DecodePayload(br) if br.Err != nil { return br.Err @@ -42,20 +45,20 @@ func (a *AddrMessage) DecodePayload(r io.Reader) error { return br.Err } -// Implements messager interface -func (v *AddrMessage) EncodePayload(w io.Writer) error { +// EncodePayload Implements messager interface +func (a *AddrMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} - listLen := uint64(len(v.AddrList)) + listLen := uint64(len(a.AddrList)) bw.VarUint(listLen) - for _, addr := range v.AddrList { + for _, addr := range a.AddrList { addr.EncodePayload(bw) } return bw.Err } -// Implements messager interface -func (v *AddrMessage) Command() command.Type { +// Command Implements messager interface +func (a *AddrMessage) Command() command.Type { return command.Addr } diff --git a/pkg/wire/payload/mblock.go b/pkg/wire/payload/mblock.go index 399d28965..db82ad9c4 100644 --- a/pkg/wire/payload/mblock.go +++ b/pkg/wire/payload/mblock.go @@ -8,29 +8,31 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) +// BlockMessage represents a block message on the neo-network type BlockMessage struct { Block } +// NewBlockMessage will return a block message object func NewBlockMessage() (*BlockMessage, error) { return &BlockMessage{}, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (b *BlockMessage) DecodePayload(r io.Reader) error { br := &util.BinReader{R: r} b.Block.DecodePayload(br) return br.Err } -// Implements messager interface +// EncodePayload Implements messager interface func (b *BlockMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} b.Block.EncodePayload(bw) return bw.Err } -// Implements messager interface -func (v *BlockMessage) Command() command.Type { +// Command Implements messager interface +func (b *BlockMessage) Command() command.Type { return command.Block } diff --git a/pkg/wire/payload/mgetaddr.go b/pkg/wire/payload/mgetaddr.go index 02473ba12..58010adc5 100644 --- a/pkg/wire/payload/mgetaddr.go +++ b/pkg/wire/payload/mgetaddr.go @@ -6,24 +6,25 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) -// No payload +//GetAddrMessage represents a GetAddress message on the neo-network type GetAddrMessage struct{} +// NewGetAddrMessage returns a GetAddrMessage object func NewGetAddrMessage() (*GetAddrMessage, error) { return &GetAddrMessage{}, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (v *GetAddrMessage) DecodePayload(r io.Reader) error { return nil } -// Implements messager interface +// EncodePayload Implements messager interface func (v *GetAddrMessage) EncodePayload(w io.Writer) error { return nil } -// Implements messager interface +// Command Implements messager interface func (v *GetAddrMessage) Command() command.Type { return command.GetAddr } diff --git a/pkg/wire/payload/mgetblocks.go b/pkg/wire/payload/mgetblocks.go index 660e17d96..c6daf8ee2 100644 --- a/pkg/wire/payload/mgetblocks.go +++ b/pkg/wire/payload/mgetblocks.go @@ -5,10 +5,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// GetBlocksMessage represnts a GetBlocks message on the neo-network type GetBlocksMessage struct { *GetHeadersMessage } +// NewGetBlocksMessage returns a GetBlocksMessage object func NewGetBlocksMessage(start []util.Uint256, stop util.Uint256) (*GetBlocksMessage, error) { GetHeaders, err := newAbstractGetHeaders(start, stop, command.GetBlocks) diff --git a/pkg/wire/payload/mgetdata.go b/pkg/wire/payload/mgetdata.go index d13fbab30..23f2523ff 100644 --- a/pkg/wire/payload/mgetdata.go +++ b/pkg/wire/payload/mgetdata.go @@ -4,10 +4,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) +// GetDataMessage represents a GetData message on the neo-network type GetDataMessage struct { *InvMessage } +//NewGetDataMessage returns a GetDataMessage object func NewGetDataMessage(typ InvType) (*GetDataMessage, error) { getData, err := newAbstractInv(typ, command.GetData) return &GetDataMessage{ diff --git a/pkg/wire/payload/mgetheaders.go b/pkg/wire/payload/mgetheaders.go index 09d91c5e0..87c30c607 100644 --- a/pkg/wire/payload/mgetheaders.go +++ b/pkg/wire/payload/mgetheaders.go @@ -7,12 +7,14 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//GetHeadersMessage represents a GetHeaders message on the neo-network type GetHeadersMessage struct { cmd command.Type hashStart []util.Uint256 hashStop util.Uint256 } +// NewGetHeadersMessage returns a NewGetHeaders object // Start contains the list of all headers you want to fetch // End contains the list of the highest header hash you would like to fetch func NewGetHeadersMessage(start []util.Uint256, stop util.Uint256) (*GetHeadersMessage, error) { @@ -32,7 +34,7 @@ func newAbstractGetHeaders(start []util.Uint256, stop util.Uint256, cmd command. return getHeaders, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (v *GetHeadersMessage) DecodePayload(r io.Reader) error { br := util.BinReader{R: r} @@ -44,7 +46,7 @@ func (v *GetHeadersMessage) DecodePayload(r io.Reader) error { return br.Err } -// Implements messager interface +// EncodePayload Implements messager interface func (v *GetHeadersMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} bw.VarUint(uint64(len(v.hashStart))) @@ -53,7 +55,7 @@ func (v *GetHeadersMessage) EncodePayload(w io.Writer) error { return bw.Err } -// Implements messager interface +// Command Implements messager interface func (v *GetHeadersMessage) Command() command.Type { return v.cmd } diff --git a/pkg/wire/payload/mheaders.go b/pkg/wire/payload/mheaders.go index b7d008d3d..fefc56b17 100644 --- a/pkg/wire/payload/mheaders.go +++ b/pkg/wire/payload/mheaders.go @@ -8,6 +8,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// HeadersMessage represents a Header(s) Message on the neo-network type HeadersMessage struct { Headers []*BlockBase @@ -21,31 +22,34 @@ const ( ) var ( - ErrMaxHeaders = errors.New("Maximum amount of headers allowed is 2000") + errMaxHeaders = errors.New("Maximum amount of headers allowed is 2000") ) +//NewHeadersMessage returns a HeadersMessage Object func NewHeadersMessage() (*HeadersMessage, error) { headers := &HeadersMessage{nil, 0} return headers, nil } +// AddHeader adds a header into the list of Headers. +// Since a header is just blockbase with padding, we use BlockBase func (h *HeadersMessage) AddHeader(head *BlockBase) error { if len(h.Headers)+1 > maxHeadersAllowed { - return ErrMaxHeaders + return errMaxHeaders } h.Headers = append(h.Headers, head) return nil } -// Implements Messager interface -func (v *HeadersMessage) DecodePayload(r io.Reader) error { +// DecodePayload Implements Messager interface +func (h *HeadersMessage) DecodePayload(r io.Reader) error { br := &util.BinReader{R: r} lenHeaders := br.VarUint() - v.Headers = make([]*BlockBase, lenHeaders) + h.Headers = make([]*BlockBase, lenHeaders) for i := 0; i < int(lenHeaders); i++ { header := &BlockBase{} @@ -53,26 +57,26 @@ func (v *HeadersMessage) DecodePayload(r io.Reader) error { var padding uint8 br.Read(&padding) if padding != 0 { - return ErrPadding + return errPadding } - v.Headers[i] = header + h.Headers[i] = header } return br.Err } -// Implements messager interface -func (v *HeadersMessage) EncodePayload(w io.Writer) error { +// EncodePayload Implements messager interface +func (h *HeadersMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} - bw.VarUint(uint64(len(v.Headers))) - for _, header := range v.Headers { + bw.VarUint(uint64(len(h.Headers))) + for _, header := range h.Headers { header.EncodePayload(bw) bw.Write(uint8(0)) } return bw.Err } -// Implements messager interface -func (v *HeadersMessage) Command() command.Type { +// Command Implements messager interface +func (h *HeadersMessage) Command() command.Type { return command.Headers } diff --git a/pkg/wire/payload/minventory.go b/pkg/wire/payload/minventory.go index d2f4207dd..cd994edb3 100644 --- a/pkg/wire/payload/minventory.go +++ b/pkg/wire/payload/minventory.go @@ -8,29 +8,30 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//InvType represents the enum of inventory types type InvType uint8 -//Inventory types const ( - InvTypeTx InvType = 0x01 - InvTypeBlock InvType = 0x02 + // InvTypeTx represents the transaction inventory type + InvTypeTx InvType = 0x01 + // InvTypeBlock represents the block inventory type + InvTypeBlock InvType = 0x02 + // InvTypeConsensus represents the consensus inventory type InvTypeConsensus InvType = 0xe0 ) -const ( - maxHashes = 0x10000000 -) +const maxHashes = 0x10000000 -var ( - MaxHashError = errors.New("Max size For Hashes reached") -) +var errMaxHash = errors.New("max size For Hashes reached") +// InvMessage represents an Inventory message on the neo-network type InvMessage struct { cmd command.Type Type InvType Hashes []util.Uint256 } +//NewInvMessage returns an InvMessage object func NewInvMessage(typ InvType) (*InvMessage, error) { inv := &InvMessage{ @@ -53,17 +54,20 @@ func newAbstractInv(typ InvType, cmd command.Type) (*InvMessage, error) { } -func (i *InvMessage) AddHash(h util.Uint256) error { - if len(i.Hashes)+1 > maxHashes { - return MaxHashError +// AddHash adds a hash to the list of hashes +func (inv *InvMessage) AddHash(h util.Uint256) error { + if len(inv.Hashes)+1 > maxHashes { + return errMaxHash } - i.Hashes = append(i.Hashes, h) + inv.Hashes = append(inv.Hashes, h) return nil } -func (i *InvMessage) AddHashes(hashes []util.Uint256) error { + +// AddHashes adds multiple hashes to the list of hashes +func (inv *InvMessage) AddHashes(hashes []util.Uint256) error { var err error for _, hash := range hashes { - err = i.AddHash(hash) + err = inv.AddHash(hash) if err != nil { break } @@ -71,31 +75,31 @@ func (i *InvMessage) AddHashes(hashes []util.Uint256) error { return err } -// Implements Messager interface -func (v *InvMessage) DecodePayload(r io.Reader) error { +// DecodePayload Implements Messager interface +func (inv *InvMessage) DecodePayload(r io.Reader) error { br := &util.BinReader{R: r} - br.Read(&v.Type) + br.Read(&inv.Type) listLen := br.VarUint() - v.Hashes = make([]util.Uint256, listLen) + inv.Hashes = make([]util.Uint256, listLen) for i := 0; i < int(listLen); i++ { - br.Read(&v.Hashes[i]) + br.Read(&inv.Hashes[i]) } return nil } -// Implements messager interface -func (v *InvMessage) EncodePayload(w io.Writer) error { +// EncodePayload Implements messager interface +func (inv *InvMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} - bw.Write(v.Type) + bw.Write(inv.Type) - lenhashes := len(v.Hashes) + lenhashes := len(inv.Hashes) bw.VarUint(uint64(lenhashes)) - for _, hash := range v.Hashes { + for _, hash := range inv.Hashes { bw.Write(hash) @@ -104,7 +108,7 @@ func (v *InvMessage) EncodePayload(w io.Writer) error { return bw.Err } -// Implements messager interface -func (v *InvMessage) Command() command.Type { - return v.cmd +// Command Implements messager interface +func (inv *InvMessage) Command() command.Type { + return inv.cmd } diff --git a/pkg/wire/payload/mmempool.go b/pkg/wire/payload/mmempool.go index 5408c826b..b05c2c74b 100644 --- a/pkg/wire/payload/mmempool.go +++ b/pkg/wire/payload/mmempool.go @@ -6,24 +6,25 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) -// No payload +// GetMempool represents a GetMempool message on the neo-network type GetMempool struct{} +//NewGetMempool returns a GetMempool message func NewGetMempool() (*GetMempool, error) { return &GetMempool{}, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (v *GetMempool) DecodePayload(r io.Reader) error { return nil } -// Implements messager interface +// EncodePayload Implements messager interface func (v *GetMempool) EncodePayload(w io.Writer) error { return nil } -// Implements messager interface +// Command Implements messager interface func (v *GetMempool) Command() command.Type { return command.Mempool } diff --git a/pkg/wire/payload/mtx.go b/pkg/wire/payload/mtx.go index 8b6c08b28..9c80af355 100644 --- a/pkg/wire/payload/mtx.go +++ b/pkg/wire/payload/mtx.go @@ -7,28 +7,29 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" ) +// TXMessage represents a transaction message on the neo-network type TXMessage struct { - // w *bytes.Buffer Tx transaction.Transactioner } +//NewTXMessage returns a new tx object func NewTXMessage(tx transaction.Transactioner) (*TXMessage, error) { Tx := &TXMessage{tx} return Tx, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (t *TXMessage) DecodePayload(r io.Reader) error { return t.Tx.Decode(r) } -// Implements messager interface +// EncodePayload Implements messager interface func (t *TXMessage) EncodePayload(w io.Writer) error { return t.Tx.Encode(w) } -// Implements messager interface -func (v *TXMessage) Command() command.Type { +// Command Implements messager interface +func (t *TXMessage) Command() command.Type { return command.TX } diff --git a/pkg/wire/payload/mverack.go b/pkg/wire/payload/mverack.go index 249579415..b67e995a9 100644 --- a/pkg/wire/payload/mverack.go +++ b/pkg/wire/payload/mverack.go @@ -6,24 +6,25 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/command" ) -// No payload +//VerackMessage represents a verack message on the neo-network type VerackMessage struct{} +//NewVerackMessage returns a verack message func NewVerackMessage() (*VerackMessage, error) { return &VerackMessage{}, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (v *VerackMessage) DecodePayload(r io.Reader) error { return nil } -// Implements messager interface +// EncodePayload Implements messager interface func (v *VerackMessage) EncodePayload(w io.Writer) error { return nil } -// Implements messager interface +// Command Implements messager interface func (v *VerackMessage) Command() command.Type { return command.Verack } diff --git a/pkg/wire/payload/mversion.go b/pkg/wire/payload/mversion.go index 1a7cff06e..ff4190de0 100644 --- a/pkg/wire/payload/mversion.go +++ b/pkg/wire/payload/mversion.go @@ -13,15 +13,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) -const ( - minMsgVersionSize = 28 -) +const minMsgVersionSize = 28 -// TODO: Refactor to pull out the useragent out of initialiser -// and have a seperate method to add it +var errInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr") +//VersionMessage represents a version message on the neo-network type VersionMessage struct { - // w *bytes.Buffer Version protocol.Version Timestamp uint32 Services protocol.ServiceFlag @@ -33,13 +30,12 @@ type VersionMessage struct { Relay bool } -var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr") - +//NewVersionMessage will return a VersionMessage object func NewVersionMessage(addr net.Addr, startHeight uint32, relay bool, pver protocol.Version, userAgent string, nonce uint32, services protocol.ServiceFlag) (*VersionMessage, error) { tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - return nil, ErrInvalidNetAddr + return nil, errInvalidNetAddr } version := &VersionMessage{ @@ -56,13 +52,13 @@ func NewVersionMessage(addr net.Addr, startHeight uint32, relay bool, pver proto return version, nil } -// Implements Messager interface +// DecodePayload Implements Messager interface func (v *VersionMessage) DecodePayload(r io.Reader) error { br := &util.BinReader{R: r} br.Read(&v.Version) br.Read(&v.Services) br.Read(&v.Timestamp) - br.Read(&v.Port) // Port is not BigEndian + br.Read(&v.Port) // Port is not BigEndian as stated in the docs br.Read(&v.Nonce) var lenUA uint8 @@ -75,7 +71,7 @@ func (v *VersionMessage) DecodePayload(r io.Reader) error { return br.Err } -// Implements messager interface +// EncodePayload Implements messager interface func (v *VersionMessage) EncodePayload(w io.Writer) error { bw := &util.BinWriter{W: w} @@ -91,7 +87,7 @@ func (v *VersionMessage) EncodePayload(w io.Writer) error { return bw.Err } -// Implements messager interface +// Command Implements messager interface func (v *VersionMessage) Command() command.Type { return command.Version } diff --git a/pkg/wire/payload/net_addr.go b/pkg/wire/payload/net_addr.go index 5e1c528d0..ea31c2d59 100644 --- a/pkg/wire/payload/net_addr.go +++ b/pkg/wire/payload/net_addr.go @@ -9,21 +9,21 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) -// Once a VersionMessage is received, we can then store it inside of AddrMessage struct -// TODO: store this inside version message and have a bool to indicate whether to encode ip -// VersionMessage does not encodeIP -type Net_addr struct { +//NetAddr is an abstraction for the IP layer +type NetAddr struct { Timestamp uint32 IP [16]byte Port uint16 Service protocol.ServiceFlag } -func NewNetAddr(time uint32, ip [16]byte, port uint16, service protocol.ServiceFlag) (*Net_addr, error) { - return &Net_addr{time, ip, port, service}, nil +//NewNetAddr returns a NetAddr object +func NewNetAddr(time uint32, ip [16]byte, port uint16, service protocol.ServiceFlag) (*NetAddr, error) { + return &NetAddr{time, ip, port, service}, nil } -func NewAddrFromVersionMessage(version VersionMessage) (*Net_addr, error) { +//NewAddrFromVersionMessage returns a NetAddr object from a version message +func NewAddrFromVersionMessage(version VersionMessage) (*NetAddr, error) { var ip [16]byte @@ -32,21 +32,26 @@ func NewAddrFromVersionMessage(version VersionMessage) (*Net_addr, error) { return NewNetAddr(version.Timestamp, ip, version.Port, version.Services) } -func (n *Net_addr) EncodePayload(bw *util.BinWriter) { +// EncodePayload Implements messager interface +func (n *NetAddr) EncodePayload(bw *util.BinWriter) { bw.Write(uint32(time.Now().Unix())) bw.Write(protocol.NodePeerService) bw.WriteBigEnd(n.IP) bw.WriteBigEnd(n.Port) } -func (n *Net_addr) DecodePayload(br *util.BinReader) { + +// DecodePayload Implements Messager interface +func (n *NetAddr) DecodePayload(br *util.BinReader) { br.Read(&n.Timestamp) br.Read(&n.Service) br.ReadBigEnd(&n.IP) br.ReadBigEnd(&n.Port) } -func (n *Net_addr) IPPort() string { + +//IPPort returns the IPPort from the NetAddr +func (n *NetAddr) IPPort() string { ip := net.IP(n.IP[:]).String() port := strconv.Itoa(int(n.Port)) ipport := ip + ":" + port diff --git a/pkg/wire/payload/transaction/Attribute.go b/pkg/wire/payload/transaction/Attribute.go index 64e98df34..7b747c1b0 100644 --- a/pkg/wire/payload/transaction/Attribute.go +++ b/pkg/wire/payload/transaction/Attribute.go @@ -12,17 +12,14 @@ type Attribute struct { Data []byte } -var ( - ErrMaxData = errors.New("Max Size of Attribute reached") -) +var errMaxData = errors.New("max Size of Attribute reached") -const ( - maxAttrSize = 65535 -) +const maxAttrSize = 65535 +// Encode encodes the given Attribute into the binary writer func (a *Attribute) Encode(bw *util.BinWriter) { if len(a.Data) > maxAttrSize { - bw.Err = ErrMaxData + bw.Err = errMaxData return } bw.Write(uint8(a.Usage)) @@ -43,6 +40,7 @@ func (a *Attribute) Encode(bw *util.BinWriter) { } +// Decode decodes the binary reader into an Attribute object func (a *Attribute) Decode(br *util.BinReader) { br.Read(&a.Usage) if a.Usage == DescriptionURL || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 { diff --git a/pkg/wire/payload/transaction/Input.go b/pkg/wire/payload/transaction/Input.go index 788e47960..0a9aa9525 100644 --- a/pkg/wire/payload/transaction/Input.go +++ b/pkg/wire/payload/transaction/Input.go @@ -11,17 +11,21 @@ type Input struct { PrevIndex uint16 } +//NewInput returns a transaction input object func NewInput(prevHash util.Uint256, prevIndex uint16) *Input { return &Input{ prevHash, prevIndex, } } + +// Encode encodes the given input into a binary writer func (i *Input) Encode(bw *util.BinWriter) { bw.Write(i.PrevHash) bw.Write(i.PrevIndex) } +// Decode decodes a binary reader into an input object func (i *Input) Decode(br *util.BinReader) { br.Read(&i.PrevHash) br.Read(&i.PrevIndex) diff --git a/pkg/wire/payload/transaction/Output.go b/pkg/wire/payload/transaction/Output.go index 9af346dc3..ab590458d 100644 --- a/pkg/wire/payload/transaction/Output.go +++ b/pkg/wire/payload/transaction/Output.go @@ -2,6 +2,7 @@ package transaction import "github.com/CityOfZion/neo-go/pkg/wire/util" +// Output represents a transaction output in the neo-network type Output struct { // The NEO asset id used in the transaction. AssetID util.Uint256 @@ -13,6 +14,7 @@ type Output struct { ScriptHash util.Uint160 } +//NewOutput returns an output object func NewOutput(assetID util.Uint256, Amount int64, ScriptHash util.Uint160) *Output { return &Output{ assetID, @@ -21,12 +23,14 @@ func NewOutput(assetID util.Uint256, Amount int64, ScriptHash util.Uint160) *Out } } +// Encode encodes the Output into a binary writer func (o *Output) Encode(bw *util.BinWriter) { bw.Write(o.AssetID) bw.Write(o.Amount) bw.Write(o.ScriptHash) } +// Decode decodes a binary reader into an output object func (o *Output) Decode(br *util.BinReader) { br.Read(&o.AssetID) br.Read(&o.Amount) diff --git a/pkg/wire/payload/transaction/Witness.go b/pkg/wire/payload/transaction/Witness.go index 8b0d2fa0b..ad31203a2 100644 --- a/pkg/wire/payload/transaction/Witness.go +++ b/pkg/wire/payload/transaction/Witness.go @@ -4,11 +4,13 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//Witness represents a Witness object in a neo transaction type Witness struct { InvocationScript []byte VerificationScript []byte } +// Encode encodes a Witness into a binary writer func (s *Witness) Encode(bw *util.BinWriter) error { bw.VarUint(uint64(len(s.InvocationScript))) @@ -20,6 +22,7 @@ func (s *Witness) Encode(bw *util.BinWriter) error { return bw.Err } +// Decode decodes a binary reader into a Witness object func (s *Witness) Decode(br *util.BinReader) error { lenb := br.VarUint() diff --git a/pkg/wire/payload/transaction/attr_usage.go b/pkg/wire/payload/transaction/attr_usage.go index b8f3a3469..6f80421a3 100644 --- a/pkg/wire/payload/transaction/attr_usage.go +++ b/pkg/wire/payload/transaction/attr_usage.go @@ -1,5 +1,6 @@ package transaction +// AttrUsage represents an attribute usage on the neo network type AttrUsage uint8 // List of valid attribute usages. diff --git a/pkg/wire/payload/transaction/base.go b/pkg/wire/payload/transaction/base.go index 9770e3bd1..541e958e4 100644 --- a/pkg/wire/payload/transaction/base.go +++ b/pkg/wire/payload/transaction/base.go @@ -53,16 +53,20 @@ func createBaseTransaction(typ types.TX, ver version.TX) *Base { } +// Decode implements the transactioner interface func (b *Base) Decode(r io.Reader) error { br := &util.BinReader{R: r} return b.DecodePayload(br) } + +// Encode implements the transactioner interface func (b *Base) Encode(w io.Writer) error { bw := &util.BinWriter{W: w} b.EncodePayload(bw) return bw.Err } +//EncodePayload implements the Messager interface func (b *Base) EncodePayload(bw *util.BinWriter) { b.encodeHashableFields(bw) @@ -74,6 +78,7 @@ func (b *Base) EncodePayload(bw *util.BinWriter) { } } +// DecodePayload implements the messager interface func (b *Base) DecodePayload(br *util.BinReader) error { b.decodeHashableFields(br) @@ -118,7 +123,6 @@ func (b *Base) encodeHashableFields(bw *util.BinWriter) { } } -// created for consistency func (b *Base) decodeHashableFields(br *util.BinReader) { b.Type.Decode(br) @@ -151,15 +155,22 @@ func (b *Base) decodeHashableFields(br *util.BinReader) { } +// AddInput adds an input to the transaction func (b *Base) AddInput(i *Input) { b.Inputs = append(b.Inputs, i) } + +// AddOutput adds an output to the transaction func (b *Base) AddOutput(o *Output) { b.Outputs = append(b.Outputs, o) } + +// AddAttribute adds an attribute to the transaction func (b *Base) AddAttribute(a *Attribute) { b.Attributes = append(b.Attributes, a) } + +// AddWitness adds a witness object to the transaction func (b *Base) AddWitness(w *Witness) { b.Witnesses = append(b.Witnesses, w) } diff --git a/pkg/wire/payload/transaction/claim.go b/pkg/wire/payload/transaction/claim.go index d93e4263c..dce88b2a0 100644 --- a/pkg/wire/payload/transaction/claim.go +++ b/pkg/wire/payload/transaction/claim.go @@ -6,11 +6,13 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//Claim represents a claim transaction on the neo network type Claim struct { *Base Claims []*Input } +//NewClaim returns a ClaimTransaction func NewClaim(ver version.TX) *Claim { basicTrans := createBaseTransaction(types.Contract, ver) @@ -39,5 +41,3 @@ func (c *Claim) decodeExcl(br *util.BinReader) { } } - -// use encode and decode exclusive to make the interface diff --git a/pkg/wire/payload/transaction/contract.go b/pkg/wire/payload/transaction/contract.go index dc93ef0ef..2fa8e5d96 100644 --- a/pkg/wire/payload/transaction/contract.go +++ b/pkg/wire/payload/transaction/contract.go @@ -6,10 +6,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//Contract represents a contract transaction on the neo network type Contract struct { *Base } +//NewContract returns a contract transaction func NewContract(ver version.TX) *Contract { basicTrans := createBaseTransaction(types.Contract, ver) @@ -21,10 +23,6 @@ func NewContract(ver version.TX) *Contract { return contract } -func (c *Contract) encodeExcl(bw *util.BinWriter) { - return -} +func (c *Contract) encodeExcl(bw *util.BinWriter) {} -func (c *Contract) decodeExcl(br *util.BinReader) { - return -} +func (c *Contract) decodeExcl(br *util.BinReader) {} diff --git a/pkg/wire/payload/transaction/enrollment.go b/pkg/wire/payload/transaction/enrollment.go index 317124783..a1c1af669 100644 --- a/pkg/wire/payload/transaction/enrollment.go +++ b/pkg/wire/payload/transaction/enrollment.go @@ -6,24 +6,26 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//Enrollment represents an Enrollment transaction on the neo network type Enrollment struct { *Base Key PublicKey } +//NewEnrollment returns an Enrollment transaction func NewEnrollment(ver version.TX) *Enrollment { basicTrans := createBaseTransaction(types.Enrollment, ver) - Enrollment := &Enrollment{} - Enrollment.Base = basicTrans - Enrollment.encodeExclusive = Enrollment.encodeExcl - Enrollment.decodeExclusive = Enrollment.decodeExcl - return Enrollment + enrollment := &Enrollment{ + Base: basicTrans, + } + enrollment.encodeExclusive = enrollment.encodeExcl + enrollment.decodeExclusive = enrollment.decodeExcl + return enrollment } func (e *Enrollment) encodeExcl(bw *util.BinWriter) { e.Key.Encode(bw) - } func (e *Enrollment) decodeExcl(br *util.BinReader) { diff --git a/pkg/wire/payload/transaction/invocation.go b/pkg/wire/payload/transaction/invocation.go index 04fe85a89..126a4b8d4 100644 --- a/pkg/wire/payload/transaction/invocation.go +++ b/pkg/wire/payload/transaction/invocation.go @@ -9,12 +9,14 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util/fixed8" ) +//Invocation represents an invocation transaction on the neo network type Invocation struct { *Base Script []byte Gas fixed8.Fixed8 } +//NewInvocation returns an invocation transaction func NewInvocation(ver version.TX) *Invocation { basicTrans := createBaseTransaction(types.Invocation, ver) @@ -53,7 +55,7 @@ func (c *Invocation) decodeExcl(br *util.BinReader) { case 1: br.Read(&c.Gas) default: - br.Err = errors.New("Invalid Version Number for Invocation Transaction") + br.Err = errors.New("invalid Version Number for Invocation Transaction") } return } diff --git a/pkg/wire/payload/transaction/issue.go b/pkg/wire/payload/transaction/issue.go index c64f7bfb8..7b999d4ba 100644 --- a/pkg/wire/payload/transaction/issue.go +++ b/pkg/wire/payload/transaction/issue.go @@ -8,10 +8,12 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// Issue represents an issue transaction on the neo network type Issue struct { *Base } +//NewIssue returns an issue transaction func NewIssue(ver version.TX) *Issue { basicTrans := createBaseTransaction(types.Issue, ver) @@ -27,9 +29,6 @@ func (c *Issue) encodeExcl(bw *util.BinWriter) { if c.Version > 1 { bw.Err = errors.New("Version Number Invalid, Issue cannot be more than 0") } - return } -func (c *Issue) decodeExcl(br *util.BinReader) { - return -} +func (c *Issue) decodeExcl(br *util.BinReader) {} diff --git a/pkg/wire/payload/transaction/miner.go b/pkg/wire/payload/transaction/miner.go index 24fdaea5b..fd27f3ef2 100644 --- a/pkg/wire/payload/transaction/miner.go +++ b/pkg/wire/payload/transaction/miner.go @@ -6,11 +6,13 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//Miner represents a miner transaction on the neo network type Miner struct { *Base Nonce uint32 } +//NewMiner returns a miner transaction func NewMiner(ver version.TX) *Miner { basicTrans := createBaseTransaction(types.Miner, ver) @@ -22,13 +24,9 @@ func NewMiner(ver version.TX) *Miner { } func (c *Miner) encodeExcl(bw *util.BinWriter) { - bw.Write(c.Nonce) - return } func (c *Miner) decodeExcl(br *util.BinReader) { - br.Read(&c.Nonce) - } diff --git a/pkg/wire/payload/transaction/publickey.go b/pkg/wire/payload/transaction/publickey.go index b5ea2c72e..6252cfdd4 100644 --- a/pkg/wire/payload/transaction/publickey.go +++ b/pkg/wire/payload/transaction/publickey.go @@ -6,13 +6,17 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// PublicKey represents a public key on the neo network type PublicKey struct { Key []byte } +//Encode encodes a public key into a binary writer func (p *PublicKey) Encode(bw *util.BinWriter) { bw.Write(p.Key) } + +// Decode decodes a bianry reader into a public key func (p *PublicKey) Decode(br *util.BinReader) { var prefix uint8 br.Read(&prefix) @@ -30,6 +34,5 @@ func (p *PublicKey) Decode(br *util.BinReader) { br.Err = errors.New("Prefix not recognised for public key") return } - p.Key = append([]byte{prefix}, p.Key...) } diff --git a/pkg/wire/payload/transaction/publish.go b/pkg/wire/payload/transaction/publish.go index 07988b364..e79475996 100644 --- a/pkg/wire/payload/transaction/publish.go +++ b/pkg/wire/payload/transaction/publish.go @@ -8,6 +8,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// Publish represents a publish transaction on the neo network type Publish struct { *Base Script []byte @@ -21,6 +22,7 @@ type Publish struct { Description string } +//NewPublish returns a publish transaction func NewPublish(ver version.TX) *Publish { basicTrans := createBaseTransaction(types.Publish, ver) diff --git a/pkg/wire/payload/transaction/register.go b/pkg/wire/payload/transaction/register.go index 36bc41772..ab3876d5f 100644 --- a/pkg/wire/payload/transaction/register.go +++ b/pkg/wire/payload/transaction/register.go @@ -7,6 +7,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util/fixed8" ) +// Register represents a register transaction on the neo network type Register struct { *Base // The type of the asset being registered. @@ -28,6 +29,7 @@ type Register struct { Admin util.Uint160 } +//NewRegister returns a register transaction func NewRegister(ver version.TX) *Register { basicTrans := createBaseTransaction(types.Register, ver) Register := &Register{} diff --git a/pkg/wire/payload/transaction/state.go b/pkg/wire/payload/transaction/state.go index 2dcbbeebd..3cbcc11e3 100644 --- a/pkg/wire/payload/transaction/state.go +++ b/pkg/wire/payload/transaction/state.go @@ -6,11 +6,14 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +//StateTX represents a state transaction on the neo network +// XXX: TX postfix here as `state` is ambiguous. We can remove it for consistency type StateTX struct { *Base Descriptors []*StateDescriptor } +//NewStateTX returns a state transaction func NewStateTX(ver version.TX) *StateTX { basicTrans := createBaseTransaction(types.State, ver) diff --git a/pkg/wire/payload/transaction/statedescriptor.go b/pkg/wire/payload/transaction/statedescriptor.go index 17e09a457..bae4b97ac 100644 --- a/pkg/wire/payload/transaction/statedescriptor.go +++ b/pkg/wire/payload/transaction/statedescriptor.go @@ -13,7 +13,8 @@ const ( Validator DescStateType = 0x48 ) -// StateDescriptor .. +// StateDescriptor represents a state descriptor on the neo network +// used in a state transaction type StateDescriptor struct { Type DescStateType Key []byte @@ -21,6 +22,7 @@ type StateDescriptor struct { Field string } +// Decode decodes a binary reader into a state descriptor func (s *StateDescriptor) Decode(br *util.BinReader) { br.Read(&s.Type) @@ -38,6 +40,8 @@ func (s *StateDescriptor) Decode(br *util.BinReader) { s.Field = string(field) } + +//Encode encodes a state descriptor into a binary writer func (s *StateDescriptor) Encode(bw *util.BinWriter) { bw.Write(s.Type) @@ -48,5 +52,4 @@ func (s *StateDescriptor) Encode(bw *util.BinWriter) { bw.Write(s.Value) bw.VarString(s.Field) - } diff --git a/pkg/wire/payload/transaction/types/types.go b/pkg/wire/payload/transaction/types/types.go index e39138600..4b1351438 100644 --- a/pkg/wire/payload/transaction/types/types.go +++ b/pkg/wire/payload/transaction/types/types.go @@ -7,6 +7,7 @@ import ( // TX is the type of a transaction. type TX uint8 +// List of transaction types const ( Miner TX = 0x00 Issue TX = 0x01 @@ -21,9 +22,12 @@ const ( Invocation TX = 0xd1 ) +// Encode encodes a tx type into the binary writer func (t *TX) Encode(bw *util.BinWriter) { bw.Write(t) } + +// Decode decodes a binary reader into a tx type func (t *TX) Decode(br *util.BinReader) { br.Read(t) } diff --git a/pkg/wire/payload/transaction/util.go b/pkg/wire/payload/transaction/util.go index 63fdd458c..3a1fb8bf1 100644 --- a/pkg/wire/payload/transaction/util.go +++ b/pkg/wire/payload/transaction/util.go @@ -8,7 +8,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction/types" ) -func FromBytes(reader *bufio.Reader) (Transactioner, error) { +// FromReader returns a transaction from a bufio.Reader +func FromReader(reader *bufio.Reader) (Transactioner, error) { t, err := reader.Peek(1) @@ -45,17 +46,17 @@ func FromBytes(reader *bufio.Reader) (Transactioner, error) { err = pub.Decode(reader) trans = pub case types.State: - sta := NewStateTX(0) - err = sta.Decode(reader) - trans = sta + state := NewStateTX(0) + err = state.Decode(reader) + trans = state case types.Enrollment: enr := NewEnrollment(0) err = enr.Decode(reader) trans = enr case types.Agency: - err = errors.New("Unsupported transaction type: Agency") + err = errors.New("unsupported transaction type: Agency") default: - err = errors.New("Unsupported transaction with byte type " + hex.EncodeToString([]byte{t[0]})) + err = errors.New("unsupported transaction with byte type " + hex.EncodeToString([]byte{t[0]})) } return trans, err } diff --git a/pkg/wire/payload/transaction/version/version.go b/pkg/wire/payload/transaction/version/version.go index 9b9e9ce57..80480e7ac 100644 --- a/pkg/wire/payload/transaction/version/version.go +++ b/pkg/wire/payload/transaction/version/version.go @@ -4,16 +4,21 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +// TX represents a tx version type TX uint8 +// List of latest tx version const ( - Contract TX = 0 + Contract TX = 0 + Invocation TX = 1 ) +// Encode encodes the tx version into the binary writer func (v *TX) Encode(bw *util.BinWriter) { bw.Write(v) } +// Decode decodes the binary reader into a tx type func (v *TX) Decode(br *util.BinReader) { br.Read(v) } diff --git a/pkg/wire/protocol/protocol.go b/pkg/wire/protocol/protocol.go index 3f9493f4e..ebc916e50 100644 --- a/pkg/wire/protocol/protocol.go +++ b/pkg/wire/protocol/protocol.go @@ -1,15 +1,19 @@ package protocol +//Version represents the latest protocol version for the neo node type Version uint32 const ( + // DefaultVersion is the nodes default protocol version DefaultVersion Version = 0 - UserAgent = "/NEO-GO/" // TODO: This may be relocated to a config file + // UserAgent is the nodes user agent or human-readable name + UserAgent = "/NEO-GO/" ) // ServiceFlag indicates the services provided by the node. 1 = P2P Full Node type ServiceFlag uint64 +// List of Services offered by the node const ( NodePeerService ServiceFlag = 1 // BloomFilerService ServiceFlag = 2 // Not implemented @@ -21,6 +25,7 @@ const ( // Magic is the network that NEO is running on type Magic uint32 +// List of possible networks const ( MainNet Magic = 7630401 TestNet Magic = 0x74746e41 diff --git a/pkg/wire/util/Checksum/checksum.go b/pkg/wire/util/Checksum/checksum.go index 092ea177b..6282650c7 100644 --- a/pkg/wire/util/Checksum/checksum.go +++ b/pkg/wire/util/Checksum/checksum.go @@ -7,16 +7,20 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" ) +// Compare calculates the checksum of b +// then compares it with the `have` checksum passed as a parameter func Compare(have uint32, b []byte) bool { want := FromBytes(b) return have == want } +// FromBuf calculates the checksum of a buffer func FromBuf(buf *bytes.Buffer) uint32 { return FromBytes(buf.Bytes()) } +// FromBytes calculates the checksum of a byte slice func FromBytes(buf []byte) uint32 { b, err := hash.DoubleSha256(buf) diff --git a/pkg/wire/util/address/address.go b/pkg/wire/util/address/address.go index 6a9baeb8b..84daf5345 100644 --- a/pkg/wire/util/address/address.go +++ b/pkg/wire/util/address/address.go @@ -6,6 +6,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/base58" ) +// ToScriptHash converts an address to a script hash func ToScriptHash(address string) string { decodedAddressAsBytes, err := base58.Decode(address) diff --git a/pkg/wire/util/binaryReader.go b/pkg/wire/util/binaryReader.go index ce2c0219b..529610f14 100644 --- a/pkg/wire/util/binaryReader.go +++ b/pkg/wire/util/binaryReader.go @@ -5,17 +5,24 @@ import ( "io" ) +//BinReader is a convenient wrapper around a io.Reader and err object +// Used to simplify error handling when reading into a struct with many fields type BinReader struct { R io.Reader Err error } +// Read reads from the underlying io.Reader +// into the interface v in LE func (r *BinReader) Read(v interface{}) { if r.Err != nil { return } r.Err = binary.Read(r.R, binary.LittleEndian, v) } + +// ReadBigEnd reads from the underlying io.Reader +// into the interface v in BE func (r *BinReader) ReadBigEnd(v interface{}) { if r.Err != nil { return @@ -23,6 +30,8 @@ func (r *BinReader) ReadBigEnd(v interface{}) { r.Err = binary.Read(r.R, binary.BigEndian, v) } +//VarUint reads a variable integer from the +// underlying reader func (r *BinReader) VarUint() uint64 { var b uint8 r.Err = binary.Read(r.R, binary.LittleEndian, &b) @@ -46,6 +55,8 @@ func (r *BinReader) VarUint() uint64 { return uint64(b) } +// VarBytes reads the next set of bytes from the underlying reader. +// VarUInt is used to determine how large that slice is func (r *BinReader) VarBytes() []byte { n := r.VarUint() b := make([]byte, n) @@ -53,6 +64,7 @@ func (r *BinReader) VarBytes() []byte { return b } +// VarString calls VarBytes and casts the results as a string func (r *BinReader) VarString() string { b := r.VarBytes() return string(b) diff --git a/pkg/wire/util/binaryWriter.go b/pkg/wire/util/binaryWriter.go index 076e78dea..d4fa34ae6 100644 --- a/pkg/wire/util/binaryWriter.go +++ b/pkg/wire/util/binaryWriter.go @@ -6,11 +6,15 @@ import ( "io" ) +//BinWriter is a convenient wrapper around a io.Writer and err object +// Used to simplify error handling when writing into a io.Writer +// from a struct with many fields type BinWriter struct { W io.Writer Err error } +// Write writes into the underlying io.Writer from an object v in LE format func (w *BinWriter) Write(v interface{}) { if w.Err != nil { return @@ -18,6 +22,7 @@ func (w *BinWriter) Write(v interface{}) { w.Err = binary.Write(w.W, binary.LittleEndian, v) } +// WriteBigEnd writes into the underlying io.Writer from an object v in BE format // Only used for IP and PORT. Additional method makes the default LittleEndian case clean func (w *BinWriter) WriteBigEnd(v interface{}) { if w.Err != nil { @@ -26,10 +31,7 @@ func (w *BinWriter) WriteBigEnd(v interface{}) { w.Err = binary.Write(w.W, binary.BigEndian, v) } -func (w *BinWriter) VarString(s string) { - w.VarBytes([]byte(s)) -} - +// VarUint writes a uint64 into the underlying writer func (w *BinWriter) VarUint(val uint64) { if val < 0 { w.Err = errors.New("value out of range") @@ -61,8 +63,13 @@ func (w *BinWriter) VarUint(val uint64) { } -// WriteVarBytes writes a variable length byte array. +// VarBytes writes a variable length byte array into the underlying io.Writer func (w *BinWriter) VarBytes(b []byte) { w.VarUint(uint64(len(b))) w.Write(b) } + +//VarString casts the string as a byte slice and calls VarBytes +func (w *BinWriter) VarString(s string) { + w.VarBytes([]byte(s)) +} diff --git a/pkg/wire/util/crypto/base58/base58.go b/pkg/wire/util/crypto/base58/base58.go index b0a67a912..0e9710009 100644 --- a/pkg/wire/util/crypto/base58/base58.go +++ b/pkg/wire/util/crypto/base58/base58.go @@ -1,8 +1,11 @@ package base58 import ( + "bytes" "fmt" "math/big" + + "github.com/CityOfZion/neo-go/pkg/wire/util/crypto/hash" ) const prefix rune = '1' @@ -22,7 +25,7 @@ var decodeMap = map[rune]int64{ 'x': 55, 'y': 56, 'z': 57, } -// Base58Decode decodes the base58 encoded string. +// Decode decodes the base58 encoded string. func Decode(s string) ([]byte, error) { var ( startIndex = 0 @@ -58,7 +61,7 @@ func Decode(s string) ([]byte, error) { return buf, nil } -// Base58Encode encodes a byte slice to be a base58 encoded string. +// Encode encodes a byte slice to be a base58 encoded string. func Encode(bytes []byte) string { var ( lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" @@ -77,47 +80,47 @@ func Encode(bytes []byte) string { return encoded } -// Base58CheckDecode decodes the given string. -// func CheckDecode(s string) (b []byte, err error) { -// b, err = Decode(s) -// if err != nil { -// return nil, err -// } +// CheckDecode decodes the given string. +func CheckDecode(s string) (b []byte, err error) { + b, err = Decode(s) + if err != nil { + return nil, err + } -// for i := 0; i < len(s); i++ { -// if s[i] != '1' { -// break -// } -// b = append([]byte{0x00}, b...) -// } + for i := 0; i < len(s); i++ { + if s[i] != '1' { + break + } + b = append([]byte{0x00}, b...) + } -// if len(b) < 5 { -// return nil, fmt.Errorf("Invalid base-58 check string: missing checksum. -1") -// } + if len(b) < 5 { + return nil, fmt.Errorf("Invalid base-58 check string: missing checksum. -1") + } -// hash, err := hash.DoubleSha256(b[:len(b)-4]) + hash, err := hash.DoubleSha256(b[:len(b)-4]) -// if err != nil { -// return nil, fmt.Errorf("Could not double sha256 data") -// } + if err != nil { + return nil, fmt.Errorf("Could not double sha256 data") + } -// if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 { -// return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum. -2") -// } + if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 { + return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum. -2") + } -// // Strip the 4 byte long hash. -// b = b[:len(b)-4] + // Strip the 4 byte long hash. + b = b[:len(b)-4] -// return b, nil -// } + return b, nil +} -// // Base58checkEncode encodes b into a base-58 check encoded string. -// func CheckEncode(b []byte) (string, error) { -// hash, err := hash.DoubleSha256(b) -// if err != nil { -// return "", fmt.Errorf("Could not double sha256 data") -// } -// b = append(b, hash[0:4]...) +// CheckEncode encodes b into a base-58 check encoded string. +func CheckEncode(b []byte) (string, error) { + hash, err := hash.DoubleSha256(b) + if err != nil { + return "", fmt.Errorf("Could not double sha256 data") + } + b = append(b, hash[0:4]...) -// return Encode(b), nil -// } + return Encode(b), nil +} diff --git a/pkg/wire/util/crypto/hash/hash.go b/pkg/wire/util/crypto/hash/hash.go index 22ab1c6d6..018885984 100644 --- a/pkg/wire/util/crypto/hash/hash.go +++ b/pkg/wire/util/crypto/hash/hash.go @@ -8,6 +8,7 @@ import ( "golang.org/x/crypto/ripemd160" ) +// Sha256 hashes the byte slice using sha256 func Sha256(data []byte) (util.Uint256, error) { var hash util.Uint256 hasher := sha256.New() @@ -21,6 +22,7 @@ func Sha256(data []byte) (util.Uint256, error) { return hash, nil } +// DoubleSha256 hashes the underlying data twice using sha256 func DoubleSha256(data []byte) (util.Uint256, error) { var hash util.Uint256 @@ -36,6 +38,7 @@ func DoubleSha256(data []byte) (util.Uint256, error) { return hash, nil } +// RipeMD160 hashes the underlying data using ripemd160 func RipeMD160(data []byte) (util.Uint160, error) { var hash util.Uint160 hasher := ripemd160.New() @@ -49,6 +52,7 @@ func RipeMD160(data []byte) (util.Uint160, error) { return hash, nil } +//Hash160 hashes the underlying data using sha256 then ripemd160 func Hash160(data []byte) (util.Uint160, error) { var hash util.Uint160 h1, err := Sha256(data) @@ -63,6 +67,7 @@ func Hash160(data []byte) (util.Uint160, error) { return hash, nil } +// Checksum calculates the checksum of the byte slice using sha256 func Checksum(data []byte) ([]byte, error) { hash, err := Sum(data) if err != nil { @@ -71,6 +76,7 @@ func Checksum(data []byte) ([]byte, error) { return hash[:4], nil } +// Sum calculates the Sum of the data by using double sha256 func Sum(b []byte) (util.Uint256, error) { hash, err := DoubleSha256((b)) return hash, err diff --git a/pkg/wire/util/fixed8/fixed8.go b/pkg/wire/util/fixed8/fixed8.go index 9a8119ae7..9b7f04e4a 100644 --- a/pkg/wire/util/fixed8/fixed8.go +++ b/pkg/wire/util/fixed8/fixed8.go @@ -25,12 +25,16 @@ func (f Fixed8) String() string { func (f Fixed8) Value() float64 { return float64(f) / float64(decimals) } + +// Add adds two Fixed8 values together func (f Fixed8) Add(val Fixed8) Fixed8 { a := int64(f.Value()) b := int64(val.Value()) c := a + b return FromInt(c) } + +//Sub subtracts two fixed values from each other func (f Fixed8) Sub(val Fixed8) Fixed8 { a := int64(f.Value()) b := int64(val.Value()) @@ -38,18 +42,21 @@ func (f Fixed8) Sub(val Fixed8) Fixed8 { return FromInt(c) } +//FromInt returns a Fixed8 objects from an int64 func FromInt(val int64) Fixed8 { return Fixed8(val * decimals) } +// FromFloat returns a Fixed8 object from a float64 func FromFloat(val float64) Fixed8 { return Fixed8(val * decimals) } +// FromString returns a Fixed8 object from a string func FromString(val string) (Fixed8, error) { res, err := strconv.ParseFloat(val, 64) if err != nil { - return 0, fmt.Errorf("Failed at parsing string %s", val) + return 0, fmt.Errorf("failed at parsing string %s", val) } return FromFloat(res), nil } diff --git a/pkg/wire/util/io/io.go b/pkg/wire/util/io/io.go index 0d6ada43c..89117c602 100644 --- a/pkg/wire/util/io/io.go +++ b/pkg/wire/util/io/io.go @@ -4,6 +4,7 @@ import ( "os" ) +// UpdateFile appends a byte slice to a file func UpdateFile(filename string, data []byte) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) diff --git a/pkg/wire/util/ip/ip.go b/pkg/wire/util/ip/ip.go index 7befd8cca..4de12e8bd 100644 --- a/pkg/wire/util/ip/ip.go +++ b/pkg/wire/util/ip/ip.go @@ -5,6 +5,7 @@ import ( "net" ) +//GetLocalIP returns the ip address of the current node // https://stackoverflow.com/a/37382208 func GetLocalIP() net.IP { conn, err := net.Dial("udp", "8.8.8.8:80") diff --git a/pkg/wire/util/slice/slice.go b/pkg/wire/util/slice/slice.go index 0cf29435f..fdb93adc3 100644 --- a/pkg/wire/util/slice/slice.go +++ b/pkg/wire/util/slice/slice.go @@ -1,6 +1,6 @@ package slice -// SliceReverse return a reversed version of the given byte slice. +// Reverse return a reversed version of the given byte slice. func Reverse(b []byte) []byte { // Protect from big.Ints that have 1 len bytes. if len(b) < 2 { diff --git a/pkg/wire/util/uint256.go b/pkg/wire/util/uint256.go index e1a5a914b..e7a5e73fc 100644 --- a/pkg/wire/util/uint256.go +++ b/pkg/wire/util/uint256.go @@ -44,9 +44,11 @@ func (u Uint256) Bytes() []byte { } return b } -func (u Uint256) Reverse() (res Uint256) { - res, _ = Uint256DecodeBytes(u.BytesReverse()) - return + +// Reverse reverses the Uint256 object +func (u Uint256) Reverse() Uint256 { + res, _ := Uint256DecodeBytes(u.BytesReverse()) + return res } // BytesReverse return a reversed byte representation of u. diff --git a/pkg/wire/util/util.go b/pkg/wire/util/util.go index 65c485617..58499562c 100644 --- a/pkg/wire/util/util.go +++ b/pkg/wire/util/util.go @@ -8,18 +8,23 @@ import ( "io/ioutil" ) -// Functions +// Convenience function + +// BufferLength returns the length of a buffer as uint32 func BufferLength(buf *bytes.Buffer) uint32 { return uint32(buf.Len()) } +// SumSHA256 returns the sha256 sum of the data func SumSHA256(b []byte) []byte { h := sha256.New() h.Write(b) return h.Sum(nil) } +// CalculateHash takes a function with a binary writer and returns +// the double hash of the io.Writer func CalculateHash(f func(bw *BinWriter)) (Uint256, error) { buf := new(bytes.Buffer) bw := &BinWriter{W: buf} @@ -33,15 +38,12 @@ func CalculateHash(f func(bw *BinWriter)) (Uint256, error) { } -func ReaderToBuffer(r io.Reader) (buf *bytes.Buffer, err error) { +//ReaderToBuffer converts a io.Reader into a bytes.Buffer +func ReaderToBuffer(r io.Reader) (*bytes.Buffer, error) { byt, err := ioutil.ReadAll(r) - if err != nil { - - return + return nil, err } - - buf = bytes.NewBuffer(byt) - - return + buf := bytes.NewBuffer(byt) + return buf, nil } From a7db1ceaa59d9a298934e1fa7c023e32cada9854 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:30:12 +0000 Subject: [PATCH 040/117] minor comment --- pkg/vm/vm_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index db12c014c..896e528e7 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -76,9 +76,11 @@ func TestSimpleRun(t *testing.T) { builder := stack.NewBuilder() builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT) + // Pass program to VM vm := NewVM(builder.Bytes()) + // Runs vm with program _, err := vm.Run() assert.Nil(t, err) From 98096f6c20e922b5cbdb82f9063d8791c05c6024 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:31:58 +0000 Subject: [PATCH 041/117] golint --- pkg/vm/state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 64db70f2f..98c5cc912 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -3,6 +3,7 @@ package vm //Vmstate represents all possible states that the neo-vm can be in type Vmstate byte +// List of possible vm states const ( NONE = 0 HALT = 1 << 0 From 231aa29e5ae20e3f13f2892199d3b778ea5b6ab6 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 18:38:52 +0000 Subject: [PATCH 042/117] Fix test --- pkg/vm/vm_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 896e528e7..4ecc44317 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -51,11 +51,13 @@ func TestPushAdd(t *testing.T) { ok = peekTopEStackIsValue(t, vm, 54) assert.True(t, ok) - // If we try to step again, we should get an error and HALT + // If we try to step again, we should get a nil error and HALT // because we have gone over the instruction pointer + // error is nil because when there are nomore instructions, the vm + // will add a RET opcode and return state, err = vm.step() assert.Equal(t, HALT, int(state)) - assert.NotNil(t, err) + assert.Nil(t, err) } From 9e16bac7d8c31ef701f5f615a76161e9f2e51a16 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 20:21:48 +0000 Subject: [PATCH 043/117] clarify vm states --- pkg/vm/state.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/vm/state.go b/pkg/vm/state.go index 98c5cc912..e6760c7c9 100644 --- a/pkg/vm/state.go +++ b/pkg/vm/state.go @@ -5,8 +5,16 @@ type Vmstate byte // List of possible vm states const ( - NONE = 0 - HALT = 1 << 0 + // NONE is the running state of the vm + // NONE signifies that the vm is ready to process an opcode + NONE = 0 + // HALT is a stopped state of the vm + // where the stop was signalled by the program completion + HALT = 1 << 0 + // FAULT is a stopped state of the vm + // where the stop was signalled by an error in the program FAULT = 1 << 1 + // BREAK is a suspended state for the VM + // were the break was signalled by a breakpoint BREAK = 1 << 2 ) From 351f0acdfe612227894e66c9e26ee39f64326115 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 22:57:36 +0000 Subject: [PATCH 044/117] Add astack --- pkg/vm/stack/context.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index ee6cce1a3..d381d74cb 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -6,8 +6,7 @@ import ( ) // Context represent the current execution context of the VM. -// context will be treated as stack item -// and placed onto the invocation stack +// context will be treated as stack item and placed onto the invocation stack type Context struct { *abstractItem @@ -22,6 +21,9 @@ type Context struct { // Evaluation Stack Estack RandomAccess + + // Alternative Stack + Astack RandomAccess } // NewContext return a new Context object. @@ -44,7 +46,7 @@ func (c *Context) Context() (*Context, error) { func (c *Context) Next() (Instruction, error) { c.ip++ if c.ip >= len(c.prog) { - return RET, errors.New("program pointer is more than the length of program. RETURNING") + return RET, errors.New("program pointer is more than the length of program. Returning RET OPCODE") } return Instruction(c.prog[c.ip]), nil } From de3137197a46d7d03fc2a1660c636dd9265d4fc2 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 17 Mar 2019 23:02:34 +0000 Subject: [PATCH 045/117] remove extra base58 --- pkg/crypto/base58/base58.go | 48 ++++++++ pkg/wire/util/crypto/base58/base58.go | 126 --------------------- pkg/wire/util/crypto/base58/base58_test.go | 32 ------ pkg/wire/util/crypto/hash/hash.go | 83 -------------- pkg/wire/util/crypto/hash/hash_test.go | 62 ---------- 5 files changed, 48 insertions(+), 303 deletions(-) delete mode 100644 pkg/wire/util/crypto/base58/base58.go delete mode 100644 pkg/wire/util/crypto/base58/base58_test.go delete mode 100644 pkg/wire/util/crypto/hash/hash.go delete mode 100644 pkg/wire/util/crypto/hash/hash_test.go diff --git a/pkg/crypto/base58/base58.go b/pkg/crypto/base58/base58.go index bfac3d4f0..5d57dc8c4 100755 --- a/pkg/crypto/base58/base58.go +++ b/pkg/crypto/base58/base58.go @@ -1,8 +1,11 @@ package base58 import ( + "bytes" "fmt" "math/big" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" ) const prefix rune = '1' @@ -76,3 +79,48 @@ func Encode(bytes []byte) string { return encoded } + +// CheckDecode decodes the given string. +func CheckDecode(s string) (b []byte, err error) { + b, err = Decode(s) + if err != nil { + return nil, err + } + + for i := 0; i < len(s); i++ { + if s[i] != '1' { + break + } + b = append([]byte{0x00}, b...) + } + + if len(b) < 5 { + return nil, fmt.Errorf("Invalid base-58 check string: missing checksum") + } + + hash, err := hash.DoubleSha256(b[:len(b)-4]) + + if err != nil { + return nil, fmt.Errorf("Could not double sha256 data") + } + + if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 { + return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum") + } + + // Strip the 4 byte long hash. + b = b[:len(b)-4] + + return b, nil +} + +// CheckEncode encodes b into a base-58 check encoded string. +func CheckEncode(b []byte) (string, error) { + hash, err := hash.DoubleSha256(b) + if err != nil { + return "", fmt.Errorf("Could not double sha256 data") + } + b = append(b, hash[0:4]...) + + return Encode(b), nil +} diff --git a/pkg/wire/util/crypto/base58/base58.go b/pkg/wire/util/crypto/base58/base58.go deleted file mode 100644 index 0e9710009..000000000 --- a/pkg/wire/util/crypto/base58/base58.go +++ /dev/null @@ -1,126 +0,0 @@ -package base58 - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/CityOfZion/neo-go/pkg/wire/util/crypto/hash" -) - -const prefix rune = '1' - -var decodeMap = map[rune]int64{ - '1': 0, '2': 1, '3': 2, '4': 3, '5': 4, - '6': 5, '7': 6, '8': 7, '9': 8, 'A': 9, - 'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14, - 'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19, - 'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24, - 'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29, - 'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34, - 'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39, - 'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44, - 'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49, - 's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54, - 'x': 55, 'y': 56, 'z': 57, -} - -// Decode decodes the base58 encoded string. -func Decode(s string) ([]byte, error) { - var ( - startIndex = 0 - zero = 0 - ) - for i, c := range s { - if c == prefix { - zero++ - } else { - startIndex = i - break - } - } - - var ( - n = big.NewInt(0) - div = big.NewInt(58) - ) - for _, c := range s[startIndex:] { - charIndex, ok := decodeMap[c] - if !ok { - return nil, fmt.Errorf( - "invalid character '%c' when decoding this base58 string: '%s'", c, s, - ) - } - n.Add(n.Mul(n, div), big.NewInt(charIndex)) - } - - out := n.Bytes() - buf := make([]byte, (zero + len(out))) - copy(buf[zero:], out[:]) - - return buf, nil -} - -// Encode encodes a byte slice to be a base58 encoded string. -func Encode(bytes []byte) string { - var ( - lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - x = new(big.Int).SetBytes(bytes) - r = new(big.Int) - m = big.NewInt(58) - zero = big.NewInt(0) - encoded string - ) - - for x.Cmp(zero) > 0 { - x.QuoRem(x, m, r) - encoded = string(lookupTable[r.Int64()]) + encoded - } - - return encoded -} - -// CheckDecode decodes the given string. -func CheckDecode(s string) (b []byte, err error) { - b, err = Decode(s) - if err != nil { - return nil, err - } - - for i := 0; i < len(s); i++ { - if s[i] != '1' { - break - } - b = append([]byte{0x00}, b...) - } - - if len(b) < 5 { - return nil, fmt.Errorf("Invalid base-58 check string: missing checksum. -1") - } - - hash, err := hash.DoubleSha256(b[:len(b)-4]) - - if err != nil { - return nil, fmt.Errorf("Could not double sha256 data") - } - - if bytes.Compare(hash[0:4], b[len(b)-4:]) != 0 { - return nil, fmt.Errorf("Invalid base-58 check string: invalid checksum. -2") - } - - // Strip the 4 byte long hash. - b = b[:len(b)-4] - - return b, nil -} - -// CheckEncode encodes b into a base-58 check encoded string. -func CheckEncode(b []byte) (string, error) { - hash, err := hash.DoubleSha256(b) - if err != nil { - return "", fmt.Errorf("Could not double sha256 data") - } - b = append(b, hash[0:4]...) - - return Encode(b), nil -} diff --git a/pkg/wire/util/crypto/base58/base58_test.go b/pkg/wire/util/crypto/base58/base58_test.go deleted file mode 100644 index 524f0e55e..000000000 --- a/pkg/wire/util/crypto/base58/base58_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package base58 - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDecode(t *testing.T) { - input := "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" - - data, err := Decode(input) - if err != nil { - t.Fatal(err) - } - - expected := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" - actual := hex.EncodeToString(data) - assert.Equal(t, expected, actual) -} -func TestEncode(t *testing.T) { - input := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2" - - inputBytes, _ := hex.DecodeString(input) - - data := Encode(inputBytes) - - expected := "F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" // Removed the 1 as it is not checkEncoding - actual := data - assert.Equal(t, expected, actual) -} diff --git a/pkg/wire/util/crypto/hash/hash.go b/pkg/wire/util/crypto/hash/hash.go deleted file mode 100644 index 018885984..000000000 --- a/pkg/wire/util/crypto/hash/hash.go +++ /dev/null @@ -1,83 +0,0 @@ -package hash - -import ( - "crypto/sha256" - "io" - - "github.com/CityOfZion/neo-go/pkg/wire/util" - "golang.org/x/crypto/ripemd160" -) - -// Sha256 hashes the byte slice using sha256 -func Sha256(data []byte) (util.Uint256, error) { - var hash util.Uint256 - hasher := sha256.New() - hasher.Reset() - _, err := hasher.Write(data) - - hash, err = util.Uint256DecodeBytes(hasher.Sum(nil)) - if err != nil { - return hash, err - } - return hash, nil -} - -// DoubleSha256 hashes the underlying data twice using sha256 -func DoubleSha256(data []byte) (util.Uint256, error) { - var hash util.Uint256 - - h1, err := Sha256(data) - if err != nil { - return hash, err - } - - hash, err = Sha256(h1.Bytes()) - if err != nil { - return hash, err - } - return hash, nil -} - -// RipeMD160 hashes the underlying data using ripemd160 -func RipeMD160(data []byte) (util.Uint160, error) { - var hash util.Uint160 - hasher := ripemd160.New() - hasher.Reset() - _, err := io.WriteString(hasher, string(data)) - - hash, err = util.Uint160DecodeBytes(hasher.Sum(nil)) - if err != nil { - return hash, err - } - return hash, nil -} - -//Hash160 hashes the underlying data using sha256 then ripemd160 -func Hash160(data []byte) (util.Uint160, error) { - var hash util.Uint160 - h1, err := Sha256(data) - - h2, err := RipeMD160(h1.Bytes()) - - hash, err = util.Uint160DecodeBytes(h2.Bytes()) - - if err != nil { - return hash, err - } - return hash, nil -} - -// Checksum calculates the checksum of the byte slice using sha256 -func Checksum(data []byte) ([]byte, error) { - hash, err := Sum(data) - if err != nil { - return nil, err - } - return hash[:4], nil -} - -// Sum calculates the Sum of the data by using double sha256 -func Sum(b []byte) (util.Uint256, error) { - hash, err := DoubleSha256((b)) - return hash, err -} diff --git a/pkg/wire/util/crypto/hash/hash_test.go b/pkg/wire/util/crypto/hash/hash_test.go deleted file mode 100644 index aa23718f6..000000000 --- a/pkg/wire/util/crypto/hash/hash_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package hash - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSha256(t *testing.T) { - input := []byte("hello") - data, err := Sha256(input) - - if err != nil { - t.Fatal(err) - } - expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" - actual := hex.EncodeToString(data.Bytes()) // MARK: In the DecodeBytes function, there is a bytes reverse, not sure why? - - assert.Equal(t, expected, actual) -} - -func TestHashDoubleSha256(t *testing.T) { - input := []byte("hello") - data, err := DoubleSha256(input) - - if err != nil { - t.Fatal(err) - } - - firstSha, _ := Sha256(input) - doubleSha, _ := Sha256(firstSha.Bytes()) - expected := hex.EncodeToString(doubleSha.Bytes()) - - actual := hex.EncodeToString(data.Bytes()) - assert.Equal(t, expected, actual) -} - -func TestHashRipeMD160(t *testing.T) { - input := []byte("hello") - data, err := RipeMD160(input) - - if err != nil { - t.Fatal(err) - } - expected := "108f07b8382412612c048d07d13f814118445acd" - actual := hex.EncodeToString(data.Bytes()) - assert.Equal(t, expected, actual) -} - -func TestHash160(t *testing.T) { - input := "02cccafb41b220cab63fd77108d2d1ebcffa32be26da29a04dca4996afce5f75db" - publicKeyBytes, _ := hex.DecodeString(input) - data, err := Hash160(publicKeyBytes) - - if err != nil { - t.Fatal(err) - } - expected := "c8e2b685cc70ec96743b55beb9449782f8f775d8" - actual := hex.EncodeToString(data.Bytes()) - assert.Equal(t, expected, actual) -} From 38ad4572c4c1f44fbd41e5523230d6f6b1b4ce6f Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:13:08 +0000 Subject: [PATCH 046/117] [VM] Pass ResultStack to the opcode handlers --- pkg/vm/vm.go | 7 ++++++- pkg/vm/vm_ops.go | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 604348baf..1f9fa66df 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -8,6 +8,11 @@ import ( // VM represents an instance of a Neo Virtual Machine type VM struct { + // ResultStack contains the results of + // the last evaluation stack before the program terminated + ResultStack stack.RandomAccess + // InvocationStack contains all of the contexts + // loaded into the vm InvocationStack stack.Invocation state Vmstate } @@ -63,5 +68,5 @@ func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error if !ok { return FAULT, fmt.Errorf("unknown opcode entered %v", op) } - return handleOp(op, ctx, &v.InvocationStack) + return handleOp(op, ctx, &v.InvocationStack, &v.ResultStack) } diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index bf9291933..39b796389 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -2,7 +2,9 @@ package vm import "github.com/CityOfZion/neo-go/pkg/vm/stack" -var opFunc = map[stack.Instruction]func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error){ +type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) + +var opFunc = map[stack.Instruction]stackInfo{ stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, From c177e5577ec3874657c18d876ed198a6238cfce7 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:14:03 +0000 Subject: [PATCH 047/117] [VM] refactor handlers to have rstack as argument --- pkg/vm/vm_ops_bitwise.go | 2 +- pkg/vm/vm_ops_exceptions.go | 4 ++-- pkg/vm/vm_ops_flow.go | 12 +++++++----- pkg/vm/vm_ops_maths.go | 4 ++-- pkg/vm/vm_ops_stackmani.go | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go index 8c73ce25e..350543fa2 100644 --- a/pkg/vm/vm_ops_bitwise.go +++ b/pkg/vm/vm_ops_bitwise.go @@ -6,7 +6,7 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" // EQUAL pushes true to the stack // If the two top items on the stack are equal -func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { itemA, itemB, err := popTwoByteArrays(ctx) if err != nil { diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go index bdf45dbfa..dd09cfb60 100644 --- a/pkg/vm/vm_ops_exceptions.go +++ b/pkg/vm/vm_ops_exceptions.go @@ -12,7 +12,7 @@ import ( // does not evaluate to true // For specific logic on how a number of bytearray is evaluated can be seen // from the boolean conversion methods on the stack items -func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { // Pop item from top of stack item, err := ctx.Estack.Pop() @@ -27,7 +27,7 @@ func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati // If false, throw if !ok.Value() { - return FAULT, errors.New("Item on top of stack evaluates to false") + return FAULT, errors.New("item on top of stack evaluates to false") } return NONE, nil } diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 7a90638b0..67ca4f825 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -8,17 +8,19 @@ import ( // RET Returns from the current context // Returns HALT if there are nomore context's to run -func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { // Pop current context from the Inovation stack - err := istack.RemoveCurrentContext() + ctx, err := istack.PopCurrentContext() if err != nil { return FAULT, err } - - // If there are no-more context's left to ran, then we HALT + // If this was the last context, then we copy over the evaluation stack to the resultstack + // As the program is about to terminate, once we remove the context if istack.Len() == 0 { - return HALT, nil + + err = ctx.Estack.CopyTo(rstack) + return HALT, err } return NONE, nil diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 6085dccb8..a15596a30 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -7,7 +7,7 @@ import ( // Add adds two stack Items together. // Returns an error if either items cannot be casted to an integer // or if integers cannot be added together -func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) if err != nil { @@ -26,7 +26,7 @@ func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vm // Sub subtracts two stack Items. // Returns an error if either items cannot be casted to an integer // or if integers cannot be subtracted together -func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { operandA, operandB, err := popTwoIntegers(ctx) if err != nil { diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index 366beb15b..f5e2ddc24 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -7,7 +7,7 @@ import ( // Stack Manipulation Opcodes // PushNBytes will Read N Bytes from the script and push it onto the stack -func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation) (Vmstate, error) { +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { val, err := ctx.ReadBytes(int(op)) if err != nil { From da27c2b3f0c8e32b5ff0b64f747da17c6f7c75d8 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:15:09 +0000 Subject: [PATCH 048/117] [Stack] - Change RemoveCurrentContext for PopCurrentContext - Add CopTo method to stack --- pkg/vm/stack/invocationstack.go | 19 ++++++++++++------- pkg/vm/stack/stack.go | 8 ++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pkg/vm/stack/invocationstack.go b/pkg/vm/stack/invocationstack.go index a3a66b8ec..49f058f62 100644 --- a/pkg/vm/stack/invocationstack.go +++ b/pkg/vm/stack/invocationstack.go @@ -29,11 +29,17 @@ func (i *Invocation) CurrentContext() (*Context, error) { return i.peekContext(0) } -// RemoveCurrentContext removes the context on the top of the invocation stack -// This is a convenience method for Pop -func (i *Invocation) RemoveCurrentContext() error { - _, err := i.Pop() - return err +// PopCurrentContext Pops a context item from the top of the stack +func (i *Invocation) PopCurrentContext() (*Context, error) { + item, err := i.Pop() + if err != nil { + return nil, err + } + ctx, err := item.Context() + if err != nil { + return nil, err + } + return ctx, err } // CallingContext will return the cntext item @@ -49,8 +55,7 @@ func (i *Invocation) CallingContext() (*Context, error) { // started the program func (i *Invocation) EntryContext() (*Context, error) { - // firstItemIndex refers to the first item - // that was popped on the stack + // firstItemIndex refers to the first item that was popped on the stack firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned return i.peekContext(firstItemIndex) } diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index e857a9ada..8d1ac5b78 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -119,6 +119,14 @@ func (ras *RandomAccess) Peek(n uint16) (Item, error) { return ras.vals[index], nil } +// CopyTo will copy all of the stack items from `ras` into the stack that is passed as an argument +// XXX: once maxstacksize is implemented, we will return error if size goes over +// There will also be additional checks needed once stack isolation is added +func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { + stack.vals = append(stack.vals, ras.vals...) + return nil +} + // Convenience Functions // PopInt will remove the last stack item that was added From 8809be183e265791c8a3cc8dd356f121caa2692a Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:15:40 +0000 Subject: [PATCH 049/117] [VM] Add Result stack len check in simple run test --- pkg/vm/vm_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 4ecc44317..adb8b5db3 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -86,6 +86,9 @@ func TestSimpleRun(t *testing.T) { _, err := vm.Run() assert.Nil(t, err) + // ResultStack should be nil + assert.Equal(t, -1, vm.ResultStack.Len()) + } // returns true if the value at the top of the evaluation stack is a integer @@ -108,8 +111,7 @@ func peakTopEstack(t *testing.T, vm *VM) stack.Item { return item } -// returns true if the total number of items on the evaluation stack -// is equal to value +// returns true if the total number of items on the evaluation stack is equal to value func EstackLen(t *testing.T, vm *VM, value int) bool { ctx, err := vm.InvocationStack.CurrentContext() assert.Nil(t, err) From a2bdc076d29d74eb72beed1cd59e1346cd9a11ab Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:17:43 +0000 Subject: [PATCH 050/117] [VM] fix typo --- pkg/vm/vm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 1f9fa66df..e207a7ed6 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -30,7 +30,7 @@ func NewVM(script []byte) *VM { return v } -// Run loops over the current context by continuously steppping. +// Run loops over the current context by continuously stepping. // Run breaks; once step returns an error or any state that is not NONE func (v *VM) Run() (Vmstate, error) { for { From 9951f040993a7702c99d771bdfc71a3b6e8d0858 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:33:07 +0000 Subject: [PATCH 051/117] [Stall] Change seconds to milliseconds in test --- pkg/peer/stall/stall_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go index b86412b2a..4d5494e12 100644 --- a/pkg/peer/stall/stall_test.go +++ b/pkg/peer/stall/stall_test.go @@ -12,8 +12,8 @@ import ( func TestAddRemoveMessage(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) @@ -51,15 +51,15 @@ loop: } func TestDeadlineWorks(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) mp := mockPeer{online: true, detector: d, lock: new(sync.RWMutex)} go mp.loop() d.AddMessage(command.GetAddr) - time.Sleep(responseTime + 1*time.Second) + time.Sleep(responseTime + 1*time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() @@ -70,12 +70,12 @@ func TestDeadlineWorks(t *testing.T) { mp.lock.RUnlock() } func TestDeadlineShouldNotBeEmpty(t *testing.T) { - responseTime := 10 * time.Second - tickerInterval := 1 * time.Second + responseTime := 10 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) - time.Sleep(1 * time.Second) + time.Sleep(1 * time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() From c1b6738bdb17602633ab098e89c42978fee932c9 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Mon, 18 Mar 2019 21:40:21 +0000 Subject: [PATCH 052/117] VM: Add basic vm (#166) * VM:Add abstract stack item * VM: Add stackItems; Array, Boolean, Int and ByteArray * VM: Add tests for stack item * VM: first pass at Random Access Stack object * VM: Add Sub, Mul, Mod LSH, RSH * VM: moved test helper functions into separate file * VM: removed helper functions from stack_test.go * Add conversions for bytearray and Int stack items * Add instructions file for vm * - Add guide to stack readme - Add testReadInt64 * Add Builder * Refactor Int, Boolean, ByteArray conversion * Add Context stack Item * Add Invocation stack - convenience RAS * rename testhelper to test_helper * Move opcode file * - Add `Add` OpCode - Add Opcode Function map * - Add test for math `Add` opcode - basic opcode execution * Add popTwoIntegers convenience func * Add `SUB` Opcode * Export Context Read methods - Return errors where failable * - Add `Op` to handleOP func signature - Add PushNBytes OPcode * remove error on NewBoolean - Expose underlying with Getter on Boolean StackItem - Add Equals method for ByteArray * Make Next() method on Context failable, refactor peekContext and Peek * Add ExecuteOp, Step and Run methods on the VM * Add Equal Opcode * Add THROWIFNOT Opcode * Add RET Opcode * Refactor PushNBytes Opcode * refactor Add, Sub to return VMSTATE add popTwoByteArrays helper function * Add basic tests for vm * clarify vm states * Add astack * [VM] Pass ResultStack to the opcode handlers * [VM] refactor handlers to have rstack as argument * [Stack] - Change RemoveCurrentContext for PopCurrentContext - Add CopTo method to stack * [VM] Add Result stack Len check in simple run test * [VM] fix typo * [Peer/Stall] Change seconds to milliseconds in test --- pkg/peer/stall/stall_test.go | 16 +-- pkg/vm/stack/Int.go | 94 +++++++++++++++++ pkg/vm/stack/Readme.md | 24 +++++ pkg/vm/stack/array.go | 13 +++ pkg/vm/stack/boolean.go | 26 +++++ pkg/vm/stack/builder.go | 177 ++++++++++++++++++++++++++++++++ pkg/vm/stack/bytearray.go | 71 +++++++++++++ pkg/vm/stack/context.go | 152 +++++++++++++++++++++++++++ pkg/vm/stack/instruction.go | 133 ++++++++++++++++++++++++ pkg/vm/stack/int_test.go | 72 +++++++++++++ pkg/vm/stack/invocationstack.go | 61 +++++++++++ pkg/vm/stack/stack.go | 150 +++++++++++++++++++++++++++ pkg/vm/stack/stack_test.go | 161 +++++++++++++++++++++++++++++ pkg/vm/stack/stackitem.go | 49 +++++++++ pkg/vm/stack/stackitem_test.go | 68 ++++++++++++ pkg/vm/stack/test_helper.go | 44 ++++++++ pkg/vm/state.go | 20 ++++ pkg/vm/vm.go | 72 +++++++++++++ pkg/vm/vm_ops.go | 21 ++++ pkg/vm/vm_ops_bitwise.go | 17 +++ pkg/vm/vm_ops_exceptions.go | 33 ++++++ pkg/vm/vm_ops_flow.go | 27 +++++ pkg/vm/vm_ops_maths.go | 70 +++++++++++++ pkg/vm/vm_ops_maths_test.go | 69 +++++++++++++ pkg/vm/vm_ops_stackmani.go | 19 ++++ pkg/vm/vm_test.go | 119 +++++++++++++++++++++ 26 files changed, 1770 insertions(+), 8 deletions(-) create mode 100644 pkg/vm/stack/Int.go create mode 100644 pkg/vm/stack/Readme.md create mode 100644 pkg/vm/stack/array.go create mode 100644 pkg/vm/stack/boolean.go create mode 100644 pkg/vm/stack/builder.go create mode 100644 pkg/vm/stack/bytearray.go create mode 100644 pkg/vm/stack/context.go create mode 100644 pkg/vm/stack/instruction.go create mode 100644 pkg/vm/stack/int_test.go create mode 100644 pkg/vm/stack/invocationstack.go create mode 100644 pkg/vm/stack/stack.go create mode 100644 pkg/vm/stack/stack_test.go create mode 100644 pkg/vm/stack/stackitem.go create mode 100644 pkg/vm/stack/stackitem_test.go create mode 100644 pkg/vm/stack/test_helper.go create mode 100644 pkg/vm/state.go create mode 100644 pkg/vm/vm.go create mode 100644 pkg/vm/vm_ops.go create mode 100644 pkg/vm/vm_ops_bitwise.go create mode 100644 pkg/vm/vm_ops_exceptions.go create mode 100644 pkg/vm/vm_ops_flow.go create mode 100644 pkg/vm/vm_ops_maths.go create mode 100644 pkg/vm/vm_ops_maths_test.go create mode 100644 pkg/vm/vm_ops_stackmani.go create mode 100644 pkg/vm/vm_test.go diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go index b86412b2a..4d5494e12 100644 --- a/pkg/peer/stall/stall_test.go +++ b/pkg/peer/stall/stall_test.go @@ -12,8 +12,8 @@ import ( func TestAddRemoveMessage(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) @@ -51,15 +51,15 @@ loop: } func TestDeadlineWorks(t *testing.T) { - responseTime := 2 * time.Second - tickerInterval := 1 * time.Second + responseTime := 2 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) mp := mockPeer{online: true, detector: d, lock: new(sync.RWMutex)} go mp.loop() d.AddMessage(command.GetAddr) - time.Sleep(responseTime + 1*time.Second) + time.Sleep(responseTime + 1*time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() @@ -70,12 +70,12 @@ func TestDeadlineWorks(t *testing.T) { mp.lock.RUnlock() } func TestDeadlineShouldNotBeEmpty(t *testing.T) { - responseTime := 10 * time.Second - tickerInterval := 1 * time.Second + responseTime := 10 * time.Millisecond + tickerInterval := 1 * time.Millisecond d := NewDetector(responseTime, tickerInterval) d.AddMessage(command.GetAddr) - time.Sleep(1 * time.Second) + time.Sleep(1 * time.Millisecond) k := make(map[command.Type]time.Time) d.lock.RLock() diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go new file mode 100644 index 000000000..ac9a4cdba --- /dev/null +++ b/pkg/vm/stack/Int.go @@ -0,0 +1,94 @@ +package stack + +import "math/big" + +// Int represents an integer on the stack +type Int struct { + *abstractItem + val *big.Int +} + +// NewInt will convert a big integer into +// a StackInteger +func NewInt(val *big.Int) (*Int, error) { + return &Int{ + abstractItem: &abstractItem{}, + val: val, + }, nil +} + +// Equal will check if two integers hold equal value +func (i *Int) Equal(s *Int) bool { + if i.val.Cmp(s.val) != 0 { + return false + } + return true +} + +// Add will add two stackIntegers together +func (i *Int) Add(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Add(i.val, s.val), + }, nil +} + +// Sub will subtract two stackIntegers together +func (i *Int) Sub(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Sub(i.val, s.val), + }, nil +} + +// Mul will multiply two stackIntegers together +func (i *Int) Mul(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mul(i.val, s.val), + }, nil +} + +// Mod will take the mod of two stackIntegers together +func (i *Int) Mod(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Mod(i.val, s.val), + }, nil +} + +// Rsh will shift the integer b to the right by `n` bits +func (i *Int) Rsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Rsh(i.val, uint(n.val.Int64())), + }, nil +} + +// Lsh will shift the integer b to the left by `n` bits +func (i *Int) Lsh(n *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Lsh(i.val, uint(n.val.Int64())), + }, nil +} + +// Integer will overwrite the default implementation +// to allow go to cast this item as an integer. +func (i *Int) Integer() (*Int, error) { + return i, nil +} + +// ByteArray override the default ByteArray method +// to convert a Integer into a byte Array +func (i *Int) ByteArray() (*ByteArray, error) { + b := i.val.Bytes() + dest := reverse(b) + return NewByteArray(dest), nil +} + +//Boolean override the default Boolean method +// to convert an Integer into a Boolean StackItem +func (i *Int) Boolean() (*Boolean, error) { + boolean := (i.val.Int64() != 0) + return NewBoolean(boolean), nil +} + +//Value returns the underlying big.Int +func (i *Int) Value() *big.Int { + return i.val +} diff --git a/pkg/vm/stack/Readme.md b/pkg/vm/stack/Readme.md new file mode 100644 index 000000000..2e1b6ba78 --- /dev/null +++ b/pkg/vm/stack/Readme.md @@ -0,0 +1,24 @@ +## VM - Stack + +- How do i implement a new StackItem? + +Answer: You add it's type to the Item interface, then you implement the default return method on the abstract stack item, this should be the behaviour of the stack item, if it is not the new type. Then you embed the abstract item in the new struct and override the method. + +For example, If I wanted to add a new type called `HashMap` + +type Item interface{ + HashMap()(*HashMap, error) +} + +func (a *abstractItem) HashMap() (*HashMap, error) { + return nil, errors.New(This stack item is not a hashmap) +} + +type HashMap struct { + *abstractItem + // Variables needed for hashmap +} + +func (h *HashMap) HashMap()(*HashMap, error) { + // logic to override default behaviour +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go new file mode 100644 index 000000000..96fe876a4 --- /dev/null +++ b/pkg/vm/stack/array.go @@ -0,0 +1,13 @@ +package stack + +// Array represents an Array of stackItems on the stack +type Array struct { + *abstractItem + val []Item +} + +// Array overrides the default implementation +// by the abstractItem, returning an Array struct +func (a *Array) Array() (*Array, error) { + return a, nil +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go new file mode 100644 index 000000000..66e3647e5 --- /dev/null +++ b/pkg/vm/stack/boolean.go @@ -0,0 +1,26 @@ +package stack + +// Boolean represents a boolean value on the stack +type Boolean struct { + *abstractItem + val bool +} + +//NewBoolean returns a new boolean stack item +func NewBoolean(val bool) *Boolean { + return &Boolean{ + &abstractItem{}, + val, + } +} + +// Boolean overrides the default implementation +// by the abstractItem, returning a Boolean struct +func (b *Boolean) Boolean() (*Boolean, error) { + return b, nil +} + +// Value returns the underlying boolean value +func (b *Boolean) Value() bool { + return b.val +} diff --git a/pkg/vm/stack/builder.go b/pkg/vm/stack/builder.go new file mode 100644 index 000000000..e50587aff --- /dev/null +++ b/pkg/vm/stack/builder.go @@ -0,0 +1,177 @@ +package stack + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Builder follows the builder pattern and will be used to build scripts +type Builder struct { + w *bytes.Buffer + err error +} + +// NewBuilder returns a new builder object +func NewBuilder() *Builder { + return &Builder{ + w: &bytes.Buffer{}, + err: nil, + } +} + +// Bytes returns the byte representation of the built buffer +func (br *Builder) Bytes() []byte { + return br.w.Bytes() +} + +// Emit a VM Opcode with data to the given buffer. +func (br *Builder) Emit(op Instruction, b []byte) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + _, br.err = br.w.Write(b) + return br +} + +// EmitOpcode emits a single VM Opcode the given buffer. +func (br *Builder) EmitOpcode(op Instruction) *Builder { + if br.err != nil { + return br + } + br.err = br.w.WriteByte(byte(op)) + return br +} + +// EmitBool emits a bool type the given buffer. +func (br *Builder) EmitBool(ok bool) *Builder { + if br.err != nil { + return br + } + op := PUSHT + if !ok { + op = PUSHF + } + return br.EmitOpcode(op) +} + +// EmitInt emits a int type to the given buffer. +func (br *Builder) EmitInt(i int64) *Builder { + if br.err != nil { + return br + } + if i == -1 { + return br.EmitOpcode(PUSHM1) + } + if i == 0 { + return br.EmitOpcode(PUSHF) + } + if i > 0 && i < 16 { + val := Instruction(int(PUSH1) - 1 + int(i)) + return br.EmitOpcode(val) + } + + bInt := big.NewInt(i) + val := reverse(bInt.Bytes()) + return br.EmitBytes(val) +} + +// EmitString emits a string to the given buffer. +func (br *Builder) EmitString(s string) *Builder { + if br.err != nil { + return br + } + return br.EmitBytes([]byte(s)) +} + +// EmitBytes emits a byte array to the given buffer. +func (br *Builder) EmitBytes(b []byte) *Builder { + if br.err != nil { + return br + } + var ( + n = len(b) + ) + + if n <= int(PUSHBYTES75) { + return br.Emit(Instruction(n), b) + } else if n < 0x100 { + br.Emit(PUSHDATA1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + br.Emit(PUSHDATA2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + br.Emit(PUSHDATA4, buf) + } + _, br.err = br.w.Write(b) + return br +} + +// EmitSyscall emits the syscall API to the given buffer. +// Syscall API string cannot be 0. +func (br *Builder) EmitSyscall(api string) *Builder { + if br.err != nil { + return br + } + if len(api) == 0 { + br.err = errors.New("syscall api cannot be of length 0") + } + buf := make([]byte, len(api)+1) + buf[0] = byte(len(api)) + copy(buf[1:], []byte(api)) + return br.Emit(SYSCALL, buf) +} + +// EmitCall emits a call Opcode with label to the given buffer. +func (br *Builder) EmitCall(op Instruction, label int16) *Builder { + return br.EmitJmp(op, label) +} + +// EmitJmp emits a jump Opcode along with label to the given buffer. +func (br *Builder) EmitJmp(op Instruction, label int16) *Builder { + if !isOpcodeJmp(op) { + br.err = fmt.Errorf("opcode %d is not a jump or call type", op) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return br.Emit(op, buf) +} + +// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be +// emitted instead. +func (br *Builder) EmitAppCall(scriptHash util.Uint160, tailCall bool) *Builder { + op := APPCALL + if tailCall { + op = TAILCALL + } + return br.Emit(op, scriptHash.Bytes()) +} + +// EmitAppCallWithOperationAndData emits an appcall with the given operation and data. +func (br *Builder) EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) *Builder { + br.EmitBytes(data) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +// EmitAppCallWithOperation emits an appcall with the given operation. +func (br *Builder) EmitAppCallWithOperation(scriptHash util.Uint160, operation string) *Builder { + br.EmitBool(false) + br.EmitString(operation) + return br.EmitAppCall(scriptHash, false) +} + +func isOpcodeJmp(op Instruction) bool { + if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL { + return true + } + return false +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go new file mode 100644 index 000000000..7d1c3c818 --- /dev/null +++ b/pkg/vm/stack/bytearray.go @@ -0,0 +1,71 @@ +package stack + +import ( + "bytes" + "errors" + "math/big" + "strconv" +) + +// ByteArray represents a slice of bytes on the stack +type ByteArray struct { + *abstractItem + val []byte +} + +//NewByteArray returns a ByteArray stack item +// given a byte slice +func NewByteArray(val []byte) *ByteArray { + return &ByteArray{ + &abstractItem{}, + val, + } +} + +//ByteArray overrides the default abstractItem Bytes array method +func (ba *ByteArray) ByteArray() (*ByteArray, error) { + return ba, nil +} + +//Equals returns true, if two bytearrays are equal +func (ba *ByteArray) Equals(other *ByteArray) *Boolean { + // If either are nil, return false + if ba == nil || other == nil { + return NewBoolean(false) + } + return NewBoolean(bytes.Equal(ba.val, other.val)) +} + +//Integer overrides the default Integer method to convert an +// ByteArray Into an integer +func (ba *ByteArray) Integer() (*Int, error) { + dest := reverse(ba.val) + integerVal := new(big.Int).SetBytes(dest) + return NewInt(integerVal) + +} + +// Boolean will convert a byte array into a boolean stack item +func (ba *ByteArray) Boolean() (*Boolean, error) { + boolean, err := strconv.ParseBool(string(ba.val)) + if err != nil { + return nil, errors.New("cannot convert byte array to a boolean") + } + return NewBoolean(boolean), nil +} + +// XXX: move this into a pkg/util/slice folder +// Go mod not working +func reverse(b []byte) []byte { + if len(b) < 2 { + return b + } + + dest := make([]byte, len(b)) + + for i, j := 0, len(b)-1; i < j+1; i, j = i+1, j-1 { + dest[i], dest[j] = b[j], b[i] + } + + return dest +} diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go new file mode 100644 index 000000000..d381d74cb --- /dev/null +++ b/pkg/vm/stack/context.go @@ -0,0 +1,152 @@ +package stack + +import ( + "encoding/binary" + "errors" +) + +// Context represent the current execution context of the VM. +// context will be treated as stack item and placed onto the invocation stack +type Context struct { + *abstractItem + + // Instruction pointer. + ip int + + // The raw program script. + prog []byte + + // Breakpoints + breakPoints []int + + // Evaluation Stack + Estack RandomAccess + + // Alternative Stack + Astack RandomAccess +} + +// NewContext return a new Context object. +func NewContext(b []byte) *Context { + return &Context{ + abstractItem: &abstractItem{}, + ip: -1, + prog: b, + breakPoints: []int{}, + } +} + +// Context overrides the default implementation +// to return a context item +func (c *Context) Context() (*Context, error) { + return c, nil +} + +// Next return the next instruction to execute. +func (c *Context) Next() (Instruction, error) { + c.ip++ + if c.ip >= len(c.prog) { + return RET, errors.New("program pointer is more than the length of program. Returning RET OPCODE") + } + return Instruction(c.prog[c.ip]), nil +} + +// IP returns the absolute instruction without taking 0 into account. +// If that program starts the ip = 0 but IP() will return 1, cause its +// the first instruction. +func (c *Context) IP() int { + return c.ip + 1 +} + +// LenInstr returns the number of instructions loaded. +func (c *Context) LenInstr() int { + return len(c.prog) +} + +// CurrInstr returns the current instruction and opcode. +func (c *Context) CurrInstr() (int, Instruction) { + if c.ip < 0 { + return c.ip, Instruction(0x00) + } + return c.ip, Instruction(c.prog[c.ip]) +} + +// Copy returns an new exact copy of c. +func (c *Context) Copy() *Context { + return &Context{ + ip: c.ip, + prog: c.prog, + breakPoints: c.breakPoints, + } +} + +// Program returns the loaded program. +func (c *Context) Program() []byte { + return c.prog +} + +func (c *Context) atBreakPoint() bool { + for _, n := range c.breakPoints { + if n == c.ip { + return true + } + } + return false +} + +func (c *Context) String() string { + return "execution context" +} + +// ReadUint32 reads a uint32 from the script +func (c *Context) ReadUint32() uint32 { + start, end := c.IP(), c.IP()+4 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint32(c.prog[start:end]) + c.ip += 4 + return val +} + +// ReadUint16 reads a uint16 from the script +func (c *Context) ReadUint16() uint16 { + start, end := c.IP(), c.IP()+2 + if end > len(c.prog) { + return 0 + } + val := binary.LittleEndian.Uint16(c.prog[start:end]) + c.ip += 2 + return val +} + +// ReadByte reads one byte from the script +func (c *Context) ReadByte() (byte, error) { + byt, err := c.ReadBytes(1) + if err != nil { + return 0, err + } + + return byt[0], nil +} + +// ReadBytes will read n bytes from the context +func (c *Context) ReadBytes(n int) ([]byte, error) { + start, end := c.IP(), c.IP()+n + if end > len(c.prog) { + return nil, errors.New("Too many bytes to read, pointer goes past end of program") + } + + out := make([]byte, n) + copy(out, c.prog[start:end]) + c.ip += n + return out, nil +} + +func (c *Context) readVarBytes() ([]byte, error) { + n, err := c.ReadByte() + if err != nil { + return nil, err + } + return c.ReadBytes(int(n)) +} diff --git a/pkg/vm/stack/instruction.go b/pkg/vm/stack/instruction.go new file mode 100644 index 000000000..1317a1e3e --- /dev/null +++ b/pkg/vm/stack/instruction.go @@ -0,0 +1,133 @@ +package stack + +// Instruction represents a operation code in the neovm +type Instruction byte + +// Viable list of supported instruction constants. +const ( + // Constants + PUSH0 Instruction = 0x00 + PUSHF Instruction = PUSH0 + PUSHBYTES1 Instruction = 0x01 + PUSHBYTES75 Instruction = 0x4B + PUSHDATA1 Instruction = 0x4C + PUSHDATA2 Instruction = 0x4D + PUSHDATA4 Instruction = 0x4E + PUSHM1 Instruction = 0x4F + PUSH1 Instruction = 0x51 + PUSHT Instruction = PUSH1 + PUSH2 Instruction = 0x52 + PUSH3 Instruction = 0x53 + PUSH4 Instruction = 0x54 + PUSH5 Instruction = 0x55 + PUSH6 Instruction = 0x56 + PUSH7 Instruction = 0x57 + PUSH8 Instruction = 0x58 + PUSH9 Instruction = 0x59 + PUSH10 Instruction = 0x5A + PUSH11 Instruction = 0x5B + PUSH12 Instruction = 0x5C + PUSH13 Instruction = 0x5D + PUSH14 Instruction = 0x5E + PUSH15 Instruction = 0x5F + PUSH16 Instruction = 0x60 + + // Flow control + NOP Instruction = 0x61 + JMP Instruction = 0x62 + JMPIF Instruction = 0x63 + JMPIFNOT Instruction = 0x64 + CALL Instruction = 0x65 + RET Instruction = 0x66 + APPCALL Instruction = 0x67 + SYSCALL Instruction = 0x68 + TAILCALL Instruction = 0x69 + + // Stack + DUPFROMALTSTACK Instruction = 0x6A + TOALTSTACK Instruction = 0x6B + FROMALTSTACK Instruction = 0x6C + XDROP Instruction = 0x6D + XSWAP Instruction = 0x72 + XTUCK Instruction = 0x73 + DEPTH Instruction = 0x74 + DROP Instruction = 0x75 + DUP Instruction = 0x76 + NIP Instruction = 0x77 + OVER Instruction = 0x78 + PICK Instruction = 0x79 + ROLL Instruction = 0x7A + ROT Instruction = 0x7B + SWAP Instruction = 0x7C + TUCK Instruction = 0x7D + + // Splice + CAT Instruction = 0x7E + SUBSTR Instruction = 0x7F + LEFT Instruction = 0x80 + RIGHT Instruction = 0x81 + SIZE Instruction = 0x82 + + // Bitwise logic + INVERT Instruction = 0x83 + AND Instruction = 0x84 + OR Instruction = 0x85 + XOR Instruction = 0x86 + EQUAL Instruction = 0x87 + + // Arithmetic + INC Instruction = 0x8B + DEC Instruction = 0x8C + SIGN Instruction = 0x8D + NEGATE Instruction = 0x8F + ABS Instruction = 0x90 + NOT Instruction = 0x91 + NZ Instruction = 0x92 + ADD Instruction = 0x93 + SUB Instruction = 0x94 + MUL Instruction = 0x95 + DIV Instruction = 0x96 + MOD Instruction = 0x97 + SHL Instruction = 0x98 + SHR Instruction = 0x99 + BOOLAND Instruction = 0x9A + BOOLOR Instruction = 0x9B + NUMEQUAL Instruction = 0x9C + NUMNOTEQUAL Instruction = 0x9E + LT Instruction = 0x9F + GT Instruction = 0xA0 + LTE Instruction = 0xA1 + GTE Instruction = 0xA2 + MIN Instruction = 0xA3 + MAX Instruction = 0xA4 + WITHIN Instruction = 0xA5 + + // Crypto + SHA1 Instruction = 0xA7 + SHA256 Instruction = 0xA8 + HASH160 Instruction = 0xA9 + HASH256 Instruction = 0xAA + CHECKSIG Instruction = 0xAC + CHECKMULTISIG Instruction = 0xAE + + // Array + ARRAYSIZE Instruction = 0xC0 + PACK Instruction = 0xC1 + UNPACK Instruction = 0xC2 + PICKITEM Instruction = 0xC3 + SETITEM Instruction = 0xC4 + NEWARRAY Instruction = 0xC5 + NEWSTRUCT Instruction = 0xC6 + APPEND Instruction = 0xC8 + REVERSE Instruction = 0xC9 + REMOVE Instruction = 0xCA + + // Exceptions + THROW Instruction = 0xF0 + THROWIFNOT Instruction = 0xF1 +) + +// Value returns the byte-value of the opcode. +func (i Instruction) Value() byte { + return byte(i) +} diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go new file mode 100644 index 000000000..25d360183 --- /dev/null +++ b/pkg/vm/stack/int_test.go @@ -0,0 +1,72 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 30) + c, err := a.Add(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestSub(t *testing.T) { + a := testMakeStackInt(t, 30) + b := testMakeStackInt(t, 200) + expected := testMakeStackInt(t, 170) + c, err := b.Sub(a) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMul(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 200) + c, err := a.Mul(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestMod(t *testing.T) { + a := testMakeStackInt(t, 10) + b := testMakeStackInt(t, 20) + expected := testMakeStackInt(t, 10) + c, err := a.Mod(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} +func TestLsh(t *testing.T) { + a := testMakeStackInt(t, 23) + b := testMakeStackInt(t, 8) + expected := testMakeStackInt(t, 5888) + c, err := a.Lsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} + +func TestRsh(t *testing.T) { + a := testMakeStackInt(t, 128) + b := testMakeStackInt(t, 3) + expected := testMakeStackInt(t, 16) + c, err := a.Rsh(b) + assert.Nil(t, err) + assert.Equal(t, true, expected.Equal(c)) +} + +func TestByteArrConversion(t *testing.T) { + + var num int64 = 100000 + + a := testMakeStackInt(t, num) + ba, err := a.ByteArray() + assert.Nil(t, err) + + have, err := ba.Integer() + assert.Nil(t, err) + + assert.Equal(t, num, have.val.Int64()) + +} diff --git a/pkg/vm/stack/invocationstack.go b/pkg/vm/stack/invocationstack.go new file mode 100644 index 000000000..49f058f62 --- /dev/null +++ b/pkg/vm/stack/invocationstack.go @@ -0,0 +1,61 @@ +package stack + +import "errors" + +// Invocation embeds a Random Access stack +// Providing helper methods for the context object +type Invocation struct{ RandomAccess } + +//NewInvocation will return a new +// Invocation stack +func NewInvocation() *Invocation { + return &Invocation{ + RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + }, + } +} + +func (i *Invocation) peekContext(n uint16) (*Context, error) { + item, err := i.Peek(n) + if err != nil { + return nil, err + } + return item.Context() +} + +// CurrentContext returns the current context on the invocation stack +func (i *Invocation) CurrentContext() (*Context, error) { + return i.peekContext(0) +} + +// PopCurrentContext Pops a context item from the top of the stack +func (i *Invocation) PopCurrentContext() (*Context, error) { + item, err := i.Pop() + if err != nil { + return nil, err + } + ctx, err := item.Context() + if err != nil { + return nil, err + } + return ctx, err +} + +// CallingContext will return the cntext item +// that will be called next. +func (i *Invocation) CallingContext() (*Context, error) { + if i.Len() < 1 { + return nil, errors.New("Length of invocation stack is < 1, no calling context") + } + return i.peekContext(1) +} + +// EntryContext will return the context item that +// started the program +func (i *Invocation) EntryContext() (*Context, error) { + + // firstItemIndex refers to the first item that was popped on the stack + firstItemIndex := uint16(i.Len() - 1) // N.B. if this overflows because len is zero, then an error will be returned + return i.peekContext(firstItemIndex) +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go new file mode 100644 index 000000000..8d1ac5b78 --- /dev/null +++ b/pkg/vm/stack/stack.go @@ -0,0 +1,150 @@ +package stack + +import ( + "errors" + "fmt" +) + +const ( + // StackAverageSize is used to set the capacity of the stack + // setting this number too low, will cause extra allocations + StackAverageSize = 20 +) + +// RandomAccess represents a Random Access Stack +type RandomAccess struct { + vals []Item +} + +// New will return a new random access stack +func New() *RandomAccess { + return &RandomAccess{ + vals: make([]Item, 0, StackAverageSize), + } +} + +// Items will return all items in the stack +func (ras *RandomAccess) items() []Item { + return ras.vals +} + +//Len will return the length of the stack +func (ras *RandomAccess) Len() int { + if ras.vals == nil { + return -1 + } + return len(ras.vals) +} + +// Clear will remove all items in the stack +func (ras *RandomAccess) Clear() { + ras.vals = make([]Item, 0, StackAverageSize) +} + +// Pop will remove the last stack item that was added +func (ras *RandomAccess) Pop() (Item, error) { + if len(ras.vals) == 0 { + return nil, errors.New("There are no items on the stack to pop") + } + if ras.vals == nil { + return nil, errors.New("Cannot pop from a nil stack") + } + + l := len(ras.vals) + item := ras.vals[l-1] + ras.vals = ras.vals[:l-1] + + return item, nil +} + +// Push will put a stack item onto the top of the stack +func (ras *RandomAccess) Push(item Item) *RandomAccess { + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + ras.vals = append(ras.vals, item) + + return ras +} + +// Insert will push a stackItem onto the stack at position `n` +// Note; index 0 is the top of the stack, which is the end of slice +func (ras *RandomAccess) Insert(n uint16, item Item) (*RandomAccess, error) { + + if n == 0 { + return ras.Push(item), nil + } + + if ras.vals == nil { + ras.vals = make([]Item, 0, StackAverageSize) + } + + // Check that we are not inserting out of the bounds + stackSize := uint16(len(ras.vals)) + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to insert at index %d when length of stack is %d", n, len(ras.vals)) + } + + index := stackSize - n + + ras.vals = append(ras.vals, item) + copy(ras.vals[index:], ras.vals[index-1:]) + ras.vals[index] = item + + return ras, nil +} + +// Peek will check an element at a given index +// Note: 0 is the top of the stack, which is the end of the slice +func (ras *RandomAccess) Peek(n uint16) (Item, error) { + + stackSize := uint16(len(ras.vals)) + + if n == 0 { + index := stackSize - 1 + return ras.vals[index], nil + } + + if ras.Len() < 1 { + return nil, fmt.Errorf("cannot peak at a stack with no item, length of stack is %d", ras.Len()) + } + + // Check that we are not peeking out of the bounds + if n > stackSize-1 { + return nil, fmt.Errorf("Tried to peek at index %d when length of stack is %d", n, len(ras.vals)) + } + index := stackSize - n - 1 + + return ras.vals[index], nil +} + +// CopyTo will copy all of the stack items from `ras` into the stack that is passed as an argument +// XXX: once maxstacksize is implemented, we will return error if size goes over +// There will also be additional checks needed once stack isolation is added +func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { + stack.vals = append(stack.vals, ras.vals...) + return nil +} + +// Convenience Functions + +// PopInt will remove the last stack item that was added +// And cast it to an integer +func (ras *RandomAccess) PopInt() (*Int, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.Integer() +} + +// PopByteArray will remove the last stack item that was added +// And cast it to an ByteArray +func (ras *RandomAccess) PopByteArray() (*ByteArray, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.ByteArray() +} diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go new file mode 100644 index 000000000..246e983f4 --- /dev/null +++ b/pkg/vm/stack/stack_test.go @@ -0,0 +1,161 @@ +package stack + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStackPushPop(t *testing.T) { + // Create two stack Integers + a, err := NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + // Create a new stack + testStack := New() + + // Push to stack + testStack.Push(a).Push(b) + + // There should only be two values on the stack + assert.Equal(t, 2, testStack.Len()) + + // Pop first element and it should be equal to b + stackElement, err := testStack.Pop() + if err != nil { + t.Fail() + } + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(b)) + + // Pop second element and it should be equal to a + stackElement, err = testStack.Pop() + if err != nil { + t.Fail() + } + item, err = stackElement.Integer() + if err != nil { + t.Fail() + } + assert.Equal(t, true, item.Equal(a)) + + // We should get an error as there are nomore items left to pop + stackElement, err = testStack.Pop() + assert.NotNil(t, err) + +} + +// For this test to pass, we should get an error when popping from a nil stack +// and we should initialise and push an element if pushing to an empty stack +func TestPushPopNil(t *testing.T) { + + // stack is nil when initialised without New constructor + testStack := RandomAccess{} + + // Popping from nil stack + // - should give an error + // - element returned should be nil + stackElement, err := testStack.Pop() + assert.NotNil(t, err) + assert.Nil(t, stackElement) + + // stack should still be nil after failing to pop + assert.Nil(t, testStack.vals) + + // create a random test stack item + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + + // push random item to stack + testStack.Push(a) + + // push should initialise the stack and put one element on the stack + assert.Equal(t, 1, testStack.Len()) +} + +// Test passes if we can peek and modify an item +//without modifying the value on the stack +func TestStackPeekMutability(t *testing.T) { + + testStack := New() + + a, err := NewInt(big.NewInt(2)) + assert.Nil(t, err) + b, err := NewInt(big.NewInt(3)) + assert.Nil(t, err) + + testStack.Push(a).Push(b) + + peekedItem := testPeakInteger(t, testStack, 0) + assert.Equal(t, true, peekedItem.Equal(b)) + + // Check that by modifying the peeked value, + // we did not modify the item on the stack + peekedItem = a + peekedItem.val = big.NewInt(0) + + // Pop item from stack and check it is still the same + poppedItem := testPopInteger(t, testStack) + assert.Equal(t, true, poppedItem.Equal(b)) +} +func TestStackPeek(t *testing.T) { + + testStack := New() + + values := []int64{23, 45, 67, 89, 12, 344} + for _, val := range values { + a := testMakeStackInt(t, val) + testStack.Push(a) + } + + // i starts at 0, j starts at len(values)-1 + for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 { + + peekedItem := testPeakInteger(t, testStack, uint16(i)) + a := testMakeStackInt(t, values[j]) + + fmt.Printf("%#v\n", peekedItem.val.Int64()) + + assert.Equal(t, true, a.Equal(peekedItem)) + + } + +} + +func TestStackInsert(t *testing.T) { + + testStack := New() + + a := testMakeStackInt(t, 2) + b := testMakeStackInt(t, 4) + c := testMakeStackInt(t, 6) + + // insert on an empty stack should put element on top + _, err := testStack.Insert(0, a) + assert.Equal(t, err, nil) + _, err = testStack.Insert(0, b) + assert.Equal(t, err, nil) + _, err = testStack.Insert(1, c) + assert.Equal(t, err, nil) + + // Order should be [a,c,b] + pop1 := testPopInteger(t, testStack) + pop2 := testPopInteger(t, testStack) + pop3 := testPopInteger(t, testStack) + + assert.Equal(t, true, pop1.Equal(b)) + assert.Equal(t, true, pop2.Equal(c)) + assert.Equal(t, true, pop3.Equal(a)) + +} diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go new file mode 100644 index 000000000..beed13363 --- /dev/null +++ b/pkg/vm/stack/stackitem.go @@ -0,0 +1,49 @@ +package stack + +import ( + "errors" +) + +//Item is an interface which represents object that can be placed on the stack +type Item interface { + Integer() (*Int, error) + Boolean() (*Boolean, error) + ByteArray() (*ByteArray, error) + Array() (*Array, error) + Context() (*Context, error) +} + +// Represents an `abstract` stack item +// which will hold default values for stack items +// this is intended to be embedded into types that you will use on the stack +type abstractItem struct{} + +// Integer is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Integer() (*Int, error) { + return nil, errors.New("This stack item is not an Integer") +} + +// Boolean is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Boolean() (*Boolean, error) { + return nil, errors.New("This stack item is not a Boolean") +} + +// ByteArray is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) ByteArray() (*ByteArray, error) { + return nil, errors.New("This stack item is not a byte array") +} + +// Array is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Array() (*Array, error) { + return nil, errors.New("This stack item is not an array") +} + +// Context is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Context() (*Context, error) { + return nil, errors.New("This stack item is not of type context") +} diff --git a/pkg/vm/stack/stackitem_test.go b/pkg/vm/stack/stackitem_test.go new file mode 100644 index 000000000..741d6f530 --- /dev/null +++ b/pkg/vm/stack/stackitem_test.go @@ -0,0 +1,68 @@ +package stack + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// A simple test to ensure that by embedding the abstract interface +// we immediately become a stack item, with the default values set to nil +func TestInterfaceEmbedding(t *testing.T) { + + // Create an anonymous struct that embeds the abstractItem + a := struct { + *abstractItem + }{ + &abstractItem{}, + } + + // Since interface checking can be done at compile time. + // If he abstractItem did not implement all methods of our interface `Item` + // Then any struct which embeds it, will also not implement the Item interface. + // This test would then give errors, at compile time. + var Items []Item + Items = append(Items, a) + + // Default methods should give errors + // Here we just need to test against one of the methods in the interface + for _, element := range Items { + x, err := element.Integer() + assert.Nil(t, x) + assert.NotNil(t, err, nil) + } + +} + +// TestIntCasting is a simple test to test that the Integer method is overwritten +// from the abstractItem +func TestIntMethodOverride(t *testing.T) { + + testValues := []int64{0, 10, 200, 30, 90} + var Items []Item + + // Convert a range of int64s into Stack Integers + // Adding them into an array of StackItems + for _, num := range testValues { + stackInteger, err := NewInt(big.NewInt(num)) + if err != nil { + t.Fail() + } + Items = append(Items, stackInteger) + } + + // For each item, call the Integer method on the interface + // Which should return an integer and no error + // as the stack integer struct overrides that method + for i, element := range Items { + k, err := element.Integer() + if err != nil { + t.Fail() + } + if k.val.Cmp(big.NewInt(testValues[i])) != 0 { + t.Fail() + } + } + +} diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go new file mode 100644 index 000000000..15c6f87de --- /dev/null +++ b/pkg/vm/stack/test_helper.go @@ -0,0 +1,44 @@ +package stack + +import ( + "bytes" + "encoding/binary" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +// helper functions +func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { + stackElement, err := tStack.Peek(n) + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testPopInteger(t *testing.T, tStack *RandomAccess) *Int { + stackElement, err := tStack.Pop() + assert.Nil(t, err) + item, err := stackElement.Integer() + if err != nil { + t.Fail() + } + return item +} + +func testMakeStackInt(t *testing.T, num int64) *Int { + a, err := NewInt(big.NewInt(num)) + assert.Nil(t, err) + return a +} + +func testReadInt64(data []byte) int64 { + var ret int64 + buf := bytes.NewBuffer(data) + binary.Read(buf, binary.LittleEndian, &ret) + return ret +} diff --git a/pkg/vm/state.go b/pkg/vm/state.go new file mode 100644 index 000000000..e6760c7c9 --- /dev/null +++ b/pkg/vm/state.go @@ -0,0 +1,20 @@ +package vm + +//Vmstate represents all possible states that the neo-vm can be in +type Vmstate byte + +// List of possible vm states +const ( + // NONE is the running state of the vm + // NONE signifies that the vm is ready to process an opcode + NONE = 0 + // HALT is a stopped state of the vm + // where the stop was signalled by the program completion + HALT = 1 << 0 + // FAULT is a stopped state of the vm + // where the stop was signalled by an error in the program + FAULT = 1 << 1 + // BREAK is a suspended state for the VM + // were the break was signalled by a breakpoint + BREAK = 1 << 2 +) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go new file mode 100644 index 000000000..e207a7ed6 --- /dev/null +++ b/pkg/vm/vm.go @@ -0,0 +1,72 @@ +package vm + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// VM represents an instance of a Neo Virtual Machine +type VM struct { + // ResultStack contains the results of + // the last evaluation stack before the program terminated + ResultStack stack.RandomAccess + // InvocationStack contains all of the contexts + // loaded into the vm + InvocationStack stack.Invocation + state Vmstate +} + +// NewVM will: +// Set the state of the VM to NONE +// instantiate a script as a new context +// Push the Context to the Invocation stack +func NewVM(script []byte) *VM { + ctx := stack.NewContext(script) + v := &VM{ + state: NONE, + } + v.InvocationStack.Push(ctx) + return v +} + +// Run loops over the current context by continuously stepping. +// Run breaks; once step returns an error or any state that is not NONE +func (v *VM) Run() (Vmstate, error) { + for { + state, err := v.step() + if err != nil || state != NONE { + return state, err + } + } +} + +// step will read `one` opcode from the script in the current context +// Then excute that opcode +func (v *VM) step() (Vmstate, error) { + // Get Current Context + ctx, err := v.InvocationStack.CurrentContext() + if err != nil { + return FAULT, err + } + // Read Opcode from context + op, _ := ctx.Next() // The only error that can occur from this, is if the pointer goes over the pointer + // In the NEO-VM specs, this is ignored and we return the RET opcode + // Execute OpCode + state, err := v.executeOp(stack.Instruction(op), ctx) + if err != nil { + return FAULT, err + } + return state, nil +} + +// ExecuteOp will execute one opcode on a given context. +// If the opcode is not registered, then an unknown opcode error will be returned +func (v *VM) executeOp(op stack.Instruction, ctx *stack.Context) (Vmstate, error) { + //Find function which handles that specific opcode + handleOp, ok := opFunc[op] + if !ok { + return FAULT, fmt.Errorf("unknown opcode entered %v", op) + } + return handleOp(op, ctx, &v.InvocationStack, &v.ResultStack) +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go new file mode 100644 index 000000000..39b796389 --- /dev/null +++ b/pkg/vm/vm_ops.go @@ -0,0 +1,21 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) + +var opFunc = map[stack.Instruction]stackInfo{ + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, +} + +func init() { + for i := int(stack.PUSHBYTES1); i <= int(stack.PUSHBYTES75); i++ { + opFunc[stack.Instruction(i)] = PushNBytes + } +} diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go new file mode 100644 index 000000000..350543fa2 --- /dev/null +++ b/pkg/vm/vm_ops_bitwise.go @@ -0,0 +1,17 @@ +package vm + +import "github.com/CityOfZion/neo-go/pkg/vm/stack" + +// Bitwise logic + +// EQUAL pushes true to the stack +// If the two top items on the stack are equal +func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + itemA, itemB, err := popTwoByteArrays(ctx) + if err != nil { + return FAULT, err + } + ctx.Estack.Push(itemA.Equals(itemB)) + return NONE, nil +} diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go new file mode 100644 index 000000000..dd09cfb60 --- /dev/null +++ b/pkg/vm/vm_ops_exceptions.go @@ -0,0 +1,33 @@ +package vm + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// vm exceptions + +// THROWIFNOT faults if the item on the top of the stack +// does not evaluate to true +// For specific logic on how a number of bytearray is evaluated can be seen +// from the boolean conversion methods on the stack items +func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + // Pop item from top of stack + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + // Convert to a boolean + ok, err := item.Boolean() + if err != nil { + return FAULT, err + } + + // If false, throw + if !ok.Value() { + return FAULT, errors.New("item on top of stack evaluates to false") + } + return NONE, nil +} diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go new file mode 100644 index 000000000..67ca4f825 --- /dev/null +++ b/pkg/vm/vm_ops_flow.go @@ -0,0 +1,27 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Flow control + +// RET Returns from the current context +// Returns HALT if there are nomore context's to run +func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + // Pop current context from the Inovation stack + ctx, err := istack.PopCurrentContext() + if err != nil { + return FAULT, err + } + // If this was the last context, then we copy over the evaluation stack to the resultstack + // As the program is about to terminate, once we remove the context + if istack.Len() == 0 { + + err = ctx.Estack.CopyTo(rstack) + return HALT, err + } + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go new file mode 100644 index 000000000..a15596a30 --- /dev/null +++ b/pkg/vm/vm_ops_maths.go @@ -0,0 +1,70 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Add adds two stack Items together. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be added together +func Add(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Add(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Sub subtracts two stack Items. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be subtracted together +func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandB.Sub(operandA) + if err != nil { + return HALT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} + +func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { + // Pop first stack item and cast as byte array + ba1, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + // Pop second stack item and cast as byte array + ba2, err := ctx.Estack.PopByteArray() + if err != nil { + return nil, nil, err + } + return ba1, ba2, nil +} diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go new file mode 100644 index 000000000..4964e6923 --- /dev/null +++ b/pkg/vm/vm_ops_maths_test.go @@ -0,0 +1,69 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestAddOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(23)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.ADD, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(43), item.Value().Int64()) + +} + +func TestSubOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(30)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(40)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.SUB, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(-10), item.Value().Int64()) + +} diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go new file mode 100644 index 000000000..f5e2ddc24 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani.go @@ -0,0 +1,19 @@ +package vm + +import ( + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// Stack Manipulation Opcodes + +// PushNBytes will Read N Bytes from the script and push it onto the stack +func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + val, err := ctx.ReadBytes(int(op)) + if err != nil { + return FAULT, err + } + ba := stack.NewByteArray(val) + ctx.Estack.Push(ba) + return NONE, nil +} diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go new file mode 100644 index 000000000..adb8b5db3 --- /dev/null +++ b/pkg/vm/vm_test.go @@ -0,0 +1,119 @@ +package vm + +import ( + "fmt" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestPushAdd(t *testing.T) { + builder := stack.NewBuilder() + + // PUSH TWO NUMBER + // ADD THEM TOGETHER + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Execute first OPCODE + // Should be PUSH(20) + state, err := vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 20 on stack + ok := peekTopEStackIsValue(t, vm, 20) + assert.True(t, ok) + + // Excute second OPCODE + // Should be PUSH(34) + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should have the number 34 at the top of the stack + ok = peekTopEStackIsValue(t, vm, 34) + assert.True(t, ok) + + // Excute third OPCODE + // Should Add both values on the stack + state, err = vm.step() + assert.Equal(t, NONE, int(state)) + assert.Nil(t, err) + + // We should now have one value on the stack + //It should be equal to 20+34 = 54 + ok = EstackLen(t, vm, 1) + assert.True(t, ok) + ok = peekTopEStackIsValue(t, vm, 54) + assert.True(t, ok) + + // If we try to step again, we should get a nil error and HALT + // because we have gone over the instruction pointer + // error is nil because when there are nomore instructions, the vm + // will add a RET opcode and return + state, err = vm.step() + assert.Equal(t, HALT, int(state)) + assert.Nil(t, err) + +} + +func TestSimpleRun(t *testing.T) { + + // Program pushes 20 and 34 to the stack + // Adds them together + // pushes 54 to the stack + // Checks if result of addition and 54 are equal + // Faults if not + + // Push(20) + // Push(34) + // Add + // Push(54) + // Equal + //THROWIFNOT + builder := stack.NewBuilder() + builder.EmitInt(20).EmitInt(34).EmitOpcode(stack.ADD) + builder.EmitInt(54).EmitOpcode(stack.EQUAL).EmitOpcode(stack.THROWIFNOT) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Runs vm with program + _, err := vm.Run() + assert.Nil(t, err) + + // ResultStack should be nil + assert.Equal(t, -1, vm.ResultStack.Len()) + +} + +// returns true if the value at the top of the evaluation stack is a integer +// and equals the value passed in +func peekTopEStackIsValue(t *testing.T, vm *VM, value int64) bool { + item := peakTopEstack(t, vm) + integer, err := item.Integer() + assert.Nil(t, err) + return value == integer.Value().Int64() +} + +// peaks the stack item on the top of the evaluation stack +// if the current context and returns it +func peakTopEstack(t *testing.T, vm *VM) stack.Item { + ctx, err := vm.InvocationStack.CurrentContext() + fmt.Println(err) + assert.Nil(t, err) + item, err := ctx.Estack.Peek(0) + assert.Nil(t, err) + return item +} + +// returns true if the total number of items on the evaluation stack is equal to value +func EstackLen(t *testing.T, vm *VM, value int) bool { + ctx, err := vm.InvocationStack.CurrentContext() + assert.Nil(t, err) + return value == ctx.Estack.Len() +} From bab5d370bba6be834477f1a49b8ed835239bed75 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Mon, 18 Mar 2019 21:58:51 +0000 Subject: [PATCH 053/117] Interop --- .../csharp-interop-test/push/pushbytes1.json | 81 +++++++++++++++++++ pkg/vm/csharp-interop-test/readme.md | 6 ++ pkg/vm/csharp-interop-test/testStruct.go | 26 ++++++ 3 files changed, 113 insertions(+) create mode 100644 pkg/vm/csharp-interop-test/push/pushbytes1.json create mode 100644 pkg/vm/csharp-interop-test/readme.md create mode 100644 pkg/vm/csharp-interop-test/testStruct.go diff --git a/pkg/vm/csharp-interop-test/push/pushbytes1.json b/pkg/vm/csharp-interop-test/push/pushbytes1.json new file mode 100644 index 000000000..474944423 --- /dev/null +++ b/pkg/vm/csharp-interop-test/push/pushbytes1.json @@ -0,0 +1,81 @@ +{ + "category": "Push", + "name": "PUSHBYTES1", + "tests": + [ + { + "name": "Good definition", + "script": "0x0100", + "steps": + [ + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Break", + "invocationStack": + [ + { + "scriptHash": "0xFBC22D517F38E7612798ECE8E5957CF6C41D8CAF", + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": + [ + { + "type": "ByteArray", + "value": "0x00" + } + ] + } + ] + } + }, + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Halt", + "resultStack": + [ + { + "type": "ByteArray", + "value": "0x00" + } + ] + } + } + ] + }, + { + "name": "Wrong definition (without enough length)", + "script": "0x01", + "steps": + [ + { + "actions": + [ + "StepInto" + ], + "result": + { + "state": "Fault", + "invocationStack": + [ + { + "scriptHash": "0xC51B66BCED5E4491001BD702669770DCCF440982", + "instructionPointer": 1, + "nextInstruction": "RET" + } + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/pkg/vm/csharp-interop-test/readme.md b/pkg/vm/csharp-interop-test/readme.md new file mode 100644 index 000000000..0e457c7c0 --- /dev/null +++ b/pkg/vm/csharp-interop-test/readme.md @@ -0,0 +1,6 @@ +## Package VM Interop + + +This package will use the tests in the neo-vm repo to test interopabilty + + diff --git a/pkg/vm/csharp-interop-test/testStruct.go b/pkg/vm/csharp-interop-test/testStruct.go new file mode 100644 index 000000000..c0da0112b --- /dev/null +++ b/pkg/vm/csharp-interop-test/testStruct.go @@ -0,0 +1,26 @@ +package csharpinterop + +// VMUnitTest is a struct for capturing the fields in the json files +type VMUnitTest struct { + Category string `json:"category"` + Name string `json:"name"` + Tests []struct { + Name string `json:"name"` + Script string `json:"script"` + Steps []struct { + Actions []string `json:"actions"` + Result struct { + State string `json:"state"` + InvocationStack []struct { + ScriptHash string `json:"scriptHash"` + InstructionPointer int `json:"instructionPointer"` + NextInstruction string `json:"nextInstruction"` + EvaluationStack []struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"evaluationStack"` + } `json:"invocationStack"` + } `json:"result"` + } `json:"steps"` + } `json:"tests"` +} From 9a1ef386892474a0cafa476655e02ca342d43f48 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 21 Mar 2019 21:28:03 +0000 Subject: [PATCH 054/117] [Database] (#202) Remove unnecesary methods --- pkg/database/leveldb.go | 102 ++-------------------------------------- 1 file changed, 3 insertions(+), 99 deletions(-) diff --git a/pkg/database/leveldb.go b/pkg/database/leveldb.go index bb6c3ca0a..b90ffaf65 100644 --- a/pkg/database/leveldb.go +++ b/pkg/database/leveldb.go @@ -1,13 +1,6 @@ package database import ( - "bytes" - "encoding/binary" - "fmt" - - "github.com/CityOfZion/neo-go/pkg/wire/payload" - "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" - "github.com/CityOfZion/neo-go/pkg/wire/util" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" ) @@ -32,29 +25,17 @@ type Database interface { Close() error } -var ( - // TX is the prefix used when inserting a tx into the db - TX = []byte("TX") - // HEADER is the prefix used when inserting a header into the db - HEADER = []byte("HEADER") - // LATESTHEADER is the prefix used when inserting the latests header into the db - LATESTHEADER = []byte("LH") - // UTXO is the prefix used when inserting a utxo into the db - UTXO = []byte("UTXO") -) - // New will return a new leveldb instance func New(path string) *LDB { db, err := leveldb.OpenFile(path, nil) + if err != nil { + return nil + } if _, corrupted := err.(*errors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(path, nil) } - if err != nil { - return nil - } - return &LDB{ db, path, @@ -85,80 +66,3 @@ func (l *LDB) Delete(key []byte) error { func (l *LDB) Close() error { return l.db.Close() } - -// AddHeader adds a header into the database -func (l *LDB) AddHeader(header *payload.BlockBase) error { - - table := NewTable(l, HEADER) - - byt, err := header.Bytes() - if err != nil { - fmt.Println("Could not Get bytes from decoded BlockBase") - return nil - } - - fmt.Println("Adding Header, This should be batched!!!!") - - // This is the main mapping - //Key: HEADER+BLOCKHASH Value: contents of blockhash - key := header.Hash.Bytes() - err = table.Put(key, byt) - if err != nil { - fmt.Println("Error trying to add the original mapping into the DB for Header. Mapping is [Header]+[Hash]") - return err - } - - // This is the secondary mapping - // Key: HEADER + BLOCKHEIGHT Value: blockhash - - bh := uint32ToBytes(header.Index) - key = []byte(bh) - err = table.Put(key, header.Hash.Bytes()) - if err != nil { - return err - } - // This is the third mapping - // WARNING: This assumes that headers are adding in order. - return table.Put(LATESTHEADER, header.Hash.Bytes()) -} - -// AddTransactions adds a set of transactions into the database -func (l *LDB) AddTransactions(blockhash util.Uint256, txs []transaction.Transactioner) error { - - // SHOULD BE DONE IN BATCH!!!! - for i, tx := range txs { - buf := new(bytes.Buffer) - fmt.Println(tx.ID()) - tx.Encode(buf) - txByt := buf.Bytes() - txhash, err := tx.ID() - if err != nil { - fmt.Println("Error adding transaction with bytes", txByt) - return err - } - // This is the original mapping - // Key: [TX] + TXHASH - key := append(TX, txhash.Bytes()...) - l.Put(key, txByt) - - // This is the index - // Key: [TX] + BLOCKHASH + I <- i is the incrementer from the for loop - //Value : TXHASH - key = append(TX, blockhash.Bytes()...) - key = append(key, uint32ToBytes(uint32(i))...) - - err = l.Put(key, txhash.Bytes()) - if err != nil { - fmt.Println("Error could not add tx index into db") - return err - } - } - return nil -} - -// BigEndian -func uint32ToBytes(h uint32) []byte { - a := make([]byte, 4) - binary.BigEndian.PutUint32(a, h) - return a -} From e12255dd73dec62b5058093c72b14da6bec8a4c5 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 21 Mar 2019 23:18:02 +0000 Subject: [PATCH 055/117] [connmgr] Refactor Connmgr (#205) * [connmgr] - Refactor Connmgr - Remove un-needed async code - Add comment for Request --- pkg/connmgr/config.go | 25 ++++ pkg/connmgr/connmgr.go | 246 ++++++++++++++++++++++++++++++++++++ pkg/connmgr/connmgr_test.go | 107 ++++++++++++++++ pkg/connmgr/readme.md | 22 ++++ pkg/connmgr/request.go | 15 +++ 5 files changed, 415 insertions(+) create mode 100755 pkg/connmgr/config.go create mode 100755 pkg/connmgr/connmgr.go create mode 100755 pkg/connmgr/connmgr_test.go create mode 100755 pkg/connmgr/readme.md create mode 100755 pkg/connmgr/request.go diff --git a/pkg/connmgr/config.go b/pkg/connmgr/config.go new file mode 100755 index 000000000..66fada355 --- /dev/null +++ b/pkg/connmgr/config.go @@ -0,0 +1,25 @@ +package connmgr + +import ( + "net" +) + +// Config contains all methods which will be set by the caller to setup the connection manager. +type Config struct { + // GetAddress will return a single address for the connection manager to connect to + // This will be the source of addresses for the connection manager + GetAddress func() (string, error) + + // OnConnection is called by the connection manager when we successfully connect to a peer + // The caller should ideally inform the address manager that we have connected to this address in this function + OnConnection func(conn net.Conn, addr string) + + // OnAccept will take an established connection + OnAccept func(net.Conn) + + // AddressPort is the address port of the local node in the format "address:port" + AddressPort string + + // DialTimeout is the amount of time to wait, before we can disconnect a pending dialed connection + DialTimeout int +} diff --git a/pkg/connmgr/connmgr.go b/pkg/connmgr/connmgr.go new file mode 100755 index 000000000..a8e8442ac --- /dev/null +++ b/pkg/connmgr/connmgr.go @@ -0,0 +1,246 @@ +package connmgr + +import ( + "errors" + "fmt" + "net" + "net/http" + "time" +) + +var ( + // maxOutboundConn is the maximum number of active peers + // that the connection manager will try to have + maxOutboundConn = 10 + + // maxRetries is the maximum amount of successive retries that + // we can have before we stop dialing that peer + maxRetries = uint8(5) +) + +// Connmgr manages pending/active/failed cnnections +type Connmgr struct { + config Config + PendingList map[string]*Request + ConnectedList map[string]*Request + actionch chan func() +} + +//New creates a new connection manager +func New(cfg Config) *Connmgr { + cnnmgr := &Connmgr{ + cfg, + make(map[string]*Request), + make(map[string]*Request), + make(chan func(), 300), + } + + go func() { + + listener, err := net.Listen("tcp", cfg.AddressPort) + + if err != nil { + fmt.Println("Error connecting to outbound ", err) + } + + defer func() { + listener.Close() + }() + + for { + + conn, err := listener.Accept() + + if err != nil { + continue + } + go cfg.OnAccept(conn) + } + + }() + + return cnnmgr +} + +// NewRequest will make a new connection gets the address from address func in config +// Then dials it and assigns it to pending +func (c *Connmgr) NewRequest() error { + + // Fetch address + addr, err := c.config.GetAddress() + if err != nil { + return fmt.Errorf("error getting address " + err.Error()) + } + + r := &Request{ + Addr: addr, + } + return c.Connect(r) +} + +// Connect will dial the address in the Request +// Updating the request object depending on the outcome +func (c *Connmgr) Connect(r *Request) error { + + r.Retries++ + + conn, err := c.dial(r.Addr) + if err != nil { + c.failed(r) + return err + } + + r.Conn = conn + r.Inbound = true + + // r.Permanent is set by the address manager/caller. default is false + // The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network + // or are reliable. The connmgr will be more leniennt to permanent addresses as they have + // a track record or reputation of being reliable. + + return c.connected(r) +} + +//Disconnect will remove the request from the connected/pending list and close the connection +func (c *Connmgr) Disconnect(addr string) { + + var r *Request + + // fetch from connected list + r, ok := c.ConnectedList[addr] + if !ok { + // If not in connected, check pending + r, _ = c.PendingList[addr] + } + + c.disconnected(r) + +} + +// Dial is used to dial up connections given the addres and ip in the form address:port +func (c *Connmgr) dial(addr string) (net.Conn, error) { + dialTimeout := 1 * time.Second + conn, err := net.DialTimeout("tcp", addr, dialTimeout) + if err != nil { + if !isConnected() { + return nil, errors.New("Fatal Error: You do not seem to be connected to the internet") + } + return conn, err + } + return conn, nil +} +func (c *Connmgr) failed(r *Request) { + + c.actionch <- func() { + // priority to check if it is permanent or inbound + // if so then these peers are valuable in NEO and so we will just retry another time + if r.Inbound || r.Permanent { + multiplier := time.Duration(r.Retries * 10) + time.AfterFunc(multiplier*time.Second, + func() { + c.Connect(r) + }, + ) + // if not then we should check if this request has had maxRetries + // if it has then get a new address + // if not then call Connect on it again + } else if r.Retries > maxRetries { + if c.config.GetAddress != nil { + go c.NewRequest() + } + } else { + go c.Connect(r) + } + } + +} + +// Disconnected is called when a peer disconnects. +// we take the addr from peer, which is also it's key in the map +// and we use it to remove it from the connectedList +func (c *Connmgr) disconnected(r *Request) error { + + if r == nil { + // if object is nil, we return nil + return nil + } + + // if for some reason the underlying connection is not closed, close it + err := r.Conn.Close() + if err != nil { + return err + } + + // remove from any pending/connected list + delete(c.PendingList, r.Addr) + delete(c.ConnectedList, r.Addr) + + // If permanent,then lets retry + if r.Permanent { + return c.Connect(r) + } + + return nil +} + +//Connected is called when the connection manager makes a successful connection. +func (c *Connmgr) connected(r *Request) error { + + // This should not be the case, since we connected + if r == nil { + return errors.New("request object as nil inside of the connected function") + } + + // reset retries to 0 + r.Retries = 0 + + // add to connectedList + c.ConnectedList[r.Addr] = r + + // remove from pending if it was there + delete(c.PendingList, r.Addr) + + if c.config.OnConnection != nil { + c.config.OnConnection(r.Conn, r.Addr) + } + + return nil +} + +// Pending is synchronous, we do not want to continue with logic +// until we are certain it has been added to the pendingList +func (c *Connmgr) pending(r *Request) error { + + if r == nil { + return errors.New("request object is nil") + } + + c.PendingList[r.Addr] = r + + return nil +} + +// Run will start the connection manager +func (c *Connmgr) Run() error { + fmt.Println("Connection manager started") + go c.loop() + return nil +} + +func (c *Connmgr) loop() { + for { + select { + case f := <-c.actionch: + f() + } + } +} + +// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application +func isConnected() (ok bool) { + _, err := http.Get("http://clients3.google.com/generate_204") + if err != nil { + return false + } + return true +} diff --git a/pkg/connmgr/connmgr_test.go b/pkg/connmgr/connmgr_test.go new file mode 100755 index 000000000..a060cf838 --- /dev/null +++ b/pkg/connmgr/connmgr_test.go @@ -0,0 +1,107 @@ +package connmgr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDial(t *testing.T) { + cfg := Config{ + GetAddress: nil, + OnConnection: nil, + OnAccept: nil, + AddressPort: "", + DialTimeout: 0, + } + + cm := New(cfg) + err := cm.Run() + assert.Equal(t, nil, err) + + ipport := "google.com:80" // google unlikely to go offline, a better approach to test Dialing is welcome. + + conn, err := cm.dial(ipport) + assert.Equal(t, nil, err) + assert.NotEqual(t, nil, conn) +} +func TestConnect(t *testing.T) { + cfg := Config{ + GetAddress: nil, + OnConnection: nil, + OnAccept: nil, + AddressPort: "", + DialTimeout: 0, + } + + cm := New(cfg) + cm.Run() + + ipport := "google.com:80" + + r := Request{Addr: ipport} + + err := cm.Connect(&r) + assert.Nil(t, err) + + assert.Equal(t, 1, len(cm.ConnectedList)) + +} +func TestNewRequest(t *testing.T) { + + address := "google.com:80" + + var getAddr = func() (string, error) { + return address, nil + } + + cfg := Config{ + GetAddress: getAddr, + OnConnection: nil, + OnAccept: nil, + AddressPort: "", + DialTimeout: 0, + } + + cm := New(cfg) + + cm.Run() + + cm.NewRequest() + + if _, ok := cm.ConnectedList[address]; ok { + assert.Equal(t, true, ok) + assert.Equal(t, 1, len(cm.ConnectedList)) + return + } + + assert.Fail(t, "Could not find the address in the connected lists") + +} +func TestDisconnect(t *testing.T) { + + address := "google.com:80" + + var getAddr = func() (string, error) { + return address, nil + } + + cfg := Config{ + GetAddress: getAddr, + OnConnection: nil, + OnAccept: nil, + AddressPort: "", + DialTimeout: 0, + } + + cm := New(cfg) + + cm.Run() + + cm.NewRequest() + + cm.Disconnect(address) + + assert.Equal(t, 0, len(cm.ConnectedList)) + +} diff --git a/pkg/connmgr/readme.md b/pkg/connmgr/readme.md new file mode 100755 index 000000000..d5d43fa01 --- /dev/null +++ b/pkg/connmgr/readme.md @@ -0,0 +1,22 @@ +# Package - Connection Manager + +## Responsibility + +- Manages the active, failed and pending connections for the node. + +## Features + +- Takes an Request, dials it and logs information based on the connectivity. + +- Retry failed connections. + +- Removable address source. The connection manager does not manage addresses, only connections. + + +## Usage + +The following methods are exposed from the Connection manager: + +- Connect(r *Request) : This takes a Request object and connects to it. It follow the same logic as NewRequest() however instead of getting the address from the datasource given upon initialisation, you directly feed the address you want to connect to. + +- Disconnect(addrport string) : Given an address:port, this will disconnect it, close the connection and remove it from the connected and pending list, if it was there. diff --git a/pkg/connmgr/request.go b/pkg/connmgr/request.go new file mode 100755 index 000000000..0d7def3c3 --- /dev/null +++ b/pkg/connmgr/request.go @@ -0,0 +1,15 @@ +package connmgr + +import ( + "net" +) + +// Request is a layer on top of connection and allows us to add metadata to the net.Conn +// that the connection manager can use to determine whether to retry and other useful heuristics +type Request struct { + Conn net.Conn + Addr string + Permanent bool + Inbound bool + Retries uint8 // should not be trying more than 255 tries +} From 6f496754fb035bf1a630feb151f4f804070c05ba Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Sat, 23 Mar 2019 17:52:36 +0100 Subject: [PATCH 056/117] 1) Fixed String method in Uint256 (#208) 2) Added ReverseString Method to Uint256 --- pkg/wire/payload/block_test.go | 8 ++++---- pkg/wire/payload/mheaders_test.go | 4 ++-- pkg/wire/payload/transaction/claim_test.go | 4 ++-- pkg/wire/payload/transaction/contract_test.go | 6 +++--- pkg/wire/payload/transaction/enrollment_test.go | 2 +- pkg/wire/payload/transaction/invocation_test.go | 2 +- pkg/wire/payload/transaction/miner_test.go | 2 +- pkg/wire/payload/transaction/publish_test.go | 2 +- pkg/wire/payload/transaction/register_test.go | 4 ++-- pkg/wire/payload/transaction/state_test.go | 4 ++-- pkg/wire/util/uint256.go | 5 +++++ pkg/wire/util/uint256_test.go | 4 ++-- 12 files changed, 26 insertions(+), 21 deletions(-) diff --git a/pkg/wire/payload/block_test.go b/pkg/wire/payload/block_test.go index 2de7cdb4e..88c479c81 100644 --- a/pkg/wire/payload/block_test.go +++ b/pkg/wire/payload/block_test.go @@ -46,13 +46,13 @@ func TestBlockDecodeEncode(t *testing.T) { for _, tx := range b.Txs { switch t := tx.(type) { case *transaction.Contract: - hashes = append(hashes, t.Hash.String()) + hashes = append(hashes, t.Hash.ReverseString()) case *transaction.Miner: - hashes = append(hashes, t.Hash.String()) + hashes = append(hashes, t.Hash.ReverseString()) case *transaction.Claim: - hashes = append(hashes, t.Hash.String()) + hashes = append(hashes, t.Hash.ReverseString()) case *transaction.Invocation: - hashes = append(hashes, t.Hash.String()) + hashes = append(hashes, t.Hash.ReverseString()) } } diff --git a/pkg/wire/payload/mheaders_test.go b/pkg/wire/payload/mheaders_test.go index a03f43486..7527b2908 100644 --- a/pkg/wire/payload/mheaders_test.go +++ b/pkg/wire/payload/mheaders_test.go @@ -50,7 +50,7 @@ func TestAddAndEncodeHeaders(t *testing.T) { err := msgHeaders.Headers[0].createHash() assert.Equal(t, nil, err) // Hash being correct, automatically verifies that the fields are encoded properly - assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", msgHeaders.Headers[0].Hash.String()) + assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", msgHeaders.Headers[0].Hash.ReverseString()) } @@ -70,7 +70,7 @@ func TestEncodeDecode(t *testing.T) { header := headerMsg.Headers[0] err = header.createHash() - assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", header.Hash.String()) + assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", header.Hash.ReverseString()) buf := new(bytes.Buffer) diff --git a/pkg/wire/payload/transaction/claim_test.go b/pkg/wire/payload/transaction/claim_test.go index fb049fb58..53ae8ba79 100644 --- a/pkg/wire/payload/transaction/claim_test.go +++ b/pkg/wire/payload/transaction/claim_test.go @@ -26,9 +26,9 @@ func TestEncodeDecodeClaim(t *testing.T) { assert.Equal(t, 1, int(len(c.Claims))) claim := c.Claims[0] - assert.Equal(t, "497037a4c5e0a9ea1721e06f9d5e9aec183d11f2824ece93285729370f3a1baf", claim.PrevHash.String()) + assert.Equal(t, "497037a4c5e0a9ea1721e06f9d5e9aec183d11f2824ece93285729370f3a1baf", claim.PrevHash.ReverseString()) assert.Equal(t, uint16(0), claim.PrevIndex) - assert.Equal(t, "abf142faf539c340e42722b5b34b505cf4fd73185fed775784e37c2c5ef1b866", c.Hash.String()) + assert.Equal(t, "abf142faf539c340e42722b5b34b505cf4fd73185fed775784e37c2c5ef1b866", c.Hash.ReverseString()) // Encode buf := new(bytes.Buffer) diff --git a/pkg/wire/payload/transaction/contract_test.go b/pkg/wire/payload/transaction/contract_test.go index ea0393301..17c4de77f 100644 --- a/pkg/wire/payload/transaction/contract_test.go +++ b/pkg/wire/payload/transaction/contract_test.go @@ -27,12 +27,12 @@ func TestEncodeDecodeContract(t *testing.T) { input := c.Inputs[0] - assert.Equal(t, "eec17cc828d6ede932b57e4eaf79c2591151096a7825435cd67f498f9fa98d88", input.PrevHash.String()) + assert.Equal(t, "eec17cc828d6ede932b57e4eaf79c2591151096a7825435cd67f498f9fa98d88", input.PrevHash.ReverseString()) assert.Equal(t, 0, int(input.PrevIndex)) assert.Equal(t, int64(70600000000), c.Outputs[0].Amount) - assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", c.Outputs[0].AssetID.String()) + assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", c.Outputs[0].AssetID.ReverseString()) assert.Equal(t, "a8666b4830229d6a1a9b80f6088059191c122d2b", c.Outputs[0].ScriptHash.String()) - assert.Equal(t, "bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79", c.Hash.String()) + assert.Equal(t, "bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79", c.Hash.ReverseString()) // Encode buf := new(bytes.Buffer) diff --git a/pkg/wire/payload/transaction/enrollment_test.go b/pkg/wire/payload/transaction/enrollment_test.go index cf76af8e7..b2f0a3a16 100644 --- a/pkg/wire/payload/transaction/enrollment_test.go +++ b/pkg/wire/payload/transaction/enrollment_test.go @@ -26,5 +26,5 @@ func TestEncodeDecodeEnrollment(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) - assert.Equal(t, "988832f693785dcbcb8d5a0e9d5d22002adcbfb1eb6bbeebf8c494fff580e147", enroll.Hash.String()) + assert.Equal(t, "988832f693785dcbcb8d5a0e9d5d22002adcbfb1eb6bbeebf8c494fff580e147", enroll.Hash.ReverseString()) } diff --git a/pkg/wire/payload/transaction/invocation_test.go b/pkg/wire/payload/transaction/invocation_test.go index 82ce2fcb6..fcd6a9818 100644 --- a/pkg/wire/payload/transaction/invocation_test.go +++ b/pkg/wire/payload/transaction/invocation_test.go @@ -34,7 +34,7 @@ func TestEncodeDecodeInvoc(t *testing.T) { assert.Equal(t, "31363a30373a3032203a2030333366616431392d643638322d343035382d626437662d313563393331323434336538", hex.EncodeToString(attr2.Data)) assert.Equal(t, "050034e23004141ad842821c7341d5a32b17d7177a1750d30014ca14628c9e5bc6a9346ca6bcdf050ceabdeb2bdc774953c1087472616e736665726703e1df72015bdef1a1b9567d4700635f23b1f406f1", hex.EncodeToString(i.Script)) - assert.Equal(t, "b2a22cd9dd7636ae23e25576866cd1d9e2f3d85a85e80874441f085cd60006d1", i.Hash.String()) + assert.Equal(t, "b2a22cd9dd7636ae23e25576866cd1d9e2f3d85a85e80874441f085cd60006d1", i.Hash.ReverseString()) // Encode buf := new(bytes.Buffer) diff --git a/pkg/wire/payload/transaction/miner_test.go b/pkg/wire/payload/transaction/miner_test.go index d053ce414..fa22ebcd4 100644 --- a/pkg/wire/payload/transaction/miner_test.go +++ b/pkg/wire/payload/transaction/miner_test.go @@ -24,7 +24,7 @@ func TestEncodeDecodeMiner(t *testing.T) { assert.Equal(t, types.Miner, m.Type) assert.Equal(t, uint32(571397116), m.Nonce) - assert.Equal(t, "a1f219dc6be4c35eca172e65e02d4591045220221b1543f1a4b67b9e9442c264", m.Hash.String()) + assert.Equal(t, "a1f219dc6be4c35eca172e65e02d4591045220221b1543f1a4b67b9e9442c264", m.Hash.ReverseString()) // Encode buf := new(bytes.Buffer) diff --git a/pkg/wire/payload/transaction/publish_test.go b/pkg/wire/payload/transaction/publish_test.go index 1c9bb1de1..e27a44777 100644 --- a/pkg/wire/payload/transaction/publish_test.go +++ b/pkg/wire/payload/transaction/publish_test.go @@ -28,6 +28,6 @@ func TestEncodeDecodePublish(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) - assert.Equal(t, "5467a1fc8723ceffa8e5ee59399b02eea1df6fbaa53768c6704b90b960d223fa", publ.Hash.String()) + assert.Equal(t, "5467a1fc8723ceffa8e5ee59399b02eea1df6fbaa53768c6704b90b960d223fa", publ.Hash.ReverseString()) } diff --git a/pkg/wire/payload/transaction/register_test.go b/pkg/wire/payload/transaction/register_test.go index d5492a6d8..da8bfdc45 100644 --- a/pkg/wire/payload/transaction/register_test.go +++ b/pkg/wire/payload/transaction/register_test.go @@ -28,7 +28,7 @@ func TestEncodeDecodeRegister(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) - assert.Equal(t, "0c092117b4ba47b81001712425e6e7f760a637695eaf23741ba335925b195ecd", reg.Hash.String()) + assert.Equal(t, "0c092117b4ba47b81001712425e6e7f760a637695eaf23741ba335925b195ecd", reg.Hash.ReverseString()) } func TestEncodeDecodeGenesisRegister(t *testing.T) { @@ -50,5 +50,5 @@ func TestEncodeDecodeGenesisRegister(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) - assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", reg.Hash.String()) + assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", reg.Hash.ReverseString()) } diff --git a/pkg/wire/payload/transaction/state_test.go b/pkg/wire/payload/transaction/state_test.go index 209d9b9c0..d585daf76 100644 --- a/pkg/wire/payload/transaction/state_test.go +++ b/pkg/wire/payload/transaction/state_test.go @@ -25,7 +25,7 @@ func TestEncodeDecodeState(t *testing.T) { assert.Equal(t, 1, len(s.Inputs)) input := s.Inputs[0] - assert.Equal(t, "a192cbabc6d613ecfcce43fd09e9197556ca5cf7d4bd1f6c65726ea9f08441cb", input.PrevHash.String()) + assert.Equal(t, "a192cbabc6d613ecfcce43fd09e9197556ca5cf7d4bd1f6c65726ea9f08441cb", input.PrevHash.ReverseString()) assert.Equal(t, uint16(0), input.PrevIndex) assert.Equal(t, 1, len(s.Descriptors)) @@ -43,5 +43,5 @@ func TestEncodeDecodeState(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtxBytes, buf.Bytes()) - assert.Equal(t, "8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724", s.Hash.String()) + assert.Equal(t, "8abf5ebdb9a8223b12109513647f45bd3c0a6cf1a6346d56684cff71ba308724", s.Hash.ReverseString()) } diff --git a/pkg/wire/util/uint256.go b/pkg/wire/util/uint256.go index e7a5e73fc..fc3fe86ca 100644 --- a/pkg/wire/util/uint256.go +++ b/pkg/wire/util/uint256.go @@ -63,6 +63,11 @@ func (u Uint256) Equals(other Uint256) bool { // String implements the stringer interface. func (u Uint256) String() string { + return hex.EncodeToString(u.Bytes()) +} + +// ReverseString displays a reverse string representation of Uint256. +func (u Uint256) ReverseString() string { return hex.EncodeToString(slice.Reverse(u.Bytes())) } diff --git a/pkg/wire/util/uint256_test.go b/pkg/wire/util/uint256_test.go index 120bee380..ae6b8a438 100644 --- a/pkg/wire/util/uint256_test.go +++ b/pkg/wire/util/uint256_test.go @@ -13,7 +13,7 @@ func TestUint256DecodeString(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, hexStr, val.Reverse().String()) + assert.Equal(t, hexStr, val.String()) } func TestUint256DecodeBytes(t *testing.T) { @@ -26,7 +26,7 @@ func TestUint256DecodeBytes(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, hexStr, val.Reverse().String()) + assert.Equal(t, hexStr, val.String()) } func TestUInt256Equals(t *testing.T) { From 30e5aa8f480474a087afca9edad3e1eb73491672 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Sat, 23 Mar 2019 16:57:05 +0000 Subject: [PATCH 057/117] Add Chain saving functionality to Database (#206) * [database] - Add Prefix method to interface - Convert leveldb error to `database error` - Be explicit with prefixedKey in `Table` as slices can be pointers * [protocol] - Add stringer method to protocol * [Chaindb] - Added saveBlock() which will allow us to save a block into the database. The block is broken up into transactions and Headers. The headers are saved as is. The transactions are saved as is, then the utxos in the transactions are collected to make the utxo db. - Verification for blocks and transactions will reside in the same package. Note that the save methods are all unexported, while the Get methods are exported. Making it so that any can call a get method, but only code in this package may save to the database. The other code which will reside in this package will be code verification logic. * [chaindb] - Added saveHeader function which saveHeaders uses - Update the latest header, each time we save a header instead of after a batch. This is so that we can call saveHeader without saveHeaders. This functionality can be rolled back if the performance of updating the header after a batch is significant - small refactor in test code --- pkg/chain/chaindb.go | 372 ++++++++++++++++++++++++++++++++++ pkg/chain/chaindb_test.go | 201 ++++++++++++++++++ pkg/database/leveldb.go | 62 +++++- pkg/database/leveldb_test.go | 33 ++- pkg/database/table.go | 22 +- pkg/peer/peer.go | 6 +- pkg/wire/protocol/protocol.go | 12 ++ 7 files changed, 679 insertions(+), 29 deletions(-) create mode 100644 pkg/chain/chaindb.go create mode 100644 pkg/chain/chaindb_test.go diff --git a/pkg/chain/chaindb.go b/pkg/chain/chaindb.go new file mode 100644 index 000000000..9b16b366a --- /dev/null +++ b/pkg/chain/chaindb.go @@ -0,0 +1,372 @@ +package chain + +import ( + "bufio" + "bytes" + "encoding/binary" + + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + // TX is the prefix used when inserting a tx into the db + TX = []byte("TX") + // HEADER is the prefix used when inserting a header into the db + HEADER = []byte("HE") + // LATESTHEADER is the prefix used when inserting the latests header into the db + LATESTHEADER = []byte("LH") + // UTXO is the prefix used when inserting a utxo into the db + UTXO = []byte("UT") + // LATESTBLOCK is the prefix used when inserting the latest block into the db + LATESTBLOCK = []byte("LB") + // BLOCKHASHTX is the prefix used when linking a blockhash to a given tx + BLOCKHASHTX = []byte("BT") + // BLOCKHASHHEIGHT is the prefix used when linking a blockhash to it's height + // This is linked both ways + BLOCKHASHHEIGHT = []byte("BH") + // SCRIPTHASHUTXO is the prefix used when linking a utxo to a scripthash + // This is linked both ways + SCRIPTHASHUTXO = []byte("SU") +) + +// Chaindb is a wrapper around the db interface which adds an extra block chain specific layer on top. +type Chaindb struct { + db database.Database +} + +// This should not be exported for other callers. +// It is safe-guarded by the chain's verification logic +func (c *Chaindb) saveBlock(blk payload.Block, genesis bool) error { + + latestBlockTable := database.NewTable(c.db, LATESTBLOCK) + hashHeightTable := database.NewTable(c.db, BLOCKHASHHEIGHT) + + // Save Txs and link to block hash + err := c.saveTXs(blk.Txs, blk.Hash.Bytes(), genesis) + if err != nil { + return err + } + + // LINK block height to hash - Both ways + // This allows us to fetch a block using it's hash or it's height + // Given the height, we will search the table to get the hash + // We can then fetch all transactions in the tx table, which match that block hash + height := uint32ToBytes(blk.Index) + err = hashHeightTable.Put(height, blk.Hash.Bytes()) + if err != nil { + return err + } + + err = hashHeightTable.Put(blk.Hash.Bytes(), height) + if err != nil { + return err + } + + // Add block as latest block + // This also acts a Commit() for the block. + // If an error occured, then this will be set to the previous block + // This is useful because if the node suddently shut down while saving and the database was not corrupted + // Then the node will see the latestBlock as the last saved block, and re-download the faulty block + // Note: We check for the latest block on startup + return latestBlockTable.Put([]byte(""), blk.Hash.Bytes()) +} + +// Saves a tx and links each tx to the block it was found in +// This should never be exported. Only way to add a tx, is through it's block +func (c *Chaindb) saveTXs(txs []transaction.Transactioner, blockHash []byte, genesis bool) error { + + for txIndex, tx := range txs { + err := c.saveTx(tx, uint32(txIndex), blockHash, genesis) + if err != nil { + return err + } + } + return nil +} + +func (c *Chaindb) saveTx(tx transaction.Transactioner, txIndex uint32, blockHash []byte, genesis bool) error { + + txTable := database.NewTable(c.db, TX) + blockTxTable := database.NewTable(c.db, BLOCKHASHTX) + + // Save the whole tx using it's hash a key + // In order to find a tx in this table, we need to know it's hash + txHash, err := tx.ID() + if err != nil { + return err + } + err = txTable.Put(txHash.Bytes(), tx.Bytes()) + if err != nil { + return err + } + + // LINK TXhash to block + // This allows us to fetch a tx by just knowing what block it was in + // This is useful for when we want to re-construct a block from it's hash + // In order to ge the tx, we must do a prefix search on blockHash + // This will return a set of txHashes. + //We can then use these hashes to search the txtable for the tx's we need + key := bytesConcat(blockHash, uint32ToBytes(txIndex)) + err = blockTxTable.Put(key, txHash.Bytes()) + if err != nil { + return err + } + + // Save all of the utxos in a transaction + // We do this additional save so that we can form a utxo database + // and know when a transaction is a double spend. + utxos := tx.UTXOs() + for utxoIndex, utxo := range utxos { + err := c.saveUTXO(utxo, uint16(utxoIndex), txHash.Bytes(), blockHash) + if err != nil { + return err + } + } + + // Do not check for spent utxos on the genesis block + if genesis { + return nil + } + + // Remove all spent utxos + // We do this so that once an output has been spent + // It will be removed from the utxo database and cannot be spent again + // If the output was never in the utxo database, this function will return an error + txos := tx.TXOs() + for _, txo := range txos { + err := c.removeUTXO(txo) + if err != nil { + return err + } + } + return nil +} + +// saveUTxo will save a utxo and link it to it's transaction and block +func (c *Chaindb) saveUTXO(utxo *transaction.Output, utxoIndex uint16, txHash, blockHash []byte) error { + + utxoTable := database.NewTable(c.db, UTXO) + scripthashUTXOTable := database.NewTable(c.db, SCRIPTHASHUTXO) + + // This is quite messy, we should (if possible) find a way to pass a Writer and Reader interface + // Encode utxo into a buffer + buf := new(bytes.Buffer) + bw := &util.BinWriter{W: buf} + if utxo.Encode(bw); bw.Err != nil { + return bw.Err + } + + // Save UTXO + // In order to find a utxo in the utxoTable + // One must know the txHash that the utxo was in + key := bytesConcat(txHash, uint16ToBytes(utxoIndex)) + if err := utxoTable.Put(key, buf.Bytes()); err != nil { + return err + } + + // LINK utxo to scripthash + // This allows us to find a utxo with the scriptHash + // Since the key starts with scriptHash, we can look for the scriptHash prefix + // and find all utxos for a given scriptHash. + // Additionally, we can search for all utxos for a certain user in a certain block with scriptHash+blockHash + // But this may not be of use to us. However, note that we cannot have just the scriptHash with the utxoIndex + // as this may not be unique. If Kim/Dautt agree, we can change blockHash to blockHeight, which allows us + // To get all utxos above a certain blockHeight. Question is; Would this be useful? + newKey := bytesConcat(utxo.ScriptHash.Bytes(), blockHash, uint16ToBytes(utxoIndex)) + if err := scripthashUTXOTable.Put(newKey, key); err != nil { + return err + } + if err := scripthashUTXOTable.Put(key, newKey); err != nil { + return err + } + return nil +} + +// Remove +func (c *Chaindb) removeUTXO(txo *transaction.Input) error { + + utxoTable := database.NewTable(c.db, UTXO) + scripthashUTXOTable := database.NewTable(c.db, SCRIPTHASHUTXO) + + // Remove spent utxos from utxo database + key := bytesConcat(txo.PrevHash.Bytes(), uint16ToBytes(txo.PrevIndex)) + err := utxoTable.Delete(key) + if err != nil { + return err + } + + // Remove utxos from scripthash table + otherKey, err := scripthashUTXOTable.Get(key) + if err != nil { + return err + } + if err := scripthashUTXOTable.Delete(otherKey); err != nil { + return err + } + if err := scripthashUTXOTable.Delete(key); err != nil { + return err + } + + return nil +} + +// saveHeaders will save a set of headers into the database +func (c *Chaindb) saveHeaders(headers []*payload.BlockBase) error { + + for _, hdr := range headers { + err := c.saveHeader(hdr) + if err != nil { + return err + } + } + return nil +} + +// saveHeader saves a header into the database and updates the latest header +// The headers are saved with their `blockheights` as Key +// If we want to search for a header, we need to know it's index +// Alternatively, we can search the hashHeightTable with the block index to get the hash +// If the block has been saved. +// The reason why headers are saved with their index as Key, is so that we can +// increment the key to find out what block we should fetch next during the initial +// block download, when we are saving thousands of headers +func (c *Chaindb) saveHeader(hdr *payload.BlockBase) error { + + headerTable := database.NewTable(c.db, HEADER) + latestHeaderTable := database.NewTable(c.db, LATESTHEADER) + + index := uint32ToBytes(hdr.Index) + + byt, err := hdr.Bytes() + if err != nil { + return err + } + + err = headerTable.Put(index, byt) + if err != nil { + return err + } + + // Update latest header + return latestHeaderTable.Put([]byte(""), index) +} + +// GetHeaderFromHeight will get a header given it's block height +func (c *Chaindb) GetHeaderFromHeight(index []byte) (*payload.BlockBase, error) { + headerTable := database.NewTable(c.db, HEADER) + hdrBytes, err := headerTable.Get(index) + if err != nil { + return nil, err + } + reader := bytes.NewReader(hdrBytes) + + blockBase := &payload.BlockBase{} + err = blockBase.Decode(reader) + if err != nil { + return nil, err + } + return blockBase, nil +} + +// GetLastHeader will get the header which was saved last in the database +func (c *Chaindb) GetLastHeader() (*payload.BlockBase, error) { + + latestHeaderTable := database.NewTable(c.db, LATESTHEADER) + index, err := latestHeaderTable.Get([]byte("")) + if err != nil { + return nil, err + } + return c.GetHeaderFromHeight(index) +} + +// GetBlockFromHash will return a block given it's hash +func (c *Chaindb) GetBlockFromHash(blockHash []byte) (*payload.Block, error) { + + blockTxTable := database.NewTable(c.db, BLOCKHASHTX) + + // To get a block we need to fetch: + // The transactions (1) + // The header (2) + + // Reconstruct block by fetching it's txs (1) + var txs []transaction.Transactioner + + // Get all Txhashes for this block + txHashes, err := blockTxTable.Prefix(blockHash) + if err != nil { + return nil, err + } + + // Get all Tx's given their hash + txTable := database.NewTable(c.db, TX) + for _, txHash := range txHashes { + + // Fetch tx by it's hash + txBytes, err := txTable.Get(txHash) + if err != nil { + return nil, err + } + reader := bufio.NewReader(bytes.NewReader(txBytes)) + + tx, err := transaction.FromReader(reader) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + + // Now fetch the header (2) + // We have the block hash, but headers are stored with their `Height` as key. + // We first search the `BlockHashHeight` table to get the height. + //Then we search the headers table with the height + hashHeightTable := database.NewTable(c.db, BLOCKHASHHEIGHT) + height, err := hashHeightTable.Get(blockHash) + if err != nil { + return nil, err + } + hdr, err := c.GetHeaderFromHeight(height) + if err != nil { + return nil, err + } + + // Construct block + block := &payload.Block{ + BlockBase: *hdr, + Txs: txs, + } + return block, nil +} + +// GetLastBlock will return the last block that has been saved +func (c *Chaindb) GetLastBlock() (*payload.Block, error) { + + latestBlockTable := database.NewTable(c.db, LATESTBLOCK) + blockHash, err := latestBlockTable.Get([]byte("")) + if err != nil { + return nil, err + } + return c.GetBlockFromHash(blockHash) +} + +func uint16ToBytes(x uint16) []byte { + index := make([]byte, 2) + binary.BigEndian.PutUint16(index, x) + return index +} + +func uint32ToBytes(x uint32) []byte { + index := make([]byte, 4) + binary.BigEndian.PutUint32(index, x) + return index +} + +func bytesConcat(args ...[]byte) []byte { + var res []byte + for _, arg := range args { + res = append(res, arg...) + } + return res +} diff --git a/pkg/chain/chaindb_test.go b/pkg/chain/chaindb_test.go new file mode 100644 index 000000000..b5656d30b --- /dev/null +++ b/pkg/chain/chaindb_test.go @@ -0,0 +1,201 @@ +package chain + +import ( + "bytes" + "math/rand" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var s = rand.NewSource(time.Now().UnixNano()) +var r = rand.New(s) + +func TestLastHeader(t *testing.T) { + _, cdb, hdrs := saveRandomHeaders(t) + + // Select last header from list of headers + lastHeader := hdrs[len(hdrs)-1] + // GetLastHeader from the database + hdr, err := cdb.GetLastHeader() + assert.Nil(t, err) + assert.Equal(t, hdr.Index, lastHeader.Index) + + // Clean up + os.RemoveAll(database.DbDir) +} + +func TestSaveHeader(t *testing.T) { + // save headers then fetch a random element + + db, _, hdrs := saveRandomHeaders(t) + + headerTable := database.NewTable(db, HEADER) + // check that each header was saved + for _, hdr := range hdrs { + index := uint32ToBytes(hdr.Index) + ok, err := headerTable.Has(index) + assert.Nil(t, err) + assert.True(t, ok) + } + + // Clean up + os.RemoveAll(database.DbDir) +} + +func TestSaveBlock(t *testing.T) { + + // Init databases + db, err := database.New("temp.test") + assert.Nil(t, err) + + cdb := &Chaindb{db} + + // Construct block0 and block1 + block0, block1 := twoBlocksLinked(t) + + // Save genesis header + err = cdb.saveHeader(&block0.BlockBase) + assert.Nil(t, err) + + // Save genesis block + err = cdb.saveBlock(block0, true) + assert.Nil(t, err) + + // Test genesis block saved + testBlockWasSaved(t, cdb, block0) + + // Save block1 header + err = cdb.saveHeader(&block1.BlockBase) + assert.Nil(t, err) + + // Save block1 + err = cdb.saveBlock(block1, false) + assert.Nil(t, err) + + // Test block1 was saved + testBlockWasSaved(t, cdb, block1) + + // Clean up + os.RemoveAll(database.DbDir) +} + +func testBlockWasSaved(t *testing.T, cdb *Chaindb, block payload.Block) { + // Fetch last block from database + lastBlock, err := cdb.GetLastBlock() + assert.Nil(t, err) + + // Get byte representation of last block from database + byts, err := lastBlock.Bytes() + assert.Nil(t, err) + + // Get byte representation of block that we saved + blockBytes, err := block.Bytes() + assert.Nil(t, err) + + // Should be equal + assert.True(t, bytes.Equal(byts, blockBytes)) +} + +func randomHeaders(t *testing.T) []*payload.BlockBase { + assert := assert.New(t) + hdrsMsg, err := payload.NewHeadersMessage() + assert.Nil(err) + + for i := 0; i < 2000; i++ { + err = hdrsMsg.AddHeader(randomBlockBase(t)) + assert.Nil(err) + } + + return hdrsMsg.Headers +} + +func randomBlockBase(t *testing.T) *payload.BlockBase { + + base := &payload.BlockBase{ + Version: r.Uint32(), + PrevHash: randUint256(t), + MerkleRoot: randUint256(t), + Timestamp: r.Uint32(), + Index: r.Uint32(), + ConsensusData: r.Uint64(), + NextConsensus: randUint160(t), + Witness: transaction.Witness{ + InvocationScript: []byte{0, 1, 2, 34, 56}, + VerificationScript: []byte{0, 12, 3, 45, 66}, + }, + Hash: randUint256(t), + } + return base +} + +func randomTxs(t *testing.T) []transaction.Transactioner { + + var txs []transaction.Transactioner + for i := 0; i < 10; i++ { + tx := transaction.NewContract(0) + tx.AddInput(transaction.NewInput(randUint256(t), uint16(r.Int()))) + tx.AddOutput(transaction.NewOutput(randUint256(t), r.Int63(), randUint160(t))) + txs = append(txs, tx) + } + return txs +} + +func saveRandomHeaders(t *testing.T) (database.Database, *Chaindb, []*payload.BlockBase) { + db, err := database.New("temp.test") + assert.Nil(t, err) + + cdb := &Chaindb{db} + + hdrs := randomHeaders(t) + + err = cdb.saveHeaders(hdrs) + assert.Nil(t, err) + return db, cdb, hdrs +} + +func randUint256(t *testing.T) util.Uint256 { + slice := make([]byte, 32) + _, err := r.Read(slice) + u, err := util.Uint256DecodeBytes(slice) + assert.Nil(t, err) + return u +} +func randUint160(t *testing.T) util.Uint160 { + slice := make([]byte, 20) + _, err := r.Read(slice) + u, err := util.Uint160DecodeBytes(slice) + assert.Nil(t, err) + return u +} + +// twoBlocksLinked will return two blocks, the second block spends from the utxos in the first +func twoBlocksLinked(t *testing.T) (payload.Block, payload.Block) { + genesisBase := randomBlockBase(t) + genesisTxs := randomTxs(t) + genesisBlock := payload.Block{BlockBase: *genesisBase, Txs: genesisTxs} + + var txs []transaction.Transactioner + + // Form transactions that spend from the genesis block + for _, tx := range genesisTxs { + txHash, err := tx.ID() + assert.Nil(t, err) + newTx := transaction.NewContract(0) + newTx.AddInput(transaction.NewInput(txHash, 0)) + newTx.AddOutput(transaction.NewOutput(randUint256(t), r.Int63(), randUint160(t))) + txs = append(txs, newTx) + } + + nextBase := randomBlockBase(t) + nextBlock := payload.Block{BlockBase: *nextBase, Txs: txs} + + return genesisBlock, nextBlock +} diff --git a/pkg/database/leveldb.go b/pkg/database/leveldb.go index b90ffaf65..a039c6010 100644 --- a/pkg/database/leveldb.go +++ b/pkg/database/leveldb.go @@ -3,14 +3,22 @@ package database import ( "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" + ldbutil "github.com/syndtr/goleveldb/leveldb/util" ) +//DbDir is the folder which all database files will be put under +// Structure /DbDir/net +const DbDir = "db/" + // LDB represents a leveldb object type LDB struct { db *leveldb.DB - path string + Path string } +// ErrNotFound means that the value was not found in the db +var ErrNotFound = errors.New("value not found for that key") + // Database contains all methods needed for an object to be a database type Database interface { // Has checks whether the key is in the database @@ -21,25 +29,30 @@ type Database interface { Get(key []byte) ([]byte, error) // Delete deletes the given value for the key from the database Delete(key []byte) error + //Prefix returns all values that start with key + Prefix(key []byte) ([][]byte, error) // Close closes the underlying db object Close() error } // New will return a new leveldb instance -func New(path string) *LDB { - db, err := leveldb.OpenFile(path, nil) +func New(path string) (*LDB, error) { + dbPath := DbDir + path + db, err := leveldb.OpenFile(dbPath, nil) if err != nil { - return nil + return nil, err } - if _, corrupted := err.(*errors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(path, nil) + if err != nil { + return nil, err + } } return &LDB{ db, - path, - } + dbPath, + }, nil } // Has implements the database interface @@ -54,7 +67,15 @@ func (l *LDB) Put(key []byte, value []byte) error { // Get implements the database interface func (l *LDB) Get(key []byte) ([]byte, error) { - return l.db.Get(key, nil) + val, err := l.db.Get(key, nil) + if err == nil { + return val, nil + } + if err == leveldb.ErrNotFound { + return val, ErrNotFound + } + return val, err + } // Delete implements the database interface @@ -66,3 +87,28 @@ func (l *LDB) Delete(key []byte) error { func (l *LDB) Close() error { return l.db.Close() } + +// Prefix implements the database interface +func (l *LDB) Prefix(key []byte) ([][]byte, error) { + + var results [][]byte + + iter := l.db.NewIterator(ldbutil.BytesPrefix(key), nil) + for iter.Next() { + + value := iter.Value() + + // Copy the data, as we cannot modify it + // Once the iter has been released + deref := make([]byte, len(value)) + + copy(deref, value) + + // Append result + results = append(results, deref) + + } + iter.Release() + err := iter.Error() + return results, err +} diff --git a/pkg/database/leveldb_test.go b/pkg/database/leveldb_test.go index 0991682ee..61c831bd3 100644 --- a/pkg/database/leveldb_test.go +++ b/pkg/database/leveldb_test.go @@ -6,27 +6,31 @@ import ( "github.com/CityOfZion/neo-go/pkg/database" "github.com/stretchr/testify/assert" - "github.com/syndtr/goleveldb/leveldb/errors" ) const path = "temp" func cleanup(db *database.LDB) { db.Close() - os.RemoveAll(path) + os.RemoveAll(database.DbDir) } func TestDBCreate(t *testing.T) { - db := database.New(path) + + db, err := database.New(path) + assert.Nil(t, err) + assert.NotEqual(t, nil, db) cleanup(db) } func TestPutGet(t *testing.T) { - db := database.New(path) + + db, err := database.New(path) + assert.Nil(t, err) key := []byte("Hello") value := []byte("World") - err := db.Put(key, value) + err = db.Put(key, value) assert.Equal(t, nil, err) res, err := db.Get(key) @@ -36,25 +40,28 @@ func TestPutGet(t *testing.T) { } func TestPutDelete(t *testing.T) { - db := database.New(path) + db, err := database.New(path) + assert.Nil(t, err) key := []byte("Hello") value := []byte("World") - err := db.Put(key, value) + err = db.Put(key, value) err = db.Delete(key) assert.Equal(t, nil, err) res, err := db.Get(key) - assert.Equal(t, errors.ErrNotFound, err) + assert.Equal(t, database.ErrNotFound, err) assert.Equal(t, res, []byte{}) cleanup(db) } func TestHas(t *testing.T) { - db := database.New("temp") + + db, err := database.New(path) + assert.Nil(t, err) res, err := db.Has([]byte("NotExist")) assert.Equal(t, res, false) @@ -73,8 +80,12 @@ func TestHas(t *testing.T) { } func TestDBClose(t *testing.T) { - db := database.New("temp") - err := db.Close() + + db, err := database.New(path) + assert.Nil(t, err) + + err = db.Close() assert.Equal(t, nil, err) + cleanup(db) } diff --git a/pkg/database/table.go b/pkg/database/table.go index c36b80185..8c4cf3023 100644 --- a/pkg/database/table.go +++ b/pkg/database/table.go @@ -16,29 +16,35 @@ func NewTable(db Database, prefix []byte) *Table { // Has implements the database interface func (t *Table) Has(key []byte) (bool, error) { - key = append(t.prefix, key...) - return t.db.Has(key) + prefixedKey := append(t.prefix, key...) + return t.db.Has(prefixedKey) } // Put implements the database interface func (t *Table) Put(key []byte, value []byte) error { - key = append(t.prefix, key...) - return t.db.Put(key, value) + prefixedKey := append(t.prefix, key...) + return t.db.Put(prefixedKey, value) } // Get implements the database interface func (t *Table) Get(key []byte) ([]byte, error) { - key = append(t.prefix, key...) - return t.db.Get(key) + prefixedKey := append(t.prefix, key...) + return t.db.Get(prefixedKey) } // Delete implements the database interface func (t *Table) Delete(key []byte) error { - key = append(t.prefix, key...) - return t.db.Delete(key) + prefixedKey := append(t.prefix, key...) + return t.db.Delete(prefixedKey) } // Close implements the database interface func (t *Table) Close() error { return nil } + +// Prefix implements the database interface +func (t *Table) Prefix(key []byte) ([][]byte, error) { + prefixedKey := append(t.prefix, key...) + return t.db.Prefix(prefixedKey) +} diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go index 6a87a4aa6..035dad835 100644 --- a/pkg/peer/peer.go +++ b/pkg/peer/peer.go @@ -195,13 +195,15 @@ func (p *Peer) PingLoop() { /*not implemented in other neo clients*/ } func (p *Peer) Run() error { err := p.Handshake() - + if err != nil { + return err + } go p.StartProtocol() go p.ReadLoop() go p.WriteLoop() //go p.PingLoop() // since it is not implemented. It will disconnect all other impls. - return err + return nil } diff --git a/pkg/wire/protocol/protocol.go b/pkg/wire/protocol/protocol.go index ebc916e50..d8b223267 100644 --- a/pkg/wire/protocol/protocol.go +++ b/pkg/wire/protocol/protocol.go @@ -30,3 +30,15 @@ const ( MainNet Magic = 7630401 TestNet Magic = 0x74746e41 ) + +// String implements the stringer interface +func (m Magic) String() string { + switch m { + case MainNet: + return "Mainnet" + case TestNet: + return "Testnet" + default: + return "UnknownNet" + } +} From 7d84d44b08a4b36442b2e6129baa4030c6cabe12 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Sat, 23 Mar 2019 19:09:25 +0000 Subject: [PATCH 058/117] [chain] (#209) - Add basic skeleton for chain struct --- pkg/chain/chain.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 pkg/chain/chain.go diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go new file mode 100644 index 000000000..c1492cb8f --- /dev/null +++ b/pkg/chain/chain.go @@ -0,0 +1,91 @@ +package chain + +import ( + "errors" + + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +var ( + // ErrBlockAlreadyExists happens when you try to save the same block twice + ErrBlockAlreadyExists = errors.New("this block has already been saved in the database") +) + +// Chain represents a blockchain instance +type Chain struct { + db *Chaindb +} + +//New returns a new chain instance +func New(db database.Database) *Chain { + return &Chain{ + db: &Chaindb{db}, + } +} + +// SaveBlock verifies and saves the block in the database +// XXX: for now we will just save without verifying the block +// This function is called by the server and if an error is returned then +// the server informs the sync manager to redownload the block +// XXX:We should also check if the header is already saved in the database +// If not, then we need to validate the header with the rest of the chain +// For now we re-save the header +func (c *Chain) SaveBlock(msg payload.BlockMessage) error { + err := c.VerifyBlock(msg.Block) + if err != nil { + return err + } + //XXX(Issue): We can either check the hash here for genesisblock. + //We most likely will have it anyways after validation/ We can return it from VerifyBlock + // Or we can do it somewhere in startup, performance benefits + // won't be that big since it's just a bytes.Equal. + // so it's more about which is more readable and where it makes sense to put + return c.db.saveBlock(msg.Block, false) +} + +// VerifyBlock verifies whether a block is valid according +// to the rules of consensus +func (c *Chain) VerifyBlock(block payload.Block) error { + + // Check if we already have this block + // XXX: We can optimise by implementing a Has method + // caching the last block in memory + lastBlock, err := c.db.GetLastBlock() + if err != nil { + return err + } + // Check if we have already saved this block + // by looking if the latest block height is more than + // incoming block height + if lastBlock.Index > block.Index { + return ErrBlockAlreadyExists + } + + return nil +} + +// VerifyTx verifies whether a transaction is valid according +// to the rules of consensus +func (c *Chain) VerifyTx(tx transaction.Transactioner) error { + return nil +} + +// SaveHeaders will save the set of headers without validating +func (c *Chain) SaveHeaders(msg payload.HeadersMessage) error { + + err := c.verifyHeaders(msg.Headers) + if err != nil { + return err + } + return c.db.saveHeaders(msg.Headers) +} + +// verifyHeaders will be used to verify a batch of headers +// should only ever be called during the initial block download +// or when the node receives a HeadersMessage +func (c *Chain) verifyHeaders(hdrs []*payload.BlockBase) error { + return nil +} From beab4d186ff14aa372c65e1fe87cb1cd7524d358 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Mon, 25 Mar 2019 00:11:54 +0100 Subject: [PATCH 059/117] Fixed Txn Attribute econding/decoding (issue: #216) (#217) --- pkg/wire/payload/transaction/Attribute.go | 20 +++++------ .../payload/transaction/invocation_test.go | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/pkg/wire/payload/transaction/Attribute.go b/pkg/wire/payload/transaction/Attribute.go index 7b747c1b0..0dc873da7 100644 --- a/pkg/wire/payload/transaction/Attribute.go +++ b/pkg/wire/payload/transaction/Attribute.go @@ -24,14 +24,13 @@ func (a *Attribute) Encode(bw *util.BinWriter) { } bw.Write(uint8(a.Usage)) - if a.Usage == DescriptionURL || a.Usage == Vote || (a.Usage >= Hash1 && a.Usage <= Hash15) { + if a.Usage == ContractHash || a.Usage == Vote || (a.Usage >= Hash1 && a.Usage <= Hash15) { bw.Write(a.Data[:32]) - - } else if a.Usage == Script { - bw.Write(a.Data[:20]) } else if a.Usage == ECDH02 || a.Usage == ECDH03 { bw.Write(a.Data[1:33]) - } else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { + } else if a.Usage == Script { + bw.Write(a.Data[:20]) + } else if a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { bw.VarUint(uint64(len(a.Data))) bw.Write(a.Data) } else { @@ -43,17 +42,16 @@ func (a *Attribute) Encode(bw *util.BinWriter) { // Decode decodes the binary reader into an Attribute object func (a *Attribute) Decode(br *util.BinReader) { br.Read(&a.Usage) - if a.Usage == DescriptionURL || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 { + if a.Usage == ContractHash || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 { a.Data = make([]byte, 32) br.Read(&a.Data) - - } else if a.Usage == Script { - a.Data = make([]byte, 20) - br.Read(&a.Data) } else if a.Usage == ECDH02 || a.Usage == ECDH03 { a.Data = make([]byte, 32) br.Read(&a.Data) - } else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { + } else if a.Usage == Script { + a.Data = make([]byte, 20) + br.Read(&a.Data) + } else if a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark { lenData := br.VarUint() a.Data = make([]byte, lenData) br.Read(&a.Data) diff --git a/pkg/wire/payload/transaction/invocation_test.go b/pkg/wire/payload/transaction/invocation_test.go index fcd6a9818..c87ddc8d8 100644 --- a/pkg/wire/payload/transaction/invocation_test.go +++ b/pkg/wire/payload/transaction/invocation_test.go @@ -42,3 +42,37 @@ func TestEncodeDecodeInvoc(t *testing.T) { assert.Equal(t, nil, err) assert.Equal(t, rawtxBytes, buf.Bytes()) } + +func TestEncodeDecodeInvocAttributes(t *testing.T) { + // taken from mainnet cb0b5edc7e87b3b1bd9e029112fd3ce17c16d3de20c43ca1c0c26f3add578ecb + + rawtx := "d1015308005b950f5e010000140000000000000000000000000000000000000000141a1e29d6232d2148e1e71e30249835ea41eb7a3d53c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce000000000000000002201a1e29d6232d2148e1e71e30249835ea41eb7a3d8110f9f504da6334935a2db42b18296d88700000014140461370f6847c4abbdddff54a3e1337e453ecc8133c882ec5b9aabcf0f47dafd3432d47e449f4efc77447ef03519b7808c450a998cca3ecc10e6536ed9db862ba23210285264b6f349f0fe86e9bb3044fde8f705b016593cf88cd5e8a802b78c7d2c950ac" + rawtxBytes, _ := hex.DecodeString(rawtx) + + i := NewInvocation(30) + + r := bytes.NewReader(rawtxBytes) + err := i.Decode(r) + assert.Equal(t, nil, err) + + assert.Equal(t, types.Invocation, i.Type) + + assert.Equal(t, 1, int(i.Version)) + + assert.Equal(t, 2, len(i.Attributes)) + + assert.Equal(t, Script, i.Attributes[0].Usage) + assert.Equal(t, "1a1e29d6232d2148e1e71e30249835ea41eb7a3d", hex.EncodeToString(i.Attributes[0].Data)) + assert.Equal(t, DescriptionURL, i.Attributes[1].Usage) + assert.Equal(t, "f9f504da6334935a2db42b18296d8870", hex.EncodeToString(i.Attributes[1].Data)) + + assert.Equal(t, "08005b950f5e010000140000000000000000000000000000000000000000141a1e29d6232d2148e1e71e30249835ea41eb7a3d53c1087472616e7366657267fb1c540417067c270dee32f21023aa8b9b71abce", hex.EncodeToString(i.Script)) + assert.Equal(t, "cb0b5edc7e87b3b1bd9e029112fd3ce17c16d3de20c43ca1c0c26f3add578ecb", i.Hash.ReverseString()) + + // Encode + buf := new(bytes.Buffer) + err = i.Encode(buf) + assert.Equal(t, nil, err) + assert.Equal(t, rawtxBytes, buf.Bytes()) + +} From ce1fe72607ad1f4bd8e04805c1c71511cb1e593d Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Mon, 25 Mar 2019 02:04:54 +0100 Subject: [PATCH 060/117] Finalized size calculation methodology (#215) - Simplified Transactioner interface - Added size calculation test - Added utility methods in the address pkg: FromUint160, Uint160Decode --- pkg/chain/chaindb.go | 6 +- pkg/wire/payload/block_test.go | 92 ++++++++++++++++++++++++++++ pkg/wire/payload/transaction/base.go | 21 ++----- pkg/wire/util/address/address.go | 19 ++++++ 4 files changed, 118 insertions(+), 20 deletions(-) diff --git a/pkg/chain/chaindb.go b/pkg/chain/chaindb.go index 9b16b366a..5f1a7124e 100644 --- a/pkg/chain/chaindb.go +++ b/pkg/chain/chaindb.go @@ -98,7 +98,7 @@ func (c *Chaindb) saveTx(tx transaction.Transactioner, txIndex uint32, blockHash if err != nil { return err } - err = txTable.Put(txHash.Bytes(), tx.Bytes()) + err = txTable.Put(txHash.Bytes(), tx.BaseTx().Bytes()) if err != nil { return err } @@ -118,7 +118,7 @@ func (c *Chaindb) saveTx(tx transaction.Transactioner, txIndex uint32, blockHash // Save all of the utxos in a transaction // We do this additional save so that we can form a utxo database // and know when a transaction is a double spend. - utxos := tx.UTXOs() + utxos := tx.BaseTx().Outputs for utxoIndex, utxo := range utxos { err := c.saveUTXO(utxo, uint16(utxoIndex), txHash.Bytes(), blockHash) if err != nil { @@ -135,7 +135,7 @@ func (c *Chaindb) saveTx(tx transaction.Transactioner, txIndex uint32, blockHash // We do this so that once an output has been spent // It will be removed from the utxo database and cannot be spent again // If the output was never in the utxo database, this function will return an error - txos := tx.TXOs() + txos := tx.BaseTx().Inputs for _, txo := range txos { err := c.removeUTXO(txo) if err != nil { diff --git a/pkg/wire/payload/block_test.go b/pkg/wire/payload/block_test.go index 88c479c81..0736ff90d 100644 --- a/pkg/wire/payload/block_test.go +++ b/pkg/wire/payload/block_test.go @@ -5,6 +5,8 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/wire/util/address" + "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" "github.com/stretchr/testify/assert" ) @@ -78,3 +80,93 @@ func TestBlockDecodeEncode(t *testing.T) { assert.Equal(t, rawtx, hex.EncodeToString(buf.Bytes())) } +func TestBlockSizeCalculation(t *testing.T) { + // block taken from mainnet: 0006d3ff96e269f599eb1b5c5a527c218439e498dcc65b63794591bbcdc0516b + // The Size in golang is given by counting the number of bytes of an object. (len(Bytes)) + // its implementation is different from the corresponding C# and python implentation. But the result should + // should be the same.In this test we provide more details then necessary because in case of failure we can easily debug the + // root cause of the size calculation missmatch. + + rawBlock := "00000000ba33df12e8adbf38b6039e79ee91fdb8b1519e2e6154cb59c0653c81769288f4a22492109b7a84077ed7226c28612eb61428ea9ded9bdc952cdfc13deb4172ef85d1115b0bb62300abff35093d19a14a59e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd45014012afae6df64195041e4764b57caa9e27fc2cfc596833163904136ec95816d104b44b3737d0e9f6b1b4445cd3b6a5cc80f6b0935675bc44dba44415eb309832b3404dc95bcf85e4635556a1d618e4ce947b26972992ed74788df5f9501b850ac0b40b7112d1ff30e4ade00369e16f0d13932d1ba76725e7682db072f8e2cd7752b840d12bb7dd45dd3b0e2098db5c67b6de55b7c40164937491fcaca1239b25860251224ead23ab232add78ccccd347239eae50ffc98f50b2a84c60ec5c3d284647a7406fabf6ca241b759af6b71080c0dfad7395632e989226a7e52f8cd2c133aeb2226e6e1aea47666fd81f578405a9f9bbd9d0bc523c3a44d7a5099ddc649feabe5f406188b8ee478731a89beeb76fdbd108eb0071b8f2b8678f40c5a1f387a491314336783255dee8cc5af4bf914dfeaacecc318fc13e02262658e39e8ce0631941b1f1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae140000abff350900000000d101530800e1f5050000000014d8dd86f6d91eb2add2f2d8afeda2184ed94ac33314288017f85d80b889fe02beb5ff203ed9ef538f1653c1087472616e7366657267cf9472821400ceb06ca780c2a937fec5bbec51b900000000000000000220288017f85d80b889fe02beb5ff203ed9ef538f16f0153135323738393433373039313963623362646632650000014140b61b1a8d220c28633fa1a43ef02d334731b16013778664bc28db838d9ab8cfa64f9134fe952ad8a8eb8a6dd9d864055301bcdba4177fd6ee0f52b3f096db2fe4232102bca5c56af0f11e7042f5eaf3d8b2767feb3a8e3ba5668b00e6ea21cb7a215689ac8000000177de54907d16326ff29c3fcd4892afae32043e87ca844929157857093632c4010000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e9a435000000000849ee84e1f3daaf226589c05de2bf8bcae8c94001414092ba4569663dacd95921318658f8f40662bcff61fdcbbe08da0938a6c93a6d1075b76c86fa5454ca41f762d3b955b8d6755b79ccaf52754169b69e8904f166f8232102e019359f675526fc8505198647e31ed3044ccb0e5cc2ea22fb3bed5420cdf687ac800001ff076e656f2d6f6e6501f3617958f115913192d1de50c8c5ac2c411a4177c7801554d2bf69c27dac86c7010002e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c606400000000000000915fe29d2d3847bce516f31fcd33f0fb1d90573be72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6034ed1a0000000000c85c8dca1fbb7473522109cecaf3acac2e27afc60141406a5ad8c2b6e3783184703d22f3bee39a8b0b6f81477bff61833090361cd52ca5160d66215e6044c31aaed44304a28273c65c9ba736cc75341b45fb18995a6c922321039eae6f12690848807983df6accc1b2929de8582e772be6b3ac084a02a576272eacd10187084d3198000000000008660ceb7bc700000014b3a766ac60afa2990d9251db08138fd1facf07ed0800e3598f01000000209b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc514e420bbe1e5bcdfa1b43e97a57380a6cd8fddfd4c56c1096d616b654f6666657267bd097b2fcf70e1fd30a5c3ef51e662feeafeba0100000000000000000120e420bbe1e5bcdfa1b43e97a57380a6cd8fddfd4c01659c1aa6f2f42f7d0548a9680df197d4dda767434490e8bed3e25dbbac5206cd000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e70241400f871fcc8bb58d110ea7b1a4c34390542a925203b147bd68137c9edc79044a657cd5dc00514270d65a77a97c814d1f5090054b6c35f1cde698f765538ccf290c2321023056f0a219758bcd503ff4123a589962003331cb9e14168d649ae7426e3ec26eac4140a4036b311b31e0620fcde6e83ed29e6c7e7fcadf59dcac1ccae352a83608c8117dcd36ca64d5a2ce041b2274481ce65b6d30b463d1ca87a0269177d034ad218d232102a1e6ed9a5cff73ad33b7896465af8e9206eab9c8c75502868b783deb64f232eaacd10179207dcc40f47af5b26c52342d5292eb741b7beaa0d58a9a4a1563441fd0f40c30e1349b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5b3a766ac60afa2990d9251db08138fd1facf07ed52c10b63616e63656c4f6666657267bd097b2fcf70e1fd30a5c3ef51e662feeafeba01000000000000000001202e5596a5c23eff8907aa180201c5a6f53c041dd101a9822ade57f6f9b6e999be06961af808137f3c9051b6c674bcc9e8aec8c1b6f1000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7024140b5b58794f8c4f2493a2cff25aa7546a79b6f9e1f279911b2d15a07926084705aeaf64d18454f40feda8ddb9096745eb2e16a208320e9122007fac350892cb3d823210308ec2156f3366339c54c59e4c0342888665abdd76ccbd6b2020961225ccfa3f4ac41404f947ca69b7a2ec0926989f30f3d3d986d488b7c01270eee56326b2f0be856353448062a5d2d73ce34bfdacec3af42b082f1c5186fc063b38c321c1a1eb37157232102a1e6ed9a5cff73ad33b7896465af8e9206eab9c8c75502868b783deb64f232eaacd101792084e25c75cef1e92d39333408dae6e31799d6316b2d908aa094d0dc18f137484c349b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5b3a766ac60afa2990d9251db08138fd1facf07ed52c10b63616e63656c4f6666657267bd097b2fcf70e1fd30a5c3ef51e662feeafeba01000000000000000001202e5596a5c23eff8907aa180201c5a6f53c041dd101ead2059c2cf4101cb02a1dae5875dc993189d047a84f05c080318779453b3c0e000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7024140c4c924181d2b1516467f20fd3cf4b75ce5dd75d916318b77cd9ebd4bf9a3fb3bef47151adb01e0828bc127db6859f11890f86bc5bae2980cba5fcefba0d7edf523210308ec2156f3366339c54c59e4c0342888665abdd76ccbd6b2020961225ccfa3f4ac41407beaabc59bbc4db241aca812b624befcc595d3f8299e996a7d660decd3cb162afc6d810d5e3001aae5874c42b7031b1445f8562c209a2e2726ca1316dffd8867232102a1e6ed9a5cff73ad33b7896465af8e9206eab9c8c75502868b783deb64f232eaacd101530800e1f5050000000014d8dd86f6d91eb2add2f2d8afeda2184ed94ac33314288017f85d80b889fe02beb5ff203ed9ef538f1653c1087472616e7366657267f91d6b7085db7c5aaf09f19eeec1ca3c0db2c6ec00000000000000000220288017f85d80b889fe02beb5ff203ed9ef538f16f015313532373839343337373637356464383030353031000001414061609a0460a3ccfe1a9cb5db9f75811e08d52328f291a1b848ec607718be0a37206a90e3a81908c4b71ec859b684e493c088e640b2e2d471bd370aae50bdf160232102bca5c56af0f11e7042f5eaf3d8b2767feb3a8e3ba5668b00e6ea21cb7a215689ac80000001128218ba3c40a03066c862b0eaad5c06a39a1ae653e08f69e70f528ec4e18dc60500019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000db2395d79fbd27d7b93b62cccd0fe0afc15ff80b0141404d953b03a1f53911fb1524a9f12126790e5e8468c05ed24ac3f71b881623510fcbe2f5497a4e8d1f1d1748774b9b3487be5f45dbd91bfb697a27ae2d2f2d9bb32321030ab39b99d8675cd9bd90aaec37cba964297cc817078d33e508ab11f1d245c068acd101792017a323cf2b0c650243be29d1b2c3ca69c141fdd7d72a36242c225f175c818a46349b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5b3a766ac60afa2990d9251db08138fd1facf07ed52c10b63616e63656c4f6666657267bd097b2fcf70e1fd30a5c3ef51e662feeafeba01000000000000000001202e5596a5c23eff8907aa180201c5a6f53c041dd1010a190ead75c8235b2db69d9eb00040209ce719c3450d90bca893bcfcdbe5e58c000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7024140b1dbd06a250ee76dc773e7efc915caea48732ca10f813d1027073dbba57032752c0ac7ed30fdb8b18df0abaf5831bd86e444785445253099b46c271676c4a16423210308ec2156f3366339c54c59e4c0342888665abdd76ccbd6b2020961225ccfa3f4ac4140a3b256ac1dd57de358bc1a45b632b3dd41f07d4c6d6e9ffe33fd0cf7c44e29673100bc8a8760727b46bf96642d6dd4a9c4edf657fb2a27b83f564a5b91408ad9232102a1e6ed9a5cff73ad33b7896465af8e9206eab9c8c75502868b783deb64f232eaacd1014e03a0860114d023de91710f63a0259e8d95d1f6563e1572783c14b18f53a903d7873b1453a748c3f80787eca2e30f53c1087472616e7366657267187fc13bec8ff0906c079e7f4cc8276709472913000000000000000004fee36e656f2d6f6e652d696e766f6b653a7b22636f6e7472616374223a22307831333239343730393637323763383463376639653037366339306630386665633362633137663138222c226d6574686f64223a227472616e73666572222c22706172616d73223a5b5b2266726f6d222c22307830666533613265633837303766386333343861373533313433623837643730336139353338666231225d2c5b22746f222c22307833633738373231353365353666366431393538643965323561303633306637313931646532336430225d2c5b2276616c7565222c22302e303031225d5d7dff076e656f2d6f6e65ff0a3233313635343031383420b18f53a903d7873b1453a748c3f80787eca2e30f0000014140910c4b3be37fe09052215f99ef546342c21ded22ef862c165ff0da3ff1087274d0fafb5bbac25aa927dc477b239df732d95afe782a46caeaca669ae096659f13232102c2dbc83931d5e550b95ceab8a94c6af37735fe2aa4e9fb217bce46001937b2f1acd1014e03a0860114de33c5d07f933c0f90da952cdfe2677d3f9a24d714f26e3a75379dc20c8642f3f11e20af76b12065f453c1087472616e7366657267aa67d0447c61bdddc4d1690d2269d772f9c37795000000000000000004fee36e656f2d6f6e652d696e766f6b653a7b22636f6e7472616374223a22307839353737633366393732643736393232306436396431633464646264363137633434643036376161222c226d6574686f64223a227472616e73666572222c22706172616d73223a5b5b2266726f6d222c22307866343635323062313736616632303165663166333432383630636332396433373735336136656632225d2c5b22746f222c22307864373234396133663764363765326466326339356461393030663363393337666430633533336465225d2c5b2276616c7565222c22302e303031225d5d7dff076e656f2d6f6e65ff0933363133393630393120f26e3a75379dc20c8642f3f11e20af76b12065f40000014140b1630c1546547df4b69fdf6c68fcbf9a3f1de2787d0ea7237aea639ab0a227a092147e47194595c0d960df0d209773da32808da767c15730926bcf844ed12f702321021a9f5ed87fe58e7a366c20975d4112698e4c0ccb3ba9cbce0400a482ecf99b67acd1014e03a08601148ba89ee5ab5c4975e0e12f88a8ce4aa8928108c9145e5c739e5d4b5a29af596c2cc162f1facded25fe53c1087472616e7366657267952d12a025325e56a4cb3ba2d469b1e23c7c77a0000000000000000004fee36e656f2d6f6e652d696e766f6b653a7b22636f6e7472616374223a22307861303737376333636532623136396434613233626362613435363565333232356130313232643935222c226d6574686f64223a227472616e73666572222c22706172616d73223a5b5b2266726f6d222c22307866653235656463646661663136326331326336633539616632393561346235643965373335633565225d2c5b22746f222c22307863393038383139326138346163656138383832666531653037353439356361626535396561383862225d2c5b2276616c7565222c22302e303031225d5d7dff076e656f2d6f6e65ff0a33383237333138333238205e5c739e5d4b5a29af596c2cc162f1facded25fe000001414050f556b29417eb44b66b283a72ff33b697f49b2b6d1520eb4fe5a9974632c1b1586b3e7f0aafac4707d65f89daa86592c8ad98c78016769a5de904330d02d2ed23210201008fe0ffcdab73b598c89c6ae2b46d90de38287abd7dd50a325d0bfb2469d5acd1014e03a086011444d65fda3f2062502c03c2bfd85c700a0d046fae149d12fcef2c830d73a570e9b89857962fcf3a619a53c1087472616e7366657267187fc13bec8ff0906c079e7f4cc8276709472913000000000000000004fee36e656f2d6f6e652d696e766f6b653a7b22636f6e7472616374223a22307831333239343730393637323763383463376639653037366339306630386665633362633137663138222c226d6574686f64223a227472616e73666572222c22706172616d73223a5b5b2266726f6d222c22307839613631336163663266393635373938623865393730613537333064383332636566666331323964225d2c5b22746f222c22307861653666303430643061373035636438626663323033326335303632323033666461356664363434225d2c5b2276616c7565222c22302e303031225d5d7dff076e656f2d6f6e65ff0a31353931373638373437209d12fcef2c830d73a570e9b89857962fcf3a619a00000141409ec506aab7045da733d02c8c9ebaa615d9aa02fa8c4d8eb35ad08a3b4843167cdd059094f492f77e68efa2b66d87305e13b3f08a326cf4b3dadaa29aaa64b30a23210275699ef1532219ee8e9d2d8a75b6b96b7cb4d5f9788391de31daceebed9ac8edacd1014e03a086011444bb85601b2d8c5247a1999dfb18ee7928e10cdf1479ed35e989051c8f2404b869c17ce75912de78b953c1087472616e7366657267aa67d0447c61bdddc4d1690d2269d772f9c37795000000000000000004fee36e656f2d6f6e652d696e766f6b653a7b22636f6e7472616374223a22307839353737633366393732643736393232306436396431633464646264363137633434643036376161222c226d6574686f64223a227472616e73666572222c22706172616d73223a5b5b2266726f6d222c22307862393738646531323539653737636331363962383034323438663163303538396539333565643739225d2c5b22746f222c22307864663063653132383739656531386662396439396131343735323863326431623630383562623434225d2c5b2276616c7565222c22302e303031225d5d7dff076e656f2d6f6e65ff0a333738313933373030382079ed35e989051c8f2404b869c17ce75912de78b9000001414081a2d21af431df3345478836ae24dc7714ce647daa1daac3a99ed17645a8c86a981d86c5d2280af45259a1cf4a85c115ecbe8ff5a398c0bea23c218c0c8c26f02321020be10b4bddffe752a7cfaa16d1718ce6da460608ca41ad9bf7ff66dc5f60c860acd10179201322db68df23cee8da600e7cf5875a8d919bf8e318bbe7afbcd1fdbd5758d67b349b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5b3a766ac60afa2990d9251db08138fd1facf07ed52c10b63616e63656c4f6666657267bd097b2fcf70e1fd30a5c3ef51e662feeafeba01000000000000000001202e5596a5c23eff8907aa180201c5a6f53c041dd10181c3670591e333a19a14aae556d211f3490163380c1f28ef55b0bb0e4a8aec1a000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7024140dd2aa6075329f43375655c83b04d343974fb13908888fa6b242b599c789c1cf635b530b6834093332720f93d185b450884951acfb261aec61fe9057efbdeb4b523210308ec2156f3366339c54c59e4c0342888665abdd76ccbd6b2020961225ccfa3f4ac4140f152dc9454ebf518bc0ab8192543add5844ad7007177986a3dd038db313740ea72abb5d49866caa4d470d75182a7e715fd2b9a6e345ef2568bf82e92878743db232102a1e6ed9a5cff73ad33b7896465af8e9206eab9c8c75502868b783deb64f232eaac8000000198f4ec54f46fd86c0955accb7bda7ff06ffc10c45651206fbcbfeabe78c597750000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e40b54020000002fb7e583c973498fef06f317a9762507e7ac0306014140c4a7388365a4e8a93e2229fd617805902773a8ef7354e95487819eec59379a6a8fb045ed126025db151336e2393eaf75dd524f1ebe872f92185f8d1a6a71a6a5232102b37eaec8631a5bb8579d4ba268e2fbc9c81b555f08558bba6eccb9d1448332c5ac02000155810e77f1dd89622915f3fceb051ac8abeaa2e7ea7a2c38e0744137c3c8982b0000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605e1502000000000024fdf844d637cf5a60a9744f1ab8ab8d6f2c18cf0141407dafb244fceee305d477892a21ca8cbd31906fbcc233e0bfccd0fe91d4f6dafdd067f7301cd3e8c7c6197f71615df4b7cedb987eb3ebff79ad729bb8768e1e50232102f8a4a73deefad4c114039b854cc4e974c409f30720e2567d15dea2204b8c45f7acd1012000c108776974686472617769bd097b2fcf70e1fd30a5c3ef51e662feeafeba01000000000000000003a15100000000000000000000000000000000000000000000000000000000000000a232e125258b7db0a0dffde5bd03b2b859253538ab000000000000000000000000a48098c835c493eb3b1967e44150630cc6435e564e00000000000000000000000001dfeee6c60db6403b5538e86120f01e60dcea749c73ce36b9058f050be8b7d2c9000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c600100000000000000bd097b2fcf70e1fd30a5c3ef51e662feeafeba010102000000d101530800e1f5050000000014d8dd86f6d91eb2add2f2d8afeda2184ed94ac33314288017f85d80b889fe02beb5ff203ed9ef538f1653c1087472616e7366657267f56c89be8bfcdec617e2402b5c3fd5b6d71b820d00000000000000000220288017f85d80b889fe02beb5ff203ed9ef538f16f0153135323738393433383433393264623331653662630000014140654af3a0cc69faa3dd42ff76c4012aa9c72e269bba004d6e910f195e33b2ecae980be4531a3677f27d3c90f4196632790997078bca4f8471c6db43b55928c3ef232102bca5c56af0f11e7042f5eaf3d8b2767feb3a8e3ba5668b00e6ea21cb7a215689ac" + rawBlockBytes, _ := hex.DecodeString(rawBlock) + + b := Block{} + + r := bytes.NewReader(rawBlockBytes) + b.Decode(r) + + expected := []struct { + ID string + Type string + Size int + Version int + InputsLen int + OutputsLen int + AttributesLen int + WitnessesLen int + }{ // 20 trans + {ID: "f59b04d8e6526684b94b5f8cdbdf691feaff5d45e9aa8e2325a668f1b9130786", Type: "MinerTransaction", Size: 10, Version: 0, InputsLen: 0, OutputsLen: 0, AttributesLen: 0, WitnessesLen: 0}, + {ID: "7463345f771e70019185d72fa5bd00fbb4f26735daae398ecc6540419332d81e", Type: "InvocationTransaction", Size: 244, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 2, WitnessesLen: 1}, + {ID: "cf3aeda21d320ec9b49d322f2b88fea21aa7e9bf243c1e02dfe08f5cc82b74b0", Type: "ContractTransaction", Size: 202, Version: 0, InputsLen: 1, OutputsLen: 1, AttributesLen: 0, WitnessesLen: 1}, + {ID: "07e502d13ae6255cfabbc9ee2f78a48fc1c43a4f7f713f128342db721bc01af5", Type: "ContractTransaction", Size: 271, Version: 0, InputsLen: 1, OutputsLen: 2, AttributesLen: 1, WitnessesLen: 1}, + {ID: "0de0baf53136c188bdd179fed9530dfb7dd80697fd59e47ffe294db4f421eb67", Type: "InvocationTransaction", Size: 469, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 1, WitnessesLen: 2}, + {ID: "233c8b00ab6a43aafae7fcc2be47fc46493185bb3376160b5809cb745aee3329", Type: "InvocationTransaction", Size: 455, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 1, WitnessesLen: 2}, + {ID: "8bf3ae0c692fc830753029fcb6575625ea8181b444cffcbe38404a28b77b3856", Type: "InvocationTransaction", Size: 455, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 1, WitnessesLen: 2}, + {ID: "c5285e1460191e1ca7fc07e3c26c5facebb033d56b63b7a41ebf11f2a1cb4306", Type: "InvocationTransaction", Size: 244, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 2, WitnessesLen: 1}, + {ID: "dce8b5f6dc093a910e405a230e9b7d546688d411cf960c8a1cc7d386d89b56d6", Type: "ContractTransaction", Size: 202, Version: 0, InputsLen: 1, OutputsLen: 1, AttributesLen: 0, WitnessesLen: 1}, + {ID: "4cce087cadfa99c2adeaaf1916ada025db124cef8f05d4535b0ad8047ef7d29e", Type: "InvocationTransaction", Size: 455, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 1, WitnessesLen: 2}, + {ID: "67df57a20c9d3b2942925f2c66fdc15a21be2c229a22122f6acbdac4dd10bf0a", Type: "InvocationTransaction", Size: 466, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 4, WitnessesLen: 1}, + {ID: "ad51030b30e016293caed92781b3bb3f993f86c15ab1153582f658d603fe23db", Type: "InvocationTransaction", Size: 465, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 4, WitnessesLen: 1}, + {ID: "1db2d62ad3530f1ae6ca7bd95e766beaff97058681f0e203d8744d7bba065012", Type: "InvocationTransaction", Size: 466, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 4, WitnessesLen: 1}, + {ID: "7d7bb6f0db6a71aca85fc9267fa6a59654b00b5f778a39c27214c68f11950f61", Type: "InvocationTransaction", Size: 466, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 4, WitnessesLen: 1}, + {ID: "1d534dcf1ce63a9ea9328eab309891ea2a0a5cb11e95cabf22860ee1fb649521", Type: "InvocationTransaction", Size: 466, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 4, WitnessesLen: 1}, + {ID: "8f4c9089871a4ad0076b27c061395079e0862f685f27e4bc01b7bac67b0cf8d0", Type: "InvocationTransaction", Size: 455, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 1, WitnessesLen: 2}, + {ID: "52b4653fca02e8042092456490036f0b9b18b339f65d9c334e7e9d2b4599f8db", Type: "ContractTransaction", Size: 202, Version: 0, InputsLen: 1, OutputsLen: 1, AttributesLen: 0, WitnessesLen: 1}, + {ID: "2b4854b1f46c9af0eb06587fd375355adfeea3c8f5921295421251af93d737e1", Type: "ClaimTransaction", Size: 203, Version: 0, InputsLen: 0, OutputsLen: 1, AttributesLen: 0, WitnessesLen: 1}, + {ID: "61e95e5b14625e897423670dfc3babf021d6b99ca2a73203dd1ac2604a2daadf", Type: "InvocationTransaction", Size: 244, Version: 1, InputsLen: 1, OutputsLen: 1, AttributesLen: 3, WitnessesLen: 1}, + {ID: "b361dfec8c2cde980b340d2c3ec63cecaea634f91b6d76f24a586aa60fbde483", Type: "InvocationTransaction", Size: 244, Version: 1, InputsLen: 0, OutputsLen: 0, AttributesLen: 2, WitnessesLen: 1}, + } + + for i, tx := range b.Txs { + txID, err := tx.ID() + assert.Equal(t, nil, err) + assert.Equal(t, expected[i].ID, txID.ReverseString()) + + assert.Equal(t, expected[i].Size, len(tx.BaseTx().Bytes())) + assert.Equal(t, expected[i].Type, tx.BaseTx().Type.String()) + assert.Equal(t, expected[i].Version, int(tx.BaseTx().Version)) + assert.Equal(t, expected[i].InputsLen, len(tx.BaseTx().Inputs)) + assert.Equal(t, expected[i].OutputsLen, len(tx.BaseTx().Outputs)) + assert.Equal(t, expected[i].AttributesLen, len(tx.BaseTx().Attributes)) + assert.Equal(t, expected[i].WitnessesLen, len(tx.BaseTx().Witnesses)) + } + + assert.Equal(t, len(expected), len(b.Txs)) + + // Block specific tests + assert.Equal(t, 0, int(b.Version)) + assert.Equal(t, "f4889276813c65c059cb54612e9e51b1b8fd91ee799e03b638bfade812df33ba", b.PrevHash.ReverseString()) + assert.Equal(t, "ef7241eb3dc1df2c95dc9bed9dea2814b62e61286c22d77e07847a9b109224a2", b.MerkleRoot.ReverseString()) + assert.Equal(t, 1527894405, int(b.Timestamp)) + assert.Equal(t, 2340363, int(b.Index)) + + nextConsensus, err := address.FromUint160(b.NextConsensus) + assert.Equal(t, nil, err) + assert.Equal(t, "APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR", nextConsensus) + + assert.Equal(t, "4012afae6df64195041e4764b57caa9e27fc2cfc596833163904136ec95816d104b44b3737d0e9f6b1b4445cd3b6a5cc80f6b0935675bc44dba44415eb309832b3404dc95bcf85e4635556a1d618e4ce947b26972992ed74788df5f9501b850ac0b40b7112d1ff30e4ade00369e16f0d13932d1ba76725e7682db072f8e2cd7752b840d12bb7dd45dd3b0e2098db5c67b6de55b7c40164937491fcaca1239b25860251224ead23ab232add78ccccd347239eae50ffc98f50b2a84c60ec5c3d284647a7406fabf6ca241b759af6b71080c0dfad7395632e989226a7e52f8cd2c133aeb2226e6e1aea47666fd81f578405a9f9bbd9d0bc523c3a44d7a5099ddc649feabe5f406188b8ee478731a89beeb76fdbd108eb0071b8f2b8678f40c5a1f387a491314336783255dee8cc5af4bf914dfeaacecc318fc13e02262658e39e8ce0631941b1", hex.EncodeToString(b.Witness.InvocationScript)) + assert.Equal(t, "552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae", hex.EncodeToString(b.Witness.VerificationScript)) + assert.Equal(t, "0006d3ff96e269f599eb1b5c5a527c218439e498dcc65b63794591bbcdc0516b", b.Hash.ReverseString()) + + bb, err := b.Bytes() + assert.Equal(t, nil, err) + + // test size of the block + assert.Equal(t, 7360, len(bb)) + + buf := new(bytes.Buffer) + + b.Encode(buf) + + assert.Equal(t, rawBlock, hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/wire/payload/transaction/base.go b/pkg/wire/payload/transaction/base.go index 541e958e4..567e907d8 100644 --- a/pkg/wire/payload/transaction/base.go +++ b/pkg/wire/payload/transaction/base.go @@ -18,11 +18,8 @@ type decodeExclusiveFields func(br *util.BinReader) type Transactioner interface { Encode(w io.Writer) error Decode(r io.Reader) error + BaseTx() *Base ID() (util.Uint256, error) - Bytes() []byte - UTXOs() []*Output - TXOs() []*Input - Witness() []*Witness } // Base transaction is the template for all other transactions @@ -199,17 +196,7 @@ func (b *Base) Bytes() []byte { return buf.Bytes() } -// UTXOs returns the outputs in the tx -func (b *Base) UTXOs() []*Output { - return b.Outputs -} - -// TXOs returns the inputs in the tx -func (b *Base) TXOs() []*Input { - return b.Inputs -} - -// Witness returns the witnesses in the tx -func (b *Base) Witness() []*Witness { - return b.Witnesses +// BaseTx returns the Base object in a transaction +func (b *Base) BaseTx() *Base { + return b } diff --git a/pkg/wire/util/address/address.go b/pkg/wire/util/address/address.go index 84daf5345..12f09c63a 100644 --- a/pkg/wire/util/address/address.go +++ b/pkg/wire/util/address/address.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "github.com/CityOfZion/neo-go/pkg/crypto/base58" + "github.com/CityOfZion/neo-go/pkg/wire/util" ) // ToScriptHash converts an address to a script hash @@ -17,3 +18,21 @@ func ToScriptHash(address string) string { scriptHash := (decodedAddressAsHex[2:42]) return scriptHash } + +// FromUint160 returns the "NEO address" from the given +// Uint160. +func FromUint160(u util.Uint160) (string, error) { + // Dont forget to prepend the Address version 0x17 (23) A + b := append([]byte{0x17}, u.Bytes()...) + return base58.CheckEncode(b) +} + +// Uint160Decode attempts to decode the given NEO address string +// into an Uint160. +func Uint160Decode(s string) (u util.Uint160, err error) { + b, err := base58.CheckDecode(s) + if err != nil { + return u, err + } + return util.Uint160DecodeBytes(b[1:21]) +} From 24cd21bd8cb713e6ffd1d3c93bd2b1059275052c Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Tue, 26 Mar 2019 22:19:41 +0100 Subject: [PATCH 061/117] VM:Implement THROW opcode (#219) [VM] - Changed vmstate from HALT to FAULT in Sub opcode - Implemented THROW opcode + tests - Renamed TestSimpleRun test to TestThrowIfNot --- pkg/vm/vm_ops.go | 1 + pkg/vm/vm_ops_exceptions.go | 6 ++++++ pkg/vm/vm_ops_maths.go | 2 +- pkg/vm/vm_test.go | 29 ++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 39b796389..d8b032ebc 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -12,6 +12,7 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.RET: RET, stack.EQUAL: EQUAL, stack.THROWIFNOT: THROWIFNOT, + stack.THROW: THROW, } func init() { diff --git a/pkg/vm/vm_ops_exceptions.go b/pkg/vm/vm_ops_exceptions.go index dd09cfb60..01dd74616 100644 --- a/pkg/vm/vm_ops_exceptions.go +++ b/pkg/vm/vm_ops_exceptions.go @@ -31,3 +31,9 @@ func THROWIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati } return NONE, nil } + +// THROW returns a FAULT VM state. This indicate that there is an error in the +// current context loaded program. +func THROW(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + return FAULT, errors.New("the execution of the script program end with an error") +} diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index a15596a30..612c7630c 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -34,7 +34,7 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst } res, err := operandB.Sub(operandA) if err != nil { - return HALT, err + return FAULT, err } ctx.Estack.Push(res) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index adb8b5db3..bc38bfaed 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -61,7 +61,7 @@ func TestPushAdd(t *testing.T) { } -func TestSimpleRun(t *testing.T) { +func TestThrowIfNot(t *testing.T) { // Program pushes 20 and 34 to the stack // Adds them together @@ -89,6 +89,33 @@ func TestSimpleRun(t *testing.T) { // ResultStack should be nil assert.Equal(t, -1, vm.ResultStack.Len()) + // InvocationStack should be empty + assert.Equal(t, 0, vm.InvocationStack.Len()) + +} + +func TestThrow(t *testing.T) { + + // Program pushes 20 to the stack + // exits with an error + + // Push(20) + // THROW + + builder := stack.NewBuilder() + builder.EmitInt(20).EmitOpcode(stack.THROW) + + // Pass program to VM + vm := NewVM(builder.Bytes()) + + // Runs vm with program + _, err := vm.Run() + assert.NotNil(t, err) + + ctx, err := vm.InvocationStack.CurrentContext() + assert.Equal(t, nil, err) + assert.Equal(t, 1, ctx.Estack.Len()) + assert.Equal(t, -1, ctx.Astack.Len()) } // returns true if the value at the top of the evaluation stack is a integer From d8e399f67d6188881090620b7507cf8659e8b99d Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Wed, 27 Mar 2019 00:15:13 +0100 Subject: [PATCH 062/117] VM: Implement INC, DEC opcode (#231) [VM] - Implemented INC, DEC opcode --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 52 +++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index d8b032ebc..016c96389 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.INC: Inc, + stack.DEC: Dec, stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 612c7630c..77a4c2622 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/CityOfZion/neo-go/pkg/vm/stack" ) @@ -42,6 +44,56 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Inc increments the stack Item's value by 1. +// Returns an error if the item cannot be casted to an integer +// or if 1 cannot be added to the item +func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + one, err := stack.NewInt(big.NewInt(1)) + if err != nil { + return FAULT, err + } + + res, err := i.Add(one) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Dec decrements the stack Item's value by 1. +// Returns an error if the item cannot be casted to an integer +// or if 1 cannot be subtracted to the item +func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + one, err := stack.NewInt(big.NewInt(1)) + if err != nil { + return FAULT, err + } + + res, err := i.Sub(one) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { operandA, err := ctx.Estack.PopInt() if err != nil { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 4964e6923..f50c27916 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -8,6 +8,56 @@ import ( "github.com/stretchr/testify/assert" ) +func TestIncOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.INC, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(21), item.Value().Int64()) +} + +func TestDecOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.DEC, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(19), item.Value().Int64()) +} + func TestAddOp(t *testing.T) { v := VM{} From 14e0ab48dd471c2bf8e4465ab90de0524ac5161c Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 19:30:36 +0100 Subject: [PATCH 063/117] VM: Implemented SIGN, NEGATE opcode (#232) * Implemented SIGN, NEGATE opcode --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 45 +++++++++++++++++++++++++++++++-- pkg/vm/vm_ops_maths_test.go | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 016c96389..9ba44ccbd 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.SIGN: Sign, + stack.NEGATE: Negate, stack.ADD: Add, stack.SUB: Sub, stack.PUSHBYTES1: PushNBytes, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 77a4c2622..e15e25c3e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -46,7 +46,7 @@ func Sub(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst // Inc increments the stack Item's value by 1. // Returns an error if the item cannot be casted to an integer -// or if 1 cannot be added to the item +// or if 1 cannot be added to the item. func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { i, err := ctx.Estack.PopInt() @@ -71,7 +71,7 @@ func Inc(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst // Dec decrements the stack Item's value by 1. // Returns an error if the item cannot be casted to an integer -// or if 1 cannot be subtracted to the item +// or if 1 cannot be subtracted to the item. func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { i, err := ctx.Estack.PopInt() @@ -94,6 +94,47 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Sign puts the sign of the top stack Item on top of the stack. +// If value is negative, put -1; +// If positive, put 1; +// If value is zero, put 0. +func Sign(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + s := int64(i.Value().Sign()) + sign, err := stack.NewInt(big.NewInt(s)) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(sign) + + return NONE, nil +} + +// Negate flips the sign of the stack Item. +func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + a := big.NewInt(0).Neg(i.Value()) + b, err := stack.NewInt(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b) + + return NONE, nil +} + func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { operandA, err := ctx.Estack.PopInt() if err != nil { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index f50c27916..4324aec97 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -117,3 +117,53 @@ func TestSubOp(t *testing.T) { assert.Equal(t, int64(-10), item.Value().Int64()) } + +func TestSignOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.SIGN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(-1), item.Value().Int64()) +} + +func TestNegateOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NEGATE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(20), item.Value().Int64()) +} From dc5de1fa6db9655c11d31215e0c38c169fd418e9 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 28 Mar 2019 19:09:55 +0000 Subject: [PATCH 064/117] [Peer] Refactor (#240) [Peer] - Closes #239 - moved response handlers to their own functions - removed DefaultConfig from LocalConfig file - passed peer as a parameter to all response handlers - added peer start height - refactored NewPeer function to be more concise and clear - removed empty lines at end of functions - Added AddMessage/RemoveMessage for Detector in outgoing and ingoing requests for Block and Headers --- pkg/peer/config.go | 34 ++----- pkg/peer/peer.go | 167 ++++++----------------------------- pkg/peer/peer_test.go | 31 ++----- pkg/peer/responsehandlers.go | 111 +++++++++++++++++++++++ pkg/peer/stall/stall.go | 16 ++-- pkg/peer/stall/stall_test.go | 2 +- 6 files changed, 165 insertions(+), 196 deletions(-) create mode 100644 pkg/peer/responsehandlers.go diff --git a/pkg/peer/config.go b/pkg/peer/config.go index e4794c282..47c688741 100644 --- a/pkg/peer/config.go +++ b/pkg/peer/config.go @@ -14,34 +14,18 @@ type LocalConfig struct { ProtocolVer protocol.Version Relay bool Port uint16 - // pointer to config will keep the startheight updated for each version - //Message we plan to send - StartHeight func() uint32 + + // pointer to config will keep the startheight updated + StartHeight func() uint32 + + // Response Handlers OnHeader func(*Peer, *payload.HeadersMessage) - OnGetHeaders func(msg *payload.GetHeadersMessage) // returns HeaderMessage + OnGetHeaders func(*Peer, *payload.GetHeadersMessage) OnAddr func(*Peer, *payload.AddrMessage) OnGetAddr func(*Peer, *payload.GetAddrMessage) OnInv func(*Peer, *payload.InvMessage) - OnGetData func(msg *payload.GetDataMessage) + OnGetData func(*Peer, *payload.GetDataMessage) OnBlock func(*Peer, *payload.BlockMessage) - OnGetBlocks func(msg *payload.GetBlocksMessage) + OnGetBlocks func(*Peer, *payload.GetBlocksMessage) + OnTx func(*Peer, *payload.TXMessage) } - -// func DefaultConfig() LocalConfig { -// return LocalConfig{ -// Net: protocol.MainNet, -// UserAgent: "NEO-GO-Default", -// Services: protocol.NodePeerService, -// Nonce: 1200, -// ProtocolVer: 0, -// Relay: false, -// Port: 10332, -// // pointer to config will keep the startheight updated for each version -// //Message we plan to send -// StartHeight: DefaultHeight, -// } -// } - -// func DefaultHeight() uint32 { -// return 10 -// } diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go index 035dad835..94f9c685c 100644 --- a/pkg/peer/peer.go +++ b/pkg/peer/peer.go @@ -58,6 +58,8 @@ type Peer struct { config LocalConfig conn net.Conn + startHeight uint32 + // atomic vals disconnected int32 @@ -84,20 +86,18 @@ type Peer struct { // NewPeer returns a new NEO peer func NewPeer(con net.Conn, inbound bool, cfg LocalConfig) *Peer { - p := Peer{} - p.inch = make(chan func(), inputBufferSize) - p.outch = make(chan func(), outputBufferSize) - p.quitch = make(chan struct{}, 1) - p.inbound = inbound - p.config = cfg - p.conn = con - p.createdAt = time.Now() - p.addr = p.conn.RemoteAddr().String() - - p.Detector = stall.NewDetector(responseTime, tickerInterval) - - // TODO: set the unchangeable states - return &p + return &Peer{ + inch: make(chan func(), inputBufferSize), + outch: make(chan func(), outputBufferSize), + quitch: make(chan struct{}, 1), + inbound: inbound, + config: cfg, + conn: con, + createdAt: time.Now(), + startHeight: 0, + addr: con.RemoteAddr().String(), + Detector: stall.NewDetector(responseTime, tickerInterval), + } } // Write to a peer @@ -125,7 +125,6 @@ func (p *Peer) Disconnect() { p.conn.Close() fmt.Println("Disconnected Peer with address", p.RemoteAddr().String()) - } // Port returns the peers port @@ -138,6 +137,11 @@ func (p *Peer) CreatedAt() time.Time { return p.createdAt } +// Height returns the latest recorded height of this peer +func (p *Peer) Height() uint32 { + return p.startHeight +} + // CanRelay returns true, if the peer can relay information func (p *Peer) CanRelay() bool { return p.relay @@ -163,11 +167,6 @@ func (p *Peer) Inbound() bool { return p.inbound } -// UserAgent returns this nodes, useragent -func (p *Peer) UserAgent() string { - return p.config.UserAgent -} - // IsVerackReceived returns true, if this node has // received a verack from this peer func (p *Peer) IsVerackReceived() bool { @@ -204,7 +203,6 @@ func (p *Peer) Run() error { //go p.PingLoop() // since it is not implemented. It will disconnect all other impls. return nil - } // StartProtocol run as a go-routine, will act as our queue for messages @@ -305,128 +303,17 @@ func (p *Peer) WriteLoop() { } } -// OnGetData is called when a GetData message is received -func (p *Peer) OnGetData(msg *payload.GetDataMessage) { - - p.inch <- func() { - if p.config.OnInv != nil { - p.config.OnGetData(msg) - } - fmt.Println("That was an getdata Message please pass func down through config", msg.Command()) - } -} - -//OnTX is callwed when a TX message is received -func (p *Peer) OnTX(msg *payload.TXMessage) { - - p.inch <- func() { - getdata, err := payload.NewGetDataMessage(payload.InvTypeTx) - if err != nil { - fmt.Println("Eor", err) - } - id, err := msg.Tx.ID() - getdata.AddHash(id) - p.Write(getdata) - } -} - -// OnInv is called when a Inv message is received -func (p *Peer) OnInv(msg *payload.InvMessage) { - - p.inch <- func() { - if p.config.OnInv != nil { - p.config.OnInv(p, msg) - } - fmt.Println("That was an inv Message please pass func down through config", msg.Command()) - } -} - -// OnGetHeaders is called when a GetHeaders message is received -func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) { - p.inch <- func() { - if p.config.OnGetHeaders != nil { - p.config.OnGetHeaders(msg) - } - fmt.Println("That was a getheaders message, please pass func down through config", msg.Command()) - - } -} - -// OnAddr is called when a Addr message is received -func (p *Peer) OnAddr(msg *payload.AddrMessage) { - p.inch <- func() { - if p.config.OnAddr != nil { - p.config.OnAddr(p, msg) - } - fmt.Println("That was a addr message, please pass func down through config", msg.Command()) - - } -} - -// OnGetAddr is called when a GetAddr message is received -func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) { - p.inch <- func() { - if p.config.OnGetAddr != nil { - p.config.OnGetAddr(p, msg) - } - fmt.Println("That was a getaddr message, please pass func down through config", msg.Command()) - - } -} - -// OnGetBlocks is called when a GetBlocks message is received -func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) { - p.inch <- func() { - if p.config.OnGetBlocks != nil { - p.config.OnGetBlocks(msg) - } - fmt.Println("That was a getblocks message, please pass func down through config", msg.Command()) - } -} - -// OnBlocks is called when a Blocks message is received -func (p *Peer) OnBlocks(msg *payload.BlockMessage) { - p.inch <- func() { - if p.config.OnBlock != nil { - p.config.OnBlock(p, msg) - } - } -} - -// OnVersion Listener will be called -// during the handshake, any error checking should be done here for the versionMessage. -// This should only ever be called during the handshake. Any other place and the peer will disconnect. -func (p *Peer) OnVersion(msg *payload.VersionMessage) error { - if msg.Nonce == p.config.Nonce { - p.conn.Close() - return errors.New("Self connection, disconnecting Peer") - } - p.versionKnown = true - p.port = msg.Port - p.services = msg.Services - p.userAgent = string(msg.UserAgent) - p.createdAt = time.Now() - p.relay = msg.Relay - return nil -} - -// OnHeaders is called when a Headers message is received -func (p *Peer) OnHeaders(msg *payload.HeadersMessage) { - fmt.Println("We have received the headers") - p.inch <- func() { - if p.config.OnHeader != nil { - p.config.OnHeader(p, msg) - } - } -} +// Outgoing Requests // RequestHeaders will write a getheaders to this peer func (p *Peer) RequestHeaders(hash util.Uint256) error { c := make(chan error, 0) p.outch <- func() { - p.Detector.AddMessage(command.GetHeaders) getHeaders, err := payload.NewGetHeadersMessage([]util.Uint256{hash}, util.Uint256{}) err = p.Write(getHeaders) + if err != nil { + p.Detector.AddMessage(command.GetHeaders) + } c <- err } return <-c @@ -437,17 +324,19 @@ func (p *Peer) RequestBlocks(hashes []util.Uint256) error { c := make(chan error, 0) p.outch <- func() { - p.Detector.AddMessage(command.GetData) getdata, err := payload.NewGetDataMessage(payload.InvTypeBlock) err = getdata.AddHashes(hashes) if err != nil { c <- err return } + err = p.Write(getdata) + if err != nil { + p.Detector.AddMessage(command.GetData) + } + c <- err } - return <-c - } diff --git a/pkg/peer/peer_test.go b/pkg/peer/peer_test.go index adcb4d342..fd27aeb55 100644 --- a/pkg/peer/peer_test.go +++ b/pkg/peer/peer_test.go @@ -1,7 +1,6 @@ package peer_test import ( - "fmt" "net" "testing" "time" @@ -21,11 +20,11 @@ func returnConfig() peer.LocalConfig { OnAddr := func(p *peer.Peer, msg *payload.AddrMessage) {} OnHeader := func(p *peer.Peer, msg *payload.HeadersMessage) {} - OnGetHeaders := func(msg *payload.GetHeadersMessage) {} + OnGetHeaders := func(p *peer.Peer, msg *payload.GetHeadersMessage) {} OnInv := func(p *peer.Peer, msg *payload.InvMessage) {} - OnGetData := func(msg *payload.GetDataMessage) {} + OnGetData := func(p *peer.Peer, msg *payload.GetDataMessage) {} OnBlock := func(p *peer.Peer, msg *payload.BlockMessage) {} - OnGetBlocks := func(msg *payload.GetBlocksMessage) {} + OnGetBlocks := func(p *peer.Peer, msg *payload.GetBlocksMessage) {} return peer.LocalConfig{ Net: protocol.MainNet, @@ -157,17 +156,9 @@ func TestConfigurations(t *testing.T) { assert.Equal(t, config.Services, p.Services()) - assert.Equal(t, config.UserAgent, p.UserAgent()) - assert.Equal(t, config.Relay, p.CanRelay()) assert.WithinDuration(t, time.Now(), p.CreatedAt(), 1*time.Second) - -} - -func TestHandshakeCancelled(t *testing.T) { - // These are the conditions which should invalidate the handshake. - // Make sure peer is disconnected. } func TestPeerDisconnect(t *testing.T) { @@ -178,21 +169,17 @@ func TestPeerDisconnect(t *testing.T) { inbound := true config := returnConfig() p := peer.NewPeer(conn, inbound, config) - fmt.Println("Calling disconnect") + p.Disconnect() - fmt.Println("Disconnect finished calling") - verack, _ := payload.NewVerackMessage() + verack, err := payload.NewVerackMessage() + assert.Nil(t, err) - fmt.Println(" We good here") + err = p.Write(verack) + assert.NotNil(t, err) - err := p.Write(verack) - - assert.NotEqual(t, err, nil) - - // Check if Stall detector is still running + // Check if stall detector is still running _, ok := <-p.Detector.Quitch assert.Equal(t, ok, false) - } func TestNotifyDisconnect(t *testing.T) { diff --git a/pkg/peer/responsehandlers.go b/pkg/peer/responsehandlers.go new file mode 100644 index 000000000..303ee0759 --- /dev/null +++ b/pkg/peer/responsehandlers.go @@ -0,0 +1,111 @@ +package peer + +import ( + "errors" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +// OnGetData is called when a GetData message is received +func (p *Peer) OnGetData(msg *payload.GetDataMessage) { + p.inch <- func() { + if p.config.OnInv != nil { + p.config.OnGetData(p, msg) + } + } +} + +//OnTX is called when a TX message is received +func (p *Peer) OnTX(msg *payload.TXMessage) { + p.inch <- func() { + p.inch <- func() { + if p.config.OnTx != nil { + p.config.OnTx(p, msg) + } + } + } +} + +// OnInv is called when a Inv message is received +func (p *Peer) OnInv(msg *payload.InvMessage) { + p.inch <- func() { + if p.config.OnInv != nil { + p.config.OnInv(p, msg) + } + } +} + +// OnGetHeaders is called when a GetHeaders message is received +func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) { + p.inch <- func() { + if p.config.OnGetHeaders != nil { + p.config.OnGetHeaders(p, msg) + } + } +} + +// OnAddr is called when a Addr message is received +func (p *Peer) OnAddr(msg *payload.AddrMessage) { + p.inch <- func() { + if p.config.OnAddr != nil { + p.config.OnAddr(p, msg) + } + } +} + +// OnGetAddr is called when a GetAddr message is received +func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) { + p.inch <- func() { + if p.config.OnGetAddr != nil { + p.config.OnGetAddr(p, msg) + } + } +} + +// OnGetBlocks is called when a GetBlocks message is received +func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) { + p.inch <- func() { + if p.config.OnGetBlocks != nil { + p.config.OnGetBlocks(p, msg) + } + } +} + +// OnBlocks is called when a Blocks message is received +func (p *Peer) OnBlocks(msg *payload.BlockMessage) { + p.Detector.RemoveMessage(msg.Command()) + p.inch <- func() { + if p.config.OnBlock != nil { + p.config.OnBlock(p, msg) + } + } +} + +// OnHeaders is called when a Headers message is received +func (p *Peer) OnHeaders(msg *payload.HeadersMessage) { + p.Detector.RemoveMessage(msg.Command()) + p.inch <- func() { + if p.config.OnHeader != nil { + p.config.OnHeader(p, msg) + } + } +} + +// OnVersion Listener will be called +// during the handshake, any error checking should be done here for the versionMessage. +// This should only ever be called during the handshake. Any other place and the peer will disconnect. +func (p *Peer) OnVersion(msg *payload.VersionMessage) error { + if msg.Nonce == p.config.Nonce { + p.conn.Close() + return errors.New("self connection, disconnecting Peer") + } + p.versionKnown = true + p.port = msg.Port + p.services = msg.Services + p.userAgent = string(msg.UserAgent) + p.createdAt = time.Now() + p.relay = msg.Relay + p.startHeight = msg.StartHeight + return nil +} diff --git a/pkg/peer/stall/stall.go b/pkg/peer/stall/stall.go index fc19891fa..e69bcb442 100644 --- a/pkg/peer/stall/stall.go +++ b/pkg/peer/stall/stall.go @@ -61,6 +61,7 @@ func (d *Detector) loop() { d.lock.RUnlock() for _, deadline := range resp { if now.After(deadline) { + fmt.Println(resp) fmt.Println("Deadline passed") return } @@ -99,7 +100,7 @@ func (d *Detector) AddMessage(cmd command.Type) { // peer. This will remove the pendingresponse message from the map. // The command passed through is the command we received func (d *Detector) RemoveMessage(cmd command.Type) { - cmds := d.addMessage(cmd) + cmds := d.removeMessage(cmd) d.lock.Lock() for _, cmd := range cmds { delete(d.responses, cmd) @@ -137,10 +138,8 @@ func (d *Detector) addMessage(cmd command.Type) []command.Type { case command.GetAddr: // We now will expect a Headers Message cmds = append(cmds, command.Addr) - case command.GetData: // We will now expect a block/tx message - // We can optimise this by including the exact inventory type, however it is not needed cmds = append(cmds, command.Block) cmds = append(cmds, command.TX) case command.GetBlocks: @@ -159,19 +158,18 @@ func (d *Detector) removeMessage(cmd command.Type) []command.Type { switch cmd { case command.Block: - // We will now expect a block/tx message + // We will now remove a block and tx message cmds = append(cmds, command.Block) cmds = append(cmds, command.TX) case command.TX: - // We will now expect a block/tx message + // We will now remove a block and tx message cmds = append(cmds, command.Block) cmds = append(cmds, command.TX) - case command.GetBlocks: - // we will now expect a inv message - cmds = append(cmds, command.Inv) - default: + case command.Verack: // We will now expect a verack cmds = append(cmds, cmd) + default: + cmds = append(cmds, cmd) } return cmds } diff --git a/pkg/peer/stall/stall_test.go b/pkg/peer/stall/stall_test.go index 4d5494e12..83de4e3a0 100644 --- a/pkg/peer/stall/stall_test.go +++ b/pkg/peer/stall/stall_test.go @@ -22,7 +22,7 @@ func TestAddRemoveMessage(t *testing.T) { assert.Equal(t, 1, len(mp)) assert.IsType(t, time.Time{}, mp[command.GetAddr]) - d.RemoveMessage(command.GetAddr) + d.RemoveMessage(command.Addr) mp = d.GetMessages() assert.Equal(t, 0, len(mp)) From afe670f17895dfbadfd9521d4f7e70418d68cde2 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 20:26:55 +0100 Subject: [PATCH 065/117] VM: Implement, ABS, NOT opcode (#233) * Implemented, ABS, NOT opcode --- pkg/vm/stack/Int.go | 12 ++++++++++ pkg/vm/stack/boolean.go | 6 +++++ pkg/vm/stack/stack.go | 10 ++++++++ pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 33 ++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 47 +++++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ac9a4cdba..ebd83cea1 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -92,3 +92,15 @@ func (i *Int) Boolean() (*Boolean, error) { func (i *Int) Value() *big.Int { return i.val } + +// Abs returns a stack integer whose underlying value is +// the absolute value of the original stack integer. +func (i *Int) Abs() (*Int, error) { + a := big.NewInt(0).Abs(i.Value()) + b, err := NewInt(a) + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 66e3647e5..93fabd84e 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -24,3 +24,9 @@ func (b *Boolean) Boolean() (*Boolean, error) { func (b *Boolean) Value() bool { return b.val } + +// Not returns a Boolean whose underlying value is flipped. +// If the value is True, it is flipped to False and viceversa +func (b *Boolean) Not() *Boolean { + return NewBoolean(!b.Value()) +} diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 8d1ac5b78..c832a9f79 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -148,3 +148,13 @@ func (ras *RandomAccess) PopByteArray() (*ByteArray, error) { } return item.ByteArray() } + +// PopBoolean will remove the last stack item that was added +// and cast it to a Boolean. +func (ras *RandomAccess) PopBoolean() (*Boolean, error) { + item, err := ras.Pop() + if err != nil { + return nil, err + } + return item.Boolean() +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 9ba44ccbd..135936e05 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.ABS: Abs, + stack.NOT: Not, stack.SIGN: Sign, stack.NEGATE: Negate, stack.ADD: Add, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index e15e25c3e..cbd82d670 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,39 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Abs pops an integer off of the stack and pushes its absolute value onto the stack. +// Returns an error if the popped value is not an integer or if the absolute value cannot be taken +func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + a, err := i.Abs() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(a) + + return NONE, nil +} + +// Not flips the stack Item's value. +// If the value is True, it is flipped to False and viceversa. +func Not(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b.Not()) + + return NONE, nil +} + // Sign puts the sign of the top stack Item on top of the stack. // If value is negative, put -1; // If positive, put 1; diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 4324aec97..68f7ae860 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,53 @@ func TestSubOp(t *testing.T) { } +func TestAbsOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(-20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.ABS, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(20), item.Value().Int64()) +} + +func TestNotOp(t *testing.T) { + + v := VM{} + + b := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(b) + + v.executeOp(stack.NOT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestSignOp(t *testing.T) { v := VM{} From 8afec1ea45cf18c6e5237271ea3c70b285b1722d Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 28 Mar 2019 19:46:31 +0000 Subject: [PATCH 066/117] [Peer] Add peer manager (#241) * [PeerMgr] - Add basic peer manager --- pkg/peer/peer.go | 3 +- pkg/peermgr/peermgr.go | 122 ++++++++++++++++++++++++ pkg/peermgr/peermgr_test.go | 179 ++++++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 pkg/peermgr/peermgr.go create mode 100644 pkg/peermgr/peermgr_test.go diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go index 94f9c685c..d1594d6de 100644 --- a/pkg/peer/peer.go +++ b/pkg/peer/peer.go @@ -175,11 +175,10 @@ func (p *Peer) IsVerackReceived() bool { //NotifyDisconnect returns once the peer has disconnected // Blocking -func (p *Peer) NotifyDisconnect() bool { +func (p *Peer) NotifyDisconnect() { fmt.Println("Peer has not disconnected yet") <-p.quitch fmt.Println("Peer has just disconnected") - return true } //End of Exposed API functions// diff --git a/pkg/peermgr/peermgr.go b/pkg/peermgr/peermgr.go new file mode 100644 index 000000000..047a109c3 --- /dev/null +++ b/pkg/peermgr/peermgr.go @@ -0,0 +1,122 @@ +package peermgr + +import ( + "errors" + "sync" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + //ErrNoAvailablePeers is returned when a request for data from a peer is invoked + // but there are no available peers to request data from + ErrNoAvailablePeers = errors.New("there are no available peers to interact with") + + // ErrUnknownPeer is returned when a peer that the peer manager does not know about + // sends a message to this node + ErrUnknownPeer = errors.New("this peer has not been registered with the peer manager") +) + +//mPeer represents a peer that is managed by the peer manager +type mPeer interface { + Disconnect() + RequestBlocks([]util.Uint256) error + RequestHeaders(util.Uint256) error + NotifyDisconnect() +} + +type peerstats struct { + requests map[command.Type]bool +} + +//PeerMgr manages all peers that the node is connected to +type PeerMgr struct { + pLock sync.RWMutex + peers map[mPeer]peerstats +} + +//New returns a new peermgr object +func New() *PeerMgr { + return &PeerMgr{ + peers: make(map[mPeer]peerstats), + } +} + +// AddPeer adds a peer to the list of managed peers +func (pmgr *PeerMgr) AddPeer(peer mPeer) { + + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() + if _, exists := pmgr.peers[peer]; exists { + return + } + pmgr.peers[peer] = peerstats{requests: make(map[command.Type]bool)} + go pmgr.onDisconnect(peer) +} + +//MsgReceived notifies the peer manager that we have received a +// message from a peer +func (pmgr *PeerMgr) MsgReceived(peer mPeer, cmd command.Type) error { + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() + val, ok := pmgr.peers[peer] + if !ok { + + go func() { + peer.NotifyDisconnect() + }() + + peer.Disconnect() + return ErrUnknownPeer + } + val.requests[cmd] = false + + return nil +} + +// Len returns the amount of peers that the peer manager +//currently knows about +func (pmgr *PeerMgr) Len() int { + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() + return len(pmgr.peers) +} + +// RequestBlock will request a block from the most +// available peer. Then update it's stats, so we know that +// this peer is busy +func (pmgr *PeerMgr) RequestBlock(hash util.Uint256) error { + return pmgr.callPeerForCmd(command.Block, func(p mPeer) error { + return p.RequestBlocks([]util.Uint256{hash}) + }) +} + +// RequestHeaders will request a headers from the most available peer. +func (pmgr *PeerMgr) RequestHeaders(hash util.Uint256) error { + return pmgr.callPeerForCmd(command.Headers, func(p mPeer) error { + return p.RequestHeaders(hash) + }) +} + +func (pmgr *PeerMgr) callPeerForCmd(cmd command.Type, f func(p mPeer) error) error { + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() + for peer, stats := range pmgr.peers { + if !stats.requests[cmd] { + stats.requests[cmd] = true + return f(peer) + } + } + return ErrNoAvailablePeers +} +func (pmgr *PeerMgr) onDisconnect(p mPeer) { + + // Blocking until peer is disconnected + p.NotifyDisconnect() + + pmgr.pLock.Lock() + delete(pmgr.peers, p) + pmgr.pLock.Unlock() +} diff --git a/pkg/peermgr/peermgr_test.go b/pkg/peermgr/peermgr_test.go new file mode 100644 index 000000000..86d5be41c --- /dev/null +++ b/pkg/peermgr/peermgr_test.go @@ -0,0 +1,179 @@ +package peermgr + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/command" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/stretchr/testify/assert" +) + +type peer struct { + quit chan bool + nonce int + disconnected bool + blockRequested int + headersRequested int +} + +func (p *peer) Disconnect() { + p.disconnected = true + p.quit <- true +} +func (p *peer) RequestBlocks([]util.Uint256) error { + p.blockRequested++ + return nil +} +func (p *peer) RequestHeaders(util.Uint256) error { + p.headersRequested++ + return nil +} +func (p *peer) NotifyDisconnect() { + <-p.quit +} + +func TestAddPeer(t *testing.T) { + pmgr := New() + + peerA := &peer{nonce: 1} + peerB := &peer{nonce: 2} + peerC := &peer{nonce: 3} + + pmgr.AddPeer(peerA) + pmgr.AddPeer(peerB) + pmgr.AddPeer(peerC) + pmgr.AddPeer(peerC) + + assert.Equal(t, 3, pmgr.Len()) +} + +func TestRequestBlocks(t *testing.T) { + pmgr := New() + + peerA := &peer{nonce: 1} + peerB := &peer{nonce: 2} + peerC := &peer{nonce: 3} + + pmgr.AddPeer(peerA) + pmgr.AddPeer(peerB) + pmgr.AddPeer(peerC) + + err := pmgr.RequestBlock(util.Uint256{}) + assert.Nil(t, err) + + err = pmgr.RequestBlock(util.Uint256{}) + assert.Nil(t, err) + + err = pmgr.RequestBlock(util.Uint256{}) + assert.Nil(t, err) + + // Since the peer manager did not get a MsgReceived + // in between the block requests + // a request should be sent to all peers + + assert.Equal(t, 1, peerA.blockRequested) + assert.Equal(t, 1, peerB.blockRequested) + assert.Equal(t, 1, peerC.blockRequested) + + // Since the peer manager still has not received a MsgReceived + // another call to request blocks, will return a NoAvailablePeerError + + err = pmgr.RequestBlock(util.Uint256{}) + assert.Equal(t, ErrNoAvailablePeers, err) + + // If we tell the peer manager that peerA has given us a block + // then send another BlockRequest. It will go to peerA + // since the other two peers are still busy with their + // block requests + + pmgr.MsgReceived(peerA, command.Block) + err = pmgr.RequestBlock(util.Uint256{}) + assert.Nil(t, err) + + assert.Equal(t, 2, peerA.blockRequested) + assert.Equal(t, 1, peerB.blockRequested) + assert.Equal(t, 1, peerC.blockRequested) +} +func TestRequestHeaders(t *testing.T) { + pmgr := New() + + peerA := &peer{nonce: 1} + peerB := &peer{nonce: 2} + peerC := &peer{nonce: 3} + + pmgr.AddPeer(peerA) + pmgr.AddPeer(peerB) + pmgr.AddPeer(peerC) + + err := pmgr.RequestHeaders(util.Uint256{}) + assert.Nil(t, err) + + err = pmgr.RequestHeaders(util.Uint256{}) + assert.Nil(t, err) + + err = pmgr.RequestHeaders(util.Uint256{}) + assert.Nil(t, err) + + // Since the peer manager did not get a MsgReceived + // in between the header requests + // a request should be sent to all peers + + assert.Equal(t, 1, peerA.headersRequested) + assert.Equal(t, 1, peerB.headersRequested) + assert.Equal(t, 1, peerC.headersRequested) + + // Since the peer manager still has not received a MsgReceived + // another call to request header, will return a NoAvailablePeerError + + err = pmgr.RequestHeaders(util.Uint256{}) + assert.Equal(t, ErrNoAvailablePeers, err) + + // If we tell the peer manager that peerA has given us a block + // then send another BlockRequest. It will go to peerA + // since the other two peers are still busy with their + // block requests + + err = pmgr.MsgReceived(peerA, command.Headers) + assert.Nil(t, err) + err = pmgr.RequestHeaders(util.Uint256{}) + assert.Nil(t, err) + + assert.Equal(t, 2, peerA.headersRequested) + assert.Equal(t, 1, peerB.headersRequested) + assert.Equal(t, 1, peerC.headersRequested) +} + +func TestUnknownPeer(t *testing.T) { + pmgr := New() + + unknownPeer := &peer{ + disconnected: false, + quit: make(chan bool), + } + + err := pmgr.MsgReceived(unknownPeer, command.Block) + assert.Equal(t, true, unknownPeer.disconnected) + assert.Equal(t, ErrUnknownPeer, err) +} + +func TestNotifyDisconnect(t *testing.T) { + pmgr := New() + + peerA := &peer{ + nonce: 1, + quit: make(chan bool), + } + + pmgr.AddPeer(peerA) + + if pmgr.Len() != 1 { + t.Fail() + } + + peerA.Disconnect() + + if pmgr.Len() != 0 { + t.Fail() + } +} From 139b770712246e9a630c9da950d5fbcf0395e73d Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 20:47:47 +0100 Subject: [PATCH 067/117] Implemented NZ, MUL opcode (#235) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 41 +++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 135936e05..5b199baa1 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.NZ: Nz, + stack.MUL: Mul, stack.ABS: Abs, stack.NOT: Not, stack.SIGN: Sign, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index cbd82d670..65d6b4af1 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,47 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Nz pops an integer from the stack. +// Then pushes a boolean to the stack which evaluates to true +// iff the integer was not zero. +// Returns an error if the popped item cannot be casted to an integer +// or if we cannot create a boolean. +func Nz(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + b, err := i.Boolean() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(b) + + return NONE, nil +} + +// Mul multiplies two stack Items together. +// Returns an error if either items cannot be casted to an integer +// or if integers cannot be multiplied together. +func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Mul(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 68f7ae860..700f17bcb 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,62 @@ func TestSubOp(t *testing.T) { } +func TestNzOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NZ, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) + +} + +func TestMulOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(20)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MUL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(400), item.Value().Int64()) + +} + func TestAbsOp(t *testing.T) { v := VM{} From 493d8f3d951cdefee2a970a5dd302f6d5b64597b Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 28 Mar 2019 20:23:50 +0000 Subject: [PATCH 068/117] [chain] Refactor, add chaincfg and database initialisation (#243) * [chain] - Add basic chain cfg parameters - Added logic to insert genesis block, if it is a fresh database - changed SaveBlock to ProcessBlock - changed SaveHeaders to ProcessHeaders - Changed parameter from a wire message to the payload, for header and block processing - Added check in chain for when the block is in the future, i.e. not at the tip of the chain - Added custom error returns, to distinguish between a database error and a validation error --- pkg/chain/chain.go | 107 +++++++++++++++++++++++----------- pkg/chain/errors.go | 19 ++++++ pkg/chaincfg/chaincfg.go | 44 ++++++++++++++ pkg/chaincfg/chaincfg_test.go | 13 +++++ 4 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 pkg/chain/errors.go create mode 100644 pkg/chaincfg/chaincfg.go create mode 100644 pkg/chaincfg/chaincfg_test.go diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index c1492cb8f..c44764388 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -1,9 +1,13 @@ package chain import ( - "errors" + "fmt" + "github.com/pkg/errors" + + "github.com/CityOfZion/neo-go/pkg/chaincfg" "github.com/CityOfZion/neo-go/pkg/wire/payload/transaction" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" "github.com/CityOfZion/neo-go/pkg/database" "github.com/CityOfZion/neo-go/pkg/wire/payload" @@ -12,58 +16,89 @@ import ( var ( // ErrBlockAlreadyExists happens when you try to save the same block twice ErrBlockAlreadyExists = errors.New("this block has already been saved in the database") + + // ErrFutureBlock happens when you try to save a block that is not the next block sequentially + ErrFutureBlock = errors.New("this is not the next block sequentially, that should be added to the chain") ) // Chain represents a blockchain instance type Chain struct { - db *Chaindb + Db *Chaindb } -//New returns a new chain instance -func New(db database.Database) *Chain { - return &Chain{ - db: &Chaindb{db}, +// New returns a new chain instance +func New(db database.Database, magic protocol.Magic) (*Chain, error) { + + chain := &Chain{ + Db: &Chaindb{db}, } + + // Get last header saved to see if this is a fresh database + _, err := chain.Db.GetLastHeader() + if err == nil { + return chain, nil + } + + if err != database.ErrNotFound { + return nil, err + } + + // We have a database.ErrNotFound. Insert the genesisBlock + fmt.Printf("Starting a fresh database for %s\n", magic.String()) + + params, err := chaincfg.NetParams(magic) + if err != nil { + return nil, err + } + err = chain.Db.saveHeader(¶ms.GenesisBlock.BlockBase) + if err != nil { + return nil, err + } + err = chain.Db.saveBlock(params.GenesisBlock, true) + if err != nil { + return nil, err + } + return chain, nil } -// SaveBlock verifies and saves the block in the database +// ProcessBlock verifies and saves the block in the database // XXX: for now we will just save without verifying the block // This function is called by the server and if an error is returned then // the server informs the sync manager to redownload the block // XXX:We should also check if the header is already saved in the database // If not, then we need to validate the header with the rest of the chain // For now we re-save the header -func (c *Chain) SaveBlock(msg payload.BlockMessage) error { - err := c.VerifyBlock(msg.Block) - if err != nil { - return err - } - //XXX(Issue): We can either check the hash here for genesisblock. - //We most likely will have it anyways after validation/ We can return it from VerifyBlock - // Or we can do it somewhere in startup, performance benefits - // won't be that big since it's just a bytes.Equal. - // so it's more about which is more readable and where it makes sense to put - return c.db.saveBlock(msg.Block, false) -} +func (c *Chain) ProcessBlock(block payload.Block) error { -// VerifyBlock verifies whether a block is valid according -// to the rules of consensus -func (c *Chain) VerifyBlock(block payload.Block) error { - - // Check if we already have this block - // XXX: We can optimise by implementing a Has method + // Check if we already have this block saved + // XXX: We can optimise by implementing a Has() method // caching the last block in memory - lastBlock, err := c.db.GetLastBlock() + lastBlock, err := c.Db.GetLastBlock() if err != nil { return err } - // Check if we have already saved this block - // by looking if the latest block height is more than - // incoming block height if lastBlock.Index > block.Index { return ErrBlockAlreadyExists } + if block.Index > lastBlock.Index+1 { + return ErrFutureBlock + } + + err = c.verifyBlock(block) + if err != nil { + return ValidationError{err.Error()} + } + err = c.Db.saveBlock(block, false) + if err != nil { + return DatabaseError{err.Error()} + } + return nil +} + +// VerifyBlock verifies whether a block is valid according +// to the rules of consensus +func (c *Chain) verifyBlock(block payload.Block) error { return nil } @@ -73,14 +108,18 @@ func (c *Chain) VerifyTx(tx transaction.Transactioner) error { return nil } -// SaveHeaders will save the set of headers without validating -func (c *Chain) SaveHeaders(msg payload.HeadersMessage) error { +// ProcessHeaders will save the set of headers without validating +func (c *Chain) ProcessHeaders(hdrs []*payload.BlockBase) error { - err := c.verifyHeaders(msg.Headers) + err := c.verifyHeaders(hdrs) if err != nil { - return err + return ValidationError{err.Error()} } - return c.db.saveHeaders(msg.Headers) + err = c.Db.saveHeaders(hdrs) + if err != nil { + return DatabaseError{err.Error()} + } + return nil } // verifyHeaders will be used to verify a batch of headers diff --git a/pkg/chain/errors.go b/pkg/chain/errors.go new file mode 100644 index 000000000..627a304ba --- /dev/null +++ b/pkg/chain/errors.go @@ -0,0 +1,19 @@ +package chain + +// ValidationError occurs when verificatio of the object fails +type ValidationError struct { + msg string +} + +func (v ValidationError) Error() string { + return v.msg +} + +// DatabaseError occurs when the chain fails to save the object in the database +type DatabaseError struct { + msg string +} + +func (d DatabaseError) Error() string { + return d.msg +} diff --git a/pkg/chaincfg/chaincfg.go b/pkg/chaincfg/chaincfg.go new file mode 100644 index 000000000..ff6024c6f --- /dev/null +++ b/pkg/chaincfg/chaincfg.go @@ -0,0 +1,44 @@ +package chaincfg + +import ( + "bytes" + "encoding/hex" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +// Params are the parameters needed to setup the network +type Params struct { + GenesisBlock payload.Block +} + +//NetParams returns the parameters for the chosen network magic +func NetParams(magic protocol.Magic) (Params, error) { + switch magic { + case protocol.MainNet: + return mainnet() + default: + return mainnet() + } +} + +//Mainnet returns the parameters needed for mainnet +func mainnet() (Params, error) { + rawHex := "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151" + rawBytes, err := hex.DecodeString(rawHex) + if err != nil { + return Params{}, err + } + reader := bytes.NewReader(rawBytes) + + block := payload.Block{} + err = block.Decode(reader) + if err != nil { + return Params{}, err + } + + return Params{ + GenesisBlock: block, + }, nil +} diff --git a/pkg/chaincfg/chaincfg_test.go b/pkg/chaincfg/chaincfg_test.go new file mode 100644 index 000000000..043350b81 --- /dev/null +++ b/pkg/chaincfg/chaincfg_test.go @@ -0,0 +1,13 @@ +package chaincfg + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMainnet(t *testing.T) { + p, err := mainnet() + assert.Nil(t, err) + assert.Equal(t, p.GenesisBlock.Hash.ReverseString(), "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf") +} From 84b4b41288113fa613ff9bb7e51469fc49a0c820 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 21:32:32 +0100 Subject: [PATCH 069/117] Implemented DIV, MOD opcode (#237) --- pkg/vm/stack/Int.go | 7 +++++ pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 38 +++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 60 +++++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index ebd83cea1..52e6240d4 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -46,6 +46,13 @@ func (i *Int) Mul(s *Int) (*Int, error) { }, nil } +// Div will divide one stackInteger by an other. +func (i *Int) Div(s *Int) (*Int, error) { + return &Int{ + val: new(big.Int).Div(i.val, s.val), + }, nil +} + // Mod will take the mod of two stackIntegers together func (i *Int) Mod(s *Int) (*Int, error) { return &Int{ diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 5b199baa1..238d300e0 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -7,6 +7,8 @@ type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invo var opFunc = map[stack.Instruction]stackInfo{ stack.INC: Inc, stack.DEC: Dec, + stack.DIV: Div, + stack.MOD: Mod, stack.NZ: Nz, stack.MUL: Mul, stack.ABS: Abs, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 65d6b4af1..345092eea 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -94,6 +94,44 @@ func Dec(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Div divides one stack Item by an other. +// Returns an error if either items cannot be casted to an integer +// or if the division of the integers cannot be performed. +func Div(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandB.Div(operandA) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Mod returns the mod of two stack Items. +// Returns an error if either items cannot be casted to an integer +// or if the mode of the integers cannot be performed. +func Mod(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandB.Mod(operandA) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Nz pops an integer from the stack. // Then pushes a boolean to the stack which evaluates to true // iff the integer was not zero. diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 700f17bcb..613ddefea 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -118,6 +118,64 @@ func TestSubOp(t *testing.T) { } +func TestDivOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(4)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.DIV, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestModOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(15)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(4)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MOD, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(3), item.Value().Int64()) +} + func TestNzOp(t *testing.T) { v := VM{} @@ -141,7 +199,6 @@ func TestNzOp(t *testing.T) { } assert.Equal(t, true, item.Value()) - } func TestMulOp(t *testing.T) { @@ -171,7 +228,6 @@ func TestMulOp(t *testing.T) { } assert.Equal(t, int64(400), item.Value().Int64()) - } func TestAbsOp(t *testing.T) { From cb21c66316d8da57a5d0d2485b620a00e3548188 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 28 Mar 2019 21:22:17 +0000 Subject: [PATCH 070/117] Syncmgr: Implement synchronisation manager (#249) * [syncmgr] - Add blockmode, normal mode, headermode - Add config file - Add test files - removed RequestBlocks and RequestHeaders from peers, as we will use the peermanager for this - OnHeaders and OnBlock in syncmgr, now return errors - refactored all tests to use a convenience method to return a syncmgr and testHelper --- pkg/syncmgr/blockmode.go | 55 +++++++++++ pkg/syncmgr/config.go | 47 +++++++++ pkg/syncmgr/headermode.go | 41 ++++++++ pkg/syncmgr/mockhelpers_test.go | 113 +++++++++++++++++++++ pkg/syncmgr/normalmode.go | 59 +++++++++++ pkg/syncmgr/syncmgr.go | 135 ++++++++++++++++++++++++++ pkg/syncmgr/syncmgr_onblock_test.go | 97 ++++++++++++++++++ pkg/syncmgr/syncmgr_onheaders_test.go | 117 ++++++++++++++++++++++ 8 files changed, 664 insertions(+) create mode 100644 pkg/syncmgr/blockmode.go create mode 100644 pkg/syncmgr/config.go create mode 100644 pkg/syncmgr/headermode.go create mode 100644 pkg/syncmgr/mockhelpers_test.go create mode 100644 pkg/syncmgr/normalmode.go create mode 100644 pkg/syncmgr/syncmgr.go create mode 100644 pkg/syncmgr/syncmgr_onblock_test.go create mode 100644 pkg/syncmgr/syncmgr_onheaders_test.go diff --git a/pkg/syncmgr/blockmode.go b/pkg/syncmgr/blockmode.go new file mode 100644 index 000000000..2e5dadd6f --- /dev/null +++ b/pkg/syncmgr/blockmode.go @@ -0,0 +1,55 @@ +package syncmgr + +import ( + "github.com/CityOfZion/neo-go/pkg/chain" + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +// blockModeOnBlock is called when the sync manager is block mode +// and receives a block. +func (s *Syncmgr) blockModeOnBlock(peer SyncPeer, block payload.Block) error { + + // Process Block + err := s.cfg.ProcessBlock(block) + + if err == chain.ErrFutureBlock { + // XXX(Optimisation): We can cache future blocks in blockmode, if we have the corresponding header + // We can have the server cache them and sort out the semantics for when to send them to the chain + // Server can listen on chain for when a new block is saved + // or we could embed a struct in this syncmgr called blockCache, syncmgr can just tell it when it has processed + //a block and we can call ProcessBlock + return err + } + + if err != nil && err != chain.ErrBlockAlreadyExists { + return s.cfg.FetchBlockAgain(block.Hash) + } + + // Check if blockhashReceived == the header hash from last get headers this node performed + // if not then increment and request next block + if s.headerHash != block.Hash { + nextHash, err := s.cfg.GetNextBlockHash() + if err != nil { + return err + } + err = s.cfg.RequestBlock(nextHash) + return err + } + + // If we are caught up then go into normal mode + diff := peer.Height() - block.Index + if diff <= cruiseHeight { + s.syncmode = normalMode + s.timer.Reset(blockTimer) + return nil + } + + // If not then we go back into headersMode and request more headers. + s.syncmode = headersMode + return s.cfg.RequestHeaders(block.Hash) +} + +func (s *Syncmgr) blockModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) error { + // We ignore headers when in this mode + return nil +} diff --git a/pkg/syncmgr/config.go b/pkg/syncmgr/config.go new file mode 100644 index 000000000..bc921b33b --- /dev/null +++ b/pkg/syncmgr/config.go @@ -0,0 +1,47 @@ +package syncmgr + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +// Config is the configuration file for the sync manager +type Config struct { + + // Chain functions + ProcessBlock func(msg payload.Block) error + ProcessHeaders func(hdrs []*payload.BlockBase) error + + // RequestHeaders will send a getHeaders request + // with the hash passed in as a parameter + RequestHeaders func(hash util.Uint256) error + + //RequestBlock will send a getdata request for the block + // with the hash passed as a parameter + RequestBlock func(hash util.Uint256) error + + // GetNextBlockHash returns the block hash of the header infront of thr block + // at the tip of this nodes chain. This assumes that the node is not in sync + GetNextBlockHash func() (util.Uint256, error) + + // GetBestBlockHash gets the block hash of the last saved block. + GetBestBlockHash func() (util.Uint256, error) + + // AskForNewBlocks will send out a message to the network + // asking for new blocks + AskForNewBlocks func() + + // FetchHeadersAgain is called when a peer has provided headers that have not + // validated properly. We pass in the hash of the first header + FetchHeadersAgain func(util.Uint256) error + + // FetchHeadersAgain is called when a peer has provided a block that has not + // validated properly. We pass in the hash of the block + FetchBlockAgain func(util.Uint256) error +} + +// SyncPeer represents a peer on the network +// that this node can sync with +type SyncPeer interface { + Height() uint32 +} diff --git a/pkg/syncmgr/headermode.go b/pkg/syncmgr/headermode.go new file mode 100644 index 000000000..898dc933d --- /dev/null +++ b/pkg/syncmgr/headermode.go @@ -0,0 +1,41 @@ +package syncmgr + +import ( + "github.com/CityOfZion/neo-go/pkg/chain" + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +// headersModeOnHeaders is called when the sync manager is headers mode +// and receives a header. +func (s *Syncmgr) headersModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) error { + // If we are in Headers mode, then we just need to process the headers + // Note: For the un-optimised version, we move straight to blocksOnly mode + + firstHash := hdrs[0].Hash + + err := s.cfg.ProcessHeaders(hdrs) + if err == nil { + // Update syncmgr last header + s.headerHash = hdrs[len(hdrs)-1].Hash + + s.syncmode = blockMode + return s.cfg.RequestBlock(firstHash) + } + + // Check whether it is a validation error, or a database error + if _, ok := err.(*chain.ValidationError); ok { + // If we get a validation error we re-request the headers + // the method will automatically fetch from a different peer + // XXX: Add increment banScore for this peer + return s.cfg.FetchHeadersAgain(firstHash) + } + // This means it is a database error. We have no way to recover from this. + panic(err.Error()) +} + +// headersModeOnBlock is called when the sync manager is headers mode +// and receives a block. +func (s *Syncmgr) headersModeOnBlock(peer SyncPeer, block payload.Block) error { + // While in headers mode, ignore any blocks received + return nil +} diff --git a/pkg/syncmgr/mockhelpers_test.go b/pkg/syncmgr/mockhelpers_test.go new file mode 100644 index 000000000..8c84225ac --- /dev/null +++ b/pkg/syncmgr/mockhelpers_test.go @@ -0,0 +1,113 @@ +package syncmgr + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type syncTestHelper struct { + blocksProcessed int + headersProcessed int + newBlockRequest int + headersFetchRequest int + blockFetchRequest int + err error +} + +func (s *syncTestHelper) ProcessBlock(msg payload.Block) error { + s.blocksProcessed++ + return s.err +} +func (s *syncTestHelper) ProcessHeaders(hdrs []*payload.BlockBase) error { + s.headersProcessed = s.headersProcessed + len(hdrs) + return s.err +} + +func (s *syncTestHelper) GetNextBlockHash() (util.Uint256, error) { + return util.Uint256{}, s.err +} + +func (s *syncTestHelper) AskForNewBlocks() { + s.newBlockRequest++ +} + +func (s *syncTestHelper) FetchHeadersAgain(util.Uint256) error { + s.headersFetchRequest++ + return s.err +} + +func (s *syncTestHelper) FetchBlockAgain(util.Uint256) error { + s.blockFetchRequest++ + return s.err +} + +func (s *syncTestHelper) RequestBlock(util.Uint256) error { + s.blockFetchRequest++ + return s.err +} + +func (s *syncTestHelper) RequestHeaders(util.Uint256) error { + s.headersFetchRequest++ + return s.err +} + +type mockPeer struct { + height uint32 +} + +func (p *mockPeer) Height() uint32 { return p.height } + +func randomHeadersMessage(t *testing.T, num int) *payload.HeadersMessage { + var hdrs []*payload.BlockBase + + for i := 0; i < num; i++ { + hash := randomUint256(t) + hdr := &payload.BlockBase{Hash: hash} + hdrs = append(hdrs, hdr) + } + + hdrsMsg, err := payload.NewHeadersMessage() + assert.Nil(t, err) + + hdrsMsg.Headers = hdrs + + return hdrsMsg +} + +func randomUint256(t *testing.T) util.Uint256 { + hash := make([]byte, 32) + _, err := rand.Read(hash) + assert.Nil(t, err) + + u, err := util.Uint256DecodeBytes(hash) + assert.Nil(t, err) + return u +} + +func setupSyncMgr(mode mode) (*Syncmgr, *syncTestHelper) { + helper := &syncTestHelper{} + + cfg := &Config{ + ProcessBlock: helper.ProcessBlock, + ProcessHeaders: helper.ProcessHeaders, + + GetNextBlockHash: helper.GetNextBlockHash, + AskForNewBlocks: helper.AskForNewBlocks, + + FetchHeadersAgain: helper.FetchHeadersAgain, + FetchBlockAgain: helper.FetchBlockAgain, + + RequestBlock: helper.RequestBlock, + RequestHeaders: helper.RequestHeaders, + } + + syncmgr := New(cfg) + syncmgr.syncmode = mode + + return syncmgr, helper +} diff --git a/pkg/syncmgr/normalmode.go b/pkg/syncmgr/normalmode.go new file mode 100644 index 000000000..218d6151b --- /dev/null +++ b/pkg/syncmgr/normalmode.go @@ -0,0 +1,59 @@ +package syncmgr + +import ( + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +func (s *Syncmgr) normalModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) error { + // If in normal mode, first process the headers + err := s.cfg.ProcessHeaders(hdrs) + if err != nil { + // If something went wrong with processing the headers + // Ask another peer for the headers. + //XXX: Increment banscore for this peer + return s.cfg.FetchHeadersAgain(hdrs[0].Hash) + } + + lenHeaders := len(hdrs) + firstHash := hdrs[0].Hash + lastHash := hdrs[lenHeaders-1].Hash + + // Update syncmgr latest header + s.headerHash = lastHash + + // If there are 2k headers, then ask for more headers and switch back to headers mode. + if lenHeaders == 2000 { + s.syncmode = headersMode + return s.cfg.RequestHeaders(lastHash) + } + + // Ask for the corresponding block iff there is < 2k headers + // then switch to blocksMode + // Bounds state that len > 1 && len!= 2000 & maxHeadersInMessage == 2000 + // This means that we have less than 2k headers + s.syncmode = blockMode + return s.cfg.RequestBlock(firstHash) +} + +// normalModeOnBlock is called when the sync manager is normal mode +// and receives a block. +func (s *Syncmgr) normalModeOnBlock(peer SyncPeer, block payload.Block) error { + // stop the timer that periodically asks for blocks + s.timer.Stop() + + // process block + err := s.cfg.ProcessBlock(block) + if err != nil { + s.timer.Reset(blockTimer) + return s.cfg.FetchBlockAgain(block.Hash) + } + + diff := peer.Height() - block.Index + if diff > trailingHeight { + s.syncmode = headersMode + return s.cfg.RequestHeaders(block.Hash) + } + + s.timer.Reset(blockTimer) + return nil +} diff --git a/pkg/syncmgr/syncmgr.go b/pkg/syncmgr/syncmgr.go new file mode 100644 index 000000000..bb06dfa7e --- /dev/null +++ b/pkg/syncmgr/syncmgr.go @@ -0,0 +1,135 @@ +package syncmgr + +import ( + "fmt" + "time" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +type mode uint8 + +// Note: this is the unoptimised version without parallel sync +// The algorithm for the unoptimsied version is simple: +// Download 2000 headers, then download the blocks for those headers +// Once those blocks are downloaded, we repeat the process again +// Until we are nomore than one block behind the tip. +// Once this happens, we switch into normal mode. +//In normal mode, we have a timer on for X seconds and ask nodes for blocks and also to doublecheck +// if we are behind once the timer runs out. +// The timer restarts whenever we receive a block. +// The parameter X should be approximately the time it takes the network to reach consensus + +//blockTimer approximates to how long it takes to reach consensus and propagate +// a block in the network. Once a node has synchronised with the network, he will +// ask the network for a newblock every blockTimer +const blockTimer = 20 * time.Second + +// trailingHeight indicates how many blocks the node has to be behind by +// before he switches to headersMode. +const trailingHeight = 100 + +// indicates how many blocks the node has to be behind by +// before he switches to normalMode and fetches blocks every X seconds. +const cruiseHeight = 0 + +const ( + headersMode mode = 1 + blockMode mode = 2 + normalMode mode = 3 +) + +//Syncmgr keeps the node in sync with the rest of the network +type Syncmgr struct { + syncmode mode + cfg *Config + timer *time.Timer + + // headerHash is the hash of the last header in the last OnHeaders message that we received. + // When receiving blocks, we can use this to determine whether the node has downloaded + // all of the blocks for the last headers messages + headerHash util.Uint256 +} + +// New creates a new sync manager +func New(cfg *Config) *Syncmgr { + + newBlockTimer := time.AfterFunc(blockTimer, func() { + cfg.AskForNewBlocks() + }) + newBlockTimer.Stop() + + return &Syncmgr{ + syncmode: headersMode, + cfg: cfg, + timer: newBlockTimer, + } +} + +// OnHeader is called when the node receives a headers message +func (s *Syncmgr) OnHeader(peer SyncPeer, msg *payload.HeadersMessage) error { + + // XXX(Optimisation): First check if we actually need these headers + // Check the last header in msg and then check what our latest header that was saved is + // If our latest header is above the lastHeader, then we do not save it + // We could also have that our latest header is above only some of the headers. + // In this case, we should remove the headers that we already have + + if len(msg.Headers) == 0 { + // XXX: Increment banScore for this peer, for sending empty headers message + return nil + } + + var err error + + switch s.syncmode { + case headersMode: + err = s.headersModeOnHeaders(peer, msg.Headers) + case blockMode: + err = s.blockModeOnHeaders(peer, msg.Headers) + case normalMode: + err = s.normalModeOnHeaders(peer, msg.Headers) + default: + err = s.headersModeOnHeaders(peer, msg.Headers) + } + + // XXX(Kev):The only meaningful error here would be if the peer + // we re-requested blocks from failed. In the next iteration, this will be handled + // by the peer manager, who will only return an error, if we are connected to no peers. + // Upon re-alising this, the node will then send out GetAddresses to the network and + // syncing will be resumed, once we find peers to connect to. + + hdr := msg.Headers[len(msg.Headers)-1] + fmt.Printf("Finished processing headers. LastHash in set was: %s\n ", hdr.Hash.ReverseString()) + + return err +} + +// OnBlock is called when the node receives a block +func (s *Syncmgr) OnBlock(peer SyncPeer, msg *payload.BlockMessage) error { + fmt.Printf("Block received with height %d\n", msg.Block.Index) + + var err error + + switch s.syncmode { + case headersMode: + err = s.headersModeOnBlock(peer, msg.Block) + case blockMode: + err = s.blockModeOnBlock(peer, msg.Block) + case normalMode: + err = s.normalModeOnBlock(peer, msg.Block) + default: + err = s.headersModeOnBlock(peer, msg.Block) + } + + fmt.Printf("Processed Block with height %d\n", msg.Block.Index) + + return err +} + +//IsCurrent returns true if the node is currently +// synced up with the network +func (s *Syncmgr) IsCurrent() bool { + return s.syncmode == normalMode +} diff --git a/pkg/syncmgr/syncmgr_onblock_test.go b/pkg/syncmgr/syncmgr_onblock_test.go new file mode 100644 index 000000000..d5b79f0b3 --- /dev/null +++ b/pkg/syncmgr/syncmgr_onblock_test.go @@ -0,0 +1,97 @@ +package syncmgr + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/chain" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/stretchr/testify/assert" +) + +func TestHeadersModeOnBlock(t *testing.T) { + + syncmgr, helper := setupSyncMgr(headersMode) + + syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) + + // In headerMode, we do nothing + assert.Equal(t, 0, helper.blocksProcessed) +} + +func TestBlockModeOnBlock(t *testing.T) { + + syncmgr, helper := setupSyncMgr(blockMode) + + syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) + + // When a block is received in blockMode, it is processed + assert.Equal(t, 1, helper.blocksProcessed) +} +func TestNormalModeOnBlock(t *testing.T) { + + syncmgr, helper := setupSyncMgr(normalMode) + + syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) + + // When a block is received in normal, it is processed + assert.Equal(t, 1, helper.blocksProcessed) +} + +func TestBlockModeToNormalMode(t *testing.T) { + + syncmgr, _ := setupSyncMgr(blockMode) + + peer := &mockPeer{ + height: 100, + } + + blkMessage := randomBlockMessage(t, 100) + + syncmgr.OnBlock(peer, blkMessage) + + // We should switch to normal mode, since the block + //we received is close to the height of the peer. See cruiseHeight + assert.Equal(t, normalMode, syncmgr.syncmode) + +} +func TestBlockModeStayInBlockMode(t *testing.T) { + + syncmgr, _ := setupSyncMgr(blockMode) + + // We need our latest know hash to not be equal to the hash + // of the block we received, to stay in blockmode + syncmgr.headerHash = randomUint256(t) + + peer := &mockPeer{ + height: 2000, + } + + blkMessage := randomBlockMessage(t, 100) + + syncmgr.OnBlock(peer, blkMessage) + + // We should stay in block mode, since the block we received is + // still quite far behind the peers height + assert.Equal(t, blockMode, syncmgr.syncmode) +} +func TestBlockModeAlreadyExistsErr(t *testing.T) { + + syncmgr, helper := setupSyncMgr(blockMode) + helper.err = chain.ErrBlockAlreadyExists + + syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 100)) + + assert.Equal(t, 0, helper.blockFetchRequest) + + // If we have a block already exists in blockmode, then we + // switch back to headers mode. + assert.Equal(t, headersMode, syncmgr.syncmode) +} + +func randomBlockMessage(t *testing.T, height uint32) *payload.BlockMessage { + blockMessage, err := payload.NewBlockMessage() + blockMessage.BlockBase.Index = height + assert.Nil(t, err) + return blockMessage +} diff --git a/pkg/syncmgr/syncmgr_onheaders_test.go b/pkg/syncmgr/syncmgr_onheaders_test.go new file mode 100644 index 000000000..2f60a4b72 --- /dev/null +++ b/pkg/syncmgr/syncmgr_onheaders_test.go @@ -0,0 +1,117 @@ +package syncmgr + +import ( + "testing" + + "github.com/CityOfZion/neo-go/pkg/chain" + + "github.com/stretchr/testify/assert" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +func TestHeadersModeOnHeaders(t *testing.T) { + + syncmgr, helper := setupSyncMgr(headersMode) + + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 0)) + + // Since there were no headers, we should have exited early and processed nothing + assert.Equal(t, 0, helper.headersProcessed) + + // ProcessHeaders should have been called once to process all 100 headers + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 100)) + assert.Equal(t, 100, helper.headersProcessed) + + // Mode should now be blockMode + assert.Equal(t, blockMode, syncmgr.syncmode) + +} + +func TestBlockModeOnHeaders(t *testing.T) { + syncmgr, helper := setupSyncMgr(blockMode) + + // If we receive a header in blockmode, no headers will be processed + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 100)) + assert.Equal(t, 0, helper.headersProcessed) +} +func TestNormalModeOnHeadersMaxHeaders(t *testing.T) { + syncmgr, helper := setupSyncMgr(normalMode) + + // If we receive a header in normalmode, headers will be processed + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 2000)) + assert.Equal(t, 2000, helper.headersProcessed) + + // Mode should now be headersMode since we received 2000 headers + assert.Equal(t, headersMode, syncmgr.syncmode) +} + +// This differs from the previous function in that +//we did not receive the max amount of headers +func TestNormalModeOnHeaders(t *testing.T) { + syncmgr, helper := setupSyncMgr(normalMode) + + // If we receive a header in normalmode, headers will be processed + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) + assert.Equal(t, 200, helper.headersProcessed) + + // Because we did not receive 2000 headers, we switch to blockMode + assert.Equal(t, blockMode, syncmgr.syncmode) +} + +func TestLastHeaderUpdates(t *testing.T) { + syncmgr, _ := setupSyncMgr(headersMode) + + hdrsMessage := randomHeadersMessage(t, 200) + hdrs := hdrsMessage.Headers + lastHeader := hdrs[len(hdrs)-1] + + syncmgr.OnHeader(&mockPeer{}, hdrsMessage) + + // Headers are processed in headersMode + // Last header should be updated + assert.True(t, syncmgr.headerHash.Equals(lastHeader.Hash)) + + // Change mode to blockMode and reset lastHeader + syncmgr.syncmode = blockMode + syncmgr.headerHash = util.Uint256{} + + syncmgr.OnHeader(&mockPeer{}, hdrsMessage) + + // header should not be changed + assert.False(t, syncmgr.headerHash.Equals(lastHeader.Hash)) + + // Change mode to normalMode and reset lastHeader + syncmgr.syncmode = normalMode + syncmgr.headerHash = util.Uint256{} + + syncmgr.OnHeader(&mockPeer{}, hdrsMessage) + + // headers are processed in normalMode + // hash should be updated + assert.True(t, syncmgr.headerHash.Equals(lastHeader.Hash)) + +} + +func TestHeadersModeOnHeadersErr(t *testing.T) { + + syncmgr, helper := setupSyncMgr(headersMode) + helper.err = &chain.ValidationError{} + + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) + + // On a validation error, we should request for another peer + // to send us these headers + assert.Equal(t, 1, helper.headersFetchRequest) +} + +func TestNormalModeOnHeadersErr(t *testing.T) { + syncmgr, helper := setupSyncMgr(normalMode) + helper.err = &chain.ValidationError{} + + syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) + + // On a validation error, we should request for another peer + // to send us these headers + assert.Equal(t, 1, helper.headersFetchRequest) +} From 955bb373fc4a8b0212df66255a42df059ee59797 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Thu, 28 Mar 2019 23:28:20 +0100 Subject: [PATCH 071/117] Implemented SHL, SHR opcode (#250) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 40 +++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 70 ++++++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 238d300e0..cef46a199 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.SHR: Shr, + stack.SHL: Shl, stack.INC: Inc, stack.DEC: Dec, stack.DIV: Div, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 345092eea..5cbe0552a 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -260,6 +260,46 @@ func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { return operandA, operandB, nil } +// Shl pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is the b's value shift to the left by a's value bits. +// Returns an error if either items cannot be casted to an integer +// or if the left shift operation cannot per performed with the two integer's value. +func Shl(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := b.Lsh(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Shr pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is the b's value shift to the right by a's value bits. +// Returns an error if either items cannot be casted to an integer +// or if the right shift operation cannot per performed with the two integer's value. +func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := b.Rsh(a) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 613ddefea..b703751be 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -143,7 +143,7 @@ func TestDivOp(t *testing.T) { if err != nil { t.Fail() } - + assert.Equal(t, int64(2), item.Value().Int64()) } @@ -326,3 +326,71 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, int64(20), item.Value().Int64()) } + +func TestShlOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(2)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(3)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a.Lsh(b) and place + // the result on top of the evaluation + // stack + v.executeOp(stack.SHL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(16), item.Value().Int64()) +} + +func TestShrOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(2)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a.Rsh(b) and place + // the result on top of the evaluation + // stack + v.executeOp(stack.SHR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + if err != nil { + t.Fail() + } + + assert.Equal(t, int64(2), item.Value().Int64()) +} From 1a6bdd40993ab19e735f9fc00772baf246d1ade9 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Thu, 28 Mar 2019 22:49:34 +0000 Subject: [PATCH 072/117] [Server] Implements Orchestration server (#252) * [pubsub] - remove pubsub package * [chain] - Add height to chain * [peer] - remove unnecesary println * [server] - Implement server package * Add main.go to run node --- main.go | 20 +++++++ pkg/chain/chain.go | 9 +++- pkg/peer/peer.go | 1 - pkg/pubsub/event.go | 20 ------- pkg/pubsub/pub.go | 21 -------- pkg/pubsub/sub.go | 7 --- pkg/server/addrmgr.go | 7 +++ pkg/server/chain.go | 15 ++++++ pkg/server/connmgr.go | 62 +++++++++++++++++++++ pkg/server/database.go | 14 +++++ pkg/server/peerconfig.go | 23 ++++++++ pkg/server/peermgr.go | 9 ++++ pkg/server/server.go | 113 +++++++++++++++++++++++++++++++++++++++ pkg/server/syncmgr.go | 94 ++++++++++++++++++++++++++++++++ pkg/syncmgr/config.go | 2 +- 15 files changed, 366 insertions(+), 51 deletions(-) create mode 100644 main.go delete mode 100644 pkg/pubsub/event.go delete mode 100644 pkg/pubsub/pub.go delete mode 100644 pkg/pubsub/sub.go create mode 100644 pkg/server/addrmgr.go create mode 100644 pkg/server/chain.go create mode 100644 pkg/server/connmgr.go create mode 100644 pkg/server/database.go create mode 100644 pkg/server/peerconfig.go create mode 100644 pkg/server/peermgr.go create mode 100644 pkg/server/server.go create mode 100644 pkg/server/syncmgr.go diff --git a/main.go b/main.go new file mode 100644 index 000000000..b7b54ab7b --- /dev/null +++ b/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/server" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +func main() { + s, err := server.New(protocol.MainNet, 10332) + if err != nil { + fmt.Println(err) + return + } + err = s.Run() + if err != nil { + fmt.Println("Server has stopped from the following error: ", err.Error()) + } +} diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index c44764388..f49aedbac 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -23,7 +23,8 @@ var ( // Chain represents a blockchain instance type Chain struct { - Db *Chaindb + Db *Chaindb + height uint32 } // New returns a new chain instance @@ -128,3 +129,9 @@ func (c *Chain) ProcessHeaders(hdrs []*payload.BlockBase) error { func (c *Chain) verifyHeaders(hdrs []*payload.BlockBase) error { return nil } + +// CurrentHeight returns the index of the block +// at the tip of the chain +func (c Chain) CurrentHeight() uint32 { + return c.height +} diff --git a/pkg/peer/peer.go b/pkg/peer/peer.go index d1594d6de..8fe2f28d6 100644 --- a/pkg/peer/peer.go +++ b/pkg/peer/peer.go @@ -176,7 +176,6 @@ func (p *Peer) IsVerackReceived() bool { //NotifyDisconnect returns once the peer has disconnected // Blocking func (p *Peer) NotifyDisconnect() { - fmt.Println("Peer has not disconnected yet") <-p.quitch fmt.Println("Peer has just disconnected") } diff --git a/pkg/pubsub/event.go b/pkg/pubsub/event.go deleted file mode 100644 index 1a57e86cd..000000000 --- a/pkg/pubsub/event.go +++ /dev/null @@ -1,20 +0,0 @@ -package pubsub - -// EventType is an enum -// representing the types of messages we can subscribe to -type EventType int - -const ( - // NewBlock is called When blockchain connects a new block, it will emit an NewBlock Event - NewBlock EventType = iota - // BadBlock is called When blockchain declines a block, it will emit a new block event - BadBlock - // BadHeader is called When blockchain rejects a Header, it will emit this event - BadHeader -) - -// Event represents a new Event that a subscriber can listen to -type Event struct { - Type EventType // E.g. event.NewBlock - data []byte // Raw information -} diff --git a/pkg/pubsub/pub.go b/pkg/pubsub/pub.go deleted file mode 100644 index a9f6084a1..000000000 --- a/pkg/pubsub/pub.go +++ /dev/null @@ -1,21 +0,0 @@ -package pubsub - -// Publisher sends events to subscribers -type Publisher struct { - subs []Subscriber -} - -// Send iterates over each subscriber and checks -// if they are interested in the Event -// By looking at their topics, if they are then -// the event is emitted to them -func (p *Publisher) Send(e Event) error { - for _, sub := range p.subs { - for _, topic := range sub.Topics() { - if e.Type == topic { - sub.Emit(e) - } - } - } - return nil -} diff --git a/pkg/pubsub/sub.go b/pkg/pubsub/sub.go deleted file mode 100644 index 4540870ff..000000000 --- a/pkg/pubsub/sub.go +++ /dev/null @@ -1,7 +0,0 @@ -package pubsub - -// Subscriber will listen for Events from publishers -type Subscriber interface { - Topics() []EventType - Emit(Event) -} diff --git a/pkg/server/addrmgr.go b/pkg/server/addrmgr.go new file mode 100644 index 000000000..6237d79e3 --- /dev/null +++ b/pkg/server/addrmgr.go @@ -0,0 +1,7 @@ +package server + +// etAddress will return a viable address to connect to +// Currently it is hardcoded to be one neo node until address manager is implemented +func (s *Server) getAddress() (string, error) { + return "seed1.ngd.network:10333", nil +} diff --git a/pkg/server/chain.go b/pkg/server/chain.go new file mode 100644 index 000000000..23b6c2cdc --- /dev/null +++ b/pkg/server/chain.go @@ -0,0 +1,15 @@ +package server + +import ( + "github.com/CityOfZion/neo-go/pkg/chain" + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +func setupChain(db database.Database, net protocol.Magic) (*chain.Chain, error) { + chain, err := chain.New(db, net) + if err != nil { + return nil, err + } + return chain, nil +} diff --git a/pkg/server/connmgr.go b/pkg/server/connmgr.go new file mode 100644 index 000000000..64b16bdaf --- /dev/null +++ b/pkg/server/connmgr.go @@ -0,0 +1,62 @@ +package server + +import ( + "encoding/hex" + "fmt" + "net" + "strconv" + + "github.com/CityOfZion/neo-go/pkg/connmgr" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/util" + iputils "github.com/CityOfZion/neo-go/pkg/wire/util/ip" +) + +func setupConnManager(s *Server, port uint16) *connmgr.Connmgr { + cfg := connmgr.Config{ + GetAddress: s.getAddress, + OnAccept: s.onAccept, + OnConnection: s.onConnection, + AddressPort: iputils.GetLocalIP().String() + ":" + strconv.FormatUint(uint64(port), 10), + } + return connmgr.New(cfg) +} + +func (s *Server) onConnection(conn net.Conn, addr string) { + fmt.Println("We have connected successfully to: ", addr) + + p := peer.NewPeer(conn, false, *s.peerCfg) + err := p.Run() + if err != nil { + fmt.Println("Error running peer" + err.Error()) + return + } + + s.pmg.AddPeer(p) + + byt, err := hex.DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf") + if err != nil { + fmt.Println("Error getting hash " + err.Error()) + } + lh, err := util.Uint256DecodeBytes(byt) + if err != nil { + fmt.Println("Error getting hash " + err.Error()) + } + err = p.RequestHeaders(lh.Reverse()) + if err != nil { + fmt.Println(err) + } +} + +func (s *Server) onAccept(conn net.Conn) { + fmt.Println("A peer with address: ", conn.RemoteAddr().String(), "has connect to us") + + p := peer.NewPeer(conn, true, *s.peerCfg) + err := p.Run() + if err != nil { + fmt.Println("Error running peer" + err.Error()) + return + } + s.pmg.AddPeer(p) +} diff --git a/pkg/server/database.go b/pkg/server/database.go new file mode 100644 index 000000000..9b7eea9f0 --- /dev/null +++ b/pkg/server/database.go @@ -0,0 +1,14 @@ +package server + +import ( + "github.com/CityOfZion/neo-go/pkg/database" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +func setupDatabase(net protocol.Magic) (database.Database, error) { + db, err := database.New(net.String()) + if err != nil { + return nil, err + } + return db, nil +} diff --git a/pkg/server/peerconfig.go b/pkg/server/peerconfig.go new file mode 100644 index 000000000..4fa8307bf --- /dev/null +++ b/pkg/server/peerconfig.go @@ -0,0 +1,23 @@ +package server + +import ( + "math/rand" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +func setupPeerConfig(s *Server, port uint16, net protocol.Magic) *peer.LocalConfig { + return &peer.LocalConfig{ + Net: net, + UserAgent: "NEO-GO", + Services: protocol.NodePeerService, + Nonce: rand.Uint32(), + ProtocolVer: 0, + Relay: false, + Port: port, + StartHeight: s.chain.CurrentHeight, + OnHeader: s.onHeader, + OnBlock: s.onBlock, + } +} diff --git a/pkg/server/peermgr.go b/pkg/server/peermgr.go new file mode 100644 index 000000000..670be60fc --- /dev/null +++ b/pkg/server/peermgr.go @@ -0,0 +1,9 @@ +package server + +import ( + "github.com/CityOfZion/neo-go/pkg/peermgr" +) + +func setupPeerManager() *peermgr.PeerMgr { + return peermgr.New() +} diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 000000000..b1e4c0f5b --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,113 @@ +package server + +import ( + "fmt" + + "github.com/CityOfZion/neo-go/pkg/peermgr" + + "github.com/CityOfZion/neo-go/pkg/chain" + "github.com/CityOfZion/neo-go/pkg/connmgr" + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/syncmgr" + + "github.com/CityOfZion/neo-go/pkg/database" + + "github.com/CityOfZion/neo-go/pkg/wire/protocol" +) + +// Server orchestrates all of the modules +type Server struct { + net protocol.Magic + stopCh chan error + + // Modules + db database.Database + smg *syncmgr.Syncmgr + cmg *connmgr.Connmgr + pmg *peermgr.PeerMgr + chain *chain.Chain + + peerCfg *peer.LocalConfig +} + +//New creates a new server object for a particular network and sets up each module +func New(net protocol.Magic, port uint16) (*Server, error) { + s := &Server{ + net: net, + stopCh: make(chan error, 0), + } + + // Setup database + db, err := setupDatabase(net) + if err != nil { + return nil, err + } + s.db = db + + // setup peermgr + peermgr := setupPeerManager() + s.pmg = peermgr + + // Setup chain + chain, err := setupChain(db, net) + if err != nil { + return nil, err + } + s.chain = chain + + // Setup sync manager + syncmgr := setupSyncManager(s) + s.smg = syncmgr + + // Setup connection manager + connmgr := setupConnManager(s, port) + s.cmg = connmgr + + // Setup peer config + peerCfg := setupPeerConfig(s, port, net) + s.peerCfg = peerCfg + + return s, nil +} + +// Run starts the daemon by connecting to previously nodes or connectng to seed nodes. +// This should be called once all modules have been setup +func (s *Server) Run() error { + fmt.Println("Server is starting up") + + // start the connmgr + err := s.cmg.Run() + if err != nil { + return err + } + + // Attempt to connect to a peer + err = s.cmg.NewRequest() + if err != nil { + return err + } + + // Request header to start synchronisation + bestHeader, err := s.chain.Db.GetLastHeader() + if err != nil { + return err + } + err = s.pmg.RequestHeaders(bestHeader.Hash.Reverse()) + if err != nil { + return err + } + fmt.Println("Server Successfully started") + return s.wait() +} + +func (s *Server) wait() error { + err := <-s.stopCh + return err +} + +// Stop stops the server +func (s *Server) Stop(err error) error { + fmt.Println("Server is shutting down") + s.stopCh <- err + return nil +} diff --git a/pkg/server/syncmgr.go b/pkg/server/syncmgr.go new file mode 100644 index 000000000..3df456626 --- /dev/null +++ b/pkg/server/syncmgr.go @@ -0,0 +1,94 @@ +package server + +import ( + "encoding/binary" + + "github.com/CityOfZion/neo-go/pkg/peer" + "github.com/CityOfZion/neo-go/pkg/syncmgr" + "github.com/CityOfZion/neo-go/pkg/wire/payload" + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +func setupSyncManager(s *Server) *syncmgr.Syncmgr { + + cfg := &syncmgr.Config{ + ProcessBlock: s.processBlock, + ProcessHeaders: s.processHeaders, + + RequestBlock: s.requestBlock, + RequestHeaders: s.requestHeaders, + + GetNextBlockHash: s.getNextBlockHash, + AskForNewBlocks: s.askForNewBlocks, + + FetchHeadersAgain: s.fetchHeadersAgain, + FetchBlockAgain: s.fetchBlockAgain, + } + + return syncmgr.New(cfg) +} + +func (s *Server) onHeader(peer *peer.Peer, hdrsMessage *payload.HeadersMessage) { + s.pmg.MsgReceived(peer, hdrsMessage.Command()) + s.smg.OnHeader(peer, hdrsMessage) +} + +func (s *Server) onBlock(peer *peer.Peer, blockMsg *payload.BlockMessage) { + s.pmg.MsgReceived(peer, blockMsg.Command()) + s.smg.OnBlock(peer, blockMsg) +} + +func (s *Server) processBlock(block payload.Block) error { + return s.chain.ProcessBlock(block) +} + +func (s *Server) processHeaders(hdrs []*payload.BlockBase) error { + return s.chain.ProcessHeaders(hdrs) +} + +func (s *Server) requestHeaders(hash util.Uint256) error { + return s.pmg.RequestHeaders(hash) +} + +func (s *Server) requestBlock(hash util.Uint256) error { + return s.pmg.RequestBlock(hash) +} + +// getNextBlockHash searches the database for the blockHash +// that is the height above our best block. The hash will be taken from a header. +func (s *Server) getNextBlockHash() (util.Uint256, error) { + bestBlock, err := s.chain.Db.GetLastBlock() + if err != nil { + // Panic! + // XXX: One alternative, is to get the network, erase the database and then start again from scratch. + // This should never happen. The latest block will always be atleast the genesis block + panic("could not get best block from database" + err.Error()) + } + + index := make([]byte, 4) + binary.BigEndian.PutUint32(index, bestBlock.Index+1) + + hdr, err := s.chain.Db.GetHeaderFromHeight(index) + if err != nil { + return util.Uint256{}, err + } + return hdr.Hash, nil +} + +func (s *Server) getBestBlockHash() (util.Uint256, error) { + return util.Uint256{}, nil +} + +func (s *Server) askForNewBlocks() { + // send a getblocks message with the latest block saved + + // when we receive something then send get data +} + +func (s *Server) fetchHeadersAgain(util.Uint256) error { + return nil +} + +func (s *Server) fetchBlockAgain(util.Uint256) error { + return nil +} diff --git a/pkg/syncmgr/config.go b/pkg/syncmgr/config.go index bc921b33b..af27f37c6 100644 --- a/pkg/syncmgr/config.go +++ b/pkg/syncmgr/config.go @@ -9,7 +9,7 @@ import ( type Config struct { // Chain functions - ProcessBlock func(msg payload.Block) error + ProcessBlock func(block payload.Block) error ProcessHeaders func(hdrs []*payload.BlockBase) error // RequestHeaders will send a getHeaders request From d818c162976043aca12ac3eb8bfc8a1622960386 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 29 Mar 2019 17:43:16 +0100 Subject: [PATCH 073/117] Implemented MIN, MAX WITHIN opcode --- pkg/vm/stack/Int.go | 26 +++++++++++ pkg/vm/vm_ops.go | 3 ++ pkg/vm/vm_ops_maths.go | 91 +++++++++++++++++++++++++++++++------ pkg/vm/vm_ops_maths_test.go | 82 +++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 52e6240d4..2c32a8b1d 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -111,3 +111,29 @@ func (i *Int) Abs() (*Int, error) { return b, nil } + +// Min returns the mininum between two integers. +func Min(a *Int, b *Int) *Int { + if a.Value().Cmp(b.Value()) == -1 { + return a + } + return b + +} + +// Max returns the maximun between two integers. +func Max(a *Int, b *Int) *Int { + if a.Value().Cmp(b.Value()) == 1 { + return a + } + return b +} + +// Within returns a bool whose value is true +// iff the value of the integer i is within the specified +// range [a,b) (left-inclusive). +func (i *Int) Within(a *Int, b *Int) bool { + // i >= a && i < b + return !(i.Value().Cmp(a.Value()) == -1) && i.Value().Cmp(b.Value()) == -1 + +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index cef46a199..bd2715d72 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,9 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.MIN: Min, + stack.MAX: Max, + stack.WITHIN: Within, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 5cbe0552a..c1e362781 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -173,6 +173,54 @@ func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Min pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is is the minum between a and b's value. +// Returns an error if either items cannot be casted to an integer +func Min(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.Min(operandA, operandB) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Max pops two integers, a and b, off of the stack and pushes an integer to the stack +// whose value is is the maximum between a and b's value. +// Returns an error if either items cannot be casted to an integer +func Max(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.Max(operandA, operandB) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Within pops three integers, a, b, and c off of the stack and pushes a boolean to the stack +// whose value is true iff c's value is within b's value (include) and a's value. +// Returns an error if at least one item cannot be casted to an boolean. +func Within(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + a, b, c, err := popThreeIntegers(ctx) + if err != nil { + return FAULT, err + } + res := stack.NewBoolean(c.Within(b, a)) + + ctx.Estack.Push(res) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { @@ -247,19 +295,6 @@ func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return NONE, nil } -func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - - return operandA, operandB, nil -} - // Shl pops two integers, a and b, off of the stack and pushes an integer to the stack // whose value is the b's value shift to the left by a's value bits. // Returns an error if either items cannot be casted to an integer @@ -300,6 +335,36 @@ func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} + +func popThreeIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + operandC, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, nil, err + } + + return operandA, operandB, operandC, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b703751be..4671267f7 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -394,3 +394,85 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } + +func TestMinOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MIN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.NoError(t, err) + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestMaxOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.MAX, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.NoError(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestWithinOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + c, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // c is the first item popped. + // b is the second item popped. + // a is the third item popped. + // if a is within [b, c) we place a boolean, + // whose value is true, on top of the evaluation + // stack. Otherwise we place a boolean with + // false value. + v.executeOp(stack.WITHIN, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, true, item.Value()) +} From 9402540c3a3ca471bfd6a301d6764f872cf18cc0 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Fri, 29 Mar 2019 20:35:16 +0100 Subject: [PATCH 074/117] Implemented LT, GT opcode (#259) * Implemented LT, GT opcode --- pkg/vm/stack/Int.go | 14 +++++++++ pkg/vm/vm_ops.go | 3 ++ pkg/vm/vm_ops_maths.go | 32 ++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 58 +++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 52e6240d4..3226904ee 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -111,3 +111,17 @@ func (i *Int) Abs() (*Int, error) { return b, nil } + +// Lt returns a bool value from the comparison of two integers, a and b. +// value is true if a < b. +// value is false if a >= b. +func (i *Int) Lt(s *Int) bool { + return i.Value().Cmp(s.Value()) == -1 +} + +// Gt returns a bool value from the comparison of two integers, a and b. +// value is true if a > b. +// value is false if a <= b. +func (i *Int) Gt(s *Int) bool { + return i.Value().Cmp(s.Value()) == 1 +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index cef46a199..1854b1634 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,9 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + + stack.LT: Lt, + stack.GT: Gt, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 5cbe0552a..7abd73b9f 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -300,6 +300,38 @@ func Shr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// Lt pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is less than b's value. +// Returns an error if either items cannot be casted to an integer +func Lt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Lt(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// Gt pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is greated than b's value. +// Returns an error if either items cannot be casted to an integer +func Gt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Gt(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b703751be..29d843999 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -394,3 +394,61 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } + +func TestLtOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a < b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.LT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, false, item.Value()) +} + +func TestGtOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.NoError(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.NoError(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a > b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.GT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.NoError(t, err) + + assert.Equal(t, true, item.Value()) +} From 1fbc0af5db60316c058ff54881daabb094883872 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Fri, 29 Mar 2019 22:22:45 +0100 Subject: [PATCH 075/117] VM: Implement BOOLAND, BOOLOR opcode (#251) * Implemented BOOLAND, BOOLOR opcode --- pkg/vm/stack/boolean.go | 14 ++++++++++ pkg/vm/vm_ops.go | 3 ++- pkg/vm/vm_ops_maths.go | 51 +++++++++++++++++++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 47 ++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 93fabd84e..5a5a93207 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -30,3 +30,17 @@ func (b *Boolean) Value() bool { func (b *Boolean) Not() *Boolean { return NewBoolean(!b.Value()) } + +// And returns a Boolean whose underlying value is obtained +// by applying the && operator to two Booleans' values. +func (b *Boolean) And(a *Boolean) *Boolean { + c := b.Value() && a.Value() + return NewBoolean(c) +} + +// Or returns a Boolean whose underlying value is obtained +// by applying the || operator to two Booleans' values. +func (b *Boolean) Or(a *Boolean) *Boolean { + c := b.Value() || a.Value() + return NewBoolean(c) +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 1854b1634..b73b6aa31 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,7 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ - + stack.BOOLAND: BoolAnd, + stack.BOOLOR: BoolOr, stack.LT: Lt, stack.GT: Gt, stack.SHR: Shr, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index 7abd73b9f..d9a3a9bbc 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -206,6 +206,44 @@ func Not(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// BoolAnd pops two booleans off of the stack and pushes a boolean to the stack +// whose value is true iff both booleans' values are true. +// Returns an error if either items cannot be casted to an boolean +func BoolAnd(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + bool1, bool2, err := popTwoBooleans(ctx) + if err != nil { + return FAULT, err + } + res := bool1.And(bool2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// BoolOr pops two booleans off of the stack and pushes a boolean to the stack +// whose value is true iff at least one of the two booleans' value is true. +// Returns an error if either items cannot be casted to an boolean +func BoolOr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + bool1, bool2, err := popTwoBooleans(ctx) + if err != nil { + return FAULT, err + } + res := bool1.Or(bool2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + // Sign puts the sign of the top stack Item on top of the stack. // If value is negative, put -1; // If positive, put 1; @@ -345,3 +383,16 @@ func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, e } return ba1, ba2, nil } + +func popTwoBooleans(ctx *stack.Context) (*stack.Boolean, *stack.Boolean, error) { + bool1, err := ctx.Estack.PopBoolean() + if err != nil { + return nil, nil, err + } + bool2, err := ctx.Estack.PopBoolean() + if err != nil { + return nil, nil, err + } + + return bool1, bool2, nil +} diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 29d843999..3d0163f47 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -395,6 +395,52 @@ func TestShrOp(t *testing.T) { assert.Equal(t, int64(2), item.Value().Int64()) } +func TestBoolAndOp(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + b := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.BOOLAND, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + + func TestBoolOrOp(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + b := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.BOOLOR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestLtOp(t *testing.T) { v := VM{} @@ -452,3 +498,4 @@ func TestGtOp(t *testing.T) { assert.Equal(t, true, item.Value()) } + From 7bf4d691a9f651d12708b44657a5587a8979dc5f Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Sat, 30 Mar 2019 16:01:06 +0100 Subject: [PATCH 076/117] Implemented NUMEQUAL, NUMNOTEQUAL opcode (#256) --- pkg/vm/vm_ops.go | 2 ++ pkg/vm/vm_ops_maths.go | 32 ++++++++++++++++++++ pkg/vm/vm_ops_maths_test.go | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index b73b6aa31..d50609462 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,8 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.NUMEQUAL: NumEqual, + stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, stack.LT: Lt, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index d9a3a9bbc..e23a7a4a0 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -173,6 +173,38 @@ func Mul(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } +// NumEqual pops two Items off of the stack and pushes a boolean to the stack +// whose value is true iff the the two Items are equal. +// Returns an error if either items cannot be casted to an integer. +func NumEqual(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandA.Equal(operandB) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// NumNotEqual pops two Items off of the stack and pushes a boolean to the stack +// whose value is true iff the two Items are not equal. +// Returns an error if either items cannot be casted to an integer. +func NumNotEqual(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandA.Equal(operandB) + + ctx.Estack.Push(stack.NewBoolean(!res)) + + return NONE, nil +} + // Abs pops an integer off of the stack and pushes its absolute value onto the stack. // Returns an error if the popped value is not an integer or if the absolute value cannot be taken func Abs(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 3d0163f47..b8478f29b 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -277,6 +277,64 @@ func TestNotOp(t *testing.T) { assert.Equal(t, true, item.Value()) } +func TestNumEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.NUMEQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + +func TestNumNotEqual(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(5)) + if err != nil { + t.Fail() + } + b, err := stack.NewInt(big.NewInt(6)) + if err != nil { + t.Fail() + } + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.NUMNOTEQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + if err != nil { + t.Fail() + } + + assert.Equal(t, true, item.Value()) +} + func TestSignOp(t *testing.T) { v := VM{} From abb4da9cbd615c062979cf544ba1d1c891176160 Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Sat, 30 Mar 2019 18:10:27 +0000 Subject: [PATCH 077/117] [PeerMgr] Add Caching and Re-processing system (#263) [peermgr] - Add request cache with tests - Add requestCache to peermgr - refactored peer manager tests - Added blockInfo struct, to allow sorting on the blockIndex - added helper methods for cache, pickItem, pickFirstItem, removeHash, findHash and refactored tests - renamed requestcache to blockcache - refactored peer manager to use block cache for block requests *only* - added blockCallPeer function to handle block requests only - refactored onDisconnect to add back any pending peer requests that the disconnected peer did not complete into the peer manager queue [peermgr/server] - Modify onBlock handler in server, to send peermgr a BlockInfo struct [peermgr/syncmgr/server] - Modified blockIndex in BlockInfo to be uint32 and not uint64 - RequestBlocks in syncmgr now takes an index along with the hash - modified syncmgr code to pass index along with hash in all methods --- pkg/peermgr/blockcache.go | 155 ++++++++++++++++++++++++++++++++ pkg/peermgr/blockcache_test.go | 80 +++++++++++++++++ pkg/peermgr/peermgr.go | 125 +++++++++++++++++++++++--- pkg/peermgr/peermgr_test.go | 56 ++++++++---- pkg/server/syncmgr.go | 14 ++- pkg/syncmgr/blockmode.go | 2 +- pkg/syncmgr/config.go | 2 +- pkg/syncmgr/headermode.go | 3 +- pkg/syncmgr/mockhelpers_test.go | 2 +- pkg/syncmgr/normalmode.go | 3 +- 10 files changed, 407 insertions(+), 35 deletions(-) create mode 100644 pkg/peermgr/blockcache.go create mode 100644 pkg/peermgr/blockcache_test.go diff --git a/pkg/peermgr/blockcache.go b/pkg/peermgr/blockcache.go new file mode 100644 index 000000000..8e06b8251 --- /dev/null +++ b/pkg/peermgr/blockcache.go @@ -0,0 +1,155 @@ +package peermgr + +import ( + "errors" + "sort" + "sync" + + "github.com/CityOfZion/neo-go/pkg/wire/util" +) + +var ( + //ErrCacheLimit is returned when the cache limit is reached + ErrCacheLimit = errors.New("nomore items can be added to the cache") + + //ErrNoItems is returned when pickItem is called and there are no items in the cache + ErrNoItems = errors.New("there are no items in the cache") + + //ErrDuplicateItem is returned when you try to add the same item, more than once to the cache + ErrDuplicateItem = errors.New("this item is already in the cache") +) + +//BlockInfo holds the necessary information that the cache needs +// to sort and store block requests +type BlockInfo struct { + BlockHash util.Uint256 + BlockIndex uint32 +} + +// Equals returns true if two blockInfo objects +// have the same hash and the same index +func (bi *BlockInfo) Equals(other BlockInfo) bool { + return bi.BlockHash.Equals(other.BlockHash) && bi.BlockIndex == other.BlockIndex +} + +// indexSorter sorts the blockInfos by blockIndex. +type indexSorter []BlockInfo + +func (is indexSorter) Len() int { return len(is) } +func (is indexSorter) Swap(i, j int) { is[i], is[j] = is[j], is[i] } +func (is indexSorter) Less(i, j int) bool { return is[i].BlockIndex < is[j].BlockIndex } + +//blockCache will cache any pending block requests +// for the node when there are no available nodes +type blockCache struct { + cacheLimit int + cacheLock sync.Mutex + cache []BlockInfo +} + +func newBlockCache(cacheLimit int) *blockCache { + return &blockCache{ + cache: make([]BlockInfo, 0, cacheLimit), + cacheLimit: cacheLimit, + } +} + +func (bc *blockCache) addBlockInfo(bi BlockInfo) error { + if bc.cacheLen() == bc.cacheLimit { + return ErrCacheLimit + } + + bc.cacheLock.Lock() + defer bc.cacheLock.Unlock() + + // Check for duplicates. slice will always be small so a simple for loop will work + for _, bInfo := range bc.cache { + if bInfo.Equals(bi) { + return ErrDuplicateItem + } + } + bc.cache = append(bc.cache, bi) + + sort.Sort(indexSorter(bc.cache)) + + return nil +} + +func (bc *blockCache) addBlockInfos(bis []BlockInfo) error { + + if len(bis)+bc.cacheLen() > bc.cacheLimit { + return errors.New("too many items to add, this will exceed the cache limit") + } + + for _, bi := range bis { + err := bc.addBlockInfo(bi) + if err != nil { + return err + } + } + return nil +} + +func (bc *blockCache) cacheLen() int { + bc.cacheLock.Lock() + defer bc.cacheLock.Unlock() + return len(bc.cache) +} + +func (bc *blockCache) pickFirstItem() (BlockInfo, error) { + return bc.pickItem(0) +} + +func (bc *blockCache) pickAllItems() ([]BlockInfo, error) { + + numOfItems := bc.cacheLen() + + items := make([]BlockInfo, 0, numOfItems) + + for i := 0; i < numOfItems; i++ { + bi, err := bc.pickFirstItem() + if err != nil { + return nil, err + } + items = append(items, bi) + } + return items, nil +} + +func (bc *blockCache) pickItem(i uint) (BlockInfo, error) { + if bc.cacheLen() < 1 { + return BlockInfo{}, ErrNoItems + } + + if i >= uint(bc.cacheLen()) { + return BlockInfo{}, errors.New("index out of range") + } + + bc.cacheLock.Lock() + defer bc.cacheLock.Unlock() + + item := bc.cache[i] + bc.cache = append(bc.cache[:i], bc.cache[i+1:]...) + return item, nil +} + +func (bc *blockCache) removeHash(hashToRemove util.Uint256) error { + index, err := bc.findHash(hashToRemove) + if err != nil { + return err + } + + _, err = bc.pickItem(uint(index)) + return err +} + +func (bc *blockCache) findHash(hashToFind util.Uint256) (int, error) { + bc.cacheLock.Lock() + defer bc.cacheLock.Unlock() + for i, bInfo := range bc.cache { + if bInfo.BlockHash.Equals(hashToFind) { + return i, nil + } + } + return -1, errors.New("hash cannot be found in the cache") +} diff --git a/pkg/peermgr/blockcache_test.go b/pkg/peermgr/blockcache_test.go new file mode 100644 index 000000000..3cb928e9e --- /dev/null +++ b/pkg/peermgr/blockcache_test.go @@ -0,0 +1,80 @@ +package peermgr + +import ( + "math/rand" + "testing" + + "github.com/CityOfZion/neo-go/pkg/wire/util" + "github.com/stretchr/testify/assert" +) + +func TestAddBlock(t *testing.T) { + + bc := &blockCache{ + cacheLimit: 20, + } + bi := randomBlockInfo(t) + + err := bc.addBlockInfo(bi) + assert.Equal(t, nil, err) + + assert.Equal(t, 1, bc.cacheLen()) + + err = bc.addBlockInfo(bi) + assert.Equal(t, ErrDuplicateItem, err) + + assert.Equal(t, 1, bc.cacheLen()) +} + +func TestCacheLimit(t *testing.T) { + + bc := &blockCache{ + cacheLimit: 20, + } + + for i := 0; i < bc.cacheLimit; i++ { + err := bc.addBlockInfo(randomBlockInfo(t)) + assert.Equal(t, nil, err) + } + + err := bc.addBlockInfo(randomBlockInfo(t)) + assert.Equal(t, ErrCacheLimit, err) + + assert.Equal(t, bc.cacheLimit, bc.cacheLen()) +} +func TestPickItem(t *testing.T) { + + bc := &blockCache{ + cacheLimit: 20, + } + + for i := 0; i < bc.cacheLimit; i++ { + err := bc.addBlockInfo(randomBlockInfo(t)) + assert.Equal(t, nil, err) + } + + for i := 0; i < bc.cacheLimit; i++ { + _, err := bc.pickFirstItem() + assert.Equal(t, nil, err) + } + + assert.Equal(t, 0, bc.cacheLen()) +} + +func randomUint256(t *testing.T) util.Uint256 { + rand32 := make([]byte, 32) + rand.Read(rand32) + + u, err := util.Uint256DecodeBytes(rand32) + assert.Equal(t, nil, err) + + return u +} + +func randomBlockInfo(t *testing.T) BlockInfo { + + return BlockInfo{ + randomUint256(t), + rand.Uint32(), + } +} diff --git a/pkg/peermgr/peermgr.go b/pkg/peermgr/peermgr.go index 047a109c3..e83e37768 100644 --- a/pkg/peermgr/peermgr.go +++ b/pkg/peermgr/peermgr.go @@ -2,6 +2,7 @@ package peermgr import ( "errors" + "fmt" "sync" "github.com/CityOfZion/neo-go/pkg/wire/command" @@ -9,6 +10,15 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) +const ( + // blockCacheLimit is the maximum amount of pending requests that the cache can hold + pendingBlockCacheLimit = 20 + + //peerBlockCacheLimit is the maximum amount of inflight blocks that a peer can + // have, before they are flagged as busy + peerBlockCacheLimit = 1 +) + var ( //ErrNoAvailablePeers is returned when a request for data from a peer is invoked // but there are no available peers to request data from @@ -28,6 +38,10 @@ type mPeer interface { } type peerstats struct { + // when a peer is sent a blockRequest + // the peermanager will track this using this blockCache + blockCache *blockCache + // all other requests will be tracked using the requests map requests map[command.Type]bool } @@ -35,12 +49,15 @@ type peerstats struct { type PeerMgr struct { pLock sync.RWMutex peers map[mPeer]peerstats + + requestCache *blockCache } //New returns a new peermgr object func New() *PeerMgr { return &PeerMgr{ - peers: make(map[mPeer]peerstats), + peers: make(map[mPeer]peerstats), + requestCache: newBlockCache(pendingBlockCacheLimit), } } @@ -52,7 +69,10 @@ func (pmgr *PeerMgr) AddPeer(peer mPeer) { if _, exists := pmgr.peers[peer]; exists { return } - pmgr.peers[peer] = peerstats{requests: make(map[command.Type]bool)} + pmgr.peers[peer] = peerstats{ + requests: make(map[command.Type]bool), + blockCache: newBlockCache(peerBlockCacheLimit), + } go pmgr.onDisconnect(peer) } @@ -61,6 +81,8 @@ func (pmgr *PeerMgr) AddPeer(peer mPeer) { func (pmgr *PeerMgr) MsgReceived(peer mPeer, cmd command.Type) error { pmgr.pLock.Lock() defer pmgr.pLock.Unlock() + + // if peer was unknown then disconnect val, ok := pmgr.peers[peer] if !ok { @@ -76,6 +98,44 @@ func (pmgr *PeerMgr) MsgReceived(peer mPeer, cmd command.Type) error { return nil } +//BlockMsgReceived notifies the peer manager that we have received a +// block message from a peer +func (pmgr *PeerMgr) BlockMsgReceived(peer mPeer, bi BlockInfo) error { + + // if peer was unknown then disconnect + val, ok := pmgr.peers[peer] + if !ok { + + go func() { + peer.NotifyDisconnect() + }() + + peer.Disconnect() + return ErrUnknownPeer + } + + // // remove item from the peersBlock cache + err := val.blockCache.removeHash(bi.BlockHash) + if err != nil { + return err + } + + // check if cache empty, if so then return + if pmgr.requestCache.cacheLen() == 0 { + return nil + } + + // Try to clean an item from the pendingBlockCache, a peer has just finished serving a block request + cachedBInfo, err := pmgr.requestCache.pickFirstItem() + if err != nil { + return err + } + + return pmgr.blockCallPeer(cachedBInfo, func(p mPeer) error { + return p.RequestBlocks([]util.Uint256{cachedBInfo.BlockHash}) + }) +} + // Len returns the amount of peers that the peer manager //currently knows about func (pmgr *PeerMgr) Len() int { @@ -84,25 +144,34 @@ func (pmgr *PeerMgr) Len() int { return len(pmgr.peers) } -// RequestBlock will request a block from the most +// RequestBlock will request a block from the most // available peer. Then update it's stats, so we know that // this peer is busy -func (pmgr *PeerMgr) RequestBlock(hash util.Uint256) error { - return pmgr.callPeerForCmd(command.Block, func(p mPeer) error { - return p.RequestBlocks([]util.Uint256{hash}) +func (pmgr *PeerMgr) RequestBlock(bi BlockInfo) error { + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() + + err := pmgr.blockCallPeer(bi, func(p mPeer) error { + return p.RequestBlocks([]util.Uint256{bi.BlockHash}) }) + + if err == ErrNoAvailablePeers { + return pmgr.requestCache.addBlockInfo(bi) + } + + return err } // RequestHeaders will request a headers from the most available peer. func (pmgr *PeerMgr) RequestHeaders(hash util.Uint256) error { + pmgr.pLock.Lock() + defer pmgr.pLock.Unlock() return pmgr.callPeerForCmd(command.Headers, func(p mPeer) error { return p.RequestHeaders(hash) }) } func (pmgr *PeerMgr) callPeerForCmd(cmd command.Type, f func(p mPeer) error) error { - pmgr.pLock.Lock() - defer pmgr.pLock.Unlock() for peer, stats := range pmgr.peers { if !stats.requests[cmd] { stats.requests[cmd] = true @@ -111,12 +180,48 @@ func (pmgr *PeerMgr) callPeerForCmd(cmd command.Type, f func(p mPeer) error) err } return ErrNoAvailablePeers } + +func (pmgr *PeerMgr) blockCallPeer(bi BlockInfo, f func(p mPeer) error) error { + for peer, stats := range pmgr.peers { + if stats.blockCache.cacheLen() < peerBlockCacheLimit { + err := stats.blockCache.addBlockInfo(bi) + if err != nil { + return err + } + return f(peer) + } + } + return ErrNoAvailablePeers +} + func (pmgr *PeerMgr) onDisconnect(p mPeer) { // Blocking until peer is disconnected p.NotifyDisconnect() pmgr.pLock.Lock() - delete(pmgr.peers, p) - pmgr.pLock.Unlock() + defer func() { + delete(pmgr.peers, p) + pmgr.pLock.Unlock() + }() + + // Add all of peers outstanding block requests into + // the peer managers pendingBlockRequestCache + + val, ok := pmgr.peers[p] + if !ok { + return + } + + pendingRequests, err := val.blockCache.pickAllItems() + if err != nil { + fmt.Println(err.Error()) + return + } + + err = pmgr.requestCache.addBlockInfos(pendingRequests) + if err != nil { + fmt.Println(err.Error()) + return + } } diff --git a/pkg/peermgr/peermgr_test.go b/pkg/peermgr/peermgr_test.go index 86d5be41c..9a725af43 100644 --- a/pkg/peermgr/peermgr_test.go +++ b/pkg/peermgr/peermgr_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/CityOfZion/neo-go/pkg/wire/command" - "github.com/CityOfZion/neo-go/pkg/wire/util" "github.com/stretchr/testify/assert" ) @@ -59,42 +58,65 @@ func TestRequestBlocks(t *testing.T) { pmgr.AddPeer(peerB) pmgr.AddPeer(peerC) - err := pmgr.RequestBlock(util.Uint256{}) + firstBlock := randomBlockInfo(t) + err := pmgr.RequestBlock(firstBlock) assert.Nil(t, err) - err = pmgr.RequestBlock(util.Uint256{}) + secondBlock := randomBlockInfo(t) + err = pmgr.RequestBlock(secondBlock) assert.Nil(t, err) - err = pmgr.RequestBlock(util.Uint256{}) + thirdBlock := randomBlockInfo(t) + err = pmgr.RequestBlock(thirdBlock) assert.Nil(t, err) // Since the peer manager did not get a MsgReceived // in between the block requests // a request should be sent to all peers + // This is only true, if peerBlockCacheLimit == 1 assert.Equal(t, 1, peerA.blockRequested) assert.Equal(t, 1, peerB.blockRequested) assert.Equal(t, 1, peerC.blockRequested) // Since the peer manager still has not received a MsgReceived - // another call to request blocks, will return a NoAvailablePeerError + // another call to request blocks, will add the request to the cache + // and return a nil err - err = pmgr.RequestBlock(util.Uint256{}) - assert.Equal(t, ErrNoAvailablePeers, err) + fourthBlock := randomBlockInfo(t) + err = pmgr.RequestBlock(fourthBlock) + assert.Equal(t, nil, err) + assert.Equal(t, 1, pmgr.requestCache.cacheLen()) - // If we tell the peer manager that peerA has given us a block - // then send another BlockRequest. It will go to peerA - // since the other two peers are still busy with their - // block requests + // If we tell the peer manager that we have received a block + // it will check the cache for any pending requests and send a block request if there are any. + // The request will go to the peer who sent back the block corresponding to the first hash + // since the other two peers are still busy with their block requests - pmgr.MsgReceived(peerA, command.Block) - err = pmgr.RequestBlock(util.Uint256{}) + peer := findPeerwithHash(t, pmgr, firstBlock.BlockHash) + err = pmgr.BlockMsgReceived(peer, firstBlock) assert.Nil(t, err) - assert.Equal(t, 2, peerA.blockRequested) - assert.Equal(t, 1, peerB.blockRequested) - assert.Equal(t, 1, peerC.blockRequested) + totalRequests := peerA.blockRequested + peerB.blockRequested + peerC.blockRequested + assert.Equal(t, 4, totalRequests) + + // // cache should be empty now + assert.Equal(t, 0, pmgr.requestCache.cacheLen()) } + +// The peer manager does not tell you what peer was sent a particular block request +// For testing purposes, the following function will find that peer +func findPeerwithHash(t *testing.T, pmgr *PeerMgr, blockHash util.Uint256) mPeer { + for peer, stats := range pmgr.peers { + _, err := stats.blockCache.findHash(blockHash) + if err == nil { + return peer + } + } + assert.Fail(t, "cannot find a peer with that hash") + return nil +} + func TestRequestHeaders(t *testing.T) { pmgr := New() @@ -152,7 +174,7 @@ func TestUnknownPeer(t *testing.T) { quit: make(chan bool), } - err := pmgr.MsgReceived(unknownPeer, command.Block) + err := pmgr.MsgReceived(unknownPeer, command.Headers) assert.Equal(t, true, unknownPeer.disconnected) assert.Equal(t, ErrUnknownPeer, err) } diff --git a/pkg/server/syncmgr.go b/pkg/server/syncmgr.go index 3df456626..7de87188b 100644 --- a/pkg/server/syncmgr.go +++ b/pkg/server/syncmgr.go @@ -3,6 +3,8 @@ package server import ( "encoding/binary" + "github.com/CityOfZion/neo-go/pkg/peermgr" + "github.com/CityOfZion/neo-go/pkg/peer" "github.com/CityOfZion/neo-go/pkg/syncmgr" "github.com/CityOfZion/neo-go/pkg/wire/payload" @@ -34,7 +36,10 @@ func (s *Server) onHeader(peer *peer.Peer, hdrsMessage *payload.HeadersMessage) } func (s *Server) onBlock(peer *peer.Peer, blockMsg *payload.BlockMessage) { - s.pmg.MsgReceived(peer, blockMsg.Command()) + s.pmg.BlockMsgReceived(peer, peermgr.BlockInfo{ + BlockHash: blockMsg.Hash, + BlockIndex: blockMsg.Index, + }) s.smg.OnBlock(peer, blockMsg) } @@ -50,8 +55,11 @@ func (s *Server) requestHeaders(hash util.Uint256) error { return s.pmg.RequestHeaders(hash) } -func (s *Server) requestBlock(hash util.Uint256) error { - return s.pmg.RequestBlock(hash) +func (s *Server) requestBlock(hash util.Uint256, index uint32) error { + return s.pmg.RequestBlock(peermgr.BlockInfo{ + BlockHash: hash, + BlockIndex: index, + }) } // getNextBlockHash searches the database for the blockHash diff --git a/pkg/syncmgr/blockmode.go b/pkg/syncmgr/blockmode.go index 2e5dadd6f..83d3acd50 100644 --- a/pkg/syncmgr/blockmode.go +++ b/pkg/syncmgr/blockmode.go @@ -32,7 +32,7 @@ func (s *Syncmgr) blockModeOnBlock(peer SyncPeer, block payload.Block) error { if err != nil { return err } - err = s.cfg.RequestBlock(nextHash) + err = s.cfg.RequestBlock(nextHash, block.Index) return err } diff --git a/pkg/syncmgr/config.go b/pkg/syncmgr/config.go index af27f37c6..713059802 100644 --- a/pkg/syncmgr/config.go +++ b/pkg/syncmgr/config.go @@ -18,7 +18,7 @@ type Config struct { //RequestBlock will send a getdata request for the block // with the hash passed as a parameter - RequestBlock func(hash util.Uint256) error + RequestBlock func(hash util.Uint256, index uint32) error // GetNextBlockHash returns the block hash of the header infront of thr block // at the tip of this nodes chain. This assumes that the node is not in sync diff --git a/pkg/syncmgr/headermode.go b/pkg/syncmgr/headermode.go index 898dc933d..3a8e4d681 100644 --- a/pkg/syncmgr/headermode.go +++ b/pkg/syncmgr/headermode.go @@ -12,6 +12,7 @@ func (s *Syncmgr) headersModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) // Note: For the un-optimised version, we move straight to blocksOnly mode firstHash := hdrs[0].Hash + firstHdrIndex := hdrs[0].Index err := s.cfg.ProcessHeaders(hdrs) if err == nil { @@ -19,7 +20,7 @@ func (s *Syncmgr) headersModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) s.headerHash = hdrs[len(hdrs)-1].Hash s.syncmode = blockMode - return s.cfg.RequestBlock(firstHash) + return s.cfg.RequestBlock(firstHash, firstHdrIndex) } // Check whether it is a validation error, or a database error diff --git a/pkg/syncmgr/mockhelpers_test.go b/pkg/syncmgr/mockhelpers_test.go index 8c84225ac..157fdd513 100644 --- a/pkg/syncmgr/mockhelpers_test.go +++ b/pkg/syncmgr/mockhelpers_test.go @@ -46,7 +46,7 @@ func (s *syncTestHelper) FetchBlockAgain(util.Uint256) error { return s.err } -func (s *syncTestHelper) RequestBlock(util.Uint256) error { +func (s *syncTestHelper) RequestBlock(util.Uint256, uint32) error { s.blockFetchRequest++ return s.err } diff --git a/pkg/syncmgr/normalmode.go b/pkg/syncmgr/normalmode.go index 218d6151b..10bbac365 100644 --- a/pkg/syncmgr/normalmode.go +++ b/pkg/syncmgr/normalmode.go @@ -16,6 +16,7 @@ func (s *Syncmgr) normalModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) lenHeaders := len(hdrs) firstHash := hdrs[0].Hash + firstHdrIndex := hdrs[0].Index lastHash := hdrs[lenHeaders-1].Hash // Update syncmgr latest header @@ -32,7 +33,7 @@ func (s *Syncmgr) normalModeOnHeaders(peer SyncPeer, hdrs []*payload.BlockBase) // Bounds state that len > 1 && len!= 2000 & maxHeadersInMessage == 2000 // This means that we have less than 2k headers s.syncmode = blockMode - return s.cfg.RequestBlock(firstHash) + return s.cfg.RequestBlock(firstHash, firstHdrIndex) } // normalModeOnBlock is called when the sync manager is normal mode From 5ed61ff389002f94f60fbe2da7d6c2549581c1bc Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 21:32:44 +0000 Subject: [PATCH 078/117] [syncmgr] - add blockpool plus test --- pkg/syncmgr/blockpool.go | 57 +++++++++++++++++++++++++++++++++++ pkg/syncmgr/blockpool_test.go | 42 ++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 pkg/syncmgr/blockpool.go create mode 100644 pkg/syncmgr/blockpool_test.go diff --git a/pkg/syncmgr/blockpool.go b/pkg/syncmgr/blockpool.go new file mode 100644 index 000000000..b038b29f3 --- /dev/null +++ b/pkg/syncmgr/blockpool.go @@ -0,0 +1,57 @@ +package syncmgr + +import ( + "sort" + + "github.com/CityOfZion/neo-go/pkg/wire/payload" +) + +func (s *Syncmgr) addToBlockPool(newBlock payload.Block) { + s.poolLock.Lock() + defer s.poolLock.Unlock() + + for _, block := range s.blockPool { + if block.Index == newBlock.Index { + return + } + } + + s.blockPool = append(s.blockPool, newBlock) + + // sort slice using block index + sort.Slice(s.blockPool, func(i, j int) bool { + return s.blockPool[i].Index < s.blockPool[j].Index + }) + +} + +func (s *Syncmgr) checkPool() error { + // Assuming that the blocks are sorted in order + + var indexesToRemove = -1 + + s.poolLock.Lock() + defer func() { + // removes all elements before this index, including the element at this index + s.blockPool = s.blockPool[indexesToRemove+1:] + s.poolLock.Unlock() + }() + + // loop iterates through the cache, processing any + // blocks that can be added to the chain + for i, block := range s.blockPool { + if s.nextBlockIndex != block.Index { + break + } + + // Save this block and save the indice so we can remove it + err := s.processBlock(block) + if err != nil { + return err + } + + indexesToRemove = i + } + + return nil +} diff --git a/pkg/syncmgr/blockpool_test.go b/pkg/syncmgr/blockpool_test.go new file mode 100644 index 000000000..c236afc2c --- /dev/null +++ b/pkg/syncmgr/blockpool_test.go @@ -0,0 +1,42 @@ +package syncmgr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddBlockPoolFlush(t *testing.T) { + syncmgr, _ := setupSyncMgr(blockMode, 10) + + blockMessage := randomBlockMessage(t, 11) + + peer := &mockPeer{ + height: 100, + } + + // Since the block has Index 11 and the sync manager needs the block with index 10 + // This block will be added to the blockPool + err := syncmgr.OnBlock(peer, blockMessage) + assert.Nil(t, err) + assert.Equal(t, 1, len(syncmgr.blockPool)) + + // The sync manager is still looking for the block at height 10 + // Since this block is at height 12, it will be added to the block pool + blockMessage = randomBlockMessage(t, 12) + err = syncmgr.OnBlock(peer, blockMessage) + assert.Nil(t, err) + assert.Equal(t, 2, len(syncmgr.blockPool)) + + // This is the block that the sync manager was waiting for + // It should process this block, the check the pool for the next set of blocks + blockMessage = randomBlockMessage(t, 10) + err = syncmgr.OnBlock(peer, blockMessage) + assert.Nil(t, err) + assert.Equal(t, 0, len(syncmgr.blockPool)) + + // Since we processed 3 blocks and the sync manager started + //looking for block with index 10. The syncmananger should be looking for + // the block with index 13 + assert.Equal(t, uint32(13), syncmgr.nextBlockIndex) +} From c401247af9ed72c38db53dabe8779222be1c2fb9 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 21:34:27 +0000 Subject: [PATCH 079/117] [syncmgr] - refactor syncmgr for blockpool - add nextBlockIndex so that we can add blocks to the blockPool by comparing their index - add processBlock helper method,wraps around cfg.ProcessBlock and increments the nextBlockIndex internally --- pkg/syncmgr/syncmgr.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/syncmgr/syncmgr.go b/pkg/syncmgr/syncmgr.go index bb06dfa7e..dc639a8d3 100644 --- a/pkg/syncmgr/syncmgr.go +++ b/pkg/syncmgr/syncmgr.go @@ -2,6 +2,7 @@ package syncmgr import ( "fmt" + "sync" "time" "github.com/CityOfZion/neo-go/pkg/wire/payload" @@ -50,10 +51,14 @@ type Syncmgr struct { // When receiving blocks, we can use this to determine whether the node has downloaded // all of the blocks for the last headers messages headerHash util.Uint256 + + poolLock sync.Mutex + blockPool []payload.Block + nextBlockIndex uint32 } // New creates a new sync manager -func New(cfg *Config) *Syncmgr { +func New(cfg *Config, nextBlockIndex uint32) *Syncmgr { newBlockTimer := time.AfterFunc(blockTimer, func() { cfg.AskForNewBlocks() @@ -61,9 +66,10 @@ func New(cfg *Config) *Syncmgr { newBlockTimer.Stop() return &Syncmgr{ - syncmode: headersMode, - cfg: cfg, - timer: newBlockTimer, + syncmode: headersMode, + cfg: cfg, + timer: newBlockTimer, + nextBlockIndex: nextBlockIndex, } } @@ -133,3 +139,14 @@ func (s *Syncmgr) OnBlock(peer SyncPeer, msg *payload.BlockMessage) error { func (s *Syncmgr) IsCurrent() bool { return s.syncmode == normalMode } + +func (s *Syncmgr) processBlock(block payload.Block) error { + err := s.cfg.ProcessBlock(block) + if err != nil { + return err + } + + s.nextBlockIndex++ + + return nil +} From 751d2711d478b9b8328c27e92d68b2e86269cbc1 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 21:36:50 +0000 Subject: [PATCH 080/117] [syncmgr] - refactored OnBlockBlockMode to use blockPool - syncmgr now checks for future blocks, instead of looking for a FutureBlockErr from the Chain. This makes it so that syncmgr does not depend on the Chain package. - removed GetBestBlockHash function from config(unused) - refactored all tests in the syncmgr package --- pkg/syncmgr/blockmode.go | 30 ++++++++++++++++----------- pkg/syncmgr/config.go | 3 --- pkg/syncmgr/mockhelpers_test.go | 4 ++-- pkg/syncmgr/normalmode.go | 2 +- pkg/syncmgr/syncmgr_onblock_test.go | 12 +++++------ pkg/syncmgr/syncmgr_onheaders_test.go | 14 ++++++------- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/pkg/syncmgr/blockmode.go b/pkg/syncmgr/blockmode.go index 83d3acd50..406204bf5 100644 --- a/pkg/syncmgr/blockmode.go +++ b/pkg/syncmgr/blockmode.go @@ -9,22 +9,29 @@ import ( // and receives a block. func (s *Syncmgr) blockModeOnBlock(peer SyncPeer, block payload.Block) error { - // Process Block - err := s.cfg.ProcessBlock(block) - - if err == chain.ErrFutureBlock { - // XXX(Optimisation): We can cache future blocks in blockmode, if we have the corresponding header - // We can have the server cache them and sort out the semantics for when to send them to the chain - // Server can listen on chain for when a new block is saved - // or we could embed a struct in this syncmgr called blockCache, syncmgr can just tell it when it has processed - //a block and we can call ProcessBlock - return err + // Check if it is a future block + // XXX: since we are storing blocks in memory, we do not want to store blocks + // from the tip + if block.Index > s.nextBlockIndex+2000 { + return nil + } + if block.Index != s.nextBlockIndex { + s.addToBlockPool(block) + return nil } + // Process Block + err := s.processBlock(block) if err != nil && err != chain.ErrBlockAlreadyExists { return s.cfg.FetchBlockAgain(block.Hash) } + // Check the block pool + err = s.checkPool() + if err != nil { + return err + } + // Check if blockhashReceived == the header hash from last get headers this node performed // if not then increment and request next block if s.headerHash != block.Hash { @@ -32,8 +39,7 @@ func (s *Syncmgr) blockModeOnBlock(peer SyncPeer, block payload.Block) error { if err != nil { return err } - err = s.cfg.RequestBlock(nextHash, block.Index) - return err + return s.cfg.RequestBlock(nextHash, block.Index) } // If we are caught up then go into normal mode diff --git a/pkg/syncmgr/config.go b/pkg/syncmgr/config.go index 713059802..43387d414 100644 --- a/pkg/syncmgr/config.go +++ b/pkg/syncmgr/config.go @@ -24,9 +24,6 @@ type Config struct { // at the tip of this nodes chain. This assumes that the node is not in sync GetNextBlockHash func() (util.Uint256, error) - // GetBestBlockHash gets the block hash of the last saved block. - GetBestBlockHash func() (util.Uint256, error) - // AskForNewBlocks will send out a message to the network // asking for new blocks AskForNewBlocks func() diff --git a/pkg/syncmgr/mockhelpers_test.go b/pkg/syncmgr/mockhelpers_test.go index 157fdd513..d95e95e6a 100644 --- a/pkg/syncmgr/mockhelpers_test.go +++ b/pkg/syncmgr/mockhelpers_test.go @@ -89,7 +89,7 @@ func randomUint256(t *testing.T) util.Uint256 { return u } -func setupSyncMgr(mode mode) (*Syncmgr, *syncTestHelper) { +func setupSyncMgr(mode mode, nextBlockIndex uint32) (*Syncmgr, *syncTestHelper) { helper := &syncTestHelper{} cfg := &Config{ @@ -106,7 +106,7 @@ func setupSyncMgr(mode mode) (*Syncmgr, *syncTestHelper) { RequestHeaders: helper.RequestHeaders, } - syncmgr := New(cfg) + syncmgr := New(cfg, nextBlockIndex) syncmgr.syncmode = mode return syncmgr, helper diff --git a/pkg/syncmgr/normalmode.go b/pkg/syncmgr/normalmode.go index 10bbac365..ad22e52f2 100644 --- a/pkg/syncmgr/normalmode.go +++ b/pkg/syncmgr/normalmode.go @@ -43,7 +43,7 @@ func (s *Syncmgr) normalModeOnBlock(peer SyncPeer, block payload.Block) error { s.timer.Stop() // process block - err := s.cfg.ProcessBlock(block) + err := s.processBlock(block) if err != nil { s.timer.Reset(blockTimer) return s.cfg.FetchBlockAgain(block.Hash) diff --git a/pkg/syncmgr/syncmgr_onblock_test.go b/pkg/syncmgr/syncmgr_onblock_test.go index d5b79f0b3..d08aa8a74 100644 --- a/pkg/syncmgr/syncmgr_onblock_test.go +++ b/pkg/syncmgr/syncmgr_onblock_test.go @@ -11,7 +11,7 @@ import ( func TestHeadersModeOnBlock(t *testing.T) { - syncmgr, helper := setupSyncMgr(headersMode) + syncmgr, helper := setupSyncMgr(headersMode, 0) syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) @@ -21,7 +21,7 @@ func TestHeadersModeOnBlock(t *testing.T) { func TestBlockModeOnBlock(t *testing.T) { - syncmgr, helper := setupSyncMgr(blockMode) + syncmgr, helper := setupSyncMgr(blockMode, 0) syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) @@ -30,7 +30,7 @@ func TestBlockModeOnBlock(t *testing.T) { } func TestNormalModeOnBlock(t *testing.T) { - syncmgr, helper := setupSyncMgr(normalMode) + syncmgr, helper := setupSyncMgr(normalMode, 0) syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 0)) @@ -40,7 +40,7 @@ func TestNormalModeOnBlock(t *testing.T) { func TestBlockModeToNormalMode(t *testing.T) { - syncmgr, _ := setupSyncMgr(blockMode) + syncmgr, _ := setupSyncMgr(blockMode, 100) peer := &mockPeer{ height: 100, @@ -57,7 +57,7 @@ func TestBlockModeToNormalMode(t *testing.T) { } func TestBlockModeStayInBlockMode(t *testing.T) { - syncmgr, _ := setupSyncMgr(blockMode) + syncmgr, _ := setupSyncMgr(blockMode, 0) // We need our latest know hash to not be equal to the hash // of the block we received, to stay in blockmode @@ -77,7 +77,7 @@ func TestBlockModeStayInBlockMode(t *testing.T) { } func TestBlockModeAlreadyExistsErr(t *testing.T) { - syncmgr, helper := setupSyncMgr(blockMode) + syncmgr, helper := setupSyncMgr(blockMode, 100) helper.err = chain.ErrBlockAlreadyExists syncmgr.OnBlock(&mockPeer{}, randomBlockMessage(t, 100)) diff --git a/pkg/syncmgr/syncmgr_onheaders_test.go b/pkg/syncmgr/syncmgr_onheaders_test.go index 2f60a4b72..f2bcef3f3 100644 --- a/pkg/syncmgr/syncmgr_onheaders_test.go +++ b/pkg/syncmgr/syncmgr_onheaders_test.go @@ -12,7 +12,7 @@ import ( func TestHeadersModeOnHeaders(t *testing.T) { - syncmgr, helper := setupSyncMgr(headersMode) + syncmgr, helper := setupSyncMgr(headersMode, 0) syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 0)) @@ -29,14 +29,14 @@ func TestHeadersModeOnHeaders(t *testing.T) { } func TestBlockModeOnHeaders(t *testing.T) { - syncmgr, helper := setupSyncMgr(blockMode) + syncmgr, helper := setupSyncMgr(blockMode, 0) // If we receive a header in blockmode, no headers will be processed syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 100)) assert.Equal(t, 0, helper.headersProcessed) } func TestNormalModeOnHeadersMaxHeaders(t *testing.T) { - syncmgr, helper := setupSyncMgr(normalMode) + syncmgr, helper := setupSyncMgr(normalMode, 0) // If we receive a header in normalmode, headers will be processed syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 2000)) @@ -49,7 +49,7 @@ func TestNormalModeOnHeadersMaxHeaders(t *testing.T) { // This differs from the previous function in that //we did not receive the max amount of headers func TestNormalModeOnHeaders(t *testing.T) { - syncmgr, helper := setupSyncMgr(normalMode) + syncmgr, helper := setupSyncMgr(normalMode, 0) // If we receive a header in normalmode, headers will be processed syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) @@ -60,7 +60,7 @@ func TestNormalModeOnHeaders(t *testing.T) { } func TestLastHeaderUpdates(t *testing.T) { - syncmgr, _ := setupSyncMgr(headersMode) + syncmgr, _ := setupSyncMgr(headersMode, 0) hdrsMessage := randomHeadersMessage(t, 200) hdrs := hdrsMessage.Headers @@ -95,7 +95,7 @@ func TestLastHeaderUpdates(t *testing.T) { func TestHeadersModeOnHeadersErr(t *testing.T) { - syncmgr, helper := setupSyncMgr(headersMode) + syncmgr, helper := setupSyncMgr(headersMode, 0) helper.err = &chain.ValidationError{} syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) @@ -106,7 +106,7 @@ func TestHeadersModeOnHeadersErr(t *testing.T) { } func TestNormalModeOnHeadersErr(t *testing.T) { - syncmgr, helper := setupSyncMgr(normalMode) + syncmgr, helper := setupSyncMgr(normalMode, 0) helper.err = &chain.ValidationError{} syncmgr.OnHeader(&mockPeer{}, randomHeadersMessage(t, 200)) From 68b0e2e3f2d545c3b38328250e3d59af7c1100d0 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 21:39:39 +0000 Subject: [PATCH 081/117] [server] - modified server to pass the syncmgr the lastBlock.Index+1 so that syncmgr knows what blockIndex it should be looking for upon initialisation --- pkg/server/server.go | 5 ++++- pkg/server/syncmgr.go | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/server/server.go b/pkg/server/server.go index b1e4c0f5b..7730ee79b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -56,7 +56,10 @@ func New(net protocol.Magic, port uint16) (*Server, error) { s.chain = chain // Setup sync manager - syncmgr := setupSyncManager(s) + syncmgr, err := setupSyncManager(s) + if err != nil { + return nil, err + } s.smg = syncmgr // Setup connection manager diff --git a/pkg/server/syncmgr.go b/pkg/server/syncmgr.go index 7de87188b..e6020d5b9 100644 --- a/pkg/server/syncmgr.go +++ b/pkg/server/syncmgr.go @@ -11,7 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/wire/util" ) -func setupSyncManager(s *Server) *syncmgr.Syncmgr { +func setupSyncManager(s *Server) (*syncmgr.Syncmgr, error) { cfg := &syncmgr.Config{ ProcessBlock: s.processBlock, @@ -27,7 +27,15 @@ func setupSyncManager(s *Server) *syncmgr.Syncmgr { FetchBlockAgain: s.fetchBlockAgain, } - return syncmgr.New(cfg) + // Add nextBlockIndex in syncmgr + lastBlock, err := s.chain.Db.GetLastBlock() + if err != nil { + return nil, err + } + + nextBlockIndex := lastBlock.Index + 1 + + return syncmgr.New(cfg, nextBlockIndex), nil } func (s *Server) onHeader(peer *peer.Peer, hdrsMessage *payload.HeadersMessage) { From 06e71d9b7e166025201b8120e10a883c2d2db379 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 21:44:24 +0000 Subject: [PATCH 082/117] [syncmgr] - update comment --- pkg/syncmgr/blockpool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/syncmgr/blockpool.go b/pkg/syncmgr/blockpool.go index b038b29f3..2b1f37761 100644 --- a/pkg/syncmgr/blockpool.go +++ b/pkg/syncmgr/blockpool.go @@ -44,7 +44,7 @@ func (s *Syncmgr) checkPool() error { break } - // Save this block and save the indice so we can remove it + // Save this block and save the indice location so we can remove it, when we defer err := s.processBlock(block) if err != nil { return err From 336fc02ad5b7dba862e20b1dcc9189214cb38ad5 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sat, 30 Mar 2019 22:42:07 +0000 Subject: [PATCH 083/117] [syncmgr] - Fix bug; accounts for a fresh database startup and we only have the genesis block --- pkg/syncmgr/blockmode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/syncmgr/blockmode.go b/pkg/syncmgr/blockmode.go index 406204bf5..f357ca642 100644 --- a/pkg/syncmgr/blockmode.go +++ b/pkg/syncmgr/blockmode.go @@ -15,7 +15,7 @@ func (s *Syncmgr) blockModeOnBlock(peer SyncPeer, block payload.Block) error { if block.Index > s.nextBlockIndex+2000 { return nil } - if block.Index != s.nextBlockIndex { + if block.Index > s.nextBlockIndex { s.addToBlockPool(block) return nil } From fb672c00adc58279ebb7a77f111eb398bc00ecdb Mon Sep 17 00:00:00 2001 From: decentralisedkev <37423678+decentralisedkev@users.noreply.github.com> Date: Sat, 30 Mar 2019 23:52:28 +0000 Subject: [PATCH 084/117] [travis] add verbose flag to test --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf7f94759..630f4078d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,9 @@ install: - go mod tidy -v script: - golint -set_exit_status ./... - - go test -race -coverprofile=coverage.txt -covermode=atomic ./... + - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... after_success: - bash <(curl -s https://codecov.io/bash) matrix: allow_failures: - - go: tip \ No newline at end of file + - go: tip From 12149c93790fb2a7b613b2de54f4ce59f247c1f5 Mon Sep 17 00:00:00 2001 From: BlockChainDev Date: Sun, 31 Mar 2019 00:24:10 +0000 Subject: [PATCH 085/117] [server] - remove extra call after adding a peer - switched endian for headers endian switch --- pkg/server/connmgr.go | 15 --------------- pkg/server/server.go | 3 ++- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/pkg/server/connmgr.go b/pkg/server/connmgr.go index 64b16bdaf..e97416801 100644 --- a/pkg/server/connmgr.go +++ b/pkg/server/connmgr.go @@ -1,7 +1,6 @@ package server import ( - "encoding/hex" "fmt" "net" "strconv" @@ -9,7 +8,6 @@ import ( "github.com/CityOfZion/neo-go/pkg/connmgr" "github.com/CityOfZion/neo-go/pkg/peer" - "github.com/CityOfZion/neo-go/pkg/wire/util" iputils "github.com/CityOfZion/neo-go/pkg/wire/util/ip" ) @@ -34,19 +32,6 @@ func (s *Server) onConnection(conn net.Conn, addr string) { } s.pmg.AddPeer(p) - - byt, err := hex.DecodeString("d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf") - if err != nil { - fmt.Println("Error getting hash " + err.Error()) - } - lh, err := util.Uint256DecodeBytes(byt) - if err != nil { - fmt.Println("Error getting hash " + err.Error()) - } - err = p.RequestHeaders(lh.Reverse()) - if err != nil { - fmt.Println(err) - } } func (s *Server) onAccept(conn net.Conn) { diff --git a/pkg/server/server.go b/pkg/server/server.go index 7730ee79b..22a781170 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -95,7 +95,8 @@ func (s *Server) Run() error { if err != nil { return err } - err = s.pmg.RequestHeaders(bestHeader.Hash.Reverse()) + + err = s.pmg.RequestHeaders(bestHeader.Hash) if err != nil { return err } From c6cd0e0c21c0b4e5c61d882a1d9f7f63c3d4ce1c Mon Sep 17 00:00:00 2001 From: DauTT Date: Tue, 2 Apr 2019 22:38:41 +0200 Subject: [PATCH 086/117] Implemented Map Stack Item: 1) Added new file map.go, map_test.go 2) Added Map, Hash Method to Item interface 3) Implemented Hash Method for every stack items (Boolean, Array, Int, ...) --- pkg/vm/stack/Int.go | 11 ++- pkg/vm/stack/array.go | 23 +++++ pkg/vm/stack/boolean.go | 10 +++ pkg/vm/stack/bytearray.go | 12 +++ pkg/vm/stack/context.go | 7 ++ pkg/vm/stack/map.go | 166 ++++++++++++++++++++++++++++++++++++ pkg/vm/stack/map_test.go | 141 ++++++++++++++++++++++++++++++ pkg/vm/stack/stackitem.go | 12 +++ pkg/vm/stack/test_helper.go | 12 +++ 9 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 pkg/vm/stack/map.go create mode 100644 pkg/vm/stack/map_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 3226904ee..08e800e36 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -1,6 +1,9 @@ package stack -import "math/big" +import ( + "fmt" + "math/big" +) // Int represents an integer on the stack type Int struct { @@ -125,3 +128,9 @@ func (i *Int) Lt(s *Int) bool { func (i *Int) Gt(s *Int) bool { return i.Value().Cmp(s.Value()) == 1 } + +// Hash overrides the default abstract hash method. +func (i *Int) Hash() (string, error) { + data := fmt.Sprintf("%T %v", i, i.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go index 96fe876a4..c11730b1d 100644 --- a/pkg/vm/stack/array.go +++ b/pkg/vm/stack/array.go @@ -1,5 +1,9 @@ package stack +import ( + "fmt" +) + // Array represents an Array of stackItems on the stack type Array struct { *abstractItem @@ -11,3 +15,22 @@ type Array struct { func (a *Array) Array() (*Array, error) { return a, nil } + +//Value returns the underlying Array's value +func (a *Array) Value() []Item { + return a.val +} + +// NewArray returns a new Array. +func NewArray(val []Item) *Array { + return &Array{ + &abstractItem{}, + val, + } +} + +// Hash overrides the default abstract hash method. +func (a *Array) Hash() (string, error) { + data := fmt.Sprintf("%T %v", a, a.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/boolean.go b/pkg/vm/stack/boolean.go index 5a5a93207..5dc1e6e65 100644 --- a/pkg/vm/stack/boolean.go +++ b/pkg/vm/stack/boolean.go @@ -1,5 +1,9 @@ package stack +import ( + "fmt" +) + // Boolean represents a boolean value on the stack type Boolean struct { *abstractItem @@ -44,3 +48,9 @@ func (b *Boolean) Or(a *Boolean) *Boolean { c := b.Value() || a.Value() return NewBoolean(c) } + +// Hash overrides the default abstract hash method. +func (b *Boolean) Hash() (string, error) { + data := fmt.Sprintf("%T %v", b, b.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d1c3c818..2b9c7f69f 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -3,6 +3,7 @@ package stack import ( "bytes" "errors" + "fmt" "math/big" "strconv" ) @@ -69,3 +70,14 @@ func reverse(b []byte) []byte { return dest } + +//Value returns the underlying ByteArray's value. +func (ba *ByteArray) Value() []byte { + return ba.val +} + +// Hash overrides the default abstract hash method. +func (ba *ByteArray) Hash() (string, error) { + data := fmt.Sprintf("%T %v", ba, ba.Value()) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index d381d74cb..0b8ed8bd2 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -3,6 +3,7 @@ package stack import ( "encoding/binary" "errors" + "fmt" ) // Context represent the current execution context of the VM. @@ -150,3 +151,9 @@ func (c *Context) readVarBytes() ([]byte, error) { } return c.ReadBytes(int(n)) } + +// Hash overrides the default abstract hash method. +func (c *Context) Hash() (string, error) { + data := c.String() + fmt.Sprintf(" %v-%v-%v-%v-%v", c.ip, c.prog, c.breakPoints, c.Estack, c.Astack) + return KeyGenerator([]byte(data)) +} diff --git a/pkg/vm/stack/map.go b/pkg/vm/stack/map.go new file mode 100644 index 000000000..b8e462c71 --- /dev/null +++ b/pkg/vm/stack/map.go @@ -0,0 +1,166 @@ +package stack + +import ( + "errors" + "fmt" + "sort" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" +) + +// Map represents a map of key, value pair on the stack. +// Both key and value are stack Items. +type Map struct { + *abstractItem + val map[Item]Item +} + +// NewMap returns a Map stack Item given +// a map whose keys and values are stack Items. +func NewMap(val map[Item]Item) (*Map, error) { + return &Map{ + abstractItem: &abstractItem{}, + val: val, + }, nil +} + +// Map will overwrite the default implementation +// to allow go to cast this item as an Map. +func (m *Map) Map() (*Map, error) { + return m, nil +} + +// Boolean overrides the default Boolean method +// to convert an Map into a Boolean StackItem +func (m *Map) Boolean() (*Boolean, error) { + return NewBoolean(true), nil +} + +// ContainsKey returns a boolean whose value is true +// iff the underlying map value contains the Item i +// as a key. +func (m *Map) ContainsKey(key Item) (*Boolean, error) { + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return nil, err + } else if ok.Value() == true { + return ok, nil + } + + } + return NewBoolean(false), nil +} + +// Value returns the underlying map's value +func (m *Map) Value() map[Item]Item { + return m.val +} + +// Remove removes the Item i from the +// underlying map's value. +func (m *Map) Remove(key Item) error { + var d Item + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return err + } else if ok.Value() == true { + d = k + } + + } + if d != nil { + delete(m.Value(), d) + } + return nil +} + +// Add inserts a new key, value pair of Items into +// the underlying map's value. +func (m *Map) Add(key Item, value Item) error { + for k := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return err + } else if ok.Value() == true { + return errors.New("try to insert duplicate key! ") + } + } + m.Value()[key] = value + return nil +} + +// ValueOfKey tries to get the value of the key Item +// from the map's underlying value. +func (m *Map) ValueOfKey(key Item) (Item, error) { + for k, v := range m.Value() { + if ok, err := CompareHash(k, key); err != nil { + return nil, err + } else if ok.Value() == true { + return v, nil + } + + } + return nil, nil + +} + +// Clear empties the the underlying map's value. +func (m *Map) Clear() { + m.val = map[Item]Item{} +} + +// CompareHash compare the the Hashes of two items. +// If they are equal it returns a true boolean. Otherwise +// it returns false boolean. Item whose hashes are equal are +// to be considered equal. +func CompareHash(i1 Item, i2 Item) (*Boolean, error) { + hash1, err := i1.Hash() + if err != nil { + return nil, err + } + hash2, err := i2.Hash() + if err != nil { + return nil, err + } + if hash1 == hash2 { + return NewBoolean(true), nil + } + + return NewBoolean(false), nil +} + +// Hash overrides the default abstract hash method. +func (m *Map) Hash() (string, error) { + var hashSlice sort.StringSlice = []string{} + var data = fmt.Sprintf("%T ", m) + + for k, v := range m.Value() { + hk, err := k.Hash() + if err != nil { + return "", err + } + hv, err := v.Hash() + + if err != nil { + return "", err + } + + hashSlice = append(hashSlice, hk) + hashSlice = append(hashSlice, hv) + } + hashSlice.Sort() + + for _, h := range hashSlice { + data += h + } + + return KeyGenerator([]byte(data)) +} + +// KeyGenerator hashes a byte slice to obtain a unique identifier. +func KeyGenerator(data []byte) (string, error) { + h, err := hash.Sha256([]byte(data)) + if err != nil { + return "", err + } + return h.String(), nil +} diff --git a/pkg/vm/stack/map_test.go b/pkg/vm/stack/map_test.go new file mode 100644 index 000000000..2c2091dc4 --- /dev/null +++ b/pkg/vm/stack/map_test.go @@ -0,0 +1,141 @@ +package stack + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + // define Map m for testing + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var c Item = NewByteArray([]byte{1, 2, 34}) + var d Item = testMakeStackMap(t, map[Item]Item{ + a: c, + b: a, + }) + var e = NewContext([]byte{1, 2, 3, 4}) + var f = NewArray([]Item{a, b}) + + val := map[Item]Item{ + a: c, + b: a, + c: b, + d: a, + e: d, + f: e, + } + m := testMakeStackMap(t, val) + + // test ValueOfKey + valueA, _ := m.ValueOfKey(testMakeStackInt(t, 10)) + assert.Equal(t, c, valueA) + + valueB, _ := m.ValueOfKey(b) + assert.Equal(t, a, valueB) + + valueC, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 34})) + assert.Equal(t, b, valueC) + + valueD, _ := m.ValueOfKey(testMakeStackMap(t, map[Item]Item{ + b: a, + a: c, + })) + assert.Equal(t, a, valueD) + + valueE, _ := m.ValueOfKey(NewContext([]byte{1, 2, 3, 4})) + assert.Equal(t, d, valueE) + + valueF, _ := m.ValueOfKey(NewArray([]Item{a, b})) + assert.Equal(t, e, valueF) + + valueX, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 35})) + assert.NotEqual(t, b, valueX) + + checkA, err := m.ContainsKey(a) + assert.Nil(t, err) + assert.Equal(t, true, checkA.Value()) + + //test ContainsKey + checkB, err := m.ContainsKey(b) + assert.Nil(t, err) + assert.Equal(t, true, checkB.Value()) + + checkC, err := m.ContainsKey(c) + assert.Nil(t, err) + assert.Equal(t, true, checkC.Value()) + + checkD, err := m.ContainsKey(d) + assert.Nil(t, err) + assert.Equal(t, true, checkD.Value()) + + checkE, err := m.ContainsKey(e) + assert.Nil(t, err) + assert.Equal(t, true, checkE.Value()) + + //test CompareHash + val2 := map[Item]Item{ + f: e, + e: d, + d: a, + c: b, + b: a, + a: c, + } + m2 := testMakeStackMap(t, val2) + checkMap, err := CompareHash(m, m2) + assert.Nil(t, err) + assert.Equal(t, true, checkMap.Value()) + + checkBoolean, err := CompareHash(b, NewBoolean(true)) + assert.Nil(t, err) + assert.Equal(t, true, checkBoolean.Value()) + + checkByteArray, err := CompareHash(c, NewByteArray([]byte{1, 2, 34})) + assert.Nil(t, err) + assert.Equal(t, true, checkByteArray.Value()) + + checkContext, err := CompareHash(e, NewContext([]byte{1, 2, 3, 4})) + assert.Nil(t, err) + assert.Equal(t, true, checkContext.Value()) + + checkArray, err := CompareHash(f, NewArray([]Item{a, b})) + assert.Nil(t, err) + assert.Equal(t, true, checkArray.Value()) +} + +func TestMapAdd(t *testing.T) { + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var m = testMakeStackMap(t, map[Item]Item{}) + + err := m.Add(a, a) + assert.Nil(t, err) + err = m.Add(b, a) + assert.Nil(t, err) + + assert.Equal(t, 2, len(m.Value())) + + expected := testMakeStackMap(t, map[Item]Item{b: a, a: a}) + check, err := CompareHash(m, expected) + assert.Nil(t, err) + assert.Equal(t, true, check.Value()) + +} + +func TestMapRemove(t *testing.T) { + var a Item = testMakeStackInt(t, 10) + var b Item = NewBoolean(true) + var m = testMakeStackMap(t, map[Item]Item{b: a, a: a}) + + err := m.Remove(a) + assert.Nil(t, err) + assert.Equal(t, 1, len(m.Value())) + + expected := testMakeStackMap(t, map[Item]Item{b: a}) + check, err := CompareHash(m, expected) + assert.Nil(t, err) + assert.Equal(t, true, check.Value()) + +} diff --git a/pkg/vm/stack/stackitem.go b/pkg/vm/stack/stackitem.go index beed13363..d9990adcb 100644 --- a/pkg/vm/stack/stackitem.go +++ b/pkg/vm/stack/stackitem.go @@ -11,6 +11,8 @@ type Item interface { ByteArray() (*ByteArray, error) Array() (*Array, error) Context() (*Context, error) + Map() (*Map, error) + Hash() (string, error) } // Represents an `abstract` stack item @@ -47,3 +49,13 @@ func (a *abstractItem) Array() (*Array, error) { func (a *abstractItem) Context() (*Context, error) { return nil, errors.New("This stack item is not of type context") } + +// Context is the default implementation for a stackItem +// Implements Item interface +func (a *abstractItem) Map() (*Map, error) { + return nil, errors.New("This stack item is not a map") +} + +func (a *abstractItem) Hash() (string, error) { + return "", errors.New("This stack item need to override the Hash Method") +} diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 15c6f87de..b2615141e 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -42,3 +42,15 @@ func testReadInt64(data []byte) int64 { binary.Read(buf, binary.LittleEndian, &ret) return ret } + +func testMakeStackMap(t *testing.T, m map[Item]Item) *Map { + a, err := NewMap(m) + assert.Nil(t, err) + return a +} + +func testArray(t *testing.T, m map[Item]Item) *Map { + a, err := NewMap(m) + assert.Nil(t, err) + return a +} From 045db09af24ddee70287105685f99d877dd45cf7 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Wed, 3 Apr 2019 00:43:52 +0200 Subject: [PATCH 087/117] Implemented LTE, GTE opcode (#260) * Implemented LTE, GTE opcode --- pkg/vm/stack/Int.go | 14 ++++++ pkg/vm/vm_ops.go | 2 + pkg/vm/vm_ops_maths.go | 52 +++++++++++++++++----- pkg/vm/vm_ops_maths_test.go | 89 ++++++++++++++++++++++++++++++------- 4 files changed, 131 insertions(+), 26 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 3226904ee..a10c099c7 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -100,6 +100,20 @@ func (i *Int) Value() *big.Int { return i.val } +// Lte returns a bool value from the comparison of two integers, a and b. +// value is true if a <= b. +// value is false if a > b. +func (i *Int) Lte(s *Int) bool { + return i.Value().Cmp(s.Value()) != 1 +} + +// Gte returns a bool value from the comparison of two integers, a and b. +// value is true if a >= b. +// value is false if a < b. +func (i *Int) Gte(s *Int) bool { + return i.Value().Cmp(s.Value()) != -1 +} + // Abs returns a stack integer whose underlying value is // the absolute value of the original stack integer. func (i *Int) Abs() (*Int, error) { diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index d50609462..0a9c6f173 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -10,7 +10,9 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, stack.LT: Lt, + stack.LTE: Lte, stack.GT: Gt, + stack.GTE: Gte, stack.SHR: Shr, stack.SHL: Shl, stack.INC: Inc, diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index e23a7a4a0..9bab3e48e 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -317,17 +317,36 @@ func Negate(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return NONE, nil } -func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { - operandA, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } - operandB, err := ctx.Estack.PopInt() - if err != nil { - return nil, nil, err - } +// Lte pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is less than or equal to b's value. +// Returns an error if either items cannot be casted to an integer +func Lte(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - return operandA, operandB, nil + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Lte(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil +} + +// Gte pops two integers, a and b, off of the stack and pushes a boolean the stack +// whose value is true if a's value is greated than or equal to b's value. +// Returns an error if either items cannot be casted to an integer +func Gte(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res := operandB.Gte(operandA) + + ctx.Estack.Push(stack.NewBoolean(res)) + + return NONE, nil } // Shl pops two integers, a and b, off of the stack and pushes an integer to the stack @@ -402,6 +421,19 @@ func Gt(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rsta return NONE, nil } +func popTwoIntegers(ctx *stack.Context) (*stack.Int, *stack.Int, error) { + operandA, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + operandB, err := ctx.Estack.PopInt() + if err != nil { + return nil, nil, err + } + + return operandA, operandB, nil +} + func popTwoByteArrays(ctx *stack.Context) (*stack.ByteArray, *stack.ByteArray, error) { // Pop first stack item and cast as byte array ba1, err := ctx.Estack.PopByteArray() diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index b8478f29b..14f83b1ec 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -385,6 +385,64 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, int64(20), item.Value().Int64()) } +func TestLteOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a <= b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.LTE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} + +func TestGteOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // b is the first item pop. + // a is the second item pop. + // we perform a >= b and place + // the result on top of the evaluation + // stack + v.executeOp(stack.GTE, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} + func TestShlOp(t *testing.T) { v := VM{} @@ -455,48 +513,48 @@ func TestShrOp(t *testing.T) { func TestBoolAndOp(t *testing.T) { - v := VM{} + v := VM{} - a := stack.NewBoolean(true) + a := stack.NewBoolean(true) b := stack.NewBoolean(true) - ctx := stack.NewContext([]byte{}) + ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLAND, ctx) + v.executeOp(stack.BOOLAND, ctx) - // Stack should have one item + // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) - item, err := ctx.Estack.PopBoolean() + item, err := ctx.Estack.PopBoolean() if err != nil { t.Fail() } - assert.Equal(t, true, item.Value()) + assert.Equal(t, true, item.Value()) } - func TestBoolOrOp(t *testing.T) { +func TestBoolOrOp(t *testing.T) { - v := VM{} + v := VM{} - a := stack.NewBoolean(false) + a := stack.NewBoolean(false) b := stack.NewBoolean(true) - ctx := stack.NewContext([]byte{}) + ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLOR, ctx) + v.executeOp(stack.BOOLOR, ctx) - // Stack should have one item + // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) - item, err := ctx.Estack.PopBoolean() + item, err := ctx.Estack.PopBoolean() if err != nil { t.Fail() } - assert.Equal(t, true, item.Value()) + assert.Equal(t, true, item.Value()) } func TestLtOp(t *testing.T) { @@ -556,4 +614,3 @@ func TestGtOp(t *testing.T) { assert.Equal(t, true, item.Value()) } - From de1c4e01a18a4b7d209669979d03892d95049cf6 Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 4 Apr 2019 00:34:21 +0200 Subject: [PATCH 088/117] Implemented bitwise opcodes: 1) AND 2) XOR 3) OR 4) INVERT --- pkg/vm/stack/Int.go | 31 ++++++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_bitwise.go | 85 +++++++++++++++++++++ pkg/vm/vm_ops_bitwise_test.go | 137 ++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 pkg/vm/vm_ops_bitwise_test.go diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index a10c099c7..9bf2b82ba 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -139,3 +139,34 @@ func (i *Int) Lt(s *Int) bool { func (i *Int) Gt(s *Int) bool { return i.Value().Cmp(s.Value()) == 1 } + +// Invert returns an Integer whose underlying value is the bitwise complement +// of the original value. +func (i *Int) Invert() (*Int, error) { + res := new(big.Int).Not(i.Value()) + return NewInt(res) +} + +// And returns an Integer whose underlying value is the result of the +// application of the bitwise AND operator to the two original integers' +// values. +func (i *Int) And(s *Int) (*Int, error) { + res := new(big.Int).And(i.Value(), s.Value()) + return NewInt(res) +} + +// Or returns an Integer whose underlying value is the result of the +// application of the bitwise OR operator to the two original integers' +// values. +func (i *Int) Or(s *Int) (*Int, error) { + res := new(big.Int).Or(i.Value(), s.Value()) + return NewInt(res) +} + +// Xor returns an Integer whose underlying value is the result of the +// application of the bitwise XOR operator to the two original integers' +// values. +func (i *Int) Xor(s *Int) (*Int, error) { + res := new(big.Int).Xor(i.Value(), s.Value()) + return NewInt(res) +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..5680f463d 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.XOR: Xor, + stack.OR: Or, + stack.AND: And, + stack.INVERT: Invert, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_bitwise.go b/pkg/vm/vm_ops_bitwise.go index 350543fa2..4e65cafd4 100644 --- a/pkg/vm/vm_ops_bitwise.go +++ b/pkg/vm/vm_ops_bitwise.go @@ -15,3 +15,88 @@ func EQUAL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r ctx.Estack.Push(itemA.Equals(itemB)) return NONE, nil } + +// Invert pops an integer x off of the stack and +// pushes an integer on the stack whose value +// is the bitwise complement of the value of x. +// Returns an error if the popped value is not an integer or +// if the bitwise complement cannot be taken. +func Invert(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + inv, err := i.Invert() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(inv) + + return NONE, nil +} + +// And pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise AND +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func And(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.And(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Or pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise OR +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func Or(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Or(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} + +// Xor pops two integer off of the stack and +// pushes an integer onto the stack whose value +// is the result of the application of the bitwise XOR +// operator to the two original integers' values. +// Returns an error if either items cannot be casted to an integer. +func Xor(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + operandA, operandB, err := popTwoIntegers(ctx) + if err != nil { + return FAULT, err + } + res, err := operandA.Xor(operandB) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(res) + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_bitwise_test.go b/pkg/vm/vm_ops_bitwise_test.go new file mode 100644 index 000000000..6e92ada2f --- /dev/null +++ b/pkg/vm/vm_ops_bitwise_test.go @@ -0,0 +1,137 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestInvertOp(t *testing.T) { + + v := VM{} + + // 0000 00110 = 5 + a, err := stack.NewInt(big.NewInt(5)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + // 1111 11001 = -6 (two complement representation) + v.executeOp(stack.INVERT, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(-6), item.Value().Int64()) +} + +func TestAndOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 100001 = 33 + v.executeOp(stack.AND, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(33), item.Value().Int64()) +} + +func TestOrOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 110011 = 51 (49 OR 35) + v.executeOp(stack.OR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(51), item.Value().Int64()) +} + +func TestXorOp(t *testing.T) { + + v := VM{} + + // 110001 = 49 + a, err := stack.NewInt(big.NewInt(49)) + assert.Nil(t, err) + + // 100011 = 35 + b, err := stack.NewInt(big.NewInt(35)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // 010010 = 18 (49 XOR 35) + v.executeOp(stack.XOR, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(18), item.Value().Int64()) +} + +func TestEqualOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + v.executeOp(stack.EQUAL, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopBoolean() + assert.Nil(t, err) + + assert.Equal(t, true, item.Value()) +} From 2897c3cbc1b3a110ce15434403b5d5abd96fddf9 Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 4 Apr 2019 22:46:06 +0200 Subject: [PATCH 089/117] Implemented crypto opcodes: 1) SHA1 2) SHA256 3) HASH160 4) HASH256 --- pkg/vm/stack/bytearray.go | 5 ++ pkg/vm/vm_ops.go | 4 ++ pkg/vm/vmopscrypto.go | 126 +++++++++++++++++++++++++++++++++++++ pkg/vm/vmopscrypto_test.go | 101 +++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 pkg/vm/vmopscrypto.go create mode 100644 pkg/vm/vmopscrypto_test.go diff --git a/pkg/vm/stack/bytearray.go b/pkg/vm/stack/bytearray.go index 7d1c3c818..cd7a23398 100644 --- a/pkg/vm/stack/bytearray.go +++ b/pkg/vm/stack/bytearray.go @@ -69,3 +69,8 @@ func reverse(b []byte) []byte { return dest } + +// Value returns the underlying ByteArray's value. +func (ba ByteArray) Value() []byte { + return ba.val +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..d87f830b5 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.HASH256: HASH256, + stack.HASH160: HASH160, + stack.SHA256: SHA256, + stack.SHA1: SHA1, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go new file mode 100644 index 000000000..9094a6853 --- /dev/null +++ b/pkg/vm/vmopscrypto.go @@ -0,0 +1,126 @@ +package vm + +import ( + "crypto/sha1" + + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/vm/stack" +) + +// SHA1 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the sha1 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + alg := sha1.New() + alg.Write(ba.Value()) + hash := alg.Sum(nil) + res := stack.NewByteArray(hash) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// SHA256 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Sha256 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.Sha256(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// HASH160 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Hash160 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.Hash160(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} + +// HASH256 pops an item off of the stack and +// pushes a bytearray onto the stack whose value +// is obtained by applying the Hash256 algorithm to +// the corresponding bytearray representation of the item. +// Returns an error if the Pop method cannot be execute or +// the popped item does not have a concrete bytearray implementation. +func HASH256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + i, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ba, err := i.ByteArray() + if err != nil { + return FAULT, err + } + + hash, err := hash.DoubleSha256(ba.Value()) + if err != nil { + return FAULT, err + } + + res := stack.NewByteArray(hash.Bytes()) + + ctx.Estack.Push(res) + + return NONE, nil +} diff --git a/pkg/vm/vmopscrypto_test.go b/pkg/vm/vmopscrypto_test.go new file mode 100644 index 000000000..08e26a786 --- /dev/null +++ b/pkg/vm/vmopscrypto_test.go @@ -0,0 +1,101 @@ +package vm + +import ( + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestSha1Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.SHA1, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "62d40fe74cf301cbfbe55c2679b96352449fb26d", hex.EncodeToString(ba2.Value())) +} + +func TestSha256Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.SHA256, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "8e76c5b9e6be2559bedccbd0ff104ebe02358ba463a44a68e96caf55f9400de5", hex.EncodeToString(ba2.Value())) +} + +func TestHash160Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.HASH160, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "e9c052b05a762ca9961a975db52e5417d99d958c", hex.EncodeToString(ba2.Value())) +} + +func TestHash256Op(t *testing.T) { + + v := VM{} + + ba1 := stack.NewByteArray([]byte("this is test string")) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(ba1) + + v.executeOp(stack.HASH256, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.Pop() + assert.Nil(t, err) + + ba2, err := item.ByteArray() + assert.Nil(t, err) + + assert.Equal(t, "90ef790ee2557a3f9a1ba0e6910a9ff0ea75af3767ea7380760d729ac9927a60", hex.EncodeToString(ba2.Value())) +} From 3c8448ed40fd77c913ab1b1f4bf7cf80ad82ed39 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 5 Apr 2019 20:34:02 +0200 Subject: [PATCH 090/117] Simplied code by using help method PopByteArray --- pkg/vm/vmopscrypto.go | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go index 9094a6853..e38ab7402 100644 --- a/pkg/vm/vmopscrypto.go +++ b/pkg/vm/vmopscrypto.go @@ -15,12 +15,7 @@ import ( // the popped item does not have a concrete bytearray implementation. func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -43,12 +38,7 @@ func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rs // the popped item does not have a concrete bytearray implementation. func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -73,12 +63,7 @@ func SHA256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, // the popped item does not have a concrete bytearray implementation. func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } @@ -103,12 +88,7 @@ func HASH160(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, // the popped item does not have a concrete bytearray implementation. func HASH256(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { - i, err := ctx.Estack.Pop() - if err != nil { - return FAULT, err - } - - ba, err := i.ByteArray() + ba, err := ctx.Estack.PopByteArray() if err != nil { return FAULT, err } From 51f835172345ce15fab7410ef288c1a415ea21c1 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 5 Apr 2019 21:13:23 +0200 Subject: [PATCH 091/117] Used consistently assert.Nil for checking absence of error --- pkg/vm/vm_ops_maths_test.go | 209 ++++++++++++------------------------ 1 file changed, 67 insertions(+), 142 deletions(-) diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index d685d6cf0..14e8c88a3 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -13,9 +13,7 @@ func TestIncOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -26,9 +24,7 @@ func TestIncOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(21), item.Value().Int64()) } @@ -38,9 +34,7 @@ func TestDecOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -51,9 +45,7 @@ func TestDecOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(19), item.Value().Int64()) } @@ -63,13 +55,10 @@ func TestAddOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(23)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -80,9 +69,7 @@ func TestAddOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(43), item.Value().Int64()) @@ -93,13 +80,10 @@ func TestSubOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(30)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(40)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -110,9 +94,7 @@ func TestSubOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(-10), item.Value().Int64()) @@ -123,13 +105,10 @@ func TestDivOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(4)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -140,9 +119,7 @@ func TestDivOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -152,13 +129,10 @@ func TestModOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(15)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(4)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -169,9 +143,7 @@ func TestModOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(3), item.Value().Int64()) } @@ -181,9 +153,7 @@ func TestNzOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -194,9 +164,7 @@ func TestNzOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -206,13 +174,10 @@ func TestMulOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -223,9 +188,7 @@ func TestMulOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(400), item.Value().Int64()) } @@ -235,9 +198,7 @@ func TestAbsOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -248,9 +209,7 @@ func TestAbsOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(20), item.Value().Int64()) } @@ -270,9 +229,7 @@ func TestNotOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -282,13 +239,10 @@ func TestNumEqual(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -299,9 +253,7 @@ func TestNumEqual(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -311,13 +263,10 @@ func TestNumNotEqual(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(5)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(6)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -328,9 +277,7 @@ func TestNumNotEqual(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -340,9 +287,7 @@ func TestSignOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -353,9 +298,7 @@ func TestSignOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(-1), item.Value().Int64()) } @@ -365,9 +308,7 @@ func TestNegateOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(-20)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) @@ -378,9 +319,7 @@ func TestNegateOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(20), item.Value().Int64()) } @@ -448,13 +387,10 @@ func TestShlOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(2)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(3)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -470,9 +406,7 @@ func TestShlOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(16), item.Value().Int64()) } @@ -482,13 +416,10 @@ func TestShrOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) + b, err := stack.NewInt(big.NewInt(2)) - if err != nil { - t.Fail() - } + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -504,9 +435,7 @@ func TestShrOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -527,9 +456,7 @@ func TestBoolAndOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -550,9 +477,7 @@ func TestBoolOrOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - if err != nil { - t.Fail() - } + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -562,10 +487,10 @@ func TestLtOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -581,7 +506,7 @@ func TestLtOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, false, item.Value()) } @@ -591,10 +516,10 @@ func TestGtOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -610,7 +535,7 @@ func TestGtOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } @@ -620,10 +545,10 @@ func TestMinOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -634,7 +559,7 @@ func TestMinOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, int64(2), item.Value().Int64()) } @@ -644,10 +569,10 @@ func TestMaxOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) @@ -658,7 +583,7 @@ func TestMaxOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopInt() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, int64(10), item.Value().Int64()) } @@ -668,13 +593,13 @@ func TestWithinOp(t *testing.T) { v := VM{} a, err := stack.NewInt(big.NewInt(5)) - assert.NoError(t, err) + assert.Nil(t, err) b, err := stack.NewInt(big.NewInt(2)) - assert.NoError(t, err) + assert.Nil(t, err) c, err := stack.NewInt(big.NewInt(10)) - assert.NoError(t, err) + assert.Nil(t, err) ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b).Push(c) @@ -692,7 +617,7 @@ func TestWithinOp(t *testing.T) { assert.Equal(t, 1, ctx.Estack.Len()) item, err := ctx.Estack.PopBoolean() - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, true, item.Value()) } From 4dc11ee48fd5debd9c9dfd7a611e1d0a9eb83c9f Mon Sep 17 00:00:00 2001 From: DauTT Date: Tue, 9 Apr 2019 01:07:15 +0200 Subject: [PATCH 092/117] Implemented following control flow opcodes: 1) NOP 2) JMP 3) JMPIF 4) JMPIFNOT --- pkg/vm/stack/context.go | 15 ++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_flow.go | 68 +++++++++++++++ pkg/vm/vm_ops_flow_test.go | 168 +++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 pkg/vm/vm_ops_flow_test.go diff --git a/pkg/vm/stack/context.go b/pkg/vm/stack/context.go index d381d74cb..096481150 100644 --- a/pkg/vm/stack/context.go +++ b/pkg/vm/stack/context.go @@ -120,6 +120,11 @@ func (c *Context) ReadUint16() uint16 { return val } +// ReadInt16 reads a int16 from the script +func (c *Context) ReadInt16() int16 { + return int16(c.ReadUint16()) +} + // ReadByte reads one byte from the script func (c *Context) ReadByte() (byte, error) { byt, err := c.ReadBytes(1) @@ -150,3 +155,13 @@ func (c *Context) readVarBytes() ([]byte, error) { } return c.ReadBytes(int(n)) } + +// SetIP sets the instruction pointer ip to a given integer. +// Returns an error if ip is less than -1 or greater than LenInstr. +func (c *Context) SetIP(ip int) error { + if ok := ip < -1 || ip > c.LenInstr(); ok { + return errors.New("invalid instruction pointer") + } + c.ip = ip + return nil +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..e466c5fae 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.JMPIFNOT: JMPIFNOT, + stack.JMPIF: JMPIF, + stack.JMP: JMP, + stack.NOP: NOP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 67ca4f825..71f32d42d 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -25,3 +25,71 @@ func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rst return NONE, nil } + +// NOP Returns NONE VMState. +func NOP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + return NONE, nil +} + +// JMP moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range. +func JMP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + + } + + return NONE, nil +} + +// JMPIF pops a boolean off of the stack and, +// if the the boolean's value is true, it +// moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range or +// the popped item is not a boolean. +func JMPIF(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + if b.Value() { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + } + + } + + return NONE, nil +} + +// JMPIFNOT pops a boolean off of the stack and, +// if the the boolean's value is false, it +// moves the instruction pointer to an offset which is +// calculated base on the instructionPointerOffset method. +// Returns and error if the offset is out of range or +// the popped item is not a boolean. +func JMPIFNOT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + b, err := ctx.Estack.PopBoolean() + if err != nil { + return FAULT, err + } + + if !b.Value() { + offset := instructionPointerOffset(ctx) + if err := ctx.SetIP(offset); err != nil { + return FAULT, err + } + + } + + return NONE, nil +} + +func instructionPointerOffset(ctx *stack.Context) int { + return ctx.IP() + int(ctx.ReadInt16()) - 3 +} diff --git a/pkg/vm/vm_ops_flow_test.go b/pkg/vm/vm_ops_flow_test.go new file mode 100644 index 000000000..2801e51a4 --- /dev/null +++ b/pkg/vm/vm_ops_flow_test.go @@ -0,0 +1,168 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestNopOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.NOP, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestJmpOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMP, ctx) + + // Stack should have one item + assert.Equal(t, 1, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} + +// test JMPIF instruction with true boolean +// on top of the stack +func TestJmpIfOp1(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset + // because the there is a true boolean + // on top of the stack. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMPIF, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} + +// test JMPIF instruction with false boolean +// on top of the stack +func TestJmpIfOp2(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // nothing will happen because + // the value of the boolean on top of the stack + // is false + v.executeOp(stack.JMPIF, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) +} + +// test JMPIFNOT instruction with true boolean +// on top of the stack +func TestJmpIfNotOp1(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(true) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // nothing will happen because + // the value of the boolean on top of the stack + // is true + v.executeOp(stack.JMPIFNOT, ctx) + + // Stack should have 0 item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) +} + +// test JMPIFNOT instruction with false boolean +// on top of the stack +func TestJmpIfNotOp2(t *testing.T) { + + v := VM{} + + a := stack.NewBoolean(false) + + ctx := stack.NewContext([]byte{5, 0, 2, 3, 4}) + ctx.Estack.Push(a) + + // ctx.ip = -1 + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 0, ctx.IP()) + + // ctx.ip will be set to offset + // because the there is a false boolean + // on top of the stack. + // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 + // = 0 + 5 -3 = 2 + v.executeOp(stack.JMPIFNOT, ctx) + + // Stack should have one item + assert.Equal(t, 0, ctx.Estack.Len()) + + // ctx.IP() = ctx.ip + 1 + assert.Equal(t, 3, ctx.IP()) +} From 7e20b604b4f61dc0c7d6017290d0567df6d5f82b Mon Sep 17 00:00:00 2001 From: DauTT Date: Thu, 11 Apr 2019 00:07:17 +0200 Subject: [PATCH 093/117] Implemented following opcodes: 1) DUPFROMALTSTACK 2) TOALTSTACK 3) FROMALTSTACK 4) XDROP --- pkg/vm/stack/stack.go | 16 ++++ pkg/vm/vm_ops.go | 60 ++++++++------- pkg/vm/vm_ops_stackmani.go | 63 +++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 131 ++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 28 deletions(-) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..996027138 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -158,3 +158,19 @@ func (ras *RandomAccess) PopBoolean() (*Boolean, error) { } return item.Boolean() } + +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..57accf126 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,34 +5,38 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ - stack.NUMEQUAL: NumEqual, - stack.NUMNOTEQUAL: NumNotEqual, - stack.BOOLAND: BoolAnd, - stack.BOOLOR: BoolOr, - stack.LT: Lt, - stack.LTE: Lte, - stack.GT: Gt, - stack.GTE: Gte, - stack.SHR: Shr, - stack.SHL: Shl, - stack.INC: Inc, - stack.DEC: Dec, - stack.DIV: Div, - stack.MOD: Mod, - stack.NZ: Nz, - stack.MUL: Mul, - stack.ABS: Abs, - stack.NOT: Not, - stack.SIGN: Sign, - stack.NEGATE: Negate, - stack.ADD: Add, - stack.SUB: Sub, - stack.PUSHBYTES1: PushNBytes, - stack.PUSHBYTES75: PushNBytes, - stack.RET: RET, - stack.EQUAL: EQUAL, - stack.THROWIFNOT: THROWIFNOT, - stack.THROW: THROW, + stack.XDROP: XDROP, + stack.FROMALTSTACK: FROMALTSTACK, + stack.TOALTSTACK: TOALTSTACK, + stack.DUPFROMALTSTACK: DUPFROMALTSTACK, + stack.NUMEQUAL: NumEqual, + stack.NUMNOTEQUAL: NumNotEqual, + stack.BOOLAND: BoolAnd, + stack.BOOLOR: BoolOr, + stack.LT: Lt, + stack.LTE: Lte, + stack.GT: Gt, + stack.GTE: Gte, + stack.SHR: Shr, + stack.SHL: Shl, + stack.INC: Inc, + stack.DEC: Dec, + stack.DIV: Div, + stack.MOD: Mod, + stack.NZ: Nz, + stack.MUL: Mul, + stack.ABS: Abs, + stack.NOT: Not, + stack.SIGN: Sign, + stack.NEGATE: Negate, + stack.ADD: Add, + stack.SUB: Sub, + stack.PUSHBYTES1: PushNBytes, + stack.PUSHBYTES75: PushNBytes, + stack.RET: RET, + stack.EQUAL: EQUAL, + stack.THROWIFNOT: THROWIFNOT, + stack.THROW: THROW, } func init() { diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..ce3af78d6 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,66 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// DUPFROMALTSTACK duplicates the item on top of alternative stack and +// puts it on top of evaluation stack. +// Returns an error if the alt stack is empty. +func DUPFROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Peek(0) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// TOALTSTACK pops an item off of the evaluation stack and +// pushes it on top of the alternative stack. +// Returns an error if the alternative stack is empty. +func TOALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Astack.Push(item) + + return NONE, nil +} + +// FROMALTSTACK pops an item off of the alternative stack and +// pushes it on top of the evaluation stack. +// Returns an error if the evaluation stack is empty. +func FROMALTSTACK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Astack.Pop() + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// XDROP pops an integer n off of the stack and +// removes the n-item from the stack starting from +// the top of the stack. +func XDROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + ctx.Estack.Remove(uint16(n.Value().Uint64())) + if err != nil { + return FAULT, err + } + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..6e257ad99 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,131 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.DUPFROMALTSTACK, ctx) + + assert.Equal(t, 1, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + itemE, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), itemE.Value().Int64()) + assert.Equal(t, int64(2), itemA.Value().Int64()) +} + +func TestToAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.TOALTSTACK, ctx) + + assert.Equal(t, 2, ctx.Astack.Len()) + assert.Equal(t, 0, ctx.Estack.Len()) + + item, err := ctx.Astack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(10), item.Value().Int64()) +} + +func TestFromAltStackOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(10)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Astack.Push(b) + + v.executeOp(stack.FROMALTSTACK, ctx) + + assert.Equal(t, 0, ctx.Astack.Len()) + assert.Equal(t, 2, ctx.Estack.Len()) + + item, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item.Value().Int64()) +} + +func TestXDropOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + ctx.Estack.Push(b) + ctx.Estack.Push(c) + ctx.Estack.Push(d) + + // pop n (= d = 2) from the stack. + // we will remove the n-item which + // is located at position + // len(stack)-n-1 = 3-2-1 = 0. + // Therefore a is removed from the stack. + // Only b, c remain on the stack. + v.executeOp(stack.XDROP, ctx) + + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} From aa67e98726c03ef62cf04b7eca638da9599f6ca6 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 00:38:57 +0200 Subject: [PATCH 094/117] Implemented following opcodes: 1) XSWAP 2) XTUCK 3) DEPTH 4) DROP --- pkg/vm/stack/stack.go | 15 +++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 83 ++++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 162 ++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..d42c7b123 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,21 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Set sets the n-item from the stack +// starting from the top of the stack with the new item. +// the n-item to replace is located at the position "len(stack)-index-1". +func (ras *RandomAccess) Set(index uint16, item Item) error { + stackSize := uint16(len(ras.vals)) + if ok := index >= stackSize; ok { + return errors.New("index out of range") + } + + n := stackSize - index - 1 + ras.vals[n] = item + + return nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..e6d61edc9 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.DROP: DROP, + stack.DEPTH: DEPTH, + stack.XTUCK: XTUCK, + stack.XSWAP: XSWAP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..81ec1d91c 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/CityOfZion/neo-go/pkg/vm/stack" ) @@ -17,3 +19,84 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// XSWAP pops an integer n off of the stack and +// swaps the n-item from the stack starting from +// the top of the stack with the top stack item. +func XSWAP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + nItem, err := ctx.Estack.Peek(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(uint16(n.Value().Int64()), item); err != nil { + return FAULT, err + } + + if err := ctx.Estack.Set(0, nItem); err != nil { + return FAULT, err + } + + return NONE, nil +} + +// XTUCK pops an integer n off of the stack and +// inserts the top stack item to the position len(stack)-n in the evaluation stack. +func XTUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil || n.Value().Int64() < 0 { + return FAULT, err + } + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(uint16(n.Value().Int64()), item) + if err != nil { + return FAULT, err + } + + ctx.Estack = *ras + + return NONE, nil +} + +// DEPTH puts the number of stack items onto the stack. +func DEPTH(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + l := ctx.Estack.Len() + length, err := stack.NewInt(big.NewInt(int64(l))) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(length) + + return NONE, nil +} + +// DROP removes the the top stack item. +// Returns error if the operation Pop cannot +// be performed. +func DROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Pop() + if err != nil { + return FAULT, err + } + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..eb771df36 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,162 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestXswapOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we will swap the n-item which + // is located in position len(stack)-n-1 (= 3-2-1= 0) + // with the top stack item. + // The final stack will be [c,b,a] + v.executeOp(stack.XSWAP, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestXTuckOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack + // and insert the top stack item c + // to the position len(stack)-n (= 3-2 = 1) + // of the stack.The final stack will be [a,c,b,c] + v.executeOp(stack.XTUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + // c + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // c + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item3, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(9), item2.Value().Int64()) + assert.Equal(t, int64(3), item3.Value().Int64()) + +} + +func TestXDepthOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // push integer whose value is len(stack) (2) + // on top of the stack + v.executeOp(stack.DEPTH, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + // len(stack) + item0, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // b + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + // a + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(2), item0.Value().Int64()) + assert.Equal(t, int64(6), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) +} + +func TestXDropOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // Remove the top stack item from the stack. + // The remaining stack is [a] + v.executeOp(stack.DROP, ctx) + + // Stack should have 2 items + assert.Equal(t, 1, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) +} From d43dcf03cb4257b00a0e189145bbcbcdc40fac83 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 20:25:20 +0200 Subject: [PATCH 095/117] Implemented following opcodes: 1) DUP 2) NIP 3) OVER 4) PICK --- pkg/vm/stack/stack.go | 31 +++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 64 +++++++++++ pkg/vm/vm_ops_stackmani_test.go | 197 ++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..91a888a7d 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,37 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Set sets the n-item from the stack +// starting from the top of the stack with the new item. +// the n-item to replace is located at the position "len(stack)-index-1". +func (ras *RandomAccess) Set(index uint16, item Item) error { + stackSize := uint16(len(ras.vals)) + if ok := index >= stackSize; ok { + return errors.New("index out of range") + } + + n := stackSize - index - 1 + ras.vals[n] = item + + return nil +} + +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..f02d09be8 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.PICK: PICK, + stack.OVER: OVER, + stack.NIP: NIP, + stack.DUP: DUP, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..5eecad252 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,67 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// DUP duplicates the top stack item. +// Returns an error if stack is empty. +func DUP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// NIP removes the second top stack item. +// Returns error if the stack item contains +// only one element. +func NIP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + _, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + return NONE, nil +} + +// OVER copies the second-to-top stack item onto the top. +// Returns an error if the stack item contains +// only one element. +func OVER(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// PICK pops an integer n off of the stack and +// copies the n-item starting from +// the top of the stack onto the top stack item. +// Returns an error if the top stack item is not an +// integer or n-item does not exist. +func PICK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + nItem, err := ctx.Estack.Peek(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(nItem) + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..45e84ddb7 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,197 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestDupOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a) + + v.executeOp(stack.DUP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + item1, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + item2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), item1.Value().Int64()) + assert.Equal(t, int64(3), item2.Value().Int64()) + +} + +func TestNipOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + v.executeOp(stack.NIP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} + +func TestOverOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // OVER copies the second top stack item a + // onto the top stack item b. + // the new stack will be [a,b,a]. + v.executeOp(stack.OVER, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(3), itemA2.Value().Int64()) + +} + +func TestPickOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we will copy the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [a,b,c,a] + v.executeOp(stack.PICK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(3), itemA2.Value().Int64()) + +} + +/* +func TestXswapOp(t *testing.T) { + + v := VM{} + + sert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack. + // we wa, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + asill swap the n-item which + // is located in position len(stack)-n-1 (= 3-2-1= 0) + // with the top stack item. + // The final stack will be [c,b,a] + v.executeOp(stack.XSWAP, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + +} +*/ From d36d8b456c1b256faa34534dad4d0de9c15caaec Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 22:27:52 +0200 Subject: [PATCH 096/117] Implemented following opcodes: 1) ROLL 2) ROT 3) SWAP 4) TUCK --- pkg/vm/stack/stack.go | 16 ++++ pkg/vm/vm_ops.go | 4 + pkg/vm/vm_ops_stackmani.go | 74 ++++++++++++++ pkg/vm/vm_ops_stackmani_test.go | 164 ++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 pkg/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index c832a9f79..5e837258c 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -127,6 +127,22 @@ func (ras *RandomAccess) CopyTo(stack *RandomAccess) error { return nil } +// Remove removes the n-item from the stack +// starting from the top of the stack. In other words +// the n-item to remove is located at the index "len(stack)-n-1" +func (ras *RandomAccess) Remove(n uint16) (Item, error) { + if int(n) >= len(ras.vals) { + return nil, errors.New("index out of range") + } + + index := uint16(len(ras.vals)) - n - 1 + item := ras.vals[index] + + ras.vals = append(ras.vals[:index], ras.vals[index+1:]...) + + return item, nil +} + // Convenience Functions // PopInt will remove the last stack item that was added diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0a9c6f173..83aafda7d 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -5,6 +5,10 @@ import "github.com/CityOfZion/neo-go/pkg/vm/stack" type stackInfo func(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) var opFunc = map[stack.Instruction]stackInfo{ + stack.TUCK: TUCK, + stack.SWAP: SWAP, + stack.ROT: ROT, + stack.ROLL: ROLL, stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index f5e2ddc24..eb8fcc8d4 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -17,3 +17,77 @@ func PushNBytes(op stack.Instruction, ctx *stack.Context, istack *stack.Invocati ctx.Estack.Push(ba) return NONE, nil } + +// ROLL pops an integer n off of the stack and +// moves the n-item starting from +// the top of the stack onto the top stack item. +// Returns an error if the top stack item is not an +// integer or n-item does not exist. +func ROLL(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + n, err := ctx.Estack.PopInt() + if err != nil { + return FAULT, err + } + + nItem, err := ctx.Estack.Remove(uint16(n.Value().Int64())) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(nItem) + + return NONE, nil +} + +// ROT moves the third top stack item +// onto the top stack item. +// Returns an error if the third top stack item +// does not exist. +func ROT(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(2) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// SWAP swaps the second top stack item with +// the top stack item. +// Returns an error if the second top stack item +// does not exist. +func SWAP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Remove(1) + if err != nil { + return FAULT, err + } + + ctx.Estack.Push(item) + + return NONE, nil +} + +// TUCK copies the top stack item and +// inserts it before the second top stack item. +// Returns an error if the stack is empty or +// len(stack) is less or equal 2. +func TUCK(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + + item, err := ctx.Estack.Peek(0) + if err != nil { + return FAULT, err + } + + ras, err := ctx.Estack.Insert(2, item) + if err != nil { + return FAULT, err + } + ctx.Estack = *ras + + return NONE, nil +} diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go new file mode 100644 index 000000000..ab80b36d2 --- /dev/null +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -0,0 +1,164 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/stack" + "github.com/stretchr/testify/assert" +) + +func TestRollOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + d, err := stack.NewInt(big.NewInt(2)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c).Push(d) + + // pop n (= d = 2) from the stack + // and move the n-item which + // has index len(stack)-n-1 (= 3-2-1= 0) + // onto the top stack item. + // The final stack will be [b,c,a] + v.executeOp(stack.ROLL, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestRotOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // move the third top stack a item onto + // the top stack item c. + // The final stack will be [b,c,a] + v.executeOp(stack.ROT, ctx) + + // Stack should have three items + assert.Equal(t, 3, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) +} + +func TestSwapOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b) + + // Swaps the top two stack items. + // The final stack will be [b,a] + v.executeOp(stack.SWAP, ctx) + + // Stack should have two items + assert.Equal(t, 2, ctx.Estack.Len()) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(3), itemA.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + +} + +func TestTuckOp(t *testing.T) { + + v := VM{} + + a, err := stack.NewInt(big.NewInt(3)) + assert.Nil(t, err) + + b, err := stack.NewInt(big.NewInt(6)) + assert.Nil(t, err) + + c, err := stack.NewInt(big.NewInt(9)) + assert.Nil(t, err) + + ctx := stack.NewContext([]byte{}) + ctx.Estack.Push(a).Push(b).Push(c) + + // copy the top stack item c and + // inserts it before the second top stack item. + // The final stack will be [a,c,b,c] + v.executeOp(stack.TUCK, ctx) + + // Stack should have four items + assert.Equal(t, 4, ctx.Estack.Len()) + + itemC, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemB, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemC2, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + itemA, err := ctx.Estack.PopInt() + assert.Nil(t, err) + + assert.Equal(t, int64(9), itemC.Value().Int64()) + assert.Equal(t, int64(6), itemB.Value().Int64()) + assert.Equal(t, int64(9), itemC2.Value().Int64()) + assert.Equal(t, int64(3), itemA.Value().Int64()) + +} From 458e6f922b3c0103e34944155bc6f3e9d93609b7 Mon Sep 17 00:00:00 2001 From: DauTT Date: Fri, 12 Apr 2019 22:34:28 +0200 Subject: [PATCH 097/117] Clean up comments --- pkg/vm/vm_ops_stackmani_test.go | 46 --------------------------------- 1 file changed, 46 deletions(-) diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index 45e84ddb7..eb9bf9a91 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -149,49 +149,3 @@ func TestPickOp(t *testing.T) { assert.Equal(t, int64(3), itemA2.Value().Int64()) } - -/* -func TestXswapOp(t *testing.T) { - - v := VM{} - - sert.Nil(t, err) - - ctx := stack.NewContext([]byte{}) - ctx.Estack.Push(a).Push(b).Push(c).Push(d) - - // pop n (= d = 2) from the stack. - // we wa, err := stack.NewInt(big.NewInt(3)) - assert.Nil(t, err) - - b, err := stack.NewInt(big.NewInt(6)) - assert.Nil(t, err) - - c, err := stack.NewInt(big.NewInt(9)) - assert.Nil(t, err) - - d, err := stack.NewInt(big.NewInt(2)) - asill swap the n-item which - // is located in position len(stack)-n-1 (= 3-2-1= 0) - // with the top stack item. - // The final stack will be [c,b,a] - v.executeOp(stack.XSWAP, ctx) - - // Stack should have three items - assert.Equal(t, 3, ctx.Estack.Len()) - - itemA, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - itemB, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - itemC, err := ctx.Estack.PopInt() - assert.Nil(t, err) - - assert.Equal(t, int64(3), itemA.Value().Int64()) - assert.Equal(t, int64(6), itemB.Value().Int64()) - assert.Equal(t, int64(9), itemC.Value().Int64()) - -} -*/ From b9b118d3ea8eb9c772f1bf9f37b52d0f9777f573 Mon Sep 17 00:00:00 2001 From: dauTT <30392990+dauTT@users.noreply.github.com> Date: Mon, 5 Aug 2019 09:34:31 +0200 Subject: [PATCH 098/117] Add ReverseString, ToReverseScriptHash method (#281) * Added 1) ReverseString method to the Uint160 type 2) ToReverseScriptHash method to convert a base58 address to a reverse script hash * Simplified ToScriptHash method --- pkg/wire/util/address/address.go | 19 ++++++++++++------- pkg/wire/util/address/address_test.go | 17 +++++++++++++++++ pkg/wire/util/uint160.go | 5 +++++ pkg/wire/util/uint160_test.go | 12 ++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 pkg/wire/util/address/address_test.go diff --git a/pkg/wire/util/address/address.go b/pkg/wire/util/address/address.go index 12f09c63a..08b0347af 100644 --- a/pkg/wire/util/address/address.go +++ b/pkg/wire/util/address/address.go @@ -1,22 +1,27 @@ package address import ( - "encoding/hex" - "github.com/CityOfZion/neo-go/pkg/crypto/base58" "github.com/CityOfZion/neo-go/pkg/wire/util" ) // ToScriptHash converts an address to a script hash func ToScriptHash(address string) string { - - decodedAddressAsBytes, err := base58.Decode(address) + a, err := Uint160Decode(address) if err != nil { return "" } - decodedAddressAsHex := hex.EncodeToString(decodedAddressAsBytes) - scriptHash := (decodedAddressAsHex[2:42]) - return scriptHash + return a.String() + +} + +// ToReverseScriptHash converts an address to a reverse script hash +func ToReverseScriptHash(address string) string { + a, err := Uint160Decode(address) + if err != nil { + return "" + } + return a.ReverseString() } // FromUint160 returns the "NEO address" from the given diff --git a/pkg/wire/util/address/address_test.go b/pkg/wire/util/address/address_test.go new file mode 100644 index 000000000..af264a935 --- /dev/null +++ b/pkg/wire/util/address/address_test.go @@ -0,0 +1,17 @@ +package address + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestScriptHash(t *testing.T) { + address := "AJeAEsmeD6t279Dx4n2HWdUvUmmXQ4iJvP" + + hash := ToScriptHash(address) + reverseHash := ToReverseScriptHash(address) + + assert.Equal(t, "b28427088a3729b2536d10122960394e8be6721f", reverseHash) + assert.Equal(t, "1f72e68b4e39602912106d53b229378a082784b2", hash) +} diff --git a/pkg/wire/util/uint160.go b/pkg/wire/util/uint160.go index 429fe5d95..fa81299bf 100644 --- a/pkg/wire/util/uint160.go +++ b/pkg/wire/util/uint160.go @@ -68,6 +68,11 @@ func (u Uint160) String() string { return hex.EncodeToString(u.Bytes()) } +// ReverseString implements the stringer interface. +func (u Uint160) ReverseString() string { + return hex.EncodeToString(u.BytesReverse()) +} + // Equals returns true if both Uint256 values are the same. func (u Uint160) Equals(other Uint160) bool { for i := 0; i < uint160Size; i++ { diff --git a/pkg/wire/util/uint160_test.go b/pkg/wire/util/uint160_test.go index 96416653c..f92f736c7 100644 --- a/pkg/wire/util/uint160_test.go +++ b/pkg/wire/util/uint160_test.go @@ -48,3 +48,15 @@ func TestUInt160Equals(t *testing.T) { t.Fatalf("%s and %s must be equal", ua, ua) } } + +func TestUInt160String(t *testing.T) { + hexStr := "b28427088a3729b2536d10122960394e8be6721f" + hexRevStr := "1f72e68b4e39602912106d53b229378a082784b2" + + val, err := Uint160DecodeString(hexStr) + assert.Nil(t, err) + + assert.Equal(t, hexStr, val.String()) + assert.Equal(t, hexRevStr, val.ReverseString()) + +} From 0258fa48f857eadf018894040d6ff8dd009aa081 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:25:16 +0300 Subject: [PATCH 099/117] pkg/vm: fix all GolangCI warnings about v.executeOp errcheck Fixes lots of warnings like this in the test code: Error return value of v.executeOp is not checked (from errcheck) --- pkg/vm/vm_ops_bitwise_test.go | 15 ++++--- pkg/vm/vm_ops_flow_test.go | 18 +++++--- pkg/vm/vm_ops_maths_test.go | 75 ++++++++++++++++++++++----------- pkg/vm/vm_ops_stackmani_test.go | 45 +++++++++++++------- pkg/vm/vmopscrypto_test.go | 12 ++++-- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/pkg/vm/vm_ops_bitwise_test.go b/pkg/vm/vm_ops_bitwise_test.go index 6e92ada2f..9e00a487e 100644 --- a/pkg/vm/vm_ops_bitwise_test.go +++ b/pkg/vm/vm_ops_bitwise_test.go @@ -20,7 +20,8 @@ func TestInvertOp(t *testing.T) { ctx.Estack.Push(a) // 1111 11001 = -6 (two complement representation) - v.executeOp(stack.INVERT, ctx) + _, err = v.executeOp(stack.INVERT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -47,7 +48,8 @@ func TestAndOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 100001 = 33 - v.executeOp(stack.AND, ctx) + _, err = v.executeOp(stack.AND, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -74,7 +76,8 @@ func TestOrOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 110011 = 51 (49 OR 35) - v.executeOp(stack.OR, ctx) + _, err = v.executeOp(stack.OR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -101,7 +104,8 @@ func TestXorOp(t *testing.T) { ctx.Estack.Push(a).Push(b) // 010010 = 18 (49 XOR 35) - v.executeOp(stack.XOR, ctx) + _, err = v.executeOp(stack.XOR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -125,7 +129,8 @@ func TestEqualOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.EQUAL, ctx) + _, err = v.executeOp(stack.EQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_flow_test.go b/pkg/vm/vm_ops_flow_test.go index 2801e51a4..0094b4f3f 100644 --- a/pkg/vm/vm_ops_flow_test.go +++ b/pkg/vm/vm_ops_flow_test.go @@ -18,7 +18,8 @@ func TestNopOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NOP, ctx) + _, err = v.executeOp(stack.NOP, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -46,7 +47,8 @@ func TestJmpOp(t *testing.T) { // ctx.ip will be set to offset. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMP, ctx) + _, err = v.executeOp(stack.JMP, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -75,7 +77,8 @@ func TestJmpIfOp1(t *testing.T) { // on top of the stack. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMPIF, ctx) + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -102,7 +105,8 @@ func TestJmpIfOp2(t *testing.T) { // nothing will happen because // the value of the boolean on top of the stack // is false - v.executeOp(stack.JMPIF, ctx) + _, err := v.executeOp(stack.JMPIF, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -129,7 +133,8 @@ func TestJmpIfNotOp1(t *testing.T) { // nothing will happen because // the value of the boolean on top of the stack // is true - v.executeOp(stack.JMPIFNOT, ctx) + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) // Stack should have 0 item assert.Equal(t, 0, ctx.Estack.Len()) @@ -158,7 +163,8 @@ func TestJmpIfNotOp2(t *testing.T) { // on top of the stack. // offset = ctx.IP() + int(ctx.ReadInt16()) - 3 // = 0 + 5 -3 = 2 - v.executeOp(stack.JMPIFNOT, ctx) + _, err := v.executeOp(stack.JMPIFNOT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 0, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_maths_test.go b/pkg/vm/vm_ops_maths_test.go index 14e8c88a3..9fb92c203 100644 --- a/pkg/vm/vm_ops_maths_test.go +++ b/pkg/vm/vm_ops_maths_test.go @@ -18,7 +18,8 @@ func TestIncOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.INC, ctx) + _, err = v.executeOp(stack.INC, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -39,7 +40,8 @@ func TestDecOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.DEC, ctx) + _, err = v.executeOp(stack.DEC, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -63,7 +65,8 @@ func TestAddOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.ADD, ctx) + _, err = v.executeOp(stack.ADD, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -88,7 +91,8 @@ func TestSubOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.SUB, ctx) + _, err = v.executeOp(stack.SUB, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -113,7 +117,8 @@ func TestDivOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.DIV, ctx) + _, err = v.executeOp(stack.DIV, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -137,7 +142,8 @@ func TestModOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MOD, ctx) + _, err = v.executeOp(stack.MOD, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -158,7 +164,8 @@ func TestNzOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NZ, ctx) + _, err = v.executeOp(stack.NZ, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -182,7 +189,8 @@ func TestMulOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MUL, ctx) + _, err = v.executeOp(stack.MUL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -203,7 +211,8 @@ func TestAbsOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.ABS, ctx) + _, err = v.executeOp(stack.ABS, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -223,7 +232,8 @@ func TestNotOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(b) - v.executeOp(stack.NOT, ctx) + _, err := v.executeOp(stack.NOT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -247,7 +257,8 @@ func TestNumEqual(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.NUMEQUAL, ctx) + _, err = v.executeOp(stack.NUMEQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -271,7 +282,8 @@ func TestNumNotEqual(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.NUMNOTEQUAL, ctx) + _, err = v.executeOp(stack.NUMNOTEQUAL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -292,7 +304,8 @@ func TestSignOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.SIGN, ctx) + _, err = v.executeOp(stack.SIGN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -313,7 +326,8 @@ func TestNegateOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.NEGATE, ctx) + _, err = v.executeOp(stack.NEGATE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -342,7 +356,8 @@ func TestLteOp(t *testing.T) { // we perform a <= b and place // the result on top of the evaluation // stack - v.executeOp(stack.LTE, ctx) + _, err = v.executeOp(stack.LTE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -371,7 +386,8 @@ func TestGteOp(t *testing.T) { // we perform a >= b and place // the result on top of the evaluation // stack - v.executeOp(stack.GTE, ctx) + _, err = v.executeOp(stack.GTE, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -400,7 +416,8 @@ func TestShlOp(t *testing.T) { // we perform a.Lsh(b) and place // the result on top of the evaluation // stack - v.executeOp(stack.SHL, ctx) + _, err = v.executeOp(stack.SHL, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -429,7 +446,8 @@ func TestShrOp(t *testing.T) { // we perform a.Rsh(b) and place // the result on top of the evaluation // stack - v.executeOp(stack.SHR, ctx) + _, err = v.executeOp(stack.SHR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -450,7 +468,8 @@ func TestBoolAndOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLAND, ctx) + _, err := v.executeOp(stack.BOOLAND, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -471,7 +490,8 @@ func TestBoolOrOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.BOOLOR, ctx) + _, err := v.executeOp(stack.BOOLOR, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -500,7 +520,8 @@ func TestLtOp(t *testing.T) { // we perform a < b and place // the result on top of the evaluation // stack - v.executeOp(stack.LT, ctx) + _, err = v.executeOp(stack.LT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -529,7 +550,8 @@ func TestGtOp(t *testing.T) { // we perform a > b and place // the result on top of the evaluation // stack - v.executeOp(stack.GT, ctx) + _, err = v.executeOp(stack.GT, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -553,7 +575,8 @@ func TestMinOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MIN, ctx) + _, err = v.executeOp(stack.MIN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -577,7 +600,8 @@ func TestMaxOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b) - v.executeOp(stack.MAX, ctx) + _, err = v.executeOp(stack.MAX, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -611,7 +635,8 @@ func TestWithinOp(t *testing.T) { // whose value is true, on top of the evaluation // stack. Otherwise we place a boolean with // false value. - v.executeOp(stack.WITHIN, ctx) + _, err = v.executeOp(stack.WITHIN, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index efe554351..669ede0d2 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -32,7 +32,8 @@ func TestRollOp(t *testing.T) { // has index len(stack)-n-1 (= 3-2-1= 0) // onto the top stack item. // The final stack will be [b,c,a] - v.executeOp(stack.ROLL, ctx) + _, err = v.executeOp(stack.ROLL, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -69,7 +70,8 @@ func TestRotOp(t *testing.T) { // move the third top stack a item onto // the top stack item c. // The final stack will be [b,c,a] - v.executeOp(stack.ROT, ctx) + _, err = v.executeOp(stack.ROT, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -103,7 +105,8 @@ func TestSwapOp(t *testing.T) { // Swaps the top two stack items. // The final stack will be [b,a] - v.executeOp(stack.SWAP, ctx) + _, err = v.executeOp(stack.SWAP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -138,7 +141,8 @@ func TestTuckOp(t *testing.T) { // copy the top stack item c and // inserts it before the second top stack item. // The final stack will be [a,c,b,c] - v.executeOp(stack.TUCK, ctx) + _, err = v.executeOp(stack.TUCK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -172,7 +176,8 @@ func TestDupOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a) - v.executeOp(stack.DUP, ctx) + _, err = v.executeOp(stack.DUP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -204,7 +209,8 @@ func TestNipOp(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(a).Push(b).Push(c) - v.executeOp(stack.NIP, ctx) + _, err = v.executeOp(stack.NIP, ctx) + assert.Nil(t, err) // Stack should have two items assert.Equal(t, 2, ctx.Estack.Len()) @@ -236,7 +242,8 @@ func TestOverOp(t *testing.T) { // OVER copies the second top stack item a // onto the top stack item b. // the new stack will be [a,b,a]. - v.executeOp(stack.OVER, ctx) + _, err = v.executeOp(stack.OVER, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -280,7 +287,8 @@ func TestPickOp(t *testing.T) { // has index len(stack)-n-1 (= 3-2-1= 0) // onto the top stack item. // The final stack will be [a,b,c,a] - v.executeOp(stack.PICK, ctx) + _, err = v.executeOp(stack.PICK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -327,7 +335,8 @@ func TestXswapOp(t *testing.T) { // is located in position len(stack)-n-1 (= 3-2-1= 0) // with the top stack item. // The final stack will be [c,b,a] - v.executeOp(stack.XSWAP, ctx) + _, err = v.executeOp(stack.XSWAP, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -370,7 +379,8 @@ func TestXTuckOp(t *testing.T) { // and insert the top stack item c // to the position len(stack)-n (= 3-2 = 1) // of the stack.The final stack will be [a,c,b,c] - v.executeOp(stack.XTUCK, ctx) + _, err = v.executeOp(stack.XTUCK, ctx) + assert.Nil(t, err) // Stack should have four items assert.Equal(t, 4, ctx.Estack.Len()) @@ -410,7 +420,8 @@ func TestXDepthOp(t *testing.T) { // push integer whose value is len(stack) (2) // on top of the stack - v.executeOp(stack.DEPTH, ctx) + _, err = v.executeOp(stack.DEPTH, ctx) + assert.Nil(t, err) // Stack should have three items assert.Equal(t, 3, ctx.Estack.Len()) @@ -444,7 +455,8 @@ func TestDupFromAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.DUPFROMALTSTACK, ctx) + _, err = v.executeOp(stack.DUPFROMALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 1, ctx.Astack.Len()) assert.Equal(t, 2, ctx.Estack.Len()) @@ -473,7 +485,8 @@ func TestToAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.TOALTSTACK, ctx) + _, err = v.executeOp(stack.TOALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 2, ctx.Astack.Len()) assert.Equal(t, 0, ctx.Estack.Len()) @@ -498,7 +511,8 @@ func TestFromAltStackOp(t *testing.T) { ctx.Estack.Push(a) ctx.Astack.Push(b) - v.executeOp(stack.FROMALTSTACK, ctx) + _, err = v.executeOp(stack.FROMALTSTACK, ctx) + assert.Nil(t, err) assert.Equal(t, 0, ctx.Astack.Len()) assert.Equal(t, 2, ctx.Estack.Len()) @@ -537,7 +551,8 @@ func TestXDropOp(t *testing.T) { // len(stack)-n-1 = 3-2-1 = 0. // Therefore a is removed from the stack. // Only b, c remain on the stack. - v.executeOp(stack.XDROP, ctx) + _, err = v.executeOp(stack.XDROP, ctx) + assert.Nil(t, err) assert.Equal(t, 2, ctx.Estack.Len()) diff --git a/pkg/vm/vmopscrypto_test.go b/pkg/vm/vmopscrypto_test.go index 08e26a786..3ceba1b75 100644 --- a/pkg/vm/vmopscrypto_test.go +++ b/pkg/vm/vmopscrypto_test.go @@ -17,7 +17,8 @@ func TestSha1Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.SHA1, ctx) + _, err := v.executeOp(stack.SHA1, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -40,7 +41,8 @@ func TestSha256Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.SHA256, ctx) + _, err := v.executeOp(stack.SHA256, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -63,7 +65,8 @@ func TestHash160Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.HASH160, ctx) + _, err := v.executeOp(stack.HASH160, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) @@ -86,7 +89,8 @@ func TestHash256Op(t *testing.T) { ctx := stack.NewContext([]byte{}) ctx.Estack.Push(ba1) - v.executeOp(stack.HASH256, ctx) + _, err := v.executeOp(stack.HASH256, ctx) + assert.Nil(t, err) // Stack should have one item assert.Equal(t, 1, ctx.Estack.Len()) From a976c4d04ff667f921d61edda61460684ff6650e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:29:24 +0300 Subject: [PATCH 100/117] pkg/vm: go fmt Some errors were introduced by github merges. --- pkg/vm/vm_ops.go | 2 +- pkg/vm/vm_ops_stackmani.go | 2 +- pkg/vm/vm_ops_stackmani_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/vm_ops.go b/pkg/vm/vm_ops.go index 0800058fe..d26ea5af4 100644 --- a/pkg/vm/vm_ops.go +++ b/pkg/vm/vm_ops.go @@ -36,7 +36,7 @@ var opFunc = map[stack.Instruction]stackInfo{ stack.MIN: Min, stack.MAX: Max, stack.WITHIN: Within, - stack.NUMEQUAL: NumEqual, + stack.NUMEQUAL: NumEqual, stack.NUMNOTEQUAL: NumNotEqual, stack.BOOLAND: BoolAnd, stack.BOOLOR: BoolOr, diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index 1b1670a1c..ebeaafca0 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -226,7 +226,7 @@ func DEPTH(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r return FAULT, err } - ctx.Estack.Push(length) + ctx.Estack.Push(length) return NONE, nil } diff --git a/pkg/vm/vm_ops_stackmani_test.go b/pkg/vm/vm_ops_stackmani_test.go index 669ede0d2..12e2e176e 100644 --- a/pkg/vm/vm_ops_stackmani_test.go +++ b/pkg/vm/vm_ops_stackmani_test.go @@ -302,7 +302,7 @@ func TestPickOp(t *testing.T) { itemB, err := ctx.Estack.PopInt() assert.Nil(t, err) - itemA2, err := ctx.Estack.PopInt() + itemA2, err := ctx.Estack.PopInt() assert.Nil(t, err) assert.Equal(t, int64(3), itemA.Value().Int64()) From 06f9e1d123a651c7df7c8854965ac9367da63ec9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:36:06 +0300 Subject: [PATCH 101/117] pkg/(crypto|vm): fix GolangCI errcheck warnings Like: Error return value of alg.Write is not checked (from errcheck) Actually even though the hash.Hash implements an io.Writer interface (that return meaningful things on .Write()) it has this comment in its documentation: // Write (via the embedded io.Writer interface) adds more data to the running hash. // It never returns an error. so it should be OK to ignore return results here. --- pkg/crypto/rfc6979/example_test.go | 4 ++-- pkg/vm/vmopscrypto.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/crypto/rfc6979/example_test.go b/pkg/crypto/rfc6979/example_test.go index 29693c272..b756ea80e 100755 --- a/pkg/crypto/rfc6979/example_test.go +++ b/pkg/crypto/rfc6979/example_test.go @@ -26,7 +26,7 @@ func ExampleSignECDSA() { // Hash a message. alg := sha512.New() - alg.Write([]byte("I am a potato.")) + _, _ = alg.Write([]byte("I am a potato.")) hash := alg.Sum(nil) // Sign the message. You don't need a PRNG for this. @@ -59,7 +59,7 @@ func ExampleSignDSA() { // Hash a message. alg := sha1.New() - alg.Write([]byte("I am a potato.")) + _, _ = alg.Write([]byte("I am a potato.")) hash := alg.Sum(nil) // Sign the message. You don't need a PRNG for this. diff --git a/pkg/vm/vmopscrypto.go b/pkg/vm/vmopscrypto.go index e38ab7402..5560c1d80 100644 --- a/pkg/vm/vmopscrypto.go +++ b/pkg/vm/vmopscrypto.go @@ -21,7 +21,7 @@ func SHA1(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rs } alg := sha1.New() - alg.Write(ba.Value()) + _, _ = alg.Write(ba.Value()) hash := alg.Sum(nil) res := stack.NewByteArray(hash) From 3bc195659a7ee2e1a09b0accbf57a77a8f73ef91 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 16:40:19 +0300 Subject: [PATCH 102/117] pkg/vm: fix error handling in XDROP() Found by GolangCI: Error return value of ctx.Estack.Remove is not checked (from errcheck) nilness: impossible condition: nil != nil (from govet) --- pkg/vm/vm_ops_stackmani.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/vm_ops_stackmani.go b/pkg/vm/vm_ops_stackmani.go index ebeaafca0..77448ff2f 100644 --- a/pkg/vm/vm_ops_stackmani.go +++ b/pkg/vm/vm_ops_stackmani.go @@ -291,7 +291,7 @@ func XDROP(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, r return FAULT, err } - ctx.Estack.Remove(uint16(n.Value().Uint64())) + _, err = ctx.Estack.Remove(uint16(n.Value().Uint64())) if err != nil { return FAULT, err } From 1fb66d6b73ae46bbccbadb25fb3d42b696fb6ed2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:12:05 +0300 Subject: [PATCH 103/117] pkg/vm/stack: improve Array testing code slightly GolangCI complained: testArray is unused (from deadcode) But this function was actually wrong being a copy-paste of testMakeStackMap(), it also didn't conform to testMake... naming scheme, so this fixes it. To make thing more uniform NewArray() was also changed to return error, map_test.go code adjusted to this changes and finally array_test.go was added as a stub for future Array testing. --- pkg/vm/stack/array.go | 4 ++-- pkg/vm/stack/array_test.go | 16 ++++++++++++++++ pkg/vm/stack/map_test.go | 6 +++--- pkg/vm/stack/test_helper.go | 4 ++-- 4 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 pkg/vm/stack/array_test.go diff --git a/pkg/vm/stack/array.go b/pkg/vm/stack/array.go index c11730b1d..badb03cb0 100644 --- a/pkg/vm/stack/array.go +++ b/pkg/vm/stack/array.go @@ -22,11 +22,11 @@ func (a *Array) Value() []Item { } // NewArray returns a new Array. -func NewArray(val []Item) *Array { +func NewArray(val []Item) (*Array, error) { return &Array{ &abstractItem{}, val, - } + }, nil } // Hash overrides the default abstract hash method. diff --git a/pkg/vm/stack/array_test.go b/pkg/vm/stack/array_test.go new file mode 100644 index 000000000..92b5b3bd0 --- /dev/null +++ b/pkg/vm/stack/array_test.go @@ -0,0 +1,16 @@ +package stack + +import ( + "testing" + +// it's a stub at the moment, but will need it anyway +// "github.com/stretchr/testify/assert" +) + +func TestArray(t *testing.T) { + var a Item = testMakeStackInt(t, 3) + var b Item = testMakeStackInt(t, 6) + var c Item = testMakeStackInt(t, 9) + var ta = testMakeArray(t, []Item{a, b, c}) + _ = ta +} diff --git a/pkg/vm/stack/map_test.go b/pkg/vm/stack/map_test.go index 2c2091dc4..071e7fcc7 100644 --- a/pkg/vm/stack/map_test.go +++ b/pkg/vm/stack/map_test.go @@ -16,7 +16,7 @@ func TestMap(t *testing.T) { b: a, }) var e = NewContext([]byte{1, 2, 3, 4}) - var f = NewArray([]Item{a, b}) + var f = testMakeArray(t, []Item{a, b}) val := map[Item]Item{ a: c, @@ -47,7 +47,7 @@ func TestMap(t *testing.T) { valueE, _ := m.ValueOfKey(NewContext([]byte{1, 2, 3, 4})) assert.Equal(t, d, valueE) - valueF, _ := m.ValueOfKey(NewArray([]Item{a, b})) + valueF, _ := m.ValueOfKey(testMakeArray(t, []Item{a, b})) assert.Equal(t, e, valueF) valueX, _ := m.ValueOfKey(NewByteArray([]byte{1, 2, 35})) @@ -100,7 +100,7 @@ func TestMap(t *testing.T) { assert.Nil(t, err) assert.Equal(t, true, checkContext.Value()) - checkArray, err := CompareHash(f, NewArray([]Item{a, b})) + checkArray, err := CompareHash(f, testMakeArray(t, []Item{a, b})) assert.Nil(t, err) assert.Equal(t, true, checkArray.Value()) } diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index b2615141e..29be378dd 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -49,8 +49,8 @@ func testMakeStackMap(t *testing.T, m map[Item]Item) *Map { return a } -func testArray(t *testing.T, m map[Item]Item) *Map { - a, err := NewMap(m) +func testMakeArray(t *testing.T, v []Item) *Array { + a, err := NewArray(v) assert.Nil(t, err) return a } From 6be27ad4b0e22404322e3c685d110cc8daebb078 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:16:47 +0300 Subject: [PATCH 104/117] pkg/vm/stack: s,testPeakInteger,testPeakInteger, Fix obvious typo. --- pkg/vm/stack/stack_test.go | 4 ++-- pkg/vm/stack/test_helper.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 246e983f4..148931a5a 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -97,7 +97,7 @@ func TestStackPeekMutability(t *testing.T) { testStack.Push(a).Push(b) - peekedItem := testPeakInteger(t, testStack, 0) + peekedItem := testPeekInteger(t, testStack, 0) assert.Equal(t, true, peekedItem.Equal(b)) // Check that by modifying the peeked value, @@ -122,7 +122,7 @@ func TestStackPeek(t *testing.T) { // i starts at 0, j starts at len(values)-1 for i, j := 0, len(values)-1; j >= 0; i, j = i+1, j-1 { - peekedItem := testPeakInteger(t, testStack, uint16(i)) + peekedItem := testPeekInteger(t, testStack, uint16(i)) a := testMakeStackInt(t, values[j]) fmt.Printf("%#v\n", peekedItem.val.Int64()) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 29be378dd..d12bea71f 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -10,7 +10,7 @@ import ( ) // helper functions -func testPeakInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { +func testPeekInteger(t *testing.T, tStack *RandomAccess, n uint16) *Int { stackElement, err := tStack.Peek(n) assert.Nil(t, err) item, err := stackElement.Integer() From d6c3f74e3c6b63c57d62ca1d1e99f688a2848f2e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:53:19 +0300 Subject: [PATCH 105/117] pkg/vm/stack: make some use of testReadInt64() GolangCI complains: testReadInt64 is unused (from deadcode) Fix it to always provide correctly-sized buffer for the binary.Read(). --- pkg/vm/stack/int_test.go | 2 ++ pkg/vm/stack/test_helper.go | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 25d360183..46a2c302e 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -64,6 +64,8 @@ func TestByteArrConversion(t *testing.T) { ba, err := a.ByteArray() assert.Nil(t, err) + assert.Equal(t, num, testReadInt64(ba.val)) + have, err := ba.Integer() assert.Nil(t, err) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index d12bea71f..56a5ff89d 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -38,7 +38,11 @@ func testMakeStackInt(t *testing.T, num int64) *Int { func testReadInt64(data []byte) int64 { var ret int64 - buf := bytes.NewBuffer(data) + var arr [8]byte + + // expands or shrinks data automatically + copy(arr[:], data) + buf := bytes.NewBuffer(arr[:]) binary.Read(buf, binary.LittleEndian, &ret) return ret } From 613bad36e07716501444235e82f71b5c1b2b19d6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:56:51 +0300 Subject: [PATCH 106/117] pkg/vm/stack: fix unused binary.Read() result in testReadInt64() GolangCI: Error return value of binary.Read is not checked (from errcheck) --- pkg/vm/stack/int_test.go | 2 +- pkg/vm/stack/test_helper.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/vm/stack/int_test.go b/pkg/vm/stack/int_test.go index 46a2c302e..07fee3958 100644 --- a/pkg/vm/stack/int_test.go +++ b/pkg/vm/stack/int_test.go @@ -64,7 +64,7 @@ func TestByteArrConversion(t *testing.T) { ba, err := a.ByteArray() assert.Nil(t, err) - assert.Equal(t, num, testReadInt64(ba.val)) + assert.Equal(t, num, testReadInt64(t, ba.val)) have, err := ba.Integer() assert.Nil(t, err) diff --git a/pkg/vm/stack/test_helper.go b/pkg/vm/stack/test_helper.go index 56a5ff89d..ba497cffc 100644 --- a/pkg/vm/stack/test_helper.go +++ b/pkg/vm/stack/test_helper.go @@ -36,14 +36,15 @@ func testMakeStackInt(t *testing.T, num int64) *Int { return a } -func testReadInt64(data []byte) int64 { +func testReadInt64(t *testing.T, data []byte) int64 { var ret int64 var arr [8]byte // expands or shrinks data automatically copy(arr[:], data) buf := bytes.NewBuffer(arr[:]) - binary.Read(buf, binary.LittleEndian, &ret) + err := binary.Read(buf, binary.LittleEndian, &ret) + assert.Nil(t, err) return ret } From bab187f4a8f59abdca2a9869789677566bec2332 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 17:59:59 +0300 Subject: [PATCH 107/117] pkg/vm: drop bogus err check in BoolAnd() and BoolOr() GolangCI: nilness: impossible condition: nil != nil (from govet) --- pkg/vm/vm_ops_maths.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/vm/vm_ops_maths.go b/pkg/vm/vm_ops_maths.go index ab4b52f71..264278092 100644 --- a/pkg/vm/vm_ops_maths.go +++ b/pkg/vm/vm_ops_maths.go @@ -296,9 +296,6 @@ func BoolAnd(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return FAULT, err } res := bool1.And(bool2) - if err != nil { - return FAULT, err - } ctx.Estack.Push(res) @@ -315,9 +312,6 @@ func BoolOr(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, return FAULT, err } res := bool1.Or(bool2) - if err != nil { - return FAULT, err - } ctx.Estack.Push(res) From 4e40467dc38f1261f7523fed7d8731d7e9b6ffdb Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:05:04 +0300 Subject: [PATCH 108/117] pkg/vm/stack: fix S1008 gosimple warning from GolangCI S1008: should use 'return ' instead of 'if { return }; return ' (from gosimple) --- pkg/vm/stack/Int.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/vm/stack/Int.go b/pkg/vm/stack/Int.go index 903190541..d1c06e572 100644 --- a/pkg/vm/stack/Int.go +++ b/pkg/vm/stack/Int.go @@ -22,10 +22,7 @@ func NewInt(val *big.Int) (*Int, error) { // Equal will check if two integers hold equal value func (i *Int) Equal(s *Int) bool { - if i.val.Cmp(s.val) != 0 { - return false - } - return true + return i.val.Cmp(s.val) == 0 } // Add will add two stackIntegers together From f4451032609e4fcd5c0b42c26c570d480cd4b43a Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:08:39 +0300 Subject: [PATCH 109/117] pkg/vm/stack: fix S1002 gosimple warnings in map.go S1002: should omit comparison to bool constant, can be simplified to ok.Value() (from gosimple) --- pkg/vm/stack/map.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/vm/stack/map.go b/pkg/vm/stack/map.go index b8e462c71..9db2d6e4d 100644 --- a/pkg/vm/stack/map.go +++ b/pkg/vm/stack/map.go @@ -43,7 +43,7 @@ func (m *Map) ContainsKey(key Item) (*Boolean, error) { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return nil, err - } else if ok.Value() == true { + } else if ok.Value() { return ok, nil } @@ -63,7 +63,7 @@ func (m *Map) Remove(key Item) error { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return err - } else if ok.Value() == true { + } else if ok.Value() { d = k } @@ -80,7 +80,7 @@ func (m *Map) Add(key Item, value Item) error { for k := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return err - } else if ok.Value() == true { + } else if ok.Value() { return errors.New("try to insert duplicate key! ") } } @@ -94,7 +94,7 @@ func (m *Map) ValueOfKey(key Item) (Item, error) { for k, v := range m.Value() { if ok, err := CompareHash(k, key); err != nil { return nil, err - } else if ok.Value() == true { + } else if ok.Value() { return v, nil } From 9be53e5961b00745af99863647ec639fab1eba5e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:13:04 +0300 Subject: [PATCH 110/117] pkg/vm/stack: fix SA4006 GolangCI warning in TestStackPushPop() SA4006: this value of stackElement is never used (from staticcheck) --- pkg/vm/stack/stack_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/stack/stack_test.go b/pkg/vm/stack/stack_test.go index 148931a5a..2241782a2 100644 --- a/pkg/vm/stack/stack_test.go +++ b/pkg/vm/stack/stack_test.go @@ -51,7 +51,7 @@ func TestStackPushPop(t *testing.T) { assert.Equal(t, true, item.Equal(a)) // We should get an error as there are nomore items left to pop - stackElement, err = testStack.Pop() + _, err = testStack.Pop() assert.NotNil(t, err) } From bc87d3e8e6f1fb6b6c7ec57358f4fea7398fd5da Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:15:48 +0300 Subject: [PATCH 111/117] pkg/vm: fix GolangCI's SA4009 in RET() SA4009: argument ctx is overwritten before first use (from staticcheck) It should be OK here. --- pkg/vm/vm_ops_flow.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/vm/vm_ops_flow.go b/pkg/vm/vm_ops_flow.go index 71f32d42d..45a8248da 100644 --- a/pkg/vm/vm_ops_flow.go +++ b/pkg/vm/vm_ops_flow.go @@ -9,6 +9,7 @@ import ( // RET Returns from the current context // Returns HALT if there are nomore context's to run func RET(op stack.Instruction, ctx *stack.Context, istack *stack.Invocation, rstack *stack.RandomAccess) (Vmstate, error) { + _ = ctx // fix SA4009 warning // Pop current context from the Inovation stack ctx, err := istack.PopCurrentContext() From f068f4c0eda9ddaf0f4b02aaef3d0bf2bea1e244 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 18:19:26 +0300 Subject: [PATCH 112/117] pkg/vm/stack: drop unused (*RandomAccess).items() GolangCI complains: U1000: func (*RandomAccess).items is unused And it looks like everyone and their dog just use ras.vals without any wrappers when there is a need to access it, so drop the wrapper. --- pkg/vm/stack/stack.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/vm/stack/stack.go b/pkg/vm/stack/stack.go index 2a1b01693..eb08e5d52 100644 --- a/pkg/vm/stack/stack.go +++ b/pkg/vm/stack/stack.go @@ -23,11 +23,6 @@ func New() *RandomAccess { } } -// Items will return all items in the stack -func (ras *RandomAccess) items() []Item { - return ras.vals -} - //Len will return the length of the stack func (ras *RandomAccess) Len() int { if ras.vals == nil { From b3d6740a77a1ef812bff02d0283204fb77a0dbd7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 19:25:32 +0300 Subject: [PATCH 113/117] .gitignore: add some standard ignore files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..02cfc430a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +TAGS From bb4680a59d72f0ad01c61ccfb772a942b6977996 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 19:32:17 +0300 Subject: [PATCH 114/117] go.(mod|sum): make a clenup with go mod tidy --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 385728796..3582a7ed1 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,8 @@ module github.com/CityOfZion/neo-go require ( github.com/o3labs/neo-utils v0.0.0-20190129071622-8ae0bc31751f + github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 github.com/syndtr/goleveldb v1.0.0 - golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c // indirect golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b ) diff --git a/go.sum b/go.sum index 0764f88ed..6061dd1cd 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -22,8 +24,6 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM= -golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b h1:+/WWzjwW6gidDJnMKWLKLX1gxn7irUTF1fLpQovfQ5M= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= @@ -42,4 +42,3 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 2949c3bd9d9cc1bbc0a50fd7404a7e4057abceec Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 12 Aug 2019 19:42:26 +0300 Subject: [PATCH 115/117] .gitignore: add db directory Where the node stores its stuff. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 02cfc430a..27b7ae562 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *~ TAGS +db From 9a30f2fbcc1b033b71588e4c2df2f40b8063d51c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 19 Aug 2019 15:29:36 +0300 Subject: [PATCH 116/117] connmgr: correctly pass binding error to the server And make the node fail gracefully if it's unable to bind. Before this commit: ----- Server is starting up Connection manager started Error connecting to outbound listen tcp 127.0.0.1:20332: bind: address already in use We have connected successfully to: 127.0.0.1:20334 panic: runtime error: invalid memory address or nil pointer dereference panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x6bbfa2] goroutine 12 [running]: github.com/CityOfZion/neo-go/pkg/connmgr.New.func1.1(0x0, 0x0) /home/rik/dev/neo-go/pkg/connmgr/connmgr.go:47 +0x22 panic(0x718980, 0xa22f70) /usr/lib64/go/1.12/src/runtime/panic.go:522 +0x1b5 github.com/CityOfZion/neo-go/pkg/connmgr.New.func1(0xc000013430, 0xc000013450, 0xc000013440, 0xc0005265f0, 0xf, 0x0) /home/rik/dev/neo-go/pkg/connmgr/connmgr.go:52 +0x15e created by github.com/CityOfZion/neo-go/pkg/connmgr.New /home/rik/dev/neo-go/pkg/connmgr/connmgr.go:38 +0xfe exit status 2 ----- After this commit: ----- listen tcp 127.0.0.1:20332: bind: address already in use ----- --- pkg/connmgr/connmgr.go | 17 ++++++++--------- pkg/server/connmgr.go | 2 +- pkg/server/server.go | 5 ++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/connmgr/connmgr.go b/pkg/connmgr/connmgr.go index a8e8442ac..450e87e33 100755 --- a/pkg/connmgr/connmgr.go +++ b/pkg/connmgr/connmgr.go @@ -27,7 +27,13 @@ type Connmgr struct { } //New creates a new connection manager -func New(cfg Config) *Connmgr { +func New(cfg Config) (*Connmgr, error) { + listener, err := net.Listen("tcp", cfg.AddressPort) + + if err != nil { + return nil, err + } + cnnmgr := &Connmgr{ cfg, make(map[string]*Request), @@ -36,13 +42,6 @@ func New(cfg Config) *Connmgr { } go func() { - - listener, err := net.Listen("tcp", cfg.AddressPort) - - if err != nil { - fmt.Println("Error connecting to outbound ", err) - } - defer func() { listener.Close() }() @@ -59,7 +58,7 @@ func New(cfg Config) *Connmgr { }() - return cnnmgr + return cnnmgr, nil } // NewRequest will make a new connection gets the address from address func in config diff --git a/pkg/server/connmgr.go b/pkg/server/connmgr.go index e97416801..155712cb6 100644 --- a/pkg/server/connmgr.go +++ b/pkg/server/connmgr.go @@ -11,7 +11,7 @@ import ( iputils "github.com/CityOfZion/neo-go/pkg/wire/util/ip" ) -func setupConnManager(s *Server, port uint16) *connmgr.Connmgr { +func setupConnManager(s *Server, port uint16) (*connmgr.Connmgr, error) { cfg := connmgr.Config{ GetAddress: s.getAddress, OnAccept: s.onAccept, diff --git a/pkg/server/server.go b/pkg/server/server.go index 22a781170..1e4d6407b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -63,7 +63,10 @@ func New(net protocol.Magic, port uint16) (*Server, error) { s.smg = syncmgr // Setup connection manager - connmgr := setupConnManager(s, port) + connmgr, err := setupConnManager(s, port) + if err != nil { + return nil, err + } s.cmg = connmgr // Setup peer config From ddd1d92ff151dba6de938bd14cb43f1dd866e987 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 20 Aug 2019 18:39:50 +0300 Subject: [PATCH 117/117] pkg: hide it by moving to _pkg.dev The idea here is to preserve the history of `dev` branch development and its code when merging with the `master`. Later this code could be moved into the masters code where appropriate. --- {pkg => _pkg.dev}/Readme.md | 0 {pkg => _pkg.dev}/chain/chain.go | 0 {pkg => _pkg.dev}/chain/chaindb.go | 0 {pkg => _pkg.dev}/chain/chaindb_test.go | 0 {pkg => _pkg.dev}/chain/errors.go | 0 {pkg => _pkg.dev}/chaincfg/chaincfg.go | 0 {pkg => _pkg.dev}/chaincfg/chaincfg_test.go | 0 {pkg => _pkg.dev}/connmgr/config.go | 0 {pkg => _pkg.dev}/connmgr/connmgr.go | 0 {pkg => _pkg.dev}/connmgr/connmgr_test.go | 0 {pkg => _pkg.dev}/connmgr/readme.md | 0 {pkg => _pkg.dev}/connmgr/request.go | 0 {pkg => _pkg.dev}/crypto/aes/aes256.go | 0 {pkg => _pkg.dev}/crypto/base58/base58.go | 0 {pkg => _pkg.dev}/crypto/base58/base58_test.go | 0 {pkg => _pkg.dev}/crypto/elliptic/Readme.md | 0 {pkg => _pkg.dev}/crypto/elliptic/curves.go | 0 {pkg => _pkg.dev}/crypto/elliptic/elliptic.go | 0 {pkg => _pkg.dev}/crypto/elliptic/elliptic_test.go | 0 {pkg => _pkg.dev}/crypto/hash/hash.go | 0 {pkg => _pkg.dev}/crypto/hash/hash_test.go | 0 {pkg => _pkg.dev}/crypto/privatekey/privatekey.go | 0 {pkg => _pkg.dev}/crypto/privatekey/privatekey_test.go | 0 {pkg => _pkg.dev}/crypto/publickey/TestHelper/helper.go | 0 {pkg => _pkg.dev}/crypto/publickey/TestHelper/helper_test.go | 0 {pkg => _pkg.dev}/crypto/publickey/publickey.go | 0 {pkg => _pkg.dev}/crypto/publickey/publickey_test.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/LICENSE | 0 {pkg => _pkg.dev}/crypto/rfc6979/dsa.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/dsa_test.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/ecdsa.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/ecdsa_test.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/example_test.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/rfc6979.go | 0 {pkg => _pkg.dev}/crypto/rfc6979/rfc6979_test.go | 0 {pkg => _pkg.dev}/database/leveldb.go | 0 {pkg => _pkg.dev}/database/leveldb_test.go | 0 {pkg => _pkg.dev}/database/table.go | 0 {pkg => _pkg.dev}/peer/config.go | 0 {pkg => _pkg.dev}/peer/peer.go | 0 {pkg => _pkg.dev}/peer/peer_test.go | 0 {pkg => _pkg.dev}/peer/peerhandshake.go | 0 {pkg => _pkg.dev}/peer/readme.md | 0 {pkg => _pkg.dev}/peer/responsehandlers.go | 0 {pkg => _pkg.dev}/peer/stall/stall.go | 0 {pkg => _pkg.dev}/peer/stall/stall_test.go | 0 {pkg => _pkg.dev}/peermgr/blockcache.go | 0 {pkg => _pkg.dev}/peermgr/blockcache_test.go | 0 {pkg => _pkg.dev}/peermgr/peermgr.go | 0 {pkg => _pkg.dev}/peermgr/peermgr_test.go | 0 {pkg => _pkg.dev}/server/addrmgr.go | 0 {pkg => _pkg.dev}/server/chain.go | 0 {pkg => _pkg.dev}/server/connmgr.go | 0 {pkg => _pkg.dev}/server/database.go | 0 {pkg => _pkg.dev}/server/peerconfig.go | 0 {pkg => _pkg.dev}/server/peermgr.go | 0 {pkg => _pkg.dev}/server/server.go | 0 {pkg => _pkg.dev}/server/syncmgr.go | 0 {pkg => _pkg.dev}/syncmgr/blockmode.go | 0 {pkg => _pkg.dev}/syncmgr/blockpool.go | 0 {pkg => _pkg.dev}/syncmgr/blockpool_test.go | 0 {pkg => _pkg.dev}/syncmgr/config.go | 0 {pkg => _pkg.dev}/syncmgr/headermode.go | 0 {pkg => _pkg.dev}/syncmgr/mockhelpers_test.go | 0 {pkg => _pkg.dev}/syncmgr/normalmode.go | 0 {pkg => _pkg.dev}/syncmgr/syncmgr.go | 0 {pkg => _pkg.dev}/syncmgr/syncmgr_onblock_test.go | 0 {pkg => _pkg.dev}/syncmgr/syncmgr_onheaders_test.go | 0 {pkg => _pkg.dev}/vm/csharp-interop-test/push/pushbytes1.json | 0 {pkg => _pkg.dev}/vm/csharp-interop-test/readme.md | 0 {pkg => _pkg.dev}/vm/csharp-interop-test/testStruct.go | 0 {pkg => _pkg.dev}/vm/stack/Int.go | 0 {pkg => _pkg.dev}/vm/stack/Readme.md | 0 {pkg => _pkg.dev}/vm/stack/array.go | 0 {pkg => _pkg.dev}/vm/stack/array_test.go | 0 {pkg => _pkg.dev}/vm/stack/boolean.go | 0 {pkg => _pkg.dev}/vm/stack/builder.go | 0 {pkg => _pkg.dev}/vm/stack/bytearray.go | 0 {pkg => _pkg.dev}/vm/stack/context.go | 0 {pkg => _pkg.dev}/vm/stack/instruction.go | 0 {pkg => _pkg.dev}/vm/stack/int_test.go | 0 {pkg => _pkg.dev}/vm/stack/invocationstack.go | 0 {pkg => _pkg.dev}/vm/stack/map.go | 0 {pkg => _pkg.dev}/vm/stack/map_test.go | 0 {pkg => _pkg.dev}/vm/stack/stack.go | 0 {pkg => _pkg.dev}/vm/stack/stack_test.go | 0 {pkg => _pkg.dev}/vm/stack/stackitem.go | 0 {pkg => _pkg.dev}/vm/stack/stackitem_test.go | 0 {pkg => _pkg.dev}/vm/stack/test_helper.go | 0 {pkg => _pkg.dev}/vm/state.go | 0 {pkg => _pkg.dev}/vm/vm.go | 0 {pkg => _pkg.dev}/vm/vm_ops.go | 0 {pkg => _pkg.dev}/vm/vm_ops_bitwise.go | 0 {pkg => _pkg.dev}/vm/vm_ops_bitwise_test.go | 0 {pkg => _pkg.dev}/vm/vm_ops_exceptions.go | 0 {pkg => _pkg.dev}/vm/vm_ops_flow.go | 0 {pkg => _pkg.dev}/vm/vm_ops_flow_test.go | 0 {pkg => _pkg.dev}/vm/vm_ops_maths.go | 0 {pkg => _pkg.dev}/vm/vm_ops_maths_test.go | 0 {pkg => _pkg.dev}/vm/vm_ops_stackmani.go | 0 {pkg => _pkg.dev}/vm/vm_ops_stackmani_test.go | 0 {pkg => _pkg.dev}/vm/vm_test.go | 0 {pkg => _pkg.dev}/vm/vmopscrypto.go | 0 {pkg => _pkg.dev}/vm/vmopscrypto_test.go | 0 {pkg => _pkg.dev}/wire/Readme.md | 0 {pkg => _pkg.dev}/wire/base.go | 0 {pkg => _pkg.dev}/wire/command/command.go | 0 {pkg => _pkg.dev}/wire/message.go | 0 {pkg => _pkg.dev}/wire/message_test.go | 0 {pkg => _pkg.dev}/wire/payload/block.go | 0 {pkg => _pkg.dev}/wire/payload/block_test.go | 0 {pkg => _pkg.dev}/wire/payload/blockbase.go | 0 {pkg => _pkg.dev}/wire/payload/blockbase_test.go | 0 {pkg => _pkg.dev}/wire/payload/maddr.go | 0 {pkg => _pkg.dev}/wire/payload/maddr_test.go | 0 {pkg => _pkg.dev}/wire/payload/mblock.go | 0 {pkg => _pkg.dev}/wire/payload/mgetaddr.go | 0 {pkg => _pkg.dev}/wire/payload/mgetaddr_test.go | 0 {pkg => _pkg.dev}/wire/payload/mgetblocks.go | 0 {pkg => _pkg.dev}/wire/payload/mgetblocks_test.go | 0 {pkg => _pkg.dev}/wire/payload/mgetdata.go | 0 {pkg => _pkg.dev}/wire/payload/mgetdata_test.go | 0 {pkg => _pkg.dev}/wire/payload/mgetheaders.go | 0 {pkg => _pkg.dev}/wire/payload/mgetheaders_test.go | 0 {pkg => _pkg.dev}/wire/payload/mheaders.go | 0 {pkg => _pkg.dev}/wire/payload/mheaders_test.go | 0 {pkg => _pkg.dev}/wire/payload/minventory.go | 0 {pkg => _pkg.dev}/wire/payload/minventory_test.go | 0 {pkg => _pkg.dev}/wire/payload/mmempool.go | 0 {pkg => _pkg.dev}/wire/payload/mtx.go | 0 {pkg => _pkg.dev}/wire/payload/mverack.go | 0 {pkg => _pkg.dev}/wire/payload/mverack_test.go | 0 {pkg => _pkg.dev}/wire/payload/mversion.go | 0 {pkg => _pkg.dev}/wire/payload/mversion_test.go | 0 {pkg => _pkg.dev}/wire/payload/net_addr.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/Attribute.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/Input.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/Output.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/Witness.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/assettype.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/attr_usage.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/base.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/claim.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/claim_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/contract.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/contract_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/enrollment.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/enrollment_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/invocation.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/invocation_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/issue.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/issue_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/miner.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/miner_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/paramtype.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/publickey.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/publish.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/publish_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/register.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/register_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/state.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/state_test.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/statedescriptor.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/types/types.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/util.go | 0 {pkg => _pkg.dev}/wire/payload/transaction/version/version.go | 0 {pkg => _pkg.dev}/wire/protocol/protocol.go | 0 {pkg => _pkg.dev}/wire/util/Checksum/checksum.go | 0 {pkg => _pkg.dev}/wire/util/address/address.go | 0 {pkg => _pkg.dev}/wire/util/address/address_test.go | 0 {pkg => _pkg.dev}/wire/util/binaryReader.go | 0 {pkg => _pkg.dev}/wire/util/binaryWriter.go | 0 {pkg => _pkg.dev}/wire/util/fixed8/fixed8.go | 0 {pkg => _pkg.dev}/wire/util/fixed8/fixed8_test.go | 0 {pkg => _pkg.dev}/wire/util/io/io.go | 0 {pkg => _pkg.dev}/wire/util/ip/ip.go | 0 {pkg => _pkg.dev}/wire/util/slice/slice.go | 0 {pkg => _pkg.dev}/wire/util/slice/slice_test.go | 0 {pkg => _pkg.dev}/wire/util/uint160.go | 0 {pkg => _pkg.dev}/wire/util/uint160_test.go | 0 {pkg => _pkg.dev}/wire/util/uint256.go | 0 {pkg => _pkg.dev}/wire/util/uint256_test.go | 0 {pkg => _pkg.dev}/wire/util/util.go | 0 183 files changed, 0 insertions(+), 0 deletions(-) rename {pkg => _pkg.dev}/Readme.md (100%) rename {pkg => _pkg.dev}/chain/chain.go (100%) rename {pkg => _pkg.dev}/chain/chaindb.go (100%) rename {pkg => _pkg.dev}/chain/chaindb_test.go (100%) rename {pkg => _pkg.dev}/chain/errors.go (100%) rename {pkg => _pkg.dev}/chaincfg/chaincfg.go (100%) rename {pkg => _pkg.dev}/chaincfg/chaincfg_test.go (100%) rename {pkg => _pkg.dev}/connmgr/config.go (100%) rename {pkg => _pkg.dev}/connmgr/connmgr.go (100%) rename {pkg => _pkg.dev}/connmgr/connmgr_test.go (100%) rename {pkg => _pkg.dev}/connmgr/readme.md (100%) rename {pkg => _pkg.dev}/connmgr/request.go (100%) rename {pkg => _pkg.dev}/crypto/aes/aes256.go (100%) rename {pkg => _pkg.dev}/crypto/base58/base58.go (100%) rename {pkg => _pkg.dev}/crypto/base58/base58_test.go (100%) rename {pkg => _pkg.dev}/crypto/elliptic/Readme.md (100%) rename {pkg => _pkg.dev}/crypto/elliptic/curves.go (100%) rename {pkg => _pkg.dev}/crypto/elliptic/elliptic.go (100%) rename {pkg => _pkg.dev}/crypto/elliptic/elliptic_test.go (100%) rename {pkg => _pkg.dev}/crypto/hash/hash.go (100%) rename {pkg => _pkg.dev}/crypto/hash/hash_test.go (100%) rename {pkg => _pkg.dev}/crypto/privatekey/privatekey.go (100%) rename {pkg => _pkg.dev}/crypto/privatekey/privatekey_test.go (100%) rename {pkg => _pkg.dev}/crypto/publickey/TestHelper/helper.go (100%) rename {pkg => _pkg.dev}/crypto/publickey/TestHelper/helper_test.go (100%) rename {pkg => _pkg.dev}/crypto/publickey/publickey.go (100%) rename {pkg => _pkg.dev}/crypto/publickey/publickey_test.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/LICENSE (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/dsa.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/dsa_test.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/ecdsa.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/ecdsa_test.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/example_test.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/rfc6979.go (100%) rename {pkg => _pkg.dev}/crypto/rfc6979/rfc6979_test.go (100%) rename {pkg => _pkg.dev}/database/leveldb.go (100%) rename {pkg => _pkg.dev}/database/leveldb_test.go (100%) rename {pkg => _pkg.dev}/database/table.go (100%) rename {pkg => _pkg.dev}/peer/config.go (100%) rename {pkg => _pkg.dev}/peer/peer.go (100%) rename {pkg => _pkg.dev}/peer/peer_test.go (100%) rename {pkg => _pkg.dev}/peer/peerhandshake.go (100%) rename {pkg => _pkg.dev}/peer/readme.md (100%) rename {pkg => _pkg.dev}/peer/responsehandlers.go (100%) rename {pkg => _pkg.dev}/peer/stall/stall.go (100%) rename {pkg => _pkg.dev}/peer/stall/stall_test.go (100%) rename {pkg => _pkg.dev}/peermgr/blockcache.go (100%) rename {pkg => _pkg.dev}/peermgr/blockcache_test.go (100%) rename {pkg => _pkg.dev}/peermgr/peermgr.go (100%) rename {pkg => _pkg.dev}/peermgr/peermgr_test.go (100%) rename {pkg => _pkg.dev}/server/addrmgr.go (100%) rename {pkg => _pkg.dev}/server/chain.go (100%) rename {pkg => _pkg.dev}/server/connmgr.go (100%) rename {pkg => _pkg.dev}/server/database.go (100%) rename {pkg => _pkg.dev}/server/peerconfig.go (100%) rename {pkg => _pkg.dev}/server/peermgr.go (100%) rename {pkg => _pkg.dev}/server/server.go (100%) rename {pkg => _pkg.dev}/server/syncmgr.go (100%) rename {pkg => _pkg.dev}/syncmgr/blockmode.go (100%) rename {pkg => _pkg.dev}/syncmgr/blockpool.go (100%) rename {pkg => _pkg.dev}/syncmgr/blockpool_test.go (100%) rename {pkg => _pkg.dev}/syncmgr/config.go (100%) rename {pkg => _pkg.dev}/syncmgr/headermode.go (100%) rename {pkg => _pkg.dev}/syncmgr/mockhelpers_test.go (100%) rename {pkg => _pkg.dev}/syncmgr/normalmode.go (100%) rename {pkg => _pkg.dev}/syncmgr/syncmgr.go (100%) rename {pkg => _pkg.dev}/syncmgr/syncmgr_onblock_test.go (100%) rename {pkg => _pkg.dev}/syncmgr/syncmgr_onheaders_test.go (100%) rename {pkg => _pkg.dev}/vm/csharp-interop-test/push/pushbytes1.json (100%) rename {pkg => _pkg.dev}/vm/csharp-interop-test/readme.md (100%) rename {pkg => _pkg.dev}/vm/csharp-interop-test/testStruct.go (100%) rename {pkg => _pkg.dev}/vm/stack/Int.go (100%) rename {pkg => _pkg.dev}/vm/stack/Readme.md (100%) rename {pkg => _pkg.dev}/vm/stack/array.go (100%) rename {pkg => _pkg.dev}/vm/stack/array_test.go (100%) rename {pkg => _pkg.dev}/vm/stack/boolean.go (100%) rename {pkg => _pkg.dev}/vm/stack/builder.go (100%) rename {pkg => _pkg.dev}/vm/stack/bytearray.go (100%) rename {pkg => _pkg.dev}/vm/stack/context.go (100%) rename {pkg => _pkg.dev}/vm/stack/instruction.go (100%) rename {pkg => _pkg.dev}/vm/stack/int_test.go (100%) rename {pkg => _pkg.dev}/vm/stack/invocationstack.go (100%) rename {pkg => _pkg.dev}/vm/stack/map.go (100%) rename {pkg => _pkg.dev}/vm/stack/map_test.go (100%) rename {pkg => _pkg.dev}/vm/stack/stack.go (100%) rename {pkg => _pkg.dev}/vm/stack/stack_test.go (100%) rename {pkg => _pkg.dev}/vm/stack/stackitem.go (100%) rename {pkg => _pkg.dev}/vm/stack/stackitem_test.go (100%) rename {pkg => _pkg.dev}/vm/stack/test_helper.go (100%) rename {pkg => _pkg.dev}/vm/state.go (100%) rename {pkg => _pkg.dev}/vm/vm.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_bitwise.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_bitwise_test.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_exceptions.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_flow.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_flow_test.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_maths.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_maths_test.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_stackmani.go (100%) rename {pkg => _pkg.dev}/vm/vm_ops_stackmani_test.go (100%) rename {pkg => _pkg.dev}/vm/vm_test.go (100%) rename {pkg => _pkg.dev}/vm/vmopscrypto.go (100%) rename {pkg => _pkg.dev}/vm/vmopscrypto_test.go (100%) rename {pkg => _pkg.dev}/wire/Readme.md (100%) rename {pkg => _pkg.dev}/wire/base.go (100%) rename {pkg => _pkg.dev}/wire/command/command.go (100%) rename {pkg => _pkg.dev}/wire/message.go (100%) rename {pkg => _pkg.dev}/wire/message_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/block.go (100%) rename {pkg => _pkg.dev}/wire/payload/block_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/blockbase.go (100%) rename {pkg => _pkg.dev}/wire/payload/blockbase_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/maddr.go (100%) rename {pkg => _pkg.dev}/wire/payload/maddr_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mblock.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetaddr.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetaddr_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetblocks.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetblocks_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetdata.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetdata_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetheaders.go (100%) rename {pkg => _pkg.dev}/wire/payload/mgetheaders_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mheaders.go (100%) rename {pkg => _pkg.dev}/wire/payload/mheaders_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/minventory.go (100%) rename {pkg => _pkg.dev}/wire/payload/minventory_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mmempool.go (100%) rename {pkg => _pkg.dev}/wire/payload/mtx.go (100%) rename {pkg => _pkg.dev}/wire/payload/mverack.go (100%) rename {pkg => _pkg.dev}/wire/payload/mverack_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/mversion.go (100%) rename {pkg => _pkg.dev}/wire/payload/mversion_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/net_addr.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/Attribute.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/Input.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/Output.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/Witness.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/assettype.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/attr_usage.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/base.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/claim.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/claim_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/contract.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/contract_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/enrollment.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/enrollment_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/invocation.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/invocation_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/issue.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/issue_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/miner.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/miner_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/paramtype.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/publickey.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/publish.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/publish_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/register.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/register_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/state.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/state_test.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/statedescriptor.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/types/types.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/util.go (100%) rename {pkg => _pkg.dev}/wire/payload/transaction/version/version.go (100%) rename {pkg => _pkg.dev}/wire/protocol/protocol.go (100%) rename {pkg => _pkg.dev}/wire/util/Checksum/checksum.go (100%) rename {pkg => _pkg.dev}/wire/util/address/address.go (100%) rename {pkg => _pkg.dev}/wire/util/address/address_test.go (100%) rename {pkg => _pkg.dev}/wire/util/binaryReader.go (100%) rename {pkg => _pkg.dev}/wire/util/binaryWriter.go (100%) rename {pkg => _pkg.dev}/wire/util/fixed8/fixed8.go (100%) rename {pkg => _pkg.dev}/wire/util/fixed8/fixed8_test.go (100%) rename {pkg => _pkg.dev}/wire/util/io/io.go (100%) rename {pkg => _pkg.dev}/wire/util/ip/ip.go (100%) rename {pkg => _pkg.dev}/wire/util/slice/slice.go (100%) rename {pkg => _pkg.dev}/wire/util/slice/slice_test.go (100%) rename {pkg => _pkg.dev}/wire/util/uint160.go (100%) rename {pkg => _pkg.dev}/wire/util/uint160_test.go (100%) rename {pkg => _pkg.dev}/wire/util/uint256.go (100%) rename {pkg => _pkg.dev}/wire/util/uint256_test.go (100%) rename {pkg => _pkg.dev}/wire/util/util.go (100%) diff --git a/pkg/Readme.md b/_pkg.dev/Readme.md similarity index 100% rename from pkg/Readme.md rename to _pkg.dev/Readme.md diff --git a/pkg/chain/chain.go b/_pkg.dev/chain/chain.go similarity index 100% rename from pkg/chain/chain.go rename to _pkg.dev/chain/chain.go diff --git a/pkg/chain/chaindb.go b/_pkg.dev/chain/chaindb.go similarity index 100% rename from pkg/chain/chaindb.go rename to _pkg.dev/chain/chaindb.go diff --git a/pkg/chain/chaindb_test.go b/_pkg.dev/chain/chaindb_test.go similarity index 100% rename from pkg/chain/chaindb_test.go rename to _pkg.dev/chain/chaindb_test.go diff --git a/pkg/chain/errors.go b/_pkg.dev/chain/errors.go similarity index 100% rename from pkg/chain/errors.go rename to _pkg.dev/chain/errors.go diff --git a/pkg/chaincfg/chaincfg.go b/_pkg.dev/chaincfg/chaincfg.go similarity index 100% rename from pkg/chaincfg/chaincfg.go rename to _pkg.dev/chaincfg/chaincfg.go diff --git a/pkg/chaincfg/chaincfg_test.go b/_pkg.dev/chaincfg/chaincfg_test.go similarity index 100% rename from pkg/chaincfg/chaincfg_test.go rename to _pkg.dev/chaincfg/chaincfg_test.go diff --git a/pkg/connmgr/config.go b/_pkg.dev/connmgr/config.go similarity index 100% rename from pkg/connmgr/config.go rename to _pkg.dev/connmgr/config.go diff --git a/pkg/connmgr/connmgr.go b/_pkg.dev/connmgr/connmgr.go similarity index 100% rename from pkg/connmgr/connmgr.go rename to _pkg.dev/connmgr/connmgr.go diff --git a/pkg/connmgr/connmgr_test.go b/_pkg.dev/connmgr/connmgr_test.go similarity index 100% rename from pkg/connmgr/connmgr_test.go rename to _pkg.dev/connmgr/connmgr_test.go diff --git a/pkg/connmgr/readme.md b/_pkg.dev/connmgr/readme.md similarity index 100% rename from pkg/connmgr/readme.md rename to _pkg.dev/connmgr/readme.md diff --git a/pkg/connmgr/request.go b/_pkg.dev/connmgr/request.go similarity index 100% rename from pkg/connmgr/request.go rename to _pkg.dev/connmgr/request.go diff --git a/pkg/crypto/aes/aes256.go b/_pkg.dev/crypto/aes/aes256.go similarity index 100% rename from pkg/crypto/aes/aes256.go rename to _pkg.dev/crypto/aes/aes256.go diff --git a/pkg/crypto/base58/base58.go b/_pkg.dev/crypto/base58/base58.go similarity index 100% rename from pkg/crypto/base58/base58.go rename to _pkg.dev/crypto/base58/base58.go diff --git a/pkg/crypto/base58/base58_test.go b/_pkg.dev/crypto/base58/base58_test.go similarity index 100% rename from pkg/crypto/base58/base58_test.go rename to _pkg.dev/crypto/base58/base58_test.go diff --git a/pkg/crypto/elliptic/Readme.md b/_pkg.dev/crypto/elliptic/Readme.md similarity index 100% rename from pkg/crypto/elliptic/Readme.md rename to _pkg.dev/crypto/elliptic/Readme.md diff --git a/pkg/crypto/elliptic/curves.go b/_pkg.dev/crypto/elliptic/curves.go similarity index 100% rename from pkg/crypto/elliptic/curves.go rename to _pkg.dev/crypto/elliptic/curves.go diff --git a/pkg/crypto/elliptic/elliptic.go b/_pkg.dev/crypto/elliptic/elliptic.go similarity index 100% rename from pkg/crypto/elliptic/elliptic.go rename to _pkg.dev/crypto/elliptic/elliptic.go diff --git a/pkg/crypto/elliptic/elliptic_test.go b/_pkg.dev/crypto/elliptic/elliptic_test.go similarity index 100% rename from pkg/crypto/elliptic/elliptic_test.go rename to _pkg.dev/crypto/elliptic/elliptic_test.go diff --git a/pkg/crypto/hash/hash.go b/_pkg.dev/crypto/hash/hash.go similarity index 100% rename from pkg/crypto/hash/hash.go rename to _pkg.dev/crypto/hash/hash.go diff --git a/pkg/crypto/hash/hash_test.go b/_pkg.dev/crypto/hash/hash_test.go similarity index 100% rename from pkg/crypto/hash/hash_test.go rename to _pkg.dev/crypto/hash/hash_test.go diff --git a/pkg/crypto/privatekey/privatekey.go b/_pkg.dev/crypto/privatekey/privatekey.go similarity index 100% rename from pkg/crypto/privatekey/privatekey.go rename to _pkg.dev/crypto/privatekey/privatekey.go diff --git a/pkg/crypto/privatekey/privatekey_test.go b/_pkg.dev/crypto/privatekey/privatekey_test.go similarity index 100% rename from pkg/crypto/privatekey/privatekey_test.go rename to _pkg.dev/crypto/privatekey/privatekey_test.go diff --git a/pkg/crypto/publickey/TestHelper/helper.go b/_pkg.dev/crypto/publickey/TestHelper/helper.go similarity index 100% rename from pkg/crypto/publickey/TestHelper/helper.go rename to _pkg.dev/crypto/publickey/TestHelper/helper.go diff --git a/pkg/crypto/publickey/TestHelper/helper_test.go b/_pkg.dev/crypto/publickey/TestHelper/helper_test.go similarity index 100% rename from pkg/crypto/publickey/TestHelper/helper_test.go rename to _pkg.dev/crypto/publickey/TestHelper/helper_test.go diff --git a/pkg/crypto/publickey/publickey.go b/_pkg.dev/crypto/publickey/publickey.go similarity index 100% rename from pkg/crypto/publickey/publickey.go rename to _pkg.dev/crypto/publickey/publickey.go diff --git a/pkg/crypto/publickey/publickey_test.go b/_pkg.dev/crypto/publickey/publickey_test.go similarity index 100% rename from pkg/crypto/publickey/publickey_test.go rename to _pkg.dev/crypto/publickey/publickey_test.go diff --git a/pkg/crypto/rfc6979/LICENSE b/_pkg.dev/crypto/rfc6979/LICENSE similarity index 100% rename from pkg/crypto/rfc6979/LICENSE rename to _pkg.dev/crypto/rfc6979/LICENSE diff --git a/pkg/crypto/rfc6979/dsa.go b/_pkg.dev/crypto/rfc6979/dsa.go similarity index 100% rename from pkg/crypto/rfc6979/dsa.go rename to _pkg.dev/crypto/rfc6979/dsa.go diff --git a/pkg/crypto/rfc6979/dsa_test.go b/_pkg.dev/crypto/rfc6979/dsa_test.go similarity index 100% rename from pkg/crypto/rfc6979/dsa_test.go rename to _pkg.dev/crypto/rfc6979/dsa_test.go diff --git a/pkg/crypto/rfc6979/ecdsa.go b/_pkg.dev/crypto/rfc6979/ecdsa.go similarity index 100% rename from pkg/crypto/rfc6979/ecdsa.go rename to _pkg.dev/crypto/rfc6979/ecdsa.go diff --git a/pkg/crypto/rfc6979/ecdsa_test.go b/_pkg.dev/crypto/rfc6979/ecdsa_test.go similarity index 100% rename from pkg/crypto/rfc6979/ecdsa_test.go rename to _pkg.dev/crypto/rfc6979/ecdsa_test.go diff --git a/pkg/crypto/rfc6979/example_test.go b/_pkg.dev/crypto/rfc6979/example_test.go similarity index 100% rename from pkg/crypto/rfc6979/example_test.go rename to _pkg.dev/crypto/rfc6979/example_test.go diff --git a/pkg/crypto/rfc6979/rfc6979.go b/_pkg.dev/crypto/rfc6979/rfc6979.go similarity index 100% rename from pkg/crypto/rfc6979/rfc6979.go rename to _pkg.dev/crypto/rfc6979/rfc6979.go diff --git a/pkg/crypto/rfc6979/rfc6979_test.go b/_pkg.dev/crypto/rfc6979/rfc6979_test.go similarity index 100% rename from pkg/crypto/rfc6979/rfc6979_test.go rename to _pkg.dev/crypto/rfc6979/rfc6979_test.go diff --git a/pkg/database/leveldb.go b/_pkg.dev/database/leveldb.go similarity index 100% rename from pkg/database/leveldb.go rename to _pkg.dev/database/leveldb.go diff --git a/pkg/database/leveldb_test.go b/_pkg.dev/database/leveldb_test.go similarity index 100% rename from pkg/database/leveldb_test.go rename to _pkg.dev/database/leveldb_test.go diff --git a/pkg/database/table.go b/_pkg.dev/database/table.go similarity index 100% rename from pkg/database/table.go rename to _pkg.dev/database/table.go diff --git a/pkg/peer/config.go b/_pkg.dev/peer/config.go similarity index 100% rename from pkg/peer/config.go rename to _pkg.dev/peer/config.go diff --git a/pkg/peer/peer.go b/_pkg.dev/peer/peer.go similarity index 100% rename from pkg/peer/peer.go rename to _pkg.dev/peer/peer.go diff --git a/pkg/peer/peer_test.go b/_pkg.dev/peer/peer_test.go similarity index 100% rename from pkg/peer/peer_test.go rename to _pkg.dev/peer/peer_test.go diff --git a/pkg/peer/peerhandshake.go b/_pkg.dev/peer/peerhandshake.go similarity index 100% rename from pkg/peer/peerhandshake.go rename to _pkg.dev/peer/peerhandshake.go diff --git a/pkg/peer/readme.md b/_pkg.dev/peer/readme.md similarity index 100% rename from pkg/peer/readme.md rename to _pkg.dev/peer/readme.md diff --git a/pkg/peer/responsehandlers.go b/_pkg.dev/peer/responsehandlers.go similarity index 100% rename from pkg/peer/responsehandlers.go rename to _pkg.dev/peer/responsehandlers.go diff --git a/pkg/peer/stall/stall.go b/_pkg.dev/peer/stall/stall.go similarity index 100% rename from pkg/peer/stall/stall.go rename to _pkg.dev/peer/stall/stall.go diff --git a/pkg/peer/stall/stall_test.go b/_pkg.dev/peer/stall/stall_test.go similarity index 100% rename from pkg/peer/stall/stall_test.go rename to _pkg.dev/peer/stall/stall_test.go diff --git a/pkg/peermgr/blockcache.go b/_pkg.dev/peermgr/blockcache.go similarity index 100% rename from pkg/peermgr/blockcache.go rename to _pkg.dev/peermgr/blockcache.go diff --git a/pkg/peermgr/blockcache_test.go b/_pkg.dev/peermgr/blockcache_test.go similarity index 100% rename from pkg/peermgr/blockcache_test.go rename to _pkg.dev/peermgr/blockcache_test.go diff --git a/pkg/peermgr/peermgr.go b/_pkg.dev/peermgr/peermgr.go similarity index 100% rename from pkg/peermgr/peermgr.go rename to _pkg.dev/peermgr/peermgr.go diff --git a/pkg/peermgr/peermgr_test.go b/_pkg.dev/peermgr/peermgr_test.go similarity index 100% rename from pkg/peermgr/peermgr_test.go rename to _pkg.dev/peermgr/peermgr_test.go diff --git a/pkg/server/addrmgr.go b/_pkg.dev/server/addrmgr.go similarity index 100% rename from pkg/server/addrmgr.go rename to _pkg.dev/server/addrmgr.go diff --git a/pkg/server/chain.go b/_pkg.dev/server/chain.go similarity index 100% rename from pkg/server/chain.go rename to _pkg.dev/server/chain.go diff --git a/pkg/server/connmgr.go b/_pkg.dev/server/connmgr.go similarity index 100% rename from pkg/server/connmgr.go rename to _pkg.dev/server/connmgr.go diff --git a/pkg/server/database.go b/_pkg.dev/server/database.go similarity index 100% rename from pkg/server/database.go rename to _pkg.dev/server/database.go diff --git a/pkg/server/peerconfig.go b/_pkg.dev/server/peerconfig.go similarity index 100% rename from pkg/server/peerconfig.go rename to _pkg.dev/server/peerconfig.go diff --git a/pkg/server/peermgr.go b/_pkg.dev/server/peermgr.go similarity index 100% rename from pkg/server/peermgr.go rename to _pkg.dev/server/peermgr.go diff --git a/pkg/server/server.go b/_pkg.dev/server/server.go similarity index 100% rename from pkg/server/server.go rename to _pkg.dev/server/server.go diff --git a/pkg/server/syncmgr.go b/_pkg.dev/server/syncmgr.go similarity index 100% rename from pkg/server/syncmgr.go rename to _pkg.dev/server/syncmgr.go diff --git a/pkg/syncmgr/blockmode.go b/_pkg.dev/syncmgr/blockmode.go similarity index 100% rename from pkg/syncmgr/blockmode.go rename to _pkg.dev/syncmgr/blockmode.go diff --git a/pkg/syncmgr/blockpool.go b/_pkg.dev/syncmgr/blockpool.go similarity index 100% rename from pkg/syncmgr/blockpool.go rename to _pkg.dev/syncmgr/blockpool.go diff --git a/pkg/syncmgr/blockpool_test.go b/_pkg.dev/syncmgr/blockpool_test.go similarity index 100% rename from pkg/syncmgr/blockpool_test.go rename to _pkg.dev/syncmgr/blockpool_test.go diff --git a/pkg/syncmgr/config.go b/_pkg.dev/syncmgr/config.go similarity index 100% rename from pkg/syncmgr/config.go rename to _pkg.dev/syncmgr/config.go diff --git a/pkg/syncmgr/headermode.go b/_pkg.dev/syncmgr/headermode.go similarity index 100% rename from pkg/syncmgr/headermode.go rename to _pkg.dev/syncmgr/headermode.go diff --git a/pkg/syncmgr/mockhelpers_test.go b/_pkg.dev/syncmgr/mockhelpers_test.go similarity index 100% rename from pkg/syncmgr/mockhelpers_test.go rename to _pkg.dev/syncmgr/mockhelpers_test.go diff --git a/pkg/syncmgr/normalmode.go b/_pkg.dev/syncmgr/normalmode.go similarity index 100% rename from pkg/syncmgr/normalmode.go rename to _pkg.dev/syncmgr/normalmode.go diff --git a/pkg/syncmgr/syncmgr.go b/_pkg.dev/syncmgr/syncmgr.go similarity index 100% rename from pkg/syncmgr/syncmgr.go rename to _pkg.dev/syncmgr/syncmgr.go diff --git a/pkg/syncmgr/syncmgr_onblock_test.go b/_pkg.dev/syncmgr/syncmgr_onblock_test.go similarity index 100% rename from pkg/syncmgr/syncmgr_onblock_test.go rename to _pkg.dev/syncmgr/syncmgr_onblock_test.go diff --git a/pkg/syncmgr/syncmgr_onheaders_test.go b/_pkg.dev/syncmgr/syncmgr_onheaders_test.go similarity index 100% rename from pkg/syncmgr/syncmgr_onheaders_test.go rename to _pkg.dev/syncmgr/syncmgr_onheaders_test.go diff --git a/pkg/vm/csharp-interop-test/push/pushbytes1.json b/_pkg.dev/vm/csharp-interop-test/push/pushbytes1.json similarity index 100% rename from pkg/vm/csharp-interop-test/push/pushbytes1.json rename to _pkg.dev/vm/csharp-interop-test/push/pushbytes1.json diff --git a/pkg/vm/csharp-interop-test/readme.md b/_pkg.dev/vm/csharp-interop-test/readme.md similarity index 100% rename from pkg/vm/csharp-interop-test/readme.md rename to _pkg.dev/vm/csharp-interop-test/readme.md diff --git a/pkg/vm/csharp-interop-test/testStruct.go b/_pkg.dev/vm/csharp-interop-test/testStruct.go similarity index 100% rename from pkg/vm/csharp-interop-test/testStruct.go rename to _pkg.dev/vm/csharp-interop-test/testStruct.go diff --git a/pkg/vm/stack/Int.go b/_pkg.dev/vm/stack/Int.go similarity index 100% rename from pkg/vm/stack/Int.go rename to _pkg.dev/vm/stack/Int.go diff --git a/pkg/vm/stack/Readme.md b/_pkg.dev/vm/stack/Readme.md similarity index 100% rename from pkg/vm/stack/Readme.md rename to _pkg.dev/vm/stack/Readme.md diff --git a/pkg/vm/stack/array.go b/_pkg.dev/vm/stack/array.go similarity index 100% rename from pkg/vm/stack/array.go rename to _pkg.dev/vm/stack/array.go diff --git a/pkg/vm/stack/array_test.go b/_pkg.dev/vm/stack/array_test.go similarity index 100% rename from pkg/vm/stack/array_test.go rename to _pkg.dev/vm/stack/array_test.go diff --git a/pkg/vm/stack/boolean.go b/_pkg.dev/vm/stack/boolean.go similarity index 100% rename from pkg/vm/stack/boolean.go rename to _pkg.dev/vm/stack/boolean.go diff --git a/pkg/vm/stack/builder.go b/_pkg.dev/vm/stack/builder.go similarity index 100% rename from pkg/vm/stack/builder.go rename to _pkg.dev/vm/stack/builder.go diff --git a/pkg/vm/stack/bytearray.go b/_pkg.dev/vm/stack/bytearray.go similarity index 100% rename from pkg/vm/stack/bytearray.go rename to _pkg.dev/vm/stack/bytearray.go diff --git a/pkg/vm/stack/context.go b/_pkg.dev/vm/stack/context.go similarity index 100% rename from pkg/vm/stack/context.go rename to _pkg.dev/vm/stack/context.go diff --git a/pkg/vm/stack/instruction.go b/_pkg.dev/vm/stack/instruction.go similarity index 100% rename from pkg/vm/stack/instruction.go rename to _pkg.dev/vm/stack/instruction.go diff --git a/pkg/vm/stack/int_test.go b/_pkg.dev/vm/stack/int_test.go similarity index 100% rename from pkg/vm/stack/int_test.go rename to _pkg.dev/vm/stack/int_test.go diff --git a/pkg/vm/stack/invocationstack.go b/_pkg.dev/vm/stack/invocationstack.go similarity index 100% rename from pkg/vm/stack/invocationstack.go rename to _pkg.dev/vm/stack/invocationstack.go diff --git a/pkg/vm/stack/map.go b/_pkg.dev/vm/stack/map.go similarity index 100% rename from pkg/vm/stack/map.go rename to _pkg.dev/vm/stack/map.go diff --git a/pkg/vm/stack/map_test.go b/_pkg.dev/vm/stack/map_test.go similarity index 100% rename from pkg/vm/stack/map_test.go rename to _pkg.dev/vm/stack/map_test.go diff --git a/pkg/vm/stack/stack.go b/_pkg.dev/vm/stack/stack.go similarity index 100% rename from pkg/vm/stack/stack.go rename to _pkg.dev/vm/stack/stack.go diff --git a/pkg/vm/stack/stack_test.go b/_pkg.dev/vm/stack/stack_test.go similarity index 100% rename from pkg/vm/stack/stack_test.go rename to _pkg.dev/vm/stack/stack_test.go diff --git a/pkg/vm/stack/stackitem.go b/_pkg.dev/vm/stack/stackitem.go similarity index 100% rename from pkg/vm/stack/stackitem.go rename to _pkg.dev/vm/stack/stackitem.go diff --git a/pkg/vm/stack/stackitem_test.go b/_pkg.dev/vm/stack/stackitem_test.go similarity index 100% rename from pkg/vm/stack/stackitem_test.go rename to _pkg.dev/vm/stack/stackitem_test.go diff --git a/pkg/vm/stack/test_helper.go b/_pkg.dev/vm/stack/test_helper.go similarity index 100% rename from pkg/vm/stack/test_helper.go rename to _pkg.dev/vm/stack/test_helper.go diff --git a/pkg/vm/state.go b/_pkg.dev/vm/state.go similarity index 100% rename from pkg/vm/state.go rename to _pkg.dev/vm/state.go diff --git a/pkg/vm/vm.go b/_pkg.dev/vm/vm.go similarity index 100% rename from pkg/vm/vm.go rename to _pkg.dev/vm/vm.go diff --git a/pkg/vm/vm_ops.go b/_pkg.dev/vm/vm_ops.go similarity index 100% rename from pkg/vm/vm_ops.go rename to _pkg.dev/vm/vm_ops.go diff --git a/pkg/vm/vm_ops_bitwise.go b/_pkg.dev/vm/vm_ops_bitwise.go similarity index 100% rename from pkg/vm/vm_ops_bitwise.go rename to _pkg.dev/vm/vm_ops_bitwise.go diff --git a/pkg/vm/vm_ops_bitwise_test.go b/_pkg.dev/vm/vm_ops_bitwise_test.go similarity index 100% rename from pkg/vm/vm_ops_bitwise_test.go rename to _pkg.dev/vm/vm_ops_bitwise_test.go diff --git a/pkg/vm/vm_ops_exceptions.go b/_pkg.dev/vm/vm_ops_exceptions.go similarity index 100% rename from pkg/vm/vm_ops_exceptions.go rename to _pkg.dev/vm/vm_ops_exceptions.go diff --git a/pkg/vm/vm_ops_flow.go b/_pkg.dev/vm/vm_ops_flow.go similarity index 100% rename from pkg/vm/vm_ops_flow.go rename to _pkg.dev/vm/vm_ops_flow.go diff --git a/pkg/vm/vm_ops_flow_test.go b/_pkg.dev/vm/vm_ops_flow_test.go similarity index 100% rename from pkg/vm/vm_ops_flow_test.go rename to _pkg.dev/vm/vm_ops_flow_test.go diff --git a/pkg/vm/vm_ops_maths.go b/_pkg.dev/vm/vm_ops_maths.go similarity index 100% rename from pkg/vm/vm_ops_maths.go rename to _pkg.dev/vm/vm_ops_maths.go diff --git a/pkg/vm/vm_ops_maths_test.go b/_pkg.dev/vm/vm_ops_maths_test.go similarity index 100% rename from pkg/vm/vm_ops_maths_test.go rename to _pkg.dev/vm/vm_ops_maths_test.go diff --git a/pkg/vm/vm_ops_stackmani.go b/_pkg.dev/vm/vm_ops_stackmani.go similarity index 100% rename from pkg/vm/vm_ops_stackmani.go rename to _pkg.dev/vm/vm_ops_stackmani.go diff --git a/pkg/vm/vm_ops_stackmani_test.go b/_pkg.dev/vm/vm_ops_stackmani_test.go similarity index 100% rename from pkg/vm/vm_ops_stackmani_test.go rename to _pkg.dev/vm/vm_ops_stackmani_test.go diff --git a/pkg/vm/vm_test.go b/_pkg.dev/vm/vm_test.go similarity index 100% rename from pkg/vm/vm_test.go rename to _pkg.dev/vm/vm_test.go diff --git a/pkg/vm/vmopscrypto.go b/_pkg.dev/vm/vmopscrypto.go similarity index 100% rename from pkg/vm/vmopscrypto.go rename to _pkg.dev/vm/vmopscrypto.go diff --git a/pkg/vm/vmopscrypto_test.go b/_pkg.dev/vm/vmopscrypto_test.go similarity index 100% rename from pkg/vm/vmopscrypto_test.go rename to _pkg.dev/vm/vmopscrypto_test.go diff --git a/pkg/wire/Readme.md b/_pkg.dev/wire/Readme.md similarity index 100% rename from pkg/wire/Readme.md rename to _pkg.dev/wire/Readme.md diff --git a/pkg/wire/base.go b/_pkg.dev/wire/base.go similarity index 100% rename from pkg/wire/base.go rename to _pkg.dev/wire/base.go diff --git a/pkg/wire/command/command.go b/_pkg.dev/wire/command/command.go similarity index 100% rename from pkg/wire/command/command.go rename to _pkg.dev/wire/command/command.go diff --git a/pkg/wire/message.go b/_pkg.dev/wire/message.go similarity index 100% rename from pkg/wire/message.go rename to _pkg.dev/wire/message.go diff --git a/pkg/wire/message_test.go b/_pkg.dev/wire/message_test.go similarity index 100% rename from pkg/wire/message_test.go rename to _pkg.dev/wire/message_test.go diff --git a/pkg/wire/payload/block.go b/_pkg.dev/wire/payload/block.go similarity index 100% rename from pkg/wire/payload/block.go rename to _pkg.dev/wire/payload/block.go diff --git a/pkg/wire/payload/block_test.go b/_pkg.dev/wire/payload/block_test.go similarity index 100% rename from pkg/wire/payload/block_test.go rename to _pkg.dev/wire/payload/block_test.go diff --git a/pkg/wire/payload/blockbase.go b/_pkg.dev/wire/payload/blockbase.go similarity index 100% rename from pkg/wire/payload/blockbase.go rename to _pkg.dev/wire/payload/blockbase.go diff --git a/pkg/wire/payload/blockbase_test.go b/_pkg.dev/wire/payload/blockbase_test.go similarity index 100% rename from pkg/wire/payload/blockbase_test.go rename to _pkg.dev/wire/payload/blockbase_test.go diff --git a/pkg/wire/payload/maddr.go b/_pkg.dev/wire/payload/maddr.go similarity index 100% rename from pkg/wire/payload/maddr.go rename to _pkg.dev/wire/payload/maddr.go diff --git a/pkg/wire/payload/maddr_test.go b/_pkg.dev/wire/payload/maddr_test.go similarity index 100% rename from pkg/wire/payload/maddr_test.go rename to _pkg.dev/wire/payload/maddr_test.go diff --git a/pkg/wire/payload/mblock.go b/_pkg.dev/wire/payload/mblock.go similarity index 100% rename from pkg/wire/payload/mblock.go rename to _pkg.dev/wire/payload/mblock.go diff --git a/pkg/wire/payload/mgetaddr.go b/_pkg.dev/wire/payload/mgetaddr.go similarity index 100% rename from pkg/wire/payload/mgetaddr.go rename to _pkg.dev/wire/payload/mgetaddr.go diff --git a/pkg/wire/payload/mgetaddr_test.go b/_pkg.dev/wire/payload/mgetaddr_test.go similarity index 100% rename from pkg/wire/payload/mgetaddr_test.go rename to _pkg.dev/wire/payload/mgetaddr_test.go diff --git a/pkg/wire/payload/mgetblocks.go b/_pkg.dev/wire/payload/mgetblocks.go similarity index 100% rename from pkg/wire/payload/mgetblocks.go rename to _pkg.dev/wire/payload/mgetblocks.go diff --git a/pkg/wire/payload/mgetblocks_test.go b/_pkg.dev/wire/payload/mgetblocks_test.go similarity index 100% rename from pkg/wire/payload/mgetblocks_test.go rename to _pkg.dev/wire/payload/mgetblocks_test.go diff --git a/pkg/wire/payload/mgetdata.go b/_pkg.dev/wire/payload/mgetdata.go similarity index 100% rename from pkg/wire/payload/mgetdata.go rename to _pkg.dev/wire/payload/mgetdata.go diff --git a/pkg/wire/payload/mgetdata_test.go b/_pkg.dev/wire/payload/mgetdata_test.go similarity index 100% rename from pkg/wire/payload/mgetdata_test.go rename to _pkg.dev/wire/payload/mgetdata_test.go diff --git a/pkg/wire/payload/mgetheaders.go b/_pkg.dev/wire/payload/mgetheaders.go similarity index 100% rename from pkg/wire/payload/mgetheaders.go rename to _pkg.dev/wire/payload/mgetheaders.go diff --git a/pkg/wire/payload/mgetheaders_test.go b/_pkg.dev/wire/payload/mgetheaders_test.go similarity index 100% rename from pkg/wire/payload/mgetheaders_test.go rename to _pkg.dev/wire/payload/mgetheaders_test.go diff --git a/pkg/wire/payload/mheaders.go b/_pkg.dev/wire/payload/mheaders.go similarity index 100% rename from pkg/wire/payload/mheaders.go rename to _pkg.dev/wire/payload/mheaders.go diff --git a/pkg/wire/payload/mheaders_test.go b/_pkg.dev/wire/payload/mheaders_test.go similarity index 100% rename from pkg/wire/payload/mheaders_test.go rename to _pkg.dev/wire/payload/mheaders_test.go diff --git a/pkg/wire/payload/minventory.go b/_pkg.dev/wire/payload/minventory.go similarity index 100% rename from pkg/wire/payload/minventory.go rename to _pkg.dev/wire/payload/minventory.go diff --git a/pkg/wire/payload/minventory_test.go b/_pkg.dev/wire/payload/minventory_test.go similarity index 100% rename from pkg/wire/payload/minventory_test.go rename to _pkg.dev/wire/payload/minventory_test.go diff --git a/pkg/wire/payload/mmempool.go b/_pkg.dev/wire/payload/mmempool.go similarity index 100% rename from pkg/wire/payload/mmempool.go rename to _pkg.dev/wire/payload/mmempool.go diff --git a/pkg/wire/payload/mtx.go b/_pkg.dev/wire/payload/mtx.go similarity index 100% rename from pkg/wire/payload/mtx.go rename to _pkg.dev/wire/payload/mtx.go diff --git a/pkg/wire/payload/mverack.go b/_pkg.dev/wire/payload/mverack.go similarity index 100% rename from pkg/wire/payload/mverack.go rename to _pkg.dev/wire/payload/mverack.go diff --git a/pkg/wire/payload/mverack_test.go b/_pkg.dev/wire/payload/mverack_test.go similarity index 100% rename from pkg/wire/payload/mverack_test.go rename to _pkg.dev/wire/payload/mverack_test.go diff --git a/pkg/wire/payload/mversion.go b/_pkg.dev/wire/payload/mversion.go similarity index 100% rename from pkg/wire/payload/mversion.go rename to _pkg.dev/wire/payload/mversion.go diff --git a/pkg/wire/payload/mversion_test.go b/_pkg.dev/wire/payload/mversion_test.go similarity index 100% rename from pkg/wire/payload/mversion_test.go rename to _pkg.dev/wire/payload/mversion_test.go diff --git a/pkg/wire/payload/net_addr.go b/_pkg.dev/wire/payload/net_addr.go similarity index 100% rename from pkg/wire/payload/net_addr.go rename to _pkg.dev/wire/payload/net_addr.go diff --git a/pkg/wire/payload/transaction/Attribute.go b/_pkg.dev/wire/payload/transaction/Attribute.go similarity index 100% rename from pkg/wire/payload/transaction/Attribute.go rename to _pkg.dev/wire/payload/transaction/Attribute.go diff --git a/pkg/wire/payload/transaction/Input.go b/_pkg.dev/wire/payload/transaction/Input.go similarity index 100% rename from pkg/wire/payload/transaction/Input.go rename to _pkg.dev/wire/payload/transaction/Input.go diff --git a/pkg/wire/payload/transaction/Output.go b/_pkg.dev/wire/payload/transaction/Output.go similarity index 100% rename from pkg/wire/payload/transaction/Output.go rename to _pkg.dev/wire/payload/transaction/Output.go diff --git a/pkg/wire/payload/transaction/Witness.go b/_pkg.dev/wire/payload/transaction/Witness.go similarity index 100% rename from pkg/wire/payload/transaction/Witness.go rename to _pkg.dev/wire/payload/transaction/Witness.go diff --git a/pkg/wire/payload/transaction/assettype.go b/_pkg.dev/wire/payload/transaction/assettype.go similarity index 100% rename from pkg/wire/payload/transaction/assettype.go rename to _pkg.dev/wire/payload/transaction/assettype.go diff --git a/pkg/wire/payload/transaction/attr_usage.go b/_pkg.dev/wire/payload/transaction/attr_usage.go similarity index 100% rename from pkg/wire/payload/transaction/attr_usage.go rename to _pkg.dev/wire/payload/transaction/attr_usage.go diff --git a/pkg/wire/payload/transaction/base.go b/_pkg.dev/wire/payload/transaction/base.go similarity index 100% rename from pkg/wire/payload/transaction/base.go rename to _pkg.dev/wire/payload/transaction/base.go diff --git a/pkg/wire/payload/transaction/claim.go b/_pkg.dev/wire/payload/transaction/claim.go similarity index 100% rename from pkg/wire/payload/transaction/claim.go rename to _pkg.dev/wire/payload/transaction/claim.go diff --git a/pkg/wire/payload/transaction/claim_test.go b/_pkg.dev/wire/payload/transaction/claim_test.go similarity index 100% rename from pkg/wire/payload/transaction/claim_test.go rename to _pkg.dev/wire/payload/transaction/claim_test.go diff --git a/pkg/wire/payload/transaction/contract.go b/_pkg.dev/wire/payload/transaction/contract.go similarity index 100% rename from pkg/wire/payload/transaction/contract.go rename to _pkg.dev/wire/payload/transaction/contract.go diff --git a/pkg/wire/payload/transaction/contract_test.go b/_pkg.dev/wire/payload/transaction/contract_test.go similarity index 100% rename from pkg/wire/payload/transaction/contract_test.go rename to _pkg.dev/wire/payload/transaction/contract_test.go diff --git a/pkg/wire/payload/transaction/enrollment.go b/_pkg.dev/wire/payload/transaction/enrollment.go similarity index 100% rename from pkg/wire/payload/transaction/enrollment.go rename to _pkg.dev/wire/payload/transaction/enrollment.go diff --git a/pkg/wire/payload/transaction/enrollment_test.go b/_pkg.dev/wire/payload/transaction/enrollment_test.go similarity index 100% rename from pkg/wire/payload/transaction/enrollment_test.go rename to _pkg.dev/wire/payload/transaction/enrollment_test.go diff --git a/pkg/wire/payload/transaction/invocation.go b/_pkg.dev/wire/payload/transaction/invocation.go similarity index 100% rename from pkg/wire/payload/transaction/invocation.go rename to _pkg.dev/wire/payload/transaction/invocation.go diff --git a/pkg/wire/payload/transaction/invocation_test.go b/_pkg.dev/wire/payload/transaction/invocation_test.go similarity index 100% rename from pkg/wire/payload/transaction/invocation_test.go rename to _pkg.dev/wire/payload/transaction/invocation_test.go diff --git a/pkg/wire/payload/transaction/issue.go b/_pkg.dev/wire/payload/transaction/issue.go similarity index 100% rename from pkg/wire/payload/transaction/issue.go rename to _pkg.dev/wire/payload/transaction/issue.go diff --git a/pkg/wire/payload/transaction/issue_test.go b/_pkg.dev/wire/payload/transaction/issue_test.go similarity index 100% rename from pkg/wire/payload/transaction/issue_test.go rename to _pkg.dev/wire/payload/transaction/issue_test.go diff --git a/pkg/wire/payload/transaction/miner.go b/_pkg.dev/wire/payload/transaction/miner.go similarity index 100% rename from pkg/wire/payload/transaction/miner.go rename to _pkg.dev/wire/payload/transaction/miner.go diff --git a/pkg/wire/payload/transaction/miner_test.go b/_pkg.dev/wire/payload/transaction/miner_test.go similarity index 100% rename from pkg/wire/payload/transaction/miner_test.go rename to _pkg.dev/wire/payload/transaction/miner_test.go diff --git a/pkg/wire/payload/transaction/paramtype.go b/_pkg.dev/wire/payload/transaction/paramtype.go similarity index 100% rename from pkg/wire/payload/transaction/paramtype.go rename to _pkg.dev/wire/payload/transaction/paramtype.go diff --git a/pkg/wire/payload/transaction/publickey.go b/_pkg.dev/wire/payload/transaction/publickey.go similarity index 100% rename from pkg/wire/payload/transaction/publickey.go rename to _pkg.dev/wire/payload/transaction/publickey.go diff --git a/pkg/wire/payload/transaction/publish.go b/_pkg.dev/wire/payload/transaction/publish.go similarity index 100% rename from pkg/wire/payload/transaction/publish.go rename to _pkg.dev/wire/payload/transaction/publish.go diff --git a/pkg/wire/payload/transaction/publish_test.go b/_pkg.dev/wire/payload/transaction/publish_test.go similarity index 100% rename from pkg/wire/payload/transaction/publish_test.go rename to _pkg.dev/wire/payload/transaction/publish_test.go diff --git a/pkg/wire/payload/transaction/register.go b/_pkg.dev/wire/payload/transaction/register.go similarity index 100% rename from pkg/wire/payload/transaction/register.go rename to _pkg.dev/wire/payload/transaction/register.go diff --git a/pkg/wire/payload/transaction/register_test.go b/_pkg.dev/wire/payload/transaction/register_test.go similarity index 100% rename from pkg/wire/payload/transaction/register_test.go rename to _pkg.dev/wire/payload/transaction/register_test.go diff --git a/pkg/wire/payload/transaction/state.go b/_pkg.dev/wire/payload/transaction/state.go similarity index 100% rename from pkg/wire/payload/transaction/state.go rename to _pkg.dev/wire/payload/transaction/state.go diff --git a/pkg/wire/payload/transaction/state_test.go b/_pkg.dev/wire/payload/transaction/state_test.go similarity index 100% rename from pkg/wire/payload/transaction/state_test.go rename to _pkg.dev/wire/payload/transaction/state_test.go diff --git a/pkg/wire/payload/transaction/statedescriptor.go b/_pkg.dev/wire/payload/transaction/statedescriptor.go similarity index 100% rename from pkg/wire/payload/transaction/statedescriptor.go rename to _pkg.dev/wire/payload/transaction/statedescriptor.go diff --git a/pkg/wire/payload/transaction/types/types.go b/_pkg.dev/wire/payload/transaction/types/types.go similarity index 100% rename from pkg/wire/payload/transaction/types/types.go rename to _pkg.dev/wire/payload/transaction/types/types.go diff --git a/pkg/wire/payload/transaction/util.go b/_pkg.dev/wire/payload/transaction/util.go similarity index 100% rename from pkg/wire/payload/transaction/util.go rename to _pkg.dev/wire/payload/transaction/util.go diff --git a/pkg/wire/payload/transaction/version/version.go b/_pkg.dev/wire/payload/transaction/version/version.go similarity index 100% rename from pkg/wire/payload/transaction/version/version.go rename to _pkg.dev/wire/payload/transaction/version/version.go diff --git a/pkg/wire/protocol/protocol.go b/_pkg.dev/wire/protocol/protocol.go similarity index 100% rename from pkg/wire/protocol/protocol.go rename to _pkg.dev/wire/protocol/protocol.go diff --git a/pkg/wire/util/Checksum/checksum.go b/_pkg.dev/wire/util/Checksum/checksum.go similarity index 100% rename from pkg/wire/util/Checksum/checksum.go rename to _pkg.dev/wire/util/Checksum/checksum.go diff --git a/pkg/wire/util/address/address.go b/_pkg.dev/wire/util/address/address.go similarity index 100% rename from pkg/wire/util/address/address.go rename to _pkg.dev/wire/util/address/address.go diff --git a/pkg/wire/util/address/address_test.go b/_pkg.dev/wire/util/address/address_test.go similarity index 100% rename from pkg/wire/util/address/address_test.go rename to _pkg.dev/wire/util/address/address_test.go diff --git a/pkg/wire/util/binaryReader.go b/_pkg.dev/wire/util/binaryReader.go similarity index 100% rename from pkg/wire/util/binaryReader.go rename to _pkg.dev/wire/util/binaryReader.go diff --git a/pkg/wire/util/binaryWriter.go b/_pkg.dev/wire/util/binaryWriter.go similarity index 100% rename from pkg/wire/util/binaryWriter.go rename to _pkg.dev/wire/util/binaryWriter.go diff --git a/pkg/wire/util/fixed8/fixed8.go b/_pkg.dev/wire/util/fixed8/fixed8.go similarity index 100% rename from pkg/wire/util/fixed8/fixed8.go rename to _pkg.dev/wire/util/fixed8/fixed8.go diff --git a/pkg/wire/util/fixed8/fixed8_test.go b/_pkg.dev/wire/util/fixed8/fixed8_test.go similarity index 100% rename from pkg/wire/util/fixed8/fixed8_test.go rename to _pkg.dev/wire/util/fixed8/fixed8_test.go diff --git a/pkg/wire/util/io/io.go b/_pkg.dev/wire/util/io/io.go similarity index 100% rename from pkg/wire/util/io/io.go rename to _pkg.dev/wire/util/io/io.go diff --git a/pkg/wire/util/ip/ip.go b/_pkg.dev/wire/util/ip/ip.go similarity index 100% rename from pkg/wire/util/ip/ip.go rename to _pkg.dev/wire/util/ip/ip.go diff --git a/pkg/wire/util/slice/slice.go b/_pkg.dev/wire/util/slice/slice.go similarity index 100% rename from pkg/wire/util/slice/slice.go rename to _pkg.dev/wire/util/slice/slice.go diff --git a/pkg/wire/util/slice/slice_test.go b/_pkg.dev/wire/util/slice/slice_test.go similarity index 100% rename from pkg/wire/util/slice/slice_test.go rename to _pkg.dev/wire/util/slice/slice_test.go diff --git a/pkg/wire/util/uint160.go b/_pkg.dev/wire/util/uint160.go similarity index 100% rename from pkg/wire/util/uint160.go rename to _pkg.dev/wire/util/uint160.go diff --git a/pkg/wire/util/uint160_test.go b/_pkg.dev/wire/util/uint160_test.go similarity index 100% rename from pkg/wire/util/uint160_test.go rename to _pkg.dev/wire/util/uint160_test.go diff --git a/pkg/wire/util/uint256.go b/_pkg.dev/wire/util/uint256.go similarity index 100% rename from pkg/wire/util/uint256.go rename to _pkg.dev/wire/util/uint256.go diff --git a/pkg/wire/util/uint256_test.go b/_pkg.dev/wire/util/uint256_test.go similarity index 100% rename from pkg/wire/util/uint256_test.go rename to _pkg.dev/wire/util/uint256_test.go diff --git a/pkg/wire/util/util.go b/_pkg.dev/wire/util/util.go similarity index 100% rename from pkg/wire/util/util.go rename to _pkg.dev/wire/util/util.go